@invariant-ui/cli 0.0.0-stage → 0.1.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.
Files changed (196) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +248 -2
  3. package/dist/agent-kit/apply.js +232 -0
  4. package/dist/agent-kit/guidance.js +236 -0
  5. package/dist/agent-kit/index.js +5 -0
  6. package/dist/agent-kit/integrations.js +121 -0
  7. package/dist/agent-kit/plugin.js +115 -0
  8. package/dist/artifact-generator/index.js +75 -0
  9. package/dist/browser-verify/driver.js +2 -0
  10. package/dist/browser-verify/index.js +4 -0
  11. package/dist/browser-verify/playwright.js +82 -0
  12. package/dist/browser-verify/runner.js +175 -0
  13. package/dist/browser-verify/spec.js +103 -0
  14. package/dist/cli/args.js +77 -0
  15. package/dist/cli/baseline.js +64 -0
  16. package/dist/cli/cli.js +121 -0
  17. package/dist/cli/commands-browser.js +104 -0
  18. package/dist/cli/commands-discover.js +312 -0
  19. package/dist/cli/commands-enforcement.js +35 -0
  20. package/dist/cli/commands-governance.js +179 -0
  21. package/dist/cli/commands-hook.js +269 -0
  22. package/dist/cli/commands-patterns.js +324 -0
  23. package/dist/cli/commands-setup.js +485 -0
  24. package/dist/cli/commands-validate.js +157 -0
  25. package/dist/cli/commands.js +517 -0
  26. package/dist/cli/config.js +197 -0
  27. package/dist/cli/diff.js +66 -0
  28. package/dist/cli/enforcement-log.js +83 -0
  29. package/dist/cli/enforcement-report.js +96 -0
  30. package/dist/cli/errors.js +10 -0
  31. package/dist/cli/format.js +22 -0
  32. package/dist/cli/index.js +5 -0
  33. package/dist/cli/io.js +2 -0
  34. package/dist/cli/main.js +31 -0
  35. package/dist/cli/pipeline.js +262 -0
  36. package/dist/cli/publish.js +77 -0
  37. package/dist/cli/report.js +127 -0
  38. package/dist/cli/starter-rules.js +57 -0
  39. package/dist/cli/store.js +60 -0
  40. package/dist/cli/version.js +5 -0
  41. package/dist/cli/watch.js +68 -0
  42. package/dist/cli/workflow.js +52 -0
  43. package/dist/compiler-angular/adapter.js +115 -0
  44. package/dist/compiler-angular/analyze.js +222 -0
  45. package/dist/compiler-angular/classes.js +446 -0
  46. package/dist/compiler-angular/declared.js +285 -0
  47. package/dist/compiler-angular/emit.js +183 -0
  48. package/dist/compiler-angular/index.js +4 -0
  49. package/dist/compiler-angular/template.js +142 -0
  50. package/dist/compiler-core/adapter.js +2 -0
  51. package/dist/compiler-core/adapters/inventory.js +312 -0
  52. package/dist/compiler-core/compile.js +148 -0
  53. package/dist/compiler-core/component-facts.js +123 -0
  54. package/dist/compiler-core/discovery/docs.js +66 -0
  55. package/dist/compiler-core/discovery/entries.js +270 -0
  56. package/dist/compiler-core/discovery/exports.js +207 -0
  57. package/dist/compiler-core/discovery/graph.js +59 -0
  58. package/dist/compiler-core/discovery/index.js +103 -0
  59. package/dist/compiler-core/discovery/packages.js +148 -0
  60. package/dist/compiler-core/discovery/schema.js +144 -0
  61. package/dist/compiler-core/discovery/storybook.js +232 -0
  62. package/dist/compiler-core/discovery/support.js +82 -0
  63. package/dist/compiler-core/discovery/tokens.js +186 -0
  64. package/dist/compiler-core/discovery/tooling.js +146 -0
  65. package/dist/compiler-core/fs.js +89 -0
  66. package/dist/compiler-core/glob.js +3 -0
  67. package/dist/compiler-core/index.js +16 -0
  68. package/dist/compiler-core/ir-builder.js +68 -0
  69. package/dist/compiler-core/json-schema.js +11 -0
  70. package/dist/compiler-core/testing/index.js +81 -0
  71. package/dist/compiler-core/ts/ast.js +166 -0
  72. package/dist/compiler-core/ts/members.js +163 -0
  73. package/dist/compiler-core/ts/program.js +81 -0
  74. package/dist/compiler-core/ts/static-eval.js +145 -0
  75. package/dist/compiler-core/ts/tsconfig-paths.js +60 -0
  76. package/dist/compiler-core/walk.js +46 -0
  77. package/dist/compiler-ir/index.js +4 -0
  78. package/dist/compiler-ir/ir.js +67 -0
  79. package/dist/compiler-ir/json-schema.js +6 -0
  80. package/dist/compiler-ir/validate.js +116 -0
  81. package/dist/compiler-react/adapter.js +147 -0
  82. package/dist/compiler-react/analyze.js +199 -0
  83. package/dist/compiler-react/ast.js +105 -0
  84. package/dist/compiler-react/behavior.js +75 -0
  85. package/dist/compiler-react/detect.js +558 -0
  86. package/dist/compiler-react/emit.js +188 -0
  87. package/dist/compiler-react/index.js +2 -0
  88. package/dist/compiler-react/program.js +58 -0
  89. package/dist/compiler-react/props.js +157 -0
  90. package/dist/compiler-react/styling.js +138 -0
  91. package/dist/compiler-react/usage.js +116 -0
  92. package/dist/compiler-react/variants.js +111 -0
  93. package/dist/compiler-storybook/adapter.js +253 -0
  94. package/dist/compiler-storybook/csf.js +521 -0
  95. package/dist/compiler-storybook/emit.js +202 -0
  96. package/dist/compiler-storybook/index-json.js +41 -0
  97. package/dist/compiler-storybook/index.js +5 -0
  98. package/dist/compiler-storybook/mdx.js +120 -0
  99. package/dist/compiler-storybook/static-eval.js +145 -0
  100. package/dist/compiler-tokens/adapter.js +158 -0
  101. package/dist/compiler-tokens/css.js +59 -0
  102. package/dist/compiler-tokens/index.js +5 -0
  103. package/dist/compiler-tokens/json-lines.js +106 -0
  104. package/dist/compiler-tokens/json-tokens.js +52 -0
  105. package/dist/compiler-vue/adapter.js +107 -0
  106. package/dist/compiler-vue/analyze.js +344 -0
  107. package/dist/compiler-vue/declared.js +343 -0
  108. package/dist/compiler-vue/emit.js +129 -0
  109. package/dist/compiler-vue/index.js +5 -0
  110. package/dist/compiler-vue/script.js +472 -0
  111. package/dist/compiler-vue/sfc.js +44 -0
  112. package/dist/compiler-vue/template.js +73 -0
  113. package/dist/contract/archetype-checks.js +153 -0
  114. package/dist/contract/components.js +102 -0
  115. package/dist/contract/conflicts.js +39 -0
  116. package/dist/contract/contract.js +46 -0
  117. package/dist/contract/evidence.js +84 -0
  118. package/dist/contract/fact-path.js +39 -0
  119. package/dist/contract/facts.js +62 -0
  120. package/dist/contract/index.js +16 -0
  121. package/dist/contract/integrity.js +21 -0
  122. package/dist/contract/json-schema.js +11 -0
  123. package/dist/contract/knowledge.js +95 -0
  124. package/dist/contract/pattern-checks.js +254 -0
  125. package/dist/contract/patterns.js +225 -0
  126. package/dist/contract/primitives.js +81 -0
  127. package/dist/contract/sources.js +30 -0
  128. package/dist/contract/tokens.js +41 -0
  129. package/dist/contract/validate.js +253 -0
  130. package/dist/contract-query/archetype.js +188 -0
  131. package/dist/contract-query/index.js +6 -0
  132. package/dist/contract-query/patterns.js +298 -0
  133. package/dist/contract-query/scan.js +164 -0
  134. package/dist/contract-query/search.js +132 -0
  135. package/dist/contract-query/system.js +148 -0
  136. package/dist/contract-query/validate-code.js +239 -0
  137. package/dist/contract-query/validate-usage.js +150 -0
  138. package/dist/contract-query/views.js +288 -0
  139. package/dist/governance/decisions.js +229 -0
  140. package/dist/governance/index.js +5 -0
  141. package/dist/governance/patterns-file.js +156 -0
  142. package/dist/governance/patterns.js +510 -0
  143. package/dist/governance/policies.js +148 -0
  144. package/dist/mcp/index.js +7 -0
  145. package/dist/mcp/loader.js +70 -0
  146. package/dist/mcp/protocol.js +27 -0
  147. package/dist/mcp/resources.js +138 -0
  148. package/dist/mcp/server.js +177 -0
  149. package/dist/mcp/stdio.js +28 -0
  150. package/dist/mcp/tools.js +267 -0
  151. package/dist/reconciliation/code.js +62 -0
  152. package/dist/reconciliation/components.js +401 -0
  153. package/dist/reconciliation/conflicts.js +126 -0
  154. package/dist/reconciliation/facts.js +41 -0
  155. package/dist/reconciliation/index.js +7 -0
  156. package/dist/reconciliation/reconcile.js +134 -0
  157. package/dist/reconciliation/resolve.js +158 -0
  158. package/dist/reconciliation/review.js +81 -0
  159. package/dist/reconciliation/storybook.js +370 -0
  160. package/dist/reconciliation/tokens.js +179 -0
  161. package/dist/reconciliation/util.js +55 -0
  162. package/dist/semantic-engine/additive.js +51 -0
  163. package/dist/semantic-engine/compounds.js +91 -0
  164. package/dist/semantic-engine/documents.js +157 -0
  165. package/dist/semantic-engine/enrich.js +42 -0
  166. package/dist/semantic-engine/index.js +7 -0
  167. package/dist/semantic-engine/infer.js +38 -0
  168. package/dist/semantic-engine/tokens.js +120 -0
  169. package/dist/shared/canonical.js +65 -0
  170. package/dist/shared/diagnostic.js +78 -0
  171. package/dist/shared/glob.js +98 -0
  172. package/dist/shared/index.js +4 -0
  173. package/dist/shared/json.js +2 -0
  174. package/dist/validation/card.js +50 -0
  175. package/dist/validation/discovery/arrangements.js +162 -0
  176. package/dist/validation/discovery/discover.js +70 -0
  177. package/dist/validation/discovery/index.js +8 -0
  178. package/dist/validation/discovery/model.js +127 -0
  179. package/dist/validation/discovery/promote.js +24 -0
  180. package/dist/validation/discovery/reviews.js +63 -0
  181. package/dist/validation/discovery/shapes.js +47 -0
  182. package/dist/validation/discovery/similar-files.js +146 -0
  183. package/dist/validation/discovery/util.js +47 -0
  184. package/dist/validation/index.js +11 -0
  185. package/dist/validation/interaction-facts.js +341 -0
  186. package/dist/validation/interaction-workflows.js +633 -0
  187. package/dist/validation/literals.js +251 -0
  188. package/dist/validation/pattern-rules.js +608 -0
  189. package/dist/validation/policy.js +241 -0
  190. package/dist/validation/report.js +163 -0
  191. package/dist/validation/rules.js +269 -0
  192. package/dist/validation/scan.js +295 -0
  193. package/dist/validation/text.js +34 -0
  194. package/dist/validation/validate-code.js +513 -0
  195. package/dist/validation/validate-usage.js +196 -0
  196. package/package.json +38 -4
@@ -0,0 +1,186 @@
1
+ import { cmp, hashText, posix, readBounded } from "./support.js";
2
+ const NOT_TOKENS = /^(?:package(?:-lock)?|tsconfig(?:\..+)?|angular|turbo|nx|lerna|biome|deno|composer)\.json$/i;
3
+ const NAMED_TOKEN_JSON = /(?:^|[./-])tokens?\.json$/i;
4
+ const TOKEN_DIR_JSON = /(?:^|\/)(?:design-)?tokens\/[^/]+\.json$/i;
5
+ const STYLE_DICTIONARY_CONFIG = /^(?:style-dictionary|sd)\.config\.[cm]?[jt]s(?:on)?$/;
6
+ const TAILWIND_CONFIG = /^tailwind\.config\.[cm]?[jt]s$/;
7
+ /** CSS custom-property files, token JSON files, and token-tool config files. Values are recorded raw, never evaluated. */
8
+ export async function discoverTokenFiles(env) {
9
+ const out = [];
10
+ for (const file of env.files) {
11
+ const base = posix.basename(file);
12
+ if (/\.css$/.test(file) && !/\.min\.css$/.test(file)) {
13
+ const text = await readBounded(env, file);
14
+ if (text === undefined)
15
+ continue;
16
+ const variables = parseCssCustomProperties(text);
17
+ if (variables.length === 0)
18
+ continue;
19
+ const truncated = variables.length > env.limits.maxCssVariablesPerFile;
20
+ out.push({
21
+ path: file,
22
+ kind: "css-custom-properties",
23
+ contentHash: hashText(text),
24
+ cssVariables: variables.slice(0, env.limits.maxCssVariablesPerFile),
25
+ ...(truncated ? { cssVariablesTruncated: true } : {})
26
+ });
27
+ }
28
+ else if (STYLE_DICTIONARY_CONFIG.test(base) || TAILWIND_CONFIG.test(base)) {
29
+ const text = await readBounded(env, file);
30
+ if (text === undefined)
31
+ continue;
32
+ out.push({
33
+ path: file,
34
+ kind: STYLE_DICTIONARY_CONFIG.test(base) ? "style-dictionary-config" : "tailwind-config",
35
+ contentHash: hashText(text)
36
+ });
37
+ }
38
+ else if (!NOT_TOKENS.test(base) && (NAMED_TOKEN_JSON.test(file) || TOKEN_DIR_JSON.test(file))) {
39
+ const text = await readBounded(env, file);
40
+ if (text === undefined)
41
+ continue;
42
+ let parsed;
43
+ try {
44
+ parsed = JSON.parse(text);
45
+ }
46
+ catch {
47
+ env.diagnostics.warning("discovery.token-json-invalid", `${file} looks like a token file but is not valid JSON`, { location: { path: file } });
48
+ continue;
49
+ }
50
+ const shape = classifyTokenJson(parsed);
51
+ // A file only matched by directory name must actually look like tokens.
52
+ if (!NAMED_TOKEN_JSON.test(file) && shape.count === 0)
53
+ continue;
54
+ out.push({
55
+ path: file,
56
+ kind: "json-tokens",
57
+ format: shape.format,
58
+ tokenCount: shape.count,
59
+ contentHash: hashText(text)
60
+ });
61
+ }
62
+ }
63
+ return out.sort((a, b) => cmp(a.path, b.path));
64
+ }
65
+ function classifyTokenJson(root) {
66
+ let dtcg = 0;
67
+ let sd = 0;
68
+ const visit = (node, depth) => {
69
+ if (typeof node !== "object" || node === null || depth > 20)
70
+ return;
71
+ const record = node;
72
+ if ("$value" in record)
73
+ dtcg++;
74
+ else if ("value" in record && typeof record.value !== "object")
75
+ sd++;
76
+ else
77
+ for (const child of Object.values(record))
78
+ visit(child, depth + 1);
79
+ };
80
+ visit(root, 0);
81
+ if (dtcg > 0 && dtcg >= sd)
82
+ return { format: "dtcg-like", count: dtcg };
83
+ if (sd > 0)
84
+ return { format: "style-dictionary-like", count: sd };
85
+ return { format: "unknown", count: 0 };
86
+ }
87
+ /**
88
+ * Tolerant CSS scanner for custom-property declarations. Tracks the enclosing selector/at-rule chain,
89
+ * skips comments, strings, and parenthesized `;` (such as in `url(data:…;base64,…)`).
90
+ */
91
+ export function parseCssCustomProperties(source) {
92
+ return scanCssCustomProperties(source).map(({ chain: _chain, ...variable }) => variable);
93
+ }
94
+ /** Like `parseCssCustomProperties`, but also returns the enclosing rule chain (outermost first). */
95
+ export function scanCssCustomProperties(source) {
96
+ const css = blankComments(source);
97
+ const found = [];
98
+ const lineStarts = [0];
99
+ for (let i = 0; i < css.length; i++)
100
+ if (css[i] === "\n")
101
+ lineStarts.push(i + 1);
102
+ const lineAt = (index) => {
103
+ let lo = 0;
104
+ let hi = lineStarts.length - 1;
105
+ while (lo < hi) {
106
+ const mid = (lo + hi + 1) >> 1;
107
+ if (lineStarts[mid] <= index)
108
+ lo = mid;
109
+ else
110
+ hi = mid - 1;
111
+ }
112
+ return lo + 1;
113
+ };
114
+ const stack = [];
115
+ let segmentStart = 0;
116
+ let parens = 0;
117
+ const flushDeclaration = (end) => {
118
+ const raw = css.slice(segmentStart, end);
119
+ const trimmed = raw.trimStart();
120
+ const match = /^(--[A-Za-z0-9_-]+)\s*:([\s\S]*)$/.exec(trimmed);
121
+ if (match && stack.length > 0) {
122
+ const value = match[2].trim().replace(/\s+/g, " ");
123
+ const alias = /^var\(\s*(--[A-Za-z0-9_-]+)\s*\)$/.exec(value);
124
+ found.push({
125
+ name: match[1],
126
+ scope: stack.join(" "),
127
+ chain: [...stack],
128
+ line: lineAt(segmentStart + (raw.length - trimmed.length)),
129
+ value: value.slice(0, 200),
130
+ ...(alias ? { aliasOf: alias[1] } : {})
131
+ });
132
+ }
133
+ };
134
+ for (let i = 0; i < css.length; i++) {
135
+ const c = css[i];
136
+ if (c === '"' || c === "'") {
137
+ for (i++; i < css.length && css[i] !== c; i++)
138
+ if (css[i] === "\\")
139
+ i++;
140
+ }
141
+ else if (c === "(")
142
+ parens++;
143
+ else if (c === ")")
144
+ parens = Math.max(0, parens - 1);
145
+ else if (parens > 0)
146
+ continue;
147
+ else if (c === "{") {
148
+ stack.push(css.slice(segmentStart, i).trim().replace(/\s+/g, " "));
149
+ segmentStart = i + 1;
150
+ }
151
+ else if (c === ";") {
152
+ flushDeclaration(i);
153
+ segmentStart = i + 1;
154
+ }
155
+ else if (c === "}") {
156
+ flushDeclaration(i);
157
+ stack.pop();
158
+ segmentStart = i + 1;
159
+ }
160
+ }
161
+ return found;
162
+ }
163
+ /** Replaces comment text with spaces (keeping newlines) so offsets and line numbers stay valid. */
164
+ function blankComments(css) {
165
+ let out = "";
166
+ for (let i = 0; i < css.length; i++) {
167
+ const c = css[i];
168
+ if (c === "/" && css[i + 1] === "*") {
169
+ const end = css.indexOf("*/", i + 2);
170
+ const stop = end < 0 ? css.length : end + 2;
171
+ out += css.slice(i, stop).replace(/[^\n]/g, " ");
172
+ i = stop - 1;
173
+ }
174
+ else if (c === '"' || c === "'") {
175
+ let j = i + 1;
176
+ while (j < css.length && css[j] !== c)
177
+ j += css[j] === "\\" ? 2 : 1;
178
+ out += css.slice(i, j + 1);
179
+ i = j;
180
+ }
181
+ else
182
+ out += c;
183
+ }
184
+ return out;
185
+ }
186
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1,146 @@
1
+ import { dependencyLine, isRecord } from "./packages.js";
2
+ import { cmp, posix } from "./support.js";
3
+ const FRAMEWORK_PACKAGES = [
4
+ ["react", "react"],
5
+ ["angular", "@angular/core"],
6
+ ["vue", "vue"]
7
+ ];
8
+ const BUILD_TOOL_DEPS = {
9
+ vite: "vite",
10
+ webpack: "webpack",
11
+ rollup: "rollup",
12
+ esbuild: "esbuild",
13
+ tsup: "tsup",
14
+ parcel: "parcel",
15
+ "@rspack/core": "rspack",
16
+ "@angular/cli": "angular-cli",
17
+ "@vue/cli-service": "vue-cli",
18
+ "ng-packagr": "ng-packagr",
19
+ nx: "nx",
20
+ turbo: "turbo",
21
+ typescript: "typescript"
22
+ };
23
+ const BUILD_TOOL_CONFIGS = [
24
+ [/^vite\.config\.[cm]?[jt]s$/, "vite"],
25
+ [/^webpack\.config\.[cm]?[jt]s$/, "webpack"],
26
+ [/^rollup\.config\.[cm]?[jt]s$/, "rollup"],
27
+ [/^tsup\.config\.[cm]?[jt]s$/, "tsup"],
28
+ [/^angular\.json$/, "angular-cli"],
29
+ [/^nx\.json$/, "nx"],
30
+ [/^turbo\.json$/, "turbo"],
31
+ [/^tsconfig\.json$/, "typescript"],
32
+ [/^vue\.config\.[cm]?js$/, "vue-cli"]
33
+ ];
34
+ const LOCKFILES = [
35
+ ["pnpm-lock.yaml", "pnpm"],
36
+ ["package-lock.json", "npm"],
37
+ ["yarn.lock", "yarn"],
38
+ ["bun.lock", "bun"],
39
+ ["bun.lockb", "bun"]
40
+ ];
41
+ export async function detectPackageManager(env, root) {
42
+ const evidence = [];
43
+ let fromField;
44
+ const field = root?.manifest.packageManager;
45
+ if (typeof field === "string") {
46
+ const match = /^(npm|pnpm|yarn|bun)@(.+)$/.exec(field);
47
+ if (match) {
48
+ fromField = {
49
+ name: match[1],
50
+ version: match[2].replace(/\+.*$/, "")
51
+ };
52
+ evidence.push("package.json#packageManager");
53
+ }
54
+ }
55
+ const lockfiles = LOCKFILES.filter(([file]) => env.fileSet.has(file));
56
+ for (const [file] of lockfiles)
57
+ evidence.push(`lockfile:${file}`);
58
+ const lockNames = new Set(lockfiles.map(([, name]) => name));
59
+ if (fromField) {
60
+ if (lockNames.size > 0 && !lockNames.has(fromField.name)) {
61
+ env.diagnostics.warning("discovery.package-manager-mismatch", `packageManager field says ${fromField.name} but lockfiles indicate ${[...lockNames].join(", ")}`);
62
+ }
63
+ return { name: fromField.name, ...(fromField.version ? { version: fromField.version } : {}), evidence };
64
+ }
65
+ if (lockNames.size > 1) {
66
+ env.diagnostics.warning("discovery.ambiguous-lockfiles", `Multiple lockfiles found (${lockfiles.map(([f]) => f).join(", ")}); package manager left unknown`);
67
+ return { name: "unknown", evidence };
68
+ }
69
+ const only = [...lockNames][0];
70
+ return only ? { name: only, evidence } : { name: "unknown", evidence };
71
+ }
72
+ /** Declared range from the manifest plus the installed version when node_modules is present (never guessed). */
73
+ export async function detectFrameworks(env, raw) {
74
+ const found = [];
75
+ for (const [framework, pkgName] of FRAMEWORK_PACKAGES) {
76
+ for (const kind of [
77
+ "dependencies",
78
+ "peerDependencies",
79
+ "devDependencies",
80
+ "optionalDependencies"
81
+ ]) {
82
+ const group = raw.manifest[kind];
83
+ if (!isRecord(group) || typeof group[pkgName] !== "string")
84
+ continue;
85
+ const installed = await installedVersion(env, raw.info.dir, pkgName);
86
+ const line = dependencyLine(raw, kind, pkgName);
87
+ found.push({
88
+ name: framework,
89
+ package: pkgName,
90
+ dependencyKind: kind,
91
+ declaredRange: group[pkgName],
92
+ ...(installed ? { installedVersion: installed } : {}),
93
+ ...(line ? { line } : {})
94
+ });
95
+ break;
96
+ }
97
+ }
98
+ return found;
99
+ }
100
+ export function detectBuildTools(env, raw) {
101
+ const tools = new Map();
102
+ const add = (name, type, ref) => {
103
+ const list = tools.get(name) ?? [];
104
+ if (!list.some((e) => e.type === type && e.ref === ref))
105
+ list.push({ type, ref });
106
+ tools.set(name, list);
107
+ };
108
+ for (const dep of raw.info.dependencies) {
109
+ const tool = BUILD_TOOL_DEPS[dep.name];
110
+ if (tool)
111
+ add(tool, "dependency", dep.name);
112
+ }
113
+ for (const file of env.files) {
114
+ if (posix.dirname(file) !== raw.info.dir)
115
+ continue;
116
+ const base = posix.basename(file);
117
+ for (const [pattern, tool] of BUILD_TOOL_CONFIGS)
118
+ if (pattern.test(base))
119
+ add(tool, "config-file", file);
120
+ }
121
+ return [...tools]
122
+ .map(([name, evidence]) => ({
123
+ name,
124
+ evidence: evidence.sort((a, b) => cmp(a.type, b.type) || cmp(a.ref, b.ref))
125
+ }))
126
+ .sort((a, b) => cmp(a.name, b.name));
127
+ }
128
+ /** Looks in `<dir>/node_modules`, then each ancestor directory's, as Node resolution would. */
129
+ async function installedVersion(env, dir, pkgName) {
130
+ for (let current = dir;; current = posix.dirname(current)) {
131
+ const candidate = posix.join(current, "node_modules", pkgName, "package.json");
132
+ if (await env.fs.exists(candidate)) {
133
+ try {
134
+ const version = JSON.parse(await env.fs.readFile(candidate)).version;
135
+ if (typeof version === "string")
136
+ return version;
137
+ }
138
+ catch {
139
+ return undefined;
140
+ }
141
+ }
142
+ if (current === ".")
143
+ return undefined;
144
+ }
145
+ }
146
+ //# sourceMappingURL=tooling.js.map
@@ -0,0 +1,89 @@
1
+ import { promises as nodeFsPromises } from "node:fs";
2
+ import path from "node:path";
3
+ /** Reads a real directory. Symlinks are listed but never followed, and no path may escape the root. */
4
+ export function nodeFs(rootDirectory) {
5
+ const root = path.resolve(rootDirectory);
6
+ const resolve = (relative) => {
7
+ const absolute = path.resolve(root, relative);
8
+ if (absolute !== root && !absolute.startsWith(root + path.sep))
9
+ throw new Error(`Path escapes project root: ${relative}`);
10
+ return absolute;
11
+ };
12
+ return {
13
+ async readFile(relative) {
14
+ const absolute = resolve(relative);
15
+ const real = await nodeFsPromises.realpath(absolute);
16
+ const realRoot = await nodeFsPromises.realpath(root);
17
+ if (real !== realRoot && !real.startsWith(realRoot + path.sep))
18
+ throw new Error(`Symlink escapes project root: ${relative}`);
19
+ return nodeFsPromises.readFile(real, "utf8");
20
+ },
21
+ async readDir(relative) {
22
+ const entries = await nodeFsPromises.readdir(resolve(relative), { withFileTypes: true });
23
+ return entries
24
+ .map((entry) => ({ name: entry.name, kind: kindOf(entry) }))
25
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
26
+ },
27
+ async exists(relative) {
28
+ try {
29
+ await nodeFsPromises.access(resolve(relative));
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
36
+ };
37
+ }
38
+ /** In-memory project for tests and fixtures. Keys are POSIX paths relative to the root. */
39
+ export function memoryFs(files) {
40
+ const normalized = new Map(Object.entries(files).map(([key, value]) => [normalize(key), value]));
41
+ const dirs = new Set(["."]);
42
+ for (const file of normalized.keys()) {
43
+ for (let dir = path.posix.dirname(file); dir !== "."; dir = path.posix.dirname(dir))
44
+ dirs.add(dir);
45
+ }
46
+ return {
47
+ async readFile(p) {
48
+ const content = normalized.get(normalize(p));
49
+ if (content === undefined)
50
+ throw new Error(`ENOENT: ${p}`);
51
+ return content;
52
+ },
53
+ async readDir(p) {
54
+ const dir = normalize(p);
55
+ if (!dirs.has(dir))
56
+ throw new Error(`ENOENT: ${p}`);
57
+ const prefix = dir === "." ? "" : `${dir}/`;
58
+ const children = new Map();
59
+ for (const file of normalized.keys()) {
60
+ if (!file.startsWith(prefix))
61
+ continue;
62
+ const [first, ...rest] = file.slice(prefix.length).split("/");
63
+ if (first)
64
+ children.set(first, rest.length > 0 ? "dir" : "file");
65
+ }
66
+ return [...children]
67
+ .map(([name, kind]) => ({ name, kind }))
68
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
69
+ },
70
+ async exists(p) {
71
+ const key = normalize(p);
72
+ return normalized.has(key) || dirs.has(key);
73
+ }
74
+ };
75
+ }
76
+ function kindOf(entry) {
77
+ if (entry.isSymbolicLink())
78
+ return "symlink";
79
+ if (entry.isDirectory())
80
+ return "dir";
81
+ if (entry.isFile())
82
+ return "file";
83
+ return "other";
84
+ }
85
+ function normalize(p) {
86
+ const result = path.posix.normalize(p.replace(/^\.\//, ""));
87
+ return result === "" ? "." : result.replace(/\/$/, "");
88
+ }
89
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1,3 @@
1
+ // The matcher lives in `@invariant/shared`, so that validation can use it without depending on a compiler.
2
+ export { globToRegExp, matchesAny } from "../shared/index.js";
3
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1,16 @@
1
+ export { INVENTORY_ADAPTERS, markdownDocsAdapter, npmPackageAdapter, storybookConfigAdapter, tokenFilesAdapter } from "./adapters/inventory.js";
2
+ export { compile, toArtifactEntries } from "./compile.js";
3
+ export { ComponentFacts, cssVariableReferences, variantsFromProps } from "./component-facts.js";
4
+ export { DISCOVERY_VERSION, discoverProject, parseCssCustomProperties, parsePnpmWorkspacePackages, projectDiscoverySchema, scanCssCustomProperties } from "./discovery/index.js";
5
+ export { cmp, fileExists, hashText, owningPackage, posix } from "./discovery/support.js";
6
+ export { memoryFs, nodeFs } from "./fs.js";
7
+ export { globToRegExp, matchesAny } from "./glob.js";
8
+ export { IrBuilder } from "./ir-builder.js";
9
+ export { discoveryJsonSchemas } from "./json-schema.js";
10
+ export { collapse, componentNameFromFile, declarationOf, excerpt, hasModifier, isProjectNode, jsxTargetDeclaration, lineOf, literalValue, relativeFileOf, resolveSymbol, subTarget, topLevelAssignments, typeReferenceInfo, unwrap, walkSameFunction } from "./ts/ast.js";
11
+ export { allowedValuesOf, classifyKind, declarationParentName, describeMember, externalBases, literalDomain, ownProperties } from "./ts/members.js";
12
+ export { createAnalysis } from "./ts/program.js";
13
+ export { evaluate } from "./ts/static-eval.js";
14
+ export { TSCONFIG_FILE, tsconfigPaths } from "./ts/tsconfig-paths.js";
15
+ export { DEFAULT_IGNORED_DIRS, walk } from "./walk.js";
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,68 @@
1
+ import { canonicalizeIrDocument, IR_VERSION } from "../compiler-ir/index.js";
2
+ import { computeEvidenceId } from "../contract/index.js";
3
+ const MAX_ORIGINAL = 2000;
4
+ /**
5
+ * Assembles an IR document. Evidence is content-addressed and deduplicated, every fact must cite
6
+ * evidence, and the result is canonical (sorted), so adapters cannot emit order-dependent output.
7
+ */
8
+ export class IrBuilder {
9
+ options;
10
+ entities = new Map();
11
+ facts = [];
12
+ evidenceById = new Map();
13
+ diagnostics = [];
14
+ constructor(options) {
15
+ this.options = options;
16
+ }
17
+ entity(ref, kind, name) {
18
+ this.entities.set(ref, { ref, kind, name });
19
+ return ref;
20
+ }
21
+ evidence(input) {
22
+ const original = input.original === undefined ? undefined : input.original.slice(0, MAX_ORIGINAL);
23
+ const base = {
24
+ sourceId: this.options.source.id,
25
+ sourceRef: input.sourceRef,
26
+ location: input.location,
27
+ method: input.method,
28
+ extractor: this.options.extractor,
29
+ ...(original === undefined ? {} : { original })
30
+ };
31
+ const evidence = {
32
+ ...base,
33
+ id: computeEvidenceId(base),
34
+ verificationStatus: "unverified",
35
+ capturedAt: this.options.clock()
36
+ };
37
+ if (!this.evidenceById.has(evidence.id))
38
+ this.evidenceById.set(evidence.id, evidence);
39
+ return evidence;
40
+ }
41
+ fact(subject, path, value, evidence, options = {}) {
42
+ const list = Array.isArray(evidence) ? evidence : [evidence];
43
+ this.facts.push({
44
+ subject,
45
+ path,
46
+ value,
47
+ origin: options.origin ?? "extracted",
48
+ evidenceIds: [...new Set(list.map((e) => e.id))].sort(),
49
+ ...(options.completeness ? { completeness: options.completeness } : {})
50
+ });
51
+ }
52
+ diagnostic(diagnostic) {
53
+ this.diagnostics.push({ ...diagnostic, sourceId: this.options.source.id });
54
+ }
55
+ build(completeness) {
56
+ return canonicalizeIrDocument({
57
+ irVersion: IR_VERSION,
58
+ source: this.options.source,
59
+ capabilities: this.options.capabilities,
60
+ completeness,
61
+ entities: [...this.entities.values()],
62
+ facts: this.facts,
63
+ evidence: [...this.evidenceById.values()],
64
+ diagnostics: this.diagnostics
65
+ });
66
+ }
67
+ }
68
+ //# sourceMappingURL=ir-builder.js.map
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ import { projectDiscoverySchema } from "./discovery/schema.js";
3
+ export function discoveryJsonSchemas() {
4
+ return {
5
+ "project-discovery-v1.schema.json": {
6
+ title: "Invariant project discovery v1",
7
+ ...z.toJSONSchema(projectDiscoverySchema)
8
+ }
9
+ };
10
+ }
11
+ //# sourceMappingURL=json-schema.js.map
@@ -0,0 +1,81 @@
1
+ import { irDocumentSchema, serializeIrDocument, verifyIrDocument } from "../../compiler-ir/index.js";
2
+ import { canonicalStringify, hasErrors } from "../../shared/index.js";
3
+ import { compile } from "../compile.js";
4
+ import { discoverProject } from "../discovery/index.js";
5
+ export { memoryFs } from "../fs.js";
6
+ /** A clock that always returns the same instant, for reproducible artifacts in tests. */
7
+ export const FIXED_CLOCK = () => "2026-01-01T00:00:00.000Z";
8
+ /**
9
+ * Conformance checks every source adapter must pass. Returns human-readable violations (empty when
10
+ * the adapter conforms), so it works with any test runner.
11
+ */
12
+ export async function checkAdapterConformance(adapter, options) {
13
+ const violations = [];
14
+ const project = await discoverProject({ fs: options.fs });
15
+ const before = canonicalStringify(project);
16
+ const typed = adapter;
17
+ const first = await typed.discover({ fs: options.fs, project });
18
+ const second = await typed.discover({ fs: options.fs, project });
19
+ if (canonicalStringify(first) !== canonicalStringify(second))
20
+ violations.push("discover() is not deterministic");
21
+ const ids = new Set();
22
+ for (const source of first.sources) {
23
+ if (source.type !== adapter.type)
24
+ violations.push(`source ${source.id} has type ${source.type}, adapter declares ${adapter.type}`);
25
+ if (ids.has(source.id))
26
+ violations.push(`duplicate source id ${source.id}`);
27
+ ids.add(source.id);
28
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(source.id))
29
+ violations.push(`source id ${source.id} is not a valid slug`);
30
+ if (!/^[0-9a-f]{64}$/.test(source.inputHash))
31
+ violations.push(`source ${source.id} has no valid inputHash`);
32
+ const context = { fs: options.fs, project, source, clock: FIXED_CLOCK };
33
+ const a = await typed.extract(context, source.config);
34
+ const b = await typed.extract(context, source.config);
35
+ if (!irDocumentSchema.safeParse(a.ir).success) {
36
+ violations.push(`extract(${source.id}) returned an IR document that fails the schema`);
37
+ continue;
38
+ }
39
+ if (serializeIrDocument(a.ir) !== serializeIrDocument(b.ir))
40
+ violations.push(`extract(${source.id}) is not deterministic`);
41
+ if (a.ir.source.id !== source.id)
42
+ violations.push(`IR source id ${a.ir.source.id} differs from discovered ${source.id}`);
43
+ if (a.ir.source.adapter.name !== adapter.id)
44
+ violations.push(`IR names adapter ${a.ir.source.adapter.name}, expected ${adapter.id}`);
45
+ const problems = verifyIrDocument(a.ir);
46
+ if (hasErrors(problems))
47
+ violations.push(`IR for ${source.id} has errors: ${problems
48
+ .filter((p) => p.severity === "error")
49
+ .map((p) => p.code)
50
+ .join(", ")}`);
51
+ const validation = await typed.validate({ project, source, ir: a.ir });
52
+ if (!Array.isArray(validation))
53
+ violations.push(`validate(${source.id}) did not return diagnostics`);
54
+ }
55
+ if (canonicalStringify(project) !== before)
56
+ violations.push("an adapter mutated the shared project discovery");
57
+ return violations;
58
+ }
59
+ /**
60
+ * Compiles twice with different clocks. Everything except evidence capture times must match, and the
61
+ * time-independent IR content hashes must be equal.
62
+ */
63
+ export async function checkCompileDeterminism(options) {
64
+ const violations = [];
65
+ const a = await compile({ ...options, clock: () => "2026-01-01T00:00:00.000Z" });
66
+ const b = await compile({ ...options, clock: () => "2031-06-30T12:34:56.000Z" });
67
+ if (canonicalStringify(a.project) !== canonicalStringify(b.project))
68
+ violations.push("project discovery differs between runs");
69
+ if (canonicalStringify(a.diagnostics) !== canonicalStringify(b.diagnostics))
70
+ violations.push("diagnostics differ between runs");
71
+ if (canonicalStringify(a.sources) !== canonicalStringify(b.sources))
72
+ violations.push("source summaries (including irContentHash) differ between runs");
73
+ const strip = (text) => text.replace(/"capturedAt": "[^"]+"/g, '"capturedAt": "-"');
74
+ a.irDocuments.forEach((doc, i) => {
75
+ const other = b.irDocuments[i];
76
+ if (!other || strip(serializeIrDocument(doc)) !== strip(serializeIrDocument(other)))
77
+ violations.push(`IR ${doc.source.id} differs beyond capture times`);
78
+ });
79
+ return violations;
80
+ }
81
+ //# sourceMappingURL=index.js.map