@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,103 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { hasIntrinsicInValue, irUsesIntrinsic, collectDependencies } from "./ir-utils";
|
|
3
|
+
import type { TemplateIR } from "./parser";
|
|
4
|
+
|
|
5
|
+
describe("hasIntrinsicInValue", () => {
|
|
6
|
+
test("finds intrinsic at top level", () => {
|
|
7
|
+
expect(hasIntrinsicInValue({ __intrinsic: "Sub", template: "hello" }, "Sub")).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("finds intrinsic nested in object", () => {
|
|
11
|
+
const value = { foo: { bar: { __intrinsic: "Ref", name: "x" } } };
|
|
12
|
+
expect(hasIntrinsicInValue(value, "Ref")).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("finds intrinsic in array", () => {
|
|
16
|
+
const value = [1, { __intrinsic: "If" }, "hello"];
|
|
17
|
+
expect(hasIntrinsicInValue(value, "If")).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("returns false for missing intrinsic", () => {
|
|
21
|
+
const value = { __intrinsic: "Sub", template: "hello" };
|
|
22
|
+
expect(hasIntrinsicInValue(value, "Ref")).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("returns false for null/undefined", () => {
|
|
26
|
+
expect(hasIntrinsicInValue(null, "Sub")).toBe(false);
|
|
27
|
+
expect(hasIntrinsicInValue(undefined, "Sub")).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("returns false for primitives", () => {
|
|
31
|
+
expect(hasIntrinsicInValue("hello", "Sub")).toBe(false);
|
|
32
|
+
expect(hasIntrinsicInValue(42, "Sub")).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("irUsesIntrinsic", () => {
|
|
37
|
+
test("finds intrinsic in resource properties", () => {
|
|
38
|
+
const ir: TemplateIR = {
|
|
39
|
+
resources: [
|
|
40
|
+
{ logicalId: "A", type: "T::A", properties: { x: { __intrinsic: "Sub", template: "hi" } } },
|
|
41
|
+
],
|
|
42
|
+
parameters: [],
|
|
43
|
+
};
|
|
44
|
+
expect(irUsesIntrinsic(ir, "Sub")).toBe(true);
|
|
45
|
+
expect(irUsesIntrinsic(ir, "Ref")).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("returns false for empty IR", () => {
|
|
49
|
+
const ir: TemplateIR = { resources: [], parameters: [] };
|
|
50
|
+
expect(irUsesIntrinsic(ir, "Sub")).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("collectDependencies", () => {
|
|
55
|
+
const awsPredicate = (obj: Record<string, unknown>): string | null => {
|
|
56
|
+
if (obj.__intrinsic === "Ref") {
|
|
57
|
+
const name = obj.name as string;
|
|
58
|
+
return name.startsWith("AWS::") ? null : name;
|
|
59
|
+
}
|
|
60
|
+
if (obj.__intrinsic === "GetAtt") {
|
|
61
|
+
return obj.logicalId as string;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
test("collects Ref dependencies", () => {
|
|
67
|
+
const value = { x: { __intrinsic: "Ref", name: "MyBucket" } };
|
|
68
|
+
const deps = collectDependencies(value, awsPredicate);
|
|
69
|
+
expect(deps).toEqual(new Set(["MyBucket"]));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("collects GetAtt dependencies", () => {
|
|
73
|
+
const value = { x: { __intrinsic: "GetAtt", logicalId: "MyBucket", attribute: "Arn" } };
|
|
74
|
+
const deps = collectDependencies(value, awsPredicate);
|
|
75
|
+
expect(deps).toEqual(new Set(["MyBucket"]));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("skips AWS:: pseudo-parameters", () => {
|
|
79
|
+
const value = { x: { __intrinsic: "Ref", name: "AWS::Region" } };
|
|
80
|
+
const deps = collectDependencies(value, awsPredicate);
|
|
81
|
+
expect(deps.size).toBe(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("collects from nested arrays and objects", () => {
|
|
85
|
+
const value = {
|
|
86
|
+
a: [
|
|
87
|
+
{ __intrinsic: "Ref", name: "A" },
|
|
88
|
+
{ nested: { __intrinsic: "GetAtt", logicalId: "B", attribute: "Arn" } },
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
const deps = collectDependencies(value, awsPredicate);
|
|
92
|
+
expect(deps).toEqual(new Set(["A", "B"]));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("deduplicates", () => {
|
|
96
|
+
const value = [
|
|
97
|
+
{ __intrinsic: "Ref", name: "A" },
|
|
98
|
+
{ __intrinsic: "Ref", name: "A" },
|
|
99
|
+
];
|
|
100
|
+
const deps = collectDependencies(value, awsPredicate);
|
|
101
|
+
expect(deps).toEqual(new Set(["A"]));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR intrinsic scanning utilities.
|
|
3
|
+
*
|
|
4
|
+
* Generic recursive walkers that scan the import IR value trees for
|
|
5
|
+
* intrinsic markers and dependency references.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { TemplateIR } from "./parser";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check if a value tree contains an intrinsic with the given name.
|
|
12
|
+
* Looks for `{ __intrinsic: name }` objects recursively.
|
|
13
|
+
*/
|
|
14
|
+
export function hasIntrinsicInValue(value: unknown, name: string): boolean {
|
|
15
|
+
if (value === null || value === undefined) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return value.some((item) => hasIntrinsicInValue(item, name));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof value === "object") {
|
|
24
|
+
const obj = value as Record<string, unknown>;
|
|
25
|
+
if (obj.__intrinsic === name) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return Object.values(obj).some((v) => hasIntrinsicInValue(v, name));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if any resource in the IR uses a given intrinsic.
|
|
36
|
+
*/
|
|
37
|
+
export function irUsesIntrinsic(ir: TemplateIR, name: string): boolean {
|
|
38
|
+
for (const resource of ir.resources) {
|
|
39
|
+
if (hasIntrinsicInValue(resource.properties, name)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Collect dependency references from a value tree.
|
|
48
|
+
*
|
|
49
|
+
* @param value - The value tree to scan
|
|
50
|
+
* @param isDependency - Given an object, returns a logical ID if it represents
|
|
51
|
+
* a dependency, or null otherwise. Each lexicon provides its own predicate.
|
|
52
|
+
*/
|
|
53
|
+
export function collectDependencies(
|
|
54
|
+
value: unknown,
|
|
55
|
+
isDependency: (obj: Record<string, unknown>) => string | null,
|
|
56
|
+
): Set<string> {
|
|
57
|
+
const deps = new Set<string>();
|
|
58
|
+
collectDepsRecursive(value, isDependency, deps);
|
|
59
|
+
return deps;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function collectDepsRecursive(
|
|
63
|
+
value: unknown,
|
|
64
|
+
isDependency: (obj: Record<string, unknown>) => string | null,
|
|
65
|
+
deps: Set<string>,
|
|
66
|
+
): void {
|
|
67
|
+
if (value === null || value === undefined) return;
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
for (const item of value) {
|
|
71
|
+
collectDepsRecursive(item, isDependency, deps);
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof value === "object") {
|
|
77
|
+
const obj = value as Record<string, unknown>;
|
|
78
|
+
const dep = isDependency(obj);
|
|
79
|
+
if (dep !== null) {
|
|
80
|
+
deps.add(dep);
|
|
81
|
+
} else {
|
|
82
|
+
for (const v of Object.values(obj)) {
|
|
83
|
+
collectDepsRecursive(v, isDependency, deps);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intermediate representation of a template parameter
|
|
3
|
+
*/
|
|
4
|
+
export interface ParameterIR {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly type: string;
|
|
7
|
+
readonly description?: string;
|
|
8
|
+
readonly defaultValue?: unknown;
|
|
9
|
+
readonly required?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Intermediate representation of a template resource
|
|
14
|
+
*/
|
|
15
|
+
export interface ResourceIR {
|
|
16
|
+
readonly logicalId: string;
|
|
17
|
+
readonly type: string;
|
|
18
|
+
readonly properties: Record<string, unknown>;
|
|
19
|
+
readonly metadata?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Intermediate representation of a parsed template
|
|
24
|
+
*/
|
|
25
|
+
export interface TemplateIR {
|
|
26
|
+
readonly resources: ResourceIR[];
|
|
27
|
+
readonly parameters: ParameterIR[];
|
|
28
|
+
readonly metadata?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Interface for template parsers that convert external formats to IR
|
|
33
|
+
*/
|
|
34
|
+
export interface TemplateParser {
|
|
35
|
+
/**
|
|
36
|
+
* Parse template content into intermediate representation
|
|
37
|
+
* @param content - Raw template content (JSON, YAML, etc.)
|
|
38
|
+
* @returns Intermediate representation of the template
|
|
39
|
+
*/
|
|
40
|
+
parse(content: string): TemplateIR;
|
|
41
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant Core
|
|
3
|
+
*
|
|
4
|
+
* Lexicon-agnostic declarative specification system
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from "./declarable";
|
|
8
|
+
export * from "./composite";
|
|
9
|
+
export * from "./intrinsic";
|
|
10
|
+
export * from "./types";
|
|
11
|
+
export * from "./errors";
|
|
12
|
+
export * from "./attrref";
|
|
13
|
+
export * from "./utils";
|
|
14
|
+
export * from "./builder";
|
|
15
|
+
export * from "./serializer";
|
|
16
|
+
export * from "./lexicon-output";
|
|
17
|
+
export * from "./discovery/files";
|
|
18
|
+
export * from "./discovery/import";
|
|
19
|
+
export * from "./discovery/collect";
|
|
20
|
+
export * from "./discovery/cycles";
|
|
21
|
+
export * from "./sort";
|
|
22
|
+
export * from "./discovery/resolve";
|
|
23
|
+
export * from "./discovery/graph";
|
|
24
|
+
export * from "./discovery/index";
|
|
25
|
+
export * from "./discovery/cache";
|
|
26
|
+
export * from "./build";
|
|
27
|
+
export * from "./detectLexicon";
|
|
28
|
+
export * from "./lint/parser";
|
|
29
|
+
export * from "./lint/rule";
|
|
30
|
+
export * from "./lint/rules";
|
|
31
|
+
export * from "./lint/declarative";
|
|
32
|
+
export * from "./lint/selectors";
|
|
33
|
+
export * from "./lint/named-checks";
|
|
34
|
+
export * from "./lint/post-synth";
|
|
35
|
+
export * from "./lint/rule-loader";
|
|
36
|
+
export * from "./import/parser";
|
|
37
|
+
export * from "./import/generator";
|
|
38
|
+
export * from "./lexicon";
|
|
39
|
+
export * from "./lexicon-integrity";
|
|
40
|
+
export * from "./lexicon-manifest";
|
|
41
|
+
export * from "./lexicon-schema";
|
|
42
|
+
export * from "./config";
|
|
43
|
+
export * from "./validation";
|
|
44
|
+
export * from "./project-validation";
|
|
45
|
+
export { barrel } from "./barrel";
|
|
46
|
+
export * from "./codegen/naming";
|
|
47
|
+
export * from "./codegen/fetch";
|
|
48
|
+
export * from "./codegen/generate";
|
|
49
|
+
export * from "./codegen/package";
|
|
50
|
+
export * from "./codegen/typecheck";
|
|
51
|
+
export * from "./codegen/rollback";
|
|
52
|
+
export * from "./codegen/coverage";
|
|
53
|
+
export * from "./codegen/validate";
|
|
54
|
+
export * from "./codegen/docs";
|
|
55
|
+
export * from "./runtime";
|
|
56
|
+
export * from "./stack-output";
|
|
57
|
+
export * from "./child-project";
|
|
58
|
+
export * from "./lsp/types";
|
|
59
|
+
export * from "./lsp/lexicon-providers";
|
|
60
|
+
export * from "./mcp/types";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildInterpolatedString, defaultInterpolationSerializer } from "./intrinsic-interpolation";
|
|
3
|
+
import { AttrRef } from "./attrref";
|
|
4
|
+
import { INTRINSIC_MARKER } from "./intrinsic";
|
|
5
|
+
import { DECLARABLE_MARKER } from "./declarable";
|
|
6
|
+
|
|
7
|
+
describe("buildInterpolatedString", () => {
|
|
8
|
+
test("joins parts and values with serializer", () => {
|
|
9
|
+
const result = buildInterpolatedString(
|
|
10
|
+
["Hello ", " world ", "!"],
|
|
11
|
+
["foo", "bar"],
|
|
12
|
+
(v) => `<${v}>`,
|
|
13
|
+
);
|
|
14
|
+
expect(result).toBe("Hello <foo> world <bar>!");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("handles no values", () => {
|
|
18
|
+
const result = buildInterpolatedString(
|
|
19
|
+
["just text"],
|
|
20
|
+
[],
|
|
21
|
+
() => { throw new Error("should not be called"); },
|
|
22
|
+
);
|
|
23
|
+
expect(result).toBe("just text");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("handles empty parts", () => {
|
|
27
|
+
const result = buildInterpolatedString(
|
|
28
|
+
["", ""],
|
|
29
|
+
["only"],
|
|
30
|
+
(v) => String(v),
|
|
31
|
+
);
|
|
32
|
+
expect(result).toBe("only");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("defaultInterpolationSerializer", () => {
|
|
37
|
+
const serialize = defaultInterpolationSerializer(
|
|
38
|
+
(name, attr) => `\${${name}.${attr}}`,
|
|
39
|
+
(ref) => `\${${ref}}`,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
test("serializes AttrRef", () => {
|
|
43
|
+
const parent = {};
|
|
44
|
+
const ref = new AttrRef(parent, "Arn");
|
|
45
|
+
ref._setLogicalName("MyBucket");
|
|
46
|
+
|
|
47
|
+
expect(serialize(ref)).toBe("${MyBucket.Arn}");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("throws for AttrRef without logical name", () => {
|
|
51
|
+
const parent = {};
|
|
52
|
+
const ref = new AttrRef(parent, "Arn");
|
|
53
|
+
|
|
54
|
+
expect(() => serialize(ref)).toThrow("logical name not set");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("serializes Intrinsic with Ref toJSON", () => {
|
|
58
|
+
const intrinsic = {
|
|
59
|
+
[INTRINSIC_MARKER]: true as const,
|
|
60
|
+
toJSON: () => ({ Ref: "AWS::Region" }),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
expect(serialize(intrinsic)).toBe("${AWS::Region}");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("serializes Intrinsic with non-Ref toJSON as String", () => {
|
|
67
|
+
const intrinsic = {
|
|
68
|
+
[INTRINSIC_MARKER]: true as const,
|
|
69
|
+
toJSON: () => ({ "Fn::Sub": "hello" }),
|
|
70
|
+
toString: () => "[SubIntrinsic]",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
expect(serialize(intrinsic)).toBe("[SubIntrinsic]");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("throws for Declarable", () => {
|
|
77
|
+
const declarable = {
|
|
78
|
+
[DECLARABLE_MARKER]: true as const,
|
|
79
|
+
lexicon: "test",
|
|
80
|
+
entityType: "Bucket",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
expect(() => serialize(declarable)).toThrow("Cannot embed Declarable");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("serializes primitives via String()", () => {
|
|
87
|
+
expect(serialize("hello")).toBe("hello");
|
|
88
|
+
expect(serialize(42)).toBe("42");
|
|
89
|
+
expect(serialize(true)).toBe("true");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tagged template interpolation helper.
|
|
3
|
+
*
|
|
4
|
+
* Implements the lockstep walk over tagged template literal parts/values,
|
|
5
|
+
* dispatching on AttrRef / Intrinsic / Declarable / primitive.
|
|
6
|
+
* Each lexicon provides format-specific serialization strings.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AttrRef } from "./attrref";
|
|
10
|
+
import { INTRINSIC_MARKER } from "./intrinsic";
|
|
11
|
+
import { DECLARABLE_MARKER } from "./declarable";
|
|
12
|
+
|
|
13
|
+
export type InterpolationValueSerializer = (value: unknown) => string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default serialization dispatch for interpolated values.
|
|
17
|
+
*
|
|
18
|
+
* Handles: AttrRef → Intrinsic (with Ref special case) → Declarable (throws) → primitive.
|
|
19
|
+
* Lexicons can wrap/extend this for format-specific behavior.
|
|
20
|
+
*
|
|
21
|
+
* @param serializeAttrRef - Format an AttrRef: (logicalName, attribute) → string
|
|
22
|
+
* @param serializeRef - Format a Ref intrinsic: (refName) → string
|
|
23
|
+
*/
|
|
24
|
+
export function defaultInterpolationSerializer(
|
|
25
|
+
serializeAttrRef: (logicalName: string, attribute: string) => string,
|
|
26
|
+
serializeRef: (refName: string) => string,
|
|
27
|
+
): InterpolationValueSerializer {
|
|
28
|
+
return (value: unknown): string => {
|
|
29
|
+
// Handle AttrRef
|
|
30
|
+
if (value instanceof AttrRef) {
|
|
31
|
+
const logicalName = value.getLogicalName();
|
|
32
|
+
if (!logicalName) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Cannot serialize AttrRef for attribute "${value.attribute}": logical name not set`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return serializeAttrRef(logicalName, value.attribute);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Handle Intrinsics with toJSON
|
|
41
|
+
if (
|
|
42
|
+
typeof value === "object" &&
|
|
43
|
+
value !== null &&
|
|
44
|
+
INTRINSIC_MARKER in value &&
|
|
45
|
+
"toJSON" in value &&
|
|
46
|
+
typeof value.toJSON === "function"
|
|
47
|
+
) {
|
|
48
|
+
const result = value.toJSON();
|
|
49
|
+
// If it's a Ref, use the ref serializer
|
|
50
|
+
if (typeof result === "object" && result !== null && "Ref" in result) {
|
|
51
|
+
return serializeRef((result as { Ref: string }).Ref);
|
|
52
|
+
}
|
|
53
|
+
return String(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Handle Declarables — error
|
|
57
|
+
if (
|
|
58
|
+
typeof value === "object" &&
|
|
59
|
+
value !== null &&
|
|
60
|
+
DECLARABLE_MARKER in value
|
|
61
|
+
) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"Cannot embed Declarable directly in Sub template. Use AttrRef instead.",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Primitive
|
|
68
|
+
return String(value);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Walk tagged template literal parts/values in lockstep,
|
|
74
|
+
* serializing each value via the provided serializer.
|
|
75
|
+
*/
|
|
76
|
+
export function buildInterpolatedString(
|
|
77
|
+
templateParts: readonly string[],
|
|
78
|
+
values: readonly unknown[],
|
|
79
|
+
serializeValue: InterpolationValueSerializer,
|
|
80
|
+
): string {
|
|
81
|
+
let result = "";
|
|
82
|
+
for (let i = 0; i < templateParts.length; i++) {
|
|
83
|
+
result += templateParts[i];
|
|
84
|
+
if (i < values.length) {
|
|
85
|
+
result += serializeValue(values[i]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { INTRINSIC_MARKER, resolveIntrinsicValue, isIntrinsic } from "./intrinsic";
|
|
3
|
+
|
|
4
|
+
describe("resolveIntrinsicValue", () => {
|
|
5
|
+
test("returns primitives as-is", () => {
|
|
6
|
+
expect(resolveIntrinsicValue(42)).toBe(42);
|
|
7
|
+
expect(resolveIntrinsicValue("hello")).toBe("hello");
|
|
8
|
+
expect(resolveIntrinsicValue(true)).toBe(true);
|
|
9
|
+
expect(resolveIntrinsicValue(null)).toBe(null);
|
|
10
|
+
expect(resolveIntrinsicValue(undefined)).toBe(undefined);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("returns plain objects as-is", () => {
|
|
14
|
+
const obj = { a: 1 };
|
|
15
|
+
expect(resolveIntrinsicValue(obj)).toBe(obj);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("calls toJSON on intrinsic objects", () => {
|
|
19
|
+
const intrinsic = {
|
|
20
|
+
[INTRINSIC_MARKER]: true as const,
|
|
21
|
+
toJSON: () => ({ Ref: "MyResource" }),
|
|
22
|
+
};
|
|
23
|
+
expect(resolveIntrinsicValue(intrinsic)).toEqual({ Ref: "MyResource" });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("recurses into arrays", () => {
|
|
27
|
+
const intrinsic = {
|
|
28
|
+
[INTRINSIC_MARKER]: true as const,
|
|
29
|
+
toJSON: () => ({ Ref: "A" }),
|
|
30
|
+
};
|
|
31
|
+
const result = resolveIntrinsicValue(["hello", intrinsic, 42]);
|
|
32
|
+
expect(result).toEqual(["hello", { Ref: "A" }, 42]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("recurses into nested arrays", () => {
|
|
36
|
+
const intrinsic = {
|
|
37
|
+
[INTRINSIC_MARKER]: true as const,
|
|
38
|
+
toJSON: () => "resolved",
|
|
39
|
+
};
|
|
40
|
+
const result = resolveIntrinsicValue([[intrinsic], "plain"]);
|
|
41
|
+
expect(result).toEqual([["resolved"], "plain"]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("does not call toJSON on non-intrinsic objects with toJSON", () => {
|
|
45
|
+
const obj = { toJSON: () => "should not be called" };
|
|
46
|
+
expect(resolveIntrinsicValue(obj)).toBe(obj);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("handles intrinsic without toJSON gracefully", () => {
|
|
50
|
+
const intrinsic = { [INTRINSIC_MARKER]: true as const };
|
|
51
|
+
expect(resolveIntrinsicValue(intrinsic)).toBe(intrinsic);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("isIntrinsic", () => {
|
|
56
|
+
test("returns true for intrinsic objects", () => {
|
|
57
|
+
const intrinsic = {
|
|
58
|
+
[INTRINSIC_MARKER]: true as const,
|
|
59
|
+
toJSON: () => ({}),
|
|
60
|
+
};
|
|
61
|
+
expect(isIntrinsic(intrinsic)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("returns false for plain objects", () => {
|
|
65
|
+
expect(isIntrinsic({})).toBe(false);
|
|
66
|
+
expect(isIntrinsic(null)).toBe(false);
|
|
67
|
+
expect(isIntrinsic("string")).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
});
|
package/src/intrinsic.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker symbol for Intrinsic type identification
|
|
3
|
+
*/
|
|
4
|
+
export const INTRINSIC_MARKER = Symbol.for("chant.intrinsic");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base interface for lexicon-provided intrinsic functions
|
|
8
|
+
*/
|
|
9
|
+
export interface Intrinsic {
|
|
10
|
+
readonly [INTRINSIC_MARKER]: true;
|
|
11
|
+
toJSON(): unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Type guard to check if a value is an Intrinsic
|
|
16
|
+
*/
|
|
17
|
+
export function isIntrinsic(value: unknown): value is Intrinsic {
|
|
18
|
+
return (
|
|
19
|
+
typeof value === "object" &&
|
|
20
|
+
value !== null &&
|
|
21
|
+
INTRINSIC_MARKER in value &&
|
|
22
|
+
(value as Record<symbol, unknown>)[INTRINSIC_MARKER] === true
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Recursively unwrap intrinsic values by calling `toJSON()`.
|
|
28
|
+
*
|
|
29
|
+
* - If the value has the INTRINSIC_MARKER and a `toJSON` method, calls it.
|
|
30
|
+
* - If the value is an array, recurses into each element.
|
|
31
|
+
* - Otherwise returns the value as-is.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveIntrinsicValue(value: unknown): unknown {
|
|
34
|
+
if (value !== null && typeof value === "object" && INTRINSIC_MARKER in value) {
|
|
35
|
+
if ("toJSON" in value && typeof value.toJSON === "function") {
|
|
36
|
+
return value.toJSON();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return value.map(resolveIntrinsicValue);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { hashArtifact, computeIntegrity, verifyIntegrity, type ArtifactIntegrity } from "./lexicon-integrity";
|
|
3
|
+
import type { BundleSpec } from "./lexicon";
|
|
4
|
+
|
|
5
|
+
describe("hashArtifact", () => {
|
|
6
|
+
test("returns hex string", () => {
|
|
7
|
+
const hash = hashArtifact("hello world");
|
|
8
|
+
expect(hash).toMatch(/^[0-9a-f]+$/);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("is deterministic", () => {
|
|
12
|
+
const a = hashArtifact("test content");
|
|
13
|
+
const b = hashArtifact("test content");
|
|
14
|
+
expect(a).toBe(b);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("different inputs produce different hashes", () => {
|
|
18
|
+
const a = hashArtifact("content-a");
|
|
19
|
+
const b = hashArtifact("content-b");
|
|
20
|
+
expect(a).not.toBe(b);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
function makeSpec(overrides?: Partial<BundleSpec>): BundleSpec {
|
|
25
|
+
return {
|
|
26
|
+
manifest: { name: "test", version: "1.0.0" },
|
|
27
|
+
registry: '{"Bucket":{"kind":"resource","resourceType":"TestDom::Storage::Bucket","lexicon":"testdom"}}',
|
|
28
|
+
typesDTS: "declare class Bucket {}",
|
|
29
|
+
rules: new Map([["rule1.ts", "export const rule1 = {};"]]),
|
|
30
|
+
skills: new Map([["skill1.md", "# Skill 1"]]),
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("computeIntegrity", () => {
|
|
36
|
+
test("returns xxhash64 algorithm", () => {
|
|
37
|
+
const integrity = computeIntegrity(makeSpec());
|
|
38
|
+
expect(integrity.algorithm).toBe("xxhash64");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("hashes all artifacts", () => {
|
|
42
|
+
const integrity = computeIntegrity(makeSpec());
|
|
43
|
+
expect(integrity.artifacts["manifest.json"]).toBeDefined();
|
|
44
|
+
expect(integrity.artifacts["meta.json"]).toBeDefined();
|
|
45
|
+
expect(integrity.artifacts["types/index.d.ts"]).toBeDefined();
|
|
46
|
+
expect(integrity.artifacts["rules/rule1.ts"]).toBeDefined();
|
|
47
|
+
expect(integrity.artifacts["skills/skill1.md"]).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("composite hash is deterministic", () => {
|
|
51
|
+
const a = computeIntegrity(makeSpec());
|
|
52
|
+
const b = computeIntegrity(makeSpec());
|
|
53
|
+
expect(a.composite).toBe(b.composite);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("composite changes when any artifact changes", () => {
|
|
57
|
+
const original = computeIntegrity(makeSpec());
|
|
58
|
+
const modified = computeIntegrity(makeSpec({ registry: '{"different": true}' }));
|
|
59
|
+
expect(original.composite).not.toBe(modified.composite);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("verifyIntegrity", () => {
|
|
64
|
+
test("passes for matching spec", () => {
|
|
65
|
+
const spec = makeSpec();
|
|
66
|
+
const integrity = computeIntegrity(spec);
|
|
67
|
+
const result = verifyIntegrity(spec, integrity);
|
|
68
|
+
expect(result.ok).toBe(true);
|
|
69
|
+
expect(result.mismatches).toHaveLength(0);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("detects tampered artifact", () => {
|
|
73
|
+
const spec = makeSpec();
|
|
74
|
+
const integrity = computeIntegrity(spec);
|
|
75
|
+
|
|
76
|
+
// Tamper with the registry
|
|
77
|
+
const tampered = makeSpec({ registry: '{"tampered": true}' });
|
|
78
|
+
const result = verifyIntegrity(tampered, integrity);
|
|
79
|
+
expect(result.ok).toBe(false);
|
|
80
|
+
expect(result.mismatches).toContain("meta.json");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("detects extra artifacts", () => {
|
|
84
|
+
const spec = makeSpec();
|
|
85
|
+
const integrity = computeIntegrity(spec);
|
|
86
|
+
|
|
87
|
+
// Add an extra rule
|
|
88
|
+
const modified = makeSpec();
|
|
89
|
+
modified.rules.set("extra.ts", "extra content");
|
|
90
|
+
const result = verifyIntegrity(modified, integrity);
|
|
91
|
+
expect(result.ok).toBe(false);
|
|
92
|
+
expect(result.mismatches).toContain("rules/extra.ts");
|
|
93
|
+
});
|
|
94
|
+
});
|