@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evl003-dynamic-property-access.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/evl003-dynamic-property-access.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"evl003-dynamic-property-access.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/evl003-dynamic-property-access.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAA+B,MAAM,SAAS,CAAC;AAuCrE,eAAO,MAAM,+BAA+B,EAAE,QAU7C,CAAC"}
|
package/dist/params.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
import type { BuildParamValue } from "./build-params.js";
|
|
41
|
+
export type { BuildParamValue };
|
|
42
|
+
/**
|
|
43
|
+
* The current build's resolved parameter values, keyed by declared name.
|
|
44
|
+
* Empty until a build populates it via {@link setBuildParams}. Mutated IN
|
|
45
|
+
* PLACE (never reassigned) so a real `import { params }` in a run-fallback
|
|
46
|
+
* file — which captures this exact object reference at import time — always
|
|
47
|
+
* observes whatever the current build resolved, even though the import
|
|
48
|
+
* happens after this module was first loaded.
|
|
49
|
+
*/
|
|
50
|
+
export declare const params: Record<string, BuildParamValue>;
|
|
51
|
+
/**
|
|
52
|
+
* Replace the shared {@link params} object's contents in place. Called once
|
|
53
|
+
* per build (../discovery/index.ts's `discover()`), before any project file
|
|
54
|
+
* is imported or folded, with the CLI/config-resolved values
|
|
55
|
+
* (../build-params.ts's `resolveBuildParams`). Never called from project
|
|
56
|
+
* source — this is chant's own build-invocation plumbing, not a public
|
|
57
|
+
* authoring API.
|
|
58
|
+
*/
|
|
59
|
+
export declare function setBuildParams(values: Readonly<Record<string, BuildParamValue>>): void;
|
|
60
|
+
//# sourceMappingURL=params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAM,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,GAAG,IAAI,CAGtF"}
|
package/dist/provenance.d.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
|
export interface EntityProvenance {
|
|
13
19
|
/** Absolute path of the source file that declared (or exported) the entity. */
|
|
@@ -30,4 +36,19 @@ export interface EntityProvenance {
|
|
|
30
36
|
export declare function setProvenance(entity: object, prov: EntityProvenance): void;
|
|
31
37
|
/** Read an entity's build provenance, if any was stamped. */
|
|
32
38
|
export declare function getProvenance(entity: object): EntityProvenance | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* One resolved build-time parameter (chant #1064, see ../build-params.ts): its
|
|
41
|
+
* final value and which source won it, so a build's parameter inputs are
|
|
42
|
+
* auditable rather than inferred. `source` records precedence, most to least
|
|
43
|
+
* specific: an explicit `--param`/`--params-file` value beats a declared `env`
|
|
44
|
+
* mapping, which beats `chant.config.ts`'s `default`.
|
|
45
|
+
*/
|
|
46
|
+
export interface BuildParamProvenance {
|
|
47
|
+
/** The declared parameter name (a key of `chant.config.ts`'s `buildParams`). */
|
|
48
|
+
name: string;
|
|
49
|
+
/** The resolved, type-coerced value actually bound to `params.<name>` for this build. */
|
|
50
|
+
value: string | number | boolean;
|
|
51
|
+
/** Which input supplied the value. */
|
|
52
|
+
source: "cli" | "params-file" | "env" | "default";
|
|
53
|
+
}
|
|
33
54
|
//# sourceMappingURL=provenance.d.ts.map
|
package/dist/provenance.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../src/provenance.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../src/provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,MAAM,WAAW,gBAAgB;IAC/B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAe1E;AAED,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE1E;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,sCAAsC;IACtC,MAAM,EAAE,KAAK,GAAG,aAAa,GAAG,KAAK,GAAG,SAAS,CAAC;CACnD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws-resources.d.ts","sourceRoot":"","sources":["../../src/terraform/aws-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,+DAA+D;AAC/D,KAAK,SAAS,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,kEAAkE;IAClE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,kBAAkB,iCAAiC,CAAC;AAajE,eAAO,MAAM,eAAe,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"aws-resources.d.ts","sourceRoot":"","sources":["../../src/terraform/aws-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,+DAA+D;AAC/D,KAAK,SAAS,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClC,kEAAkE;IAClE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,kBAAkB,iCAAiC,CAAC;AAajE,eAAO,MAAM,eAAe,EAAE,YAAY,EA0HzC,CAAC;AAIF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAErE;AASD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAoB1D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@dagrejs/dagre": "^3.0.0",
|
|
79
|
+
"esbuild": "^0.27.0",
|
|
79
80
|
"fflate": "^0.8.2",
|
|
80
81
|
"picomatch": "^4.0.3",
|
|
81
82
|
"tsx": "^4.0.0",
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { resolveBuildParams, buildParamValues, type BuildParamsConfig } from "./build-params";
|
|
3
|
+
|
|
4
|
+
const TIER_DEF: BuildParamsConfig = {
|
|
5
|
+
tier: { type: "string", default: "light", enum: ["light", "production", "production-ha"] },
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
describe("resolveBuildParams — precedence", () => {
|
|
9
|
+
test("cli beats params-file beats env beats default", () => {
|
|
10
|
+
const defs: BuildParamsConfig = { name: { type: "string", default: "d", env: "NAME_ENV" } };
|
|
11
|
+
|
|
12
|
+
const cliOnly = resolveBuildParams(defs, { cli: { name: "from-cli" } });
|
|
13
|
+
expect(cliOnly.errors).toEqual([]);
|
|
14
|
+
expect(cliOnly.provenance).toEqual([{ name: "name", value: "from-cli", source: "cli" }]);
|
|
15
|
+
|
|
16
|
+
const cliOverFile = resolveBuildParams(defs, { cli: { name: "from-cli" }, fromFile: { name: "from-file" } });
|
|
17
|
+
expect(cliOverFile.provenance).toEqual([{ name: "name", value: "from-cli", source: "cli" }]);
|
|
18
|
+
|
|
19
|
+
const fileOverEnv = resolveBuildParams(defs, { fromFile: { name: "from-file" }, env: { NAME_ENV: "from-env" } });
|
|
20
|
+
expect(fileOverEnv.provenance).toEqual([{ name: "name", value: "from-file", source: "params-file" }]);
|
|
21
|
+
|
|
22
|
+
const envOverDefault = resolveBuildParams(defs, { env: { NAME_ENV: "from-env" } });
|
|
23
|
+
expect(envOverDefault.provenance).toEqual([{ name: "name", value: "from-env", source: "env" }]);
|
|
24
|
+
|
|
25
|
+
const defaultOnly = resolveBuildParams(defs, {});
|
|
26
|
+
expect(defaultOnly.provenance).toEqual([{ name: "name", value: "d", source: "default" }]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("a parameter with no env mapping ignores an unrelated env var of the same name", () => {
|
|
30
|
+
const defs: BuildParamsConfig = { name: { type: "string", default: "d" } };
|
|
31
|
+
const result = resolveBuildParams(defs, { env: { name: "should-not-be-used" } });
|
|
32
|
+
expect(result.provenance).toEqual([{ name: "name", value: "d", source: "default" }]);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("resolveBuildParams — validation", () => {
|
|
37
|
+
test("missing value with no default is a build error naming the parameter", () => {
|
|
38
|
+
const defs: BuildParamsConfig = { tier: { type: "string" } };
|
|
39
|
+
const result = resolveBuildParams(defs, {});
|
|
40
|
+
expect(result.provenance).toEqual([]);
|
|
41
|
+
expect(result.errors).toHaveLength(1);
|
|
42
|
+
expect(result.errors[0]).toContain('"tier"');
|
|
43
|
+
expect(result.errors[0]).toContain("--param tier=");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("enum violation names the parameter and the allowed values", () => {
|
|
47
|
+
const result = resolveBuildParams(TIER_DEF, { cli: { tier: "bogus" } });
|
|
48
|
+
expect(result.provenance).toEqual([]);
|
|
49
|
+
expect(result.errors).toHaveLength(1);
|
|
50
|
+
expect(result.errors[0]).toContain('"tier"');
|
|
51
|
+
expect(result.errors[0]).toContain("light");
|
|
52
|
+
expect(result.errors[0]).toContain("bogus");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("enum success resolves normally", () => {
|
|
56
|
+
const result = resolveBuildParams(TIER_DEF, { cli: { tier: "production" } });
|
|
57
|
+
expect(result.errors).toEqual([]);
|
|
58
|
+
expect(result.provenance).toEqual([{ name: "tier", value: "production", source: "cli" }]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("unknown --param key (not declared) is a build error", () => {
|
|
62
|
+
const result = resolveBuildParams(TIER_DEF, { cli: { bogusName: "x" } });
|
|
63
|
+
expect(result.errors.some((e) => e.includes('"bogusName"') && e.includes("--param"))).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("unknown --params-file key (not declared) is a build error", () => {
|
|
67
|
+
const result = resolveBuildParams(TIER_DEF, { fromFile: { bogusName: "x" } });
|
|
68
|
+
expect(result.errors.some((e) => e.includes('"bogusName"') && e.includes("--params-file"))).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("number type coercion, including a non-numeric failure", () => {
|
|
72
|
+
const defs: BuildParamsConfig = { count: { type: "number" } };
|
|
73
|
+
const ok = resolveBuildParams(defs, { cli: { count: "42" } });
|
|
74
|
+
expect(ok.errors).toEqual([]);
|
|
75
|
+
expect(ok.provenance).toEqual([{ name: "count", value: 42, source: "cli" }]);
|
|
76
|
+
|
|
77
|
+
const bad = resolveBuildParams(defs, { cli: { count: "not-a-number" } });
|
|
78
|
+
expect(bad.provenance).toEqual([]);
|
|
79
|
+
expect(bad.errors[0]).toContain('"count"');
|
|
80
|
+
expect(bad.errors[0]).toContain("number");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("boolean type coercion, including an invalid string", () => {
|
|
84
|
+
const defs: BuildParamsConfig = { flag: { type: "boolean" } };
|
|
85
|
+
const trueVal = resolveBuildParams(defs, { cli: { flag: "true" } });
|
|
86
|
+
expect(trueVal.provenance).toEqual([{ name: "flag", value: true, source: "cli" }]);
|
|
87
|
+
|
|
88
|
+
const falseVal = resolveBuildParams(defs, { cli: { flag: "false" } });
|
|
89
|
+
expect(falseVal.provenance).toEqual([{ name: "flag", value: false, source: "cli" }]);
|
|
90
|
+
|
|
91
|
+
const bad = resolveBuildParams(defs, { cli: { flag: "nope" } });
|
|
92
|
+
expect(bad.provenance).toEqual([]);
|
|
93
|
+
expect(bad.errors[0]).toContain("boolean");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("params-file value must be a scalar, not an object/array", () => {
|
|
97
|
+
const defs: BuildParamsConfig = { thing: { type: "string" } };
|
|
98
|
+
const result = resolveBuildParams(defs, { fromFile: { thing: { nested: true } as unknown as string } });
|
|
99
|
+
expect(result.provenance).toEqual([]);
|
|
100
|
+
expect(result.errors[0]).toContain('"thing"');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("every failure across multiple parameters is reported at once", () => {
|
|
104
|
+
const defs: BuildParamsConfig = {
|
|
105
|
+
a: { type: "string" },
|
|
106
|
+
b: { type: "number" },
|
|
107
|
+
};
|
|
108
|
+
const result = resolveBuildParams(defs, { cli: { b: "nan" } });
|
|
109
|
+
expect(result.errors).toHaveLength(2);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("no declared params at all — empty resolution, no errors", () => {
|
|
113
|
+
const result = resolveBuildParams(undefined, {});
|
|
114
|
+
expect(result).toEqual({ provenance: [], errors: [] });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("required: false — an unresolved optional parameter is simply omitted, not an error", () => {
|
|
118
|
+
const defs: BuildParamsConfig = { hostedZoneId: { type: "string", required: false } };
|
|
119
|
+
const result = resolveBuildParams(defs, {});
|
|
120
|
+
expect(result.errors).toEqual([]);
|
|
121
|
+
expect(result.provenance).toEqual([]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("required: false — a supplied value still resolves normally", () => {
|
|
125
|
+
const defs: BuildParamsConfig = { hostedZoneId: { type: "string", required: false } };
|
|
126
|
+
const result = resolveBuildParams(defs, { cli: { hostedZoneId: "Z123" } });
|
|
127
|
+
expect(result.errors).toEqual([]);
|
|
128
|
+
expect(result.provenance).toEqual([{ name: "hostedZoneId", value: "Z123", source: "cli" }]);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("buildParamValues", () => {
|
|
133
|
+
test("projects provenance records down to a plain value map", () => {
|
|
134
|
+
const provenance = [
|
|
135
|
+
{ name: "tier", value: "light" as const, source: "default" as const },
|
|
136
|
+
{ name: "count", value: 3 as const, source: "cli" as const },
|
|
137
|
+
];
|
|
138
|
+
expect(buildParamValues(provenance)).toEqual({ tier: "light", count: 3 });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("empty provenance yields an empty map", () => {
|
|
142
|
+
expect(buildParamValues([])).toEqual({});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { BuildParamProvenance } from "./provenance";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build-time parameter declaration + resolution (chant #1064, follow-up to
|
|
5
|
+
* epic #1019's fold work — see issue #1064's "DECISION: option 1" comment).
|
|
6
|
+
*
|
|
7
|
+
* A build-time parameter is declared in `chant.config.ts`'s `buildParams`
|
|
8
|
+
* (name, type, optional `default`/`enum`/`env` mapping), supplied to `chant
|
|
9
|
+
* build` (a `--param name=value` flag, a `--params-file` JSON file, or a
|
|
10
|
+
* declared `env` var), and referenced from source as `params.<name>` (see
|
|
11
|
+
* ./params.ts) — never as an ambient `process.env` read. This module owns
|
|
12
|
+
* declaration + precedence + validation; ./params.ts is the plain runtime
|
|
13
|
+
* object source references; ../discovery/fold-import.ts is what makes a
|
|
14
|
+
* `params.<name>` reference fold to a literal instead of a symbolic node.
|
|
15
|
+
*
|
|
16
|
+
* This is NOT the deploy-time `Parameter` class (`lexicons/aws/src/parameter.ts`)
|
|
17
|
+
* — that resolves when a CloudFormation stack deploys; this resolves before
|
|
18
|
+
* the template is even synthesized, so its value can change which resources
|
|
19
|
+
* are produced at all. See ./params.ts's module doc for the full distinction.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** A build-time parameter's resolved (and declared-default/enum) value — always a scalar. */
|
|
23
|
+
export type BuildParamValue = string | number | boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* One declared build-time parameter (`chant.config.ts`'s `buildParams.<name>`).
|
|
27
|
+
*/
|
|
28
|
+
export interface BuildParamDef {
|
|
29
|
+
/** The value's declared type — supplied strings (CLI flags, env vars, JSON-file strings) are coerced to it. */
|
|
30
|
+
type: "string" | "number" | "boolean";
|
|
31
|
+
/** Value used when no `--param`/`--params-file`/declared `env` var supplies one. Omit to require an explicit value every build. */
|
|
32
|
+
default?: BuildParamValue;
|
|
33
|
+
/**
|
|
34
|
+
* Allowed values — a resolved value outside this list is a build error
|
|
35
|
+
* naming the parameter (never a thrown error from user source). Replaces
|
|
36
|
+
* the hand-written `if (!VALID.includes(raw)) throw ...` pattern loomster's
|
|
37
|
+
* `params.ts` files used before migrating to this mechanism.
|
|
38
|
+
*/
|
|
39
|
+
enum?: readonly BuildParamValue[];
|
|
40
|
+
/**
|
|
41
|
+
* Opt-in, EXPLICITLY declared environment-variable fallback — e.g. `env:
|
|
42
|
+
* "LOOM_TIER"`. This is the only place an env var may feed a build-time
|
|
43
|
+
* parameter: reading `process.env` directly from project source is never
|
|
44
|
+
* supported (see ./params.ts's module doc and ../fold/fold.ts's pointed
|
|
45
|
+
* error for a bare `process` reference). Consulted only when no
|
|
46
|
+
* `--param`/`--params-file` value was supplied for this parameter.
|
|
47
|
+
*/
|
|
48
|
+
env?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Set `false` to make an unresolved value NOT a build error: `params.<name>`
|
|
51
|
+
* is simply omitted (reads as plain JS `undefined` — a normal, un-erroring
|
|
52
|
+
* property access on an object missing that key), instead of the default
|
|
53
|
+
* behavior of requiring every declared parameter to resolve to something.
|
|
54
|
+
* For a value that is genuinely optional with no meaningful default (an ARN
|
|
55
|
+
* that references an existing resource only on some deploys, a CIDR
|
|
56
|
+
* override, a JSON blob) — the same "unset means the composite decides"
|
|
57
|
+
* shape a hand-written `process.env.X || undefined` used to express.
|
|
58
|
+
* Default `true` (a declared parameter must resolve to a value).
|
|
59
|
+
*/
|
|
60
|
+
required?: boolean;
|
|
61
|
+
/** Human-readable description, surfaced in error messages and docs generation. */
|
|
62
|
+
description?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A project's full set of declared build-time parameters, keyed by name. */
|
|
66
|
+
export type BuildParamsConfig = Record<string, BuildParamDef>;
|
|
67
|
+
|
|
68
|
+
/** Raw, not-yet-validated inputs {@link resolveBuildParams} resolves against a project's declared {@link BuildParamsConfig}. */
|
|
69
|
+
export interface BuildParamsInput {
|
|
70
|
+
/** `--param name=value` flags, repeated — highest precedence. */
|
|
71
|
+
cli?: Record<string, string>;
|
|
72
|
+
/** Parsed contents of a `--params-file` JSON file — second precedence. */
|
|
73
|
+
fromFile?: Record<string, unknown>;
|
|
74
|
+
/** The process environment, consulted only for a parameter that declares an `env` mapping, and only once `cli`/`fromFile` have no value for it. */
|
|
75
|
+
env?: Record<string, string | undefined>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Result of resolving a project's declared parameters against one build invocation's inputs. */
|
|
79
|
+
export interface BuildParamsResolution {
|
|
80
|
+
/** Every successfully resolved parameter — see {@link BuildParamProvenance}. Empty when the project declares none. */
|
|
81
|
+
provenance: BuildParamProvenance[];
|
|
82
|
+
/**
|
|
83
|
+
* Validation failures, each naming the offending parameter — an unknown
|
|
84
|
+
* `--param`/`--params-file` key, a missing required value, a type mismatch,
|
|
85
|
+
* or a value outside a declared `enum`. Reported by the CLI as a build
|
|
86
|
+
* error (chant #1064's acceptance criterion: "not a thrown error inside
|
|
87
|
+
* user source"); never thrown from here.
|
|
88
|
+
*/
|
|
89
|
+
errors: string[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatValue(value: unknown): string {
|
|
93
|
+
return typeof value === "string" ? `"${value}"` : String(value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Coerce `raw` to `def.type`, appending a located error and returning `undefined` on failure. */
|
|
97
|
+
function coerce(name: string, raw: BuildParamValue, def: BuildParamDef, errors: string[]): BuildParamValue | undefined {
|
|
98
|
+
if (def.type === "string") return String(raw);
|
|
99
|
+
|
|
100
|
+
if (def.type === "number") {
|
|
101
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
102
|
+
if (Number.isNaN(n)) {
|
|
103
|
+
errors.push(`build parameter "${name}" must be a number, got ${formatValue(raw)}`);
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return n;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// def.type === "boolean"
|
|
110
|
+
if (typeof raw === "boolean") return raw;
|
|
111
|
+
if (raw === "true") return true;
|
|
112
|
+
if (raw === "false") return false;
|
|
113
|
+
errors.push(`build parameter "${name}" must be a boolean ("true" or "false"), got ${formatValue(raw)}`);
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Resolve a project's declared build-time parameters against one build's
|
|
119
|
+
* supplied inputs. Precedence per parameter, most to least specific:
|
|
120
|
+
* `cli` (`--param name=value`) > `fromFile` (`--params-file`) > the
|
|
121
|
+
* parameter's own declared `env` mapping (only if set) > `def.default`.
|
|
122
|
+
*
|
|
123
|
+
* A parameter with no declared `default` and no value from any source is a
|
|
124
|
+
* build error, not a silently-`undefined` value — a build-time parameter
|
|
125
|
+
* exists specifically so a project never has an invisible dependency on
|
|
126
|
+
* ambient state; leaving one unresolved would just reintroduce that under a
|
|
127
|
+
* different name. Every failure is collected (not thrown), each naming the
|
|
128
|
+
* offending parameter, so a single invocation reports every problem at once.
|
|
129
|
+
*
|
|
130
|
+
* The one opt-out is `def.required: false` — for a parameter that is
|
|
131
|
+
* genuinely optional with no meaningful default (an ARN that only applies to
|
|
132
|
+
* a reference-existing deploy, a CIDR override), an unresolved value is
|
|
133
|
+
* simply omitted from `provenance`/`params` rather than an error; source
|
|
134
|
+
* reads it as plain `undefined`, same as before migrating off
|
|
135
|
+
* `process.env.X || undefined`.
|
|
136
|
+
*/
|
|
137
|
+
export function resolveBuildParams(defs: BuildParamsConfig | undefined, input: BuildParamsInput): BuildParamsResolution {
|
|
138
|
+
const declared = defs ?? {};
|
|
139
|
+
const cli = input.cli ?? {};
|
|
140
|
+
const fromFile = input.fromFile ?? {};
|
|
141
|
+
const env = input.env ?? {};
|
|
142
|
+
const errors: string[] = [];
|
|
143
|
+
const provenance: BuildParamProvenance[] = [];
|
|
144
|
+
|
|
145
|
+
for (const key of Object.keys(cli)) {
|
|
146
|
+
if (!(key in declared)) {
|
|
147
|
+
errors.push(`unknown build parameter "${key}" (from --param) — not declared in chant.config.ts's buildParams`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const key of Object.keys(fromFile)) {
|
|
151
|
+
if (!(key in declared)) {
|
|
152
|
+
errors.push(`unknown build parameter "${key}" (from --params-file) — not declared in chant.config.ts's buildParams`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const [name, def] of Object.entries(declared)) {
|
|
157
|
+
let raw: BuildParamValue | undefined;
|
|
158
|
+
let source: BuildParamProvenance["source"] | undefined;
|
|
159
|
+
|
|
160
|
+
if (name in cli) {
|
|
161
|
+
raw = cli[name];
|
|
162
|
+
source = "cli";
|
|
163
|
+
} else if (name in fromFile) {
|
|
164
|
+
const fileValue = fromFile[name];
|
|
165
|
+
if (typeof fileValue !== "string" && typeof fileValue !== "number" && typeof fileValue !== "boolean") {
|
|
166
|
+
errors.push(`build parameter "${name}" (from --params-file) must be a string, number, or boolean`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
raw = fileValue;
|
|
170
|
+
source = "params-file";
|
|
171
|
+
} else if (def.env && env[def.env] !== undefined) {
|
|
172
|
+
raw = env[def.env];
|
|
173
|
+
source = "env";
|
|
174
|
+
} else if (def.default !== undefined) {
|
|
175
|
+
raw = def.default;
|
|
176
|
+
source = "default";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (raw === undefined || source === undefined) {
|
|
180
|
+
if (def.required === false) continue; // omitted entirely — params.<name> reads as undefined, not an error
|
|
181
|
+
const envHint = def.env ? `, set ${def.env}` : "";
|
|
182
|
+
errors.push(
|
|
183
|
+
`build parameter "${name}" has no value — pass --param ${name}=<value>, use --params-file${envHint}, or add a default in chant.config.ts's buildParams`,
|
|
184
|
+
);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const value = coerce(name, raw, def, errors);
|
|
189
|
+
if (value === undefined) continue;
|
|
190
|
+
|
|
191
|
+
if (def.enum && !def.enum.includes(value)) {
|
|
192
|
+
errors.push(
|
|
193
|
+
`build parameter "${name}" must be one of ${def.enum.map(formatValue).join(", ")}, got ${formatValue(value)}`,
|
|
194
|
+
);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
provenance.push({ name, value, source });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { provenance, errors };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Project the resolved provenance records down to a plain `{ name: value }` map — what actually gets bound to `params.<name>` (see ./params.ts). */
|
|
205
|
+
export function buildParamValues(provenance: readonly BuildParamProvenance[]): Record<string, BuildParamValue> {
|
|
206
|
+
return Object.fromEntries(provenance.map((p) => [p.name, p.value]));
|
|
207
|
+
}
|
package/src/build.test.ts
CHANGED
|
@@ -64,6 +64,44 @@ export const testEntity = {
|
|
|
64
64
|
expect(result.entities.size).toBe(1);
|
|
65
65
|
expect(result.entities.has("testEntity")).toBe(true);
|
|
66
66
|
expect(result.errors.length).toBe(0);
|
|
67
|
+
expect(result.foldDecisions).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("#1022 — --fold folds a leaf-only module and surfaces foldDecisions", async () => {
|
|
71
|
+
const { dirname, resolve: resolvePath } = await import("node:path");
|
|
72
|
+
const { fileURLToPath } = await import("node:url");
|
|
73
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
74
|
+
const runtimePath = resolvePath(thisDir, "runtime");
|
|
75
|
+
|
|
76
|
+
await writeFile(
|
|
77
|
+
join(testDir, "resources.ts"),
|
|
78
|
+
`
|
|
79
|
+
import { createResource } from ${JSON.stringify(runtimePath)};
|
|
80
|
+
export const Bucket = createResource("Test::Bucket", "aws", { arn: "Arn" });
|
|
81
|
+
`
|
|
82
|
+
);
|
|
83
|
+
await writeFile(
|
|
84
|
+
join(testDir, "main.ts"),
|
|
85
|
+
`
|
|
86
|
+
import { Bucket } from "./resources";
|
|
87
|
+
throw new Error("must never execute — sentinel for #1022 fold verification");
|
|
88
|
+
export const bucket = new Bucket({ name: "my-bucket" });
|
|
89
|
+
`
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const mockSerializer: Serializer = {
|
|
93
|
+
name: "aws",
|
|
94
|
+
rulePrefix: "TEST",
|
|
95
|
+
serialize: (entities) => JSON.stringify([...entities.keys()]),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const result = await build(testDir, [mockSerializer], undefined, { fold: true });
|
|
99
|
+
|
|
100
|
+
expect(result.errors).toEqual([]);
|
|
101
|
+
expect(result.entities.size).toBe(1);
|
|
102
|
+
expect(result.entities.has("bucket")).toBe(true);
|
|
103
|
+
const mainDecision = result.foldDecisions.find((d) => d.file.endsWith("main.ts"));
|
|
104
|
+
expect(mainDecision?.mode).toBe("fold");
|
|
67
105
|
});
|
|
68
106
|
|
|
69
107
|
test("handles discovery errors", async () => {
|