@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,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content hashing and integrity verification for lexicon artifacts.
|
|
3
|
+
*/
|
|
4
|
+
import type { BundleSpec } from "./lexicon";
|
|
5
|
+
|
|
6
|
+
export interface ArtifactIntegrity {
|
|
7
|
+
algorithm: "xxhash64";
|
|
8
|
+
artifacts: Record<string, string>;
|
|
9
|
+
composite: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hash a single artifact's content using xxhash64.
|
|
14
|
+
*/
|
|
15
|
+
export function hashArtifact(content: string): string {
|
|
16
|
+
return Bun.hash(content).toString(16);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Compute integrity for all artifacts in a BundleSpec.
|
|
21
|
+
*/
|
|
22
|
+
export function computeIntegrity(spec: BundleSpec): ArtifactIntegrity {
|
|
23
|
+
const artifacts: Record<string, string> = {};
|
|
24
|
+
|
|
25
|
+
// Hash each artifact
|
|
26
|
+
artifacts["manifest.json"] = hashArtifact(JSON.stringify(spec.manifest, null, 2));
|
|
27
|
+
artifacts["meta.json"] = hashArtifact(spec.registry);
|
|
28
|
+
artifacts["types/index.d.ts"] = hashArtifact(spec.typesDTS);
|
|
29
|
+
|
|
30
|
+
for (const [name, content] of spec.rules) {
|
|
31
|
+
artifacts[`rules/${name}`] = hashArtifact(content);
|
|
32
|
+
}
|
|
33
|
+
for (const [name, content] of spec.skills) {
|
|
34
|
+
artifacts[`skills/${name}`] = hashArtifact(content);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Composite hash: sorted key:hash pairs
|
|
38
|
+
const sorted = Object.entries(artifacts).sort(([a], [b]) => a.localeCompare(b));
|
|
39
|
+
const compositeInput = sorted.map(([k, v]) => `${k}:${v}`).join("\n");
|
|
40
|
+
const composite = hashArtifact(compositeInput);
|
|
41
|
+
|
|
42
|
+
return { algorithm: "xxhash64", artifacts, composite };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Verify integrity of a BundleSpec against expected hashes.
|
|
47
|
+
*/
|
|
48
|
+
export function verifyIntegrity(
|
|
49
|
+
spec: BundleSpec,
|
|
50
|
+
expected: ArtifactIntegrity,
|
|
51
|
+
): { ok: boolean; mismatches: string[] } {
|
|
52
|
+
const actual = computeIntegrity(spec);
|
|
53
|
+
const mismatches: string[] = [];
|
|
54
|
+
|
|
55
|
+
for (const [key, expectedHash] of Object.entries(expected.artifacts)) {
|
|
56
|
+
if (actual.artifacts[key] !== expectedHash) {
|
|
57
|
+
mismatches.push(key);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for artifacts in actual but not in expected
|
|
62
|
+
for (const key of Object.keys(actual.artifacts)) {
|
|
63
|
+
if (!(key in expected.artifacts)) {
|
|
64
|
+
mismatches.push(key);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { ok: mismatches.length === 0, mismatches };
|
|
69
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { validateManifest } from "./lexicon-schema";
|
|
3
|
+
import { checkVersionCompatibility } from "./lexicon-manifest";
|
|
4
|
+
|
|
5
|
+
describe("validateManifest", () => {
|
|
6
|
+
test("parses valid manifest object", () => {
|
|
7
|
+
const m = validateManifest({
|
|
8
|
+
name: "testdom",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
chantVersion: ">=0.1.0",
|
|
11
|
+
namespace: "TST",
|
|
12
|
+
intrinsics: [{ name: "Interpolate", outputKey: "Fn::Interpolate", isTag: true }],
|
|
13
|
+
pseudoParameters: { StackName: "TestDom::StackName" },
|
|
14
|
+
});
|
|
15
|
+
expect(m.name).toBe("testdom");
|
|
16
|
+
expect(m.version).toBe("1.0.0");
|
|
17
|
+
expect(m.chantVersion).toBe(">=0.1.0");
|
|
18
|
+
expect(m.namespace).toBe("TST");
|
|
19
|
+
expect(m.intrinsics).toHaveLength(1);
|
|
20
|
+
expect(m.pseudoParameters?.StackName).toBe("TestDom::StackName");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("parses valid JSON string", () => {
|
|
24
|
+
const m = validateManifest(JSON.stringify({ name: "testdom", version: "0.1.0" }));
|
|
25
|
+
expect(m.name).toBe("testdom");
|
|
26
|
+
expect(m.version).toBe("0.1.0");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("optional chantVersion", () => {
|
|
30
|
+
const m = validateManifest({ name: "gcp", version: "2.0.0" });
|
|
31
|
+
expect(m.chantVersion).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("throws on empty data", () => {
|
|
35
|
+
expect(() => validateManifest(null)).toThrow("empty");
|
|
36
|
+
expect(() => validateManifest(undefined)).toThrow("empty");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("throws on invalid JSON string", () => {
|
|
40
|
+
expect(() => validateManifest("{bad json")).toThrow("invalid JSON");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("throws on missing name", () => {
|
|
44
|
+
expect(() => validateManifest({ version: "1.0.0" })).toThrow("name");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("throws on missing version", () => {
|
|
48
|
+
expect(() => validateManifest({ name: "testdom" })).toThrow("version");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("throws on non-object", () => {
|
|
52
|
+
expect(() => validateManifest(42)).toThrow("must be a JSON object");
|
|
53
|
+
expect(() => validateManifest([])).toThrow("must be a JSON object");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("checkVersionCompatibility", () => {
|
|
58
|
+
test(">= operator", () => {
|
|
59
|
+
expect(checkVersionCompatibility(">=0.1.0", "0.1.0")).toBe(true);
|
|
60
|
+
expect(checkVersionCompatibility(">=0.1.0", "0.2.0")).toBe(true);
|
|
61
|
+
expect(checkVersionCompatibility(">=0.1.0", "1.0.0")).toBe(true);
|
|
62
|
+
expect(checkVersionCompatibility(">=0.2.0", "0.1.0")).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("<= operator", () => {
|
|
66
|
+
expect(checkVersionCompatibility("<=1.0.0", "1.0.0")).toBe(true);
|
|
67
|
+
expect(checkVersionCompatibility("<=1.0.0", "0.9.0")).toBe(true);
|
|
68
|
+
expect(checkVersionCompatibility("<=1.0.0", "1.1.0")).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("> operator", () => {
|
|
72
|
+
expect(checkVersionCompatibility(">0.1.0", "0.2.0")).toBe(true);
|
|
73
|
+
expect(checkVersionCompatibility(">0.1.0", "0.1.0")).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("< operator", () => {
|
|
77
|
+
expect(checkVersionCompatibility("<1.0.0", "0.9.0")).toBe(true);
|
|
78
|
+
expect(checkVersionCompatibility("<1.0.0", "1.0.0")).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("= operator and bare version (exact match)", () => {
|
|
82
|
+
expect(checkVersionCompatibility("=1.0.0", "1.0.0")).toBe(true);
|
|
83
|
+
expect(checkVersionCompatibility("=1.0.0", "1.0.1")).toBe(false);
|
|
84
|
+
expect(checkVersionCompatibility("1.0.0", "1.0.0")).toBe(true);
|
|
85
|
+
expect(checkVersionCompatibility("1.0.0", "1.0.1")).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("empty range always passes", () => {
|
|
89
|
+
expect(checkVersionCompatibility("", "1.0.0")).toBe(true);
|
|
90
|
+
expect(checkVersionCompatibility(" ", "1.0.0")).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("invalid version returns false", () => {
|
|
94
|
+
expect(checkVersionCompatibility(">=abc", "1.0.0")).toBe(false);
|
|
95
|
+
expect(checkVersionCompatibility(">=1.0.0", "abc")).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("two-part version", () => {
|
|
99
|
+
expect(checkVersionCompatibility(">=0.1", "0.1.0")).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexicon manifest validation and version compatibility checking.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if the current version satisfies a semver range requirement.
|
|
7
|
+
* Supports: >=X.Y.Z, <=X.Y.Z, >X.Y.Z, <X.Y.Z, =X.Y.Z, X.Y.Z (exact match)
|
|
8
|
+
*/
|
|
9
|
+
export function checkVersionCompatibility(required: string, current: string): boolean {
|
|
10
|
+
const trimmed = required.trim();
|
|
11
|
+
if (!trimmed) return true;
|
|
12
|
+
|
|
13
|
+
let op: string;
|
|
14
|
+
let versionStr: string;
|
|
15
|
+
|
|
16
|
+
if (trimmed.startsWith(">=")) {
|
|
17
|
+
op = ">=";
|
|
18
|
+
versionStr = trimmed.slice(2).trim();
|
|
19
|
+
} else if (trimmed.startsWith("<=")) {
|
|
20
|
+
op = "<=";
|
|
21
|
+
versionStr = trimmed.slice(2).trim();
|
|
22
|
+
} else if (trimmed.startsWith(">")) {
|
|
23
|
+
op = ">";
|
|
24
|
+
versionStr = trimmed.slice(1).trim();
|
|
25
|
+
} else if (trimmed.startsWith("<")) {
|
|
26
|
+
op = "<";
|
|
27
|
+
versionStr = trimmed.slice(1).trim();
|
|
28
|
+
} else if (trimmed.startsWith("=")) {
|
|
29
|
+
op = "=";
|
|
30
|
+
versionStr = trimmed.slice(1).trim();
|
|
31
|
+
} else {
|
|
32
|
+
op = "=";
|
|
33
|
+
versionStr = trimmed;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const requiredParts = parseVersion(versionStr);
|
|
37
|
+
const currentParts = parseVersion(current.trim());
|
|
38
|
+
|
|
39
|
+
if (!requiredParts || !currentParts) return false;
|
|
40
|
+
|
|
41
|
+
const cmp = compareVersions(currentParts, requiredParts);
|
|
42
|
+
|
|
43
|
+
switch (op) {
|
|
44
|
+
case ">=": return cmp >= 0;
|
|
45
|
+
case "<=": return cmp <= 0;
|
|
46
|
+
case ">": return cmp > 0;
|
|
47
|
+
case "<": return cmp < 0;
|
|
48
|
+
case "=": return cmp === 0;
|
|
49
|
+
default: return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseVersion(v: string): [number, number, number] | null {
|
|
54
|
+
// Strip leading 'v'
|
|
55
|
+
if (v.startsWith("v")) v = v.slice(1);
|
|
56
|
+
|
|
57
|
+
const parts = v.split(".");
|
|
58
|
+
if (parts.length < 2 || parts.length > 3) return null;
|
|
59
|
+
|
|
60
|
+
const nums = parts.map(Number);
|
|
61
|
+
if (nums.some(isNaN)) return null;
|
|
62
|
+
|
|
63
|
+
return [nums[0], nums[1], nums[2] ?? 0];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function compareVersions(a: [number, number, number], b: [number, number, number]): number {
|
|
67
|
+
for (let i = 0; i < 3; i++) {
|
|
68
|
+
if (a[i] !== b[i]) return a[i] - b[i];
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { LexiconOutput, output, isLexiconOutput } from "./lexicon-output";
|
|
3
|
+
import { AttrRef } from "./attrref";
|
|
4
|
+
import { INTRINSIC_MARKER } from "./intrinsic";
|
|
5
|
+
import { DECLARABLE_MARKER } from "./declarable";
|
|
6
|
+
import { collectLexiconOutputs } from "./build";
|
|
7
|
+
import type { Declarable } from "./declarable";
|
|
8
|
+
|
|
9
|
+
// Mock entity with lexicon field
|
|
10
|
+
class MockResource {
|
|
11
|
+
readonly [DECLARABLE_MARKER] = true as const;
|
|
12
|
+
readonly lexicon = "testdom";
|
|
13
|
+
readonly entityType = "TestDom::Storage::Bucket";
|
|
14
|
+
readonly arn: AttrRef;
|
|
15
|
+
readonly props: Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.arn = new AttrRef(this, "Arn");
|
|
19
|
+
this.props = {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("LexiconOutput", () => {
|
|
24
|
+
test("implements Intrinsic marker", () => {
|
|
25
|
+
const bucket = new MockResource();
|
|
26
|
+
const lexiconOutput = new LexiconOutput(bucket.arn, "DataBucketArn");
|
|
27
|
+
|
|
28
|
+
expect(lexiconOutput[INTRINSIC_MARKER]).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("extracts sourceLexicon from parent entity", () => {
|
|
32
|
+
const bucket = new MockResource();
|
|
33
|
+
const lexiconOutput = new LexiconOutput(bucket.arn, "DataBucketArn");
|
|
34
|
+
|
|
35
|
+
expect(lexiconOutput.sourceLexicon).toBe("testdom");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("extracts sourceAttribute from AttrRef", () => {
|
|
39
|
+
const bucket = new MockResource();
|
|
40
|
+
const lexiconOutput = new LexiconOutput(bucket.arn, "DataBucketArn");
|
|
41
|
+
|
|
42
|
+
expect(lexiconOutput.sourceAttribute).toBe("Arn");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("stores outputName", () => {
|
|
46
|
+
const bucket = new MockResource();
|
|
47
|
+
const lexiconOutput = new LexiconOutput(bucket.arn, "DataBucketArn");
|
|
48
|
+
|
|
49
|
+
expect(lexiconOutput.outputName).toBe("DataBucketArn");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("sourceEntity starts empty and can be set internally", () => {
|
|
53
|
+
const bucket = new MockResource();
|
|
54
|
+
const lexiconOutput = new LexiconOutput(bucket.arn, "DataBucketArn");
|
|
55
|
+
|
|
56
|
+
expect(lexiconOutput.sourceEntity).toBe("");
|
|
57
|
+
lexiconOutput._setSourceEntity("dataBucket");
|
|
58
|
+
expect(lexiconOutput.sourceEntity).toBe("dataBucket");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("toJSON serializes to chant::output marker", () => {
|
|
62
|
+
const bucket = new MockResource();
|
|
63
|
+
const lexiconOutput = new LexiconOutput(bucket.arn, "DataBucketArn");
|
|
64
|
+
|
|
65
|
+
expect(lexiconOutput.toJSON()).toEqual({ "chant::output": "DataBucketArn" });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("throws when parent has no lexicon field", () => {
|
|
69
|
+
const noLexiconParent = {};
|
|
70
|
+
const ref = new AttrRef(noLexiconParent, "Arn");
|
|
71
|
+
|
|
72
|
+
expect(() => new LexiconOutput(ref, "Test")).toThrow("no lexicon field");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("LexiconOutput.auto", () => {
|
|
77
|
+
test("creates output with auto-generated name from entity and attribute", () => {
|
|
78
|
+
const bucket = new MockResource();
|
|
79
|
+
const result = LexiconOutput.auto(bucket.arn, "dataBucket");
|
|
80
|
+
|
|
81
|
+
expect(result).toBeInstanceOf(LexiconOutput);
|
|
82
|
+
expect(result.outputName).toBe("dataBucket_Arn");
|
|
83
|
+
expect(result.sourceLexicon).toBe("testdom");
|
|
84
|
+
expect(result.sourceEntity).toBe("dataBucket");
|
|
85
|
+
expect(result.sourceAttribute).toBe("Arn");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("auto-generated name follows {entityName}_{attribute} pattern", () => {
|
|
89
|
+
const bucket = new MockResource();
|
|
90
|
+
const endpointRef = new AttrRef(bucket, "Endpoint");
|
|
91
|
+
const result = LexiconOutput.auto(endpointRef, "myBucket");
|
|
92
|
+
|
|
93
|
+
expect(result.outputName).toBe("myBucket_Endpoint");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("sets sourceEntity immediately", () => {
|
|
97
|
+
const bucket = new MockResource();
|
|
98
|
+
const result = LexiconOutput.auto(bucket.arn, "logsBucket");
|
|
99
|
+
|
|
100
|
+
// sourceEntity should be set right away, not empty
|
|
101
|
+
expect(result.sourceEntity).toBe("logsBucket");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("output() helper", () => {
|
|
106
|
+
test("creates LexiconOutput from AttrRef and name", () => {
|
|
107
|
+
const bucket = new MockResource();
|
|
108
|
+
const result = output(bucket.arn, "DataBucketArn");
|
|
109
|
+
|
|
110
|
+
expect(result).toBeInstanceOf(LexiconOutput);
|
|
111
|
+
expect(result.sourceLexicon).toBe("testdom");
|
|
112
|
+
expect(result.sourceAttribute).toBe("Arn");
|
|
113
|
+
expect(result.outputName).toBe("DataBucketArn");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("isLexiconOutput", () => {
|
|
118
|
+
test("returns true for LexiconOutput instances", () => {
|
|
119
|
+
const bucket = new MockResource();
|
|
120
|
+
const lexiconOutput = new LexiconOutput(bucket.arn, "DataBucketArn");
|
|
121
|
+
|
|
122
|
+
expect(isLexiconOutput(lexiconOutput)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("returns false for non-LexiconOutput values", () => {
|
|
126
|
+
expect(isLexiconOutput(null)).toBe(false);
|
|
127
|
+
expect(isLexiconOutput(undefined)).toBe(false);
|
|
128
|
+
expect(isLexiconOutput("string")).toBe(false);
|
|
129
|
+
expect(isLexiconOutput(42)).toBe(false);
|
|
130
|
+
expect(isLexiconOutput({})).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("returns false for AttrRef", () => {
|
|
134
|
+
const parent = { lexicon: "testdom" };
|
|
135
|
+
const ref = new AttrRef(parent, "Arn");
|
|
136
|
+
|
|
137
|
+
expect(isLexiconOutput(ref)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("collectLexiconOutputs", () => {
|
|
142
|
+
test("collects LexiconOutputs from entity props", () => {
|
|
143
|
+
const bucket = new MockResource();
|
|
144
|
+
const lexiconOutput = output(bucket.arn, "DataBucketArn");
|
|
145
|
+
bucket.props.outputRef = lexiconOutput;
|
|
146
|
+
|
|
147
|
+
const entities = new Map<string, Declarable>();
|
|
148
|
+
entities.set("dataBucket", bucket as unknown as Declarable);
|
|
149
|
+
|
|
150
|
+
const collected = collectLexiconOutputs(entities);
|
|
151
|
+
|
|
152
|
+
expect(collected).toHaveLength(1);
|
|
153
|
+
expect(collected[0].outputName).toBe("DataBucketArn");
|
|
154
|
+
expect(collected[0].sourceEntity).toBe("dataBucket");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("returns empty array when no LexiconOutputs found", () => {
|
|
158
|
+
const entities = new Map<string, Declarable>();
|
|
159
|
+
entities.set("bucket", {
|
|
160
|
+
lexicon: "testdom",
|
|
161
|
+
entityType: "TestDom::Storage::Bucket",
|
|
162
|
+
[DECLARABLE_MARKER]: true,
|
|
163
|
+
} as Declarable);
|
|
164
|
+
|
|
165
|
+
const collected = collectLexiconOutputs(entities);
|
|
166
|
+
expect(collected).toHaveLength(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("collects LexiconOutputs from nested props", () => {
|
|
170
|
+
const bucket = new MockResource();
|
|
171
|
+
const lexiconOutput = output(bucket.arn, "BucketArn");
|
|
172
|
+
bucket.props.nested = { deep: { ref: lexiconOutput } };
|
|
173
|
+
|
|
174
|
+
const entities = new Map<string, Declarable>();
|
|
175
|
+
entities.set("dataBucket", bucket as unknown as Declarable);
|
|
176
|
+
|
|
177
|
+
const collected = collectLexiconOutputs(entities);
|
|
178
|
+
|
|
179
|
+
expect(collected).toHaveLength(1);
|
|
180
|
+
expect(collected[0].outputName).toBe("BucketArn");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { INTRINSIC_MARKER, type Intrinsic } from "./intrinsic";
|
|
2
|
+
import { AttrRef } from "./attrref";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents a cross-lexicon output that bridges a producing lexicon (e.g. AWS)
|
|
6
|
+
* with any consuming lexicon (e.g. GitHub, Cloudflare).
|
|
7
|
+
*
|
|
8
|
+
* Implements Intrinsic so it can be used as Value<string> anywhere.
|
|
9
|
+
*/
|
|
10
|
+
export class LexiconOutput implements Intrinsic {
|
|
11
|
+
readonly [INTRINSIC_MARKER] = true as const;
|
|
12
|
+
readonly sourceLexicon: string;
|
|
13
|
+
readonly sourceEntity: string;
|
|
14
|
+
readonly sourceAttribute: string;
|
|
15
|
+
readonly outputName: string;
|
|
16
|
+
/** @internal WeakRef to the source entity object for identity-based matching */
|
|
17
|
+
readonly _sourceParent: WeakRef<object>;
|
|
18
|
+
|
|
19
|
+
constructor(ref: AttrRef, name: string) {
|
|
20
|
+
const parent = ref.parent.deref();
|
|
21
|
+
if (!parent) {
|
|
22
|
+
throw new Error("Cannot create LexiconOutput: parent entity has been garbage collected");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!("lexicon" in parent) || typeof (parent as Record<string, unknown>).lexicon !== "string") {
|
|
26
|
+
throw new Error("Cannot create LexiconOutput: parent entity has no lexicon field");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.sourceLexicon = (parent as Record<string, unknown>).lexicon as string;
|
|
30
|
+
this.sourceEntity = "";
|
|
31
|
+
this.sourceAttribute = ref.attribute;
|
|
32
|
+
this.outputName = name;
|
|
33
|
+
this._sourceParent = ref.parent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Set the source entity logical name.
|
|
38
|
+
* Called during build when entity names are resolved.
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
_setSourceEntity(name: string): void {
|
|
42
|
+
(this as { sourceEntity: string }).sourceEntity = name;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a LexiconOutput with an auto-generated name from entity name and attribute.
|
|
47
|
+
* Used during cross-lexicon ref auto-detection.
|
|
48
|
+
*
|
|
49
|
+
* @param ref - The AttrRef pointing to the source entity
|
|
50
|
+
* @param entityName - The logical name of the source entity
|
|
51
|
+
* @returns A LexiconOutput with name `{entityName}_{attribute}`
|
|
52
|
+
*/
|
|
53
|
+
static auto(ref: AttrRef, entityName: string): LexiconOutput {
|
|
54
|
+
const name = `${entityName}_${ref.attribute}`;
|
|
55
|
+
const output = new LexiconOutput(ref, name);
|
|
56
|
+
output._setSourceEntity(entityName);
|
|
57
|
+
return output;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
toJSON(): { "chant::output": string } {
|
|
61
|
+
return { "chant::output": this.outputName };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create a LexiconOutput from an AttrRef and a user-provided output name.
|
|
67
|
+
*
|
|
68
|
+
* Usage:
|
|
69
|
+
* ```ts
|
|
70
|
+
* const bucketArn = output(dataBucket.arn, "DataBucketArn");
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function output(ref: AttrRef, name: string): LexiconOutput {
|
|
74
|
+
return new LexiconOutput(ref, name);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Type guard to check if a value is a LexiconOutput
|
|
79
|
+
*/
|
|
80
|
+
export function isLexiconOutput(value: unknown): value is LexiconOutput {
|
|
81
|
+
return value instanceof LexiconOutput;
|
|
82
|
+
}
|