@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,239 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
validateManifest,
|
|
4
|
+
validateRegistry,
|
|
5
|
+
LexiconManifestSchema,
|
|
6
|
+
IntrinsicDefSchema,
|
|
7
|
+
LexiconEntrySchema,
|
|
8
|
+
} from "./lexicon-schema";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// validateManifest
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
describe("validateManifest (Zod)", () => {
|
|
15
|
+
test("parses valid manifest object", () => {
|
|
16
|
+
const m = validateManifest({
|
|
17
|
+
name: "testdom",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
chantVersion: ">=0.1.0",
|
|
20
|
+
namespace: "TST",
|
|
21
|
+
intrinsics: [{ name: "Interpolate", outputKey: "Fn::Interpolate", isTag: true }],
|
|
22
|
+
pseudoParameters: { StackName: "TestDom::StackName" },
|
|
23
|
+
});
|
|
24
|
+
expect(m.name).toBe("testdom");
|
|
25
|
+
expect(m.version).toBe("1.0.0");
|
|
26
|
+
expect(m.chantVersion).toBe(">=0.1.0");
|
|
27
|
+
expect(m.namespace).toBe("TST");
|
|
28
|
+
expect(m.intrinsics).toHaveLength(1);
|
|
29
|
+
expect(m.pseudoParameters?.StackName).toBe("TestDom::StackName");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("parses valid JSON string", () => {
|
|
33
|
+
const m = validateManifest(JSON.stringify({ name: "testdom", version: "0.1.0" }));
|
|
34
|
+
expect(m.name).toBe("testdom");
|
|
35
|
+
expect(m.version).toBe("0.1.0");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("optional fields can be omitted", () => {
|
|
39
|
+
const m = validateManifest({ name: "gcp", version: "2.0.0" });
|
|
40
|
+
expect(m.chantVersion).toBeUndefined();
|
|
41
|
+
expect(m.namespace).toBeUndefined();
|
|
42
|
+
expect(m.intrinsics).toBeUndefined();
|
|
43
|
+
expect(m.pseudoParameters).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("throws on empty data (null/undefined)", () => {
|
|
47
|
+
expect(() => validateManifest(null)).toThrow("empty");
|
|
48
|
+
expect(() => validateManifest(undefined)).toThrow("empty");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("throws on invalid JSON string", () => {
|
|
52
|
+
expect(() => validateManifest("{bad json")).toThrow("invalid JSON");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("throws on non-object types", () => {
|
|
56
|
+
expect(() => validateManifest(42)).toThrow("must be a JSON object");
|
|
57
|
+
expect(() => validateManifest([])).toThrow("must be a JSON object");
|
|
58
|
+
expect(() => validateManifest(true)).toThrow("must be a JSON object");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("throws on missing name", () => {
|
|
62
|
+
expect(() => validateManifest({ version: "1.0.0" })).toThrow();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("throws on empty name", () => {
|
|
66
|
+
expect(() => validateManifest({ name: "", version: "1.0.0" })).toThrow("name");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("throws on missing version", () => {
|
|
70
|
+
expect(() => validateManifest({ name: "testdom" })).toThrow();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("throws on invalid semver version", () => {
|
|
74
|
+
expect(() => validateManifest({ name: "testdom", version: "not-a-version" })).toThrow("version");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("accepts prerelease and build metadata in version", () => {
|
|
78
|
+
const m = validateManifest({ name: "testdom", version: "1.0.0-alpha.1" });
|
|
79
|
+
expect(m.version).toBe("1.0.0-alpha.1");
|
|
80
|
+
|
|
81
|
+
const m2 = validateManifest({ name: "testdom", version: "1.0.0+build.42" });
|
|
82
|
+
expect(m2.version).toBe("1.0.0+build.42");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("rejects intrinsic with empty name", () => {
|
|
86
|
+
expect(() =>
|
|
87
|
+
validateManifest({
|
|
88
|
+
name: "testdom",
|
|
89
|
+
version: "1.0.0",
|
|
90
|
+
intrinsics: [{ name: "" }],
|
|
91
|
+
}),
|
|
92
|
+
).toThrow("name");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("error path includes field location for nested intrinsic error", () => {
|
|
96
|
+
try {
|
|
97
|
+
validateManifest({
|
|
98
|
+
name: "testdom",
|
|
99
|
+
version: "1.0.0",
|
|
100
|
+
intrinsics: [{ name: "" }],
|
|
101
|
+
});
|
|
102
|
+
expect(true).toBe(false); // should not reach
|
|
103
|
+
} catch (err: unknown) {
|
|
104
|
+
const msg = (err as Error).message;
|
|
105
|
+
expect(msg).toContain("intrinsics");
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// IntrinsicDefSchema direct
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
describe("IntrinsicDefSchema", () => {
|
|
115
|
+
test("valid intrinsic passes", () => {
|
|
116
|
+
const result = IntrinsicDefSchema.safeParse({
|
|
117
|
+
name: "Ref",
|
|
118
|
+
description: "Reference function",
|
|
119
|
+
outputKey: "Ref",
|
|
120
|
+
isTag: false,
|
|
121
|
+
});
|
|
122
|
+
expect(result.success).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("rejects empty name", () => {
|
|
126
|
+
const result = IntrinsicDefSchema.safeParse({ name: "" });
|
|
127
|
+
expect(result.success).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("rejects missing name", () => {
|
|
131
|
+
const result = IntrinsicDefSchema.safeParse({ description: "no name" });
|
|
132
|
+
expect(result.success).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// LexiconEntrySchema direct
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
describe("LexiconEntrySchema", () => {
|
|
141
|
+
test("valid resource entry", () => {
|
|
142
|
+
const result = LexiconEntrySchema.safeParse({
|
|
143
|
+
resourceType: "TestDom::Storage::Bucket",
|
|
144
|
+
kind: "resource",
|
|
145
|
+
lexicon: "testdom",
|
|
146
|
+
attrs: { bucketArn: "BucketArn" },
|
|
147
|
+
createOnly: ["/properties/BucketName"],
|
|
148
|
+
});
|
|
149
|
+
expect(result.success).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("valid property entry (minimal)", () => {
|
|
153
|
+
const result = LexiconEntrySchema.safeParse({
|
|
154
|
+
resourceType: "TestDom::Storage::Bucket.LifecycleRule",
|
|
155
|
+
kind: "property",
|
|
156
|
+
lexicon: "testdom",
|
|
157
|
+
});
|
|
158
|
+
expect(result.success).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("rejects invalid kind", () => {
|
|
162
|
+
const result = LexiconEntrySchema.safeParse({
|
|
163
|
+
resourceType: "Foo",
|
|
164
|
+
kind: "unknown",
|
|
165
|
+
lexicon: "testdom",
|
|
166
|
+
});
|
|
167
|
+
expect(result.success).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// validateRegistry
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
describe("validateRegistry", () => {
|
|
176
|
+
test("parses valid registry JSON", () => {
|
|
177
|
+
const registry = validateRegistry(
|
|
178
|
+
JSON.stringify({
|
|
179
|
+
S3Bucket: {
|
|
180
|
+
resourceType: "TestDom::Storage::Bucket",
|
|
181
|
+
kind: "resource",
|
|
182
|
+
lexicon: "testdom",
|
|
183
|
+
},
|
|
184
|
+
LambdaFunction: {
|
|
185
|
+
resourceType: "TestDom::Compute::Function",
|
|
186
|
+
kind: "resource",
|
|
187
|
+
lexicon: "testdom",
|
|
188
|
+
runtimeDeprecations: { "nodejs14.x": "deprecated" },
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
expect(Object.keys(registry)).toHaveLength(2);
|
|
193
|
+
expect(registry.S3Bucket.kind).toBe("resource");
|
|
194
|
+
expect(registry.LambdaFunction.runtimeDeprecations?.["nodejs14.x"]).toBe("deprecated");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("throws on invalid JSON", () => {
|
|
198
|
+
expect(() => validateRegistry("not json")).toThrow("invalid JSON");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("throws on malformed registry entry", () => {
|
|
202
|
+
expect(() =>
|
|
203
|
+
validateRegistry(
|
|
204
|
+
JSON.stringify({
|
|
205
|
+
BadEntry: { resourceType: 123, kind: "resource", lexicon: "testdom" },
|
|
206
|
+
}),
|
|
207
|
+
),
|
|
208
|
+
).toThrow();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("throws on registry with invalid kind enum", () => {
|
|
212
|
+
expect(() =>
|
|
213
|
+
validateRegistry(
|
|
214
|
+
JSON.stringify({
|
|
215
|
+
BadEntry: { resourceType: "Foo", kind: "module", lexicon: "testdom" },
|
|
216
|
+
}),
|
|
217
|
+
),
|
|
218
|
+
).toThrow();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("throws on non-object registry value", () => {
|
|
222
|
+
expect(() => validateRegistry(JSON.stringify({ entry: "not an object" }))).toThrow();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("error message includes path for nested issues", () => {
|
|
226
|
+
try {
|
|
227
|
+
validateRegistry(
|
|
228
|
+
JSON.stringify({
|
|
229
|
+
MyEntry: { resourceType: "Foo", kind: "bad", lexicon: "testdom" },
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
expect(true).toBe(false);
|
|
233
|
+
} catch (err: unknown) {
|
|
234
|
+
const msg = (err as Error).message;
|
|
235
|
+
// Should include the key or path info
|
|
236
|
+
expect(msg).toContain("registry");
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for lexicon manifest and registry validation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { LexiconManifest, IntrinsicDef } from "./lexicon";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Intrinsic definitions
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export const IntrinsicDefSchema = z.object({
|
|
13
|
+
name: z.string().min(1, "intrinsic name must not be empty"),
|
|
14
|
+
description: z.string().optional(),
|
|
15
|
+
outputKey: z.string().optional(),
|
|
16
|
+
isTag: z.boolean().optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Lexicon manifest
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/;
|
|
24
|
+
|
|
25
|
+
export const LexiconManifestSchema = z.object({
|
|
26
|
+
name: z.string().min(1, "manifest name must not be empty"),
|
|
27
|
+
version: z.string().regex(semverRegex, "version must be valid semver (X.Y.Z)"),
|
|
28
|
+
chantVersion: z.string().optional(),
|
|
29
|
+
namespace: z.string().optional(),
|
|
30
|
+
intrinsics: z.array(IntrinsicDefSchema).optional(),
|
|
31
|
+
pseudoParameters: z.record(z.string(), z.string()).optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Extension constraint schema (matches ExtensionConstraint from lexicon packages)
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
const ExtensionConstraintSchema = z.object({
|
|
39
|
+
name: z.string(),
|
|
40
|
+
type: z.enum(["if_then", "dependent_excluded", "required_or", "required_xor"]),
|
|
41
|
+
condition: z.unknown().optional(),
|
|
42
|
+
requirement: z.unknown().optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Property constraints schema (matches PropertyConstraints from lexicon packages)
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
const PropertyConstraintsSchema = z.object({
|
|
50
|
+
pattern: z.string().optional(),
|
|
51
|
+
minLength: z.number().optional(),
|
|
52
|
+
maxLength: z.number().optional(),
|
|
53
|
+
minimum: z.number().optional(),
|
|
54
|
+
maximum: z.number().optional(),
|
|
55
|
+
format: z.string().optional(),
|
|
56
|
+
const: z.unknown().optional(),
|
|
57
|
+
default: z.unknown().optional(),
|
|
58
|
+
enum: z.array(z.string()).optional(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Lexicon entry (registry entry for a resource or property type)
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
export const LexiconEntrySchema = z.object({
|
|
66
|
+
resourceType: z.string(),
|
|
67
|
+
kind: z.enum(["resource", "property"]),
|
|
68
|
+
lexicon: z.string(),
|
|
69
|
+
attrs: z.record(z.string(), z.string()).optional(),
|
|
70
|
+
constraints: z.array(ExtensionConstraintSchema).optional(),
|
|
71
|
+
propertyConstraints: z.record(z.string(), PropertyConstraintsSchema).optional(),
|
|
72
|
+
createOnly: z.array(z.string()).optional(),
|
|
73
|
+
writeOnly: z.array(z.string()).optional(),
|
|
74
|
+
primaryIdentifier: z.array(z.string()).optional(),
|
|
75
|
+
runtimeDeprecations: z.record(z.string(), z.string()).optional(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Inferred types (useful for consumers who don't import the interface types)
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
export type LexiconEntryParsed = z.infer<typeof LexiconEntrySchema>;
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Validation helpers
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate a manifest from unknown input (object or JSON string).
|
|
90
|
+
* Throws a descriptive error on invalid input.
|
|
91
|
+
*/
|
|
92
|
+
export function validateManifest(data: unknown): LexiconManifest {
|
|
93
|
+
if (data === null || data === undefined) {
|
|
94
|
+
throw new Error("manifest data is empty");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof data === "string") {
|
|
98
|
+
try {
|
|
99
|
+
data = JSON.parse(data);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
const msg = err instanceof SyntaxError ? err.message : String(err);
|
|
102
|
+
throw new Error(`invalid JSON in manifest: ${msg}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
107
|
+
throw new Error("manifest must be a JSON object");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = LexiconManifestSchema.safeParse(data);
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
const issue = result.error.issues[0];
|
|
113
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : undefined;
|
|
114
|
+
const prefix = path ? `manifest field ${path}` : "manifest";
|
|
115
|
+
throw new Error(`${prefix}: ${issue.message}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result.data as LexiconManifest;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate a registry JSON string into a typed record.
|
|
123
|
+
* Throws a descriptive error on invalid input.
|
|
124
|
+
*/
|
|
125
|
+
export function validateRegistry(json: string): Record<string, LexiconEntryParsed> {
|
|
126
|
+
let parsed: unknown;
|
|
127
|
+
try {
|
|
128
|
+
parsed = JSON.parse(json);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const msg = err instanceof SyntaxError ? err.message : String(err);
|
|
131
|
+
throw new Error(`invalid JSON in registry: ${msg}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const schema = z.record(z.string(), LexiconEntrySchema);
|
|
135
|
+
const result = schema.safeParse(parsed);
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
const issue = result.error.issues[0];
|
|
138
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : undefined;
|
|
139
|
+
const prefix = path ? `registry[${path}]` : "registry";
|
|
140
|
+
throw new Error(`${prefix}: ${issue.message}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result.data;
|
|
144
|
+
}
|
package/src/lexicon.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { Serializer } from "./serializer";
|
|
2
|
+
import type { LintRule } from "./lint/rule";
|
|
3
|
+
import type { RuleSpec } from "./lint/declarative";
|
|
4
|
+
import type { PostSynthCheck } from "./lint/post-synth";
|
|
5
|
+
import type { TemplateParser } from "./import/parser";
|
|
6
|
+
import type { TypeScriptGenerator } from "./import/generator";
|
|
7
|
+
import type { ArtifactIntegrity } from "./lexicon-integrity";
|
|
8
|
+
import type { CompletionContext, CompletionItem, HoverContext, HoverInfo, CodeActionContext, CodeAction } from "./lsp/types";
|
|
9
|
+
import type { McpToolContribution, McpResourceContribution } from "./mcp/types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manifest for a packaged lexicon — metadata embedded in the tarball.
|
|
13
|
+
*/
|
|
14
|
+
export interface LexiconManifest {
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
chantVersion?: string;
|
|
18
|
+
namespace?: string;
|
|
19
|
+
intrinsics?: IntrinsicDef[];
|
|
20
|
+
pseudoParameters?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Metadata about when and how a package was generated.
|
|
25
|
+
*/
|
|
26
|
+
export interface PackageMetadata {
|
|
27
|
+
generatedAt: string;
|
|
28
|
+
chantVersion: string;
|
|
29
|
+
generatorVersion: string;
|
|
30
|
+
sourceSchemaCount: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Container for all artifacts in a lexicon bundle.
|
|
35
|
+
*/
|
|
36
|
+
export interface BundleSpec {
|
|
37
|
+
manifest: LexiconManifest;
|
|
38
|
+
registry: string;
|
|
39
|
+
typesDTS: string;
|
|
40
|
+
rules: Map<string, string>;
|
|
41
|
+
skills: Map<string, string>;
|
|
42
|
+
integrity?: ArtifactIntegrity;
|
|
43
|
+
metadata?: PackageMetadata;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Trigger that determines when a skill should be suggested.
|
|
48
|
+
*/
|
|
49
|
+
export interface SkillTrigger {
|
|
50
|
+
type: "command" | "file-pattern" | "context";
|
|
51
|
+
value: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parameter definition for a skill.
|
|
56
|
+
*/
|
|
57
|
+
export interface SkillParameter {
|
|
58
|
+
name: string;
|
|
59
|
+
description: string;
|
|
60
|
+
type: string;
|
|
61
|
+
required?: boolean;
|
|
62
|
+
default?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Example showing how a skill is used.
|
|
67
|
+
*/
|
|
68
|
+
export interface SkillExample {
|
|
69
|
+
title: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
input?: string;
|
|
72
|
+
output?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Definition of a skill provided by a lexicon plugin.
|
|
77
|
+
* Content is full markdown with optional YAML frontmatter.
|
|
78
|
+
*/
|
|
79
|
+
export interface SkillDefinition {
|
|
80
|
+
readonly name: string;
|
|
81
|
+
readonly description: string;
|
|
82
|
+
readonly content: string;
|
|
83
|
+
readonly triggers?: SkillTrigger[];
|
|
84
|
+
readonly parameters?: SkillParameter[];
|
|
85
|
+
readonly examples?: SkillExample[];
|
|
86
|
+
readonly preConditions?: string[];
|
|
87
|
+
readonly postConditions?: string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Definition of a lexicon-specific intrinsic function
|
|
92
|
+
*/
|
|
93
|
+
export interface IntrinsicDef {
|
|
94
|
+
readonly name: string;
|
|
95
|
+
readonly description?: string;
|
|
96
|
+
readonly outputKey?: string;
|
|
97
|
+
readonly isTag?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Plugin interface for lexicon packages.
|
|
102
|
+
*
|
|
103
|
+
* Required lifecycle methods enforce consistency: every lexicon must support
|
|
104
|
+
* generate, validate, coverage, package, and rollback operations.
|
|
105
|
+
*/
|
|
106
|
+
export interface LexiconPlugin {
|
|
107
|
+
// ── Required ──────────────────────────────────────────────
|
|
108
|
+
/** Human-readable name (e.g. "aws", "gcp") */
|
|
109
|
+
readonly name: string;
|
|
110
|
+
|
|
111
|
+
/** Serializer for build output */
|
|
112
|
+
readonly serializer: Serializer;
|
|
113
|
+
|
|
114
|
+
/** Generate lexicon artifacts (types, lexicon JSON, runtime) from spec */
|
|
115
|
+
generate(options?: { verbose?: boolean }): Promise<void>;
|
|
116
|
+
|
|
117
|
+
/** Validate generated lexicon artifacts */
|
|
118
|
+
validate(options?: { verbose?: boolean }): Promise<void>;
|
|
119
|
+
|
|
120
|
+
/** Analyze lexicon coverage across resource dimensions */
|
|
121
|
+
coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void>;
|
|
122
|
+
|
|
123
|
+
/** Package lexicon into distributable tarball */
|
|
124
|
+
package(options?: { verbose?: boolean; force?: boolean }): Promise<void>;
|
|
125
|
+
|
|
126
|
+
/** List or restore generation snapshots */
|
|
127
|
+
rollback(options?: { restore?: string; verbose?: boolean }): Promise<void>;
|
|
128
|
+
|
|
129
|
+
// ── Optional extensions ───────────────────────────────────
|
|
130
|
+
/** Return lint rules provided by this lexicon */
|
|
131
|
+
lintRules?(): LintRule[];
|
|
132
|
+
|
|
133
|
+
/** Return declarative rule specs for compilation via rule() */
|
|
134
|
+
declarativeRules?(): RuleSpec[];
|
|
135
|
+
|
|
136
|
+
/** Return post-synthesis checks for build validation */
|
|
137
|
+
postSynthChecks?(): PostSynthCheck[];
|
|
138
|
+
|
|
139
|
+
/** Return intrinsic function definitions */
|
|
140
|
+
intrinsics?(): IntrinsicDef[];
|
|
141
|
+
|
|
142
|
+
/** Return pseudo-parameter names (e.g. "MyDomain::StackName") */
|
|
143
|
+
pseudoParameters?(): string[];
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Detect whether raw template content belongs to this lexicon.
|
|
147
|
+
* @param data - Parsed JSON object from a template file
|
|
148
|
+
* @returns true if this plugin can handle the template
|
|
149
|
+
*/
|
|
150
|
+
detectTemplate?(data: unknown): boolean;
|
|
151
|
+
|
|
152
|
+
/** Return a parser for importing external templates into IR */
|
|
153
|
+
templateParser?(): TemplateParser;
|
|
154
|
+
|
|
155
|
+
/** Return a generator for converting IR to TypeScript */
|
|
156
|
+
templateGenerator?(): TypeScriptGenerator;
|
|
157
|
+
|
|
158
|
+
/** Return skills provided by this lexicon */
|
|
159
|
+
skills?(): SkillDefinition[];
|
|
160
|
+
|
|
161
|
+
/** Return source file templates for `chant init` project scaffolding */
|
|
162
|
+
initTemplates?(): Record<string, string>;
|
|
163
|
+
|
|
164
|
+
/** Optional initialization hook */
|
|
165
|
+
init?(): void | Promise<void>;
|
|
166
|
+
|
|
167
|
+
// LSP
|
|
168
|
+
/** Provide completions for LSP */
|
|
169
|
+
completionProvider?(ctx: CompletionContext): CompletionItem[];
|
|
170
|
+
|
|
171
|
+
/** Provide hover information for LSP */
|
|
172
|
+
hoverProvider?(ctx: HoverContext): HoverInfo | undefined;
|
|
173
|
+
|
|
174
|
+
/** Provide code actions for LSP */
|
|
175
|
+
codeActionProvider?(ctx: CodeActionContext): CodeAction[];
|
|
176
|
+
|
|
177
|
+
// Docs
|
|
178
|
+
/** Generate documentation pages */
|
|
179
|
+
docs?(options?: { verbose?: boolean }): Promise<void>;
|
|
180
|
+
|
|
181
|
+
// MCP
|
|
182
|
+
/** Return MCP tool contributions */
|
|
183
|
+
mcpTools?(): McpToolContribution[];
|
|
184
|
+
|
|
185
|
+
/** Return MCP resource contributions */
|
|
186
|
+
mcpResources?(): McpResourceContribution[];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Type guard to check if a value is a LexiconPlugin.
|
|
191
|
+
* Checks for required lifecycle methods in addition to name/serializer.
|
|
192
|
+
*/
|
|
193
|
+
export function isLexiconPlugin(value: unknown): value is LexiconPlugin {
|
|
194
|
+
if (
|
|
195
|
+
typeof value !== "object" ||
|
|
196
|
+
value === null ||
|
|
197
|
+
!("name" in value) ||
|
|
198
|
+
typeof (value as Record<string, unknown>).name !== "string" ||
|
|
199
|
+
!("serializer" in value) ||
|
|
200
|
+
typeof (value as Record<string, unknown>).serializer !== "object"
|
|
201
|
+
) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
const obj = value as Record<string, unknown>;
|
|
205
|
+
return (
|
|
206
|
+
typeof obj.generate === "function" &&
|
|
207
|
+
typeof obj.validate === "function" &&
|
|
208
|
+
typeof obj.coverage === "function" &&
|
|
209
|
+
typeof obj.package === "function" &&
|
|
210
|
+
typeof obj.rollback === "function"
|
|
211
|
+
);
|
|
212
|
+
}
|