package main import ( "errors" "fmt" "io" "os" "path/filepath" "sort" "github.com/novox/mesh-controller/internal/catalogue" ) // moduleCheck judges manifests where they are written, with no mesh (novox/hq ADR 0037, issue 148). // // **The same functions registration runs, and nothing the command line adds** (ADR 0035): the strict // parse with every per-manifest problem, then the rules no single manifest can be judged against, // over exactly the manifests given. Somebody describing their own application in their own // repository runs this before pushing and finds out there, rather than when a running mesh refuses // the registration or, later, when a machine applies something that resolved and should not have. // // **What it cannot know without a store, it says.** The mesh's own seat set is the store's (ADR // 0122); this binary carries a compiled copy that the store overrides when loaded, so a claim on a // mesh seat is judged fully only at registration. A seat another module declares is unknown unless // that module's manifest is passed too. Both are printed as a note, not as a problem — a check that // refused what it could not see would teach people to ignore it. func moduleCheck(paths []string, out io.Writer) error { if len(paths) == 0 { return errors.New("module check ... — one file per module; pass every " + "manifest of a repository together so the rules between them are checked too") } shelf := catalogue.Shelf{} faulted := map[string]bool{} failed := 0 for _, path := range paths { raw, err := os.ReadFile(path) if err != nil { fmt.Fprintf(out, "%s: %v\n", path, err) failed++ continue } m, err := catalogue.ParseManifest(raw) if err != nil { fmt.Fprintf(out, "%s: %v\n", path, err) failed++ continue } if first, twice := shelf[m.Module]; twice { _ = first fmt.Fprintf(out, "%s: %s was already given; two manifests name one module\n", path, m.Module) failed++ continue } // A definition names no installation (novox/hq ADR 0112, ADR 0155): judged here and in the // catalogue-wide test, not yet at registration, while the declared exceptions shrink. if named := catalogue.InstallationProblems(m); len(named) > 0 { for _, p := range named { fmt.Fprintf(out, "%s: %s\n", path, p) } failed += len(named) faulted[m.Module] = true } shelf[m.Module] = m } // Between the manifests: a seat declared twice, a use of a seat nothing declares, a claim on // a seat that does not exist. Run only over what parsed, because a problem inside one manifest // has already been said and would be said again here in a worse form. problems := catalogue.CatalogueProblems(shelf) sort.Strings(problems) for _, p := range problems { fmt.Fprintln(out, p) } failed += len(problems) var names []string for name := range shelf { names = append(names, name) } sort.Strings(names) for _, name := range names { m := shelf[name] if faulted[name] { continue } fmt.Fprintf(out, "%s: ok", name) if n := len(m.Tools); n > 0 { fmt.Fprintf(out, ", %d tool(s)", n) } if len(m.Invokes) > 0 { fmt.Fprintf(out, ", invokes %s", joinInvokes(m.Invokes)) } fmt.Fprintln(out) } if failed > 0 { return fmt.Errorf("%d problem(s) in %d manifest(s)", failed, len(paths)) } fmt.Fprintf(out, "%d manifest(s) checked. Judged against the seats this binary carries; a claim on "+ "one of the mesh's own seats is judged fully at registration, and a seat declared by a "+ "module not given here reads as unknown\n", len(paths)) return nil } func joinInvokes(invokes []string) string { if len(invokes) == 1 && invokes[0] == "*" { return "every tool" } s := "" for i, t := range invokes { if i > 0 { s += ", " } s += t } return s } // manifestsUnder lists every module.json below a directory, for `module check `. func manifestsUnder(dir string) ([]string, error) { var found []string err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil { return err } if d.IsDir() && (d.Name() == "node_modules" || d.Name() == ".git" || d.Name() == "dist") { return filepath.SkipDir } if !d.IsDir() && d.Name() == "module.json" { found = append(found, path) } return nil }) sort.Strings(found) return found, err }