@intentius/chant 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +2 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/env.d.ts +31 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/graph-ir.d.ts +17 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/main.ts +20 -2
- package/src/cli/registry.ts +2 -1
- package/src/env.test.ts +77 -0
- package/src/env.ts +39 -0
- package/src/graph-ir.test.ts +18 -0
- package/src/graph-ir.ts +46 -3
- package/src/index.ts +1 -0
package/dist/cli/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAKA,OAAO,EAAmC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAKA,OAAO,EAAmC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAgB9E;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAiJpD"}
|
package/dist/cli/registry.d.ts
CHANGED
|
@@ -52,7 +52,8 @@ export interface ParsedArgs {
|
|
|
52
52
|
verbatim?: boolean;
|
|
53
53
|
/** `chant lifecycle … --src <dir>` — build root override for lifecycle commands */
|
|
54
54
|
src?: string;
|
|
55
|
-
/**
|
|
55
|
+
/** `--env <name>` — active environment: sets CHANT_ENV so env-aware source
|
|
56
|
+
* re-evaluates for that environment (`build` + `graph`), and drives policy. */
|
|
56
57
|
env?: string;
|
|
57
58
|
/** `chant graph --stacks` — render the cross-stack apply-ordering graph */
|
|
58
59
|
stacks?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/cli/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/cli/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active environment for a chant run (#505).
|
|
3
|
+
*
|
|
4
|
+
* `chant graph --env prod` / `chant build --env prod` set {@link ENV_VAR} before
|
|
5
|
+
* the project is discovered, so env-aware source can branch on it — producing an
|
|
6
|
+
* environment-specific graph (or build). Environments in chant are **build-context
|
|
7
|
+
* switches**, not a resource filter: there is no per-node env membership, so an
|
|
8
|
+
* "env view" comes from *re-evaluating* the project under the environment, exactly
|
|
9
|
+
* as a deploy of that environment would. Author env-aware source by branching on
|
|
10
|
+
* {@link env}:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { env } from "@intentius/chant";
|
|
14
|
+
* const replicas = env() === "prod" ? 5 : 1;
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Run `chant graph --env prod` and `chant graph --env dev` to get the two graphs;
|
|
18
|
+
* pinhole renders/diffs them to show environment drift (INTENTIUS/pinhole#3).
|
|
19
|
+
*/
|
|
20
|
+
/** The environment variable the CLI sets from `--env`. */
|
|
21
|
+
export declare const ENV_VAR = "CHANT_ENV";
|
|
22
|
+
/** The active environment for this run (from `--env`), or `fallback` if none. */
|
|
23
|
+
export declare function env(fallback?: string): string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Validate a requested environment against the project's declared `environments`
|
|
26
|
+
* (`chant.config`). Returns an error message for an unknown env, or `undefined`
|
|
27
|
+
* when it's valid (or when the project declares no environments, in which case
|
|
28
|
+
* any name is accepted).
|
|
29
|
+
*/
|
|
30
|
+
export declare function unknownEnvError(requested: string | undefined, declared: string[] | undefined): string | undefined;
|
|
31
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,0DAA0D;AAC1D,eAAO,MAAM,OAAO,cAAc,CAAC;AAEnC,iFAAiF;AACjF,wBAAgB,GAAG,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIjH"}
|
package/dist/graph-ir.d.ts
CHANGED
|
@@ -60,14 +60,30 @@ export interface IREdge {
|
|
|
60
60
|
export interface IRGroups {
|
|
61
61
|
byLexicon?: Record<string, string[]>;
|
|
62
62
|
byComposite?: Record<string, string[]>;
|
|
63
|
-
/**
|
|
63
|
+
/** Deployable-stack grouping (`stackName → nodeIds`). A stack is a lexicon
|
|
64
|
+
* partition today; #513 phase 2 regroups by nested child-project. Consumers
|
|
65
|
+
* (e.g. pinhole's boundary boxes) read this rather than inferring stacks. */
|
|
64
66
|
byStack?: Record<string, string[]>;
|
|
65
67
|
}
|
|
68
|
+
/** A cross-stack export this stack publishes (a `stackOutput`/`output`): its
|
|
69
|
+
* name (the matching key another stack imports by) and the node that produces it. */
|
|
70
|
+
export interface IRExport {
|
|
71
|
+
/** Output name — the cross-stack handle (e.g. "ClusterArn"). */
|
|
72
|
+
name: string;
|
|
73
|
+
/** Logical name of the producing node, when resolvable. */
|
|
74
|
+
node?: string;
|
|
75
|
+
/** Producer-side attribute the output reads (e.g. "Arn"). */
|
|
76
|
+
attr?: string;
|
|
77
|
+
}
|
|
66
78
|
/** The full graph IR for a project at the default (declarable) detail level. */
|
|
67
79
|
export interface GraphIR {
|
|
68
80
|
nodes: IRNode[];
|
|
69
81
|
edges: IREdge[];
|
|
70
82
|
groups: IRGroups;
|
|
83
|
+
/** Outputs this stack publishes for other stacks to import. Imports are already
|
|
84
|
+
* nodes (`AWS::CloudFormation::Parameter` etc.); a viewer matches an import's
|
|
85
|
+
* name to an export's name to draw the cross-stack edge to its producer (#513). */
|
|
86
|
+
exports?: IRExport[];
|
|
71
87
|
}
|
|
72
88
|
/**
|
|
73
89
|
* Build the graph IR from resolved entities (the output of `discover`). Pure and
|
package/dist/graph-ir.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-ir.d.ts","sourceRoot":"","sources":["../src/graph-ir.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAC;AAK7D;;;;;;;;GAQG;AAEH,iFAAiF;AACjF,MAAM,WAAW,SAAS;IACxB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iCAAiC;AACjC,MAAM,WAAW,MAAM;IACrB,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mCAAmC;IACnC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,IAAI,EAAE,KAAK,CAAC;IACZ,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qFAAqF;AACrF,MAAM,WAAW,QAAQ;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC
|
|
1
|
+
{"version":3,"file":"graph-ir.d.ts","sourceRoot":"","sources":["../src/graph-ir.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAC;AAK7D;;;;;;;;GAQG;AAEH,iFAAiF;AACjF,MAAM,WAAW,SAAS;IACxB,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iCAAiC;AACjC,MAAM,WAAW,MAAM;IACrB,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mCAAmC;IACnC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,IAAI,EAAE,KAAK,CAAC;IACZ,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qFAAqF;AACrF,MAAM,WAAW,QAAQ;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC;;iFAE6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACpC;AAED;qFACqF;AACrF,MAAM,WAAW,QAAQ;IACvB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,gFAAgF;AAChF,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,QAAQ,CAAC;IACjB;;uFAEmF;IACnF,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;CACtB;AA6ID;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CA6ET"}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export * from "./discovery/graph.js";
|
|
|
24
24
|
export * from "./discovery/index.js";
|
|
25
25
|
export * from "./discovery/cache.js";
|
|
26
26
|
export * from "./build.js";
|
|
27
|
+
export * from "./env.js";
|
|
27
28
|
export * from "./graph-ir.js";
|
|
28
29
|
export * from "./graph-detail.js";
|
|
29
30
|
export * from "./graph-mermaid.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,QAAQ,CAAC;AACvB,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAElC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAClE,cAAc,EAAE,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5F,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,QAAQ,CAAC;AACvB,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAElC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAClE,cAAc,EAAE,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5F,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
package/package.json
CHANGED
package/src/cli/main.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { formatSuccess, formatError } from "./format";
|
|
|
5
5
|
import { loadPlugins, resolveProjectLexicons } from "./plugins";
|
|
6
6
|
import { resolveCommand, type CommandDef, type ParsedArgs } from "./registry";
|
|
7
7
|
import { loadChantConfig } from "../config";
|
|
8
|
+
import { ENV_VAR, unknownEnvError } from "../env";
|
|
8
9
|
import { initRuntime } from "../runtime-adapter";
|
|
9
10
|
import { runBuild } from "./handlers/build";
|
|
10
11
|
import { runLint } from "./handlers/lint";
|
|
@@ -242,7 +243,10 @@ Options:
|
|
|
242
243
|
- list: text (default) or json
|
|
243
244
|
- lint: stylish (default), json, or sarif
|
|
244
245
|
-d, --lexicon <name> Build only the specified lexicon (e.g. aws, gitlab)
|
|
245
|
-
--env <name>
|
|
246
|
+
--env <name> Active environment: sets CHANT_ENV so env-aware source
|
|
247
|
+
re-evaluates for that environment (build + graph), and
|
|
248
|
+
drives organizational policy. Must be in chant.config
|
|
249
|
+
\`environments\` when declared.
|
|
246
250
|
-t, --template <name> Init template (e.g. node-pipeline, docker-build)
|
|
247
251
|
--skill <name> Init: install only this skill from the lexicon
|
|
248
252
|
--fix Auto-fix fixable issues (lint command)
|
|
@@ -369,16 +373,30 @@ async function main(): Promise<void> {
|
|
|
369
373
|
process.exit(args.help ? 0 : 1);
|
|
370
374
|
}
|
|
371
375
|
|
|
376
|
+
// `--env <name>` is a build-context switch for the whole invocation: set it
|
|
377
|
+
// *before* anything imports the project so env-aware source (and thus the graph
|
|
378
|
+
// / build) reflects that environment. Set early, before config import, since
|
|
379
|
+
// chant.config itself may branch on the env. (#505)
|
|
380
|
+
if (args.env) process.env[ENV_VAR] = args.env;
|
|
381
|
+
|
|
372
382
|
// Initialize runtime adapter early — before plugins or commands run
|
|
373
383
|
const projectPath0 = resolve(args.path === "." ? "." : args.path);
|
|
384
|
+
let loadedConfig;
|
|
374
385
|
try {
|
|
375
|
-
await loadChantConfig(projectPath0);
|
|
386
|
+
loadedConfig = await loadChantConfig(projectPath0);
|
|
376
387
|
initRuntime();
|
|
377
388
|
} catch {
|
|
378
389
|
// Config may not exist yet (e.g. `chant init`)
|
|
379
390
|
initRuntime();
|
|
380
391
|
}
|
|
381
392
|
|
|
393
|
+
// Reject an --env that isn't among the project's declared `environments`.
|
|
394
|
+
const envErr = unknownEnvError(args.env, loadedConfig?.config.environments);
|
|
395
|
+
if (envErr) {
|
|
396
|
+
console.error(formatError({ message: envErr, hint: 'Declare it in chant.config `environments`, or omit --env.' }));
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
|
|
382
400
|
const match = resolveCommand(args, registry);
|
|
383
401
|
if (!match) {
|
|
384
402
|
console.error(formatError({
|
package/src/cli/registry.ts
CHANGED
|
@@ -53,7 +53,8 @@ export interface ParsedArgs {
|
|
|
53
53
|
verbatim?: boolean;
|
|
54
54
|
/** `chant lifecycle … --src <dir>` — build root override for lifecycle commands */
|
|
55
55
|
src?: string;
|
|
56
|
-
/**
|
|
56
|
+
/** `--env <name>` — active environment: sets CHANT_ENV so env-aware source
|
|
57
|
+
* re-evaluates for that environment (`build` + `graph`), and drives policy. */
|
|
57
58
|
env?: string;
|
|
58
59
|
/** `chant graph --stacks` — render the cross-stack apply-ordering graph */
|
|
59
60
|
stacks?: boolean;
|
package/src/env.test.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { env, unknownEnvError, ENV_VAR } from "./env";
|
|
6
|
+
import { discover } from "./discovery/index";
|
|
7
|
+
|
|
8
|
+
const withEnv = async (value: string | undefined, fn: () => void | Promise<void>): Promise<void> => {
|
|
9
|
+
const prev = process.env[ENV_VAR];
|
|
10
|
+
try {
|
|
11
|
+
if (value === undefined) delete process.env[ENV_VAR];
|
|
12
|
+
else process.env[ENV_VAR] = value;
|
|
13
|
+
await fn();
|
|
14
|
+
} finally {
|
|
15
|
+
if (prev === undefined) delete process.env[ENV_VAR];
|
|
16
|
+
else process.env[ENV_VAR] = prev;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe("env()", () => {
|
|
21
|
+
test("reads CHANT_ENV, with a fallback", async () => {
|
|
22
|
+
await withEnv(undefined, () => {
|
|
23
|
+
expect(env()).toBeUndefined();
|
|
24
|
+
expect(env("dev")).toBe("dev");
|
|
25
|
+
});
|
|
26
|
+
await withEnv("prod", () => {
|
|
27
|
+
expect(env()).toBe("prod");
|
|
28
|
+
expect(env("dev")).toBe("prod");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("unknownEnvError", () => {
|
|
34
|
+
test("accepts a declared env, and any env when none are declared", () => {
|
|
35
|
+
expect(unknownEnvError("prod", ["dev", "prod"])).toBeUndefined();
|
|
36
|
+
expect(unknownEnvError("prod", undefined)).toBeUndefined();
|
|
37
|
+
expect(unknownEnvError("prod", [])).toBeUndefined();
|
|
38
|
+
expect(unknownEnvError(undefined, ["dev"])).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
test("rejects an undeclared env with a clear message", () => {
|
|
41
|
+
expect(unknownEnvError("stage", ["dev", "prod"])).toMatch(/Unknown environment "stage".*dev, prod/);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("env-aware discovery (#505)", () => {
|
|
46
|
+
// Env is a build-context switch: the same source, discovered under a different
|
|
47
|
+
// CHANT_ENV, yields a different entity set (here a prod-only resource).
|
|
48
|
+
const src = `
|
|
49
|
+
export const base = { entityType: "Base", [Symbol.for("chant.declarable")]: true };
|
|
50
|
+
export const prodOnly = process.env.CHANT_ENV === "prod"
|
|
51
|
+
? { entityType: "ProdOnly", [Symbol.for("chant.declarable")]: true }
|
|
52
|
+
: undefined;
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
test("re-evaluates the project under the active environment", async () => {
|
|
56
|
+
// Distinct dirs → distinct module URLs → fresh import per env (import() caches by path).
|
|
57
|
+
const prodDir = join(tmpdir(), `chant-env-prod-${Date.now()}-${Math.random()}`);
|
|
58
|
+
const devDir = join(tmpdir(), `chant-env-dev-${Date.now()}-${Math.random()}`);
|
|
59
|
+
await mkdir(prodDir, { recursive: true });
|
|
60
|
+
await mkdir(devDir, { recursive: true });
|
|
61
|
+
await writeFile(join(prodDir, "app.ts"), src);
|
|
62
|
+
await writeFile(join(devDir, "app.ts"), src);
|
|
63
|
+
try {
|
|
64
|
+
let prod!: Awaited<ReturnType<typeof discover>>;
|
|
65
|
+
let dev!: Awaited<ReturnType<typeof discover>>;
|
|
66
|
+
await withEnv("prod", async () => { prod = await discover(prodDir); });
|
|
67
|
+
await withEnv("dev", async () => { dev = await discover(devDir); });
|
|
68
|
+
expect(prod.entities.has("base")).toBe(true);
|
|
69
|
+
expect(dev.entities.has("base")).toBe(true);
|
|
70
|
+
expect(prod.entities.has("prodOnly")).toBe(true); // present under prod
|
|
71
|
+
expect(dev.entities.has("prodOnly")).toBe(false); // absent under dev
|
|
72
|
+
} finally {
|
|
73
|
+
await rm(prodDir, { recursive: true, force: true });
|
|
74
|
+
await rm(devDir, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active environment for a chant run (#505).
|
|
3
|
+
*
|
|
4
|
+
* `chant graph --env prod` / `chant build --env prod` set {@link ENV_VAR} before
|
|
5
|
+
* the project is discovered, so env-aware source can branch on it — producing an
|
|
6
|
+
* environment-specific graph (or build). Environments in chant are **build-context
|
|
7
|
+
* switches**, not a resource filter: there is no per-node env membership, so an
|
|
8
|
+
* "env view" comes from *re-evaluating* the project under the environment, exactly
|
|
9
|
+
* as a deploy of that environment would. Author env-aware source by branching on
|
|
10
|
+
* {@link env}:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { env } from "@intentius/chant";
|
|
14
|
+
* const replicas = env() === "prod" ? 5 : 1;
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Run `chant graph --env prod` and `chant graph --env dev` to get the two graphs;
|
|
18
|
+
* pinhole renders/diffs them to show environment drift (INTENTIUS/pinhole#3).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** The environment variable the CLI sets from `--env`. */
|
|
22
|
+
export const ENV_VAR = "CHANT_ENV";
|
|
23
|
+
|
|
24
|
+
/** The active environment for this run (from `--env`), or `fallback` if none. */
|
|
25
|
+
export function env(fallback?: string): string | undefined {
|
|
26
|
+
return process.env[ENV_VAR] ?? fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate a requested environment against the project's declared `environments`
|
|
31
|
+
* (`chant.config`). Returns an error message for an unknown env, or `undefined`
|
|
32
|
+
* when it's valid (or when the project declares no environments, in which case
|
|
33
|
+
* any name is accepted).
|
|
34
|
+
*/
|
|
35
|
+
export function unknownEnvError(requested: string | undefined, declared: string[] | undefined): string | undefined {
|
|
36
|
+
if (!requested || !declared || declared.length === 0) return undefined;
|
|
37
|
+
if (declared.includes(requested)) return undefined;
|
|
38
|
+
return `Unknown environment "${requested}". Declared environments: ${declared.join(", ")}.`;
|
|
39
|
+
}
|
package/src/graph-ir.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, test, expect } from "vitest";
|
|
|
2
2
|
import { buildGraphIr } from "./graph-ir";
|
|
3
3
|
import { DECLARABLE_MARKER, type Declarable } from "./declarable";
|
|
4
4
|
import { AttrRef } from "./attrref";
|
|
5
|
+
import { LexiconOutput } from "./lexicon-output";
|
|
5
6
|
import { resolveAttrRefs } from "./discovery/resolve";
|
|
6
7
|
import { setProvenance } from "./provenance";
|
|
7
8
|
|
|
@@ -66,6 +67,20 @@ describe("buildGraphIr", () => {
|
|
|
66
67
|
expect(ir.nodes.find((n) => n.id === "sg")!.attrs).toEqual({ VpcId: { $ref: "vpc.VpcId" } });
|
|
67
68
|
});
|
|
68
69
|
|
|
70
|
+
test("surfaces cross-stack exports with the producing node, not as nodes (#513)", () => {
|
|
71
|
+
const cluster = decl({ lexicon: "aws", entityType: "AWS::ECS::Cluster" });
|
|
72
|
+
const out = new LexiconOutput(new AttrRef(cluster, "Arn"), "ClusterArn");
|
|
73
|
+
const entities = new Map<string, Declarable>([
|
|
74
|
+
["cluster", cluster],
|
|
75
|
+
["clusterArn", out as unknown as Declarable],
|
|
76
|
+
]);
|
|
77
|
+
resolveAttrRefs(entities);
|
|
78
|
+
|
|
79
|
+
const ir = buildGraphIr(entities);
|
|
80
|
+
expect(ir.nodes.map((n) => n.id)).toEqual(["cluster"]); // the output itself is not a node
|
|
81
|
+
expect(ir.exports).toEqual([{ name: "ClusterArn", node: "cluster", attr: "Arn" }]);
|
|
82
|
+
});
|
|
83
|
+
|
|
69
84
|
test("excludes property-kind declarables and keeps resources", () => {
|
|
70
85
|
const resource = decl({ lexicon: "k8s", entityType: "Deployment" });
|
|
71
86
|
const prop = decl({ lexicon: "k8s", entityType: "Probe", kind: "property" as const });
|
|
@@ -133,6 +148,9 @@ describe("buildGraphIr", () => {
|
|
|
133
148
|
const ir = buildGraphIr(entities);
|
|
134
149
|
expect(ir.nodes.map((n) => n.id)).toEqual(["alpha", "mid", "zeta"]); // sorted
|
|
135
150
|
expect(ir.groups.byLexicon).toEqual({ gcp: ["alpha", "zeta"], k8s: ["mid"] });
|
|
151
|
+
// byStack mirrors the lexicon partition today (each lexicon → one deployable
|
|
152
|
+
// stack); emitted as its own axis so consumers read stacks, not lexicons (#513).
|
|
153
|
+
expect(ir.groups.byStack).toEqual({ gcp: ["alpha", "zeta"], k8s: ["mid"] });
|
|
136
154
|
|
|
137
155
|
// Same input (different insertion order) yields identical IR.
|
|
138
156
|
const reordered = new Map<string, Declarable>([
|
package/src/graph-ir.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { relative, isAbsolute } from "node:path";
|
|
|
2
2
|
import { AttrRef } from "./attrref";
|
|
3
3
|
import { isAttrRefLike } from "./utils";
|
|
4
4
|
import { type Declarable, isDeclarable } from "./declarable";
|
|
5
|
-
import { isLexiconOutput } from "./lexicon-output";
|
|
5
|
+
import { isLexiconOutput, type LexiconOutput } from "./lexicon-output";
|
|
6
6
|
import { getProvenance } from "./provenance";
|
|
7
7
|
import { INTRINSIC_MARKER } from "./intrinsic";
|
|
8
8
|
|
|
@@ -72,15 +72,32 @@ export interface IREdge {
|
|
|
72
72
|
export interface IRGroups {
|
|
73
73
|
byLexicon?: Record<string, string[]>;
|
|
74
74
|
byComposite?: Record<string, string[]>;
|
|
75
|
-
/**
|
|
75
|
+
/** Deployable-stack grouping (`stackName → nodeIds`). A stack is a lexicon
|
|
76
|
+
* partition today; #513 phase 2 regroups by nested child-project. Consumers
|
|
77
|
+
* (e.g. pinhole's boundary boxes) read this rather than inferring stacks. */
|
|
76
78
|
byStack?: Record<string, string[]>;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
/** A cross-stack export this stack publishes (a `stackOutput`/`output`): its
|
|
82
|
+
* name (the matching key another stack imports by) and the node that produces it. */
|
|
83
|
+
export interface IRExport {
|
|
84
|
+
/** Output name — the cross-stack handle (e.g. "ClusterArn"). */
|
|
85
|
+
name: string;
|
|
86
|
+
/** Logical name of the producing node, when resolvable. */
|
|
87
|
+
node?: string;
|
|
88
|
+
/** Producer-side attribute the output reads (e.g. "Arn"). */
|
|
89
|
+
attr?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
79
92
|
/** The full graph IR for a project at the default (declarable) detail level. */
|
|
80
93
|
export interface GraphIR {
|
|
81
94
|
nodes: IRNode[];
|
|
82
95
|
edges: IREdge[];
|
|
83
96
|
groups: IRGroups;
|
|
97
|
+
/** Outputs this stack publishes for other stacks to import. Imports are already
|
|
98
|
+
* nodes (`AWS::CloudFormation::Parameter` etc.); a viewer matches an import's
|
|
99
|
+
* name to an export's name to draw the cross-stack edge to its producer (#513). */
|
|
100
|
+
exports?: IRExport[];
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
/** A node is anything that serializes to a resource — not a property or output. */
|
|
@@ -243,6 +260,7 @@ export function buildGraphIr(
|
|
|
243
260
|
const nodes: IRNode[] = [];
|
|
244
261
|
const byLexicon: Record<string, string[]> = {};
|
|
245
262
|
const byComposite: Record<string, string[]> = {};
|
|
263
|
+
const byStack: Record<string, string[]> = {};
|
|
246
264
|
|
|
247
265
|
for (const [name, entity] of entities) {
|
|
248
266
|
if (!nodeIds.has(name)) continue;
|
|
@@ -260,6 +278,12 @@ export function buildGraphIr(
|
|
|
260
278
|
nodes.push(node);
|
|
261
279
|
|
|
262
280
|
(byLexicon[entity.lexicon] ??= []).push(name);
|
|
281
|
+
// A stack is a lexicon partition (each lexicon serialises to one deployable
|
|
282
|
+
// stack — a CloudFormation template, a CI config). `byStack` mirrors that
|
|
283
|
+
// today; #513 phase 2 will regroup it by nested child-project. It's a
|
|
284
|
+
// distinct axis from `byLexicon` (which is for provenance/colouring), so it's
|
|
285
|
+
// emitted separately even where the two currently coincide.
|
|
286
|
+
(byStack[entity.lexicon] ??= []).push(name);
|
|
263
287
|
if (prov?.composite) (byComposite[prov.composite] ??= []).push(name);
|
|
264
288
|
}
|
|
265
289
|
|
|
@@ -275,12 +299,31 @@ export function buildGraphIr(
|
|
|
275
299
|
const edges = [...edgeMap.values()].sort((a, b) => edgeKey(a).localeCompare(edgeKey(b)));
|
|
276
300
|
for (const ids of Object.values(byLexicon)) ids.sort();
|
|
277
301
|
for (const ids of Object.values(byComposite)) ids.sort();
|
|
302
|
+
for (const ids of Object.values(byStack)) ids.sort();
|
|
278
303
|
|
|
279
304
|
const groups: IRGroups = {};
|
|
280
305
|
if (Object.keys(byLexicon).length) groups.byLexicon = sortKeys(byLexicon);
|
|
281
306
|
if (Object.keys(byComposite).length) groups.byComposite = sortKeys(byComposite);
|
|
307
|
+
if (Object.keys(byStack).length) groups.byStack = sortKeys(byStack);
|
|
308
|
+
|
|
309
|
+
// Cross-stack exports: a stack's `output(ref, name)` LexiconOutputs are dropped
|
|
310
|
+
// from the node set (they're not resources), but their name + producing node is
|
|
311
|
+
// the handle another stack imports by. Surface them so a viewer can draw the
|
|
312
|
+
// cross-stack edge (#513). Resolve the producer via the output's source entity.
|
|
313
|
+
const exports: IRExport[] = [];
|
|
314
|
+
for (const entity of entities.values()) {
|
|
315
|
+
if (!isLexiconOutput(entity)) continue;
|
|
316
|
+
const lo = entity as unknown as LexiconOutput;
|
|
317
|
+
if (!lo.outputName) continue;
|
|
318
|
+
const parent = lo._sourceParent?.deref();
|
|
319
|
+
const node = (parent ? reverse.get(parent) : undefined) ?? (lo.sourceEntity || undefined);
|
|
320
|
+
exports.push({ name: lo.outputName, ...(node ? { node } : {}), ...(lo.sourceAttribute ? { attr: lo.sourceAttribute } : {}) });
|
|
321
|
+
}
|
|
322
|
+
exports.sort((a, b) => a.name.localeCompare(b.name));
|
|
282
323
|
|
|
283
|
-
|
|
324
|
+
const ir: GraphIR = { nodes, edges, groups };
|
|
325
|
+
if (exports.length) ir.exports = exports;
|
|
326
|
+
return ir;
|
|
284
327
|
}
|
|
285
328
|
|
|
286
329
|
function sortKeys(rec: Record<string, string[]>): Record<string, string[]> {
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from "./discovery/graph";
|
|
|
25
25
|
export * from "./discovery/index";
|
|
26
26
|
export * from "./discovery/cache";
|
|
27
27
|
export * from "./build";
|
|
28
|
+
export * from "./env";
|
|
28
29
|
export * from "./graph-ir";
|
|
29
30
|
export * from "./graph-detail";
|
|
30
31
|
export * from "./graph-mermaid";
|