@geonosis/verify-arch 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,109 @@
1
+ /**
2
+ * One finding. `scanner` is the check's id, which is what the human format prints in brackets and
3
+ * the ratchet format prints as the third field; `line` is absent when the fact is about the whole
4
+ * file, because a line number nobody can open is worse than none.
5
+ */
6
+ type Violation = {
7
+ file: string;
8
+ line?: number;
9
+ message: string;
10
+ scanner: string;
11
+ };
12
+ /** A check a pack ran, named in the pass line so a reader can see what was and was not looked at. */
13
+ type Check = {
14
+ id: string;
15
+ label: string;
16
+ };
17
+ type ScanContext = {
18
+ /** Every file under the root that survived the walk, absolute. One walk answers every pack. */
19
+ files: readonly string[];
20
+ /** Whatever this pack's entry in the config gave it. Each pack validates its own. */
21
+ options: Record<string, unknown>;
22
+ root: string;
23
+ };
24
+ /**
25
+ * A pack is the plugin unit: a set of whole-graph checks over one stack's conventions. It declares
26
+ * the checks it will run BEFORE it runs them, because the pass line has to name them and a line
27
+ * naming a check that did nothing is the failure this package exists to prevent.
28
+ */
29
+ type Pack = {
30
+ checks: (options: Record<string, unknown>) => Check[];
31
+ id: string;
32
+ scan: (context: ScanContext) => Violation[];
33
+ };
34
+ type PackConfig = {
35
+ options?: Record<string, unknown>;
36
+ pack: string;
37
+ };
38
+ type Config = {
39
+ /** What the pass and fail lines call themselves. A repo's script name is the repo's to choose. */
40
+ label: string;
41
+ packs: PackConfig[];
42
+ };
43
+ /**
44
+ * A run that could not measure. Separate from a run that measured and found something, because the
45
+ * two are opposite verdicts and a gate that conflates them reports "clean" for "never looked".
46
+ */
47
+ declare class VerifyArchError extends Error {
48
+ constructor(message: string);
49
+ }
50
+
51
+ declare const CONFIG_FILE = "geonosis.verify-arch.json";
52
+ /**
53
+ * Every refusal here is one shape of the same failure: a run that scanned nothing and reported
54
+ * PASS. A missing config, an empty `packs`, a pack nobody ships — each would produce a clean exit
55
+ * over a tree nothing looked at, which is worse than a red, because nobody goes back to check it.
56
+ */
57
+ declare const readConfig: (root: string) => Config;
58
+
59
+ declare const compositionTree: Pack;
60
+
61
+ declare const medusa: Pack;
62
+
63
+ /** Every pack this build ships. A repo enables the ones its stack has, by name, in its config. */
64
+ declare const PACKS: Record<string, Pack>;
65
+ declare const PACK_NAMES: string[];
66
+
67
+ /**
68
+ * The human format, byte-for-byte the one dielime's `verify:arch` prints today. That is the point:
69
+ * a scanner that found the same things and said them differently is a migration, not a drop-in, and
70
+ * a CI log, a runbook and a reviewer's habit all read these lines.
71
+ */
72
+ declare const formatHuman: ({ checks, label, root, violations, }: {
73
+ checks: readonly Check[];
74
+ label: string;
75
+ root: string;
76
+ violations: readonly Violation[];
77
+ }) => string;
78
+ /**
79
+ * One finding, one line, so a counter can count them. The message is flattened because a finding
80
+ * spread over three lines would be counted as one and read as three — or the other way round,
81
+ * depending on the regex, which is the class of silent miscount a ratchet cannot survive.
82
+ */
83
+ declare const formatRatchet: ({ root, violations, }: {
84
+ root: string;
85
+ violations: readonly Violation[];
86
+ }) => string;
87
+
88
+ type RunResult = {
89
+ /** Every check that RAN, in configured order. The pass line names these and nothing else. */
90
+ checks: Check[];
91
+ violations: Violation[];
92
+ };
93
+ /**
94
+ * One walk, every pack. The four Medusa collectors and the composition graph all want the same
95
+ * file list, and a monorepo walked once per check is four walks wearing one command's name.
96
+ */
97
+ declare const runPacks: (root: string, packs: readonly PackConfig[]) => RunResult;
98
+
99
+ /**
100
+ * The build output is the same artefact twice. A scan that read `.medusa/server/src` beside `src`
101
+ * reports every route as colliding with itself and every module name as duplicated — the gate
102
+ * failing loudest on the tree it is happiest with. So the walk excludes what a build writes, and
103
+ * the list is an argument because another stack builds somewhere else.
104
+ */
105
+ declare const DEFAULT_IGNORED: string[];
106
+ /** Every file under `root`, absolute, skipping the named directory names at any depth. */
107
+ declare const walkFiles: (root: string, ignored?: readonly string[]) => string[];
108
+
109
+ export { CONFIG_FILE, type Check, type Config, DEFAULT_IGNORED, PACKS, PACK_NAMES, type Pack, type PackConfig, type RunResult, type ScanContext, VerifyArchError, type Violation, compositionTree, formatHuman, formatRatchet, medusa, readConfig, runPacks, walkFiles };
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ import {
2
+ CONFIG_FILE,
3
+ DEFAULT_IGNORED,
4
+ PACKS,
5
+ PACK_NAMES,
6
+ VerifyArchError,
7
+ compositionTree,
8
+ formatHuman,
9
+ formatRatchet,
10
+ medusa,
11
+ readConfig,
12
+ runPacks,
13
+ walkFiles
14
+ } from "./chunk-2N2AFWCY.js";
15
+ export {
16
+ CONFIG_FILE,
17
+ DEFAULT_IGNORED,
18
+ PACKS,
19
+ PACK_NAMES,
20
+ VerifyArchError,
21
+ compositionTree,
22
+ formatHuman,
23
+ formatRatchet,
24
+ medusa,
25
+ readConfig,
26
+ runPacks,
27
+ walkFiles
28
+ };
@@ -0,0 +1,89 @@
1
+ import {
2
+ CONFIG_FILE,
3
+ VerifyArchError,
4
+ formatHuman,
5
+ formatRatchet,
6
+ readConfig,
7
+ runPacks
8
+ } from "./chunk-2N2AFWCY.js";
9
+
10
+ // src/verify-arch-cli.ts
11
+ import { resolve } from "path";
12
+ import process from "process";
13
+ var USAGE = `geonosis-verify-arch [root] [--format human|ratchet]
14
+
15
+ Runs the whole-graph architecture scans a per-file linter cannot: a name that must be unique across
16
+ modules, two files registering one route, a tier composing something above it, a file nothing
17
+ reaches. Which packs run \u2014 and with what \u2014 is ${CONFIG_FILE} at the root.
18
+
19
+ --format human (default) the pass line naming every check that ran, or the findings on stderr
20
+ --format ratchet one "\u2717 <file>[:<line>] <check> <message>" line per finding, for a counter
21
+
22
+ Exit codes: 0 clean \xB7 1 violations \xB7 2 the run could not measure (no config, unknown pack, unknown
23
+ format). A scan that never happened is not a clean scan, so it does not share an exit code with one.`;
24
+ var FORMATS = /* @__PURE__ */ new Set(["human", "ratchet"]);
25
+ var parse = (argv) => {
26
+ const args = { format: "human", root: process.cwd() };
27
+ let positional = false;
28
+ for (let at = 0; at < argv.length; at++) {
29
+ const arg = argv[at];
30
+ if (arg === "--format") {
31
+ const next = argv[at + 1];
32
+ if (next === void 0) throw new VerifyArchError("--format needs a value.");
33
+ args.format = next;
34
+ at++;
35
+ continue;
36
+ }
37
+ if (arg.startsWith("--")) throw new VerifyArchError(`Unknown option: ${arg}`);
38
+ if (positional) throw new VerifyArchError(`Unexpected second root: ${arg}`);
39
+ args.root = resolve(process.cwd(), arg);
40
+ positional = true;
41
+ }
42
+ if (!FORMATS.has(args.format)) {
43
+ throw new VerifyArchError(
44
+ `Unknown --format ${args.format}. It is one of ${[...FORMATS].join(", ")}.`
45
+ );
46
+ }
47
+ return args;
48
+ };
49
+ var main = (argv) => {
50
+ if (argv.includes("--help") || argv.includes("-h")) {
51
+ process.stdout.write(`${USAGE}
52
+ `);
53
+ return 0;
54
+ }
55
+ let result;
56
+ let label;
57
+ let root;
58
+ let format;
59
+ try {
60
+ const args = parse(argv);
61
+ root = args.root;
62
+ format = args.format;
63
+ const config = readConfig(root);
64
+ label = config.label;
65
+ result = runPacks(root, config.packs);
66
+ } catch (error) {
67
+ process.stderr.write(`${error.message}
68
+ `);
69
+ return 2;
70
+ }
71
+ if (format === "ratchet") {
72
+ process.stdout.write(formatRatchet({ root, violations: result.violations }));
73
+ return result.violations.length === 0 ? 0 : 1;
74
+ }
75
+ const text = formatHuman({ checks: result.checks, label, root, violations: result.violations });
76
+ if (result.violations.length === 0) {
77
+ process.stdout.write(text);
78
+ return 0;
79
+ }
80
+ process.stderr.write(text);
81
+ return 1;
82
+ };
83
+ try {
84
+ process.exit(main(process.argv.slice(2)));
85
+ } catch (error) {
86
+ process.stderr.write(`${error.message}
87
+ `);
88
+ process.exit(2);
89
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "_comment": "dielime's ten checks, spelled as configuration. Copy to geonosis.verify-arch.json at the repo root. `label` reproduces the line its verify:arch script prints today; `kinds` keeps `workflow` because the repo is not yet pinned to Medusa >= 2.19, where workflow-loader.js passes allowIndex.",
3
+ "label": "verify:arch",
4
+ "packs": [
5
+ {
6
+ "pack": "medusa",
7
+ "options": {
8
+ "sourceRoots": ["packages", "apps"],
9
+ "moduleRoots": ["packages"],
10
+ "workflowRoots": ["packages"],
11
+ "packages": [
12
+ { "under": "packages/medusa-plugins", "name": "@dielime/{dir}", "namespaced": true },
13
+ { "under": "apps", "name": "app:{dir}" }
14
+ ],
15
+ "workflowFactories": [
16
+ "createWorkflow",
17
+ "createScheduledWorkflow",
18
+ "createCartMutatingWorkflow"
19
+ ],
20
+ "retrySpreads": ["RETRY_"],
21
+ "kinds": ["workflow", "subscriber", "job"],
22
+ "inlineMutationOk": "arch:inline-mutation-ok",
23
+ "routeOverrideOk": "arch:route-override-ok"
24
+ }
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "_comment": "during.day's composition tree as a scanner pack. `roots` is the web app; `entryPoints` are the Next.js route-segment files that sit above biology, and naming them is what turns the orphan check on. Only the GATE is here — the mermaid/dot/json/html renderers of the original script are a viewer, not a check, and stay where they are.",
3
+ "label": "arch",
4
+ "packs": [
5
+ {
6
+ "pack": "composition-tree",
7
+ "options": {
8
+ "roots": ["apps/web/features", "apps/web/app"],
9
+ "aliases": [{ "prefix": "@/", "to": "apps/web/" }],
10
+ "entryPoints": [
11
+ "/app/(?:.*/)?(?:page|layout|template|default|error|not-found|loading|global-error)\\.tsx?$"
12
+ ]
13
+ }
14
+ }
15
+ ]
16
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@geonosis/verify-arch",
3
+ "version": "1.0.0",
4
+ "description": "Whole-graph architecture scans a per-file linter cannot do — unique module names, route collisions, tier direction, dead files — as configurable packs behind one exit code.",
5
+ "keywords": [
6
+ "architecture",
7
+ "medusa",
8
+ "monorepo",
9
+ "lint",
10
+ "scanner",
11
+ "ci",
12
+ "gate"
13
+ ],
14
+ "homepage": "https://github.com/microcompanies/geonosis/tree/main/packages/verify-arch",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/microcompanies/geonosis.git",
18
+ "directory": "packages/verify-arch"
19
+ },
20
+ "license": "Apache-2.0",
21
+ "type": "module",
22
+ "main": "dist/index.js",
23
+ "bin": {
24
+ "geonosis-verify-arch": "bin/geonosis-verify-arch.mjs"
25
+ },
26
+ "exports": {
27
+ ".": "./dist/index.js"
28
+ },
29
+ "files": [
30
+ "bin",
31
+ "dist",
32
+ "examples"
33
+ ],
34
+ "engines": {
35
+ "node": ">=22"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "typecheck": "tsc --noEmit"
43
+ }
44
+ }