@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.
- package/README.md +365 -0
- package/package.json +22 -0
- package/src/attrref.test.ts +148 -0
- package/src/attrref.ts +50 -0
- package/src/barrel.test.ts +157 -0
- package/src/barrel.ts +101 -0
- package/src/bench.test.ts +227 -0
- package/src/build.test.ts +437 -0
- package/src/build.ts +425 -0
- package/src/builder.test.ts +312 -0
- package/src/builder.ts +56 -0
- package/src/child-project.ts +44 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
- package/src/cli/commands/build.test.ts +149 -0
- package/src/cli/commands/build.ts +344 -0
- package/src/cli/commands/diff.test.ts +148 -0
- package/src/cli/commands/diff.ts +221 -0
- package/src/cli/commands/doctor.test.ts +239 -0
- package/src/cli/commands/doctor.ts +224 -0
- package/src/cli/commands/import.test.ts +379 -0
- package/src/cli/commands/import.ts +335 -0
- package/src/cli/commands/init-lexicon.test.ts +297 -0
- package/src/cli/commands/init-lexicon.ts +993 -0
- package/src/cli/commands/init.test.ts +317 -0
- package/src/cli/commands/init.ts +505 -0
- package/src/cli/commands/licenses.ts +165 -0
- package/src/cli/commands/lint.test.ts +332 -0
- package/src/cli/commands/lint.ts +408 -0
- package/src/cli/commands/list.test.ts +100 -0
- package/src/cli/commands/list.ts +108 -0
- package/src/cli/commands/update.test.ts +38 -0
- package/src/cli/commands/update.ts +207 -0
- package/src/cli/conflict-check.test.ts +255 -0
- package/src/cli/conflict-check.ts +89 -0
- package/src/cli/debug.ts +8 -0
- package/src/cli/format.test.ts +140 -0
- package/src/cli/format.ts +133 -0
- package/src/cli/handlers/build.ts +58 -0
- package/src/cli/handlers/dev.ts +38 -0
- package/src/cli/handlers/init.ts +46 -0
- package/src/cli/handlers/lint.ts +36 -0
- package/src/cli/handlers/misc.ts +57 -0
- package/src/cli/handlers/serve.ts +26 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/lsp/capabilities.ts +46 -0
- package/src/cli/lsp/diagnostics.ts +52 -0
- package/src/cli/lsp/server.test.ts +618 -0
- package/src/cli/lsp/server.ts +393 -0
- package/src/cli/main.test.ts +257 -0
- package/src/cli/main.ts +224 -0
- package/src/cli/mcp/resources/context.ts +59 -0
- package/src/cli/mcp/server.test.ts +747 -0
- package/src/cli/mcp/server.ts +402 -0
- package/src/cli/mcp/tools/build.ts +117 -0
- package/src/cli/mcp/tools/import.ts +48 -0
- package/src/cli/mcp/tools/lint.ts +45 -0
- package/src/cli/plugins.test.ts +31 -0
- package/src/cli/plugins.ts +94 -0
- package/src/cli/registry.ts +73 -0
- package/src/cli/reporters/stylish.test.ts +282 -0
- package/src/cli/reporters/stylish.ts +186 -0
- package/src/cli/watch.test.ts +81 -0
- package/src/cli/watch.ts +101 -0
- package/src/codegen/case.test.ts +30 -0
- package/src/codegen/case.ts +11 -0
- package/src/codegen/coverage.ts +167 -0
- package/src/codegen/docs.ts +634 -0
- package/src/codegen/fetch.test.ts +119 -0
- package/src/codegen/fetch.ts +261 -0
- package/src/codegen/generate-registry.test.ts +118 -0
- package/src/codegen/generate-registry.ts +107 -0
- package/src/codegen/generate-runtime-index.test.ts +81 -0
- package/src/codegen/generate-runtime-index.ts +99 -0
- package/src/codegen/generate-typescript.test.ts +146 -0
- package/src/codegen/generate-typescript.ts +161 -0
- package/src/codegen/generate.ts +206 -0
- package/src/codegen/json-patch.test.ts +113 -0
- package/src/codegen/json-patch.ts +151 -0
- package/src/codegen/json-schema.test.ts +196 -0
- package/src/codegen/json-schema.ts +209 -0
- package/src/codegen/naming.ts +201 -0
- package/src/codegen/package.ts +161 -0
- package/src/codegen/rollback.test.ts +92 -0
- package/src/codegen/rollback.ts +115 -0
- package/src/codegen/topo-sort.test.ts +69 -0
- package/src/codegen/topo-sort.ts +46 -0
- package/src/codegen/typecheck.test.ts +37 -0
- package/src/codegen/typecheck.ts +74 -0
- package/src/codegen/validate.test.ts +86 -0
- package/src/codegen/validate.ts +143 -0
- package/src/composite.test.ts +426 -0
- package/src/composite.ts +243 -0
- package/src/config.test.ts +91 -0
- package/src/config.ts +87 -0
- package/src/declarable.test.ts +160 -0
- package/src/declarable.ts +47 -0
- package/src/detectLexicon.test.ts +236 -0
- package/src/detectLexicon.ts +37 -0
- package/src/discovery/cache.test.ts +78 -0
- package/src/discovery/cache.ts +86 -0
- package/src/discovery/collect.test.ts +269 -0
- package/src/discovery/collect.ts +51 -0
- package/src/discovery/cycles.test.ts +238 -0
- package/src/discovery/cycles.ts +107 -0
- package/src/discovery/files.test.ts +154 -0
- package/src/discovery/files.ts +61 -0
- package/src/discovery/graph.test.ts +476 -0
- package/src/discovery/graph.ts +150 -0
- package/src/discovery/import.test.ts +199 -0
- package/src/discovery/import.ts +20 -0
- package/src/discovery/index.test.ts +272 -0
- package/src/discovery/index.ts +132 -0
- package/src/discovery/resolve.test.ts +267 -0
- package/src/discovery/resolve.ts +54 -0
- package/src/errors.test.ts +138 -0
- package/src/errors.ts +86 -0
- package/src/import/base-parser.test.ts +67 -0
- package/src/import/base-parser.ts +48 -0
- package/src/import/generator.ts +21 -0
- package/src/import/ir-utils.test.ts +103 -0
- package/src/import/ir-utils.ts +87 -0
- package/src/import/parser.ts +41 -0
- package/src/index.ts +60 -0
- package/src/intrinsic-interpolation.test.ts +91 -0
- package/src/intrinsic-interpolation.ts +89 -0
- package/src/intrinsic.test.ts +69 -0
- package/src/intrinsic.ts +43 -0
- package/src/lexicon-integrity.test.ts +94 -0
- package/src/lexicon-integrity.ts +69 -0
- package/src/lexicon-manifest.test.ts +101 -0
- package/src/lexicon-manifest.ts +71 -0
- package/src/lexicon-output.test.ts +182 -0
- package/src/lexicon-output.ts +82 -0
- package/src/lexicon-schema.test.ts +239 -0
- package/src/lexicon-schema.ts +144 -0
- package/src/lexicon.ts +212 -0
- package/src/lint/config-overrides.test.ts +254 -0
- package/src/lint/config.test.ts +644 -0
- package/src/lint/config.ts +375 -0
- package/src/lint/declarative.test.ts +256 -0
- package/src/lint/declarative.ts +187 -0
- package/src/lint/engine.test.ts +465 -0
- package/src/lint/engine.ts +172 -0
- package/src/lint/named-checks.test.ts +37 -0
- package/src/lint/named-checks.ts +33 -0
- package/src/lint/parser.test.ts +129 -0
- package/src/lint/parser.ts +42 -0
- package/src/lint/post-synth.test.ts +113 -0
- package/src/lint/post-synth.ts +76 -0
- package/src/lint/presets/relaxed.json +19 -0
- package/src/lint/presets/strict.json +19 -0
- package/src/lint/rule-loader.test.ts +67 -0
- package/src/lint/rule-loader.ts +67 -0
- package/src/lint/rule-options.test.ts +141 -0
- package/src/lint/rule.test.ts +196 -0
- package/src/lint/rule.ts +98 -0
- package/src/lint/rules/barrel-import-style.test.ts +80 -0
- package/src/lint/rules/barrel-import-style.ts +59 -0
- package/src/lint/rules/composite-scope.ts +55 -0
- package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
- package/src/lint/rules/cor017-composite-name-match.ts +108 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
- package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
- package/src/lint/rules/declarable-naming-convention.ts +70 -0
- package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
- package/src/lint/rules/enforce-barrel-import.ts +81 -0
- package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
- package/src/lint/rules/enforce-barrel-ref.ts +75 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
- package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
- package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
- package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
- package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
- package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
- package/src/lint/rules/evl004-spread-non-const.ts +111 -0
- package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
- package/src/lint/rules/evl005-resource-block-body.ts +49 -0
- package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
- package/src/lint/rules/evl006-barrel-usage.ts +95 -0
- package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
- package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
- package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
- package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
- package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
- package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
- package/src/lint/rules/export-required.test.ts +213 -0
- package/src/lint/rules/export-required.ts +158 -0
- package/src/lint/rules/file-declarable-limit.test.ts +148 -0
- package/src/lint/rules/file-declarable-limit.ts +96 -0
- package/src/lint/rules/flat-declarations.test.ts +210 -0
- package/src/lint/rules/flat-declarations.ts +70 -0
- package/src/lint/rules/index.ts +99 -0
- package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
- package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
- package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
- package/src/lint/rules/no-redundant-type-import.ts +85 -0
- package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
- package/src/lint/rules/no-redundant-value-cast.ts +46 -0
- package/src/lint/rules/no-string-ref.test.ts +100 -0
- package/src/lint/rules/no-string-ref.ts +66 -0
- package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
- package/src/lint/rules/no-unused-declarable-import.ts +103 -0
- package/src/lint/rules/no-unused-declarable.test.ts +134 -0
- package/src/lint/rules/no-unused-declarable.ts +118 -0
- package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
- package/src/lint/rules/prefer-namespace-import.ts +63 -0
- package/src/lint/rules/single-concern-file.test.ts +156 -0
- package/src/lint/rules/single-concern-file.ts +98 -0
- package/src/lint/rules/stale-barrel-types.ts +60 -0
- package/src/lint/selectors.test.ts +113 -0
- package/src/lint/selectors.ts +188 -0
- package/src/lsp/lexicon-providers.ts +191 -0
- package/src/lsp/types.ts +79 -0
- package/src/mcp/types.ts +22 -0
- package/src/project/scan.test.ts +178 -0
- package/src/project/scan.ts +182 -0
- package/src/project/sync.test.ts +87 -0
- package/src/project/sync.ts +46 -0
- package/src/project-validation.test.ts +64 -0
- package/src/project-validation.ts +79 -0
- package/src/pseudo-parameter.test.ts +39 -0
- package/src/pseudo-parameter.ts +47 -0
- package/src/runtime.ts +68 -0
- package/src/serializer-walker.test.ts +124 -0
- package/src/serializer-walker.ts +83 -0
- package/src/serializer.ts +42 -0
- package/src/sort.test.ts +290 -0
- package/src/sort.ts +58 -0
- package/src/stack-output.ts +82 -0
- package/src/types.test.ts +307 -0
- package/src/types.ts +46 -0
- package/src/utils.test.ts +195 -0
- package/src/utils.ts +46 -0
- package/src/validation.test.ts +308 -0
- package/src/validation.ts +50 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { importModule } from "./import";
|
|
3
|
+
import { DiscoveryError } from "../errors";
|
|
4
|
+
import { withTestDir, expectToThrow } from "@intentius/chant-test-utils";
|
|
5
|
+
import { writeFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
describe("importModule", () => {
|
|
9
|
+
|
|
10
|
+
test("imports a valid module and returns exports", async () => {
|
|
11
|
+
await withTestDir(async (testDir) => {
|
|
12
|
+
const filePath = join(testDir, "module.ts");
|
|
13
|
+
await writeFile(
|
|
14
|
+
filePath,
|
|
15
|
+
'export const greeting = "hello";\nexport const count = 42;'
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const module = await importModule(filePath);
|
|
19
|
+
expect(module.greeting).toBe("hello");
|
|
20
|
+
expect(module.count).toBe(42);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("imports module with default export", async () => {
|
|
25
|
+
await withTestDir(async (testDir) => {
|
|
26
|
+
const filePath = join(testDir, "default.ts");
|
|
27
|
+
await writeFile(filePath, "export default { name: 'test', value: 123 };");
|
|
28
|
+
|
|
29
|
+
const module = await importModule(filePath);
|
|
30
|
+
expect(module.default).toBeDefined();
|
|
31
|
+
expect(module.default.name).toBe("test");
|
|
32
|
+
expect(module.default.value).toBe(123);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("imports module with mixed exports", async () => {
|
|
37
|
+
await withTestDir(async (testDir) => {
|
|
38
|
+
const filePath = join(testDir, "mixed.ts");
|
|
39
|
+
await writeFile(
|
|
40
|
+
filePath,
|
|
41
|
+
'export default "main";\nexport const helper = "utils";\nexport const version = 1;'
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const module = await importModule(filePath);
|
|
45
|
+
expect(module.default).toBe("main");
|
|
46
|
+
expect(module.helper).toBe("utils");
|
|
47
|
+
expect(module.version).toBe(1);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("imports module with no exports", async () => {
|
|
52
|
+
await withTestDir(async (testDir) => {
|
|
53
|
+
const filePath = join(testDir, "empty.ts");
|
|
54
|
+
await writeFile(filePath, "const internal = 42;");
|
|
55
|
+
|
|
56
|
+
const module = await importModule(filePath);
|
|
57
|
+
expect(module).toBeDefined();
|
|
58
|
+
expect(Object.keys(module)).not.toContain("internal");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("throws DiscoveryError with type 'import' for non-existent file", async () => {
|
|
63
|
+
await withTestDir(async (testDir) => {
|
|
64
|
+
const nonExistentPath = join(testDir, "does-not-exist.ts");
|
|
65
|
+
|
|
66
|
+
const error = await expectToThrow(
|
|
67
|
+
() => importModule(nonExistentPath),
|
|
68
|
+
DiscoveryError,
|
|
69
|
+
(err) => {
|
|
70
|
+
expect(err.type).toBe("import");
|
|
71
|
+
expect(err.file).toBe(nonExistentPath);
|
|
72
|
+
expect(err.message).toBeDefined();
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("throws DiscoveryError with type 'import' for invalid syntax", async () => {
|
|
79
|
+
await withTestDir(async (testDir) => {
|
|
80
|
+
const filePath = join(testDir, "invalid.ts");
|
|
81
|
+
await writeFile(filePath, "export const broken = {");
|
|
82
|
+
|
|
83
|
+
await expectToThrow(
|
|
84
|
+
() => importModule(filePath),
|
|
85
|
+
DiscoveryError,
|
|
86
|
+
(error) => {
|
|
87
|
+
expect(error.type).toBe("import");
|
|
88
|
+
expect(error.file).toBe(filePath);
|
|
89
|
+
expect(error.message).toBeDefined();
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("throws DiscoveryError with type 'import' for runtime error", async () => {
|
|
96
|
+
await withTestDir(async (testDir) => {
|
|
97
|
+
const filePath = join(testDir, "runtime-error.ts");
|
|
98
|
+
await writeFile(
|
|
99
|
+
filePath,
|
|
100
|
+
'throw new Error("Module initialization failed");'
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await expectToThrow(
|
|
104
|
+
() => importModule(filePath),
|
|
105
|
+
DiscoveryError,
|
|
106
|
+
(error) => {
|
|
107
|
+
expect(error.type).toBe("import");
|
|
108
|
+
expect(error.file).toBe(filePath);
|
|
109
|
+
expect(error.message).toContain("Module initialization failed");
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("imports module with class exports", async () => {
|
|
116
|
+
await withTestDir(async (testDir) => {
|
|
117
|
+
const filePath = join(testDir, "class.ts");
|
|
118
|
+
await writeFile(
|
|
119
|
+
filePath,
|
|
120
|
+
`export class MyClass {
|
|
121
|
+
constructor(public value: number) {}
|
|
122
|
+
getValue() { return this.value; }
|
|
123
|
+
}`
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const module = await importModule(filePath);
|
|
127
|
+
expect(module.MyClass).toBeDefined();
|
|
128
|
+
const instance = new module.MyClass(100);
|
|
129
|
+
expect(instance.getValue()).toBe(100);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("imports module with function exports", async () => {
|
|
134
|
+
await withTestDir(async (testDir) => {
|
|
135
|
+
const filePath = join(testDir, "functions.ts");
|
|
136
|
+
await writeFile(
|
|
137
|
+
filePath,
|
|
138
|
+
`export function add(a: number, b: number) { return a + b; }
|
|
139
|
+
export const multiply = (a: number, b: number) => a * b;`
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const module = await importModule(filePath);
|
|
143
|
+
expect(module.add).toBeInstanceOf(Function);
|
|
144
|
+
expect(module.multiply).toBeInstanceOf(Function);
|
|
145
|
+
expect(module.add(2, 3)).toBe(5);
|
|
146
|
+
expect(module.multiply(4, 5)).toBe(20);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("imports module with re-exports", async () => {
|
|
151
|
+
await withTestDir(async (testDir) => {
|
|
152
|
+
const utilsPath = join(testDir, "utils.ts");
|
|
153
|
+
const indexPath = join(testDir, "index.ts");
|
|
154
|
+
|
|
155
|
+
await writeFile(utilsPath, 'export const util = "helper";');
|
|
156
|
+
await writeFile(indexPath, 'export { util } from "./utils.ts";');
|
|
157
|
+
|
|
158
|
+
const module = await importModule(indexPath);
|
|
159
|
+
expect(module.util).toBe("helper");
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("preserves error message from underlying import failure", async () => {
|
|
164
|
+
await withTestDir(async (testDir) => {
|
|
165
|
+
const filePath = join(testDir, "missing-dep.ts");
|
|
166
|
+
await writeFile(
|
|
167
|
+
filePath,
|
|
168
|
+
'import { nonExistent } from "./does-not-exist.ts";\nexport const value = nonExistent;'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
await expectToThrow(
|
|
172
|
+
() => importModule(filePath),
|
|
173
|
+
DiscoveryError,
|
|
174
|
+
(error) => {
|
|
175
|
+
expect(error.type).toBe("import");
|
|
176
|
+
expect(error.file).toBe(filePath);
|
|
177
|
+
expect(error.message.length).toBeGreaterThan(0);
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("error serializes to JSON correctly", async () => {
|
|
184
|
+
await withTestDir(async (testDir) => {
|
|
185
|
+
const filePath = join(testDir, "bad.ts");
|
|
186
|
+
|
|
187
|
+
const error = await expectToThrow(
|
|
188
|
+
() => importModule(filePath),
|
|
189
|
+
DiscoveryError
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const json = error.toJSON();
|
|
193
|
+
expect(json.name).toBe("DiscoveryError");
|
|
194
|
+
expect(json.file).toBe(filePath);
|
|
195
|
+
expect(json.type).toBe("import");
|
|
196
|
+
expect(json.message).toBeDefined();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DiscoveryError } from "../errors";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dynamically import a module and return its exports.
|
|
5
|
+
*
|
|
6
|
+
* @param path - The file path to import
|
|
7
|
+
* @returns The module exports
|
|
8
|
+
* @throws {DiscoveryError} with type "import" if the import fails
|
|
9
|
+
*/
|
|
10
|
+
export async function importModule(
|
|
11
|
+
path: string
|
|
12
|
+
): Promise<Record<string, unknown>> {
|
|
13
|
+
try {
|
|
14
|
+
return require(path);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
const message =
|
|
17
|
+
error instanceof Error ? error.message : "Unknown import error";
|
|
18
|
+
throw new DiscoveryError(path, message, "import");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { discover } from "./index";
|
|
3
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { DECLARABLE_MARKER } from "../declarable";
|
|
7
|
+
|
|
8
|
+
describe("discover", () => {
|
|
9
|
+
let testDir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
testDir = join(tmpdir(), `chant-discover-test-${Date.now()}-${Math.random()}`);
|
|
13
|
+
await mkdir(testDir, { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
await rm(testDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("returns empty result for empty directory", async () => {
|
|
21
|
+
const result = await discover(testDir);
|
|
22
|
+
|
|
23
|
+
expect(result.entities.size).toBe(0);
|
|
24
|
+
expect(result.dependencies.size).toBe(0);
|
|
25
|
+
expect(result.sourceFiles).toEqual([]);
|
|
26
|
+
expect(result.errors).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("discovers entities from a single file", async () => {
|
|
30
|
+
await writeFile(
|
|
31
|
+
join(testDir, "app.ts"),
|
|
32
|
+
`
|
|
33
|
+
export const myEntity = {
|
|
34
|
+
entityType: "TestEntity",
|
|
35
|
+
[Symbol.for("chant.declarable")]: true,
|
|
36
|
+
};
|
|
37
|
+
`
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const result = await discover(testDir);
|
|
41
|
+
|
|
42
|
+
expect(result.entities.size).toBe(1);
|
|
43
|
+
expect(result.entities.has("myEntity")).toBe(true);
|
|
44
|
+
expect(result.sourceFiles).toHaveLength(1);
|
|
45
|
+
expect(result.sourceFiles[0]).toMatch(/app\.ts$/);
|
|
46
|
+
expect(result.errors).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("discovers entities from multiple files", async () => {
|
|
50
|
+
await writeFile(
|
|
51
|
+
join(testDir, "app.ts"),
|
|
52
|
+
`
|
|
53
|
+
export const entity1 = {
|
|
54
|
+
entityType: "Entity1",
|
|
55
|
+
[Symbol.for("chant.declarable")]: true,
|
|
56
|
+
};
|
|
57
|
+
`
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
await writeFile(
|
|
61
|
+
join(testDir, "config.ts"),
|
|
62
|
+
`
|
|
63
|
+
export const entity2 = {
|
|
64
|
+
entityType: "Entity2",
|
|
65
|
+
[Symbol.for("chant.declarable")]: true,
|
|
66
|
+
};
|
|
67
|
+
`
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const result = await discover(testDir);
|
|
71
|
+
|
|
72
|
+
expect(result.entities.size).toBe(2);
|
|
73
|
+
expect(result.entities.has("entity1")).toBe(true);
|
|
74
|
+
expect(result.entities.has("entity2")).toBe(true);
|
|
75
|
+
expect(result.sourceFiles).toHaveLength(2);
|
|
76
|
+
expect(result.errors).toEqual([]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("builds dependency graph for entities with references", async () => {
|
|
80
|
+
await writeFile(
|
|
81
|
+
join(testDir, "entities.ts"),
|
|
82
|
+
`
|
|
83
|
+
export const parent = {
|
|
84
|
+
entityType: "Parent",
|
|
85
|
+
[Symbol.for("chant.declarable")]: true,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const child = {
|
|
89
|
+
entityType: "Child",
|
|
90
|
+
[Symbol.for("chant.declarable")]: true,
|
|
91
|
+
parentRef: parent,
|
|
92
|
+
};
|
|
93
|
+
`
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const result = await discover(testDir);
|
|
97
|
+
|
|
98
|
+
expect(result.entities.size).toBe(2);
|
|
99
|
+
expect(result.dependencies.size).toBe(2);
|
|
100
|
+
|
|
101
|
+
const childDeps = result.dependencies.get("child");
|
|
102
|
+
expect(childDeps).toBeDefined();
|
|
103
|
+
expect(childDeps?.has("parent")).toBe(true);
|
|
104
|
+
|
|
105
|
+
const parentDeps = result.dependencies.get("parent");
|
|
106
|
+
expect(parentDeps).toBeDefined();
|
|
107
|
+
expect(parentDeps?.size).toBe(0);
|
|
108
|
+
|
|
109
|
+
expect(result.errors).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("collects import errors and continues processing", async () => {
|
|
113
|
+
await writeFile(
|
|
114
|
+
join(testDir, "good.ts"),
|
|
115
|
+
`
|
|
116
|
+
export const goodEntity = {
|
|
117
|
+
entityType: "Good",
|
|
118
|
+
[Symbol.for("chant.declarable")]: true,
|
|
119
|
+
};
|
|
120
|
+
`
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
await writeFile(
|
|
124
|
+
join(testDir, "bad.ts"),
|
|
125
|
+
`
|
|
126
|
+
// This will cause a syntax error
|
|
127
|
+
export const badEntity = {
|
|
128
|
+
entityType: "Bad"
|
|
129
|
+
[Symbol.for("chant.declarable")]: true,
|
|
130
|
+
};
|
|
131
|
+
`
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const result = await discover(testDir);
|
|
135
|
+
|
|
136
|
+
// Should still process the good file
|
|
137
|
+
expect(result.entities.has("goodEntity")).toBe(true);
|
|
138
|
+
expect(result.sourceFiles).toHaveLength(2);
|
|
139
|
+
|
|
140
|
+
// Should have collected the import error
|
|
141
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
142
|
+
expect(result.errors.some(e => e.type === "import")).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("returns source files even when no entities found", async () => {
|
|
146
|
+
await writeFile(
|
|
147
|
+
join(testDir, "empty.ts"),
|
|
148
|
+
`
|
|
149
|
+
export const notAnEntity = { foo: "bar" };
|
|
150
|
+
`
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const result = await discover(testDir);
|
|
154
|
+
|
|
155
|
+
expect(result.entities.size).toBe(0);
|
|
156
|
+
expect(result.sourceFiles).toHaveLength(1);
|
|
157
|
+
expect(result.sourceFiles[0]).toMatch(/empty\.ts$/);
|
|
158
|
+
expect(result.errors).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("handles nested directory structures", async () => {
|
|
162
|
+
const subDir = join(testDir, "src", "entities");
|
|
163
|
+
await mkdir(subDir, { recursive: true });
|
|
164
|
+
|
|
165
|
+
await writeFile(
|
|
166
|
+
join(testDir, "root.ts"),
|
|
167
|
+
`
|
|
168
|
+
export const rootEntity = {
|
|
169
|
+
entityType: "Root",
|
|
170
|
+
[Symbol.for("chant.declarable")]: true,
|
|
171
|
+
};
|
|
172
|
+
`
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
await writeFile(
|
|
176
|
+
join(subDir, "nested.ts"),
|
|
177
|
+
`
|
|
178
|
+
export const nestedEntity = {
|
|
179
|
+
entityType: "Nested",
|
|
180
|
+
[Symbol.for("chant.declarable")]: true,
|
|
181
|
+
};
|
|
182
|
+
`
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const result = await discover(testDir);
|
|
186
|
+
|
|
187
|
+
expect(result.entities.size).toBe(2);
|
|
188
|
+
expect(result.entities.has("rootEntity")).toBe(true);
|
|
189
|
+
expect(result.entities.has("nestedEntity")).toBe(true);
|
|
190
|
+
expect(result.sourceFiles).toHaveLength(2);
|
|
191
|
+
expect(result.errors).toEqual([]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("excludes test files from discovery", async () => {
|
|
195
|
+
await writeFile(
|
|
196
|
+
join(testDir, "app.ts"),
|
|
197
|
+
`
|
|
198
|
+
export const appEntity = {
|
|
199
|
+
entityType: "App",
|
|
200
|
+
[Symbol.for("chant.declarable")]: true,
|
|
201
|
+
};
|
|
202
|
+
`
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
await writeFile(
|
|
206
|
+
join(testDir, "app.test.ts"),
|
|
207
|
+
`
|
|
208
|
+
export const testEntity = {
|
|
209
|
+
entityType: "Test",
|
|
210
|
+
[Symbol.for("chant.declarable")]: true,
|
|
211
|
+
};
|
|
212
|
+
`
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const result = await discover(testDir);
|
|
216
|
+
|
|
217
|
+
expect(result.entities.size).toBe(1);
|
|
218
|
+
expect(result.entities.has("appEntity")).toBe(true);
|
|
219
|
+
expect(result.entities.has("testEntity")).toBe(false);
|
|
220
|
+
expect(result.sourceFiles).toHaveLength(1);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("filters non-declarable exports", async () => {
|
|
224
|
+
await writeFile(
|
|
225
|
+
join(testDir, "mixed.ts"),
|
|
226
|
+
`
|
|
227
|
+
export const entity = {
|
|
228
|
+
entityType: "Entity",
|
|
229
|
+
[Symbol.for("chant.declarable")]: true,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const nonEntity = { foo: "bar" };
|
|
233
|
+
export const anotherNonEntity = 42;
|
|
234
|
+
`
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const result = await discover(testDir);
|
|
238
|
+
|
|
239
|
+
expect(result.entities.size).toBe(1);
|
|
240
|
+
expect(result.entities.has("entity")).toBe(true);
|
|
241
|
+
expect(result.entities.has("nonEntity")).toBe(false);
|
|
242
|
+
expect(result.entities.has("anotherNonEntity")).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("returns empty dependencies map for entities without references", async () => {
|
|
246
|
+
await writeFile(
|
|
247
|
+
join(testDir, "standalone.ts"),
|
|
248
|
+
`
|
|
249
|
+
export const standalone1 = {
|
|
250
|
+
entityType: "Standalone1",
|
|
251
|
+
[Symbol.for("chant.declarable")]: true,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export const standalone2 = {
|
|
255
|
+
entityType: "Standalone2",
|
|
256
|
+
[Symbol.for("chant.declarable")]: true,
|
|
257
|
+
};
|
|
258
|
+
`
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const result = await discover(testDir);
|
|
262
|
+
|
|
263
|
+
expect(result.entities.size).toBe(2);
|
|
264
|
+
expect(result.dependencies.size).toBe(2);
|
|
265
|
+
|
|
266
|
+
const deps1 = result.dependencies.get("standalone1");
|
|
267
|
+
expect(deps1?.size).toBe(0);
|
|
268
|
+
|
|
269
|
+
const deps2 = result.dependencies.get("standalone2");
|
|
270
|
+
expect(deps2?.size).toBe(0);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Declarable } from "../declarable";
|
|
2
|
+
import type { DiscoveryError } from "../errors";
|
|
3
|
+
import { findInfraFiles } from "./files";
|
|
4
|
+
import { importModule } from "./import";
|
|
5
|
+
import { collectEntities } from "./collect";
|
|
6
|
+
import { resolveAttrRefs } from "./resolve";
|
|
7
|
+
import { buildDependencyGraph } from "./graph";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Result of the discovery process
|
|
11
|
+
*/
|
|
12
|
+
export interface DiscoveryResult {
|
|
13
|
+
/** Map of entity name to Declarable entity */
|
|
14
|
+
entities: Map<string, Declarable>;
|
|
15
|
+
/** Map of entity name to set of entity names it depends on */
|
|
16
|
+
dependencies: Map<string, Set<string>>;
|
|
17
|
+
/** Array of source file paths that were processed */
|
|
18
|
+
sourceFiles: string[];
|
|
19
|
+
/** Array of errors encountered during discovery */
|
|
20
|
+
errors: DiscoveryError[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Discovers all declarable entities in a directory by scanning files,
|
|
25
|
+
* importing modules, collecting entities, resolving references, and building
|
|
26
|
+
* a dependency graph.
|
|
27
|
+
*
|
|
28
|
+
* @param path - The directory path to discover entities in
|
|
29
|
+
* @returns DiscoveryResult with entities, dependencies, sourceFiles, and errors
|
|
30
|
+
*/
|
|
31
|
+
export async function discover(path: string): Promise<DiscoveryResult> {
|
|
32
|
+
const errors: DiscoveryError[] = [];
|
|
33
|
+
const sourceFiles: string[] = [];
|
|
34
|
+
|
|
35
|
+
// Step 1: Scan for TypeScript files
|
|
36
|
+
const files = await findInfraFiles(path);
|
|
37
|
+
sourceFiles.push(...files);
|
|
38
|
+
|
|
39
|
+
// Step 2: Import all modules
|
|
40
|
+
const modules: Array<{ file: string; exports: Record<string, unknown> }> = [];
|
|
41
|
+
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
try {
|
|
44
|
+
const exports = await importModule(file);
|
|
45
|
+
modules.push({ file, exports });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Collect import errors but continue processing other files
|
|
48
|
+
if (error instanceof Error && error.name === "DiscoveryError") {
|
|
49
|
+
errors.push(error as DiscoveryError);
|
|
50
|
+
} else {
|
|
51
|
+
// Convert unexpected errors to DiscoveryError
|
|
52
|
+
const { DiscoveryError: DiscoveryErrorClass } = await import(
|
|
53
|
+
"../errors"
|
|
54
|
+
);
|
|
55
|
+
errors.push(
|
|
56
|
+
new DiscoveryErrorClass(
|
|
57
|
+
file,
|
|
58
|
+
error instanceof Error ? error.message : String(error),
|
|
59
|
+
"import"
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Step 3: Collect entities from imported modules
|
|
67
|
+
let entities = new Map<string, Declarable>();
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
entities = collectEntities(modules);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
// Collect resolution errors
|
|
73
|
+
if (error instanceof Error && error.name === "DiscoveryError") {
|
|
74
|
+
errors.push(error as DiscoveryError);
|
|
75
|
+
} else {
|
|
76
|
+
const { DiscoveryError: DiscoveryErrorClass } = await import("../errors");
|
|
77
|
+
errors.push(
|
|
78
|
+
new DiscoveryErrorClass(
|
|
79
|
+
"",
|
|
80
|
+
error instanceof Error ? error.message : String(error),
|
|
81
|
+
"resolution"
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
// If collection fails, return early with empty results
|
|
86
|
+
return {
|
|
87
|
+
entities: new Map(),
|
|
88
|
+
dependencies: new Map(),
|
|
89
|
+
sourceFiles,
|
|
90
|
+
errors,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Step 4: Resolve AttrRefs
|
|
95
|
+
try {
|
|
96
|
+
resolveAttrRefs(entities);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// Collect resolution errors but continue
|
|
99
|
+
const { DiscoveryError: DiscoveryErrorClass } = await import("../errors");
|
|
100
|
+
errors.push(
|
|
101
|
+
new DiscoveryErrorClass(
|
|
102
|
+
"",
|
|
103
|
+
error instanceof Error ? error.message : String(error),
|
|
104
|
+
"resolution"
|
|
105
|
+
)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Step 5: Build dependency graph
|
|
110
|
+
let dependencies = new Map<string, Set<string>>();
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
dependencies = buildDependencyGraph(entities);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
// Collect graph building errors
|
|
116
|
+
const { DiscoveryError: DiscoveryErrorClass } = await import("../errors");
|
|
117
|
+
errors.push(
|
|
118
|
+
new DiscoveryErrorClass(
|
|
119
|
+
"",
|
|
120
|
+
error instanceof Error ? error.message : String(error),
|
|
121
|
+
"resolution"
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
entities,
|
|
128
|
+
dependencies,
|
|
129
|
+
sourceFiles,
|
|
130
|
+
errors,
|
|
131
|
+
};
|
|
132
|
+
}
|