@intentius/chant 0.124.22 → 0.124.24
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/audit/rules-doc.js +1 -1
- package/dist/ci-jobs.d.ts +41 -1
- package/dist/ci-jobs.d.ts.map +1 -1
- package/dist/ci-jobs.js +48 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +14 -0
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +9 -0
- package/dist/cli/handlers/emulator.d.ts +13 -2
- package/dist/cli/handlers/emulator.d.ts.map +1 -1
- package/dist/cli/handlers/emulator.js +63 -3
- package/dist/cli/handlers/graph.js +24 -1
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +7 -1
- package/dist/cli/handlers/run-wave.d.ts.map +1 -1
- package/dist/cli/handlers/run-wave.js +5 -4
- package/dist/cli/handlers/run.d.ts +4 -3
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +73 -9
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +17 -4
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +31 -0
- package/dist/cli/registry.d.ts +12 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +45 -6
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +97 -22
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +25 -10
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +24 -2
- package/dist/graph-ir.d.ts +43 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +27 -0
- package/dist/lexicon.d.ts +25 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +11 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +62 -6
- package/dist/lifecycle/no-record.d.ts +59 -0
- package/dist/lifecycle/no-record.d.ts.map +1 -0
- package/dist/lifecycle/no-record.js +88 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/shell.js +30 -9
- package/dist/op/emulator-lifecycle.d.ts +23 -0
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/emulator-lifecycle.js +39 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +10 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +7 -2
- package/dist/op/op-waves-notify.d.ts +2 -0
- package/dist/op/op-waves-notify.d.ts.map +1 -1
- package/dist/op/op-waves-notify.js +9 -3
- package/dist/op/op-waves-run.d.ts +8 -2
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +36 -11
- package/dist/op/op-waves.d.ts +85 -1
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +153 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/runtimes/local.js +6 -0
- package/dist/op/step-console.d.ts +65 -0
- package/dist/op/step-console.d.ts.map +1 -0
- package/dist/op/step-console.js +140 -0
- package/dist/point-decision.d.ts.map +1 -1
- package/dist/point-decision.js +3 -0
- package/dist/wave-runtime.mjs +240 -224
- package/dist/workspace/checks/links.d.ts +5 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/links.js +54 -8
- package/dist/workspace/checks.d.ts +4 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +6 -2
- package/dist/workspace/compose-graph.d.ts +26 -4
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +21 -7
- package/dist/workspace/dbt-project-reader.d.ts +36 -0
- package/dist/workspace/dbt-project-reader.d.ts.map +1 -0
- package/dist/workspace/dbt-project-reader.js +290 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +3 -1
- package/dist/workspace/graph.schema.json +109 -4
- package/dist/workspace/intent.js +7 -2
- package/dist/workspace/kind-link-readers.d.ts +84 -0
- package/dist/workspace/kind-link-readers.d.ts.map +1 -0
- package/dist/workspace/kind-link-readers.js +53 -0
- package/dist/workspace/kinds.d.ts +15 -0
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +11 -0
- package/dist/workspace/lexicon-outputs.d.ts +63 -0
- package/dist/workspace/lexicon-outputs.d.ts.map +1 -0
- package/dist/workspace/lexicon-outputs.js +241 -0
- package/dist/workspace/links.d.ts +29 -7
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/links.js +109 -34
- package/dist/workspace/member-handles.d.ts +18 -1
- package/dist/workspace/member-handles.d.ts.map +1 -1
- package/dist/workspace/member-handles.js +31 -14
- package/dist/workspace/nested-graph.d.ts +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +5 -1
- package/dist/workspace/records-checkout.d.ts +11 -1
- package/dist/workspace/records-checkout.d.ts.map +1 -1
- package/dist/workspace/records-checkout.js +38 -2
- package/dist/workspace/runs.d.ts.map +1 -1
- package/dist/workspace/runs.js +4 -2
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +3 -2
- package/dist/workspace/workspace-kinds.schema.json +5 -0
- package/package.json +1 -1
- package/src/audit/rules-doc.ts +1 -1
- package/src/ci-jobs.ts +58 -1
- package/src/cli/commands/build.ts +18 -0
- package/src/cli/handlers/approve.ts +9 -0
- package/src/cli/handlers/emulator.test.ts +61 -1
- package/src/cli/handlers/emulator.ts +65 -4
- package/src/cli/handlers/graph.ts +22 -1
- package/src/cli/handlers/promote.test.ts +7 -0
- package/src/cli/handlers/promote.ts +7 -1
- package/src/cli/handlers/run-no-record.e2e.test.ts +87 -0
- package/src/cli/handlers/run-wave.ts +5 -4
- package/src/cli/handlers/run.test.ts +126 -0
- package/src/cli/handlers/run.ts +69 -9
- package/src/cli/main.test.ts +24 -0
- package/src/cli/main.ts +17 -4
- package/src/cli/parse-args.ts +21 -0
- package/src/cli/registry.ts +12 -0
- package/src/components/cli-support.test.ts +75 -0
- package/src/components/cli-support.ts +130 -25
- package/src/components/driver.test.ts +28 -0
- package/src/components/driver.ts +23 -10
- package/src/components/generate-disabled.test.ts +56 -0
- package/src/graph-ci-edges.test.ts +50 -0
- package/src/graph-detail.ts +21 -2
- package/src/graph-ir.test.ts +23 -1
- package/src/graph-ir.ts +65 -0
- package/src/lexicon.ts +26 -1
- package/src/lifecycle/git.ts +61 -6
- package/src/lifecycle/no-record.test.ts +175 -0
- package/src/lifecycle/no-record.ts +108 -0
- package/src/op/activities/shell.ts +28 -6
- package/src/op/emulator-lifecycle.test.ts +24 -1
- package/src/op/emulator-lifecycle.ts +58 -2
- package/src/op/gate.ts +3 -0
- package/src/op/index.ts +1 -1
- package/src/op/local-executor.ts +20 -3
- package/src/op/op-waves-jobs.test.ts +213 -0
- package/src/op/op-waves-notify.ts +11 -5
- package/src/op/op-waves-run.ts +42 -11
- package/src/op/op-waves.ts +202 -2
- package/src/op/runtimes/local.ts +6 -0
- package/src/op/step-console.test.ts +150 -0
- package/src/op/step-console.ts +155 -0
- package/src/point-decision-bundle.test.ts +1 -0
- package/src/point-decision.ts +3 -0
- package/src/wave-loop-golden.test.ts +3 -0
- package/src/workspace/checks/links.ts +57 -8
- package/src/workspace/checks.e2e.test.ts +1 -1
- package/src/workspace/checks.ts +9 -2
- package/src/workspace/compose-graph.test.ts +55 -0
- package/src/workspace/compose-graph.ts +33 -9
- package/src/workspace/dbt-kind.e2e.test.ts +95 -0
- package/src/workspace/dbt-kind.test.ts +283 -0
- package/src/workspace/dbt-project-reader.ts +291 -0
- package/src/workspace/graph-cli.ts +3 -1
- package/src/workspace/graph-contract.test.ts +26 -1
- package/src/workspace/graph.schema.json +109 -4
- package/src/workspace/intent.ts +7 -2
- package/src/workspace/kind-link-readers.ts +109 -0
- package/src/workspace/kinds.ts +21 -1
- package/src/workspace/lexicon-outputs.ts +272 -0
- package/src/workspace/links.test.ts +22 -0
- package/src/workspace/links.ts +129 -34
- package/src/workspace/member-handles.ts +28 -8
- package/src/workspace/nested-graph.test.ts +14 -0
- package/src/workspace/nested-graph.ts +7 -2
- package/src/workspace/records-checkout.ts +36 -2
- package/src/workspace/records-uncommitted.test.ts +5 -1
- package/src/workspace/runs.ts +4 -2
- package/src/workspace/status.ts +3 -2
- package/src/workspace/workspace-kinds.schema.json +5 -0
|
@@ -61,6 +61,7 @@ vi.mock("node:fs", async () => {
|
|
|
61
61
|
};
|
|
62
62
|
});
|
|
63
63
|
vi.mock("../../components/cli-support", () => ({
|
|
64
|
+
isComponentList: (selector: string) => selector.includes(","),
|
|
64
65
|
runComponents: (...args: unknown[]) => runComponentsMock(...args),
|
|
65
66
|
listComponents: (...args: unknown[]) => listComponentsMock(...args),
|
|
66
67
|
}));
|
|
@@ -1277,6 +1278,25 @@ describe("runOp dispatcher: --components routes to runOpComponents", () => {
|
|
|
1277
1278
|
vi.restoreAllMocks();
|
|
1278
1279
|
});
|
|
1279
1280
|
|
|
1281
|
+
test("a list selector prints the resolved order and the added dependencies before running (#3950)", async () => {
|
|
1282
|
+
discoverOpsMock.mockReset();
|
|
1283
|
+
const stderr = makeStderrSpy();
|
|
1284
|
+
let printedBeforeRun = "";
|
|
1285
|
+
runComponentsMock.mockImplementation(async (_path: string, _selector: string, options: { onPlan?: (plan: unknown) => void }) => {
|
|
1286
|
+
options.onPlan?.({ order: ["db", "api", "web"], waves: [["db"], ["api", "web"]], added: ["db"] });
|
|
1287
|
+
printedBeforeRun = stderr.join("\n");
|
|
1288
|
+
return { success: true, selected: ["db", "api", "web"], run: { order: ["db", "api", "web"], waves: [["db"], ["api", "web"]], results: [], ok: true, status: "ok" } };
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
const exit = await runOp({ args: makeArgs({ path: "api,web", components: true }), plugins: [], serializers: [] });
|
|
1292
|
+
|
|
1293
|
+
expect(exit).toBe(0);
|
|
1294
|
+
expect(runComponentsMock).toHaveBeenCalledWith(expect.any(String), "api,web", expect.objectContaining({ onPlan: expect.any(Function) }));
|
|
1295
|
+
expect(printedBeforeRun).toContain("run order: db, then api + web");
|
|
1296
|
+
expect(printedBeforeRun).toContain("added as dependencies: db");
|
|
1297
|
+
vi.restoreAllMocks();
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1280
1300
|
// chant #1116 — the component driver never checked --report, so it was
|
|
1281
1301
|
// silently ignored and the command fell through to a real dispatch.
|
|
1282
1302
|
// Hard-error instead, before runComponents is ever reached.
|
|
@@ -1929,3 +1949,109 @@ describe("runOp: --fail-on-drift (#3675)", () => {
|
|
|
1929
1949
|
expect(await runOp({ args: makeArgs({ path: "watch" }), plugins: [], serializers: [] })).toBe(2);
|
|
1930
1950
|
});
|
|
1931
1951
|
});
|
|
1952
|
+
|
|
1953
|
+
/**
|
|
1954
|
+
* chant #3955 — `--no-record` runs every step and records nothing on
|
|
1955
|
+
* `chant/lifecycle`. The run says so before it starts and after it ends, a
|
|
1956
|
+
* gate fails rather than recording a pending fact nobody could approve, and
|
|
1957
|
+
* a runtime that would record elsewhere is refused.
|
|
1958
|
+
*/
|
|
1959
|
+
describe("runOp --no-record (#3955)", () => {
|
|
1960
|
+
const START = "not recording (--no-record)";
|
|
1961
|
+
const END = "not recorded (--no-record): nothing went on chant/lifecycle and nothing was pushed";
|
|
1962
|
+
|
|
1963
|
+
beforeEach(() => {
|
|
1964
|
+
discoverOpsMock.mockReset();
|
|
1965
|
+
loadChantConfigMock.mockReset().mockResolvedValue({ config: {} });
|
|
1966
|
+
loadPluginsMock.mockReset().mockResolvedValue([]);
|
|
1967
|
+
runComponentsMock.mockReset();
|
|
1968
|
+
maybeRecordAutoReleaseMock.mockReset();
|
|
1969
|
+
maybePersistBuildManifestMock.mockReset().mockResolvedValue({ persisted: false, reason: "no-manifest" });
|
|
1970
|
+
gateLedger = memoryGateLedgerPort();
|
|
1971
|
+
delete process.env.CHANT_NO_RECORD;
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1974
|
+
afterEach(() => {
|
|
1975
|
+
delete process.env.CHANT_NO_RECORD;
|
|
1976
|
+
vi.restoreAllMocks();
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1979
|
+
test("an Op runs, the run says at its start and end that nothing was recorded, and the flag does not outlive it", async () => {
|
|
1980
|
+
let during: string | undefined;
|
|
1981
|
+
discoverOpsMock.mockImplementation(async () => {
|
|
1982
|
+
during = process.env.CHANT_NO_RECORD;
|
|
1983
|
+
return { ops: new Map([localOp("hello", [{ kind: "activity", fn: "shellCmd", profile: "atMostOnce", args: { cmd: "true" } }])]), errors: [] };
|
|
1984
|
+
});
|
|
1985
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
1986
|
+
const stderr = makeStderrSpy();
|
|
1987
|
+
|
|
1988
|
+
const exit = await runOp({ args: makeArgs({ path: "hello", noRecord: true }), plugins: [], serializers: [] });
|
|
1989
|
+
|
|
1990
|
+
expect(exit).toBe(0);
|
|
1991
|
+
expect(during).toBe("1");
|
|
1992
|
+
expect(process.env.CHANT_NO_RECORD).toBeUndefined();
|
|
1993
|
+
expect(stderr[0]).toContain(START);
|
|
1994
|
+
expect(stderr.at(-1)).toContain(END);
|
|
1995
|
+
});
|
|
1996
|
+
|
|
1997
|
+
test("a gate fails the run and leaves no pending fact, and the end line still prints", async () => {
|
|
1998
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([localOp("gated", [{ kind: "gate", gate: "approve-prod" }])]), errors: [] });
|
|
1999
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
2000
|
+
const stderr = makeStderrSpy();
|
|
2001
|
+
|
|
2002
|
+
const exit = await runOp({ args: makeArgs({ path: "gated", noRecord: true, gatedExit: 0 }), plugins: [], serializers: [] });
|
|
2003
|
+
|
|
2004
|
+
// A failure, not a gated stop: --gated-exit cannot turn it green.
|
|
2005
|
+
expect(exit).toBe(1);
|
|
2006
|
+
expect(gateLedger.appended).toHaveLength(0);
|
|
2007
|
+
expect(stderr.at(-1)).toContain(END);
|
|
2008
|
+
});
|
|
2009
|
+
|
|
2010
|
+
test("CHANT_NO_RECORD does the same without the flag, names itself, and stays set", async () => {
|
|
2011
|
+
process.env.CHANT_NO_RECORD = "1";
|
|
2012
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([localOp("hello", [])]), errors: [] });
|
|
2013
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
2014
|
+
const stderr = makeStderrSpy();
|
|
2015
|
+
|
|
2016
|
+
const exit = await runOp({ args: makeArgs({ path: "hello" }), plugins: [], serializers: [] });
|
|
2017
|
+
|
|
2018
|
+
expect(exit).toBe(0);
|
|
2019
|
+
expect(stderr[0]).toContain("not recording (CHANT_NO_RECORD=1)");
|
|
2020
|
+
expect(process.env.CHANT_NO_RECORD).toBe("1");
|
|
2021
|
+
});
|
|
2022
|
+
|
|
2023
|
+
test("a runtime other than local is refused before the run starts", async () => {
|
|
2024
|
+
const start = vi.fn();
|
|
2025
|
+
loadChantConfigMock.mockResolvedValue({ config: { lexicons: ["stub"] } });
|
|
2026
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([makeOp("hello")]), errors: [] });
|
|
2027
|
+
const stderr = makeStderrSpy();
|
|
2028
|
+
|
|
2029
|
+
const exit = await runOp({
|
|
2030
|
+
args: makeArgs({ path: "hello", on: "stub", noRecord: true }),
|
|
2031
|
+
plugins: [{ name: "stub", opRuntime: { name: "stub", start } } as never], serializers: [],
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
expect(exit).toBe(1);
|
|
2035
|
+
expect(start).not.toHaveBeenCalled();
|
|
2036
|
+
expect(stderr.join("\n")).toContain('--no-record runs on the local runtime; the "stub" runtime records the run where it executes it');
|
|
2037
|
+
});
|
|
2038
|
+
|
|
2039
|
+
test("--components records no release and persists no manifest", async () => {
|
|
2040
|
+
runComponentsMock.mockResolvedValue({
|
|
2041
|
+
success: true,
|
|
2042
|
+
selected: ["svc"],
|
|
2043
|
+
run: { order: ["svc"], waves: [["svc"]], results: [{ component: "svc", ok: true, status: "ok", records: [] }], ok: true, status: "ok" },
|
|
2044
|
+
});
|
|
2045
|
+
maybeRecordAutoReleaseMock.mockResolvedValue({ recorded: false, reason: "opted-out" });
|
|
2046
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
2047
|
+
const stderr = makeStderrSpy();
|
|
2048
|
+
|
|
2049
|
+
const exit = await runOpComponents({ args: makeArgs({ path: "svc", noRecord: true }), plugins: [], serializers: [] });
|
|
2050
|
+
|
|
2051
|
+
expect(exit).toBe(0);
|
|
2052
|
+
expect(maybeRecordAutoReleaseMock.mock.calls[0][1]).toMatchObject({ disabled: true });
|
|
2053
|
+
expect(maybePersistBuildManifestMock.mock.calls[0][1]).toMatchObject({ disabled: true });
|
|
2054
|
+
expect(stderr[0]).toContain(START);
|
|
2055
|
+
expect(stderr.at(-1)).toContain(END);
|
|
2056
|
+
});
|
|
2057
|
+
});
|
package/src/cli/handlers/run.ts
CHANGED
|
@@ -23,11 +23,13 @@ import { acquireStewardTurn, STEWARD_TURN_WAIT_MS } from "../../op/operator";
|
|
|
23
23
|
import { releaseLease, currentHolderId, type AcquireLeaseResult } from "../../lifecycle/lease";
|
|
24
24
|
import { StaleLockError } from "../../lifecycle/git";
|
|
25
25
|
import { LIFECYCLE_LOCAL_NOTE, isLifecycleLocalNote } from "../../lifecycle/local-note";
|
|
26
|
+
import { ledgerRecordingOff, noRecordEndNote, noRecordStartNote, resetNoRecordLedger, NO_RECORD_ENV } from "../../lifecycle/no-record";
|
|
26
27
|
import { renderDriverHuman, renderDriverJson } from "../../components/driver-output";
|
|
27
28
|
import { ndjsonProgressSink } from "../../components/run-progress";
|
|
28
29
|
import { maybeRecordAutoRelease } from "../../components/auto-release";
|
|
29
30
|
import { maybePersistBuildManifest } from "../../components/manifest-persistence";
|
|
30
31
|
import type { DriverComponentResult } from "../../components/driver";
|
|
32
|
+
import { isComponentList, type ComponentRunPlan } from "../../components/cli-support";
|
|
31
33
|
import type { ReleaseRecord } from "../../lifecycle/release-ledger";
|
|
32
34
|
import { runOpGenerate } from "./run-generate";
|
|
33
35
|
|
|
@@ -873,12 +875,13 @@ export async function recordAutoReleasesForRun(
|
|
|
873
875
|
return recorded;
|
|
874
876
|
}
|
|
875
877
|
|
|
876
|
-
// ── chant run --components <name|all>
|
|
878
|
+
// ── chant run --components <name|all|a,b,c> ──────────────────────────────────
|
|
877
879
|
|
|
878
880
|
/**
|
|
879
|
-
* `chant run --components <name|all> [--env <env>]` (#585) — the
|
|
880
|
-
* driver's CLI entrypoint, and since #2116 the only component run
|
|
881
|
-
* is. `args.path` is the component name (or `all
|
|
881
|
+
* `chant run --components <name|all|a,b,c> [--env <env>]` (#585) — the
|
|
882
|
+
* interpret driver's CLI entrypoint, and since #2116 the only component run
|
|
883
|
+
* path there is. `args.path` is the component name (or `all`, or a comma list
|
|
884
|
+
* that also runs what the named components depend on, #3950), matching `chant run
|
|
882
885
|
* <name>`'s Op-dispatch convention exactly (`args.path` is the Op name there
|
|
883
886
|
* too) rather than a project directory — components are always discovered from
|
|
884
887
|
* the current working directory, the same way Op discovery (`discoverOps()`)
|
|
@@ -909,11 +912,52 @@ export async function recordAutoReleasesForRun(
|
|
|
909
912
|
* auto-release check below used to apply itself) and reused for that check,
|
|
910
913
|
* rather than loaded a second time.
|
|
911
914
|
*/
|
|
912
|
-
export
|
|
915
|
+
export function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
916
|
+
return withRecordingChoice(ctx, () => runComponentSelection(ctx));
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Run `run` with recording off when `--no-record` or `CHANT_NO_RECORD` asks
|
|
921
|
+
* for it (#3955), and say so before it starts and after it ends, whatever
|
|
922
|
+
* the outcome. The flag sets `CHANT_NO_RECORD` for the length of the run, so
|
|
923
|
+
* the ledger seam (`../../lifecycle/git.ts`) and every chant process a step
|
|
924
|
+
* starts see the same choice. A run with recording on is untouched.
|
|
925
|
+
*/
|
|
926
|
+
async function withRecordingChoice(ctx: CommandContext, run: () => Promise<number>): Promise<number> {
|
|
927
|
+
const flagged = ctx.args.noRecord === true;
|
|
928
|
+
if (!flagged && !ledgerRecordingOff()) return run();
|
|
929
|
+
const source = flagged ? "--no-record" : `${NO_RECORD_ENV}=${process.env[NO_RECORD_ENV]}`;
|
|
930
|
+
const prior = process.env[NO_RECORD_ENV];
|
|
931
|
+
if (flagged) process.env[NO_RECORD_ENV] = "1";
|
|
932
|
+
resetNoRecordLedger();
|
|
933
|
+
console.error(formatWarning({ message: noRecordStartNote(source) }));
|
|
934
|
+
try {
|
|
935
|
+
return await run();
|
|
936
|
+
} finally {
|
|
937
|
+
console.error(formatWarning({ message: noRecordEndNote(source) }));
|
|
938
|
+
resetNoRecordLedger();
|
|
939
|
+
if (flagged) {
|
|
940
|
+
if (prior === undefined) delete process.env[NO_RECORD_ENV];
|
|
941
|
+
else process.env[NO_RECORD_ENV] = prior;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/** A run that records nothing stays in this process (#3955): a runtime that runs it elsewhere would record there. */
|
|
947
|
+
function refusesNoRecordOn(runtime: Pick<OpRuntimeProvider, "name">): boolean {
|
|
948
|
+
if (!ledgerRecordingOff() || runtime.name === "local") return false;
|
|
949
|
+
console.error(formatError({
|
|
950
|
+
message: `--no-record runs on the local runtime; the "${runtime.name}" runtime records the run where it executes it`,
|
|
951
|
+
hint: `Omit --on to run it here without recording, or drop --no-record (and ${NO_RECORD_ENV}).`,
|
|
952
|
+
}));
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
async function runComponentSelection(ctx: CommandContext): Promise<number> {
|
|
913
957
|
const selector = ctx.args.path;
|
|
914
958
|
if (!selector || selector === ".") {
|
|
915
959
|
console.error(formatError({
|
|
916
|
-
message: "Component name is required: chant run --components <name|all>",
|
|
960
|
+
message: "Component name is required: chant run --components <name|all|a,b,c>",
|
|
917
961
|
hint: "Run `chant list --components` to see available components.",
|
|
918
962
|
}));
|
|
919
963
|
return 1;
|
|
@@ -936,6 +980,7 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
936
980
|
|
|
937
981
|
const runtime = await resolveOpRuntime(ctx);
|
|
938
982
|
if (!runtime) return 1;
|
|
983
|
+
if (refusesNoRecordOn(runtime)) return 1;
|
|
939
984
|
if (!runtime.runComponents) {
|
|
940
985
|
console.error(formatError({
|
|
941
986
|
message: `--components is not supported on the "${runtime.name}" runtime`,
|
|
@@ -966,12 +1011,22 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
966
1011
|
// `undefined` and every `onProgress?.(...)` call in the driver is a no-op —
|
|
967
1012
|
// behavior is byte-for-byte unchanged from before this flag existed.
|
|
968
1013
|
const onProgress = ctx.args.progressJson ? ndjsonProgressSink() : undefined;
|
|
1014
|
+
// A list (#3950) pulls in what its components depend on; say which ones and
|
|
1015
|
+
// in what order before the first one runs. stderr, so --json and
|
|
1016
|
+
// --progress-json keep stdout to themselves.
|
|
1017
|
+
const onPlan = isComponentList(selector)
|
|
1018
|
+
? (plan: ComponentRunPlan) => {
|
|
1019
|
+
console.error(formatInfo(`run order: ${plan.waves.map((wave) => wave.join(" + ")).join(", then ")}`));
|
|
1020
|
+
if (plan.added.length > 0) console.error(formatInfo(`added as dependencies: ${plan.added.join(", ")}`));
|
|
1021
|
+
}
|
|
1022
|
+
: undefined;
|
|
969
1023
|
const result = await runtime.runComponents(projectPath, selector, {
|
|
970
1024
|
env: ctx.args.env,
|
|
971
1025
|
componentOutputs: seededOutputs,
|
|
972
1026
|
onProgress,
|
|
973
1027
|
sandbox: ctx.args.sandbox,
|
|
974
1028
|
buildParams: paramsResolution.provenance,
|
|
1029
|
+
...(onPlan ? { onPlan } : {}),
|
|
975
1030
|
});
|
|
976
1031
|
|
|
977
1032
|
// Dump the accumulated outputs for a downstream job to seed from. Written
|
|
@@ -1037,7 +1092,8 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
1037
1092
|
}
|
|
1038
1093
|
|
|
1039
1094
|
if (result.success && result.run) {
|
|
1040
|
-
|
|
1095
|
+
// #3955: a run that records nothing has no release to report either.
|
|
1096
|
+
const disabled = resolveAutoReleaseDisabled(config, ctx.args.noReleaseRecord) || ledgerRecordingOff();
|
|
1041
1097
|
const recorded = await recordAutoReleasesForRun(result.run.results, env, `local-${Date.now()}`, disabled);
|
|
1042
1098
|
// `--digest-file` (#2602): the releases this run recorded, one
|
|
1043
1099
|
// `<component>=<digest>` line each, the form `chant components promote
|
|
@@ -1065,7 +1121,11 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
|
|
|
1065
1121
|
* what they were before the seam existed; a runtime that reports only a state
|
|
1066
1122
|
* gets the terser render instead.
|
|
1067
1123
|
*/
|
|
1068
|
-
export
|
|
1124
|
+
export function runOpOnRuntime(ctx: CommandContext): Promise<number> {
|
|
1125
|
+
return withRecordingChoice(ctx, () => runOpOnResolvedRuntime(ctx));
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
async function runOpOnResolvedRuntime(ctx: CommandContext): Promise<number> {
|
|
1069
1129
|
const opName = ctx.args.path;
|
|
1070
1130
|
if (!opName || opName === ".") {
|
|
1071
1131
|
console.error(formatError({
|
|
@@ -1108,7 +1168,7 @@ export async function runOpOnRuntime(ctx: CommandContext): Promise<number> {
|
|
|
1108
1168
|
}
|
|
1109
1169
|
|
|
1110
1170
|
const runtime = await resolveOpRuntime(ctx);
|
|
1111
|
-
if (!runtime) {
|
|
1171
|
+
if (!runtime || refusesNoRecordOn(runtime)) {
|
|
1112
1172
|
await stewardGate?.release?.();
|
|
1113
1173
|
return 1;
|
|
1114
1174
|
}
|
package/src/cli/main.test.ts
CHANGED
|
@@ -361,6 +361,22 @@ describe("parseArgs", () => {
|
|
|
361
361
|
|
|
362
362
|
// ── --no-release-record (#597) ───────────────────────────────────────────
|
|
363
363
|
|
|
364
|
+
test("emulator up --image/--network/--setting/--loopback parse, and a missing value is refused (#3952)", () => {
|
|
365
|
+
const a = parseArgs([
|
|
366
|
+
"emulator", "up", "--image", "floci/floci:1.6.0", "--network", "loom",
|
|
367
|
+
"--setting", "FLOCI_A=1", "--setting", "FLOCI_B=2", "--loopback",
|
|
368
|
+
]);
|
|
369
|
+
expect(a.image).toBe("floci/floci:1.6.0");
|
|
370
|
+
expect(a.network).toBe("loom");
|
|
371
|
+
expect(a.setting).toEqual(["FLOCI_A=1", "FLOCI_B=2"]);
|
|
372
|
+
expect(a.loopback).toBe(true);
|
|
373
|
+
expect(parseArgs(["emulator", "up", "--setting=FLOCI_A=1"]).setting).toEqual(["FLOCI_A=1"]);
|
|
374
|
+
expect(() => parseArgs(["emulator", "up", "--image"])).toThrow(/--image needs an image reference/);
|
|
375
|
+
expect(() => parseArgs(["emulator", "up", "--network", "--json"])).toThrow(/--network needs a Docker network name/);
|
|
376
|
+
expect(() => parseArgs(["emulator", "up", "--setting"])).toThrow(/--setting needs a value/);
|
|
377
|
+
expect(() => parseArgs(["emulator", "up", "--loopback=yes"])).toThrow();
|
|
378
|
+
});
|
|
379
|
+
|
|
364
380
|
test("--promote-to <env> sets promoteTo, and a missing value is refused (#2575)", () => {
|
|
365
381
|
expect(parseArgs(["build", "--components", "--generate", "github", "--promote-to", "prod"]).promoteTo).toBe("prod");
|
|
366
382
|
expect(() => parseArgs(["build", "--components", "--generate", "github", "--promote-to"])).toThrow(/--promote-to needs an environment/);
|
|
@@ -390,6 +406,14 @@ describe("parseArgs", () => {
|
|
|
390
406
|
expect(result.noReleaseRecord).toBeUndefined();
|
|
391
407
|
});
|
|
392
408
|
|
|
409
|
+
test("parses --no-record for run <op> and run --components, apart from --no-release-record (#3955)", () => {
|
|
410
|
+
expect(parseArgs(["run", "smoke", "--no-record"])).toMatchObject({ path: "smoke", noRecord: true });
|
|
411
|
+
const components = parseArgs(["run", "--components", "svc", "--no-record"]);
|
|
412
|
+
expect(components).toMatchObject({ components: true, noRecord: true });
|
|
413
|
+
expect(components.noReleaseRecord).toBeUndefined();
|
|
414
|
+
expect(parseArgs(["run", "smoke"]).noRecord).toBeUndefined();
|
|
415
|
+
});
|
|
416
|
+
|
|
393
417
|
// ── --progress-json (M3, behold roadmap) ─────────────────────────────────
|
|
394
418
|
|
|
395
419
|
test("parses --progress-json for run --components", () => {
|
package/src/cli/main.ts
CHANGED
|
@@ -253,6 +253,8 @@ Ops:
|
|
|
253
253
|
with a work lease runs under, and who holds it
|
|
254
254
|
--fail-on-drift: exit 2 when the run completes and
|
|
255
255
|
its Drift outcome is true (WatchOp failOn: "drift")
|
|
256
|
+
--no-record: a test run that records nothing on
|
|
257
|
+
chant/lifecycle and pushes nothing (local runtime)
|
|
256
258
|
run list List all Ops with the runtime's state for each
|
|
257
259
|
run status <name> Show the runtime's state for one Op's latest run
|
|
258
260
|
run approve <op> <gate> Record a gate's resolution and wake the runtime
|
|
@@ -261,7 +263,8 @@ Ops:
|
|
|
261
263
|
run wave --spec <file> --wave <k> Plan, gate and apply wave k of an Op
|
|
262
264
|
waves spec (#3679): the job a generated waves
|
|
263
265
|
pipeline runs. --decide plans and decides only;
|
|
264
|
-
--share <i> applies one share of a decided wave
|
|
266
|
+
--share <i> applies one share of a decided wave;
|
|
267
|
+
--group <name> applies one job group's runs.
|
|
265
268
|
--record-plans (no --wave), in a pull request's
|
|
266
269
|
pipeline: plan every wave at its head and record the
|
|
267
270
|
digests a pr-review approval mode compares
|
|
@@ -278,8 +281,11 @@ Ops:
|
|
|
278
281
|
declares a schedule or from --spec <file.json>.
|
|
279
282
|
Files go to the forge's own directory unless
|
|
280
283
|
--output <dir>; --format json prints them instead
|
|
281
|
-
run --components <name|all> Run discovered Component(s) through the
|
|
282
|
-
driver on the local executor (--env <env>;
|
|
284
|
+
run --components <name|all|a,b,c> Run discovered Component(s) through the
|
|
285
|
+
interpret driver on the local executor (--env <env>;
|
|
286
|
+
#585). A comma list also runs what the named
|
|
287
|
+
components depend on, in dependency order, in one
|
|
288
|
+
run, and prints that order first (#3950).
|
|
283
289
|
On success, auto-emits a release-ledger record per
|
|
284
290
|
component that published a digest (default: on;
|
|
285
291
|
--no-release-record to opt out; #597)
|
|
@@ -807,7 +813,8 @@ Lexicon development:
|
|
|
807
813
|
Local:
|
|
808
814
|
emulator <up|down|status> Boot/stop/inspect configured lexicons' local
|
|
809
815
|
emulators (Floci etc.); --lexicon <name>, --json,
|
|
810
|
-
--port
|
|
816
|
+
--port, --image, --network, --setting, --loopback;
|
|
817
|
+
\`chant emulator --help\` lists ports and logins
|
|
811
818
|
|
|
812
819
|
Servers:
|
|
813
820
|
serve lsp Start the LSP server (stdio)
|
|
@@ -880,6 +887,12 @@ Options:
|
|
|
880
887
|
successful \`run --components\` deploy (default: on;
|
|
881
888
|
also settable via chant.config.ts's
|
|
882
889
|
release.autoRecord: false; #597)
|
|
890
|
+
--no-record (run <op>, run --components) Run every step but record
|
|
891
|
+
nothing on chant/lifecycle and push nothing: no
|
|
892
|
+
release, run record, receipt or gate fact. A gate
|
|
893
|
+
step fails. The run says so at its start and end.
|
|
894
|
+
CHANT_NO_RECORD=1 does the same for a whole CI job
|
|
895
|
+
(#3955)
|
|
883
896
|
--fold (build) Fold source modules statically instead of
|
|
884
897
|
running them; folds resource constructors and
|
|
885
898
|
composite factory calls (#1022/#1023), falling back
|
package/src/cli/parse-args.ts
CHANGED
|
@@ -54,6 +54,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
54
54
|
"--check",
|
|
55
55
|
"--bump",
|
|
56
56
|
"--no-release-record",
|
|
57
|
+
"--no-record",
|
|
57
58
|
"--fold",
|
|
58
59
|
"--no-fold",
|
|
59
60
|
"--no-cache",
|
|
@@ -74,6 +75,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
74
75
|
"--generated",
|
|
75
76
|
"--decide",
|
|
76
77
|
"--record-plans",
|
|
78
|
+
"--loopback",
|
|
77
79
|
]);
|
|
78
80
|
|
|
79
81
|
/**
|
|
@@ -261,6 +263,18 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
261
263
|
const value = args[++i];
|
|
262
264
|
if (!value || value.startsWith("-")) throw new Error("--port needs a port: --port <container-port>=<host-port>");
|
|
263
265
|
(result.port ??= []).push(value);
|
|
266
|
+
} else if (arg === "--image") {
|
|
267
|
+
result.image = args[++i];
|
|
268
|
+
if (!result.image || result.image.startsWith("-")) throw new Error("--image needs an image reference: --image <name>:<tag>");
|
|
269
|
+
} else if (arg === "--network") {
|
|
270
|
+
result.network = args[++i];
|
|
271
|
+
if (!result.network || result.network.startsWith("-")) throw new Error("--network needs a Docker network name: --network <name>");
|
|
272
|
+
} else if (arg === "--setting") {
|
|
273
|
+
const value = args[++i];
|
|
274
|
+
if (!value || value.startsWith("-")) throw new Error("--setting needs a value: --setting <KEY>=<VALUE>");
|
|
275
|
+
(result.setting ??= []).push(value);
|
|
276
|
+
} else if (arg === "--loopback") {
|
|
277
|
+
result.loopback = true;
|
|
264
278
|
} else if (arg === "--promote-to") {
|
|
265
279
|
result.promoteTo = args[++i];
|
|
266
280
|
if (!result.promoteTo || result.promoteTo.startsWith("-")) throw new Error("--promote-to needs an environment: --promote-to <env>");
|
|
@@ -333,6 +347,10 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
333
347
|
result.decide = true;
|
|
334
348
|
} else if (arg === "--record-plans") {
|
|
335
349
|
result.recordPlans = true;
|
|
350
|
+
} else if (arg === "--group") {
|
|
351
|
+
const raw = args[++i];
|
|
352
|
+
if (!raw || raw.startsWith("-")) throw new Error("--group needs a job group's name: --group <name>");
|
|
353
|
+
result.group = raw;
|
|
336
354
|
} else if (arg === "--share") {
|
|
337
355
|
const raw = args[++i];
|
|
338
356
|
const share = Number(raw);
|
|
@@ -591,6 +609,9 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
591
609
|
result.compareTo = args[++i];
|
|
592
610
|
} else if (arg === "--no-release-record") {
|
|
593
611
|
result.noReleaseRecord = true;
|
|
612
|
+
} else if (arg === "--no-record") {
|
|
613
|
+
// `chant run <op|--components> --no-record` (#3955): write nothing to chant/lifecycle and push nothing.
|
|
614
|
+
result.noRecord = true;
|
|
594
615
|
} else if (arg === "--fold") {
|
|
595
616
|
result.fold = true;
|
|
596
617
|
} else if (arg === "--no-cache") {
|
package/src/cli/registry.ts
CHANGED
|
@@ -167,6 +167,14 @@ export interface ParsedArgs {
|
|
|
167
167
|
env?: string;
|
|
168
168
|
/** `chant emulator up --port <container-port>=<host-port>`, repeatable (#3673); a bare `<host-port>` when one emulator is selected. */
|
|
169
169
|
port?: string[];
|
|
170
|
+
/** `chant emulator up --image <ref>` (#3952): the image to start, when one emulator is selected. */
|
|
171
|
+
image?: string;
|
|
172
|
+
/** `chant emulator up --network <name>` (#3952): the Docker network each started container joins. */
|
|
173
|
+
network?: string;
|
|
174
|
+
/** `chant emulator up --setting <KEY>=<VALUE>`, repeatable (#3952): an emulator setting such as `FLOCI_<NAME>`, passed as `-e`. */
|
|
175
|
+
setting?: string[];
|
|
176
|
+
/** `chant emulator up --loopback` (#3952): publish ports on 127.0.0.1 only. */
|
|
177
|
+
loopback?: boolean;
|
|
170
178
|
/** `chant build --components --generate <lexicon> --promote-to <env>` (#2575) — add a promote job to the generated pipeline. */
|
|
171
179
|
promoteTo?: string;
|
|
172
180
|
/** `chant graph --stacks` — render the cross-stack apply-ordering graph */
|
|
@@ -260,6 +268,8 @@ export interface ParsedArgs {
|
|
|
260
268
|
recordPlans?: boolean;
|
|
261
269
|
/** `chant run wave --share <i>` (#3679) — apply share `i` of a decided wide wave. */
|
|
262
270
|
share?: number;
|
|
271
|
+
/** `chant run wave --group <name>` (#3914) — apply one job group's runs of a decided wave. */
|
|
272
|
+
group?: string;
|
|
263
273
|
/** `chant components fan-out --canary <component>` (#3049), repeatable — components that form wave 1. */
|
|
264
274
|
canary?: string[];
|
|
265
275
|
/**
|
|
@@ -547,6 +557,8 @@ export interface ParsedArgs {
|
|
|
547
557
|
compareTo?: string;
|
|
548
558
|
/** `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`. */
|
|
549
559
|
noReleaseRecord?: boolean;
|
|
560
|
+
/** `chant run <op>` / `chant run --components <name>` `--no-record` (#3955) — run every step but record nothing on `chant/lifecycle` and push nothing; a gate step fails. `CHANT_NO_RECORD=1` does the same for every chant process that inherits it (../../lifecycle/no-record.ts). */
|
|
561
|
+
noRecord?: boolean;
|
|
550
562
|
/** `chant run --components <name> --dump-outputs <file>` — after the run, write the accumulated cross-component/cross-stack outputs (JSON, keyed by component name) to `<file>`, for a downstream job to `--seed-outputs`. */
|
|
551
563
|
dumpOutputs?: string;
|
|
552
564
|
/** `chant run --components <name> --digest-file <file>` (#2602) — after a successful run, write one `<component>=<digest>` line per release the run recorded to `<file>`, the form `chant components promote --digest` takes, so a generated promote job promotes exactly the release its pipeline run built. Written empty when nothing was recorded. */
|
|
@@ -601,6 +601,81 @@ describe("runComponents", () => {
|
|
|
601
601
|
expect(result.error).toMatch(/cycle/i);
|
|
602
602
|
});
|
|
603
603
|
|
|
604
|
+
describe("a comma list (#3950)", () => {
|
|
605
|
+
const comp = (name: string, dependsOn: string[], extra = "") =>
|
|
606
|
+
`export const c = { name: "${name}", ${extra}dependsOn: ${JSON.stringify(dependsOn)}, deploy: [{ phase: "Apply", steps: [{ kind: "deploy-thing" }] }] };`;
|
|
607
|
+
|
|
608
|
+
beforeEach(async () => {
|
|
609
|
+
await writeFile(join(testDir, "db.component.ts"), comp("db", []));
|
|
610
|
+
await writeFile(join(testDir, "cache.component.ts"), comp("cache", []));
|
|
611
|
+
await writeFile(join(testDir, "api.component.ts"), comp("api", ["db"]));
|
|
612
|
+
await writeFile(join(testDir, "web.component.ts"), comp("web", ["api", "cache"]));
|
|
613
|
+
await writeFile(join(testDir, "worker.component.ts"), comp("worker", ["db"]));
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("runs the named components and their transitive dependencies in one dependency-ordered run", async () => {
|
|
617
|
+
const plans: unknown[] = [];
|
|
618
|
+
const result = await runComponents(testDir, "web", { registry: fakeRegistry() });
|
|
619
|
+
expect(result.selected).toEqual(["web"]);
|
|
620
|
+
|
|
621
|
+
const listed = await runComponents(testDir, "web,worker", { registry: fakeRegistry(), onPlan: (p) => plans.push(p) });
|
|
622
|
+
expect(listed.success).toBe(true);
|
|
623
|
+
expect([...listed.selected].sort()).toEqual(["api", "cache", "db", "web", "worker"]);
|
|
624
|
+
expect(listed.run?.waves).toEqual([["cache", "db"], ["api", "worker"], ["web"]].map((w) => expect.arrayContaining(w)));
|
|
625
|
+
const order = listed.run?.order ?? [];
|
|
626
|
+
expect(order.indexOf("db")).toBeLessThan(order.indexOf("api"));
|
|
627
|
+
expect(order.indexOf("api")).toBeLessThan(order.indexOf("web"));
|
|
628
|
+
expect(plans).toHaveLength(1);
|
|
629
|
+
expect(plans[0]).toMatchObject({ order: listed.run?.order, waves: listed.run?.waves });
|
|
630
|
+
expect([...(plans[0] as { added: string[] }).added].sort()).toEqual(["api", "cache", "db"]);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
test("leaves out components the list does not reach", async () => {
|
|
634
|
+
const result = await runComponents(testDir, "api, api ,", { registry: fakeRegistry() });
|
|
635
|
+
expect(result.success).toBe(true);
|
|
636
|
+
expect(result.run?.order).toEqual(["db", "api"]);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
test("refuses unknown names, naming every one, before anything runs", async () => {
|
|
640
|
+
const onPlan = vi.fn();
|
|
641
|
+
const result = await runComponents(testDir, "api,nope,also-nope", { registry: fakeRegistry(), onPlan });
|
|
642
|
+
expect(result.success).toBe(false);
|
|
643
|
+
expect(result.run).toBeUndefined();
|
|
644
|
+
expect(result.error).toContain('Components "nope", "also-nope" not found');
|
|
645
|
+
expect(result.error).toContain("Known components: api, cache, db, web, worker");
|
|
646
|
+
expect(onPlan).not.toHaveBeenCalled();
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test("refuses a disabled name; a disabled dependency is dropped as \"all\" drops it", async () => {
|
|
650
|
+
await writeFile(join(testDir, "cache.component.ts"), comp("cache", [], "enabled: false, "));
|
|
651
|
+
const refused = await runComponents(testDir, "api,cache", { registry: fakeRegistry() });
|
|
652
|
+
expect(refused.success).toBe(false);
|
|
653
|
+
expect(refused.run).toBeUndefined();
|
|
654
|
+
expect(refused.error).toContain('Component "cache" is disabled');
|
|
655
|
+
|
|
656
|
+
const result = await runComponents(testDir, "web", { registry: fakeRegistry() });
|
|
657
|
+
expect(result.success).toBe(true);
|
|
658
|
+
const listed = await runComponents(testDir, "web,", { registry: fakeRegistry() });
|
|
659
|
+
expect(listed.success).toBe(true);
|
|
660
|
+
expect(listed.run?.order).toEqual(["db", "api", "web"]);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
test("refuses \"all\" inside a list", async () => {
|
|
664
|
+
const result = await runComponents(testDir, "all,api", { registry: fakeRegistry() });
|
|
665
|
+
expect(result.success).toBe(false);
|
|
666
|
+
expect(result.error).toContain('"all" cannot be part of a component list');
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
test("a dependency outside the discovered set fails as it does under \"all\"", async () => {
|
|
670
|
+
await writeFile(join(testDir, "api.component.ts"), comp("api", ["shared-alb"]));
|
|
671
|
+
const onPlan = vi.fn();
|
|
672
|
+
const result = await runComponents(testDir, "api,web", { registry: fakeRegistry(), onPlan });
|
|
673
|
+
expect(result.success).toBe(false);
|
|
674
|
+
expect(result.error).toContain("shared-alb");
|
|
675
|
+
expect(onPlan).not.toHaveBeenCalled();
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
|
|
604
679
|
test("unknown component name → clear error listing known components", async () => {
|
|
605
680
|
await writeFile(
|
|
606
681
|
join(testDir, "svc.component.ts"),
|