@intentius/chant 0.21.0 → 0.23.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.d.ts +7 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/build-params-cli.d.ts +55 -0
- package/dist/cli/build-params-cli.d.ts.map +1 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +14 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/lsp/server.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +33 -2
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts +28 -0
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +71 -9
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts +27 -4
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +39 -2
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lint/engine.d.ts +11 -1
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/rule.d.ts +14 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/serializer-walker.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/build.ts +9 -0
- package/src/cli/build-params-cli.test.ts +139 -0
- package/src/cli/build-params-cli.ts +107 -0
- package/src/cli/commands/build.ts +25 -36
- package/src/cli/commands/check-lexicon-examples.ts +16 -1
- package/src/cli/commands/lint.test.ts +74 -0
- package/src/cli/commands/lint.ts +33 -9
- package/src/cli/handlers/build.test.ts +147 -0
- package/src/cli/handlers/build.ts +23 -8
- package/src/cli/handlers/run.test.ts +160 -5
- package/src/cli/handlers/run.ts +46 -8
- package/src/cli/lsp/server.ts +7 -2
- package/src/components/cli-support.test.ts +221 -3
- package/src/components/cli-support.ts +37 -6
- package/src/components/discover.test.ts +63 -1
- package/src/components/discover.ts +42 -0
- package/src/discovery/fold-import.test.ts +328 -1
- package/src/discovery/fold-import.ts +414 -31
- package/src/discovery/index.test.ts +131 -0
- package/src/discovery/index.ts +53 -8
- package/src/discovery/sandbox/fold-boundary.test.ts +254 -0
- package/src/fold/fold.ts +6 -2
- package/src/fold/subset.test.ts +95 -15
- package/src/fold/subset.ts +46 -5
- package/src/lint/engine.ts +12 -0
- package/src/lint/rule.ts +14 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +39 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +1 -1
- package/src/serializer-walker.ts +14 -0
|
@@ -8,16 +8,45 @@
|
|
|
8
8
|
* rather than going through the CLI arg-parsing/handler dispatch layer.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
11
|
+
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
12
12
|
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
13
|
-
import { join } from "node:path";
|
|
13
|
+
import { join, dirname, resolve as resolvePath } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
14
15
|
import { tmpdir } from "node:os";
|
|
15
16
|
import Ajv2020 from "ajv/dist/2020";
|
|
16
17
|
import componentSchema from "./component.schema.json";
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
listComponents,
|
|
20
|
+
describeComponent,
|
|
21
|
+
computeComponentGraph,
|
|
22
|
+
runComponents,
|
|
23
|
+
findComponentGate,
|
|
24
|
+
resolveComponentTargets,
|
|
25
|
+
generateComponentsPipeline,
|
|
26
|
+
} from "./cli-support";
|
|
18
27
|
import { CapabilityRegistry, type DeployContext } from "./capability";
|
|
19
28
|
import type { DriverComponent, RunProgressEvent } from "./driver";
|
|
20
29
|
|
|
30
|
+
// A minimal stand-in for the real gitlab lexicon plugin, satisfying
|
|
31
|
+
// `isLexiconPlugin` (../lexicon.ts) — used only by the `generateComponentsPipeline`
|
|
32
|
+
// buildParams test below, so that test doesn't depend on how `@intentius/
|
|
33
|
+
// chant-lexicon-gitlab`'s own real module resolves under the test runner.
|
|
34
|
+
vi.mock("@intentius/chant-lexicon-gitlab", () => ({
|
|
35
|
+
gitlab: {
|
|
36
|
+
name: "gitlab",
|
|
37
|
+
serializer: { name: "gitlab", rulePrefix: "GL", serialize: () => "" },
|
|
38
|
+
generate: () => {},
|
|
39
|
+
validate: () => [],
|
|
40
|
+
coverage: () => ({ total: 0, covered: 0 }),
|
|
41
|
+
package: () => "gitlab",
|
|
42
|
+
generateComponentPipeline: (components: DriverComponent[]) => ({
|
|
43
|
+
yaml: components.map((c) => c.name).join(","),
|
|
44
|
+
stages: ["wave-1"],
|
|
45
|
+
jobs: components.map((c) => ({ jobName: c.name, component: c.name, stage: "wave-1", needs: [] })),
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
|
|
21
50
|
describe("listComponents", () => {
|
|
22
51
|
let testDir: string;
|
|
23
52
|
|
|
@@ -578,6 +607,195 @@ describe("runComponents", () => {
|
|
|
578
607
|
});
|
|
579
608
|
});
|
|
580
609
|
|
|
610
|
+
// ── build-time parameters (chant #1108) ──────────────────────────────────────
|
|
611
|
+
//
|
|
612
|
+
// Before #1108, `discoverComponents`'s callers here never resolved
|
|
613
|
+
// `chant.config.ts`'s declared `buildParams` at all, so a `*.component.ts`
|
|
614
|
+
// file reading `params.<name>` (`@intentius/chant/params`) always saw `{}` —
|
|
615
|
+
// the exact probe from chant#1108's reproduction. These tests lock in that
|
|
616
|
+
// `runComponents`/`resolveComponentTargets`/`generateComponentsPipeline` now
|
|
617
|
+
// forward an already-resolved `buildParams` into discovery, so a component's
|
|
618
|
+
// `params.<name>` read reflects it before any step dispatches.
|
|
619
|
+
|
|
620
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
621
|
+
const paramsModulePath = resolvePath(thisDir, "../params");
|
|
622
|
+
|
|
623
|
+
describe("runComponents — buildParams (chant #1108)", () => {
|
|
624
|
+
let testDir: string;
|
|
625
|
+
|
|
626
|
+
beforeEach(async () => {
|
|
627
|
+
testDir = join(tmpdir(), `chant-run-components-buildparams-test-${Date.now()}-${Math.random()}`);
|
|
628
|
+
await mkdir(testDir, { recursive: true });
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
afterEach(async () => {
|
|
632
|
+
await rm(testDir, { recursive: true, force: true });
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("the previously-{} case: a component's params.<name> read resolves when buildParams is passed", async () => {
|
|
636
|
+
await writeFile(
|
|
637
|
+
join(testDir, "svc.component.ts"),
|
|
638
|
+
`
|
|
639
|
+
import { params } from ${JSON.stringify(paramsModulePath)};
|
|
640
|
+
export const svc = {
|
|
641
|
+
name: "svc",
|
|
642
|
+
dependsOn: [],
|
|
643
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing", tier: params.tier }] }],
|
|
644
|
+
};
|
|
645
|
+
`,
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
const capability = fakeCapability("deploy-thing");
|
|
649
|
+
const registry = new CapabilityRegistry();
|
|
650
|
+
registry.register(capability);
|
|
651
|
+
|
|
652
|
+
const result = await runComponents(testDir, "svc", {
|
|
653
|
+
registry,
|
|
654
|
+
buildParams: [{ name: "tier", value: "production", source: "cli" }],
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
expect(result.success).toBe(true);
|
|
658
|
+
expect(capability.calls[0]?.input).toMatchObject({ tier: "production" });
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
test("without buildParams, params.<name> is undefined (the bug this issue fixes, still true for a caller that resolves none)", async () => {
|
|
662
|
+
await writeFile(
|
|
663
|
+
join(testDir, "svc.component.ts"),
|
|
664
|
+
`
|
|
665
|
+
import { params } from ${JSON.stringify(paramsModulePath)};
|
|
666
|
+
export const svc = {
|
|
667
|
+
name: "svc",
|
|
668
|
+
dependsOn: [],
|
|
669
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing", tier: params.tier ?? "unset" }] }],
|
|
670
|
+
};
|
|
671
|
+
`,
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
const capability = fakeCapability("deploy-thing");
|
|
675
|
+
const registry = new CapabilityRegistry();
|
|
676
|
+
registry.register(capability);
|
|
677
|
+
|
|
678
|
+
const result = await runComponents(testDir, "svc", { registry });
|
|
679
|
+
|
|
680
|
+
expect(result.success).toBe(true);
|
|
681
|
+
expect(capability.calls[0]?.input).toMatchObject({ tier: "unset" });
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test("RunComponentsResult.buildParams echoes back the resolved provenance that was passed in", async () => {
|
|
685
|
+
await writeFile(
|
|
686
|
+
join(testDir, "svc.component.ts"),
|
|
687
|
+
`export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`,
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
const registry = fakeRegistry();
|
|
691
|
+
const result = await runComponents(testDir, "svc", {
|
|
692
|
+
registry,
|
|
693
|
+
buildParams: [{ name: "tier", value: "production", source: "cli" }],
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
expect(result.buildParams).toEqual([{ name: "tier", value: "production", source: "cli" }]);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
test("selector 'all' also forwards buildParams into discovery", async () => {
|
|
700
|
+
await writeFile(
|
|
701
|
+
join(testDir, "svc.component.ts"),
|
|
702
|
+
`
|
|
703
|
+
import { params } from ${JSON.stringify(paramsModulePath)};
|
|
704
|
+
export const svc = { name: "svc", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing", tier: params.tier }] }] };
|
|
705
|
+
`,
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
const capability = fakeCapability("deploy-thing");
|
|
709
|
+
const registry = new CapabilityRegistry();
|
|
710
|
+
registry.register(capability);
|
|
711
|
+
|
|
712
|
+
const result = await runComponents(testDir, "all", {
|
|
713
|
+
registry,
|
|
714
|
+
buildParams: [{ name: "tier", value: "staging", source: "env" }],
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
expect(result.success).toBe(true);
|
|
718
|
+
expect(capability.calls[0]?.input).toMatchObject({ tier: "staging" });
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
describe("resolveComponentTargets — buildParams (chant #1108)", () => {
|
|
723
|
+
let testDir: string;
|
|
724
|
+
|
|
725
|
+
beforeEach(async () => {
|
|
726
|
+
testDir = join(tmpdir(), `chant-resolve-targets-buildparams-test-${Date.now()}-${Math.random()}`);
|
|
727
|
+
await mkdir(testDir, { recursive: true });
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
afterEach(async () => {
|
|
731
|
+
await rm(testDir, { recursive: true, force: true });
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("forwards buildParams into discovery — the durable (--temporal) path's entrypoint", async () => {
|
|
735
|
+
await writeFile(
|
|
736
|
+
join(testDir, "svc.component.ts"),
|
|
737
|
+
`
|
|
738
|
+
import { params } from ${JSON.stringify(paramsModulePath)};
|
|
739
|
+
export const svc = {
|
|
740
|
+
name: "svc",
|
|
741
|
+
dependsOn: [],
|
|
742
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy", stack: params.env }] }],
|
|
743
|
+
};
|
|
744
|
+
`,
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
const result = await resolveComponentTargets(testDir, "svc", undefined, [
|
|
748
|
+
{ name: "env", value: "prod-a", source: "env" },
|
|
749
|
+
]);
|
|
750
|
+
|
|
751
|
+
expect(result.success).toBe(true);
|
|
752
|
+
expect((result.targets[0].deploy[0].steps[0] as { stack?: unknown }).stack).toBe("prod-a");
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
describe("generateComponentsPipeline — buildParams (chant #1108)", () => {
|
|
757
|
+
let testDir: string;
|
|
758
|
+
|
|
759
|
+
beforeEach(async () => {
|
|
760
|
+
testDir = join(tmpdir(), `chant-generate-components-buildparams-test-${Date.now()}-${Math.random()}`);
|
|
761
|
+
await mkdir(testDir, { recursive: true });
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
afterEach(async () => {
|
|
765
|
+
await rm(testDir, { recursive: true, force: true });
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
test("forwards buildParams into discovery before synthesizing the pipeline", async () => {
|
|
769
|
+
await writeFile(
|
|
770
|
+
join(testDir, "svc.component.ts"),
|
|
771
|
+
`
|
|
772
|
+
import { params } from ${JSON.stringify(paramsModulePath)};
|
|
773
|
+
export const svc = {
|
|
774
|
+
name: String(params.name),
|
|
775
|
+
dependsOn: [],
|
|
776
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }],
|
|
777
|
+
};
|
|
778
|
+
`,
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
// Uses the mocked "gitlab" plugin above (see the `vi.mock` at the top of
|
|
782
|
+
// this file) — proves the component name `discoverComponents` resolved
|
|
783
|
+
// (via `params.name`) made it all the way through to the synthesized
|
|
784
|
+
// pipeline, not just to `discover()`'s own return value.
|
|
785
|
+
const result = await generateComponentsPipeline(
|
|
786
|
+
testDir,
|
|
787
|
+
"gitlab",
|
|
788
|
+
undefined,
|
|
789
|
+
undefined,
|
|
790
|
+
[{ name: "name", value: "named-from-params", source: "cli" }],
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
expect(result.success).toBe(true);
|
|
794
|
+
expect(result.jobs?.map((j) => j.component)).toEqual(["named-from-params"]);
|
|
795
|
+
expect(result.buildParams).toEqual([{ name: "name", value: "named-from-params", source: "cli" }]);
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
|
|
581
799
|
describe("runComponents — onProgress (--progress-json wiring, M3)", () => {
|
|
582
800
|
let testDir: string;
|
|
583
801
|
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { discoverComponents } from "./discover";
|
|
26
|
+
import type { BuildParamProvenance } from "../provenance";
|
|
26
27
|
import { projectToJson, type Archetype } from "./component";
|
|
27
28
|
import {
|
|
28
29
|
resolveComponentGraph,
|
|
@@ -185,6 +186,8 @@ export interface GenerateComponentsResult {
|
|
|
185
186
|
/** Every generated job, for a machine-readable view (`--format json`). */
|
|
186
187
|
jobs?: Array<{ jobName: string; component: string; stage: string; needs: string[] }>;
|
|
187
188
|
error?: string;
|
|
189
|
+
/** This invocation's resolved build-time parameters (chant #1108) — the generate-mode counterpart of `../cli/commands/build.ts`'s `BuildResult.buildParams`. Empty when the project declares/supplies none. */
|
|
190
|
+
buildParams?: BuildParamProvenance[];
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
/**
|
|
@@ -221,6 +224,15 @@ export async function generateComponentsPipeline(
|
|
|
221
224
|
lexicon: GenerateLexicon,
|
|
222
225
|
options?: ComponentPipelineOptions,
|
|
223
226
|
sandbox?: boolean,
|
|
227
|
+
/**
|
|
228
|
+
* chant #1108 — this invocation's resolved build-time parameter values
|
|
229
|
+
* (../cli/handlers/build.ts resolves them, the same sequence `chant build`
|
|
230
|
+
* runs, BEFORE calling this function), forwarded into discovery so a
|
|
231
|
+
* `params.<name>` reference inside a discovered `*.component.ts` file
|
|
232
|
+
* resolves instead of reading `{}`. See `discoverComponents`'s
|
|
233
|
+
* `buildParams` option (./discover.ts) for the full doc.
|
|
234
|
+
*/
|
|
235
|
+
buildParams?: BuildParamProvenance[],
|
|
224
236
|
): Promise<GenerateComponentsResult> {
|
|
225
237
|
const plugin = await loadLexiconPlugin(lexicon);
|
|
226
238
|
if (!plugin?.generateComponentPipeline) {
|
|
@@ -230,7 +242,7 @@ export async function generateComponentsPipeline(
|
|
|
230
242
|
};
|
|
231
243
|
}
|
|
232
244
|
|
|
233
|
-
const result = await discoverComponents(path, { sandbox });
|
|
245
|
+
const result = await discoverComponents(path, { sandbox, buildParams });
|
|
234
246
|
if (result.errors.length > 0) {
|
|
235
247
|
return { success: false, error: result.errors.map((e) => e.message).join("\n") };
|
|
236
248
|
}
|
|
@@ -243,7 +255,7 @@ export async function generateComponentsPipeline(
|
|
|
243
255
|
|
|
244
256
|
try {
|
|
245
257
|
const { yaml, stages, jobs } = plugin.generateComponentPipeline(driverComponents, options);
|
|
246
|
-
return { success: true, yaml, stages, jobs };
|
|
258
|
+
return { success: true, yaml, stages, jobs, buildParams };
|
|
247
259
|
} catch (err) {
|
|
248
260
|
if (err instanceof UnknownDependencyError || err instanceof DependencyCycleError) {
|
|
249
261
|
return { success: false, error: err.message };
|
|
@@ -344,6 +356,21 @@ export interface RunComponentsOptions {
|
|
|
344
356
|
* `--progress-json`, in which case nothing changes.
|
|
345
357
|
*/
|
|
346
358
|
onProgress?: (event: RunProgressEvent) => void;
|
|
359
|
+
/**
|
|
360
|
+
* chant #1108 — this run's resolved build-time parameter values, resolved
|
|
361
|
+
* the exact same way `chant build` resolves them
|
|
362
|
+
* (../cli/build-params-cli.ts's `resolveCliBuildParams`, driven by
|
|
363
|
+
* `--param`/`--params-file`/a declared `env` mapping/`chant.config.ts`'s
|
|
364
|
+
* `buildParams` defaults). The CLI handler (../cli/handlers/run.ts)
|
|
365
|
+
* resolves + logs these BEFORE calling `runComponents`, the same
|
|
366
|
+
* sequencing `chant build` uses — this function only forwards the
|
|
367
|
+
* already-resolved values into discovery (`resolveComponentTargets` below,
|
|
368
|
+
* then `discoverComponents`); it does not resolve them itself, so
|
|
369
|
+
* `runComponents` stays free of CLI-flag-parsing/formatting concerns.
|
|
370
|
+
* Default: none — `params.*` stays `{}`, matching every caller (including
|
|
371
|
+
* every test in `cli-support.test.ts`) that doesn't supply this.
|
|
372
|
+
*/
|
|
373
|
+
buildParams?: BuildParamProvenance[];
|
|
347
374
|
}
|
|
348
375
|
|
|
349
376
|
/** Result of `chant run --components <name|all>`. */
|
|
@@ -356,6 +383,8 @@ export interface RunComponentsResult {
|
|
|
356
383
|
error?: string;
|
|
357
384
|
/** Set when a selected component (or one of its `deploy`/`rollback` phases) contains a `gate` the local executor cannot run. */
|
|
358
385
|
gateUnsupported?: { component: string; signalName: string };
|
|
386
|
+
/** This run's resolved build-time parameters (chant #1108) — the component-driver counterpart of `../cli/commands/build.ts`'s `BuildResult.buildParams`. Present only once the run actually reached dispatch (mirrors `BuildResult.buildParams`, which is likewise absent on an early-error return). */
|
|
387
|
+
buildParams?: BuildParamProvenance[];
|
|
359
388
|
}
|
|
360
389
|
|
|
361
390
|
/**
|
|
@@ -404,8 +433,10 @@ export async function resolveComponentTargets(
|
|
|
404
433
|
path: string,
|
|
405
434
|
selector: string,
|
|
406
435
|
sandbox?: boolean,
|
|
436
|
+
/** chant #1108 — this invocation's resolved build-time parameter values, forwarded into `discoverComponents` so a `params.<name>` reference inside a discovered `*.component.ts` file resolves instead of reading `{}`. See `discoverComponents`'s `buildParams` option (./discover.ts). */
|
|
437
|
+
buildParams?: BuildParamProvenance[],
|
|
407
438
|
): Promise<ResolvedComponentTargets> {
|
|
408
|
-
const result = await discoverComponents(path, { sandbox });
|
|
439
|
+
const result = await discoverComponents(path, { sandbox, buildParams });
|
|
409
440
|
if (result.errors.length > 0) {
|
|
410
441
|
return { success: false, targets: [], error: result.errors.map((e) => e.message).join("\n") };
|
|
411
442
|
}
|
|
@@ -433,7 +464,7 @@ export async function runComponents(
|
|
|
433
464
|
selector: string,
|
|
434
465
|
options: RunComponentsOptions = {},
|
|
435
466
|
): Promise<RunComponentsResult> {
|
|
436
|
-
const resolved = await resolveComponentTargets(path, selector, options.sandbox);
|
|
467
|
+
const resolved = await resolveComponentTargets(path, selector, options.sandbox, options.buildParams);
|
|
437
468
|
if (!resolved.success) {
|
|
438
469
|
return { success: false, selected: [], error: resolved.error };
|
|
439
470
|
}
|
|
@@ -479,7 +510,7 @@ export async function runComponents(
|
|
|
479
510
|
try {
|
|
480
511
|
if (selector === "all") {
|
|
481
512
|
const run = await runInterpretDriver(resolvedTargets, registry, { env, componentOutputs: seedOutputs, onProgress });
|
|
482
|
-
return { success: true, run, selected };
|
|
513
|
+
return { success: true, run, selected, buildParams: options.buildParams };
|
|
483
514
|
}
|
|
484
515
|
|
|
485
516
|
// Single-component invocation: run just this component, bypassing
|
|
@@ -518,7 +549,7 @@ export async function runComponents(
|
|
|
518
549
|
failedComponent: componentResult.ok ? undefined : componentResult.component,
|
|
519
550
|
componentOutputs,
|
|
520
551
|
};
|
|
521
|
-
return { success: componentResult.ok, run, selected };
|
|
552
|
+
return { success: componentResult.ok, run, selected, buildParams: options.buildParams };
|
|
522
553
|
} catch (err) {
|
|
523
554
|
if (err instanceof DriverRunFailure) {
|
|
524
555
|
return { success: false, run: err.result, selected, error: err.message };
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
13
13
|
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
14
|
-
import { join } from "node:path";
|
|
14
|
+
import { join, dirname, resolve as resolvePath } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
15
16
|
import { tmpdir } from "node:os";
|
|
16
17
|
import { discoverComponents } from "./discover";
|
|
18
|
+
import { params } from "../params";
|
|
17
19
|
|
|
18
20
|
describe("discoverComponents", () => {
|
|
19
21
|
let testDir: string;
|
|
@@ -349,4 +351,64 @@ describe("discoverComponents", () => {
|
|
|
349
351
|
expect(result.components.has("first-svc")).toBe(true);
|
|
350
352
|
expect(result.components.has("nested-svc")).toBe(false);
|
|
351
353
|
});
|
|
354
|
+
|
|
355
|
+
// ── chant #1108 — build-time parameters populated before import ────────────
|
|
356
|
+
|
|
357
|
+
describe("buildParams (chant #1108)", () => {
|
|
358
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
359
|
+
const paramsPath = resolvePath(thisDir, "../params");
|
|
360
|
+
|
|
361
|
+
test("with no buildParams option, params.* stays empty (matches every non-run/generate caller today)", async () => {
|
|
362
|
+
await writeFile(
|
|
363
|
+
join(testDir, "svc.component.ts"),
|
|
364
|
+
`
|
|
365
|
+
export const svc = {
|
|
366
|
+
name: "svc",
|
|
367
|
+
dependsOn: [],
|
|
368
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }],
|
|
369
|
+
};
|
|
370
|
+
`,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const result = await discoverComponents(testDir);
|
|
374
|
+
|
|
375
|
+
expect(result.components.has("svc")).toBe(true);
|
|
376
|
+
expect(params).toEqual({});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("populates params.* BEFORE importing *.component.ts, so a live import observes the resolved value", async () => {
|
|
380
|
+
await writeFile(
|
|
381
|
+
join(testDir, "svc.component.ts"),
|
|
382
|
+
`
|
|
383
|
+
import { params } from ${JSON.stringify(paramsPath)};
|
|
384
|
+
export const svc = {
|
|
385
|
+
name: "svc",
|
|
386
|
+
dependsOn: [],
|
|
387
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "shell", command: String(params.tier) }] }],
|
|
388
|
+
};
|
|
389
|
+
`,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const result = await discoverComponents(testDir, {
|
|
393
|
+
buildParams: [{ name: "tier", value: "production", source: "cli" }],
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
expect(result.errors).toEqual([]);
|
|
397
|
+
const svc = result.components.get("svc");
|
|
398
|
+
expect((svc?.component.deploy[0].steps[0] as { command?: unknown }).command).toBe("production");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("a second call with no buildParams resets params.* — no stale leak from a prior call in the same process", async () => {
|
|
402
|
+
await writeFile(
|
|
403
|
+
join(testDir, "a.component.ts"),
|
|
404
|
+
`export const a = { name: "a", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "shell" }] }] };`,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
await discoverComponents(testDir, { buildParams: [{ name: "tier", value: "production", source: "cli" }] });
|
|
408
|
+
expect(params).toEqual({ tier: "production" });
|
|
409
|
+
|
|
410
|
+
await discoverComponents(testDir);
|
|
411
|
+
expect(params).toEqual({});
|
|
412
|
+
});
|
|
413
|
+
});
|
|
352
414
|
});
|
|
@@ -46,6 +46,9 @@ import { pathToFileURL } from "node:url";
|
|
|
46
46
|
import { existsSync } from "node:fs";
|
|
47
47
|
import { DiscoveryError } from "../errors";
|
|
48
48
|
import { isComponent, type Component } from "./component";
|
|
49
|
+
import type { BuildParamProvenance } from "../provenance";
|
|
50
|
+
import { buildParamValues } from "../build-params";
|
|
51
|
+
import { setBuildParams } from "../params";
|
|
49
52
|
|
|
50
53
|
/** One discovered component, paired with the file it was exported from. */
|
|
51
54
|
export interface DiscoveredComponent {
|
|
@@ -75,6 +78,34 @@ export interface ComponentDiscoveryOptions {
|
|
|
75
78
|
* requested.
|
|
76
79
|
*/
|
|
77
80
|
sandbox?: boolean;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* chant #1108 — this invocation's resolved build-time parameter values
|
|
84
|
+
* (../build-params.ts's `resolveBuildParams`, driven by the CLI's
|
|
85
|
+
* `--param`/`--params-file`/a declared `env` mapping/`chant.config.ts`'s
|
|
86
|
+
* `buildParams` defaults — see ../cli/build-params-cli.ts's
|
|
87
|
+
* `resolveCliBuildParams`, the exact resolution `chant build` runs).
|
|
88
|
+
* Populated into ../params.ts's shared `params` object (`setBuildParams`,
|
|
89
|
+
* below) before any `*.component.ts` file is scanned/imported, mirroring
|
|
90
|
+
* ../discovery/index.ts's `discover()` — the lexicon-resource counterpart
|
|
91
|
+
* of this function, and the thing chant #1108 exists to bring this one to
|
|
92
|
+
* parity with. Before #1108, no caller populated this, so a live `import {
|
|
93
|
+
* params } from "@intentius/chant/params"` inside a component file (e.g. a
|
|
94
|
+
* naming helper deriving a stack name) always saw `{}`.
|
|
95
|
+
*
|
|
96
|
+
* Default: none — `params` stays `{}`, matching every caller that doesn't
|
|
97
|
+
* resolve build-time parameters today (`chant list/describe/graph/lint
|
|
98
|
+
* --components`, `chant components status`); only the run and generate
|
|
99
|
+
* call sites (../cli/handlers/run.ts, ../cli/handlers/build.ts) pass a
|
|
100
|
+
* resolved value.
|
|
101
|
+
*
|
|
102
|
+
* Only takes effect for the in-process (non-sandboxed) import path below —
|
|
103
|
+
* `{ sandbox: true }`'s child process gets its own, unpopulated `params`
|
|
104
|
+
* module instance, the same pre-existing gap `discover({ sandbox: true })`
|
|
105
|
+
* has for lexicon resources (chant #1045 Phase 2 never threaded
|
|
106
|
+
* `buildParams` into its sandboxed child either; out of scope here too).
|
|
107
|
+
*/
|
|
108
|
+
buildParams?: BuildParamProvenance[];
|
|
78
109
|
}
|
|
79
110
|
|
|
80
111
|
/** One already-imported `*.component.ts` module — the input to {@link collectComponents}. */
|
|
@@ -240,6 +271,17 @@ export async function discoverComponents(
|
|
|
240
271
|
path: string,
|
|
241
272
|
options?: ComponentDiscoveryOptions,
|
|
242
273
|
): Promise<ComponentDiscoveryResult> {
|
|
274
|
+
// chant #1108 — populate the shared build-time-parameters object BEFORE
|
|
275
|
+
// scanning/importing any *.component.ts file below, so a live `import {
|
|
276
|
+
// params } from "@intentius/chant/params"` inside a component file
|
|
277
|
+
// observes this invocation's resolved values instead of an empty object.
|
|
278
|
+
// Unconditional (not just when `buildParams` is set) so a stale value from
|
|
279
|
+
// a PRIOR discoverComponents() call in the same process (tests, several
|
|
280
|
+
// `--components` subcommands run back-to-back) never leaks into a call
|
|
281
|
+
// that supplied none — mirrors ../discovery/index.ts's `discover()`,
|
|
282
|
+
// identical rationale.
|
|
283
|
+
setBuildParams(buildParamValues(options?.buildParams ?? []));
|
|
284
|
+
|
|
243
285
|
const sourceFiles = await findComponentFiles(path);
|
|
244
286
|
|
|
245
287
|
if (options?.sandbox && sourceFiles.length > 0) {
|