@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,91 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { loadChantConfig, DEFAULT_CHANT_CONFIG } from "./config";
|
|
3
|
+
import { writeFileSync, mkdirSync, rmSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
const TEST_DIR = join(import.meta.dir, "__test_chant_config__");
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("loadChantConfig", () => {
|
|
17
|
+
test("returns default config when no config file exists", async () => {
|
|
18
|
+
const result = await loadChantConfig(TEST_DIR);
|
|
19
|
+
expect(result.config).toEqual(DEFAULT_CHANT_CONFIG);
|
|
20
|
+
expect(result.configPath).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("loads chant.config.ts with default export", async () => {
|
|
24
|
+
const tsPath = join(TEST_DIR, "chant.config.ts");
|
|
25
|
+
writeFileSync(
|
|
26
|
+
tsPath,
|
|
27
|
+
`export default { lexicons: ["testdom"], lint: { rules: { COR001: "error" } } };`,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const result = await loadChantConfig(TEST_DIR);
|
|
31
|
+
expect(result.config.lexicons).toEqual(["testdom"]);
|
|
32
|
+
expect(result.config.lint?.rules?.COR001).toBe("error");
|
|
33
|
+
expect(result.configPath).toBe(tsPath);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("loads chant.config.ts with named config export", async () => {
|
|
37
|
+
const tsPath = join(TEST_DIR, "chant.config.ts");
|
|
38
|
+
writeFileSync(
|
|
39
|
+
tsPath,
|
|
40
|
+
`export const config = { lexicons: ["testdom"] };`,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const result = await loadChantConfig(TEST_DIR);
|
|
44
|
+
expect(result.config.lexicons).toEqual(["testdom"]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("loads chant.config.json", async () => {
|
|
48
|
+
const jsonPath = join(TEST_DIR, "chant.config.json");
|
|
49
|
+
writeFileSync(
|
|
50
|
+
jsonPath,
|
|
51
|
+
JSON.stringify({ lexicons: ["testdom"] }),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const result = await loadChantConfig(TEST_DIR);
|
|
55
|
+
expect(result.config.lexicons).toEqual(["testdom"]);
|
|
56
|
+
expect(result.configPath).toBe(jsonPath);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("prefers .ts over .json when both exist", async () => {
|
|
60
|
+
// .ts takes priority — verify by checking configPath ends with .ts
|
|
61
|
+
writeFileSync(
|
|
62
|
+
join(TEST_DIR, "chant.config.ts"),
|
|
63
|
+
`export default { lexicons: ["testdom"] };`,
|
|
64
|
+
);
|
|
65
|
+
writeFileSync(
|
|
66
|
+
join(TEST_DIR, "chant.config.json"),
|
|
67
|
+
JSON.stringify({ lexicons: ["from-json"] }),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const result = await loadChantConfig(TEST_DIR);
|
|
71
|
+
expect(result.configPath).toEndWith("chant.config.ts");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("handles empty config", async () => {
|
|
75
|
+
writeFileSync(join(TEST_DIR, "chant.config.json"), "{}");
|
|
76
|
+
|
|
77
|
+
const result = await loadChantConfig(TEST_DIR);
|
|
78
|
+
expect(result.config).toEqual({});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("handles config with only lexicons", async () => {
|
|
82
|
+
writeFileSync(
|
|
83
|
+
join(TEST_DIR, "chant.config.json"),
|
|
84
|
+
JSON.stringify({ lexicons: ["testdom"] }),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const result = await loadChantConfig(TEST_DIR);
|
|
88
|
+
expect(result.config.lexicons).toEqual(["testdom"]);
|
|
89
|
+
expect(result.config.lint).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { LintConfig } from "./lint/config";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Zod schema for ChantConfig validation.
|
|
8
|
+
*/
|
|
9
|
+
export const ChantConfigSchema = z.object({
|
|
10
|
+
lexicons: z.array(z.string().min(1)).optional(),
|
|
11
|
+
lint: z.record(z.string(), z.unknown()).optional(),
|
|
12
|
+
}).passthrough();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Top-level chant project configuration.
|
|
16
|
+
*
|
|
17
|
+
* Loaded from `chant.config.ts` (preferred) or `chant.config.json`.
|
|
18
|
+
*/
|
|
19
|
+
export interface ChantConfig {
|
|
20
|
+
/** Lexicon package names to load (e.g. ["aws"]) */
|
|
21
|
+
lexicons?: string[];
|
|
22
|
+
|
|
23
|
+
/** Lint configuration (rules, extends, overrides, plugins) */
|
|
24
|
+
lint?: LintConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolved project configuration with metadata about how it was loaded.
|
|
29
|
+
*/
|
|
30
|
+
export interface ResolvedConfig {
|
|
31
|
+
/** The loaded configuration */
|
|
32
|
+
config: ChantConfig;
|
|
33
|
+
|
|
34
|
+
/** Path to the config file that was loaded, or undefined if defaults */
|
|
35
|
+
configPath?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default configuration when no config file exists.
|
|
40
|
+
*/
|
|
41
|
+
export const DEFAULT_CHANT_CONFIG: ChantConfig = {};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load project configuration from a directory.
|
|
45
|
+
*
|
|
46
|
+
* Tries `chant.config.ts` first (via dynamic import), then `chant.config.json`.
|
|
47
|
+
* Returns default config if neither exists.
|
|
48
|
+
*/
|
|
49
|
+
export async function loadChantConfig(dir: string): Promise<ResolvedConfig> {
|
|
50
|
+
// Try chant.config.ts first
|
|
51
|
+
const tsPath = join(dir, "chant.config.ts");
|
|
52
|
+
if (existsSync(tsPath)) {
|
|
53
|
+
const mod = await import(tsPath);
|
|
54
|
+
const config = mod.default ?? mod.config ?? mod;
|
|
55
|
+
return { config: normalizeConfig(config, tsPath), configPath: tsPath };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Fall back to chant.config.json
|
|
59
|
+
const jsonPath = join(dir, "chant.config.json");
|
|
60
|
+
if (existsSync(jsonPath)) {
|
|
61
|
+
const { readFileSync } = await import("fs");
|
|
62
|
+
const content = readFileSync(jsonPath, "utf-8");
|
|
63
|
+
const parsed = JSON.parse(content);
|
|
64
|
+
return { config: normalizeConfig(parsed, jsonPath), configPath: jsonPath };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { config: DEFAULT_CHANT_CONFIG };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate and normalize a raw config object into ChantConfig shape.
|
|
72
|
+
*/
|
|
73
|
+
function normalizeConfig(raw: Record<string, unknown>, source?: string): ChantConfig {
|
|
74
|
+
if (typeof raw !== "object" || raw === null) {
|
|
75
|
+
return DEFAULT_CHANT_CONFIG;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result = ChantConfigSchema.safeParse(raw);
|
|
79
|
+
if (!result.success) {
|
|
80
|
+
const issue = result.error.issues[0];
|
|
81
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : undefined;
|
|
82
|
+
const loc = source ? ` in ${source}` : "";
|
|
83
|
+
throw new Error(`Invalid chant config${loc}: ${path ? `${path}: ` : ""}${issue.message}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return raw as ChantConfig;
|
|
87
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
DECLARABLE_MARKER,
|
|
4
|
+
type Declarable,
|
|
5
|
+
isDeclarable,
|
|
6
|
+
isPropertyDeclarable,
|
|
7
|
+
} from "./declarable";
|
|
8
|
+
|
|
9
|
+
describe("DECLARABLE_MARKER", () => {
|
|
10
|
+
test("is a symbol", () => {
|
|
11
|
+
expect(typeof DECLARABLE_MARKER).toBe("symbol");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("uses Symbol.for for global registry", () => {
|
|
15
|
+
expect(DECLARABLE_MARKER).toBe(Symbol.for("chant.declarable"));
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("isDeclarable", () => {
|
|
20
|
+
test("returns true for object with DECLARABLE_MARKER", () => {
|
|
21
|
+
const obj: Declarable = {
|
|
22
|
+
lexicon: "test",
|
|
23
|
+
entityType: "Test",
|
|
24
|
+
[DECLARABLE_MARKER]: true,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
expect(isDeclarable(obj)).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("returns true for object with kind field", () => {
|
|
31
|
+
const resourceObj: Declarable = {
|
|
32
|
+
lexicon: "test",
|
|
33
|
+
entityType: "Resource",
|
|
34
|
+
kind: "resource",
|
|
35
|
+
[DECLARABLE_MARKER]: true,
|
|
36
|
+
};
|
|
37
|
+
const propertyObj: Declarable = {
|
|
38
|
+
lexicon: "test",
|
|
39
|
+
entityType: "Property",
|
|
40
|
+
kind: "property",
|
|
41
|
+
[DECLARABLE_MARKER]: true,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
expect(isDeclarable(resourceObj)).toBe(true);
|
|
45
|
+
expect(isDeclarable(propertyObj)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("returns false for object without DECLARABLE_MARKER", () => {
|
|
49
|
+
const obj = {
|
|
50
|
+
entityType: "Test",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
expect(isDeclarable(obj)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("returns false for null", () => {
|
|
57
|
+
expect(isDeclarable(null)).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("returns false for undefined", () => {
|
|
61
|
+
expect(isDeclarable(undefined)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("returns false for primitive values", () => {
|
|
65
|
+
expect(isDeclarable("string")).toBe(false);
|
|
66
|
+
expect(isDeclarable(123)).toBe(false);
|
|
67
|
+
expect(isDeclarable(true)).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("returns false for object with DECLARABLE_MARKER but wrong value", () => {
|
|
71
|
+
const obj = {
|
|
72
|
+
entityType: "Test",
|
|
73
|
+
[DECLARABLE_MARKER]: false,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
expect(isDeclarable(obj)).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("isPropertyDeclarable", () => {
|
|
81
|
+
test("returns true for Declarable with kind property", () => {
|
|
82
|
+
const obj: Declarable = {
|
|
83
|
+
lexicon: "test",
|
|
84
|
+
entityType: "Property",
|
|
85
|
+
kind: "property",
|
|
86
|
+
[DECLARABLE_MARKER]: true,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
expect(isPropertyDeclarable(obj)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("returns false for Declarable with kind resource", () => {
|
|
93
|
+
const obj: Declarable = {
|
|
94
|
+
lexicon: "test",
|
|
95
|
+
entityType: "Resource",
|
|
96
|
+
kind: "resource",
|
|
97
|
+
[DECLARABLE_MARKER]: true,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
expect(isPropertyDeclarable(obj)).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("returns false for Declarable without kind field", () => {
|
|
104
|
+
const obj: Declarable = {
|
|
105
|
+
lexicon: "test",
|
|
106
|
+
entityType: "Test",
|
|
107
|
+
[DECLARABLE_MARKER]: true,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
expect(isPropertyDeclarable(obj)).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("returns false for Declarable with undefined kind", () => {
|
|
114
|
+
const obj: Declarable = {
|
|
115
|
+
lexicon: "test",
|
|
116
|
+
entityType: "Test",
|
|
117
|
+
kind: undefined,
|
|
118
|
+
[DECLARABLE_MARKER]: true,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
expect(isPropertyDeclarable(obj)).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("Declarable interface", () => {
|
|
126
|
+
test("allows creating objects without kind field (backward compatibility)", () => {
|
|
127
|
+
const obj: Declarable = {
|
|
128
|
+
lexicon: "test",
|
|
129
|
+
entityType: "Test",
|
|
130
|
+
[DECLARABLE_MARKER]: true,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
expect(obj.entityType).toBe("Test");
|
|
134
|
+
expect(obj.kind).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("allows creating objects with kind resource", () => {
|
|
138
|
+
const obj: Declarable = {
|
|
139
|
+
lexicon: "test",
|
|
140
|
+
entityType: "Bucket",
|
|
141
|
+
kind: "resource",
|
|
142
|
+
[DECLARABLE_MARKER]: true,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
expect(obj.entityType).toBe("Bucket");
|
|
146
|
+
expect(obj.kind).toBe("resource");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("allows creating objects with kind property", () => {
|
|
150
|
+
const obj: Declarable = {
|
|
151
|
+
lexicon: "test",
|
|
152
|
+
entityType: "BucketEncryption",
|
|
153
|
+
kind: "property",
|
|
154
|
+
[DECLARABLE_MARKER]: true,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
expect(obj.entityType).toBe("BucketEncryption");
|
|
158
|
+
expect(obj.kind).toBe("property");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker symbol for Declarable type identification
|
|
3
|
+
*/
|
|
4
|
+
export const DECLARABLE_MARKER = Symbol.for("chant.declarable");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base interface for all declarable entities
|
|
8
|
+
*/
|
|
9
|
+
export interface Declarable {
|
|
10
|
+
readonly lexicon: string;
|
|
11
|
+
readonly entityType: string;
|
|
12
|
+
readonly kind?: "resource" | "property";
|
|
13
|
+
readonly [DECLARABLE_MARKER]: true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Core parameter type for lexicon-agnostic parameters
|
|
18
|
+
*/
|
|
19
|
+
export interface CoreParameter extends Declarable {
|
|
20
|
+
readonly parameterType: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Core output type for lexicon-agnostic outputs
|
|
25
|
+
*/
|
|
26
|
+
export interface CoreOutput extends Declarable {
|
|
27
|
+
readonly value: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type guard to check if a value is a Declarable
|
|
32
|
+
*/
|
|
33
|
+
export function isDeclarable(value: unknown): value is Declarable {
|
|
34
|
+
return (
|
|
35
|
+
typeof value === "object" &&
|
|
36
|
+
value !== null &&
|
|
37
|
+
DECLARABLE_MARKER in value &&
|
|
38
|
+
(value as Record<symbol, unknown>)[DECLARABLE_MARKER] === true
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Type guard to check if a Declarable is a property-level type
|
|
44
|
+
*/
|
|
45
|
+
export function isPropertyDeclarable(value: Declarable): boolean {
|
|
46
|
+
return value.kind === "property";
|
|
47
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { detectLexicons } from "./detectLexicon";
|
|
3
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
describe("detectLexicons", () => {
|
|
8
|
+
let testDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
// Create a unique temp directory for each test
|
|
12
|
+
testDir = join(tmpdir(), `chant-test-${Date.now()}-${Math.random()}`);
|
|
13
|
+
await mkdir(testDir, { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
// Clean up temp directory
|
|
18
|
+
await rm(testDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test.each([
|
|
22
|
+
{
|
|
23
|
+
lexicon: "testdom",
|
|
24
|
+
code: 'import * as td from "@intentius/chant-lexicon-testdom";\n\nexport const bucket = new td.storage.Bucket({});',
|
|
25
|
+
},
|
|
26
|
+
])("detects $lexicon lexicon from import statement", async ({ lexicon, code }) => {
|
|
27
|
+
const file = join(testDir, "infra.ts");
|
|
28
|
+
await writeFile(file, code);
|
|
29
|
+
|
|
30
|
+
const result = await detectLexicons([file]);
|
|
31
|
+
expect(result).toEqual([lexicon]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("detects lexicon from destructured import", async () => {
|
|
35
|
+
const file = join(testDir, "infra.ts");
|
|
36
|
+
await writeFile(
|
|
37
|
+
file,
|
|
38
|
+
'import { Bucket, Interpolate } from "@intentius/chant-lexicon-testdom";\n\nexport const bucket = new Bucket({});'
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const result = await detectLexicons([file]);
|
|
42
|
+
expect(result).toEqual(["testdom"]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("detects lexicon across multiple files", async () => {
|
|
46
|
+
const file1 = join(testDir, "storage.ts");
|
|
47
|
+
const file2 = join(testDir, "compute.ts");
|
|
48
|
+
await writeFile(
|
|
49
|
+
file1,
|
|
50
|
+
'import * as td from "@intentius/chant-lexicon-testdom";\n\nexport const bucket = new td.storage.Bucket({});'
|
|
51
|
+
);
|
|
52
|
+
await writeFile(
|
|
53
|
+
file2,
|
|
54
|
+
'import * as td from "@intentius/chant-lexicon-testdom";\n\nexport const fn = new td.compute.Function({});'
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const result = await detectLexicons([file1, file2]);
|
|
58
|
+
expect(result).toEqual(["testdom"]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("throws error when no lexicon is detected", async () => {
|
|
62
|
+
const file = join(testDir, "infra.ts");
|
|
63
|
+
await writeFile(
|
|
64
|
+
file,
|
|
65
|
+
'import { readFile } from "node:fs/promises";\n\nexport const data = {};'
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
await expect(detectLexicons([file])).rejects.toThrow(
|
|
69
|
+
"No lexicon detected in infrastructure files"
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("returns multiple lexicons when multiple files use same lexicon", async () => {
|
|
74
|
+
const file1 = join(testDir, "storage.ts");
|
|
75
|
+
const file2 = join(testDir, "compute.ts");
|
|
76
|
+
await writeFile(
|
|
77
|
+
file1,
|
|
78
|
+
'import * as td from "@intentius/chant-lexicon-testdom";\n\nexport const bucket = new td.storage.Bucket({});'
|
|
79
|
+
);
|
|
80
|
+
await writeFile(
|
|
81
|
+
file2,
|
|
82
|
+
'import * as td from "@intentius/chant-lexicon-testdom";\n\nexport const fn = new td.compute.Function({});'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const result = await detectLexicons([file1, file2]);
|
|
86
|
+
expect(result).toContain("testdom");
|
|
87
|
+
expect(result).toHaveLength(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("handles file with no imports gracefully", async () => {
|
|
91
|
+
const file = join(testDir, "empty.ts");
|
|
92
|
+
await writeFile(file, "export const value = 42;");
|
|
93
|
+
|
|
94
|
+
await expect(detectLexicons([file])).rejects.toThrow(
|
|
95
|
+
"No lexicon detected in infrastructure files"
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("handles mixed chant and non-chant imports", async () => {
|
|
100
|
+
const file = join(testDir, "infra.ts");
|
|
101
|
+
await writeFile(
|
|
102
|
+
file,
|
|
103
|
+
'import { readFile } from "node:fs/promises";\nimport * as td from "@intentius/chant-lexicon-testdom";\nimport React from "react";\n\nexport const bucket = new td.storage.Bucket({});'
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const result = await detectLexicons([file]);
|
|
107
|
+
expect(result).toEqual(["testdom"]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("ignores comments containing lexicon names", async () => {
|
|
111
|
+
const file = join(testDir, "infra.ts");
|
|
112
|
+
await writeFile(
|
|
113
|
+
file,
|
|
114
|
+
'// This could be for any lexicon\nimport * as td from "@intentius/chant-lexicon-testdom";\n\nexport const bucket = new td.storage.Bucket({});'
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const result = await detectLexicons([file]);
|
|
118
|
+
expect(result).toEqual(["testdom"]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("handles single-quoted imports", async () => {
|
|
122
|
+
const file = join(testDir, "infra.ts");
|
|
123
|
+
await writeFile(
|
|
124
|
+
file,
|
|
125
|
+
"import * as aws from '@intentius/chant-lexicon-testdom';\n\nexport const bucket = new td.storage.Bucket({});"
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const result = await detectLexicons([file]);
|
|
129
|
+
expect(result).toEqual(["testdom"]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("handles double-quoted imports", async () => {
|
|
133
|
+
const file = join(testDir, "infra.ts");
|
|
134
|
+
await writeFile(
|
|
135
|
+
file,
|
|
136
|
+
'import * as td from "@intentius/chant-lexicon-testdom";\n\nexport const bucket = new td.storage.Bucket({});'
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const result = await detectLexicons([file]);
|
|
140
|
+
expect(result).toEqual(["testdom"]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("handles file that cannot be read", async () => {
|
|
144
|
+
const file = join(testDir, "nonexistent.ts");
|
|
145
|
+
|
|
146
|
+
await expect(detectLexicons([file])).rejects.toThrow(
|
|
147
|
+
"No lexicon detected in infrastructure files"
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("detects lexicon when some files are unreadable", async () => {
|
|
152
|
+
const file1 = join(testDir, "valid.ts");
|
|
153
|
+
const file2 = join(testDir, "nonexistent.ts");
|
|
154
|
+
await writeFile(
|
|
155
|
+
file1,
|
|
156
|
+
'import * as td from "@intentius/chant-lexicon-testdom";\n\nexport const bucket = new td.storage.Bucket({});'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const result = await detectLexicons([file1, file2]);
|
|
160
|
+
expect(result).toEqual(["testdom"]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("handles multiple imports from same lexicon", async () => {
|
|
164
|
+
const file = join(testDir, "infra.ts");
|
|
165
|
+
await writeFile(
|
|
166
|
+
file,
|
|
167
|
+
'import * as td from "@intentius/chant-lexicon-testdom";\nimport { Bucket } from "@intentius/chant-lexicon-testdom";\nimport type { BucketProps } from "@intentius/chant-lexicon-testdom";\nexport const bucket = new Bucket({});'
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const result = await detectLexicons([file]);
|
|
171
|
+
expect(result).toEqual(["testdom"]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("deduplicates lexicon across multiple files", async () => {
|
|
175
|
+
const file1 = join(testDir, "storage.ts");
|
|
176
|
+
const file2 = join(testDir, "compute.ts");
|
|
177
|
+
const file3 = join(testDir, "networking.ts");
|
|
178
|
+
await writeFile(file1, 'import * as td from "@intentius/chant-lexicon-testdom";');
|
|
179
|
+
await writeFile(file2, 'import * as td from "@intentius/chant-lexicon-testdom";');
|
|
180
|
+
await writeFile(file3, 'import * as td from "@intentius/chant-lexicon-testdom";');
|
|
181
|
+
|
|
182
|
+
const result = await detectLexicons([file1, file2, file3]);
|
|
183
|
+
expect(result).toContain("testdom");
|
|
184
|
+
expect(result).toHaveLength(1);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("handles empty file array", async () => {
|
|
188
|
+
await expect(detectLexicons([])).rejects.toThrow(
|
|
189
|
+
"No lexicon detected in infrastructure files"
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("detects lexicon with whitespace variations in import", async () => {
|
|
194
|
+
const file = join(testDir, "infra.ts");
|
|
195
|
+
await writeFile(
|
|
196
|
+
file,
|
|
197
|
+
'import * as td from "@intentius/chant-lexicon-testdom" ;\n\nexport const bucket = new td.storage.Bucket({});'
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const result = await detectLexicons([file]);
|
|
201
|
+
expect(result).toEqual(["testdom"]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("handles import with type modifier", async () => {
|
|
205
|
+
const file = join(testDir, "infra.ts");
|
|
206
|
+
await writeFile(
|
|
207
|
+
file,
|
|
208
|
+
'import type { BucketProps } from "@intentius/chant-lexicon-testdom";\nimport * as td from "@intentius/chant-lexicon-testdom";\nexport const bucket: BucketProps = {};'
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const result = await detectLexicons([file]);
|
|
212
|
+
expect(result).toEqual(["testdom"]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("detects lexicon from export statement", async () => {
|
|
216
|
+
const file = join(testDir, "barrel.ts");
|
|
217
|
+
await writeFile(
|
|
218
|
+
file,
|
|
219
|
+
'export * from "@intentius/chant-lexicon-testdom";\nimport * as core from "@intentius/chant";'
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const result = await detectLexicons([file]);
|
|
223
|
+
expect(result).toEqual(["testdom"]);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("detects lexicon from export with curly braces", async () => {
|
|
227
|
+
const file = join(testDir, "barrel.ts");
|
|
228
|
+
await writeFile(
|
|
229
|
+
file,
|
|
230
|
+
'export { Bucket, Interpolate } from "@intentius/chant-lexicon-testdom";'
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
const result = await detectLexicons([file]);
|
|
234
|
+
expect(result).toEqual(["testdom"]);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detects which lexicons are being used by analyzing import statements
|
|
5
|
+
* in the provided infrastructure files. Matches any `@intentius/chant-lexicon-*` package.
|
|
6
|
+
*
|
|
7
|
+
* @param files - Array of file paths to analyze
|
|
8
|
+
* @returns Array of detected lexicon names
|
|
9
|
+
* @throws Error if no lexicon is detected
|
|
10
|
+
*/
|
|
11
|
+
export async function detectLexicons(files: string[]): Promise<string[]> {
|
|
12
|
+
const detectedLexicons = new Set<string>();
|
|
13
|
+
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
let content: string;
|
|
16
|
+
try {
|
|
17
|
+
content = await readFile(file, "utf-8");
|
|
18
|
+
} catch (error) {
|
|
19
|
+
// Skip files that can't be read
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Match @intentius/chant-lexicon-<name> in import/export statements
|
|
24
|
+
const regex = /(?:import|export)\s+.*\s+from\s+['"]@intentius\/chant-lexicon-([a-z][\w-]*)['"]/g;
|
|
25
|
+
|
|
26
|
+
for (const match of content.matchAll(regex)) {
|
|
27
|
+
detectedLexicons.add(match[1]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate results
|
|
32
|
+
if (detectedLexicons.size === 0) {
|
|
33
|
+
throw new Error("No lexicon detected in infrastructure files");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return Array.from(detectedLexicons);
|
|
37
|
+
}
|