diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index eb3cf8b3..a6047645 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -63,8 +63,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.25.0" - - name: Validate Reademde + go-version: "1.24" + - name: Validate README run: go build ./cmd/readmevalidation && ./readmevalidation - name: Remove build file artifact run: rm ./readmevalidation diff --git a/cmd/readmevalidation/codermodules.go b/cmd/readmevalidation/codermodules.go index fe33a7ca..3916f05b 100644 --- a/cmd/readmevalidation/codermodules.go +++ b/cmd/readmevalidation/codermodules.go @@ -14,20 +14,14 @@ var ( terraformSourceRe = regexp.MustCompile(`^\s*source\s*=\s*"([^"]+)"`) ) -func normalizeModuleName(name string) string { - // Normalize module names by replacing hyphens with underscores for comparison - // since Terraform allows both but directory names typically use hyphens - return strings.ReplaceAll(name, "-", "_") -} - -func extractNamespaceAndModuleFromPath(filePath string) (string, string, error) { - // Expected path format: registry//modules//README.md +func extractNamespaceAndModuleFromPath(filePath string) (namespace string, moduleName string, err error) { + // Expected path format: registry//modules//README.md. parts := strings.Split(filepath.Clean(filePath), string(filepath.Separator)) if len(parts) < 5 || parts[0] != "registry" || parts[2] != "modules" || parts[4] != "README.md" { return "", "", xerrors.Errorf("invalid module path format: %s", filePath) } - namespace := parts[1] - moduleName := parts[3] + namespace = parts[1] + moduleName = parts[3] return namespace, moduleName, nil } @@ -55,21 +49,21 @@ func validateModuleSourceURL(body string, filePath string) []error { isInsideTerraform = true firstTerraformBlock = false } else if isInsideTerraform { - // End of first terraform block + // End of first terraform block. break } continue } if isInsideTerraform { - // Check for any source line in the first terraform block + // Check for any source line in the first terraform block. if matches := terraformSourceRe.FindStringSubmatch(nextLine); matches != nil { actualSource := matches[1] if actualSource == expectedSource { foundCorrectSource = true break } else if strings.HasPrefix(actualSource, "registry.coder.com/") && strings.Contains(actualSource, "/"+moduleName+"/coder") { - // Found source for this module but with wrong namespace/format + // Found source for this module but with wrong namespace/format. errs = append(errs, xerrors.Errorf("incorrect source URL format: found %q, expected %q", actualSource, expectedSource)) return errs } diff --git a/cmd/readmevalidation/coderresources.go b/cmd/readmevalidation/coderresources.go index 818d73c8..0b076c2f 100644 --- a/cmd/readmevalidation/coderresources.go +++ b/cmd/readmevalidation/coderresources.go @@ -25,7 +25,7 @@ var ( terraformVersionRe = regexp.MustCompile(`^\s*\bversion\s+=`) // Matches the format "> [!INFO]". Deliberately using a broad pattern to catch formatting issues that can mess up - // the renderer for the Registry website + // the renderer for the Registry website. gfmAlertRegex = regexp.MustCompile(`^>(\s*)\[!(\w+)\](\s*)(.*)`) ) @@ -39,7 +39,7 @@ type coderResourceFrontmatter struct { } // A slice version of the struct tags from coderResourceFrontmatter. Might be worth using reflection to generate this -// list at runtime in the future, but this should be okay for now +// list at runtime in the future, but this should be okay for now. var supportedCoderResourceStructKeys = []string{ "description", "icon", "display_name", "verified", "tags", "supported_os", // TODO: This is an old, officially deprecated key from the archived coder/modules repo. We can remove this once we @@ -315,15 +315,15 @@ func validateResourceGfmAlerts(readmeBody string) []error { } // Nested GFM alerts is such a weird mistake that it's probably not really safe to keep trying to process the - // rest of the content, so this will prevent any other validations from happening for the given line + // rest of the content, so this will prevent any other validations from happening for the given line. if isInsideGfmQuotes { - errs = append(errs, errors.New("registry does not support nested GFM alerts")) + errs = append(errs, xerrors.New("registry does not support nested GFM alerts")) continue } leadingWhitespace := currentMatch[1] if len(leadingWhitespace) != 1 { - errs = append(errs, errors.New("GFM alerts must have one space between the '>' and the start of the GFM brackets")) + errs = append(errs, xerrors.New("GFM alerts must have one space between the '>' and the start of the GFM brackets")) } isInsideGfmQuotes = true @@ -347,7 +347,7 @@ func validateResourceGfmAlerts(readmeBody string) []error { } } - if gfmAlertRegex.Match([]byte(sourceLine)) { + if gfmAlertRegex.MatchString(sourceLine) { errs = append(errs, xerrors.Errorf("README has an incomplete GFM alert at the end of the file")) } diff --git a/cmd/readmevalidation/contributors.go b/cmd/readmevalidation/contributors.go index 8a9b195b..b3c8a9ff 100644 --- a/cmd/readmevalidation/contributors.go +++ b/cmd/readmevalidation/contributors.go @@ -26,7 +26,7 @@ type contributorProfileFrontmatter struct { } // A slice version of the struct tags from contributorProfileFrontmatter. Might be worth using reflection to generate -// this list at runtime in the future, but this should be okay for now +// this list at runtime in the future, but this should be okay for now. var supportedContributorProfileStructKeys = []string{"display_name", "bio", "status", "avatar", "linkedin", "github", "website", "support_email"} type contributorProfileReadme struct { diff --git a/cmd/readmevalidation/repostructure.go b/cmd/readmevalidation/repostructure.go index c77c723b..7b6d2e02 100644 --- a/cmd/readmevalidation/repostructure.go +++ b/cmd/readmevalidation/repostructure.go @@ -13,12 +13,11 @@ import ( var supportedUserNameSpaceDirectories = append(supportedResourceTypes, ".images") -// validNameRe validates that names contain only alphanumeric characters and hyphens +// validNameRe validates that names contain only alphanumeric characters and hyphens. var validNameRe = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$`) - // validateCoderResourceSubdirectory validates that the structure of a module or template within a namespace follows all -// expected file conventions +// expected file conventions. func validateCoderResourceSubdirectory(dirPath string) []error { resourceDir, err := os.Stat(dirPath) if err != nil { @@ -47,7 +46,7 @@ func validateCoderResourceSubdirectory(dirPath string) []error { continue } - // Validate module/template name + // Validate module/template name. if !validNameRe.MatchString(f.Name()) { errs = append(errs, xerrors.Errorf("%q: name contains invalid characters (only alphanumeric characters and hyphens are allowed)", path.Join(dirPath, f.Name()))) continue @@ -90,7 +89,7 @@ func validateRegistryDirectory() []error { continue } - // Validate namespace name + // Validate namespace name. if !validNameRe.MatchString(nDir.Name()) { allErrs = append(allErrs, xerrors.Errorf("%q: namespace name contains invalid characters (only alphanumeric characters and hyphens are allowed)", namespacePath)) continue @@ -136,7 +135,7 @@ func validateRegistryDirectory() []error { // validateRepoStructure validates that the structure of the repo is "correct enough" to do all necessary validation // checks. It is NOT an exhaustive validation of the entire repo structure – it only checks the parts of the repo that -// are relevant for the main validation steps +// are relevant for the main validation steps. func validateRepoStructure() error { var errs []error if vrdErrs := validateRegistryDirectory(); len(vrdErrs) != 0 { diff --git a/go.mod b/go.mod index d3caf912..4afa354e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module coder.com/coder-registry -go 1.23.2 +go 1.24 require ( cdr.dev/slog v1.6.1