- Build-time discovery (D-028, the Caddy pattern): a module self-registers from init(); internal/modules/imports_gen.go (go:generate) blank-imports every module directory, so THE DIRECTORY IS THE SOURCE OF TRUTH - The registry: deterministic registration order, config-backed enable/disable (D-038), required modules, name-prefixed migrations, Mount() into the door - The name-collision guard now also fails a module named for a reserved level-1 workflow (SPEC-009 §13 Q-1) — access · static · api · media · ws · dev — derived from routing.ReservedWorkflows, `home` excepted (D-2) - home: the reference dashboard module, and the core Modules page
83 lines
1.9 KiB
Go
83 lines
1.9 KiB
Go
// Command gendiscovery writes the module discovery aggregator
|
|
// (internal/modules/imports_gen.go) from the directories under
|
|
// internal/modules (D-028). It is invoked by `go generate ./internal/modules`.
|
|
//
|
|
// Usage: gendiscovery [dir] (dir defaults to ".")
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func main() {
|
|
dir := "."
|
|
if len(os.Args) > 1 {
|
|
dir = os.Args[1]
|
|
}
|
|
if err := run(dir); err != nil {
|
|
fmt.Fprintln(os.Stderr, "gendiscovery:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run(dir string) error {
|
|
abs, err := filepath.Abs(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
root, modulePath, err := findModule(abs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(root, abs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
importBase := path.Join(modulePath, filepath.ToSlash(rel))
|
|
|
|
src, err := Generate(dir, importBase)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out := filepath.Join(dir, "imports_gen.go")
|
|
if err := os.WriteFile(out, src, 0o644); err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintln(os.Stderr, "gendiscovery: wrote", out)
|
|
return nil
|
|
}
|
|
|
|
// findModule walks up from dir to the nearest go.mod and returns its directory
|
|
// and module path.
|
|
func findModule(dir string) (root, modulePath string, err error) {
|
|
cur := dir
|
|
for {
|
|
candidate := filepath.Join(cur, "go.mod")
|
|
if data, readErr := os.ReadFile(candidate); readErr == nil {
|
|
if mp := parseModulePath(string(data)); mp != "" {
|
|
return cur, mp, nil
|
|
}
|
|
return "", "", fmt.Errorf("go.mod at %s declares no module path", candidate)
|
|
}
|
|
parent := filepath.Dir(cur)
|
|
if parent == cur {
|
|
return "", "", fmt.Errorf("no go.mod found above %s", dir)
|
|
}
|
|
cur = parent
|
|
}
|
|
}
|
|
|
|
// parseModulePath extracts the `module <path>` line from a go.mod.
|
|
func parseModulePath(goMod string) string {
|
|
for _, line := range strings.Split(goMod, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if strings.HasPrefix(line, "module ") {
|
|
return strings.TrimSpace(strings.TrimPrefix(line, "module "))
|
|
}
|
|
}
|
|
return ""
|
|
}
|