@intentius/chant 0.19.1 → 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.
Files changed (152) hide show
  1. package/dist/build-params.d.ts +108 -0
  2. package/dist/build-params.d.ts.map +1 -0
  3. package/dist/build.d.ts +79 -1
  4. package/dist/build.d.ts.map +1 -1
  5. package/dist/cli/commands/build.d.ts +31 -0
  6. package/dist/cli/commands/build.d.ts.map +1 -1
  7. package/dist/cli/commands/check-lexicon-examples.d.ts +41 -0
  8. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -0
  9. package/dist/cli/commands/check-lexicon-intrinsics.d.ts +54 -0
  10. package/dist/cli/commands/check-lexicon-intrinsics.d.ts.map +1 -0
  11. package/dist/cli/commands/check-lexicon.d.ts +1 -1
  12. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  13. package/dist/cli/commands/lint.d.ts +27 -0
  14. package/dist/cli/commands/lint.d.ts.map +1 -1
  15. package/dist/cli/handlers/build.d.ts.map +1 -1
  16. package/dist/cli/handlers/components.d.ts.map +1 -1
  17. package/dist/cli/handlers/lint.d.ts.map +1 -1
  18. package/dist/cli/handlers/run-client.d.ts +1 -1
  19. package/dist/cli/handlers/run-client.d.ts.map +1 -1
  20. package/dist/cli/handlers/run.d.ts.map +1 -1
  21. package/dist/cli/main.d.ts.map +1 -1
  22. package/dist/cli/plugins.d.ts +16 -0
  23. package/dist/cli/plugins.d.ts.map +1 -1
  24. package/dist/cli/registry.d.ts +10 -0
  25. package/dist/cli/registry.d.ts.map +1 -1
  26. package/dist/codegen/docs-sections.d.ts.map +1 -1
  27. package/dist/components/cli-support.d.ts +12 -5
  28. package/dist/components/cli-support.d.ts.map +1 -1
  29. package/dist/components/discover.d.ts +62 -7
  30. package/dist/components/discover.d.ts.map +1 -1
  31. package/dist/components/sandbox/driver.d.ts +12 -0
  32. package/dist/components/sandbox/driver.d.ts.map +1 -0
  33. package/dist/components/sandbox/run.d.ts +42 -0
  34. package/dist/components/sandbox/run.d.ts.map +1 -0
  35. package/dist/composite.d.ts +5 -0
  36. package/dist/composite.d.ts.map +1 -1
  37. package/dist/config.d.ts +71 -0
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/discovery/collect.d.ts.map +1 -1
  40. package/dist/discovery/entity-wire-codec.d.ts +166 -0
  41. package/dist/discovery/entity-wire-codec.d.ts.map +1 -0
  42. package/dist/discovery/entity-wire.d.ts +50 -0
  43. package/dist/discovery/entity-wire.d.ts.map +1 -0
  44. package/dist/discovery/fold-import.d.ts +210 -0
  45. package/dist/discovery/fold-import.d.ts.map +1 -0
  46. package/dist/discovery/index.d.ts +74 -1
  47. package/dist/discovery/index.d.ts.map +1 -1
  48. package/dist/discovery/sandbox/bundle.d.ts +18 -0
  49. package/dist/discovery/sandbox/bundle.d.ts.map +1 -0
  50. package/dist/discovery/sandbox/child-errors.d.ts +15 -0
  51. package/dist/discovery/sandbox/child-errors.d.ts.map +1 -0
  52. package/dist/discovery/sandbox/driver.d.ts +13 -0
  53. package/dist/discovery/sandbox/driver.d.ts.map +1 -0
  54. package/dist/discovery/sandbox/run.d.ts +69 -0
  55. package/dist/discovery/sandbox/run.d.ts.map +1 -0
  56. package/dist/errors.d.ts +9 -1
  57. package/dist/errors.d.ts.map +1 -1
  58. package/dist/fold/fold.d.ts +226 -0
  59. package/dist/fold/fold.d.ts.map +1 -0
  60. package/dist/fold/subset.d.ts +102 -0
  61. package/dist/fold/subset.d.ts.map +1 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/lexicon-output.d.ts +7 -2
  65. package/dist/lexicon-output.d.ts.map +1 -1
  66. package/dist/lexicon-schema.d.ts +2 -2
  67. package/dist/lexicon-schema.d.ts.map +1 -1
  68. package/dist/lexicon.d.ts +81 -1
  69. package/dist/lexicon.d.ts.map +1 -1
  70. package/dist/lifecycle/release-ledger.d.ts +11 -0
  71. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  72. package/dist/lint/component-checks.d.ts +7 -1
  73. package/dist/lint/component-checks.d.ts.map +1 -1
  74. package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
  75. package/dist/lint/rules/evl003-dynamic-property-access.d.ts.map +1 -1
  76. package/dist/params.d.ts +60 -0
  77. package/dist/params.d.ts.map +1 -0
  78. package/dist/provenance.d.ts +21 -0
  79. package/dist/provenance.d.ts.map +1 -1
  80. package/package.json +2 -1
  81. package/src/build-params.test.ts +144 -0
  82. package/src/build-params.ts +207 -0
  83. package/src/build.test.ts +38 -0
  84. package/src/build.ts +144 -7
  85. package/src/cli/commands/build.test.ts +220 -2
  86. package/src/cli/commands/build.ts +111 -3
  87. package/src/cli/commands/check-lexicon-examples.test.ts +92 -0
  88. package/src/cli/commands/check-lexicon-examples.ts +103 -0
  89. package/src/cli/commands/check-lexicon-intrinsics.test.ts +145 -0
  90. package/src/cli/commands/check-lexicon-intrinsics.ts +312 -0
  91. package/src/cli/commands/check-lexicon.test.ts +34 -0
  92. package/src/cli/commands/check-lexicon.ts +119 -1
  93. package/src/cli/commands/lint.ts +31 -3
  94. package/src/cli/commands/onboard.ts +1 -1
  95. package/src/cli/component-security-boundary.test.ts +170 -0
  96. package/src/cli/handlers/build.ts +24 -3
  97. package/src/cli/handlers/components.ts +9 -2
  98. package/src/cli/handlers/dev.ts +1 -1
  99. package/src/cli/handlers/graph.ts +7 -5
  100. package/src/cli/handlers/lifecycle.ts +2 -2
  101. package/src/cli/handlers/lint.ts +2 -0
  102. package/src/cli/handlers/misc.ts +2 -2
  103. package/src/cli/handlers/run-client.ts +1 -1
  104. package/src/cli/handlers/run.ts +20 -5
  105. package/src/cli/main.test.ts +22 -0
  106. package/src/cli/main.ts +39 -0
  107. package/src/cli/plugins.ts +20 -4
  108. package/src/cli/registry.ts +10 -0
  109. package/src/cli/security-boundary.test.ts +135 -0
  110. package/src/codegen/docs-sections.test.ts +61 -0
  111. package/src/codegen/docs-sections.ts +7 -3
  112. package/src/components/cli-support.ts +22 -10
  113. package/src/components/discover.ts +127 -25
  114. package/src/components/sandbox/driver.ts +114 -0
  115. package/src/components/sandbox/run.test.ts +185 -0
  116. package/src/components/sandbox/run.ts +177 -0
  117. package/src/composite.test.ts +21 -0
  118. package/src/composite.ts +20 -1
  119. package/src/config.ts +81 -0
  120. package/src/discovery/collect.ts +17 -3
  121. package/src/discovery/entity-wire-codec.ts +485 -0
  122. package/src/discovery/entity-wire.test.ts +240 -0
  123. package/src/discovery/entity-wire.ts +67 -0
  124. package/src/discovery/fold-import.test.ts +1026 -0
  125. package/src/discovery/fold-import.ts +1805 -0
  126. package/src/discovery/index.test.ts +191 -1
  127. package/src/discovery/index.ts +233 -1
  128. package/src/discovery/sandbox/bundle.ts +218 -0
  129. package/src/discovery/sandbox/child-errors.ts +65 -0
  130. package/src/discovery/sandbox/driver.ts +147 -0
  131. package/src/discovery/sandbox/run.test.ts +179 -0
  132. package/src/discovery/sandbox/run.ts +196 -0
  133. package/src/errors.ts +9 -1
  134. package/src/fold/fold.test.ts +535 -0
  135. package/src/fold/fold.ts +648 -0
  136. package/src/fold/subset-doc-parity.test.ts +183 -0
  137. package/src/fold/subset.test.ts +241 -0
  138. package/src/fold/subset.ts +302 -0
  139. package/src/index.ts +2 -0
  140. package/src/lexicon-output.ts +7 -2
  141. package/src/lexicon-schema.test.ts +14 -0
  142. package/src/lexicon-schema.ts +3 -1
  143. package/src/lexicon.ts +82 -1
  144. package/src/lifecycle/git.test.ts +10 -5
  145. package/src/lifecycle/release-ledger.test.ts +28 -0
  146. package/src/lifecycle/release-ledger.ts +11 -0
  147. package/src/lint/component-checks.ts +8 -1
  148. package/src/lint/rules/evl001-non-literal-expression.ts +26 -110
  149. package/src/lint/rules/evl003-dynamic-property-access.ts +11 -2
  150. package/src/params.test.ts +22 -0
  151. package/src/params.ts +66 -0
  152. package/src/provenance.ts +22 -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
- if (ts.isPropertyAssignment(prop)) {
118
- if (!isStaticallyEvaluable(prop.initializer)) {
119
- const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
120
- prop.initializer.getStart(context.sourceFile),
121
- );
122
- diagnostics.push({
123
- file: context.filePath,
124
- line: line + 1,
125
- column: character + 1,
126
- ruleId: "EVL001",
127
- severity: "error",
128
- message: `Non-literal expression in resource constructor property — value must be statically evaluable`,
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 (!ts.isStringLiteral(arg) && !ts.isNumericLiteral(arg)) {
23
+ if (!isLiteralElementKey(arg)) {
15
24
  const { line, character } = context.sourceFile.getLineAndCharacterOfPosition(
16
- node.getStart(context.sourceFile),
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
+ }