@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
2
|
import type { LintRule, LintContext, LintDiagnostic } from "../rule";
|
|
3
3
|
import { isInsideCompositeFactory } from "./composite-scope";
|
|
4
|
+
import { checkObjectMember } from "../../fold/subset";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* EVL001: Non-Literal Expression in Resource Constructor
|
|
@@ -9,104 +10,20 @@ import { isInsideCompositeFactory } from "./composite-scope";
|
|
|
9
10
|
* Allowed: literals, identifiers, property access, object/array literals,
|
|
10
11
|
* template expressions, binary/unary/conditional, as/satisfies casts.
|
|
11
12
|
* Blocked: function calls, method calls, and other dynamic expressions.
|
|
13
|
+
*
|
|
14
|
+
* This is a *diagnostic* (chant #1024, epic #1019) — it exists to give a
|
|
15
|
+
* friendly, early, precisely-located error before synthesis, over exactly
|
|
16
|
+
* the same subset definition `fold()` (the actual enforcement layer,
|
|
17
|
+
* {@link "../../fold/fold"}) uses to decide what it can reduce. Both import
|
|
18
|
+
* the single classifier in {@link "../../fold/subset"}
|
|
19
|
+
* ({@link checkObjectMember}) so a construct flagged here is exactly a
|
|
20
|
+
* construct `fold()` rejects, and vice versa — see that module's doc
|
|
21
|
+
* comment for the few environment-dependent exceptions (identifier
|
|
22
|
+
* resolution, intrinsic tag registration, spread-source runtime type,
|
|
23
|
+
* `&&`/`||`/`??`/`? :` short-circuit laziness) that can't be unified
|
|
24
|
+
* through shape alone.
|
|
12
25
|
*/
|
|
13
26
|
|
|
14
|
-
function isStaticallyEvaluable(node: ts.Node): boolean {
|
|
15
|
-
// Literals
|
|
16
|
-
if (
|
|
17
|
-
ts.isStringLiteral(node) ||
|
|
18
|
-
ts.isNumericLiteral(node) ||
|
|
19
|
-
ts.isNoSubstitutionTemplateLiteral(node) ||
|
|
20
|
-
node.kind === ts.SyntaxKind.TrueKeyword ||
|
|
21
|
-
node.kind === ts.SyntaxKind.FalseKeyword ||
|
|
22
|
-
node.kind === ts.SyntaxKind.NullKeyword ||
|
|
23
|
-
node.kind === ts.SyntaxKind.UndefinedKeyword
|
|
24
|
-
) {
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Identifiers (variable references)
|
|
29
|
-
if (ts.isIdentifier(node)) return true;
|
|
30
|
-
|
|
31
|
-
// Property access: obj.prop
|
|
32
|
-
if (ts.isPropertyAccessExpression(node)) {
|
|
33
|
-
return isStaticallyEvaluable(node.expression);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Element access with static key: obj["key"] or obj[0]
|
|
37
|
-
if (ts.isElementAccessExpression(node)) {
|
|
38
|
-
return (
|
|
39
|
-
isStaticallyEvaluable(node.expression) &&
|
|
40
|
-
(ts.isStringLiteral(node.argumentExpression) ||
|
|
41
|
-
ts.isNumericLiteral(node.argumentExpression))
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Object literals — check all property values
|
|
46
|
-
if (ts.isObjectLiteralExpression(node)) {
|
|
47
|
-
return node.properties.every((prop) => {
|
|
48
|
-
if (ts.isPropertyAssignment(prop)) return isStaticallyEvaluable(prop.initializer);
|
|
49
|
-
if (ts.isShorthandPropertyAssignment(prop)) return true;
|
|
50
|
-
if (ts.isSpreadAssignment(prop)) return isStaticallyEvaluable(prop.expression);
|
|
51
|
-
return false;
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Array literals — check all elements
|
|
56
|
-
if (ts.isArrayLiteralExpression(node)) {
|
|
57
|
-
return node.elements.every((el) => isStaticallyEvaluable(el));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Template expressions (tagged or untagged)
|
|
61
|
-
if (ts.isTemplateExpression(node)) return true;
|
|
62
|
-
if (ts.isTaggedTemplateExpression(node)) return true;
|
|
63
|
-
|
|
64
|
-
// Binary expressions: a + b, a ?? b, etc.
|
|
65
|
-
if (ts.isBinaryExpression(node)) {
|
|
66
|
-
return isStaticallyEvaluable(node.left) && isStaticallyEvaluable(node.right);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Prefix unary: !x, -x
|
|
70
|
-
if (ts.isPrefixUnaryExpression(node)) {
|
|
71
|
-
return isStaticallyEvaluable(node.operand);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Conditional: a ? b : c
|
|
75
|
-
if (ts.isConditionalExpression(node)) {
|
|
76
|
-
return (
|
|
77
|
-
isStaticallyEvaluable(node.condition) &&
|
|
78
|
-
isStaticallyEvaluable(node.whenTrue) &&
|
|
79
|
-
isStaticallyEvaluable(node.whenFalse)
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Type assertions: x as T, x satisfies T
|
|
84
|
-
if (ts.isAsExpression(node) || ts.isSatisfiesExpression(node)) {
|
|
85
|
-
return isStaticallyEvaluable(node.expression);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Parenthesized expression
|
|
89
|
-
if (ts.isParenthesizedExpression(node)) {
|
|
90
|
-
return isStaticallyEvaluable(node.expression);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Non-null assertion: x!
|
|
94
|
-
if (ts.isNonNullExpression(node)) {
|
|
95
|
-
return isStaticallyEvaluable(node.expression);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Spread element in arrays
|
|
99
|
-
if (ts.isSpreadElement(node)) {
|
|
100
|
-
return isStaticallyEvaluable(node.expression);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// new Expression (resource constructors) — allowed as property values
|
|
104
|
-
if (ts.isNewExpression(node)) return true;
|
|
105
|
-
|
|
106
|
-
// Everything else (call expressions, etc.) is not statically evaluable
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
27
|
function checkNode(node: ts.Node, context: LintContext, diagnostics: LintDiagnostic[]): void {
|
|
111
28
|
// Skip resource constructors inside Composite() factory callbacks
|
|
112
29
|
if (ts.isNewExpression(node) && !isInsideCompositeFactory(node)) {
|
|
@@ -114,20 +31,19 @@ function checkNode(node: ts.Node, context: LintContext, diagnostics: LintDiagnos
|
|
|
114
31
|
const firstArg = node.arguments[0];
|
|
115
32
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
116
33
|
for (const prop of firstArg.properties) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
34
|
+
const violation = checkObjectMember(prop);
|
|
35
|
+
if (violation) {
|
|
36
|
+
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
37
|
+
violation.node.getStart(context.sourceFile),
|
|
38
|
+
);
|
|
39
|
+
diagnostics.push({
|
|
40
|
+
file: context.filePath,
|
|
41
|
+
line: line + 1,
|
|
42
|
+
column: character + 1,
|
|
43
|
+
ruleId: "EVL001",
|
|
44
|
+
severity: "error",
|
|
45
|
+
message: `Non-literal expression in resource constructor property — value must be statically evaluable`,
|
|
46
|
+
});
|
|
131
47
|
}
|
|
132
48
|
}
|
|
133
49
|
}
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
2
|
import type { LintRule, LintContext, LintDiagnostic } from "../rule";
|
|
3
|
+
import { isLiteralElementKey } from "../../fold/subset";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* EVL003: Dynamic Property Access
|
|
6
7
|
*
|
|
7
8
|
* Computed property access (obj[key]) must use a string or numeric literal key.
|
|
8
9
|
* Dynamic keys (variables, expressions) are not statically evaluable.
|
|
10
|
+
*
|
|
11
|
+
* Shares its key-shape predicate with `fold()`'s `elementKey()`
|
|
12
|
+
* ({@link "../../fold/fold"}) via {@link "../../fold/subset"} (#1024) — a
|
|
13
|
+
* dynamic element-access key is exactly the construct `fold()` rejects with
|
|
14
|
+
* `FoldError.ruleId === "EVL003"`, and the diagnostic is located at the key
|
|
15
|
+
* itself (not the whole `obj[key]` access), matching where `fold()`'s
|
|
16
|
+
* `elementKey()` throws — so a rejection and its EVL003 diagnostic cite the
|
|
17
|
+
* same position, not just the same rule id.
|
|
9
18
|
*/
|
|
10
19
|
|
|
11
20
|
function checkNode(node: ts.Node, context: LintContext, diagnostics: LintDiagnostic[]): void {
|
|
12
21
|
if (ts.isElementAccessExpression(node)) {
|
|
13
22
|
const arg = node.argumentExpression;
|
|
14
|
-
if (!
|
|
23
|
+
if (!isLiteralElementKey(arg)) {
|
|
15
24
|
const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
|
|
16
|
-
|
|
25
|
+
arg.getStart(context.sourceFile),
|
|
17
26
|
);
|
|
18
27
|
diagnostics.push({
|
|
19
28
|
file: context.filePath,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { params, setBuildParams } from "./params";
|
|
3
|
+
|
|
4
|
+
describe("params — the shared build-time-parameters object", () => {
|
|
5
|
+
test("starts empty", () => {
|
|
6
|
+
setBuildParams({});
|
|
7
|
+
expect(params).toEqual({});
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("setBuildParams mutates the SAME object in place, never rebinds it", () => {
|
|
11
|
+
const before = params;
|
|
12
|
+
setBuildParams({ tier: "production", replicas: 3, enabled: true });
|
|
13
|
+
expect(params).toBe(before);
|
|
14
|
+
expect(params).toEqual({ tier: "production", replicas: 3, enabled: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("a second call fully replaces the previous values (no stale leftover keys)", () => {
|
|
18
|
+
setBuildParams({ a: "1", b: "2" });
|
|
19
|
+
setBuildParams({ c: "3" });
|
|
20
|
+
expect(params).toEqual({ c: "3" });
|
|
21
|
+
});
|
|
22
|
+
});
|
package/src/params.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time parameters — the runtime binding source references (chant #1064,
|
|
3
|
+
* follow-up to epic #1019's fold work).
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately NOT `Parameter` (`lexicons/aws/src/parameter.ts`), which
|
|
6
|
+
* is a deploy-time CloudFormation parameter: it emits a `Parameters:` block and
|
|
7
|
+
* resolves when the STACK deploys. A build-time parameter resolves before the
|
|
8
|
+
* template is even synthesized — its value can change WHICH resources are
|
|
9
|
+
* produced at all (loomster's `LOOM_TIER` selecting `light` vs `production` vs
|
|
10
|
+
* `production-ha`), which a deploy-time `Parameter` structurally cannot do.
|
|
11
|
+
*
|
|
12
|
+
* Project source never reads `process.env` directly to vary a build — that
|
|
13
|
+
* reads ambient state at module-evaluation time, which `fold()` correctly
|
|
14
|
+
* cannot reduce to a value (see ../fold/fold.ts). Instead it imports this
|
|
15
|
+
* module's `params` object:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { params } from "@intentius/chant/params";
|
|
19
|
+
* export const tier = params.tier as Tier;
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* `params` is declared, validated (type + optional `enum`), and resolved by
|
|
23
|
+
* the CLI BEFORE discovery ever touches a project file (../build-params.ts's
|
|
24
|
+
* `resolveBuildParams`, driven by `chant build --param`/`--params-file`/a
|
|
25
|
+
* declared `env` mapping/`chant.config.ts`'s `buildParams` defaults). Two
|
|
26
|
+
* consumers read the resolved values, and both see the identical object:
|
|
27
|
+
*
|
|
28
|
+
* - The FOLD path (../discovery/fold-import.ts's `buildExternals`) recognizes
|
|
29
|
+
* a named `params` import resolving to *this* module and substitutes the
|
|
30
|
+
* already-resolved values directly, with zero import performed — so
|
|
31
|
+
* `params.tier` folds to a LITERAL, not a symbolic node. This is the entire
|
|
32
|
+
* point: the value is known at build invocation, so there is nothing left
|
|
33
|
+
* to defer.
|
|
34
|
+
* - The RUN path (a run-fallback file that imports this module for real)
|
|
35
|
+
* gets the exact same values, because `setBuildParams` below is called
|
|
36
|
+
* once, in-process, before ANY project file is imported or folded —
|
|
37
|
+
* mutating this shared object in place rather than replacing the binding,
|
|
38
|
+
* so a live-imported reference always observes the current build's values.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type { BuildParamValue } from "./build-params";
|
|
42
|
+
|
|
43
|
+
export type { BuildParamValue };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The current build's resolved parameter values, keyed by declared name.
|
|
47
|
+
* Empty until a build populates it via {@link setBuildParams}. Mutated IN
|
|
48
|
+
* PLACE (never reassigned) so a real `import { params }` in a run-fallback
|
|
49
|
+
* file — which captures this exact object reference at import time — always
|
|
50
|
+
* observes whatever the current build resolved, even though the import
|
|
51
|
+
* happens after this module was first loaded.
|
|
52
|
+
*/
|
|
53
|
+
export const params: Record<string, BuildParamValue> = {};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Replace the shared {@link params} object's contents in place. Called once
|
|
57
|
+
* per build (../discovery/index.ts's `discover()`), before any project file
|
|
58
|
+
* is imported or folded, with the CLI/config-resolved values
|
|
59
|
+
* (../build-params.ts's `resolveBuildParams`). Never called from project
|
|
60
|
+
* source — this is chant's own build-invocation plumbing, not a public
|
|
61
|
+
* authoring API.
|
|
62
|
+
*/
|
|
63
|
+
export function setBuildParams(values: Readonly<Record<string, BuildParamValue>>): void {
|
|
64
|
+
for (const key of Object.keys(params)) delete params[key];
|
|
65
|
+
Object.assign(params, values);
|
|
66
|
+
}
|
package/src/provenance.ts
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
* This is entity-level provenance (which file declared it, and which composite
|
|
9
9
|
* expanded it), not a YAML-line source map. It answers "where did this resource
|
|
10
10
|
* come from?", which is the question an agent asks before changing it.
|
|
11
|
+
*
|
|
12
|
+
* {@link BuildParamProvenance} (chant #1064) is the other half: not "where did
|
|
13
|
+
* this ENTITY come from" but "what INPUTS was this whole BUILD invoked with" —
|
|
14
|
+
* the question ambient `process.env` reads used to answer invisibly. See
|
|
15
|
+
* ../build-params.ts for declaration/resolution and ../build.ts's
|
|
16
|
+
* `BuildResult.buildParams` for where a build surfaces it.
|
|
11
17
|
*/
|
|
12
18
|
|
|
13
19
|
const PROVENANCE = Symbol.for("chant.provenance");
|
|
@@ -52,3 +58,19 @@ export function setProvenance(entity: object, prov: EntityProvenance): void {
|
|
|
52
58
|
export function getProvenance(entity: object): EntityProvenance | undefined {
|
|
53
59
|
return (entity as Record<symbol, unknown>)[PROVENANCE] as EntityProvenance | undefined;
|
|
54
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* One resolved build-time parameter (chant #1064, see ../build-params.ts): its
|
|
64
|
+
* final value and which source won it, so a build's parameter inputs are
|
|
65
|
+
* auditable rather than inferred. `source` records precedence, most to least
|
|
66
|
+
* specific: an explicit `--param`/`--params-file` value beats a declared `env`
|
|
67
|
+
* mapping, which beats `chant.config.ts`'s `default`.
|
|
68
|
+
*/
|
|
69
|
+
export interface BuildParamProvenance {
|
|
70
|
+
/** The declared parameter name (a key of `chant.config.ts`'s `buildParams`). */
|
|
71
|
+
name: string;
|
|
72
|
+
/** The resolved, type-coerced value actually bound to `params.<name>` for this build. */
|
|
73
|
+
value: string | number | boolean;
|
|
74
|
+
/** Which input supplied the value. */
|
|
75
|
+
source: "cli" | "params-file" | "env" | "default";
|
|
76
|
+
}
|
|
@@ -55,7 +55,7 @@ describe("adoptFromState", () => {
|
|
|
55
55
|
expect(types).toContain("aws_s3_bucket");
|
|
56
56
|
expect(types).toContain("aws_sqs_queue");
|
|
57
57
|
expect(types).toEqual([...types].sort());
|
|
58
|
-
expect(types).not.toContain("
|
|
58
|
+
expect(types).not.toContain("aws_glue_job"); // not yet mapped
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
test("returns null for a type with no native constructor", () => {
|
|
@@ -27,10 +27,17 @@ describe("AWS carve-out table", () => {
|
|
|
27
27
|
expect(AWS_CARVE_TYPES.length).toBeGreaterThanOrEqual(20);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
test("
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
test("constructors are non-empty and consistent per native type", () => {
|
|
31
|
+
// A constructor may be shared by TF aliases for the same native resource
|
|
32
|
+
// (e.g. aws_lb and aws_alb both → LoadBalancer), but a given native type
|
|
33
|
+
// must always map to the same constructor.
|
|
34
|
+
const byNative = new Map<string, string>();
|
|
35
|
+
for (const t of AWS_CARVE_TYPES) {
|
|
36
|
+
expect(t.ctor.length).toBeGreaterThan(0);
|
|
37
|
+
const seen = byNative.get(t.nativeType);
|
|
38
|
+
if (seen) expect(t.ctor).toBe(seen);
|
|
39
|
+
else byNative.set(t.nativeType, t.ctor);
|
|
40
|
+
}
|
|
34
41
|
});
|
|
35
42
|
});
|
|
36
43
|
|
|
@@ -108,6 +108,66 @@ export const AWS_CARVE_TYPES: AwsCarveType[] = [
|
|
|
108
108
|
fields: { name: "LogGroupName", retention_in_days: "RetentionInDays", kms_key_id: "KmsKeyId" }, tags: true },
|
|
109
109
|
{ tfType: "aws_cloudwatch_metric_alarm", tier: 2, nativeType: "AWS::CloudWatch::Alarm", ctor: "Alarm", identityAttr: "alarm_name",
|
|
110
110
|
fields: { alarm_name: "AlarmName", comparison_operator: "ComparisonOperator", metric_name: "MetricName", namespace: "Namespace", threshold: "Threshold", evaluation_periods: "EvaluationPeriods", period: "Period", statistic: "Statistic", alarm_description: "AlarmDescription" }, tags: true },
|
|
111
|
+
{ tfType: "aws_cloudwatch_event_rule", tier: 2, nativeType: "AWS::Events::Rule", ctor: "EventRule", identityAttr: "name",
|
|
112
|
+
fields: { name: "Name", schedule_expression: "ScheduleExpression", event_pattern: json("EventPattern"), description: "Description", state: "State" } },
|
|
113
|
+
|
|
114
|
+
// ── Compute (EC2 / ECS / ASG) ──
|
|
115
|
+
{ tfType: "aws_instance", tier: 2, nativeType: "AWS::EC2::Instance", ctor: "Instance",
|
|
116
|
+
fields: { ami: "ImageId", instance_type: "InstanceType", subnet_id: "SubnetId", key_name: "KeyName", availability_zone: "AvailabilityZone", iam_instance_profile: "IamInstanceProfile" }, tags: true },
|
|
117
|
+
{ tfType: "aws_launch_template", tier: 2, nativeType: "AWS::EC2::LaunchTemplate", ctor: "LaunchTemplate", identityAttr: "name",
|
|
118
|
+
fields: { name: "LaunchTemplateName" }, tags: true },
|
|
119
|
+
{ tfType: "aws_autoscaling_group", tier: 2, nativeType: "AWS::AutoScaling::AutoScalingGroup", ctor: "AutoScalingGroup", identityAttr: "name",
|
|
120
|
+
fields: { name: "AutoScalingGroupName", min_size: "MinSize", max_size: "MaxSize", desired_capacity: "DesiredCapacity", vpc_zone_identifier: "VPCZoneIdentifier", health_check_type: "HealthCheckType" } },
|
|
121
|
+
{ tfType: "aws_ecs_cluster", tier: 1, nativeType: "AWS::ECS::Cluster", ctor: "EcsCluster", identityAttr: "name",
|
|
122
|
+
fields: { name: "ClusterName" }, tags: true },
|
|
123
|
+
{ tfType: "aws_ecs_task_definition", tier: 3, nativeType: "AWS::ECS::TaskDefinition", ctor: "TaskDefinition",
|
|
124
|
+
fields: { family: "Family", cpu: "Cpu", memory: "Memory", network_mode: "NetworkMode", execution_role_arn: "ExecutionRoleArn", task_role_arn: "TaskRoleArn" }, tags: true },
|
|
125
|
+
{ tfType: "aws_ebs_volume", tier: 1, nativeType: "AWS::EC2::Volume", ctor: "EC2Volume",
|
|
126
|
+
fields: { availability_zone: "AvailabilityZone", size: "Size", type: "VolumeType", encrypted: "Encrypted", iops: "Iops" }, tags: true },
|
|
127
|
+
|
|
128
|
+
// ── Load balancing (ELBv2; aws_alb is a legacy alias) ──
|
|
129
|
+
{ tfType: "aws_lb", tier: 2, nativeType: "AWS::ElasticLoadBalancingV2::LoadBalancer", ctor: "LoadBalancer", identityAttr: "name",
|
|
130
|
+
fields: { name: "Name", load_balancer_type: "Type", internal: { prop: "Scheme", transform: (v) => (v === true ? "internal" : v === false ? "internet-facing" : v) } }, tags: true },
|
|
131
|
+
{ tfType: "aws_alb", tier: 2, nativeType: "AWS::ElasticLoadBalancingV2::LoadBalancer", ctor: "LoadBalancer", identityAttr: "name",
|
|
132
|
+
fields: { name: "Name", load_balancer_type: "Type", internal: { prop: "Scheme", transform: (v) => (v === true ? "internal" : v === false ? "internet-facing" : v) } }, tags: true },
|
|
133
|
+
{ tfType: "aws_lb_target_group", tier: 2, nativeType: "AWS::ElasticLoadBalancingV2::TargetGroup", ctor: "TargetGroup", identityAttr: "name",
|
|
134
|
+
fields: { name: "Name", port: "Port", protocol: "Protocol", vpc_id: "VpcId", target_type: "TargetType" }, tags: true },
|
|
135
|
+
{ tfType: "aws_lb_listener", tier: 2, nativeType: "AWS::ElasticLoadBalancingV2::Listener", ctor: "Listener",
|
|
136
|
+
fields: { port: "Port", protocol: "Protocol", load_balancer_arn: "LoadBalancerArn" } },
|
|
137
|
+
|
|
138
|
+
// ── Databases & cache ──
|
|
139
|
+
{ tfType: "aws_db_instance", tier: 2, nativeType: "AWS::RDS::DBInstance", ctor: "DbInstance", identityAttr: "identifier",
|
|
140
|
+
fields: { identifier: "DBInstanceIdentifier", engine: "Engine", engine_version: "EngineVersion", instance_class: "DBInstanceClass", allocated_storage: "AllocatedStorage", db_name: "DBName", multi_az: "MultiAZ", storage_encrypted: "StorageEncrypted" }, tags: true },
|
|
141
|
+
{ tfType: "aws_db_subnet_group", tier: 1, nativeType: "AWS::RDS::DBSubnetGroup", ctor: "RDSDBSubnetGroup", identityAttr: "name",
|
|
142
|
+
fields: { name: "DBSubnetGroupName", description: "DBSubnetGroupDescription", subnet_ids: "SubnetIds" }, tags: true },
|
|
143
|
+
{ tfType: "aws_elasticache_cluster", tier: 2, nativeType: "AWS::ElastiCache::CacheCluster", ctor: "CacheCluster",
|
|
144
|
+
fields: { engine: "Engine", node_type: "CacheNodeType", num_cache_nodes: "NumCacheNodes", engine_version: "EngineVersion" }, tags: true },
|
|
145
|
+
{ tfType: "aws_elasticache_replication_group", tier: 2, nativeType: "AWS::ElastiCache::ReplicationGroup", ctor: "ReplicationGroup", identityAttr: "replication_group_id",
|
|
146
|
+
fields: { replication_group_id: "ReplicationGroupId", description: "ReplicationGroupDescription", node_type: "CacheNodeType", engine: "Engine" }, tags: true },
|
|
147
|
+
|
|
148
|
+
// ── DNS, CDN & API ──
|
|
149
|
+
{ tfType: "aws_route53_record", tier: 2, nativeType: "AWS::Route53::RecordSet", ctor: "RecordSet",
|
|
150
|
+
fields: { name: "Name", type: "Type", ttl: "TTL", zone_id: "HostedZoneId", records: "ResourceRecords" } },
|
|
151
|
+
{ tfType: "aws_api_gateway_rest_api", tier: 2, nativeType: "AWS::ApiGateway::RestApi", ctor: "RestApi", identityAttr: "name",
|
|
152
|
+
fields: { name: "Name", description: "Description" }, tags: true },
|
|
153
|
+
{ tfType: "aws_apigatewayv2_api", tier: 2, nativeType: "AWS::ApiGatewayV2::Api", ctor: "HttpApi", identityAttr: "name",
|
|
154
|
+
fields: { name: "Name", protocol_type: "ProtocolType", description: "Description" }, tags: true },
|
|
155
|
+
|
|
156
|
+
// ── IAM (users/groups), certs & keys ──
|
|
157
|
+
{ tfType: "aws_iam_user", tier: 1, nativeType: "AWS::IAM::User", ctor: "IAMUser", identityAttr: "name",
|
|
158
|
+
fields: { name: "UserName", path: "Path" }, tags: true },
|
|
159
|
+
{ tfType: "aws_iam_group", tier: 1, nativeType: "AWS::IAM::Group", ctor: "IAMGroup", identityAttr: "name",
|
|
160
|
+
fields: { name: "GroupName", path: "Path" } },
|
|
161
|
+
{ tfType: "aws_acm_certificate", tier: 2, nativeType: "AWS::CertificateManager::Certificate", ctor: "AcmCertificate", identityAttr: "domain_name",
|
|
162
|
+
fields: { domain_name: "DomainName", validation_method: "ValidationMethod", subject_alternative_names: "SubjectAlternativeNames" }, tags: true },
|
|
163
|
+
{ tfType: "aws_kms_alias", tier: 1, nativeType: "AWS::KMS::Alias", ctor: "KMSAlias", identityAttr: "name",
|
|
164
|
+
fields: { name: "AliasName", target_key_id: "TargetKeyId" } },
|
|
165
|
+
|
|
166
|
+
// ── Messaging & step functions ──
|
|
167
|
+
{ tfType: "aws_sns_topic_subscription", tier: 2, nativeType: "AWS::SNS::Subscription", ctor: "Subscription",
|
|
168
|
+
fields: { protocol: "Protocol", endpoint: "Endpoint", topic_arn: "TopicArn" } },
|
|
169
|
+
{ tfType: "aws_sfn_state_machine", tier: 3, nativeType: "AWS::StepFunctions::StateMachine", ctor: "StateMachine", identityAttr: "name",
|
|
170
|
+
fields: { name: "StateMachineName", role_arn: "RoleArn", type: "StateMachineType" }, tags: true },
|
|
111
171
|
];
|
|
112
172
|
|
|
113
173
|
const BY_TYPE = new Map(AWS_CARVE_TYPES.map((t) => [t.tfType, t]));
|