@intentius/chant 0.19.1 → 0.21.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 +71 -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/codegen/docs-types.d.ts +2 -0
- package/dist/codegen/docs-types.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/declarable.d.ts +16 -0
- package/dist/declarable.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 +239 -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 +299 -0
- package/dist/fold/fold.d.ts.map +1 -0
- package/dist/fold/foldable-helpers.d.ts +121 -0
- package/dist/fold/foldable-helpers.d.ts.map +1 -0
- package/dist/fold/subset.d.ts +134 -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 +4 -2
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +131 -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/runtime.d.ts +10 -1
- package/dist/runtime.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 +179 -0
- package/src/cli/commands/check-lexicon-intrinsics.ts +348 -0
- package/src/cli/commands/check-lexicon.test.ts +34 -0
- package/src/cli/commands/check-lexicon.ts +137 -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 +67 -0
- package/src/codegen/docs-sections.ts +7 -3
- package/src/codegen/docs-types.ts +2 -0
- 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/declarable.ts +20 -0
- package/src/discovery/collect.ts +17 -3
- package/src/discovery/entity-wire-codec.ts +487 -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 +1598 -0
- package/src/discovery/fold-import.ts +1998 -0
- package/src/discovery/index.test.ts +191 -1
- package/src/discovery/index.ts +242 -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 +812 -0
- package/src/fold/fold.ts +805 -0
- package/src/fold/foldable-helpers.ts +171 -0
- package/src/fold/subset-doc-parity.test.ts +210 -0
- package/src/fold/subset.test.ts +352 -0
- package/src/fold/subset.ts +383 -0
- package/src/index.ts +2 -0
- package/src/lexicon-output.ts +7 -2
- package/src/lexicon-schema.test.ts +57 -0
- package/src/lexicon-schema.ts +8 -1
- package/src/lexicon.ts +132 -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/runtime.ts +11 -2
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { mkdir, writeFile, rm, realpath } from "node:fs/promises";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { join, dirname, resolve } from "node:path";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* chant #1051 — regression test for `discoverComponents`'s exposure:
|
|
11
|
+
* `packages/core/src/components/discover.ts` used to `await import()` every
|
|
12
|
+
* `*.component.ts` file unconditionally, in the CLI's own process, with no
|
|
13
|
+
* `--fold`/`--sandbox` equivalent at all — the same class of bug #1045 fixed
|
|
14
|
+
* for lexicon-resource discovery, but for the parallel `*.component.ts`
|
|
15
|
+
* convention (`chant list --components`, `chant describe --components`,
|
|
16
|
+
* `chant graph --components`, `chant build --components --generate`, `chant
|
|
17
|
+
* run --components`, `chant lint`'s COMP* checks, `chant lifecycle
|
|
18
|
+
* diff/plan --live`).
|
|
19
|
+
*
|
|
20
|
+
* Deliberately an end-to-end test of the real CLI entry point (`main.ts`,
|
|
21
|
+
* spawned as a genuine child process — the same way `bin/chant` invokes it),
|
|
22
|
+
* NOT a call to `discoverComponents()`/`listComponents()` directly: calling
|
|
23
|
+
* either of those skips the exact thing this test exists to prove — that the
|
|
24
|
+
* `--sandbox` flag is actually wired from `chant`'s argv, through `main.ts`,
|
|
25
|
+
* into the CLI handler, into `discoverComponents`'s `sandbox` option. This
|
|
26
|
+
* mirrors `./security-boundary.test.ts`'s rationale (the #1045 Phase 2 bypass
|
|
27
|
+
* was caught precisely because a differential calling `build()` directly
|
|
28
|
+
* couldn't see a hole in the CLI layer above it).
|
|
29
|
+
*
|
|
30
|
+
* Evidence is routed through `chant list --components`'s own JSON stdout (a
|
|
31
|
+
* component NAME), never stderr and never a bare "did it print EXFIL" check —
|
|
32
|
+
* a suppressed/swallowed console message from the sandboxed child can't fake
|
|
33
|
+
* this: the hostile fixture always exports a syntactically valid `Component`
|
|
34
|
+
* (wrapping every privileged attempt in try/catch, so discovery never simply
|
|
35
|
+
* errors out) whose `name` field encodes exactly what it managed to do:
|
|
36
|
+
* "OK" for each of read/write/spawn that succeeded, or the Permission
|
|
37
|
+
* Model's real `err.code` when denied, plus the ambient env var count —
|
|
38
|
+
* mirroring how the #1045 PR itself was verified ("unsandboxed produced
|
|
39
|
+
* read-u-env77, sandboxed produced blocked-erraccessdenied-env2").
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
const mainTsPath = resolve(thisDir, "main.ts");
|
|
44
|
+
const repoRoot = resolve(thisDir, "../../../..");
|
|
45
|
+
|
|
46
|
+
describe("CLI end-to-end — discoverComponents must not execute *.component.ts unsandboxed when --sandbox is passed", () => {
|
|
47
|
+
let testDir: string;
|
|
48
|
+
let escapedMarkerPath: string;
|
|
49
|
+
|
|
50
|
+
beforeEach(async () => {
|
|
51
|
+
testDir = await realpath(
|
|
52
|
+
await (async () => {
|
|
53
|
+
const dir = join(tmpdir(), `chant-component-security-boundary-test-${Date.now()}-${Math.random()}`);
|
|
54
|
+
await mkdir(dir, { recursive: true });
|
|
55
|
+
return dir;
|
|
56
|
+
})(),
|
|
57
|
+
);
|
|
58
|
+
// Outside the project directory chant scans — the sandbox's
|
|
59
|
+
// `--allow-fs-write` allowance (there isn't one) and `--allow-fs-read`
|
|
60
|
+
// scope (project dir + bundle dir only) must both reject this.
|
|
61
|
+
escapedMarkerPath = join(testDir, "..", `chant-component-escaped-${Date.now()}.txt`);
|
|
62
|
+
|
|
63
|
+
await writeFile(join(testDir, "chant.config.ts"), "export default {};\n");
|
|
64
|
+
|
|
65
|
+
// A hostile component file: attempts a filesystem read outside the
|
|
66
|
+
// project, a filesystem write outside the project, and a child-process
|
|
67
|
+
// spawn, all at module top level (before its one real Component export),
|
|
68
|
+
// wrapping each attempt so a permission denial never throws discovery
|
|
69
|
+
// into "import error" — every signal is folded into the component's own
|
|
70
|
+
// `name`, which is what `chant list --components --format json` prints
|
|
71
|
+
// to STDOUT, so suppressed child stderr/console output cannot fake a
|
|
72
|
+
// pass here.
|
|
73
|
+
await writeFile(
|
|
74
|
+
join(testDir, "evil.component.ts"),
|
|
75
|
+
[
|
|
76
|
+
'import { readFileSync, writeFileSync } from "node:fs";',
|
|
77
|
+
'import { execSync } from "node:child_process";',
|
|
78
|
+
"",
|
|
79
|
+
"function attempt(label, fn) {",
|
|
80
|
+
" try {",
|
|
81
|
+
" fn();",
|
|
82
|
+
' return label + "OK";',
|
|
83
|
+
" } catch (err) {",
|
|
84
|
+
' const code = err && err.code ? String(err.code).replace(/[^A-Za-z0-9]/g, "") : "ERR";',
|
|
85
|
+
" return label + code;",
|
|
86
|
+
" }",
|
|
87
|
+
"}",
|
|
88
|
+
"",
|
|
89
|
+
`const readTag = attempt("read", () => { readFileSync("/etc/hosts", "utf-8"); });`,
|
|
90
|
+
`const writeTag = attempt("write", () => { writeFileSync(${JSON.stringify(escapedMarkerPath)}, "pwned"); });`,
|
|
91
|
+
`const spawnTag = attempt("spawn", () => { execSync("true"); });`,
|
|
92
|
+
"const envCount = Object.keys(process.env).length;",
|
|
93
|
+
"",
|
|
94
|
+
"export const probe = {",
|
|
95
|
+
" name: `probe-${readTag}-${writeTag}-${spawnTag}-env${envCount}`,",
|
|
96
|
+
" dependsOn: [],",
|
|
97
|
+
' deploy: [{ phase: "Apply", steps: [{ kind: "shell", command: "echo ok" }] }],',
|
|
98
|
+
"};",
|
|
99
|
+
"",
|
|
100
|
+
].join("\n"),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
afterEach(async () => {
|
|
105
|
+
await rm(testDir, { recursive: true, force: true });
|
|
106
|
+
await rm(escapedMarkerPath, { force: true });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/** Spawn the real CLI (`main.ts`, exactly as `bin/chant` invokes it via `npx tsx`) and capture its output. */
|
|
110
|
+
function runCli(args: string[]): { status: number | null; stdout: string; stderr: string } {
|
|
111
|
+
const result = spawnSync("npx", ["tsx", mainTsPath, ...args], {
|
|
112
|
+
cwd: repoRoot,
|
|
113
|
+
encoding: "utf-8",
|
|
114
|
+
timeout: 30_000,
|
|
115
|
+
env: { ...process.env, CHANT_SECRET_FOR_TEST: "should-never-leak" },
|
|
116
|
+
});
|
|
117
|
+
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Pull the discovered component's `name` field out of `chant list --components --format json`'s stdout. */
|
|
121
|
+
function probeNameFrom(stdout: string): string {
|
|
122
|
+
const parsed = JSON.parse(stdout) as Array<{ name: string }>;
|
|
123
|
+
const found = parsed.find((c) => c.name.startsWith("probe-"));
|
|
124
|
+
expect(found, `expected a "probe-*" component in stdout: ${stdout}`).toBeDefined();
|
|
125
|
+
return found!.name;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
test("without --sandbox, the hostile *.component.ts file executes fully (establishes the fixture is genuinely hostile)", () => {
|
|
129
|
+
const { status, stdout } = runCli(["list", testDir, "--components", "--format", "json"]);
|
|
130
|
+
expect(status).toBe(0);
|
|
131
|
+
|
|
132
|
+
const name = probeNameFrom(stdout);
|
|
133
|
+
expect(name).toMatch(/^probe-readOK-writeOK-spawnOK-env\d+$/);
|
|
134
|
+
|
|
135
|
+
// A real env var count, not the sandbox's scrubbed-to-just-PATH count.
|
|
136
|
+
const envCount = Number(name.match(/env(\d+)$/)![1]);
|
|
137
|
+
expect(envCount).toBeGreaterThan(2);
|
|
138
|
+
|
|
139
|
+
expect(existsSync(escapedMarkerPath), "hostile file's write should have landed unsandboxed").toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("with --sandbox, the hostile *.component.ts file is denied filesystem read/write, process spawn, and sees a scrubbed environment", () => {
|
|
143
|
+
const { status, stdout } = runCli(["list", testDir, "--components", "--format", "json", "--sandbox"]);
|
|
144
|
+
expect(status).toBe(0);
|
|
145
|
+
|
|
146
|
+
const name = probeNameFrom(stdout);
|
|
147
|
+
expect(name).toMatch(/^probe-readERRACCESSDENIED-writeERRACCESSDENIED-spawnERRACCESSDENIED-env\d+$/);
|
|
148
|
+
|
|
149
|
+
// Only PATH (deliberately kept, see `./sandbox/run.ts`) plus whatever the
|
|
150
|
+
// OS itself injects into every child regardless of the `env` passed to
|
|
151
|
+
// `fork()` (macOS adds `__CF_USER_TEXT_ENCODING`) survive the spawn-time
|
|
152
|
+
// env scrub — a small constant, nowhere near the ambient environment's
|
|
153
|
+
// real size. Mirrors the #1045 PR's own reported number for this exact
|
|
154
|
+
// shape of check ("blocked-erraccessdenied-env2").
|
|
155
|
+
const envCount = Number(name.match(/env(\d+)$/)![1]);
|
|
156
|
+
expect(envCount).toBeLessThanOrEqual(2);
|
|
157
|
+
|
|
158
|
+
expect(existsSync(escapedMarkerPath), "sandboxed write must not have landed").toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("`chant lint --sandbox` (COMP* checks) also does not let the hostile file's write land", () => {
|
|
162
|
+
const { status } = runCli(["lint", testDir, "--sandbox", "--format", "json"]);
|
|
163
|
+
// Not asserting on exit code (COMP* rules may or may not flag this
|
|
164
|
+
// fixture) — the write escaping the sandbox is the actual property
|
|
165
|
+
// under test, matching how `./security-boundary.test.ts` treats `chant
|
|
166
|
+
// lint`'s coverage of the same discovery path.
|
|
167
|
+
expect(status === 0 || status === 1).toBe(true);
|
|
168
|
+
expect(existsSync(escapedMarkerPath)).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -21,9 +21,12 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
|
|
|
21
21
|
// plugins (those implementing `generateComponentPipeline`, #688), not a
|
|
22
22
|
// hard-coded core list — `generateComponentsPipeline` returns a descriptive
|
|
23
23
|
// error when the target lexicon has no generator.
|
|
24
|
-
const result = await generateComponentsPipeline(
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const result = await generateComponentsPipeline(
|
|
25
|
+
args.path,
|
|
26
|
+
lexicon,
|
|
27
|
+
{ env: args.env },
|
|
28
|
+
args.sandbox,
|
|
29
|
+
);
|
|
27
30
|
|
|
28
31
|
if (!result.success) {
|
|
29
32
|
console.error(formatError({ message: result.error ?? "Failed to generate CI pipeline from components" }));
|
|
@@ -83,6 +86,16 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
|
|
|
83
86
|
console.error(formatInfo(formatWarningMsg));
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
// #1064 — `--param name=value`, repeated, into a flat { name: value } record.
|
|
90
|
+
const params = args.param?.length
|
|
91
|
+
? Object.fromEntries(
|
|
92
|
+
args.param.map((entry) => {
|
|
93
|
+
const eq = entry.indexOf("=");
|
|
94
|
+
return eq === -1 ? [entry, ""] : [entry.slice(0, eq), entry.slice(eq + 1)];
|
|
95
|
+
}),
|
|
96
|
+
)
|
|
97
|
+
: undefined;
|
|
98
|
+
|
|
86
99
|
if (args.watch) {
|
|
87
100
|
const cleanup = buildCommandWatch({
|
|
88
101
|
path: args.path,
|
|
@@ -90,6 +103,10 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
|
|
|
90
103
|
format: buildFormat,
|
|
91
104
|
serializers,
|
|
92
105
|
plugins,
|
|
106
|
+
fold: args.fold,
|
|
107
|
+
sandbox: args.sandbox,
|
|
108
|
+
params,
|
|
109
|
+
paramsFile: args.paramsFile,
|
|
93
110
|
});
|
|
94
111
|
process.on("SIGINT", () => {
|
|
95
112
|
cleanup();
|
|
@@ -107,6 +124,10 @@ export async function runBuild(ctx: CommandContext): Promise<number> {
|
|
|
107
124
|
plugins,
|
|
108
125
|
verbose: args.verbose,
|
|
109
126
|
env: args.env,
|
|
127
|
+
fold: args.fold,
|
|
128
|
+
sandbox: args.sandbox,
|
|
129
|
+
params,
|
|
130
|
+
paramsFile: args.paramsFile,
|
|
110
131
|
});
|
|
111
132
|
|
|
112
133
|
// When --lexicon filters to a subset, suppress "No serializer" warnings for excluded lexicons
|
|
@@ -79,7 +79,7 @@ export async function runComponentsReleaseRecord(ctx: CommandContext): Promise<n
|
|
|
79
79
|
if (!component || !digest) {
|
|
80
80
|
console.error(formatError({
|
|
81
81
|
message: "--component and --digest are required",
|
|
82
|
-
hint: "chant components release <env> --component <name> --digest <sha256:...> [--git-sha <sha>] [--run-id <id>] [--actor <name>]",
|
|
82
|
+
hint: "chant components release <env> --component <name> --digest <sha256:...> [--git-sha <sha>] [--run-id <id>] [--actor <name>] [--approver <name>]",
|
|
83
83
|
}));
|
|
84
84
|
return 1;
|
|
85
85
|
}
|
|
@@ -87,6 +87,10 @@ export async function runComponentsReleaseRecord(ctx: CommandContext): Promise<n
|
|
|
87
87
|
const gitSha = args.gitSha ?? (await getHeadCommit().catch(() => undefined));
|
|
88
88
|
const runId = args.runId ?? process.env.GITHUB_RUN_ID ?? process.env.CI_PIPELINE_ID ?? `local-${Date.now()}`;
|
|
89
89
|
const actor = args.actor ?? process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_LOGIN ?? process.env.USER;
|
|
90
|
+
// Who approved a gated change (#1035). Optional — omitted for an ungated
|
|
91
|
+
// change, and never defaulted to `actor`, since recording the approver as the
|
|
92
|
+
// triggerer would defeat the separation-of-duties the field exists to attest.
|
|
93
|
+
const approver = args.approver;
|
|
90
94
|
|
|
91
95
|
if (!gitSha) {
|
|
92
96
|
console.error(formatError({ message: "Could not resolve --git-sha (not in a git repo?) — pass it explicitly." }));
|
|
@@ -111,6 +115,7 @@ export async function runComponentsReleaseRecord(ctx: CommandContext): Promise<n
|
|
|
111
115
|
runId,
|
|
112
116
|
timestamp,
|
|
113
117
|
actor,
|
|
118
|
+
...(approver ? { approver } : {}),
|
|
114
119
|
});
|
|
115
120
|
await pushLifecycle();
|
|
116
121
|
if (args.json) {
|
|
@@ -277,7 +282,9 @@ export async function runComponentsStatus(ctx: CommandContext): Promise<number>
|
|
|
277
282
|
// Discover components (best-effort) so a live/owned-but-never-recorded
|
|
278
283
|
// component still gets a row, not just those that appear in the ledger.
|
|
279
284
|
const { config } = await loadChantConfig(resolve("."));
|
|
280
|
-
const discovery = await discoverComponents(resolve(args.src ?? config.sourceDir ?? ".")
|
|
285
|
+
const discovery = await discoverComponents(resolve(args.src ?? config.sourceDir ?? "."), {
|
|
286
|
+
sandbox: args.sandbox,
|
|
287
|
+
});
|
|
281
288
|
const allComponents = [...discovery.components.keys()];
|
|
282
289
|
|
|
283
290
|
// Component -> live entity/resource name(s) it owns (#598), when declared
|
package/src/cli/handlers/dev.ts
CHANGED
|
@@ -41,7 +41,7 @@ export async function runDevOnboard(ctx: CommandContext): Promise<number> {
|
|
|
41
41
|
export async function runDevCheckLexicon(ctx: CommandContext): Promise<number> {
|
|
42
42
|
const dir = ctx.args.extraPositional ?? ".";
|
|
43
43
|
const { checkLexicon, printCheckResult } = await import("../commands/check-lexicon");
|
|
44
|
-
const result = checkLexicon(resolve(dir));
|
|
44
|
+
const result = await checkLexicon(resolve(dir));
|
|
45
45
|
printCheckResult(result, ctx.args.format === "json");
|
|
46
46
|
return result.tier1Pass ? 0 : 1;
|
|
47
47
|
}
|
|
@@ -109,7 +109,9 @@ async function runGraphLive(
|
|
|
109
109
|
// and unions the results. A project with no components (or whose discovery
|
|
110
110
|
// errors) yields no stacks, so `observeResources` falls back to its
|
|
111
111
|
// original single-stack call — unchanged.
|
|
112
|
-
const componentsDiscovery = await discoverComponents(resolve(args.src ?? config.sourceDir ?? ".")
|
|
112
|
+
const componentsDiscovery = await discoverComponents(resolve(args.src ?? config.sourceDir ?? "."), {
|
|
113
|
+
sandbox: args.sandbox,
|
|
114
|
+
});
|
|
113
115
|
const stacks = new Set<string>();
|
|
114
116
|
if (componentsDiscovery.errors.length === 0) {
|
|
115
117
|
for (const { component } of componentsDiscovery.components.values()) {
|
|
@@ -207,7 +209,7 @@ async function runGraphLive(
|
|
|
207
209
|
*/
|
|
208
210
|
async function runComponentGraph(ctx: CommandContext): Promise<number> {
|
|
209
211
|
const projectPath = resolve(ctx.args.path === "." ? "." : ctx.args.path);
|
|
210
|
-
const graph = await computeComponentGraph(projectPath);
|
|
212
|
+
const graph = await computeComponentGraph(projectPath, ctx.args.sandbox);
|
|
211
213
|
|
|
212
214
|
if (!graph.success) {
|
|
213
215
|
console.error(formatError({ message: graph.error ?? "Failed to compute component graph" }));
|
|
@@ -254,7 +256,7 @@ async function runComponentGraphView(
|
|
|
254
256
|
): Promise<number> {
|
|
255
257
|
const projectPath = resolve(ctx.args.path === "." ? "." : ctx.args.path);
|
|
256
258
|
|
|
257
|
-
const lint = await lintCommand({ path: ctx.args.path, format: "stylish" });
|
|
259
|
+
const lint = await lintCommand({ path: ctx.args.path, format: "stylish", sandbox: ctx.args.sandbox });
|
|
258
260
|
if (!lint.success) {
|
|
259
261
|
console.error(
|
|
260
262
|
formatError({
|
|
@@ -264,7 +266,7 @@ async function runComponentGraphView(
|
|
|
264
266
|
return 1;
|
|
265
267
|
}
|
|
266
268
|
|
|
267
|
-
const graph = await computeComponentGraph(projectPath);
|
|
269
|
+
const graph = await computeComponentGraph(projectPath, ctx.args.sandbox);
|
|
268
270
|
if (!graph.success) {
|
|
269
271
|
console.error(formatError({ message: graph.error ?? "Failed to compute component graph" }));
|
|
270
272
|
return 1;
|
|
@@ -316,7 +318,7 @@ async function runGraphView(
|
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
// Gate: only emit for lint-clean source.
|
|
319
|
-
const lint = await lintCommand({ path: ctx.args.path, format: "stylish" });
|
|
321
|
+
const lint = await lintCommand({ path: ctx.args.path, format: "stylish", sandbox: ctx.args.sandbox });
|
|
320
322
|
if (!lint.success) {
|
|
321
323
|
console.error(
|
|
322
324
|
formatError({
|
|
@@ -286,7 +286,7 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
|
|
|
286
286
|
let componentStacks: string[] = [];
|
|
287
287
|
if (!target.stack) {
|
|
288
288
|
try {
|
|
289
|
-
const disc = await discoverComponents(target.root);
|
|
289
|
+
const disc = await discoverComponents(target.root, { sandbox: args.sandbox });
|
|
290
290
|
const set = new Set<string>();
|
|
291
291
|
for (const { component } of disc.components.values()) for (const s of cfnDeployStacks(component.deploy)) set.add(s);
|
|
292
292
|
componentStacks = [...set];
|
|
@@ -698,7 +698,7 @@ export async function runLifecyclePlan(ctx: CommandContext): Promise<number> {
|
|
|
698
698
|
// single-stack project → the original single-call path is kept.
|
|
699
699
|
let componentStacks: string[] = [];
|
|
700
700
|
try {
|
|
701
|
-
const disc = await discoverComponents(resolveBuildRoot(args, config));
|
|
701
|
+
const disc = await discoverComponents(resolveBuildRoot(args, config), { sandbox: args.sandbox });
|
|
702
702
|
const set = new Set<string>();
|
|
703
703
|
for (const { component } of disc.components.values()) for (const s of cfnDeployStacks(component.deploy)) set.add(s);
|
|
704
704
|
componentStacks = [...set];
|
package/src/cli/handlers/lint.ts
CHANGED
|
@@ -16,6 +16,7 @@ export async function runLint(ctx: CommandContext): Promise<number> {
|
|
|
16
16
|
path: args.path,
|
|
17
17
|
fix: args.fix,
|
|
18
18
|
format: lintFormat,
|
|
19
|
+
sandbox: args.sandbox,
|
|
19
20
|
});
|
|
20
21
|
process.on("SIGINT", () => {
|
|
21
22
|
cleanup();
|
|
@@ -29,6 +30,7 @@ export async function runLint(ctx: CommandContext): Promise<number> {
|
|
|
29
30
|
path: args.path,
|
|
30
31
|
fix: args.fix,
|
|
31
32
|
format: lintFormat,
|
|
33
|
+
sandbox: args.sandbox,
|
|
32
34
|
});
|
|
33
35
|
|
|
34
36
|
printLintResult(result);
|
package/src/cli/handlers/misc.ts
CHANGED
|
@@ -92,7 +92,7 @@ export async function runList(ctx: CommandContext): Promise<number> {
|
|
|
92
92
|
// `lexicon`/`entityType`), so it is a separate branch rather than folded
|
|
93
93
|
// into `listCommand`'s Declarable-shaped `ListEntity` rows.
|
|
94
94
|
if (args.components) {
|
|
95
|
-
const result = await listComponents(args.path);
|
|
95
|
+
const result = await listComponents(args.path, args.sandbox);
|
|
96
96
|
if (!result.success) {
|
|
97
97
|
for (const e of result.errors) console.error(formatError({ message: e }));
|
|
98
98
|
return 1;
|
|
@@ -143,7 +143,7 @@ export async function runDescribe(ctx: CommandContext): Promise<number> {
|
|
|
143
143
|
// `Component` (its full JSON contract projection) rather than a lexicon
|
|
144
144
|
// resource's resolved props bag.
|
|
145
145
|
if (args.components) {
|
|
146
|
-
const result = await describeComponent(args.extraPositional ?? ".", component);
|
|
146
|
+
const result = await describeComponent(args.extraPositional ?? ".", component, args.sandbox);
|
|
147
147
|
if (!result.success || !result.described) {
|
|
148
148
|
console.log(result.output);
|
|
149
149
|
return 1;
|
|
@@ -29,7 +29,7 @@ export interface WorkflowHandleRaw {
|
|
|
29
29
|
result(): Promise<unknown>;
|
|
30
30
|
describe(): Promise<WorkflowExecutionDescription>;
|
|
31
31
|
fetchHistory(): Promise<WorkflowHistoryRaw>;
|
|
32
|
-
signal(signalName: string): Promise<void>;
|
|
32
|
+
signal(signalName: string, ...args: unknown[]): Promise<void>;
|
|
33
33
|
cancel(): Promise<void>;
|
|
34
34
|
}
|
|
35
35
|
|
package/src/cli/handlers/run.ts
CHANGED
|
@@ -176,7 +176,7 @@ async function runComponentsList(ctx: CommandContext): Promise<number> {
|
|
|
176
176
|
if (!requireTemporalMode(ctx, "chant run list --components")) return 1;
|
|
177
177
|
|
|
178
178
|
const projectPath = resolve(".");
|
|
179
|
-
const result = await listComponents(projectPath);
|
|
179
|
+
const result = await listComponents(projectPath, ctx.args.sandbox);
|
|
180
180
|
|
|
181
181
|
if (!result.success) {
|
|
182
182
|
for (const err of result.errors) console.error(formatError({ message: err }));
|
|
@@ -442,17 +442,27 @@ export async function runOpSignal(ctx: CommandContext): Promise<number> {
|
|
|
442
442
|
|
|
443
443
|
const projectPath = resolve(".");
|
|
444
444
|
const workflowId = ctx.args.components ? componentWorkflowId(name) : resolveWorkflowId(name);
|
|
445
|
+
// Approver identity for an approval gate (#1035): supplied via --approver, or
|
|
446
|
+
// resolved from the CI/user environment the same way `chant components
|
|
447
|
+
// release` resolves --actor. Rides the gate signal payload so Temporal
|
|
448
|
+
// persists "who approved" in the workflow history. Optional — a signal that
|
|
449
|
+
// is not an approval gate (or an approver who declines to identify) sends no
|
|
450
|
+
// payload and the gate still clears.
|
|
451
|
+
const approver = ctx.args.approver ?? process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_LOGIN ?? process.env.USER;
|
|
445
452
|
let handle: WorkflowHandleRaw;
|
|
446
453
|
try {
|
|
447
454
|
const { client } = await makeTemporalClient(ctx.args.profile, projectPath);
|
|
448
455
|
handle = client.workflow.getHandle(workflowId);
|
|
449
|
-
await handle.signal(signalName);
|
|
456
|
+
await handle.signal(signalName, ...(approver ? [{ approver }] : []));
|
|
450
457
|
} catch (err) {
|
|
451
458
|
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
452
459
|
return 1;
|
|
453
460
|
}
|
|
454
461
|
|
|
455
|
-
console.error(formatSuccess(
|
|
462
|
+
console.error(formatSuccess(
|
|
463
|
+
`Signal "${signalName}" sent to ${ctx.args.components ? "component" : "Op"} "${name}"` +
|
|
464
|
+
(approver ? ` (approver: ${approver})` : ""),
|
|
465
|
+
));
|
|
456
466
|
return 0;
|
|
457
467
|
}
|
|
458
468
|
|
|
@@ -694,7 +704,12 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
694
704
|
// `undefined` and every `onProgress?.(...)` call in the driver is a no-op —
|
|
695
705
|
// behavior is byte-for-byte unchanged from before this flag existed.
|
|
696
706
|
const onProgress = ctx.args.progressJson ? ndjsonProgressSink() : undefined;
|
|
697
|
-
const result = await runComponents(resolve("."), selector, {
|
|
707
|
+
const result = await runComponents(resolve("."), selector, {
|
|
708
|
+
env: ctx.args.env,
|
|
709
|
+
componentOutputs: seededOutputs,
|
|
710
|
+
onProgress,
|
|
711
|
+
sandbox: ctx.args.sandbox,
|
|
712
|
+
});
|
|
698
713
|
|
|
699
714
|
// Dump the accumulated outputs for a downstream job to seed from. Written
|
|
700
715
|
// even on failure (partial outputs) so a resumed run still has what completed.
|
|
@@ -767,7 +782,7 @@ async function runComponentTemporal(ctx: CommandContext, selector: string): Prom
|
|
|
767
782
|
}
|
|
768
783
|
|
|
769
784
|
const projectPath = resolve(".");
|
|
770
|
-
const resolved = await resolveComponentTargets(projectPath, selector);
|
|
785
|
+
const resolved = await resolveComponentTargets(projectPath, selector, ctx.args.sandbox);
|
|
771
786
|
if (!resolved.success || resolved.targets.length === 0) {
|
|
772
787
|
console.error(formatError({ message: resolved.error ?? `Component "${selector}" not found` }));
|
|
773
788
|
return 1;
|
package/src/cli/main.test.ts
CHANGED
|
@@ -248,6 +248,28 @@ describe("parseArgs", () => {
|
|
|
248
248
|
const result = parseArgs(["run", "--components", "search-service"]);
|
|
249
249
|
expect(result.progressJson).toBeUndefined();
|
|
250
250
|
});
|
|
251
|
+
|
|
252
|
+
// ── --param / --params-file (chant #1064) ────────────────────────────────
|
|
253
|
+
|
|
254
|
+
test("parses a single --param as a one-element array", () => {
|
|
255
|
+
const result = parseArgs(["build", "src", "--param", "tier=production"]);
|
|
256
|
+
expect(result.param).toEqual(["tier=production"]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("repeated --param accumulates in order", () => {
|
|
260
|
+
const result = parseArgs(["build", "src", "--param", "tier=production", "--param", "env=staging"]);
|
|
261
|
+
expect(result.param).toEqual(["tier=production", "env=staging"]);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("--param is undefined when omitted", () => {
|
|
265
|
+
const result = parseArgs(["build", "src"]);
|
|
266
|
+
expect(result.param).toBeUndefined();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("parses --params-file with a path", () => {
|
|
270
|
+
const result = parseArgs(["build", "src", "--params-file", "./params.json"]);
|
|
271
|
+
expect(result.paramsFile).toBe("./params.json");
|
|
272
|
+
});
|
|
251
273
|
});
|
|
252
274
|
|
|
253
275
|
// ── resolveCommand tests ──────────────────────────────────────────
|
package/src/cli/main.ts
CHANGED
|
@@ -45,6 +45,8 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
45
45
|
verbose: false,
|
|
46
46
|
help: false,
|
|
47
47
|
profile: undefined,
|
|
48
|
+
param: undefined,
|
|
49
|
+
paramsFile: undefined,
|
|
48
50
|
report: undefined,
|
|
49
51
|
local: undefined,
|
|
50
52
|
temporal: undefined,
|
|
@@ -210,10 +212,20 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
210
212
|
result.runId = args[++i];
|
|
211
213
|
} else if (arg === "--actor") {
|
|
212
214
|
result.actor = args[++i];
|
|
215
|
+
} else if (arg === "--approver") {
|
|
216
|
+
result.approver = args[++i];
|
|
213
217
|
} else if (arg === "--compare-to") {
|
|
214
218
|
result.compareTo = args[++i];
|
|
215
219
|
} else if (arg === "--no-release-record") {
|
|
216
220
|
result.noReleaseRecord = true;
|
|
221
|
+
} else if (arg === "--fold") {
|
|
222
|
+
result.fold = true;
|
|
223
|
+
} else if (arg === "--sandbox") {
|
|
224
|
+
result.sandbox = true;
|
|
225
|
+
} else if (arg === "--param") {
|
|
226
|
+
(result.param ??= []).push(args[++i]);
|
|
227
|
+
} else if (arg === "--params-file") {
|
|
228
|
+
result.paramsFile = args[++i];
|
|
217
229
|
} else if (!arg.startsWith("-")) {
|
|
218
230
|
if (!result.command) {
|
|
219
231
|
result.command = arg;
|
|
@@ -403,12 +415,39 @@ Options:
|
|
|
403
415
|
successful \`run --components\` deploy (default: on;
|
|
404
416
|
also settable via chant.config.ts's
|
|
405
417
|
release.autoRecord: false; #597)
|
|
418
|
+
--fold (build) Fold source modules statically instead of
|
|
419
|
+
running them; folds resource constructors and
|
|
420
|
+
composite factory calls (#1022/#1023), falling back
|
|
421
|
+
to run per-file for anything else outside the fold
|
|
422
|
+
subset (a cross-file-only reference, a re-export,
|
|
423
|
+
\`export default\`, ...). Logs which path each file
|
|
424
|
+
took. Default: off (also settable via
|
|
425
|
+
chant.config.ts's build.fold: true; #1022)
|
|
426
|
+
--sandbox (build) Run run-fallback source files (or every
|
|
427
|
+
file, without --fold) together, isolated, in one
|
|
428
|
+
sandboxed child process instead of in-process
|
|
429
|
+
(#1045). No filesystem write, no child process, no
|
|
430
|
+
worker threads, no ambient environment visible to
|
|
431
|
+
project source; network egress is NOT blocked (see
|
|
432
|
+
docs). Default: off (also settable via
|
|
433
|
+
chant.config.ts's build.sandbox: true; #1045)
|
|
434
|
+
--param <name=value> (build) Bind a declared build-time parameter
|
|
435
|
+
(chant.config.ts's buildParams) to a value, for
|
|
436
|
+
source to read as params.<name> (#1064) instead of
|
|
437
|
+
process.env — repeatable. Distinct from the AWS
|
|
438
|
+
lexicon's deploy-time Parameter(): this resolves
|
|
439
|
+
before synthesis, so it can change which resources
|
|
440
|
+
are produced at all. Highest precedence.
|
|
441
|
+
--params-file <path> (build) JSON file of { "name": value } build-time
|
|
442
|
+
parameter values (#1064). Second precedence, after
|
|
443
|
+
--param.
|
|
406
444
|
|
|
407
445
|
Examples:
|
|
408
446
|
chant build ./infra/
|
|
409
447
|
chant build ./infra/ --output stack.json
|
|
410
448
|
chant build ./infra/ --format yaml
|
|
411
449
|
chant build ./infra/ --watch
|
|
450
|
+
chant build ./infra/ --fold
|
|
412
451
|
chant build ./infra/ --components --generate gitlab
|
|
413
452
|
chant build ./infra/ --components --generate gitlab --output .gitlab-ci.yml
|
|
414
453
|
chant run --components search-service --env staging
|
package/src/cli/plugins.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isLexiconPlugin, type LexiconPlugin } from "../lexicon";
|
|
2
2
|
import { loadChantConfig } from "../config";
|
|
3
|
-
import {
|
|
3
|
+
import { findInfraFiles, detectLexicons } from "../index";
|
|
4
4
|
import { checkConflicts } from "./conflict-check";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -80,6 +80,22 @@ export async function loadPlugins(lexiconNames: string[]): Promise<LexiconPlugin
|
|
|
80
80
|
*
|
|
81
81
|
* Reads `lexicons` from `chant.config.ts` / `chant.config.json` if present.
|
|
82
82
|
* Falls back to source-file detection via `detectLexicons()`.
|
|
83
|
+
*
|
|
84
|
+
* chant #1045 Phase 2 — this used to fall back to a full `discover()` call
|
|
85
|
+
* just to read its `sourceFiles` list, which imports and RUNS every project
|
|
86
|
+
* source file to collect entities that are then discarded here. That ran
|
|
87
|
+
* unconditionally, on every command that reaches this function without an
|
|
88
|
+
* explicit `chant.config.ts` `lexicons` list (`build`, `lint`, `doctor`,
|
|
89
|
+
* `import`, `graph`, the MCP server — effectively the whole CLI), with no
|
|
90
|
+
* `--fold`/`--sandbox` involved at all: a project could omit `lexicons` and
|
|
91
|
+
* every file would execute here, in the CLI's own process, before the
|
|
92
|
+
* caller's OWN (possibly folded, possibly sandboxed) build ever ran. Fixed
|
|
93
|
+
* by using `findInfraFiles()` alone — the pure directory walk `discover()`
|
|
94
|
+
* itself starts from, no import, no execution — since `detectLexicons()` is
|
|
95
|
+
* already a plain text/regex scan over file contents (`../detectLexicon.ts`)
|
|
96
|
+
* that never needed live module exports to begin with. This is strictly
|
|
97
|
+
* better than routing the detection through the sandbox: it removes the
|
|
98
|
+
* execution entirely rather than containing it, at no bundling/spawn cost.
|
|
83
99
|
*/
|
|
84
100
|
export async function resolveProjectLexicons(projectPath: string): Promise<string[]> {
|
|
85
101
|
const { config } = await loadChantConfig(projectPath);
|
|
@@ -88,7 +104,7 @@ export async function resolveProjectLexicons(projectPath: string): Promise<strin
|
|
|
88
104
|
return config.lexicons;
|
|
89
105
|
}
|
|
90
106
|
|
|
91
|
-
// Fallback: detect from source imports
|
|
92
|
-
const
|
|
93
|
-
return detectLexicons(
|
|
107
|
+
// Fallback: detect from source imports — a pure text scan, no execution.
|
|
108
|
+
const files = await findInfraFiles(projectPath);
|
|
109
|
+
return detectLexicons(files);
|
|
94
110
|
}
|
package/src/cli/registry.ts
CHANGED
|
@@ -136,6 +136,8 @@ export interface ParsedArgs {
|
|
|
136
136
|
runId?: string;
|
|
137
137
|
/** `chant components release record --actor <name>` (#568) — who/what triggered the deploy. */
|
|
138
138
|
actor?: string;
|
|
139
|
+
/** `--approver <name>` (#1035) — who approved a gated change. Supplied to `chant run signal` (rides the gate signal payload into workflow history) and to `chant components release` (recorded on the release ledger). Optional; absent for ungated changes. */
|
|
140
|
+
approver?: string;
|
|
139
141
|
/** `chant components status <env> --compare-to <env>` (#568) — a second environment to cross-check the same component's recorded digest against. */
|
|
140
142
|
compareTo?: string;
|
|
141
143
|
/** `chant run --components <name> --env <env> --no-release-record` (#597) — opt out of auto-emitting a release-ledger record after a successful component deploy. Default (flag omitted): recording is ON. Also settable project-wide via `chant.config.ts`'s `release.autoRecord: false`. */
|
|
@@ -144,6 +146,14 @@ export interface ParsedArgs {
|
|
|
144
146
|
dumpOutputs?: string;
|
|
145
147
|
/** `chant run --components <name> --seed-outputs <file>` (repeatable) — before the run, load each JSON outputs file (as written by `--dump-outputs`) and seed cross-component/cross-stack resolution with it, so a `stackOutput()`/`@<dep>.publish.*` reference to a component that ran in an earlier job resolves. */
|
|
146
148
|
seedOutputs?: string[];
|
|
149
|
+
/** `chant build --fold` (#1022/#1023, epic #1019) — opt-in: fold source modules statically instead of importing/running them; folds resource constructors and composite factory calls, falling back to run per-file for anything the folder can't represent (a cross-file-only reference, a re-export, `export default`, …). Also settable project-wide via `chant.config.ts`'s `build.fold: true`; the flag always wins when set. Default (flag omitted): the existing run path, unchanged. */
|
|
150
|
+
fold?: boolean;
|
|
151
|
+
/** `chant build --sandbox` (#1045 Phase 2) — opt-in: run-fallback source files (or every file, without `--fold`) execute together, isolated, in one sandboxed child process instead of in-process. Also settable project-wide via `chant.config.ts`'s `build.sandbox: true`; the flag always wins when set. Default (flag omitted): in-process execution, unchanged. */
|
|
152
|
+
sandbox?: boolean;
|
|
153
|
+
/** `chant build --param name=value` (#1064) — repeatable. Bound to `params.<name>` (`@intentius/chant/params`) for source to reference, after validation against `chant.config.ts`'s declared `buildParams`. Highest precedence over `--params-file`/a declared `env` mapping/the declared `default`. */
|
|
154
|
+
param?: string[];
|
|
155
|
+
/** `chant build --params-file <path>` (#1064) — a JSON file of `{ "name": value }` build-time parameter values. Second precedence, after `--param`. */
|
|
156
|
+
paramsFile?: string;
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
/**
|