@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,157 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
2
+ import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from "fs";
3
+ import { join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { barrel } from "./barrel";
6
+
7
+ let tempDir: string;
8
+
9
+ beforeEach(() => {
10
+ tempDir = mkdtempSync(join(tmpdir(), "barrel-test-"));
11
+ });
12
+
13
+ afterEach(() => {
14
+ rmSync(tempDir, { recursive: true, force: true });
15
+ });
16
+
17
+ describe("barrel", () => {
18
+ test("returns exports from sibling .ts files", () => {
19
+ writeFileSync(
20
+ join(tempDir, "alpha.ts"),
21
+ "export const alphaValue = 42;",
22
+ );
23
+ writeFileSync(
24
+ join(tempDir, "beta.ts"),
25
+ "export const betaValue = 'hello';",
26
+ );
27
+
28
+ const $ = barrel(tempDir);
29
+
30
+ expect($.alphaValue).toBe(42);
31
+ expect($.betaValue).toBe("hello");
32
+ });
33
+
34
+ test("excludes files starting with underscore", () => {
35
+ writeFileSync(
36
+ join(tempDir, "_config.ts"),
37
+ "export const secret = 'hidden';",
38
+ );
39
+ writeFileSync(
40
+ join(tempDir, "visible.ts"),
41
+ "export const visible = true;",
42
+ );
43
+
44
+ const $ = barrel(tempDir);
45
+
46
+ expect($.secret).toBeUndefined();
47
+ expect($.visible).toBe(true);
48
+ });
49
+
50
+ test("excludes test files", () => {
51
+ writeFileSync(
52
+ join(tempDir, "foo.test.ts"),
53
+ "export const testVal = 1;",
54
+ );
55
+ writeFileSync(
56
+ join(tempDir, "bar.spec.ts"),
57
+ "export const specVal = 2;",
58
+ );
59
+ writeFileSync(
60
+ join(tempDir, "real.ts"),
61
+ "export const realVal = 3;",
62
+ );
63
+
64
+ const $ = barrel(tempDir);
65
+
66
+ expect($.testVal).toBeUndefined();
67
+ expect($.specVal).toBeUndefined();
68
+ expect($.realVal).toBe(3);
69
+ });
70
+
71
+ test("caches results after first access", () => {
72
+ writeFileSync(
73
+ join(tempDir, "mod.ts"),
74
+ "export const x = 1; export const y = 2;",
75
+ );
76
+
77
+ const $ = barrel(tempDir);
78
+
79
+ // First access triggers load
80
+ expect($.x).toBe(1);
81
+
82
+ // Write a new file after first load
83
+ writeFileSync(
84
+ join(tempDir, "late.ts"),
85
+ "export const lateVal = 99;",
86
+ );
87
+
88
+ // Should NOT pick up new file since cache is already populated
89
+ expect($.lateVal).toBeUndefined();
90
+ // Existing values still work
91
+ expect($.y).toBe(2);
92
+ });
93
+
94
+ test("returns undefined for missing keys", () => {
95
+ writeFileSync(
96
+ join(tempDir, "mod.ts"),
97
+ "export const exists = true;",
98
+ );
99
+
100
+ const $ = barrel(tempDir);
101
+
102
+ expect($.nonExistent).toBeUndefined();
103
+ });
104
+
105
+ test("has and ownKeys work correctly", () => {
106
+ writeFileSync(
107
+ join(tempDir, "mod.ts"),
108
+ "export const dataBucket = 's3://bucket';",
109
+ );
110
+
111
+ const $ = barrel(tempDir);
112
+
113
+ expect("dataBucket" in $).toBe(true);
114
+ expect("missing" in $).toBe(false);
115
+ expect(Object.keys($)).toEqual(["dataBucket"]);
116
+ });
117
+
118
+ test("handles empty directory", () => {
119
+ const $ = barrel(tempDir);
120
+
121
+ expect(Object.keys($)).toEqual([]);
122
+ expect($.anything).toBeUndefined();
123
+ });
124
+
125
+ test("handles load errors gracefully", () => {
126
+ writeFileSync(
127
+ join(tempDir, "broken.ts"),
128
+ "export const x = ;; SYNTAX ERROR @@#$",
129
+ );
130
+ writeFileSync(
131
+ join(tempDir, "good.ts"),
132
+ "export const goodVal = 'works';",
133
+ );
134
+
135
+ const $ = barrel(tempDir);
136
+
137
+ expect($.goodVal).toBe("works");
138
+ });
139
+
140
+ test("first export wins on name collision", () => {
141
+ // Files are sorted by readdirSync (OS-dependent), but we can verify
142
+ // that the proxy doesn't crash on collisions
143
+ writeFileSync(
144
+ join(tempDir, "aaa.ts"),
145
+ "export const shared = 'first';",
146
+ );
147
+ writeFileSync(
148
+ join(tempDir, "zzz.ts"),
149
+ "export const shared = 'second';",
150
+ );
151
+
152
+ const $ = barrel(tempDir);
153
+
154
+ // One of them wins (first by readdir order)
155
+ expect(typeof $.shared).toBe("string");
156
+ });
157
+ });
package/src/barrel.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { readdirSync, readFileSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ export function barrel(dir: string): Record<string, unknown> {
5
+ let allExports: Record<string, unknown> | null = null;
6
+
7
+ function load(): Record<string, unknown> {
8
+ if (allExports) return allExports;
9
+ allExports = {};
10
+
11
+ const files = readdirSync(dir)
12
+ .filter(
13
+ (f) =>
14
+ f.endsWith(".ts") &&
15
+ !f.startsWith("_") &&
16
+ !f.endsWith(".test.ts") &&
17
+ !f.endsWith(".spec.ts"),
18
+ )
19
+ .sort();
20
+
21
+ // Identify files that reference the barrel (.$. or .$[) — these
22
+ // may silently resolve cross-references to undefined if their
23
+ // dependency files haven't loaded yet
24
+ const barrelRefPattern = /\.\$[.\[]/;
25
+ const usesBarrel = new Set<string>();
26
+ for (const file of files) {
27
+ const src = readFileSync(join(dir, file), "utf-8");
28
+ if (barrelRefPattern.test(src)) {
29
+ usesBarrel.add(file);
30
+ }
31
+ }
32
+
33
+ function loadFile(file: string, overwrite = false): boolean {
34
+ const fullPath = join(dir, file);
35
+ try {
36
+ const mod = require(fullPath);
37
+ for (const [key, val] of Object.entries(mod)) {
38
+ if (val !== undefined && (overwrite || !(key in allExports!))) {
39
+ allExports![key] = val;
40
+ }
41
+ }
42
+ return true;
43
+ } catch {
44
+ // Clear require cache so retry re-executes the file
45
+ delete require.cache[require.resolve(fullPath)];
46
+ return false;
47
+ }
48
+ }
49
+
50
+ // First pass — load all files in alphabetical order
51
+ const failed: string[] = [];
52
+ for (const file of files) {
53
+ if (!loadFile(file)) {
54
+ failed.push(file);
55
+ }
56
+ }
57
+
58
+ // Retry files that threw — their dependencies are now available
59
+ for (const file of failed) {
60
+ loadFile(file);
61
+ }
62
+
63
+ // Second pass — reload files that reference the barrel so
64
+ // cross-references that silently resolved to undefined now
65
+ // pick up the correct values. Files without barrel references
66
+ // keep their original instances to preserve the reference graph.
67
+ for (const file of files) {
68
+ if (!usesBarrel.has(file)) continue;
69
+ const fullPath = join(dir, file);
70
+ try { delete require.cache[require.resolve(fullPath)]; } catch {}
71
+ }
72
+ for (const file of files) {
73
+ if (!usesBarrel.has(file)) continue;
74
+ loadFile(file, true);
75
+ }
76
+
77
+ return allExports;
78
+ }
79
+
80
+ return new Proxy<Record<string, unknown>>({}, {
81
+ get(_, prop: string | symbol) {
82
+ if (typeof prop === 'symbol') return undefined;
83
+ return load()[prop];
84
+ },
85
+ has(_, prop: string | symbol) {
86
+ if (typeof prop === 'symbol') return false;
87
+ return prop in load();
88
+ },
89
+ ownKeys(_) {
90
+ return Object.keys(load());
91
+ },
92
+ getOwnPropertyDescriptor(_, prop: string | symbol) {
93
+ if (typeof prop === 'symbol') return undefined;
94
+ const exports = load();
95
+ if (prop in exports) {
96
+ return { configurable: true, enumerable: true, value: exports[prop] };
97
+ }
98
+ return undefined;
99
+ },
100
+ });
101
+ }
@@ -0,0 +1,227 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { withTestDir } from "@intentius/chant-test-utils";
3
+ import { writeFile, mkdir } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { discover } from "./discovery/index";
6
+ import { runLint } from "./lint/engine";
7
+ import { build } from "./build";
8
+ import type { Serializer } from "./serializer";
9
+ import type { LintRule } from "./lint/rule";
10
+
11
+ // Import all core lint rules
12
+ import {
13
+ flatDeclarationsRule,
14
+ exportRequiredRule,
15
+ fileDeclarableLimitRule,
16
+ singleConcernFileRule,
17
+ preferNamespaceImportRule,
18
+ barrelImportStyleRule,
19
+ declarableNamingConventionRule,
20
+ noUnusedDeclarableImportRule,
21
+ noRedundantValueCastRule,
22
+ noUnusedDeclarableRule,
23
+ noCyclicDeclarableRefRule,
24
+ noRedundantTypeImportRule,
25
+ noStringRefRule,
26
+ enforceBarrelImportRule,
27
+ enforceBarrelRefRule,
28
+ evl001NonLiteralExpressionRule,
29
+ evl002ControlFlowResourceRule,
30
+ evl003DynamicPropertyAccessRule,
31
+ evl004SpreadNonConstRule,
32
+ evl005ResourceBlockBodyRule,
33
+ evl006BarrelUsageRule,
34
+ evl007InvalidSiblingsRule,
35
+ evl008UnresolvableBarrelRefRule,
36
+ } from "./lint/rules/index";
37
+
38
+ const coreRules: LintRule[] = [
39
+ flatDeclarationsRule,
40
+ exportRequiredRule,
41
+ fileDeclarableLimitRule,
42
+ singleConcernFileRule,
43
+ preferNamespaceImportRule,
44
+ barrelImportStyleRule,
45
+ declarableNamingConventionRule,
46
+ noUnusedDeclarableImportRule,
47
+ noRedundantValueCastRule,
48
+ noUnusedDeclarableRule,
49
+ noCyclicDeclarableRefRule,
50
+ noRedundantTypeImportRule,
51
+ noStringRefRule,
52
+ enforceBarrelImportRule,
53
+ enforceBarrelRefRule,
54
+ evl001NonLiteralExpressionRule,
55
+ evl002ControlFlowResourceRule,
56
+ evl003DynamicPropertyAccessRule,
57
+ evl004SpreadNonConstRule,
58
+ evl005ResourceBlockBodyRule,
59
+ evl006BarrelUsageRule,
60
+ evl007InvalidSiblingsRule,
61
+ evl008UnresolvableBarrelRefRule,
62
+ ];
63
+
64
+ interface FixtureSize {
65
+ name: string;
66
+ files: number;
67
+ entitiesPerFile: number;
68
+ }
69
+
70
+ const sizes: FixtureSize[] = [
71
+ { name: "Small", files: 4, entitiesPerFile: 2 },
72
+ { name: "Medium", files: 20, entitiesPerFile: 2 },
73
+ { name: "Large", files: 100, entitiesPerFile: 2 },
74
+ ];
75
+
76
+ function generateFixtureFile(index: number, entitiesPerFile: number): string {
77
+ const lines: string[] = [];
78
+ for (let i = 0; i < entitiesPerFile; i++) {
79
+ const n = index * entitiesPerFile + i;
80
+ lines.push(`export const config_${n} = {
81
+ lexicon: "bench",
82
+ entityType: "Config",
83
+ kind: "property",
84
+ [Symbol.for("chant.declarable")]: true,
85
+ algorithm: "AES256",
86
+ };
87
+
88
+ export const bucket_${n} = {
89
+ lexicon: "bench",
90
+ entityType: "Bucket",
91
+ [Symbol.for("chant.declarable")]: true,
92
+ bucketName: "bucket-${n}",
93
+ encryption: config_${n},
94
+ };`);
95
+ }
96
+ return lines.join("\n\n");
97
+ }
98
+
99
+ async function generateFixture(dir: string, size: FixtureSize): Promise<string[]> {
100
+ const files: string[] = [];
101
+ for (let i = 0; i < size.files; i++) {
102
+ const filePath = join(dir, `infra-${String(i).padStart(3, "0")}.ts`);
103
+ await writeFile(filePath, generateFixtureFile(i, size.entitiesPerFile));
104
+ files.push(filePath);
105
+ }
106
+ return files;
107
+ }
108
+
109
+ async function timeMs<T>(fn: () => Promise<T>): Promise<{ result: T; ms: number }> {
110
+ const start = performance.now();
111
+ const result = await fn();
112
+ return { result, ms: performance.now() - start };
113
+ }
114
+
115
+ async function benchmark<T>(fn: () => Promise<T>, runs: number = 5): Promise<{ min: number; avg: number; max: number; result: T }> {
116
+ const times: number[] = [];
117
+ let lastResult!: T;
118
+ for (let i = 0; i < runs; i++) {
119
+ const { result, ms } = await timeMs(fn);
120
+ times.push(ms);
121
+ lastResult = result;
122
+ }
123
+ return {
124
+ min: Math.min(...times),
125
+ avg: times.reduce((a, b) => a + b, 0) / times.length,
126
+ max: Math.max(...times),
127
+ result: lastResult,
128
+ };
129
+ }
130
+
131
+ function fmt(ms: number): string {
132
+ return `${ms.toFixed(1)}ms`;
133
+ }
134
+
135
+ function pad(s: string, n: number): string {
136
+ return s.padEnd(n);
137
+ }
138
+
139
+ const benchSerializer: Serializer = {
140
+ name: "bench",
141
+ rulePrefix: "BENCH",
142
+ serialize: (entities) => JSON.stringify({ count: entities.size }),
143
+ };
144
+
145
+ describe("performance benchmarks", () => {
146
+ test("benchmark suite", async () => {
147
+ const output: string[] = [];
148
+ output.push("");
149
+ output.push("chant performance benchmarks");
150
+ output.push("============================");
151
+ output.push("");
152
+
153
+ // --- Fixture generation ---
154
+ output.push("Fixture generation:");
155
+
156
+ for (const size of sizes) {
157
+ await withTestDir(async (dir) => {
158
+ const totalEntities = size.files * size.entitiesPerFile;
159
+ const { ms } = await timeMs(() => generateFixture(dir, size));
160
+ output.push(` ${pad(`${size.name} (${size.files} files, ~${totalEntities} entities)`, 42)} ${fmt(ms)}`);
161
+ });
162
+ }
163
+
164
+ output.push("");
165
+
166
+ // --- Discovery ---
167
+ output.push("Discovery:");
168
+
169
+ for (const size of sizes) {
170
+ await withTestDir(async (dir) => {
171
+ await generateFixture(dir, size);
172
+ const { avg } = await benchmark(() => discover(dir), 3);
173
+ output.push(` ${pad(size.name, 10)} ${fmt(avg)} (avg of 3 runs)`);
174
+ });
175
+ }
176
+
177
+ output.push("");
178
+
179
+ // --- Lint ---
180
+ output.push(`Lint (${coreRules.length} core rules):`);
181
+
182
+ for (const size of sizes) {
183
+ await withTestDir(async (dir) => {
184
+ const files = await generateFixture(dir, size);
185
+ const { avg } = await benchmark(() => runLint(files, coreRules), 3);
186
+ output.push(` ${pad(size.name, 10)} ${fmt(avg)} (avg of 3 runs)`);
187
+ });
188
+ }
189
+
190
+ output.push("");
191
+
192
+ // --- Build ---
193
+ output.push("Build (full pipeline):");
194
+
195
+ for (const size of sizes) {
196
+ await withTestDir(async (dir) => {
197
+ await generateFixture(dir, size);
198
+ const { avg } = await benchmark(() => build(dir, [benchSerializer]), 3);
199
+ output.push(` ${pad(size.name, 10)} ${fmt(avg)} (avg of 3 runs)`);
200
+ });
201
+ }
202
+
203
+ output.push("");
204
+
205
+ // --- Startup ---
206
+ output.push("Startup:");
207
+
208
+ {
209
+ const { ms } = await timeMs(async () => {
210
+ await import("@intentius/chant");
211
+ });
212
+ output.push(` import("@intentius/chant") ${fmt(ms)}`);
213
+ }
214
+
215
+ output.push("");
216
+
217
+ // Print results
218
+ const report = output.join("\n");
219
+ console.log(report);
220
+
221
+ // Sanity assertions — just verify the benchmarks actually ran
222
+ expect(report).toContain("Discovery:");
223
+ expect(report).toContain("Lint");
224
+ expect(report).toContain("Build");
225
+ expect(report).toContain("Startup:");
226
+ }, 120_000);
227
+ });