@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,297 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
2
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync, readdirSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { tmpdir } from "os";
5
+ import { fileURLToPath } from "url";
6
+ import { initLexiconCommand } from "./init-lexicon";
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const FIXTURE_DIR = join(__dirname, "__fixtures__", "init-lexicon-output");
10
+
11
+ function makeTmpDir(): string {
12
+ const dir = join(tmpdir(), `chant-init-lexicon-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
13
+ mkdirSync(dir, { recursive: true });
14
+ return dir;
15
+ }
16
+
17
+ describe("initLexiconCommand", () => {
18
+ let tmpDir: string;
19
+
20
+ beforeEach(() => {
21
+ tmpDir = makeTmpDir();
22
+ });
23
+
24
+ afterEach(() => {
25
+ rmSync(tmpDir, { recursive: true, force: true });
26
+ });
27
+
28
+ test("creates all expected files", async () => {
29
+ const targetDir = join(tmpDir, "test-lex");
30
+ const result = await initLexiconCommand({ name: "test-lex", path: targetDir });
31
+
32
+ expect(result.success).toBe(true);
33
+ expect(result.error).toBeUndefined();
34
+
35
+ const expectedFiles = [
36
+ "src/plugin.ts",
37
+ "src/index.ts",
38
+ "src/serializer.ts",
39
+ "src/codegen/generate.ts",
40
+ "src/codegen/generate-cli.ts",
41
+ "src/codegen/naming.ts",
42
+ "src/codegen/package.ts",
43
+ "src/codegen/rollback.ts",
44
+ "src/codegen/docs.ts",
45
+ "src/spec/fetch.ts",
46
+ "src/spec/parse.ts",
47
+ "src/lint/rules/sample.ts",
48
+ "src/lint/rules/index.ts",
49
+ "src/lsp/completions.ts",
50
+ "src/lsp/hover.ts",
51
+ "src/import/parser.ts",
52
+ "src/import/generator.ts",
53
+ "src/coverage.ts",
54
+ "src/validate.ts",
55
+ "src/validate-cli.ts",
56
+ "package.json",
57
+ "tsconfig.json",
58
+ "justfile",
59
+ ".gitignore",
60
+ "README.md",
61
+ "docs/package.json",
62
+ "docs/tsconfig.json",
63
+ "docs/astro.config.mjs",
64
+ "docs/src/content.config.ts",
65
+ "docs/src/content/docs/index.mdx",
66
+ "src/generated/.gitkeep",
67
+ "examples/getting-started/.gitkeep",
68
+ ".snapshots/.gitkeep",
69
+ ];
70
+
71
+ for (const file of expectedFiles) {
72
+ expect(existsSync(join(targetDir, file))).toBe(true);
73
+ }
74
+ });
75
+
76
+ test("plugin.ts contains all 5 required lifecycle methods", async () => {
77
+ const targetDir = join(tmpDir, "lifecycle");
78
+ await initLexiconCommand({ name: "k8s", path: targetDir });
79
+
80
+ const pluginContent = readFileSync(join(targetDir, "src/plugin.ts"), "utf-8");
81
+
82
+ expect(pluginContent).toContain("async generate(");
83
+ expect(pluginContent).toContain("async validate(");
84
+ expect(pluginContent).toContain("async coverage(");
85
+ expect(pluginContent).toContain("async package(");
86
+ expect(pluginContent).toContain("async rollback(");
87
+ });
88
+
89
+ test("package name uses the provided lexicon name", async () => {
90
+ const targetDir = join(tmpDir, "pkg-name");
91
+ await initLexiconCommand({ name: "gcp", path: targetDir });
92
+
93
+ const pkgJson = JSON.parse(readFileSync(join(targetDir, "package.json"), "utf-8"));
94
+ expect(pkgJson.name).toBe("@intentius/chant-lexicon-gcp");
95
+ });
96
+
97
+ test("rule prefix derived correctly", async () => {
98
+ const targetDir = join(tmpDir, "rule-prefix");
99
+ await initLexiconCommand({ name: "k8s", path: targetDir });
100
+
101
+ const ruleContent = readFileSync(join(targetDir, "src/lint/rules/sample.ts"), "utf-8");
102
+ expect(ruleContent).toContain('"K8S001"');
103
+
104
+ const serializerContent = readFileSync(join(targetDir, "src/serializer.ts"), "utf-8");
105
+ expect(serializerContent).toContain('rulePrefix: "K8S"');
106
+ });
107
+
108
+ test("rule prefix handles hyphens", async () => {
109
+ const targetDir = join(tmpDir, "rule-prefix-hyphen");
110
+ await initLexiconCommand({ name: "my-cloud", path: targetDir });
111
+
112
+ const ruleContent = readFileSync(join(targetDir, "src/lint/rules/sample.ts"), "utf-8");
113
+ // "my-cloud" -> "MYCLOUD" -> "MYC"
114
+ expect(ruleContent).toContain('"MYC001"');
115
+ });
116
+
117
+ test("refuses non-empty dir without --force", async () => {
118
+ const targetDir = join(tmpDir, "non-empty");
119
+ mkdirSync(targetDir, { recursive: true });
120
+ writeFileSync(join(targetDir, "existing.txt"), "hello");
121
+
122
+ const result = await initLexiconCommand({ name: "test", path: targetDir });
123
+
124
+ expect(result.success).toBe(false);
125
+ expect(result.error).toContain("not empty");
126
+ });
127
+
128
+ test("allows --force in non-empty dir", async () => {
129
+ const targetDir = join(tmpDir, "force");
130
+ mkdirSync(targetDir, { recursive: true });
131
+ writeFileSync(join(targetDir, "existing.txt"), "hello");
132
+
133
+ const result = await initLexiconCommand({ name: "test", path: targetDir, force: true });
134
+
135
+ expect(result.success).toBe(true);
136
+ expect(result.warnings).toContain("Initializing in non-empty directory");
137
+ });
138
+
139
+ test("warns on second run (skip existing files)", async () => {
140
+ const targetDir = join(tmpDir, "second-run");
141
+
142
+ const first = await initLexiconCommand({ name: "test", path: targetDir });
143
+ expect(first.success).toBe(true);
144
+ expect(first.warnings.length).toBe(0);
145
+
146
+ const second = await initLexiconCommand({ name: "test", path: targetDir, force: true });
147
+ expect(second.success).toBe(true);
148
+ expect(second.warnings.length).toBeGreaterThan(0);
149
+ expect(second.warnings.some((w) => w.includes("already exists"))).toBe(true);
150
+ });
151
+
152
+ test("uses camelCase for plugin variable names", async () => {
153
+ const targetDir = join(tmpDir, "camel");
154
+ await initLexiconCommand({ name: "my-cloud", path: targetDir });
155
+
156
+ const pluginContent = readFileSync(join(targetDir, "src/plugin.ts"), "utf-8");
157
+ expect(pluginContent).toContain("export const myCloudPlugin");
158
+
159
+ const serializerContent = readFileSync(join(targetDir, "src/serializer.ts"), "utf-8");
160
+ expect(serializerContent).toContain("export const myCloudSerializer");
161
+ });
162
+
163
+ test("defaults path to lexicons/<name>", async () => {
164
+ // We can't test the actual default easily without cd'ing,
165
+ // but we can verify the interface accepts no path
166
+ const targetDir = join(tmpDir, "default-path", "lexicons", "test");
167
+ mkdirSync(targetDir, { recursive: true });
168
+
169
+ const result = await initLexiconCommand({
170
+ name: "test",
171
+ path: targetDir,
172
+ });
173
+
174
+ expect(result.success).toBe(true);
175
+ });
176
+ });
177
+
178
+ // ── Scaffold content validation (bug fix regression tests) ──────────
179
+
180
+ describe("scaffold content validation", () => {
181
+ let tmpDir: string;
182
+ let targetDir: string;
183
+
184
+ beforeEach(async () => {
185
+ tmpDir = makeTmpDir();
186
+ targetDir = join(tmpDir, "fixture-lex");
187
+ await initLexiconCommand({ name: "fixture", path: targetDir });
188
+ });
189
+
190
+ afterEach(() => {
191
+ rmSync(tmpDir, { recursive: true, force: true });
192
+ });
193
+
194
+ test("writeGeneratedArtifacts uses WriteConfig object, not positional args", () => {
195
+ const content = readFileSync(join(targetDir, "src/codegen/generate.ts"), "utf-8");
196
+
197
+ // Must call writeGeneratedArtifacts with a config object
198
+ expect(content).toContain("writeGeneratedArtifacts({");
199
+ expect(content).toContain("baseDir:");
200
+ expect(content).toContain("files:");
201
+ expect(content).toContain('"lexicon.json": result.lexiconJSON');
202
+ expect(content).toContain('"index.d.ts": result.typesDTS');
203
+ expect(content).toContain('"index.ts": result.indexTS');
204
+
205
+ // Must NOT have the old positional-arg call
206
+ expect(content).not.toContain("writeGeneratedArtifacts(result, dir)");
207
+ });
208
+
209
+ test("packagePipeline config includes collectSkills", () => {
210
+ const content = readFileSync(join(targetDir, "src/codegen/package.ts"), "utf-8");
211
+
212
+ expect(content).toContain("collectSkills:");
213
+ expect(content).toContain("collectSkills: () => new Map()");
214
+ });
215
+
216
+ test("packagePipeline forwards force flag", () => {
217
+ const content = readFileSync(join(targetDir, "src/codegen/package.ts"), "utf-8");
218
+
219
+ expect(content).toContain("force: opts?.force");
220
+ });
221
+
222
+ test("generate.ts TODO stubs include return type hints", () => {
223
+ const content = readFileSync(join(targetDir, "src/codegen/generate.ts"), "utf-8");
224
+
225
+ // fetchSchemas hint
226
+ expect(content).toContain("Must return Map<typeName, Buffer>");
227
+ // parseSchema hint
228
+ expect(content).toContain("Must return a ParsedResult");
229
+ // generateRegistry hint
230
+ expect(content).toContain("Must return a string of JSON");
231
+ // generateRuntimeIndex hint
232
+ expect(content).toContain("Must return a string of TypeScript");
233
+ // AWS references
234
+ expect(content).toContain("lexicons/aws/");
235
+ });
236
+ });
237
+
238
+ // ── Fixture snapshot tests ──────────────────────────────────────────
239
+
240
+ describe("init-lexicon fixture snapshot", () => {
241
+ const FIXTURE_LEXICON_NAME = "fixture";
242
+
243
+ test("generate and snapshot fixture files", async () => {
244
+ // Generate scaffold into the fixture directory
245
+ // Use force to overwrite any stale fixtures
246
+ rmSync(FIXTURE_DIR, { recursive: true, force: true });
247
+ mkdirSync(FIXTURE_DIR, { recursive: true });
248
+
249
+ const result = await initLexiconCommand({
250
+ name: FIXTURE_LEXICON_NAME,
251
+ path: FIXTURE_DIR,
252
+ });
253
+
254
+ expect(result.success).toBe(true);
255
+
256
+ // Key files that must exist
257
+ const criticalFiles = [
258
+ "src/codegen/generate.ts",
259
+ "src/codegen/package.ts",
260
+ "src/plugin.ts",
261
+ "src/serializer.ts",
262
+ "package.json",
263
+ ];
264
+
265
+ for (const file of criticalFiles) {
266
+ expect(existsSync(join(FIXTURE_DIR, file))).toBe(true);
267
+ }
268
+ });
269
+
270
+ test("fixture generate.ts matches snapshot", async () => {
271
+ // Ensure fixture exists
272
+ if (!existsSync(join(FIXTURE_DIR, "src/codegen/generate.ts"))) {
273
+ await initLexiconCommand({ name: FIXTURE_LEXICON_NAME, path: FIXTURE_DIR });
274
+ }
275
+
276
+ const content = readFileSync(join(FIXTURE_DIR, "src/codegen/generate.ts"), "utf-8");
277
+ expect(content).toMatchSnapshot();
278
+ });
279
+
280
+ test("fixture package.ts matches snapshot", async () => {
281
+ if (!existsSync(join(FIXTURE_DIR, "src/codegen/package.ts"))) {
282
+ await initLexiconCommand({ name: FIXTURE_LEXICON_NAME, path: FIXTURE_DIR });
283
+ }
284
+
285
+ const content = readFileSync(join(FIXTURE_DIR, "src/codegen/package.ts"), "utf-8");
286
+ expect(content).toMatchSnapshot();
287
+ });
288
+
289
+ test("fixture plugin.ts matches snapshot", async () => {
290
+ if (!existsSync(join(FIXTURE_DIR, "src/plugin.ts"))) {
291
+ await initLexiconCommand({ name: FIXTURE_LEXICON_NAME, path: FIXTURE_DIR });
292
+ }
293
+
294
+ const content = readFileSync(join(FIXTURE_DIR, "src/plugin.ts"), "utf-8");
295
+ expect(content).toMatchSnapshot();
296
+ });
297
+ });