@intentius/chant 0.19.0 → 0.20.0
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/dist/build-params.d.ts +108 -0
- package/dist/build-params.d.ts.map +1 -0
- package/dist/build.d.ts +79 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +31 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +41 -0
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -0
- package/dist/cli/commands/check-lexicon-intrinsics.d.ts +54 -0
- package/dist/cli/commands/check-lexicon-intrinsics.d.ts.map +1 -0
- package/dist/cli/commands/check-lexicon.d.ts +1 -1
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts +27 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/run-client.d.ts +1 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +16 -0
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +10 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-sections.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +12 -5
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts +62 -7
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/sandbox/driver.d.ts +12 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -0
- package/dist/components/sandbox/run.d.ts +42 -0
- package/dist/components/sandbox/run.d.ts.map +1 -0
- package/dist/composite.d.ts +5 -0
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +71 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/entity-wire-codec.d.ts +166 -0
- package/dist/discovery/entity-wire-codec.d.ts.map +1 -0
- package/dist/discovery/entity-wire.d.ts +50 -0
- package/dist/discovery/entity-wire.d.ts.map +1 -0
- package/dist/discovery/fold-import.d.ts +210 -0
- package/dist/discovery/fold-import.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +74 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.d.ts +18 -0
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -0
- package/dist/discovery/sandbox/child-errors.d.ts +15 -0
- package/dist/discovery/sandbox/child-errors.d.ts.map +1 -0
- package/dist/discovery/sandbox/driver.d.ts +13 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -0
- package/dist/discovery/sandbox/run.d.ts +69 -0
- package/dist/discovery/sandbox/run.d.ts.map +1 -0
- package/dist/errors.d.ts +9 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +226 -0
- package/dist/fold/fold.d.ts.map +1 -0
- package/dist/fold/subset.d.ts +102 -0
- package/dist/fold/subset.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon-output.d.ts +7 -2
- package/dist/lexicon-output.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -2
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +81 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +11 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lint/component-checks.d.ts +7 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
- package/dist/lint/rules/evl003-dynamic-property-access.d.ts.map +1 -1
- package/dist/params.d.ts +60 -0
- package/dist/params.d.ts.map +1 -0
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/terraform/aws-resources.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/build-params.test.ts +144 -0
- package/src/build-params.ts +207 -0
- package/src/build.test.ts +38 -0
- package/src/build.ts +144 -7
- package/src/cli/commands/build.test.ts +220 -2
- package/src/cli/commands/build.ts +111 -3
- package/src/cli/commands/check-lexicon-examples.test.ts +92 -0
- package/src/cli/commands/check-lexicon-examples.ts +103 -0
- package/src/cli/commands/check-lexicon-intrinsics.test.ts +145 -0
- package/src/cli/commands/check-lexicon-intrinsics.ts +312 -0
- package/src/cli/commands/check-lexicon.test.ts +34 -0
- package/src/cli/commands/check-lexicon.ts +119 -1
- package/src/cli/commands/lint.ts +31 -3
- package/src/cli/commands/onboard.ts +1 -1
- package/src/cli/component-security-boundary.test.ts +170 -0
- package/src/cli/handlers/build.ts +24 -3
- package/src/cli/handlers/components.ts +9 -2
- package/src/cli/handlers/dev.ts +1 -1
- package/src/cli/handlers/graph.ts +7 -5
- package/src/cli/handlers/lifecycle.ts +2 -2
- package/src/cli/handlers/lint.ts +2 -0
- package/src/cli/handlers/misc.ts +2 -2
- package/src/cli/handlers/run-client.ts +1 -1
- package/src/cli/handlers/run.ts +20 -5
- package/src/cli/main.test.ts +22 -0
- package/src/cli/main.ts +39 -0
- package/src/cli/plugins.ts +20 -4
- package/src/cli/registry.ts +10 -0
- package/src/cli/security-boundary.test.ts +135 -0
- package/src/codegen/docs-sections.test.ts +61 -0
- package/src/codegen/docs-sections.ts +7 -3
- package/src/components/cli-support.ts +22 -10
- package/src/components/discover.ts +127 -25
- package/src/components/sandbox/driver.ts +114 -0
- package/src/components/sandbox/run.test.ts +185 -0
- package/src/components/sandbox/run.ts +177 -0
- package/src/composite.test.ts +21 -0
- package/src/composite.ts +20 -1
- package/src/config.ts +81 -0
- package/src/discovery/collect.ts +17 -3
- package/src/discovery/entity-wire-codec.ts +485 -0
- package/src/discovery/entity-wire.test.ts +240 -0
- package/src/discovery/entity-wire.ts +67 -0
- package/src/discovery/fold-import.test.ts +1026 -0
- package/src/discovery/fold-import.ts +1805 -0
- package/src/discovery/index.test.ts +191 -1
- package/src/discovery/index.ts +233 -1
- package/src/discovery/sandbox/bundle.ts +218 -0
- package/src/discovery/sandbox/child-errors.ts +65 -0
- package/src/discovery/sandbox/driver.ts +147 -0
- package/src/discovery/sandbox/run.test.ts +179 -0
- package/src/discovery/sandbox/run.ts +196 -0
- package/src/errors.ts +9 -1
- package/src/fold/fold.test.ts +535 -0
- package/src/fold/fold.ts +648 -0
- package/src/fold/subset-doc-parity.test.ts +183 -0
- package/src/fold/subset.test.ts +241 -0
- package/src/fold/subset.ts +302 -0
- package/src/index.ts +2 -0
- package/src/lexicon-output.ts +7 -2
- package/src/lexicon-schema.test.ts +14 -0
- package/src/lexicon-schema.ts +3 -1
- package/src/lexicon.ts +82 -1
- package/src/lifecycle/git.test.ts +10 -5
- package/src/lifecycle/release-ledger.test.ts +28 -0
- package/src/lifecycle/release-ledger.ts +11 -0
- package/src/lint/component-checks.ts +8 -1
- package/src/lint/rules/evl001-non-literal-expression.ts +26 -110
- package/src/lint/rules/evl003-dynamic-property-access.ts +11 -2
- package/src/params.test.ts +22 -0
- package/src/params.ts +66 -0
- package/src/provenance.ts +22 -0
- package/src/terraform/adopt-state.test.ts +1 -1
- package/src/terraform/aws-resources.test.ts +11 -4
- package/src/terraform/aws-resources.ts +60 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { encodeEntitySet, decodeEntitySet, type EntitySetWire } from "./entity-wire";
|
|
3
|
+
import { walkValue } from "../serializer-walker";
|
|
4
|
+
import { createResource, createProperty } from "../runtime";
|
|
5
|
+
import { AttrRef } from "../attrref";
|
|
6
|
+
import { INTRINSIC_MARKER, type Intrinsic } from "../intrinsic";
|
|
7
|
+
import { DECLARABLE_MARKER, type Declarable } from "../declarable";
|
|
8
|
+
import { CHILD_PROJECT_MARKER } from "../child-project";
|
|
9
|
+
import { STACK_OUTPUT_MARKER, type StackOutput } from "../stack-output";
|
|
10
|
+
import { LexiconOutput, isLexiconOutput } from "../lexicon-output";
|
|
11
|
+
import { resolveAttrRefs } from "./resolve";
|
|
12
|
+
import { isAttrRefLike } from "../utils";
|
|
13
|
+
|
|
14
|
+
/** Proves the wire data really is plain JSON — no functions, symbols, or class instances leak through. */
|
|
15
|
+
function assertPureJson(value: unknown): void {
|
|
16
|
+
expect(() => JSON.parse(JSON.stringify(value))).not.toThrow();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("entity-wire round trip (chant #1045 Phase 1)", () => {
|
|
20
|
+
test("resource with primitive props and an AttrRef attribute reference", () => {
|
|
21
|
+
const Vpc = createResource("Test::Vpc", "test", { vpcId: "VpcId" });
|
|
22
|
+
const Subnet = createResource("Test::Subnet", "test", { subnetId: "SubnetId" });
|
|
23
|
+
|
|
24
|
+
const vpc = new Vpc({ CidrBlock: "10.0.0.0/16", EnableDnsSupport: true, InstanceCount: 2 });
|
|
25
|
+
const subnet = new Subnet({ VpcId: (vpc as unknown as Record<string, AttrRef>).vpcId });
|
|
26
|
+
|
|
27
|
+
const entities = new Map<string, Declarable>([
|
|
28
|
+
["Vpc", vpc as unknown as Declarable],
|
|
29
|
+
["Subnet", subnet as unknown as Declarable],
|
|
30
|
+
]);
|
|
31
|
+
resolveAttrRefs(entities);
|
|
32
|
+
|
|
33
|
+
const wire = encodeEntitySet(entities);
|
|
34
|
+
assertPureJson(wire);
|
|
35
|
+
|
|
36
|
+
const roundTripped = JSON.parse(JSON.stringify(wire)) as EntitySetWire;
|
|
37
|
+
const decoded = decodeEntitySet(roundTripped);
|
|
38
|
+
|
|
39
|
+
const decodedSubnet = decoded.get("Subnet") as unknown as { props: { VpcId: unknown } };
|
|
40
|
+
const decodedVpcId = decodedSubnet.props.VpcId;
|
|
41
|
+
expect(isAttrRefLike(decodedVpcId)).toBe(true);
|
|
42
|
+
expect((decodedVpcId as AttrRef).getLogicalName()).toBe("Vpc");
|
|
43
|
+
expect((decodedVpcId as AttrRef).attribute).toBe("VpcId");
|
|
44
|
+
|
|
45
|
+
const decodedVpc = decoded.get("Vpc") as unknown as { props: Record<string, unknown> };
|
|
46
|
+
expect(decodedVpc.props).toEqual({ CidrBlock: "10.0.0.0/16", EnableDnsSupport: true, InstanceCount: 2 });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("whole-entity embed (DependsOn-style) preserves identity by reference", () => {
|
|
50
|
+
const Bucket = createResource("Test::Bucket", "test", {});
|
|
51
|
+
const Waiter = createResource("Test::Waiter", "test", {});
|
|
52
|
+
|
|
53
|
+
const bucket = new Bucket({});
|
|
54
|
+
const waiter = new Waiter({}, { DependsOn: [bucket] });
|
|
55
|
+
|
|
56
|
+
const entities = new Map<string, Declarable>([
|
|
57
|
+
["Bucket", bucket as unknown as Declarable],
|
|
58
|
+
["Waiter", waiter as unknown as Declarable],
|
|
59
|
+
]);
|
|
60
|
+
resolveAttrRefs(entities);
|
|
61
|
+
|
|
62
|
+
const wire = encodeEntitySet(entities);
|
|
63
|
+
assertPureJson(wire);
|
|
64
|
+
|
|
65
|
+
const decoded = decodeEntitySet(JSON.parse(JSON.stringify(wire)) as EntitySetWire);
|
|
66
|
+
const decodedWaiter = decoded.get("Waiter") as unknown as { attributes: { DependsOn: unknown[] } };
|
|
67
|
+
const decodedBucket = decoded.get("Bucket");
|
|
68
|
+
|
|
69
|
+
// The whole-entity reference must resolve to the SAME reconstructed
|
|
70
|
+
// object as the one in the entities map, not a structurally-equal clone —
|
|
71
|
+
// this is exactly what `entityNames.get(decl)` (serializer-walker.ts /
|
|
72
|
+
// resolveDependsOn) relies on.
|
|
73
|
+
expect(decodedWaiter.attributes.DependsOn[0]).toBe(decodedBucket);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("nested property-kind Declarable round-trips as inline data", () => {
|
|
77
|
+
const Tag = createProperty("Test::Tag", "test");
|
|
78
|
+
const Bucket = createResource("Test::Bucket", "test", {});
|
|
79
|
+
|
|
80
|
+
const bucket = new Bucket({ Tags: [new Tag({ Key: "Name", Value: "example" })] });
|
|
81
|
+
const entities = new Map<string, Declarable>([["Bucket", bucket as unknown as Declarable]]);
|
|
82
|
+
resolveAttrRefs(entities);
|
|
83
|
+
|
|
84
|
+
const wire = encodeEntitySet(entities);
|
|
85
|
+
assertPureJson(wire);
|
|
86
|
+
|
|
87
|
+
const decoded = decodeEntitySet(JSON.parse(JSON.stringify(wire)) as EntitySetWire);
|
|
88
|
+
const decodedBucket = decoded.get("Bucket") as unknown as { props: { Tags: Array<{ kind: string; props: unknown }> } };
|
|
89
|
+
expect(decodedBucket.props.Tags[0].kind).toBe("property");
|
|
90
|
+
expect(decodedBucket.props.Tags[0].props).toEqual({ Key: "Name", Value: "example" });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("marker Declarable (StackOutput-shaped) round-trips its marker symbol and extra fields", () => {
|
|
94
|
+
const Vpc = createResource("Test::Vpc", "test", { vpcId: "VpcId" });
|
|
95
|
+
const vpc = new Vpc({});
|
|
96
|
+
|
|
97
|
+
const stackOutput: StackOutput = {
|
|
98
|
+
[STACK_OUTPUT_MARKER]: true,
|
|
99
|
+
[DECLARABLE_MARKER]: true,
|
|
100
|
+
lexicon: "test",
|
|
101
|
+
entityType: "chant:output",
|
|
102
|
+
kind: "output",
|
|
103
|
+
sourceRef: (vpc as unknown as Record<string, AttrRef>).vpcId,
|
|
104
|
+
description: "the vpc id",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const entities = new Map<string, Declarable>([
|
|
108
|
+
["Vpc", vpc as unknown as Declarable],
|
|
109
|
+
["VpcIdOutput", stackOutput],
|
|
110
|
+
]);
|
|
111
|
+
resolveAttrRefs(entities);
|
|
112
|
+
|
|
113
|
+
const wire = encodeEntitySet(entities);
|
|
114
|
+
assertPureJson(wire);
|
|
115
|
+
|
|
116
|
+
const decoded = decodeEntitySet(JSON.parse(JSON.stringify(wire)) as EntitySetWire);
|
|
117
|
+
const decodedOutput = decoded.get("VpcIdOutput") as unknown as Record<symbol, unknown> & { sourceRef: unknown; description: string };
|
|
118
|
+
expect(decodedOutput[STACK_OUTPUT_MARKER]).toBe(true);
|
|
119
|
+
expect(decodedOutput[DECLARABLE_MARKER]).toBe(true);
|
|
120
|
+
expect(decodedOutput.description).toBe("the vpc id");
|
|
121
|
+
expect(isAttrRefLike(decodedOutput.sourceRef)).toBe(true);
|
|
122
|
+
expect((decodedOutput.sourceRef as AttrRef).getLogicalName()).toBe("Vpc");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("LexiconOutput built from an AttrRef round-trips via its real constructor", () => {
|
|
126
|
+
const Vpc = createResource("Test::Vpc", "test", { vpcId: "VpcId" });
|
|
127
|
+
const vpc = new Vpc({});
|
|
128
|
+
|
|
129
|
+
const entities = new Map<string, Declarable>([["Vpc", vpc as unknown as Declarable]]);
|
|
130
|
+
resolveAttrRefs(entities);
|
|
131
|
+
const lexOutput = new LexiconOutput((vpc as unknown as Record<string, AttrRef>).vpcId, "VpcIdOut");
|
|
132
|
+
entities.set("VpcIdOut", lexOutput as unknown as Declarable);
|
|
133
|
+
|
|
134
|
+
const wire = encodeEntitySet(entities);
|
|
135
|
+
assertPureJson(wire);
|
|
136
|
+
|
|
137
|
+
const decoded = decodeEntitySet(JSON.parse(JSON.stringify(wire)) as EntitySetWire);
|
|
138
|
+
const decodedOutput = decoded.get("VpcIdOut");
|
|
139
|
+
expect(isLexiconOutput(decodedOutput)).toBe(true);
|
|
140
|
+
const lo = decodedOutput as unknown as LexiconOutput;
|
|
141
|
+
expect(lo.outputName).toBe("VpcIdOut");
|
|
142
|
+
expect(lo.sourceLexicon).toBe("test");
|
|
143
|
+
expect(lo.sourceAttribute).toBe("VpcId");
|
|
144
|
+
// `sourceEntity` is genuinely "" until `build.ts`'s `collectLexiconOutputs`
|
|
145
|
+
// calls `_setSourceEntity` by matching `_sourceParent`'s identity against
|
|
146
|
+
// the entities map (this test only exercises decode, not the full build
|
|
147
|
+
// pipeline) — so what matters here is that `_sourceParent` derefs to the
|
|
148
|
+
// SAME reconstructed "Vpc" object `collectLexiconOutputs` would match by
|
|
149
|
+
// identity, exactly like the in-process path.
|
|
150
|
+
const internal = lo as unknown as { _sourceParent: WeakRef<object> | null };
|
|
151
|
+
expect(internal._sourceParent?.deref()).toBe(decoded.get("Vpc"));
|
|
152
|
+
lo._setSourceEntity("Vpc");
|
|
153
|
+
expect(lo.getOutputValue()).toEqual({ "Fn::GetAtt": ["Vpc", "VpcId"] });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("LexiconOutput built from a generic Intrinsic round-trips its toJSON output and embedded refs", () => {
|
|
157
|
+
const Vpc = createResource("Test::Vpc", "test", { vpcId: "VpcId" });
|
|
158
|
+
const vpc = new Vpc({});
|
|
159
|
+
const entities = new Map<string, Declarable>([["Vpc", vpc as unknown as Declarable]]);
|
|
160
|
+
resolveAttrRefs(entities);
|
|
161
|
+
|
|
162
|
+
const vpcIdRef = (vpc as unknown as Record<string, AttrRef>).vpcId;
|
|
163
|
+
const customIntrinsic: Intrinsic & { values: unknown[] } = {
|
|
164
|
+
[INTRINSIC_MARKER]: true,
|
|
165
|
+
values: [vpcIdRef],
|
|
166
|
+
toJSON: () => ({ "Fn::Join": [",", [{ __attrRef: { entity: "Vpc", attribute: "VpcId" } }]] }),
|
|
167
|
+
};
|
|
168
|
+
const lexOutput = new LexiconOutput(customIntrinsic, "JoinedOut");
|
|
169
|
+
entities.set("JoinedOut", lexOutput as unknown as Declarable);
|
|
170
|
+
|
|
171
|
+
const wire = encodeEntitySet(entities);
|
|
172
|
+
assertPureJson(wire);
|
|
173
|
+
|
|
174
|
+
const decoded = decodeEntitySet(JSON.parse(JSON.stringify(wire)) as EntitySetWire);
|
|
175
|
+
const decodedOutput = decoded.get("JoinedOut") as unknown as LexiconOutput;
|
|
176
|
+
expect(isLexiconOutput(decodedOutput)).toBe(true);
|
|
177
|
+
// A same-shape `{__attrRef}` envelope that was ALREADY baked into the
|
|
178
|
+
// original intrinsic's own `toJSON()` output (as `resolveIntrinsicValue`
|
|
179
|
+
// does for real lexicon intrinsics like `Join`) may decode back as either
|
|
180
|
+
// a plain envelope or a real `AttrRef` instance — `walkValue` (the only
|
|
181
|
+
// thing that ever reads this value in production) treats both
|
|
182
|
+
// identically, so assert through it rather than on the raw shape.
|
|
183
|
+
const entityNames = new Map<Declarable, string>();
|
|
184
|
+
expect(
|
|
185
|
+
walkValue(decodedOutput.getOutputValue(), entityNames, {
|
|
186
|
+
attrRef: (name, attr) => ({ "Fn::GetAtt": [name, attr] }),
|
|
187
|
+
resourceRef: (name) => ({ Ref: name }),
|
|
188
|
+
propertyDeclarable: () => undefined,
|
|
189
|
+
}),
|
|
190
|
+
).toEqual({ "Fn::Join": [",", [{ "Fn::GetAtt": ["Vpc", "VpcId"] }]] });
|
|
191
|
+
// The embedded-refs safety net: a real AttrRef should still be
|
|
192
|
+
// discoverable by walking the reconstructed intrinsic's own fields (not
|
|
193
|
+
// through toJSON()) — this is what `detectCrossLexiconRefs`/
|
|
194
|
+
// `buildDependencyGraph` rely on.
|
|
195
|
+
const embedded = (decodedOutput as unknown as { _intrinsic: { __chantWireRefs: unknown[] } })._intrinsic.__chantWireRefs;
|
|
196
|
+
expect(embedded).toHaveLength(1);
|
|
197
|
+
expect(isAttrRefLike(embedded[0])).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("a lexicon-specific marker symbol (not core-owned) round-trips generically", () => {
|
|
201
|
+
const MARKER = Symbol.for("chant.test.customMarker");
|
|
202
|
+
const custom: Declarable = {
|
|
203
|
+
[MARKER]: true,
|
|
204
|
+
[DECLARABLE_MARKER]: true,
|
|
205
|
+
lexicon: "test",
|
|
206
|
+
entityType: "chant:test:custom",
|
|
207
|
+
tags: [{ Key: "a", Value: "b" }],
|
|
208
|
+
} as unknown as Declarable;
|
|
209
|
+
|
|
210
|
+
const entities = new Map<string, Declarable>([["Custom", custom]]);
|
|
211
|
+
resolveAttrRefs(entities);
|
|
212
|
+
|
|
213
|
+
const wire = encodeEntitySet(entities);
|
|
214
|
+
assertPureJson(wire);
|
|
215
|
+
|
|
216
|
+
const decoded = decodeEntitySet(JSON.parse(JSON.stringify(wire)) as EntitySetWire);
|
|
217
|
+
const decodedCustom = decoded.get("Custom") as unknown as Record<symbol, unknown> & { tags: unknown };
|
|
218
|
+
expect(decodedCustom[MARKER]).toBe(true);
|
|
219
|
+
expect(decodedCustom.tags).toEqual([{ Key: "a", Value: "b" }]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("a child project entity is rejected rather than silently mis-encoded", () => {
|
|
223
|
+
const childProject: Declarable = {
|
|
224
|
+
[CHILD_PROJECT_MARKER]: true,
|
|
225
|
+
[DECLARABLE_MARKER]: true,
|
|
226
|
+
lexicon: "test",
|
|
227
|
+
entityType: "chant:childProject",
|
|
228
|
+
kind: "resource",
|
|
229
|
+
projectPath: "/tmp/child",
|
|
230
|
+
logicalName: "Child",
|
|
231
|
+
outputs: {},
|
|
232
|
+
options: {},
|
|
233
|
+
} as unknown as Declarable;
|
|
234
|
+
|
|
235
|
+
const entities = new Map<string, Declarable>([["Child", childProject]]);
|
|
236
|
+
resolveAttrRefs(entities);
|
|
237
|
+
|
|
238
|
+
expect(() => encodeEntitySet(entities)).toThrow(/child project/i);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chant #1045 (Phase 1) — the JSON wire format for a discovered, named,
|
|
3
|
+
* ref-resolved entity set, plus (below) the standalone `discover()` +
|
|
4
|
+
* encode entry point.
|
|
5
|
+
*
|
|
6
|
+
* The codec itself — {@link WireValue}, {@link EntitySetWire},
|
|
7
|
+
* {@link encodeEntitySet}, {@link decodeEntitySet}, and friends — lives in
|
|
8
|
+
* `./entity-wire-codec.ts` (chant #1045 Phase 2 split this out) and is
|
|
9
|
+
* re-exported here unchanged. That file has NO dependency on `discover()` (or
|
|
10
|
+
* anything that pulls in the `typescript` compiler package), which matters
|
|
11
|
+
* because chant #1045 Phase 2's sandboxed child bundles JUST the codec —
|
|
12
|
+
* bundling this file, `discoverEntitySetJson` and all, would drag `discover`
|
|
13
|
+
* → `fold-import` → `typescript` (a multi-megabyte CJS package that doesn't
|
|
14
|
+
* survive ESM bundling on its own) into a driver that only ever needs to
|
|
15
|
+
* encode, never to discover. See `./sandbox/driver.ts`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
WireValue,
|
|
20
|
+
WireDeclarableEntity,
|
|
21
|
+
WireLexiconOutputEntity,
|
|
22
|
+
WireEntity,
|
|
23
|
+
EntitySetWire,
|
|
24
|
+
} from "./entity-wire-codec";
|
|
25
|
+
export { encodeEntitySet, decodeEntitySet } from "./entity-wire-codec";
|
|
26
|
+
|
|
27
|
+
import type { FoldDecision } from "./index";
|
|
28
|
+
import { discover, type DiscoveryOptions } from "./index";
|
|
29
|
+
import { encodeEntitySet, type EntitySetWire } from "./entity-wire-codec";
|
|
30
|
+
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
32
|
+
// Standalone entry point (chant#1045 Phase 1, ask #2): "run these files,
|
|
33
|
+
// produce named ref-resolved entity specs" as pure JSON.
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/** A discovery run's full result as pure JSON — {@link encodeEntitySet} plus the already-JSON-safe rest of {@link DiscoveryResult} (`./index.ts`). */
|
|
37
|
+
export interface DiscoveredEntitiesJson {
|
|
38
|
+
entitySet: EntitySetWire;
|
|
39
|
+
sourceFiles: string[];
|
|
40
|
+
/** `DiscoveryError.toJSON()` output — see `../errors.ts`. */
|
|
41
|
+
errors: Array<{ name: string; file: string; message: string; type: string }>;
|
|
42
|
+
foldDecisions: FoldDecision[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run discovery over `path` — scanning files, importing/folding modules,
|
|
47
|
+
* naming entities, and resolving `AttrRef`s (all unchanged, reusing
|
|
48
|
+
* `discover()` as-is) — and return the result as pure JSON via
|
|
49
|
+
* {@link encodeEntitySet}. This is the boundary chant#1045 Phase 2 moved into
|
|
50
|
+
* a sandboxed child process for the run-fallback subset — see
|
|
51
|
+
* `./sandbox/run.ts` — Phase 1 only proved the data can cross it losslessly
|
|
52
|
+
* (see the fold-vs-JSON differential in `examples/`).
|
|
53
|
+
*
|
|
54
|
+
* Reuses `discover()` unchanged, so the fold/run-fallback split and the
|
|
55
|
+
* `planFoldTaint` identity-taint invariant (`./fold-import.ts`) are exactly
|
|
56
|
+
* what they are today — this function only serializes whatever `discover()`
|
|
57
|
+
* already decided to produce.
|
|
58
|
+
*/
|
|
59
|
+
export async function discoverEntitySetJson(path: string, options?: DiscoveryOptions): Promise<DiscoveredEntitiesJson> {
|
|
60
|
+
const result = await discover(path, options);
|
|
61
|
+
return {
|
|
62
|
+
entitySet: encodeEntitySet(result.entities),
|
|
63
|
+
sourceFiles: result.sourceFiles,
|
|
64
|
+
errors: result.errors.map((e) => e.toJSON()),
|
|
65
|
+
foldDecisions: result.foldDecisions,
|
|
66
|
+
};
|
|
67
|
+
}
|