Update README validation and Go version

- Downgrade Go version in CI to 1.24 for consistency.
- Fix naming and path issues in `readmevalidation` code.
- Improve regex validation for module and namespace names.
- Correct typos and improve comments for clarity.
This commit is contained in:
Muhammad Atif Ali 2025-09-01 18:36:13 +05:00
parent 5b6d878fd7
commit 3b6b1ba4b9
6 changed files with 22 additions and 29 deletions

View File

@ -63,8 +63,8 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "1.25.0" go-version: "1.24"
- name: Validate Reademde - name: Validate README
run: go build ./cmd/readmevalidation && ./readmevalidation run: go build ./cmd/readmevalidation && ./readmevalidation
- name: Remove build file artifact - name: Remove build file artifact
run: rm ./readmevalidation run: rm ./readmevalidation

View File

@ -14,20 +14,14 @@ var (
terraformSourceRe = regexp.MustCompile(`^\s*source\s*=\s*"([^"]+)"`) terraformSourceRe = regexp.MustCompile(`^\s*source\s*=\s*"([^"]+)"`)
) )
func normalizeModuleName(name string) string { func extractNamespaceAndModuleFromPath(filePath string) (namespace string, moduleName string, err error) {
// Normalize module names by replacing hyphens with underscores for comparison // Expected path format: registry/<namespace>/modules/<module-name>/README.md.
// 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/<namespace>/modules/<module-name>/README.md
parts := strings.Split(filepath.Clean(filePath), string(filepath.Separator)) parts := strings.Split(filepath.Clean(filePath), string(filepath.Separator))
if len(parts) < 5 || parts[0] != "registry" || parts[2] != "modules" || parts[4] != "README.md" { if len(parts) < 5 || parts[0] != "registry" || parts[2] != "modules" || parts[4] != "README.md" {
return "", "", xerrors.Errorf("invalid module path format: %s", filePath) return "", "", xerrors.Errorf("invalid module path format: %s", filePath)
} }
namespace := parts[1] namespace = parts[1]
moduleName := parts[3] moduleName = parts[3]
return namespace, moduleName, nil return namespace, moduleName, nil
} }
@ -55,21 +49,21 @@ func validateModuleSourceURL(body string, filePath string) []error {
isInsideTerraform = true isInsideTerraform = true
firstTerraformBlock = false firstTerraformBlock = false
} else if isInsideTerraform { } else if isInsideTerraform {
// End of first terraform block // End of first terraform block.
break break
} }
continue continue
} }
if isInsideTerraform { 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 { if matches := terraformSourceRe.FindStringSubmatch(nextLine); matches != nil {
actualSource := matches[1] actualSource := matches[1]
if actualSource == expectedSource { if actualSource == expectedSource {
foundCorrectSource = true foundCorrectSource = true
break break
} else if strings.HasPrefix(actualSource, "registry.coder.com/") && strings.Contains(actualSource, "/"+moduleName+"/coder") { } 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)) errs = append(errs, xerrors.Errorf("incorrect source URL format: found %q, expected %q", actualSource, expectedSource))
return errs return errs
} }

View File

@ -25,7 +25,7 @@ var (
terraformVersionRe = regexp.MustCompile(`^\s*\bversion\s+=`) terraformVersionRe = regexp.MustCompile(`^\s*\bversion\s+=`)
// Matches the format "> [!INFO]". Deliberately using a broad pattern to catch formatting issues that can mess up // 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*)(.*)`) 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 // 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{ var supportedCoderResourceStructKeys = []string{
"description", "icon", "display_name", "verified", "tags", "supported_os", "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 // 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 // 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 { 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 continue
} }
leadingWhitespace := currentMatch[1] leadingWhitespace := currentMatch[1]
if len(leadingWhitespace) != 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 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")) errs = append(errs, xerrors.Errorf("README has an incomplete GFM alert at the end of the file"))
} }

View File

@ -26,7 +26,7 @@ type contributorProfileFrontmatter struct {
} }
// A slice version of the struct tags from contributorProfileFrontmatter. Might be worth using reflection to generate // 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"} var supportedContributorProfileStructKeys = []string{"display_name", "bio", "status", "avatar", "linkedin", "github", "website", "support_email"}
type contributorProfileReadme struct { type contributorProfileReadme struct {

View File

@ -13,12 +13,11 @@ import (
var supportedUserNameSpaceDirectories = append(supportedResourceTypes, ".images") 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])?$`) 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 // 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 { func validateCoderResourceSubdirectory(dirPath string) []error {
resourceDir, err := os.Stat(dirPath) resourceDir, err := os.Stat(dirPath)
if err != nil { if err != nil {
@ -47,7 +46,7 @@ func validateCoderResourceSubdirectory(dirPath string) []error {
continue continue
} }
// Validate module/template name // Validate module/template name.
if !validNameRe.MatchString(f.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()))) errs = append(errs, xerrors.Errorf("%q: name contains invalid characters (only alphanumeric characters and hyphens are allowed)", path.Join(dirPath, f.Name())))
continue continue
@ -90,7 +89,7 @@ func validateRegistryDirectory() []error {
continue continue
} }
// Validate namespace name // Validate namespace name.
if !validNameRe.MatchString(nDir.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)) allErrs = append(allErrs, xerrors.Errorf("%q: namespace name contains invalid characters (only alphanumeric characters and hyphens are allowed)", namespacePath))
continue continue
@ -136,7 +135,7 @@ func validateRegistryDirectory() []error {
// validateRepoStructure validates that the structure of the repo is "correct enough" to do all necessary validation // 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 // 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 { func validateRepoStructure() error {
var errs []error var errs []error
if vrdErrs := validateRegistryDirectory(); len(vrdErrs) != 0 { if vrdErrs := validateRegistryDirectory(); len(vrdErrs) != 0 {

2
go.mod
View File

@ -1,6 +1,6 @@
module coder.com/coder-registry module coder.com/coder-registry
go 1.23.2 go 1.24
require ( require (
cdr.dev/slog v1.6.1 cdr.dev/slog v1.6.1