@intentius/chant 0.0.1

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 (271) hide show
  1. package/README.md +365 -0
  2. package/package.json +22 -0
  3. package/src/attrref.test.ts +148 -0
  4. package/src/attrref.ts +50 -0
  5. package/src/barrel.test.ts +157 -0
  6. package/src/barrel.ts +101 -0
  7. package/src/bench.test.ts +227 -0
  8. package/src/build.test.ts +437 -0
  9. package/src/build.ts +425 -0
  10. package/src/builder.test.ts +312 -0
  11. package/src/builder.ts +56 -0
  12. package/src/child-project.ts +44 -0
  13. package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
  14. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
  15. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
  16. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
  17. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
  18. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
  19. package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
  20. package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
  21. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
  22. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
  23. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
  24. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
  25. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
  26. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
  27. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
  28. package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
  29. package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
  30. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
  31. package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
  32. package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
  33. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
  34. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
  35. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
  36. package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
  37. package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
  38. package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
  39. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
  40. package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
  41. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
  42. package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
  43. package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
  44. package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
  45. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
  46. package/src/cli/commands/build.test.ts +149 -0
  47. package/src/cli/commands/build.ts +344 -0
  48. package/src/cli/commands/diff.test.ts +148 -0
  49. package/src/cli/commands/diff.ts +221 -0
  50. package/src/cli/commands/doctor.test.ts +239 -0
  51. package/src/cli/commands/doctor.ts +224 -0
  52. package/src/cli/commands/import.test.ts +379 -0
  53. package/src/cli/commands/import.ts +335 -0
  54. package/src/cli/commands/init-lexicon.test.ts +297 -0
  55. package/src/cli/commands/init-lexicon.ts +993 -0
  56. package/src/cli/commands/init.test.ts +317 -0
  57. package/src/cli/commands/init.ts +505 -0
  58. package/src/cli/commands/licenses.ts +165 -0
  59. package/src/cli/commands/lint.test.ts +332 -0
  60. package/src/cli/commands/lint.ts +408 -0
  61. package/src/cli/commands/list.test.ts +100 -0
  62. package/src/cli/commands/list.ts +108 -0
  63. package/src/cli/commands/update.test.ts +38 -0
  64. package/src/cli/commands/update.ts +207 -0
  65. package/src/cli/conflict-check.test.ts +255 -0
  66. package/src/cli/conflict-check.ts +89 -0
  67. package/src/cli/debug.ts +8 -0
  68. package/src/cli/format.test.ts +140 -0
  69. package/src/cli/format.ts +133 -0
  70. package/src/cli/handlers/build.ts +58 -0
  71. package/src/cli/handlers/dev.ts +38 -0
  72. package/src/cli/handlers/init.ts +46 -0
  73. package/src/cli/handlers/lint.ts +36 -0
  74. package/src/cli/handlers/misc.ts +57 -0
  75. package/src/cli/handlers/serve.ts +26 -0
  76. package/src/cli/index.ts +3 -0
  77. package/src/cli/lsp/capabilities.ts +46 -0
  78. package/src/cli/lsp/diagnostics.ts +52 -0
  79. package/src/cli/lsp/server.test.ts +618 -0
  80. package/src/cli/lsp/server.ts +393 -0
  81. package/src/cli/main.test.ts +257 -0
  82. package/src/cli/main.ts +224 -0
  83. package/src/cli/mcp/resources/context.ts +59 -0
  84. package/src/cli/mcp/server.test.ts +747 -0
  85. package/src/cli/mcp/server.ts +402 -0
  86. package/src/cli/mcp/tools/build.ts +117 -0
  87. package/src/cli/mcp/tools/import.ts +48 -0
  88. package/src/cli/mcp/tools/lint.ts +45 -0
  89. package/src/cli/plugins.test.ts +31 -0
  90. package/src/cli/plugins.ts +94 -0
  91. package/src/cli/registry.ts +73 -0
  92. package/src/cli/reporters/stylish.test.ts +282 -0
  93. package/src/cli/reporters/stylish.ts +186 -0
  94. package/src/cli/watch.test.ts +81 -0
  95. package/src/cli/watch.ts +101 -0
  96. package/src/codegen/case.test.ts +30 -0
  97. package/src/codegen/case.ts +11 -0
  98. package/src/codegen/coverage.ts +167 -0
  99. package/src/codegen/docs.ts +634 -0
  100. package/src/codegen/fetch.test.ts +119 -0
  101. package/src/codegen/fetch.ts +261 -0
  102. package/src/codegen/generate-registry.test.ts +118 -0
  103. package/src/codegen/generate-registry.ts +107 -0
  104. package/src/codegen/generate-runtime-index.test.ts +81 -0
  105. package/src/codegen/generate-runtime-index.ts +99 -0
  106. package/src/codegen/generate-typescript.test.ts +146 -0
  107. package/src/codegen/generate-typescript.ts +161 -0
  108. package/src/codegen/generate.ts +206 -0
  109. package/src/codegen/json-patch.test.ts +113 -0
  110. package/src/codegen/json-patch.ts +151 -0
  111. package/src/codegen/json-schema.test.ts +196 -0
  112. package/src/codegen/json-schema.ts +209 -0
  113. package/src/codegen/naming.ts +201 -0
  114. package/src/codegen/package.ts +161 -0
  115. package/src/codegen/rollback.test.ts +92 -0
  116. package/src/codegen/rollback.ts +115 -0
  117. package/src/codegen/topo-sort.test.ts +69 -0
  118. package/src/codegen/topo-sort.ts +46 -0
  119. package/src/codegen/typecheck.test.ts +37 -0
  120. package/src/codegen/typecheck.ts +74 -0
  121. package/src/codegen/validate.test.ts +86 -0
  122. package/src/codegen/validate.ts +143 -0
  123. package/src/composite.test.ts +426 -0
  124. package/src/composite.ts +243 -0
  125. package/src/config.test.ts +91 -0
  126. package/src/config.ts +87 -0
  127. package/src/declarable.test.ts +160 -0
  128. package/src/declarable.ts +47 -0
  129. package/src/detectLexicon.test.ts +236 -0
  130. package/src/detectLexicon.ts +37 -0
  131. package/src/discovery/cache.test.ts +78 -0
  132. package/src/discovery/cache.ts +86 -0
  133. package/src/discovery/collect.test.ts +269 -0
  134. package/src/discovery/collect.ts +51 -0
  135. package/src/discovery/cycles.test.ts +238 -0
  136. package/src/discovery/cycles.ts +107 -0
  137. package/src/discovery/files.test.ts +154 -0
  138. package/src/discovery/files.ts +61 -0
  139. package/src/discovery/graph.test.ts +476 -0
  140. package/src/discovery/graph.ts +150 -0
  141. package/src/discovery/import.test.ts +199 -0
  142. package/src/discovery/import.ts +20 -0
  143. package/src/discovery/index.test.ts +272 -0
  144. package/src/discovery/index.ts +132 -0
  145. package/src/discovery/resolve.test.ts +267 -0
  146. package/src/discovery/resolve.ts +54 -0
  147. package/src/errors.test.ts +138 -0
  148. package/src/errors.ts +86 -0
  149. package/src/import/base-parser.test.ts +67 -0
  150. package/src/import/base-parser.ts +48 -0
  151. package/src/import/generator.ts +21 -0
  152. package/src/import/ir-utils.test.ts +103 -0
  153. package/src/import/ir-utils.ts +87 -0
  154. package/src/import/parser.ts +41 -0
  155. package/src/index.ts +60 -0
  156. package/src/intrinsic-interpolation.test.ts +91 -0
  157. package/src/intrinsic-interpolation.ts +89 -0
  158. package/src/intrinsic.test.ts +69 -0
  159. package/src/intrinsic.ts +43 -0
  160. package/src/lexicon-integrity.test.ts +94 -0
  161. package/src/lexicon-integrity.ts +69 -0
  162. package/src/lexicon-manifest.test.ts +101 -0
  163. package/src/lexicon-manifest.ts +71 -0
  164. package/src/lexicon-output.test.ts +182 -0
  165. package/src/lexicon-output.ts +82 -0
  166. package/src/lexicon-schema.test.ts +239 -0
  167. package/src/lexicon-schema.ts +144 -0
  168. package/src/lexicon.ts +212 -0
  169. package/src/lint/config-overrides.test.ts +254 -0
  170. package/src/lint/config.test.ts +644 -0
  171. package/src/lint/config.ts +375 -0
  172. package/src/lint/declarative.test.ts +256 -0
  173. package/src/lint/declarative.ts +187 -0
  174. package/src/lint/engine.test.ts +465 -0
  175. package/src/lint/engine.ts +172 -0
  176. package/src/lint/named-checks.test.ts +37 -0
  177. package/src/lint/named-checks.ts +33 -0
  178. package/src/lint/parser.test.ts +129 -0
  179. package/src/lint/parser.ts +42 -0
  180. package/src/lint/post-synth.test.ts +113 -0
  181. package/src/lint/post-synth.ts +76 -0
  182. package/src/lint/presets/relaxed.json +19 -0
  183. package/src/lint/presets/strict.json +19 -0
  184. package/src/lint/rule-loader.test.ts +67 -0
  185. package/src/lint/rule-loader.ts +67 -0
  186. package/src/lint/rule-options.test.ts +141 -0
  187. package/src/lint/rule.test.ts +196 -0
  188. package/src/lint/rule.ts +98 -0
  189. package/src/lint/rules/barrel-import-style.test.ts +80 -0
  190. package/src/lint/rules/barrel-import-style.ts +59 -0
  191. package/src/lint/rules/composite-scope.ts +55 -0
  192. package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
  193. package/src/lint/rules/cor017-composite-name-match.ts +108 -0
  194. package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
  195. package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
  196. package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
  197. package/src/lint/rules/declarable-naming-convention.ts +70 -0
  198. package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
  199. package/src/lint/rules/enforce-barrel-import.ts +81 -0
  200. package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
  201. package/src/lint/rules/enforce-barrel-ref.ts +75 -0
  202. package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
  203. package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
  204. package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
  205. package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
  206. package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
  207. package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
  208. package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
  209. package/src/lint/rules/evl004-spread-non-const.ts +111 -0
  210. package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
  211. package/src/lint/rules/evl005-resource-block-body.ts +49 -0
  212. package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
  213. package/src/lint/rules/evl006-barrel-usage.ts +95 -0
  214. package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
  215. package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
  216. package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
  217. package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
  218. package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
  219. package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
  220. package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
  221. package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
  222. package/src/lint/rules/export-required.test.ts +213 -0
  223. package/src/lint/rules/export-required.ts +158 -0
  224. package/src/lint/rules/file-declarable-limit.test.ts +148 -0
  225. package/src/lint/rules/file-declarable-limit.ts +96 -0
  226. package/src/lint/rules/flat-declarations.test.ts +210 -0
  227. package/src/lint/rules/flat-declarations.ts +70 -0
  228. package/src/lint/rules/index.ts +99 -0
  229. package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
  230. package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
  231. package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
  232. package/src/lint/rules/no-redundant-type-import.ts +85 -0
  233. package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
  234. package/src/lint/rules/no-redundant-value-cast.ts +46 -0
  235. package/src/lint/rules/no-string-ref.test.ts +100 -0
  236. package/src/lint/rules/no-string-ref.ts +66 -0
  237. package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
  238. package/src/lint/rules/no-unused-declarable-import.ts +103 -0
  239. package/src/lint/rules/no-unused-declarable.test.ts +134 -0
  240. package/src/lint/rules/no-unused-declarable.ts +118 -0
  241. package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
  242. package/src/lint/rules/prefer-namespace-import.ts +63 -0
  243. package/src/lint/rules/single-concern-file.test.ts +156 -0
  244. package/src/lint/rules/single-concern-file.ts +98 -0
  245. package/src/lint/rules/stale-barrel-types.ts +60 -0
  246. package/src/lint/selectors.test.ts +113 -0
  247. package/src/lint/selectors.ts +188 -0
  248. package/src/lsp/lexicon-providers.ts +191 -0
  249. package/src/lsp/types.ts +79 -0
  250. package/src/mcp/types.ts +22 -0
  251. package/src/project/scan.test.ts +178 -0
  252. package/src/project/scan.ts +182 -0
  253. package/src/project/sync.test.ts +87 -0
  254. package/src/project/sync.ts +46 -0
  255. package/src/project-validation.test.ts +64 -0
  256. package/src/project-validation.ts +79 -0
  257. package/src/pseudo-parameter.test.ts +39 -0
  258. package/src/pseudo-parameter.ts +47 -0
  259. package/src/runtime.ts +68 -0
  260. package/src/serializer-walker.test.ts +124 -0
  261. package/src/serializer-walker.ts +83 -0
  262. package/src/serializer.ts +42 -0
  263. package/src/sort.test.ts +290 -0
  264. package/src/sort.ts +58 -0
  265. package/src/stack-output.ts +82 -0
  266. package/src/types.test.ts +307 -0
  267. package/src/types.ts +46 -0
  268. package/src/utils.test.ts +195 -0
  269. package/src/utils.ts +46 -0
  270. package/src/validation.test.ts +308 -0
  271. package/src/validation.ts +50 -0
@@ -0,0 +1,37 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { typecheckDTS } from "./typecheck";
3
+
4
+ describe("typecheckDTS", () => {
5
+ test("passes valid .d.ts", async () => {
6
+ const content = `
7
+ export declare class Bucket {
8
+ readonly type: string;
9
+ readonly bucketName?: string;
10
+ }
11
+ `;
12
+ const result = await typecheckDTS(content);
13
+ expect(result.ok).toBe(true);
14
+ expect(result.diagnostics).toHaveLength(0);
15
+ });
16
+
17
+ test("fails on undefined type reference", async () => {
18
+ const content = `
19
+ export declare class Broken {
20
+ readonly value: UndefinedType;
21
+ }
22
+ `;
23
+ const result = await typecheckDTS(content);
24
+ expect(result.ok).toBe(false);
25
+ expect(result.diagnostics.length).toBeGreaterThan(0);
26
+ });
27
+
28
+ test("fails on syntax error", async () => {
29
+ const content = `
30
+ export declare class {
31
+ this is not valid typescript
32
+ }
33
+ `;
34
+ const result = await typecheckDTS(content);
35
+ expect(result.ok).toBe(false);
36
+ });
37
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Type-check generated .d.ts files by spawning tsc.
3
+ */
4
+ import { writeFileSync, mkdirSync, rmSync } from "fs";
5
+ import { join } from "path";
6
+ import { tmpdir } from "os";
7
+
8
+ export interface TypeCheckResult {
9
+ ok: boolean;
10
+ diagnostics: string[];
11
+ fileCount: number;
12
+ }
13
+
14
+ /**
15
+ * Type-check a .d.ts content string by writing to a temp file and running tsc.
16
+ */
17
+ export async function typecheckDTS(content: string): Promise<TypeCheckResult> {
18
+ // Create temp directory
19
+ const dir = join(tmpdir(), `chant-typecheck-${Date.now()}-${Math.random().toString(36).slice(2)}`);
20
+ mkdirSync(dir, { recursive: true });
21
+
22
+ try {
23
+ // Write the .d.ts file
24
+ const dtsPath = join(dir, "index.d.ts");
25
+ writeFileSync(dtsPath, content);
26
+
27
+ // Write a minimal tsconfig
28
+ const tsconfig = {
29
+ compilerOptions: {
30
+ strict: true,
31
+ noEmit: true,
32
+ skipLibCheck: false,
33
+ target: "ES2022",
34
+ module: "ES2022",
35
+ moduleResolution: "bundler",
36
+ },
37
+ include: ["index.d.ts"],
38
+ };
39
+ writeFileSync(join(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
40
+
41
+ // Run tsc
42
+ const proc = Bun.spawn(["bunx", "tsc", "--noEmit", "--project", "tsconfig.json"], {
43
+ cwd: dir,
44
+ stdout: "pipe",
45
+ stderr: "pipe",
46
+ });
47
+
48
+ const [stdout, stderr] = await Promise.all([
49
+ new Response(proc.stdout).text(),
50
+ new Response(proc.stderr).text(),
51
+ ]);
52
+ const exitCode = await proc.exited;
53
+
54
+ // Parse diagnostics from stdout (tsc writes errors to stdout)
55
+ const output = stdout + stderr;
56
+ const diagnostics = output
57
+ .split("\n")
58
+ .filter((line) => line.trim().length > 0)
59
+ .filter((line) => /error TS\d+/.test(line) || /:\d+:\d+/.test(line));
60
+
61
+ return {
62
+ ok: exitCode === 0,
63
+ diagnostics,
64
+ fileCount: 1,
65
+ };
66
+ } finally {
67
+ // Cleanup
68
+ try {
69
+ rmSync(dir, { recursive: true, force: true });
70
+ } catch {
71
+ // Best effort cleanup
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,86 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { mkdirSync, writeFileSync, rmSync } from "fs";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { validateLexiconArtifacts } from "./validate";
6
+
7
+ function makeTempDir(): string {
8
+ const dir = join(tmpdir(), `chant-validate-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
9
+ mkdirSync(dir, { recursive: true });
10
+ return dir;
11
+ }
12
+
13
+ describe("validateLexiconArtifacts", () => {
14
+ test("fails when lexicon JSON is missing", async () => {
15
+ const dir = makeTempDir();
16
+ const genDir = join(dir, "src", "generated");
17
+ mkdirSync(genDir, { recursive: true });
18
+
19
+ const result = await validateLexiconArtifacts({
20
+ lexiconJsonFilename: "lexicon-test.json",
21
+ requiredNames: ["Resource"],
22
+ basePath: dir,
23
+ });
24
+
25
+ expect(result.success).toBe(false);
26
+ const jsonCheck = result.checks.find((c) => c.name === "lexicon-json-exists");
27
+ expect(jsonCheck?.ok).toBe(false);
28
+ expect(jsonCheck?.error).toContain("lexicon-test.json not found");
29
+
30
+ rmSync(dir, { recursive: true, force: true });
31
+ });
32
+
33
+ test("passes with valid artifacts and required names", async () => {
34
+ const dir = makeTempDir();
35
+ const genDir = join(dir, "src", "generated");
36
+ mkdirSync(genDir, { recursive: true });
37
+
38
+ const lexiconData = {
39
+ Resource: {
40
+ resourceType: "Test::Storage::Bucket",
41
+ kind: "resource",
42
+ lexicon: "test",
43
+ attrs: { arn: "Arn" },
44
+ createOnly: ["/properties/Name"],
45
+ propertyConstraints: { Name: { minLength: 1 } },
46
+ constraints: [{ name: "c1", type: "required_or" }],
47
+ },
48
+ };
49
+ writeFileSync(join(genDir, "lexicon-test.json"), JSON.stringify(lexiconData));
50
+ writeFileSync(join(genDir, "index.d.ts"), "export declare class Resource { readonly type: string; }");
51
+
52
+ const result = await validateLexiconArtifacts({
53
+ lexiconJsonFilename: "lexicon-test.json",
54
+ requiredNames: ["Resource"],
55
+ basePath: dir,
56
+ coverageThresholds: { minPropertyPct: 1 },
57
+ });
58
+
59
+ expect(result.success).toBe(true);
60
+ expect(result.checks.every((c) => c.ok)).toBe(true);
61
+
62
+ rmSync(dir, { recursive: true, force: true });
63
+ });
64
+
65
+ test("detects missing required names", async () => {
66
+ const dir = makeTempDir();
67
+ const genDir = join(dir, "src", "generated");
68
+ mkdirSync(genDir, { recursive: true });
69
+
70
+ writeFileSync(join(genDir, "lexicon-test.json"), JSON.stringify({ Foo: { resourceType: "T", kind: "resource", lexicon: "t" } }));
71
+ writeFileSync(join(genDir, "index.d.ts"), "export {};");
72
+
73
+ const result = await validateLexiconArtifacts({
74
+ lexiconJsonFilename: "lexicon-test.json",
75
+ requiredNames: ["Bar", "Baz"],
76
+ basePath: dir,
77
+ });
78
+
79
+ const namesCheck = result.checks.find((c) => c.name === "required-names");
80
+ expect(namesCheck?.ok).toBe(false);
81
+ expect(namesCheck?.error).toContain("Bar");
82
+ expect(namesCheck?.error).toContain("Baz");
83
+
84
+ rmSync(dir, { recursive: true, force: true });
85
+ });
86
+ });
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Generic validation framework for lexicon artifacts.
3
+ *
4
+ * Provides configurable validation checks that any lexicon can use
5
+ * by passing lexicon-specific configuration.
6
+ */
7
+
8
+ import { existsSync, readFileSync } from "fs";
9
+ import { join } from "path";
10
+ import { computeCoverage, checkThresholds, type CoverageThresholds } from "./coverage";
11
+
12
+ export interface ValidateCheck {
13
+ name: string;
14
+ ok: boolean;
15
+ error?: string;
16
+ }
17
+
18
+ export interface ValidateResult {
19
+ success: boolean;
20
+ checks: ValidateCheck[];
21
+ }
22
+
23
+ export interface LexiconValidationConfig {
24
+ /** Filename of the lexicon JSON (e.g. "lexicon-mydom.json") */
25
+ lexiconJsonFilename: string;
26
+ /** Required backward-compatible export names to check in lexicon JSON */
27
+ requiredNames: string[];
28
+ /** Base path of the lexicon package */
29
+ basePath: string;
30
+ /** Path to the generated directory (defaults to basePath/src/generated) */
31
+ generatedDir?: string;
32
+ /** Coverage thresholds (optional) */
33
+ coverageThresholds?: CoverageThresholds;
34
+ }
35
+
36
+ /**
37
+ * Validate generated lexicon artifacts using the provided configuration.
38
+ */
39
+ export async function validateLexiconArtifacts(config: LexiconValidationConfig): Promise<ValidateResult> {
40
+ const generatedDir = config.generatedDir ?? join(config.basePath, "src", "generated");
41
+ const checks: ValidateCheck[] = [];
42
+
43
+ // Check 1: lexicon JSON exists and parses
44
+ const lexiconPath = join(generatedDir, config.lexiconJsonFilename);
45
+ let lexiconData: Record<string, unknown> | null = null;
46
+
47
+ if (!existsSync(lexiconPath)) {
48
+ checks.push({ name: "lexicon-json-exists", ok: false, error: `${config.lexiconJsonFilename} not found` });
49
+ } else {
50
+ try {
51
+ const raw = readFileSync(lexiconPath, "utf-8");
52
+ lexiconData = JSON.parse(raw);
53
+ checks.push({ name: "lexicon-json-exists", ok: true });
54
+ } catch (err) {
55
+ checks.push({
56
+ name: "lexicon-json-exists",
57
+ ok: false,
58
+ error: `Failed to parse ${config.lexiconJsonFilename}: ${err instanceof Error ? err.message : String(err)}`,
59
+ });
60
+ }
61
+ }
62
+
63
+ // Check 2: index.d.ts exists
64
+ const dtsPath = join(generatedDir, "index.d.ts");
65
+ if (!existsSync(dtsPath)) {
66
+ checks.push({ name: "types-exist", ok: false, error: "index.d.ts not found" });
67
+ } else {
68
+ checks.push({ name: "types-exist", ok: true });
69
+ }
70
+
71
+ // Check 3: Required backward-compatible names present in lexicon JSON
72
+ if (lexiconData && config.requiredNames.length > 0) {
73
+ const missing = config.requiredNames.filter((name) => !(name in lexiconData!));
74
+ if (missing.length > 0) {
75
+ checks.push({
76
+ name: "required-names",
77
+ ok: false,
78
+ error: `Missing required names: ${missing.join(", ")}`,
79
+ });
80
+ } else {
81
+ checks.push({ name: "required-names", ok: true });
82
+ }
83
+ } else if (!lexiconData && config.requiredNames.length > 0) {
84
+ checks.push({
85
+ name: "required-names",
86
+ ok: false,
87
+ error: "Skipped — lexicon JSON not available",
88
+ });
89
+ }
90
+
91
+ // Check 4: Coverage thresholds (only if lexicon JSON was loaded)
92
+ if (lexiconData && config.coverageThresholds) {
93
+ try {
94
+ const raw = readFileSync(lexiconPath, "utf-8");
95
+ const report = computeCoverage(raw);
96
+ const result = checkThresholds(report, config.coverageThresholds);
97
+ if (result.ok) {
98
+ checks.push({ name: "coverage-thresholds", ok: true });
99
+ } else {
100
+ checks.push({
101
+ name: "coverage-thresholds",
102
+ ok: false,
103
+ error: result.violations.join("; "),
104
+ });
105
+ }
106
+ } catch (err) {
107
+ checks.push({
108
+ name: "coverage-thresholds",
109
+ ok: false,
110
+ error: `Coverage check failed: ${err instanceof Error ? err.message : String(err)}`,
111
+ });
112
+ }
113
+ }
114
+
115
+ // Check 5: Type-check generated .d.ts
116
+ if (existsSync(dtsPath)) {
117
+ try {
118
+ const { typecheckDTS } = await import("./typecheck");
119
+ const dtsContent = readFileSync(dtsPath, "utf-8");
120
+ const tcResult = await typecheckDTS(dtsContent);
121
+ if (tcResult.ok) {
122
+ checks.push({ name: "types-compile", ok: true });
123
+ } else {
124
+ checks.push({
125
+ name: "types-compile",
126
+ ok: false,
127
+ error: `TypeScript errors: ${tcResult.diagnostics.slice(0, 5).join("; ")}`,
128
+ });
129
+ }
130
+ } catch (err) {
131
+ checks.push({
132
+ name: "types-compile",
133
+ ok: false,
134
+ error: `Type-check failed: ${err instanceof Error ? err.message : String(err)}`,
135
+ });
136
+ }
137
+ }
138
+
139
+ return {
140
+ success: checks.every((c) => c.ok),
141
+ checks,
142
+ };
143
+ }