@intentius/chant 0.108.1 → 0.110.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/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/composite.d.ts +5 -0
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -0
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +59 -4
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +17 -2
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/fold-provenance.d.ts +49 -4
- package/dist/fold-provenance.d.ts.map +1 -1
- package/dist/import/ir-utils.d.ts.map +1 -1
- package/dist/import/parser.d.ts +8 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/provenance.d.ts +49 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/serializer-walker.d.ts.map +1 -1
- package/dist/stack-output.d.ts +5 -0
- package/dist/stack-output.d.ts.map +1 -1
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/import.test.ts +5 -4
- package/src/cli/handlers/lifecycle.test.ts +106 -1
- package/src/cli/handlers/lifecycle.ts +26 -5
- package/src/composite.ts +37 -0
- package/src/discovery/collect.ts +13 -3
- package/src/discovery/fold-composite.test.ts +22 -0
- package/src/discovery/fold-host-composite.test.ts +23 -0
- package/src/discovery/fold-import.ts +324 -30
- package/src/discovery/fold-package-composite.test.ts +14 -1
- package/src/discovery/fold-project-provenance.test.ts +584 -0
- package/src/discovery/param-deps.test.ts +163 -3
- package/src/discovery/param-deps.ts +173 -4
- package/src/fold/subset-public-export.test.ts +38 -0
- package/src/fold/subset.ts +17 -2
- package/src/fold-provenance.test.ts +190 -16
- package/src/fold-provenance.ts +158 -13
- package/src/import/ir-utils.ts +6 -1
- package/src/import/parser.ts +8 -0
- package/src/provenance.ts +59 -2
- package/src/serializer-walker.test.ts +10 -0
- package/src/serializer-walker.ts +17 -1
- package/src/stack-output.ts +6 -1
- package/src/yaml.test.ts +6 -0
- package/src/yaml.ts +4 -0
package/src/provenance.ts
CHANGED
|
@@ -32,6 +32,13 @@ export interface EntityProvenance {
|
|
|
32
32
|
* collapse a composite to a single node at coarse diagram detail levels (#494).
|
|
33
33
|
*/
|
|
34
34
|
compositeInstance?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Where the composite call that expanded this entity was written (chant
|
|
37
|
+
* #3597): the `WebApp({...})` expression, not the factory body. Recorded
|
|
38
|
+
* only when the fold interpreted the factory at a call it could see; the
|
|
39
|
+
* run path and a nested call made from already-folded values leave it unset.
|
|
40
|
+
*/
|
|
41
|
+
compositeCall?: SourceLocation;
|
|
35
42
|
/**
|
|
36
43
|
* Per-path origins within the entity's `props` (chant #1443). Read with
|
|
37
44
|
* {@link originOfPath}, which does the prefix resolution these keys are
|
|
@@ -80,6 +87,12 @@ export interface EntityProvenance {
|
|
|
80
87
|
* expression for this path reads no parameter at all. The composite fixes
|
|
81
88
|
* this field, and no argument at the call site moves it.
|
|
82
89
|
*
|
|
90
|
+
* - `host` — the authored expression for this path reads a value host code
|
|
91
|
+
* produced (INTENTIUS/typescript-as-data#248): a method called on a real
|
|
92
|
+
* receiver or an eager lexicon helper (`host-call`), or a value a lexicon
|
|
93
|
+
* package exports (`host-value`). The author wrote the call, not the value,
|
|
94
|
+
* so ./fold-provenance.ts answers `unknown` rather than `direct`.
|
|
95
|
+
*
|
|
83
96
|
* The coarse `composite` and the two fine kinds are not alternatives. A build
|
|
84
97
|
* records `composite` when it expanded a composite without interpreting its
|
|
85
98
|
* body (the run path, an inadmissible factory), and that is why
|
|
@@ -90,8 +103,49 @@ export type PathOrigin =
|
|
|
90
103
|
| { kind: "authored" }
|
|
91
104
|
| { kind: "composite"; composite: string; instance: string }
|
|
92
105
|
| { kind: "build-param"; params: string[] }
|
|
93
|
-
| {
|
|
94
|
-
|
|
106
|
+
| {
|
|
107
|
+
kind: "composite-parameter";
|
|
108
|
+
composite: string;
|
|
109
|
+
parameters: string[];
|
|
110
|
+
/**
|
|
111
|
+
* Where each parameter's argument was written at the composite call
|
|
112
|
+
* (chant #3597), in `parameters` order. Absent when the build did not
|
|
113
|
+
* see the call; see {@link ArgumentLocation} for the fallback.
|
|
114
|
+
*/
|
|
115
|
+
arguments?: ArgumentLocation[];
|
|
116
|
+
}
|
|
117
|
+
| { kind: "composite-literal"; composite: string }
|
|
118
|
+
| { kind: "host"; reason: HostOriginReason };
|
|
119
|
+
|
|
120
|
+
/** Which host code produced a value: a call it ran, or a value its package exports. */
|
|
121
|
+
export type HostOriginReason = "host-call" | "host-value";
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A place in a source file. `file` is absolute, as
|
|
125
|
+
* {@link EntityProvenance.sourceFile} is; `line` and `column` are 1-based, the
|
|
126
|
+
* way an editor and `file:line:column` print them.
|
|
127
|
+
*/
|
|
128
|
+
export interface SourceLocation {
|
|
129
|
+
file: string;
|
|
130
|
+
line: number;
|
|
131
|
+
column: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Where one composite parameter's argument was written at the call (chant
|
|
136
|
+
* #3597). When the call's argument object has a property at the parameter's
|
|
137
|
+
* path, the location is that property and `text` is the property as written
|
|
138
|
+
* (`replicas: 3`). When it does not (the parameter was left out, or arrived
|
|
139
|
+
* through a spread or a non-literal argument), the location is the call's own
|
|
140
|
+
* and `text` is absent: the call is the nearest place that governs it, and
|
|
141
|
+
* nothing claims a property that is not there.
|
|
142
|
+
*/
|
|
143
|
+
export interface ArgumentLocation extends SourceLocation {
|
|
144
|
+
/** The factory parameter path, as in `parameters` (`replicas`, `iam.path`). */
|
|
145
|
+
parameter: string;
|
|
146
|
+
/** The innermost property as written at the call, when one is. */
|
|
147
|
+
text?: string;
|
|
148
|
+
}
|
|
95
149
|
|
|
96
150
|
/** True when `prefix` addresses `path` or an ancestor of it, on a segment boundary. */
|
|
97
151
|
function isPathPrefix(prefix: string, path: string): boolean {
|
|
@@ -114,6 +168,7 @@ export function setProvenance(entity: object, prov: EntityProvenance): void {
|
|
|
114
168
|
existing.sourceFile ??= prov.sourceFile;
|
|
115
169
|
existing.composite ??= prov.composite;
|
|
116
170
|
existing.compositeInstance ??= prov.compositeInstance;
|
|
171
|
+
existing.compositeCall ??= prov.compositeCall;
|
|
117
172
|
if (prov.paths) {
|
|
118
173
|
existing.paths ??= {};
|
|
119
174
|
for (const [path, origin] of Object.entries(prov.paths)) {
|
|
@@ -225,6 +280,8 @@ export function describePathOrigin(origin: PathOrigin): string {
|
|
|
225
280
|
return `composite ${origin.composite} parameter ${origin.parameters.join(", ")}`;
|
|
226
281
|
case "composite-literal":
|
|
227
282
|
return `composite ${origin.composite} literal`;
|
|
283
|
+
case "host":
|
|
284
|
+
return origin.reason;
|
|
228
285
|
}
|
|
229
286
|
}
|
|
230
287
|
|
|
@@ -118,6 +118,16 @@ describe("walkValue", () => {
|
|
|
118
118
|
});
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
test("an __attrRef envelope naming a property reads the attribute that property's AttrRef carries", () => {
|
|
122
|
+
// A fold writes `db.Endpoint_Address` as the property name; the attribute
|
|
123
|
+
// is `Endpoint.Address`.
|
|
124
|
+
const TestDb = createResource("Test::Db", "test", { Endpoint_Address: "Endpoint.Address" });
|
|
125
|
+
const db = new TestDb({}) as unknown as Declarable;
|
|
126
|
+
const names = new Map<Declarable, string>([[db, "MyDB"]]);
|
|
127
|
+
const value = { __attrRef: { entity: "MyDB", attribute: "Endpoint_Address" } };
|
|
128
|
+
expect(walkValue(value, names, mockVisitor)).toEqual({ __getAtt: ["MyDB", "Endpoint.Address"] });
|
|
129
|
+
});
|
|
130
|
+
|
|
121
131
|
test("resolves standalone __attrRef envelope in plain object", () => {
|
|
122
132
|
const names = new Map<Declarable, string>();
|
|
123
133
|
const value = { __attrRef: { entity: "MyBucket", attribute: "DomainName" } };
|
package/src/serializer-walker.ts
CHANGED
|
@@ -21,6 +21,22 @@ export interface SerializerVisitor {
|
|
|
21
21
|
propertyDeclarable(entity: Declarable, walk: (v: unknown) => unknown): unknown;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The attribute an `{__attrRef}` envelope names. A fold writes the envelope
|
|
26
|
+
* with the property it read (`db.Endpoint_Address`), and the property's own
|
|
27
|
+
* AttrRef carries the attribute (`Endpoint.Address`); they differ when the
|
|
28
|
+
* attribute has a dot. The envelope's name is kept when the entity or
|
|
29
|
+
* property is not found.
|
|
30
|
+
*/
|
|
31
|
+
function envelopeAttribute(entityName: string, property: string, entityNames: Map<Declarable, string>): string {
|
|
32
|
+
for (const [entity, name] of entityNames) {
|
|
33
|
+
if (name !== entityName) continue;
|
|
34
|
+
const held = (entity as unknown as Record<string, unknown>)[property];
|
|
35
|
+
return isAttrRefLike(held) ? held.attribute : property;
|
|
36
|
+
}
|
|
37
|
+
return property;
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
/**
|
|
25
41
|
* Recursively walk a value, converting AttrRefs, Intrinsics, Declarables,
|
|
26
42
|
* arrays, and objects using the provided visitor.
|
|
@@ -100,7 +116,7 @@ export function walkValue(
|
|
|
100
116
|
// Handle serialized AttrRef envelopes (produced by AttrRef.toJSON() inside intrinsics)
|
|
101
117
|
if (typeof value === "object" && "__attrRef" in value) {
|
|
102
118
|
const ref = (value as { __attrRef: { entity: string; attribute: string } }).__attrRef;
|
|
103
|
-
return visitor.attrRef(ref.entity, ref.attribute);
|
|
119
|
+
return visitor.attrRef(ref.entity, envelopeAttribute(ref.entity, ref.attribute, entityNames));
|
|
104
120
|
}
|
|
105
121
|
|
|
106
122
|
// Handle objects
|
package/src/stack-output.ts
CHANGED
|
@@ -37,6 +37,10 @@ export interface StackOutput extends Declarable {
|
|
|
37
37
|
* `Output.Condition`) — a condition name, or the condition Declarable
|
|
38
38
|
* itself, resolved to its logical name at serialization (#2068/#2069). */
|
|
39
39
|
readonly condition?: string | Declarable;
|
|
40
|
+
/** The output's key in the template, when it is not the export name. A
|
|
41
|
+
* template may use one key for an output and a parameter or resource; a
|
|
42
|
+
* module cannot export both under that name (#3604). */
|
|
43
|
+
readonly name?: string;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
/** What a wrapping intrinsic's lexicon gets derived from: either a nested
|
|
@@ -112,7 +116,7 @@ export function isStackOutput(value: unknown): value is StackOutput {
|
|
|
112
116
|
*/
|
|
113
117
|
export function stackOutput(
|
|
114
118
|
ref: AttrRef | Intrinsic | string,
|
|
115
|
-
options?: { description?: string; exportName?: string; lexicon?: string; condition?: string | Declarable },
|
|
119
|
+
options?: { description?: string; exportName?: string; lexicon?: string; condition?: string | Declarable; name?: string },
|
|
116
120
|
): StackOutput {
|
|
117
121
|
// Duck-type, not `instanceof` (chant #1137): AttrRef also implements
|
|
118
122
|
// Intrinsic (a global-symbol marker), so `isIntrinsic(ref)` alone already
|
|
@@ -152,6 +156,7 @@ export function stackOutput(
|
|
|
152
156
|
description: options?.description,
|
|
153
157
|
exportName: options?.exportName,
|
|
154
158
|
condition: options?.condition,
|
|
159
|
+
...(options?.name !== undefined ? { name: options.name } : {}),
|
|
155
160
|
};
|
|
156
161
|
|
|
157
162
|
return output;
|
package/src/yaml.test.ts
CHANGED
|
@@ -26,6 +26,12 @@ describe("emitYAML", () => {
|
|
|
26
26
|
expect(emitYAML([undefined, 1], 0)).toBe("\n- null\n- 1");
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
test("an empty mapping in a list is an item, not nothing", () => {
|
|
30
|
+
expect(emitYAML([{}], 0)).toBe("\n- {}");
|
|
31
|
+
expect(emitYAML({ egress: [{}] }, 0)).toBe("\negress:\n - {}");
|
|
32
|
+
expect(emitYAML([{ a: undefined }, { b: 1 }], 0)).toBe("\n- {}\n- b: 1");
|
|
33
|
+
});
|
|
34
|
+
|
|
29
35
|
test("booleans", () => {
|
|
30
36
|
expect(emitYAML(true, 0)).toBe("true");
|
|
31
37
|
expect(emitYAML(false, 0)).toBe("false");
|
package/src/yaml.ts
CHANGED
|
@@ -127,6 +127,10 @@ export function emitYAML(value: unknown, indent: number): string {
|
|
|
127
127
|
lines.push(`${prefix} ${key}: ${emitted}`);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
} else {
|
|
131
|
+
// An empty mapping is still an item: `egress: [{}]` allows all
|
|
132
|
+
// egress, and dropping it leaves an empty list.
|
|
133
|
+
lines.push(`${prefix}- {}`);
|
|
130
134
|
}
|
|
131
135
|
} else {
|
|
132
136
|
lines.push(`${prefix}- ${emitYAML(item, indent + 1).trimStart()}`);
|