@intentius/chant 0.124.31 → 0.124.32
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/bin/chant +7 -2
- package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +10 -7
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +18 -12
- package/dist/cli/commands/import.d.ts +10 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/onboard.js +8 -4
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/dependencies.d.ts +18 -0
- package/dist/cli/dependencies.d.ts.map +1 -0
- package/dist/cli/dependencies.js +120 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +23 -4
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +12 -4
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +16 -6
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/lint.js +6 -0
- package/dist/cli/handlers/operator.d.ts +2 -2
- package/dist/cli/handlers/operator.js +3 -3
- package/dist/cli/handlers/pr.d.ts +14 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +488 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +3 -1
- package/dist/cli/handlers/run.d.ts +13 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +38 -15
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +44 -16
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +15 -0
- package/dist/cli/registry.d.ts +13 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +6 -2
- package/dist/codegen/generate-post-synth-barrel.js +2 -2
- package/dist/codegen/lexicon-regen.d.ts +4 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.js +1 -1
- package/dist/codegen/pinned-upgrade.js +2 -2
- package/dist/components/capability.d.ts +44 -3
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +21 -0
- package/dist/components/changed-files.d.ts +7 -1
- package/dist/components/changed-files.d.ts.map +1 -1
- package/dist/components/changed-files.js +9 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +47 -6
- package/dist/components/fan-out-record.d.ts +6 -1
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +24 -0
- package/dist/components/fan-out-run.d.ts +20 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +21 -1
- package/dist/components/pr-pipeline.d.ts +65 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +107 -15
- package/dist/components/pr-run.d.ts +127 -29
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/wave-plan.d.ts +5 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +12 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +16 -6
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.js +6 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.js +8 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/fold.js +68 -1
- package/dist/fold/subset.d.ts +37 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/fold/subset.js +88 -0
- package/dist/lexicon-module.d.ts +10 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +53 -8
- package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
- package/dist/lexicon-plugin-helpers.js +6 -4
- package/dist/lexicon.d.ts +2 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/affected.d.ts.map +1 -1
- package/dist/lifecycle/affected.js +7 -1
- package/dist/lifecycle/release-ledger.d.ts +21 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +24 -0
- package/dist/lifecycle/work-dir.d.ts +45 -0
- package/dist/lifecycle/work-dir.d.ts.map +1 -0
- package/dist/lifecycle/work-dir.js +78 -0
- package/dist/lint/discover.d.ts +17 -9
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +62 -54
- package/dist/module-source.d.ts +57 -0
- package/dist/module-source.d.ts.map +1 -0
- package/dist/module-source.js +104 -0
- package/dist/op/activities/factory.js +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +13 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +20 -5
- package/dist/op/operator.d.ts +2 -2
- package/dist/op/operator.js +2 -2
- package/dist/op/steward.d.ts +6 -4
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/steward.js +5 -3
- package/dist/op/work-lease-run.d.ts +20 -7
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/op/work-lease-run.js +56 -12
- package/dist/pr-forge.d.ts +10 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +39 -0
- package/dist/pr-loop.d.ts +141 -4
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +122 -6
- package/dist/pr-report-url.d.ts +2 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +5 -5
- package/dist/ts-loader.d.ts +38 -4
- package/dist/ts-loader.d.ts.map +1 -1
- package/dist/ts-loader.js +190 -16
- package/dist/wave-runtime.mjs +208 -208
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -1
- package/dist/workspace/chant-migrations.js +2 -0
- package/dist/workspace/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/ci-green-forge.d.ts +6 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +28 -12
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +49 -21
- package/dist/workspace/ci-list.schema.json +12 -6
- package/dist/workspace/ci-report-url.d.ts +18 -0
- package/dist/workspace/ci-report-url.d.ts.map +1 -0
- package/dist/workspace/ci-report-url.js +33 -0
- package/dist/workspace/ci-run-index.d.ts +12 -0
- package/dist/workspace/ci-run-index.d.ts.map +1 -1
- package/dist/workspace/ci-run-index.js +6 -4
- package/dist/workspace/ci-run-index.schema.json +5 -3
- package/dist/workspace/ci-run.d.ts +75 -2
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +195 -53
- package/dist/workspace/ci-runner-key.d.ts +16 -3
- package/dist/workspace/ci-runner-key.d.ts.map +1 -1
- package/dist/workspace/ci-runner-key.js +30 -10
- package/dist/workspace/ci-serve.d.ts +54 -4
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +175 -26
- package/dist/workspace/declaration.d.ts +8 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +11 -2
- package/dist/workspace/declaration.schema.json +7 -2
- package/dist/workspace/export.d.ts +1 -1
- package/dist/workspace/export.d.ts.map +1 -1
- package/dist/workspace/export.js +7 -3
- package/dist/workspace/ls.d.ts +6 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +1 -0
- package/dist/workspace/ls.schema.json +7 -1
- package/dist/workspace/pin-integrity.d.ts.map +1 -1
- package/dist/workspace/pin-integrity.js +3 -2
- package/dist/workspace/pr-report.schema.json +74 -7
- package/dist/workspace/reason-codes.d.ts +1 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +2 -2
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/status.schema.json +3 -1
- package/dist/workspace/write-scope.d.ts +9 -0
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/workspace/write-scope.js +11 -1
- package/package.json +1 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
- package/src/cli/commands/audit.test.ts +2 -2
- package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
- package/src/cli/commands/check-lexicon-plugin.ts +11 -8
- package/src/cli/commands/check-lexicon.test.ts +1 -1
- package/src/cli/commands/check-lexicon.ts +16 -11
- package/src/cli/commands/import.ts +40 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
- package/src/cli/commands/init-lexicon.test.ts +8 -5
- package/src/cli/commands/onboard.test.ts +45 -0
- package/src/cli/commands/onboard.ts +8 -4
- package/src/cli/commands/update.test.ts +6 -0
- package/src/cli/commands/update.ts +4 -1
- package/src/cli/compiled-cli.e2e.test.ts +76 -11
- package/src/cli/dependencies.e2e.test.ts +101 -0
- package/src/cli/dependencies.test.ts +93 -0
- package/src/cli/dependencies.ts +122 -0
- package/src/cli/handlers/build.test.ts +69 -0
- package/src/cli/handlers/build.ts +20 -4
- package/src/cli/handlers/fan-out.test.ts +73 -0
- package/src/cli/handlers/fan-out.ts +11 -4
- package/src/cli/handlers/graph.test.ts +76 -0
- package/src/cli/handlers/graph.ts +19 -12
- package/src/cli/handlers/lint.test.ts +42 -0
- package/src/cli/handlers/lint.ts +6 -0
- package/src/cli/handlers/operator.ts +3 -3
- package/src/cli/handlers/pr-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +134 -0
- package/src/cli/handlers/pr.ts +523 -31
- package/src/cli/handlers/promote.test.ts +26 -1
- package/src/cli/handlers/promote.ts +3 -2
- package/src/cli/handlers/run-components-exit.test.ts +99 -0
- package/src/cli/handlers/run-signer.test.ts +79 -0
- package/src/cli/handlers/run.ts +34 -16
- package/src/cli/main.test.ts +18 -0
- package/src/cli/main.ts +43 -16
- package/src/cli/parse-args.ts +12 -0
- package/src/cli/registry.ts +19 -6
- package/src/codegen/generate-post-synth-barrel.ts +2 -2
- package/src/codegen/lexicon-regen.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -2
- package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
- package/src/components/capability.ts +51 -3
- package/src/components/changed-files.test.ts +16 -1
- package/src/components/changed-files.ts +9 -1
- package/src/components/driver.test.ts +36 -1
- package/src/components/driver.ts +76 -4
- package/src/components/fan-out-record.ts +29 -1
- package/src/components/fan-out-run.test.ts +35 -0
- package/src/components/fan-out-run.ts +40 -1
- package/src/components/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +132 -17
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +354 -75
- package/src/components/wave-plan.ts +16 -4
- package/src/discovery/fold-host-method.test.ts +87 -0
- package/src/discovery/fold-import.ts +15 -5
- package/src/discovery/fold-package-composite.test.ts +27 -0
- package/src/discovery/fold-specifiers.test.ts +8 -0
- package/src/discovery/sandbox/bundle.ts +6 -0
- package/src/discovery/specifier-resolve.ts +8 -2
- package/src/fold/fold.test.ts +72 -0
- package/src/fold/fold.ts +71 -0
- package/src/fold/subset.test.ts +31 -0
- package/src/fold/subset.ts +99 -0
- package/src/lexicon-dist.e2e.test.ts +254 -0
- package/src/lexicon-module.test.ts +47 -0
- package/src/lexicon-module.ts +49 -7
- package/src/lexicon-plugin-helpers.ts +6 -4
- package/src/lexicon.ts +2 -0
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/release-ledger.ts +34 -0
- package/src/lifecycle/work-dir.test.ts +77 -0
- package/src/lifecycle/work-dir.ts +82 -0
- package/src/lint/discover.test.ts +46 -2
- package/src/lint/discover.ts +55 -49
- package/src/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/factory.ts +1 -1
- package/src/op/discover-root.test.ts +31 -2
- package/src/op/discover.ts +14 -4
- package/src/op/gate.test.ts +30 -0
- package/src/op/gate.ts +3 -0
- package/src/op/op-waves-run.ts +20 -5
- package/src/op/op-waves.test.ts +49 -0
- package/src/op/operator.ts +2 -2
- package/src/op/steward.ts +6 -4
- package/src/op/work-lease-run.test.ts +88 -2
- package/src/op/work-lease-run.ts +52 -12
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +42 -0
- package/src/pr-forge.ts +40 -0
- package/src/pr-loop-bundle.test.ts +2 -1
- package/src/pr-loop.test.ts +117 -1
- package/src/pr-loop.ts +252 -10
- package/src/pr-report-url.ts +5 -5
- package/src/terraform/__fixtures__/advise.test.ts +21 -2
- package/src/ts-loader.test.ts +89 -1
- package/src/ts-loader.ts +180 -13
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
- package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
- package/src/workspace/chant-migrations.e2e.test.ts +5 -0
- package/src/workspace/chant-migrations.ts +2 -0
- package/src/workspace/checks/boxes.ts +9 -9
- package/src/workspace/ci-green-forge.ts +6 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green.test.ts +1 -1
- package/src/workspace/ci-green.ts +50 -22
- package/src/workspace/ci-list.schema.json +12 -6
- package/src/workspace/ci-report-url.test.ts +220 -0
- package/src/workspace/ci-report-url.ts +33 -0
- package/src/workspace/ci-run-index.schema.json +5 -3
- package/src/workspace/ci-run-index.test.ts +42 -3
- package/src/workspace/ci-run-index.ts +18 -4
- package/src/workspace/ci-run.test.ts +185 -7
- package/src/workspace/ci-run.ts +224 -54
- package/src/workspace/ci-runner-key.test.ts +55 -17
- package/src/workspace/ci-runner-key.ts +36 -11
- package/src/workspace/ci-serve.test.ts +150 -6
- package/src/workspace/ci-serve.ts +169 -25
- package/src/workspace/declaration.schema.json +7 -2
- package/src/workspace/declaration.test.ts +23 -0
- package/src/workspace/declaration.ts +19 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +7 -1
- package/src/workspace/ls.ts +7 -2
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +74 -7
- package/src/workspace/reason-codes.ts +2 -2
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
|
@@ -24,6 +24,8 @@ const writtenTemplates: unknown[] = [];
|
|
|
24
24
|
let configEnvironments: unknown[] | undefined;
|
|
25
25
|
let publishedDigest = "sha256:api";
|
|
26
26
|
let failApplyFor: string | undefined;
|
|
27
|
+
/** What `failApplyFor`'s apply throws; a plain error when unset. */
|
|
28
|
+
let applyError: Error | undefined;
|
|
27
29
|
|
|
28
30
|
vi.mock("../../lifecycle/git", () => ({
|
|
29
31
|
getHeadCommit: async () => "abc123",
|
|
@@ -58,7 +60,7 @@ vi.mock("../../components/fan-out-support", () => ({
|
|
|
58
60
|
step("docker-build", () => ({ digest: "sha256:rebuilt" }));
|
|
59
61
|
step("publish-image", () => ({ digest: publishedDigest, uri: `reg/api@${publishedDigest}` }));
|
|
60
62
|
step("apply", (ctx) => {
|
|
61
|
-
if (ctx.component === failApplyFor) throw new Error("apply failed");
|
|
63
|
+
if (ctx.component === failApplyFor) throw applyError ?? new Error("apply failed");
|
|
62
64
|
return {};
|
|
63
65
|
});
|
|
64
66
|
// #4045: a planner whose member is provisional.
|
|
@@ -221,6 +223,7 @@ beforeEach(() => {
|
|
|
221
223
|
configEnvironments = undefined;
|
|
222
224
|
publishedDigest = "sha256:api";
|
|
223
225
|
failApplyFor = undefined;
|
|
226
|
+
applyError = undefined;
|
|
224
227
|
errors = [];
|
|
225
228
|
logs = [];
|
|
226
229
|
vi.spyOn(console, "error").mockImplementation((...m: unknown[]) => { errors.push(m.join(" ")); });
|
|
@@ -358,6 +361,21 @@ describe("chant components promote", () => {
|
|
|
358
361
|
expect(appendReleaseRecordMock.mock.calls[0][0]).toMatchObject({ component: "api", env: "prod" });
|
|
359
362
|
});
|
|
360
363
|
|
|
364
|
+
test.each([
|
|
365
|
+
["marked planMoved", 3, { planMoved: true }],
|
|
366
|
+
["with exitCode 3", 3, { exitCode: 3 }],
|
|
367
|
+
["with exitCode 7", 1, { exitCode: 7 }],
|
|
368
|
+
])("an apply that refuses its plan %s exits %i, and what deployed is still recorded (#3997)", async (_label, code, mark) => {
|
|
369
|
+
const web: DriverComponent = { ...api, name: "web" };
|
|
370
|
+
resolveTargetsMock.mockResolvedValue({ success: true, targets: [api, web] });
|
|
371
|
+
readReleaseLedgerMock.mockResolvedValue({ records: [staging, { ...staging, component: "web" }], malformed: 0 });
|
|
372
|
+
failApplyFor = "web";
|
|
373
|
+
applyError = Object.assign(new Error("web: refusing to apply, the plan moved"), mark);
|
|
374
|
+
expect(await runComponentsPromote(ctx({}))).toBe(code);
|
|
375
|
+
expect(appendReleaseRecordMock).toHaveBeenCalledTimes(1);
|
|
376
|
+
expect(errors.join("\n")).toMatch(/promote failed at "web"/);
|
|
377
|
+
});
|
|
378
|
+
|
|
361
379
|
test("--digest <component>=<digest> promotes the pinned release, not the latest one (#2602)", async () => {
|
|
362
380
|
const web: DriverComponent = { ...api, name: "web" };
|
|
363
381
|
resolveTargetsMock.mockResolvedValue({ success: true, targets: [api, web] });
|
|
@@ -795,6 +813,13 @@ describe("chant components redeploy", () => {
|
|
|
795
813
|
]);
|
|
796
814
|
expect(errors.join("\n")).toMatch(/redeploy failed at "api"/);
|
|
797
815
|
});
|
|
816
|
+
|
|
817
|
+
test("a redeploy whose apply refuses a moved plan exits 3 (#3997)", async () => {
|
|
818
|
+
failApplyFor = "api";
|
|
819
|
+
applyError = Object.assign(new Error("api: refusing to apply, the plan moved"), { planMoved: true });
|
|
820
|
+
expect(await runComponentsRedeploy(rd({}))).toBe(3);
|
|
821
|
+
expect(appendReleaseRecordMock).not.toHaveBeenCalled();
|
|
822
|
+
});
|
|
798
823
|
});
|
|
799
824
|
|
|
800
825
|
describe("--events-json on promote, rollback and redeploy (#4041)", () => {
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
type PromotionItem,
|
|
59
59
|
type PromotionPlan,
|
|
60
60
|
} from "../../components/promote";
|
|
61
|
-
import type
|
|
61
|
+
import { failedRunExitCode, type DriverComponent } from "../../components/driver";
|
|
62
62
|
import { summaryLedgerPrefix, writeGatedRunSummary } from "../../op/gate-summary";
|
|
63
63
|
import { approveCommand, describeGateMismatch, gateIsSealed, gitGateLedgerPort, type GateLedgerPort } from "../../op/gate";
|
|
64
64
|
import type { ReleaseGateBinding } from "../../components/driver";
|
|
@@ -522,7 +522,8 @@ async function deployPlan(
|
|
|
522
522
|
|
|
523
523
|
if (run.status !== "ok") {
|
|
524
524
|
console.error(formatError({ message: `${verb} failed at "${run.failedComponent ?? "unknown"}"` }));
|
|
525
|
-
|
|
525
|
+
// A step that refused a plan that moved since it was approved exits 3 (#3997).
|
|
526
|
+
return failedRunExitCode(run.results);
|
|
526
527
|
}
|
|
527
528
|
return 0;
|
|
528
529
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant run --components` exits 3 when a step refuses a plan that moved
|
|
3
|
+
* since it was approved (#3997), on the single-component path and on the
|
|
4
|
+
* driver's `all` and list paths. Discovery and the capability registry are
|
|
5
|
+
* fakes; the run is the real `runComponents` on the local runtime.
|
|
6
|
+
*/
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
8
|
+
import type { Component } from "../../components/component";
|
|
9
|
+
import type { ParsedArgs } from "../registry";
|
|
10
|
+
|
|
11
|
+
const discoverComponentsMock = vi.fn();
|
|
12
|
+
vi.mock("../../components/discover", () => ({
|
|
13
|
+
discoverComponents: (...args: unknown[]) => discoverComponentsMock(...args),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("../../config", async () => {
|
|
17
|
+
const actual = await vi.importActual<typeof import("../../config")>("../../config");
|
|
18
|
+
return { ...actual, loadChantConfig: async () => ({ config: {} }) };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/** The error a component's step throws, in place of applying. */
|
|
22
|
+
const refusal = new Map<string, Error>();
|
|
23
|
+
|
|
24
|
+
vi.mock("../../op/runtimes/local", async () => {
|
|
25
|
+
const { CapabilityRegistry } = await import("../../components/capability");
|
|
26
|
+
const { runComponents } = await import("../../components/cli-support");
|
|
27
|
+
const { memoryGateLedgerPort } = await import("../../op/gate");
|
|
28
|
+
const registry = new CapabilityRegistry();
|
|
29
|
+
registry.register({
|
|
30
|
+
kind: "fake-apply",
|
|
31
|
+
async run(ctx: { component: string }) {
|
|
32
|
+
const err = refusal.get(ctx.component);
|
|
33
|
+
if (err) throw err;
|
|
34
|
+
return {};
|
|
35
|
+
},
|
|
36
|
+
} as never);
|
|
37
|
+
return {
|
|
38
|
+
createLocalOpRuntime: () => ({
|
|
39
|
+
name: "local",
|
|
40
|
+
runComponents: (path: string, selector: string, options: object) =>
|
|
41
|
+
runComponents(path, selector, { ...options, registry, config: {}, revision: null, gates: memoryGateLedgerPort() }),
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const { runOpComponents } = await import("./run");
|
|
47
|
+
|
|
48
|
+
const component = (name: string, dependsOn: string[] = []): Component => ({
|
|
49
|
+
name,
|
|
50
|
+
dependsOn,
|
|
51
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "fake-apply" }] }],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const ctx = (selector: string) =>
|
|
55
|
+
({
|
|
56
|
+
args: { command: "run", path: selector, components: true, on: "local", format: "", fix: false, watch: false, verbose: false, help: false, live: false } as ParsedArgs,
|
|
57
|
+
plugins: [],
|
|
58
|
+
}) as never;
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
refusal.clear();
|
|
62
|
+
const estate = [component("net"), component("app", ["net"])];
|
|
63
|
+
discoverComponentsMock.mockResolvedValue({
|
|
64
|
+
components: new Map(estate.map((c) => [c.name, { component: c, exportName: c.name, filePath: `${c.name}.component.ts` }])),
|
|
65
|
+
sourceFiles: [],
|
|
66
|
+
errors: [],
|
|
67
|
+
});
|
|
68
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
69
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
70
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
vi.restoreAllMocks();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const marks: Array<[string, number, object]> = [
|
|
78
|
+
["marked planMoved", 3, { planMoved: true }],
|
|
79
|
+
["with exitCode 3", 3, { exitCode: 3 }],
|
|
80
|
+
["with exitCode 7", 1, { exitCode: 7 }],
|
|
81
|
+
["unmarked", 1, {}],
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
describe("chant run --components: a step that refuses a moved plan exits 3 (#3997)", () => {
|
|
85
|
+
test.each(marks)("one component, its step refusing %s: exit %i", async (_label, code, mark) => {
|
|
86
|
+
refusal.set("app", Object.assign(new Error("app: refusing to apply, the plan moved"), mark));
|
|
87
|
+
expect(await runOpComponents(ctx("app"))).toBe(code);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test.each(marks)("all, a step refusing %s: exit %i", async (_label, code, mark) => {
|
|
91
|
+
refusal.set("net", Object.assign(new Error("net: refusing to apply, the plan moved"), mark));
|
|
92
|
+
expect(await runOpComponents(ctx("all"))).toBe(code);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("a list that pulls in a dependency whose step refuses a moved plan exits 3", async () => {
|
|
96
|
+
refusal.set("net", Object.assign(new Error("net: refusing to apply, the plan moved"), { planMoved: true }));
|
|
97
|
+
expect(await runOpComponents(ctx("app,net"))).toBe(3);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4156: `chant run`'s runner key is opt-in hardening. A `CHANT_RUNNER_KEY`
|
|
3
|
+
* that `.chant/trust.json` does not list leaves the run unsigned with no
|
|
4
|
+
* warning, unless the declaration sets `ci.green.signedSince`. A key that is
|
|
5
|
+
* not an Ed25519 PEM is an error either way.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createPublicKey, generateKeyPairSync } from "node:crypto";
|
|
9
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
10
|
+
import { cleanScratch, declaration, git, scratchDir, writeFiles } from "../../workspace/__fixtures__/contract-repo";
|
|
11
|
+
import { sshFromEd25519 } from "../../workspace/trust/dsse";
|
|
12
|
+
import type { CommandContext } from "../registry";
|
|
13
|
+
import { runSignerFor } from "./run";
|
|
14
|
+
|
|
15
|
+
const home = process.cwd();
|
|
16
|
+
const saved = process.env.CHANT_RUNNER_KEY;
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
process.chdir(home);
|
|
19
|
+
if (saved === undefined) delete process.env.CHANT_RUNNER_KEY;
|
|
20
|
+
else process.env.CHANT_RUNNER_KEY = saved;
|
|
21
|
+
vi.restoreAllMocks();
|
|
22
|
+
cleanScratch();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function key(): { pem: string; ssh: string } {
|
|
26
|
+
const { privateKey } = generateKeyPairSync("ed25519");
|
|
27
|
+
const jwk = createPublicKey(privateKey).export({ format: "jwk" }) as { x: string };
|
|
28
|
+
return { pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(), ssh: sshFromEd25519(Buffer.from(jwk.x, "base64url")) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function repo(green?: Record<string, unknown>): string {
|
|
32
|
+
const root = scratchDir("chant-run-signer-");
|
|
33
|
+
git(root, "init", "-q");
|
|
34
|
+
for (const [k, v] of [["user.name", "t"], ["user.email", "t@example.com"], ["commit.gpgsign", "false"]]) git(root, "config", k!, v!);
|
|
35
|
+
writeFiles(root, {
|
|
36
|
+
"chant.workspace.json": declaration([{ name: "app", dir: "app", kind: "other", because: "the app" }], green ? { ci: { green } } : {}),
|
|
37
|
+
".chant/trust.json": JSON.stringify({ schema: 1, runners: [{ principal: "ci-bot", class: "runner", key: key().ssh }], roles: { runner: ["ci-bot"] } }),
|
|
38
|
+
"app/a.txt": "a\n",
|
|
39
|
+
});
|
|
40
|
+
git(root, "add", "-A");
|
|
41
|
+
git(root, "commit", "-q", "-m", "init");
|
|
42
|
+
return root;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function signer(root: string, pem: string, verbose = false) {
|
|
46
|
+
process.chdir(root);
|
|
47
|
+
process.env.CHANT_RUNNER_KEY = pem;
|
|
48
|
+
const err: string[] = [];
|
|
49
|
+
vi.spyOn(console, "error").mockImplementation((s: string) => void err.push(String(s)));
|
|
50
|
+
const result = await runSignerFor({ args: { verbose } } as unknown as CommandContext);
|
|
51
|
+
return { result, err: err.join("\n") };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe("chant run's runner key from the environment (#4156)", () => {
|
|
55
|
+
test("a key trust.json does not list leaves the run unsigned and quiet, with one line under --verbose", async () => {
|
|
56
|
+
const root = repo();
|
|
57
|
+
const quiet = await signer(root, key().pem);
|
|
58
|
+
expect(quiet.result).toBeUndefined();
|
|
59
|
+
expect(quiet.err).toBe("");
|
|
60
|
+
const verbose = await signer(root, key().pem, true);
|
|
61
|
+
expect(verbose.result).toBeUndefined();
|
|
62
|
+
expect(verbose.err).toContain("CHANT_RUNNER_KEY is set, and the run is not signed");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("with ci.green.signedSince the same key warns", async () => {
|
|
66
|
+
const root = repo();
|
|
67
|
+
const head = git(root, "rev-parse", "HEAD");
|
|
68
|
+
writeFiles(root, { "chant.workspace.json": declaration([{ name: "app", dir: "app", kind: "other", because: "the app" }], { ci: { green: { branch: "main", phases: { test: ["test"] }, require: ["test"], signedSince: head } } }) });
|
|
69
|
+
const r = await signer(root, key().pem);
|
|
70
|
+
expect(r.result).toBeUndefined();
|
|
71
|
+
expect(r.err).toContain(`ci.green.signedSince (${head}) will not count it`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("a key that is not an Ed25519 PEM is an error", async () => {
|
|
75
|
+
const r = await signer(repo(), "not a key");
|
|
76
|
+
expect(r.result).toBeNull();
|
|
77
|
+
expect(r.err).toContain("CHANT_RUNNER_KEY is not an Ed25519 private key in PEM");
|
|
78
|
+
});
|
|
79
|
+
});
|
package/src/cli/handlers/run.ts
CHANGED
|
@@ -31,7 +31,7 @@ import type { RunEvent } from "../../run-event";
|
|
|
31
31
|
import { openCliRunEvents, refusesBothStreams, stdoutLine } from "../run-events";
|
|
32
32
|
import { maybeRecordAutoRelease } from "../../components/auto-release";
|
|
33
33
|
import { maybePersistBuildManifest } from "../../components/manifest-persistence";
|
|
34
|
-
import type
|
|
34
|
+
import { failedRunExitCode, type DriverComponentResult } from "../../components/driver";
|
|
35
35
|
import { isComponentList, type ComponentRunPlan, type RunComponentsResult } from "../../components/cli-support";
|
|
36
36
|
import { resolveCiRun, type ReleaseRecord, type RunOrigin } from "../../lifecycle/release-ledger";
|
|
37
37
|
import { gateAdmission, gateApprover } from "../../components/promote";
|
|
@@ -730,9 +730,9 @@ type StewardTurnGate =
|
|
|
730
730
|
* `chant run <op>` for an Op a declared steward lists is one of that
|
|
731
731
|
* steward's turns, not a run beside it (#2750, follow-up to #2731).
|
|
732
732
|
*
|
|
733
|
-
* On fountain, the run belongs
|
|
733
|
+
* On fountain, the run belongs in the steward's conversation: without `--on
|
|
734
734
|
* fountain` this refuses outright, naming the command that posts it there
|
|
735
|
-
* instead (the fountain runtime's own turn already refuses a busy
|
|
735
|
+
* instead (the fountain runtime's own turn already refuses a busy steward,
|
|
736
736
|
* unretried — see chant-fountain-ops).
|
|
737
737
|
*
|
|
738
738
|
* In the local form, the run takes the steward's turn lease
|
|
@@ -771,15 +771,15 @@ async function stewardTurnGate(opName: string, ctx: CommandContext): Promise<Ste
|
|
|
771
771
|
const form = stewardFormFor(owner, env);
|
|
772
772
|
|
|
773
773
|
if (form === "fountain") {
|
|
774
|
-
// `run.on: "fountain"` asks for the steward's
|
|
774
|
+
// `run.on: "fountain"` asks for the steward's conversation as much as the flag
|
|
775
775
|
// does (#2523).
|
|
776
776
|
if ((await chooseOpRuntime(ctx, true))?.on === "fountain") return { ok: true };
|
|
777
777
|
return {
|
|
778
778
|
ok: false,
|
|
779
779
|
message: `Op "${opName}" is one of steward "${owner.name}"'s Ops, which runs on fountain in environment "${env}"`,
|
|
780
780
|
hint:
|
|
781
|
-
`Run \`chant run ${opName} --on fountain\` so it posts to "${owner.name}"'s
|
|
782
|
-
`A plain \`chant run ${opName}\` would run it outside that
|
|
781
|
+
`Run \`chant run ${opName} --on fountain\` so it posts to "${owner.name}"'s conversation. ` +
|
|
782
|
+
`A plain \`chant run ${opName}\` would run it outside that conversation, beside the steward instead of one of its turns.`,
|
|
783
783
|
};
|
|
784
784
|
}
|
|
785
785
|
|
|
@@ -1283,7 +1283,10 @@ async function runComponentSelection(ctx: CommandContext): Promise<number> {
|
|
|
1283
1283
|
}
|
|
1284
1284
|
}
|
|
1285
1285
|
|
|
1286
|
-
|
|
1286
|
+
// A step that refused a plan that moved since it was approved exits 3
|
|
1287
|
+
// (#3997). `--gated-exit` doesn't remap it: the run failed.
|
|
1288
|
+
if (result.success) return 0;
|
|
1289
|
+
return result.run ? failedRunExitCode(result.run.results) : 1;
|
|
1287
1290
|
}
|
|
1288
1291
|
|
|
1289
1292
|
/**
|
|
@@ -1359,9 +1362,10 @@ async function runOpOnResolvedRuntime(ctx: CommandContext): Promise<number> {
|
|
|
1359
1362
|
return 1;
|
|
1360
1363
|
}
|
|
1361
1364
|
|
|
1362
|
-
// #4044: a runner key signs the run's record as it settles, so
|
|
1363
|
-
// can
|
|
1364
|
-
// CHANT_RUNNER_KEY that
|
|
1365
|
+
// #4044: a runner key signs the run's record as it settles, so a reader
|
|
1366
|
+
// can see who ran it. `--key` must be a runner key; the PEM in
|
|
1367
|
+
// CHANT_RUNNER_KEY that trust.json does not list leaves the run unsigned,
|
|
1368
|
+
// with a warning only when ci.green.signedSince asks for signed runs (#4156).
|
|
1365
1369
|
const signRecord = await runSignerFor(ctx);
|
|
1366
1370
|
if (signRecord === null) {
|
|
1367
1371
|
await stewardGate?.release?.();
|
|
@@ -1564,10 +1568,13 @@ const RUNNER_KEY_ENV = "CHANT_RUNNER_KEY";
|
|
|
1564
1568
|
* What signs the run's record (#4044): the runner key `--key` names, or the
|
|
1565
1569
|
* PEM in `CHANT_RUNNER_KEY`, checked against `.chant/trust.json` at `HEAD`.
|
|
1566
1570
|
* Undefined when neither is given. Null, after printing why, when `--key`
|
|
1567
|
-
* is not a runner key
|
|
1568
|
-
*
|
|
1571
|
+
* is not a runner key, or either is not an Ed25519 PEM. A `CHANT_RUNNER_KEY`
|
|
1572
|
+
* that trust.json does not list leaves the run unsigned (#4156): quietly,
|
|
1573
|
+
* with a line under `--verbose`, unless `ci.green.signedSince` asks for
|
|
1574
|
+
* signed runs, which warns. The variable may be set for `ci tick` in a job
|
|
1575
|
+
* that also runs Ops.
|
|
1569
1576
|
*/
|
|
1570
|
-
async function runSignerFor(ctx: CommandContext): Promise<((record: OpRunRecord) => NonNullable<OpRunRecord["statement"]>) | undefined | null> {
|
|
1577
|
+
export async function runSignerFor(ctx: CommandContext): Promise<((record: OpRunRecord) => NonNullable<OpRunRecord["statement"]>) | undefined | null> {
|
|
1571
1578
|
const fromFlag = ctx.args.key !== undefined;
|
|
1572
1579
|
let pem: string | undefined;
|
|
1573
1580
|
try {
|
|
@@ -1577,9 +1584,17 @@ async function runSignerFor(ctx: CommandContext): Promise<((record: OpRunRecord)
|
|
|
1577
1584
|
return null;
|
|
1578
1585
|
}
|
|
1579
1586
|
if (pem === undefined) return undefined;
|
|
1587
|
+
// Loaded only when a key is given, so a run without one loads no workspace code (level 0, #2525).
|
|
1588
|
+
const { opRunSigner, signedRunsRequired } = await import("../../workspace/ci-green");
|
|
1589
|
+
const { loadRunnerKey } = await import("../../workspace/trust/dsse");
|
|
1590
|
+
try {
|
|
1591
|
+
loadRunnerKey(pem);
|
|
1592
|
+
} catch (err) {
|
|
1593
|
+
// A key that is there and is not one is an error either way: the caller asked to sign (#4156).
|
|
1594
|
+
console.error(formatError({ message: `${fromFlag ? `--key ${ctx.args.key}` : RUNNER_KEY_ENV} is not an Ed25519 private key in PEM: ${err instanceof Error ? err.message : String(err)}` }));
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1580
1597
|
try {
|
|
1581
|
-
// Loaded only when a key is given, so a run without one loads no workspace code (level 0, #2525).
|
|
1582
|
-
const { opRunSigner } = await import("../../workspace/ci-green");
|
|
1583
1598
|
return opRunSigner(process.cwd(), pem);
|
|
1584
1599
|
} catch (err) {
|
|
1585
1600
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1587,7 +1602,10 @@ async function runSignerFor(ctx: CommandContext): Promise<((record: OpRunRecord)
|
|
|
1587
1602
|
console.error(formatError({ message: `the run would not be signed: ${message}` }));
|
|
1588
1603
|
return null;
|
|
1589
1604
|
}
|
|
1590
|
-
|
|
1605
|
+
// #4156: signing is opt-in, so a key trust.json does not list warns only when ci.green.signedSince asks for signed runs.
|
|
1606
|
+
const required = signedRunsRequired(process.cwd());
|
|
1607
|
+
if (required) console.error(formatWarning({ message: `${RUNNER_KEY_ENV} is set, and the run will not be signed, so ci.green.signedSince (${required}) will not count it: ${message}` }));
|
|
1608
|
+
else if (ctx.args.verbose) console.error(`${RUNNER_KEY_ENV} is set, and the run is not signed: ${message}`);
|
|
1591
1609
|
return undefined;
|
|
1592
1610
|
}
|
|
1593
1611
|
}
|
package/src/cli/main.test.ts
CHANGED
|
@@ -666,6 +666,18 @@ describe("resolveCommand", () => {
|
|
|
666
666
|
expect(result!.compound).toBe(false);
|
|
667
667
|
});
|
|
668
668
|
|
|
669
|
+
test("`sync` is an alias for `update` (#3988)", () => {
|
|
670
|
+
const result = resolveCommand(makeArgs({ command: "sync" }), [...testRegistry, { name: "update", handler: noop }]);
|
|
671
|
+
expect(result!.def.name).toBe("update");
|
|
672
|
+
expect(result!.compound).toBe(false);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
test("`sync <path>` resolves to `update` with the path kept (#3988)", () => {
|
|
676
|
+
const result = resolveCommand(makeArgs({ command: "sync", path: "./proj" }), [...testRegistry, { name: "update", handler: noop }]);
|
|
677
|
+
expect(result!.def.name).toBe("update");
|
|
678
|
+
expect(result!.compound).toBe(false);
|
|
679
|
+
});
|
|
680
|
+
|
|
669
681
|
test("resolves components status as compound command", () => {
|
|
670
682
|
const result = resolveCommand(makeArgs({ command: "components", path: "status" }), testRegistry);
|
|
671
683
|
expect(result!.def.name).toBe("components status");
|
|
@@ -1021,4 +1033,10 @@ describe("chant --help", () => {
|
|
|
1021
1033
|
const commands = new Set(commandRegistry.map((def) => def.name.split(" ")[0]));
|
|
1022
1034
|
expect([...commands].filter((name) => !listed.has(name))).toEqual([]);
|
|
1023
1035
|
});
|
|
1036
|
+
|
|
1037
|
+
test("names `sync` as the alias of `update` (#3988)", () => {
|
|
1038
|
+
const source = readFileSync(join(import.meta.dirname, "main.ts"), "utf-8");
|
|
1039
|
+
const help = source.slice(source.indexOf("function printHelp("), source.indexOf("Options:", source.indexOf("function printHelp(")));
|
|
1040
|
+
expect(help).toMatch(/^ {2}update \(alias: sync\)/m);
|
|
1041
|
+
});
|
|
1024
1042
|
});
|
package/src/cli/main.ts
CHANGED
|
@@ -238,33 +238,39 @@ CI:
|
|
|
238
238
|
its window, newest first, each green, revoked or
|
|
239
239
|
undecided with its required phases' results. Reads
|
|
240
240
|
local tags; --forge reads check runs for undecided
|
|
241
|
-
commits, and source runs reads the
|
|
242
|
-
|
|
241
|
+
commits, and source runs reads the Op runs on the
|
|
242
|
+
local chant/lifecycle, signed or not. --since replaces the window.
|
|
243
243
|
ci tick [--dry-run] [--forge github] [--source checks|runs] [--key <runner key.pem>]
|
|
244
244
|
Tag each commit within ci.green's window whose
|
|
245
245
|
required phases all passed ci/green/<sha>, and revoke
|
|
246
246
|
a green one that now fails ci/revoked/<sha>. Reads
|
|
247
247
|
check runs from the forge, or with source runs
|
|
248
248
|
(ci.green.source or --source) the Op runs on
|
|
249
|
-
chant/lifecycle
|
|
249
|
+
chant/lifecycle; with ci.green.signedSince, the ones a
|
|
250
|
+
runner key signed. Pushes the
|
|
250
251
|
tags. --key, or the PEM in CHANT_RUNNER_KEY, signs
|
|
251
252
|
each tag with a runner key.
|
|
252
253
|
ci run plan|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>]
|
|
253
|
-
[--forge github|gitlab|forgejo] [--gate <name>] [--
|
|
254
|
-
[--events-json] [--report-url <url>] [--image <image>]
|
|
254
|
+
[--forge github|gitlab|forgejo] [--gate <name>] [--wave-gate <name>]
|
|
255
|
+
[--branch <name>] [--events-json] [--report-url <url>] [--image <image>]
|
|
255
256
|
[--before-script <line>]... [--pr-build]
|
|
256
257
|
[--pr-build-command <line>] [--chant <command>]
|
|
257
258
|
[--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>]
|
|
258
|
-
[-o <dir>]
|
|
259
|
+
[-o <dir>] [--wave <k>] [--no-require-review]
|
|
259
260
|
[--no-status] [--status-url <url template>]
|
|
261
|
+
[--in-place | --local] [--no-push] [--verbose]
|
|
260
262
|
Run the pull-request pipeline's plan or apply job as
|
|
261
263
|
a pipeline Op in a fresh worktree of --commit (default
|
|
262
264
|
HEAD) with a clean environment. --base and --pr stand
|
|
263
265
|
in for the forge's event values. --pr-build runs
|
|
264
266
|
the job's build and lint lines first. Records the run as
|
|
265
|
-
pr-plan or pr-apply on chant/lifecycle,
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
pr-plan or pr-apply on chant/lifecycle, which ci tick
|
|
268
|
+
from runs counts, signed when --key or CHANT_RUNNER_KEY
|
|
269
|
+
gives a runner key, and copies the report to -o
|
|
270
|
+
(default .chant/pr). --in-place runs in this checkout
|
|
271
|
+
at HEAD, as a generated forge job does, and names the
|
|
272
|
+
forge's run in the record; --no-push keeps the record
|
|
273
|
+
local. Exits with the command's code. With a forge
|
|
268
274
|
token it sets a chant/pr-plan or chant/pr-apply commit
|
|
269
275
|
status (pending, then success or failure); --no-status
|
|
270
276
|
turns that off, and --status-url links it.
|
|
@@ -275,9 +281,14 @@ CI:
|
|
|
275
281
|
With --report-url it also adds the run to
|
|
276
282
|
<url>/runs/index.json, newest first, cut to the
|
|
277
283
|
newest --run-index-limit entries (default 200).
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
284
|
+
Without --report-url, the workspace's ci.reportUrl.
|
|
285
|
+
--local runs the job on this machine: the record is
|
|
286
|
+
kept on the local chant/lifecycle with runOrigin
|
|
287
|
+
local and is never pushed, no status is set, nothing
|
|
288
|
+
goes under a report URL, and ci tick does not count it.
|
|
289
|
+
ci runs [--report-url <url>] [--json]
|
|
290
|
+
List the runs in the index at the report URL (default
|
|
291
|
+
the workspace's ci.reportUrl), newest first.
|
|
281
292
|
ci serve --forge forgejo|github --repo <owner/repo> --env <env>
|
|
282
293
|
[--interval <s>] [--max-jobs <n>] [--state <dir>]
|
|
283
294
|
[--author <login>]... [--once] [--rerun-failed] [--unsigned]
|
|
@@ -290,9 +301,10 @@ CI:
|
|
|
290
301
|
each apply's resume record there, and runs at most
|
|
291
302
|
--max-jobs (default 1) jobs at once. --once runs one
|
|
292
303
|
poll and waits for its jobs. --rerun-failed runs a
|
|
293
|
-
failed apply or plan again.
|
|
294
|
-
|
|
295
|
-
|
|
304
|
+
failed apply or plan again. Signs runs with a runner
|
|
305
|
+
key when .chant/trust.json at the target's head lists
|
|
306
|
+
it, and serves unsigned otherwise; with
|
|
307
|
+
ci.green.signedSince it refuses to start without one
|
|
296
308
|
ci serve|run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]
|
|
297
309
|
Make the host's runner key (Ed25519, mode 0600) in the
|
|
298
310
|
state directory when there is none and print its public
|
|
@@ -929,6 +941,10 @@ Component release ledger + status:
|
|
|
929
941
|
member's loop, gate op pr-<n>-<member>;
|
|
930
942
|
--resume <file> finishes an apply that failed
|
|
931
943
|
partway under the same approval;
|
|
944
|
+
--wave-gate <name>: apply a wave at a time, each
|
|
945
|
+
planned after the one before applied, a moved
|
|
946
|
+
wave waiting at <name>-wave-<k>, with --wave <k>
|
|
947
|
+
for one wave per CI job;
|
|
932
948
|
--events-json, both commands: the run-event
|
|
933
949
|
stream on stdout; --report-url <s3 or file URL>,
|
|
934
950
|
both commands: copy the report, the change set
|
|
@@ -998,7 +1014,8 @@ Servers:
|
|
|
998
1014
|
serve mcp Start the MCP server (stdio)
|
|
999
1015
|
|
|
1000
1016
|
Project:
|
|
1001
|
-
update
|
|
1017
|
+
update (alias: sync) Sync lexicon types into .chant/types/ (does not install
|
|
1018
|
+
packages: run npm install first)
|
|
1002
1019
|
doctor Check project health and configuration
|
|
1003
1020
|
|
|
1004
1021
|
Options:
|
|
@@ -1217,6 +1234,9 @@ async function loadPluginsOrExit(path: string, lexicon?: string): Promise<import
|
|
|
1217
1234
|
plugins = await loadPlugins(lexiconNames);
|
|
1218
1235
|
} catch (error) {
|
|
1219
1236
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1237
|
+
// #4116: a lexicon package the project has not installed is named as such first.
|
|
1238
|
+
const { reportUninstalledDependencies } = await import("./dependencies");
|
|
1239
|
+
reportUninstalledDependencies([errorMessage], resolve(path));
|
|
1220
1240
|
console.error(formatError({ message: errorMessage }));
|
|
1221
1241
|
process.exit(1);
|
|
1222
1242
|
}
|
|
@@ -1725,6 +1745,13 @@ const isMain = isEntryPoint(process.argv[1], import.meta.url);
|
|
|
1725
1745
|
if (isMain) {
|
|
1726
1746
|
main().catch(async (err) => {
|
|
1727
1747
|
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
1748
|
+
// #4116: a failure to import a package the project has not installed is named as such first.
|
|
1749
|
+
try {
|
|
1750
|
+
const { reportUninstalledDependencies } = await import("./dependencies");
|
|
1751
|
+
reportUninstalledDependencies([err instanceof Error ? err.message : String(err)], process.cwd());
|
|
1752
|
+
} catch {
|
|
1753
|
+
// The original error below is what matters.
|
|
1754
|
+
}
|
|
1728
1755
|
if (verbose && err instanceof Error && err.stack) {
|
|
1729
1756
|
console.error(err.stack);
|
|
1730
1757
|
} else {
|
package/src/cli/parse-args.ts
CHANGED
|
@@ -69,6 +69,9 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
69
69
|
"--setup-key",
|
|
70
70
|
"--trust",
|
|
71
71
|
"--unsigned",
|
|
72
|
+
"--in-place",
|
|
73
|
+
"--no-push",
|
|
74
|
+
"--no-require-review",
|
|
72
75
|
"--expire",
|
|
73
76
|
"--check-live",
|
|
74
77
|
"--check-snapshot",
|
|
@@ -777,6 +780,15 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
777
780
|
} else if (arg === "--trust") {
|
|
778
781
|
// `chant ci serve|run --setup-key --trust` (#4099): list the key in .chant/trust.json on a branch.
|
|
779
782
|
result.trustKey = true;
|
|
783
|
+
} else if (arg === "--in-place") {
|
|
784
|
+
// `chant ci run --in-place` (#4156): run the job in this checkout, as a generated forge job does.
|
|
785
|
+
result.ciInPlace = true;
|
|
786
|
+
} else if (arg === "--no-push") {
|
|
787
|
+
// `chant ci run --no-push` (#4156): keep the run record on the local chant/lifecycle.
|
|
788
|
+
result.noPush = true;
|
|
789
|
+
} else if (arg === "--no-require-review") {
|
|
790
|
+
// `chant ci run apply --no-require-review` (#4156): the apply without pr-apply's --require-review.
|
|
791
|
+
result.requireReview = false;
|
|
780
792
|
} else if (arg === "--unsigned") {
|
|
781
793
|
// `chant ci serve --unsigned` (#4099): serve without a runner key listed at the target's head.
|
|
782
794
|
result.unsigned = true;
|
package/src/cli/registry.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface ParsedArgs {
|
|
|
23
23
|
/** `--version` / `-V` (#2701): print the installed chant's version. */
|
|
24
24
|
version?: boolean;
|
|
25
25
|
report?: boolean;
|
|
26
|
-
/** `chant run` — force the local in-process executor (the default). */
|
|
26
|
+
/** `chant run` — force the local in-process executor (the default). `chant ci run --local` (#4161): run the job on this machine with no push, status or report upload. */
|
|
27
27
|
local?: boolean;
|
|
28
28
|
/**
|
|
29
29
|
* `chant run <op> --on <lexicon>` (#2121) — which runtime hosts the run.
|
|
@@ -311,7 +311,8 @@ export interface ParsedArgs {
|
|
|
311
311
|
/**
|
|
312
312
|
* `chant components pr-apply --require-review` (#3183) — count an approval
|
|
313
313
|
* only when its approver has a standing approving review on the pull
|
|
314
|
-
* request. Needs `--forge`.
|
|
314
|
+
* request. Needs `--forge`. `chant ci run apply --no-require-review`
|
|
315
|
+
* (#4156) sets it false, for a pipeline generated with `requireReview: false`.
|
|
315
316
|
*/
|
|
316
317
|
requireReview?: boolean;
|
|
317
318
|
/**
|
|
@@ -466,7 +467,7 @@ export interface ParsedArgs {
|
|
|
466
467
|
ciSecrets?: string;
|
|
467
468
|
/** `chant ci workflow --chant <command>` (#3573): the command the tick step runs chant with, in place of the npx pin. Also `chant ci run --chant` (#4094): what runs chant in the job. */
|
|
468
469
|
ciChant?: string;
|
|
469
|
-
/** `chant ci run|serve --no-status` (#4097): post no commit status for the job. */
|
|
470
|
+
/** `chant ci run|serve --no-status` (#4097): post no commit status for the job. `chant components pr-plan|pr-apply --no-status` (#4161): post neither the status nor the note. */
|
|
470
471
|
noStatus?: boolean;
|
|
471
472
|
/** `chant ci run|serve --status-url <template>` (#4097): the link a job's status carries. `{key}` is `<pr>/<commit>[/<member>]`, as `--report-url` keys it. */
|
|
472
473
|
statusUrl?: string;
|
|
@@ -502,8 +503,16 @@ export interface ParsedArgs {
|
|
|
502
503
|
setupKey?: boolean;
|
|
503
504
|
/** `chant ci serve|run --setup-key --trust` (#4099): list the runner key in .chant/trust.json on a branch pushed for review. */
|
|
504
505
|
trustKey?: boolean;
|
|
505
|
-
/**
|
|
506
|
+
/**
|
|
507
|
+
* `chant ci serve --unsigned` (#4099): serve without signing. Since #4156
|
|
508
|
+
* that is the default without a trusted key, so it only skips the key, and
|
|
509
|
+
* is ignored when ci.green.signedSince requires signed runs.
|
|
510
|
+
*/
|
|
506
511
|
unsigned?: boolean;
|
|
512
|
+
/** `chant ci run --in-place` (#4156): run the job in this checkout at HEAD, as a generated forge job does. */
|
|
513
|
+
ciInPlace?: boolean;
|
|
514
|
+
/** `chant ci run --no-push` (#4156): write the run record on the local chant/lifecycle and push nothing. */
|
|
515
|
+
noPush?: boolean;
|
|
507
516
|
/** `chant ci serve|run --setup-key --trust --principal <name>` (#4099): the principal the runner key is listed for. */
|
|
508
517
|
ciPrincipal?: string;
|
|
509
518
|
/** `chant workspace trust runner add <principal> --class runner|service` (#3510): the runner entry's class. */
|
|
@@ -757,8 +766,12 @@ export interface ResolvedCommand {
|
|
|
757
766
|
* Supports compound commands like "dev generate" where args.command="dev"
|
|
758
767
|
* and args.path="generate". Falls back to simple command matching.
|
|
759
768
|
*/
|
|
760
|
-
/**
|
|
761
|
-
|
|
769
|
+
/**
|
|
770
|
+
* Command aliases. `chant lc …` is sugar for `chant lifecycle …`, and
|
|
771
|
+
* `chant sync` for `chant update`, the command that syncs lexicon types
|
|
772
|
+
* (chant #3988).
|
|
773
|
+
*/
|
|
774
|
+
const COMMAND_ALIASES: Record<string, string> = { lc: "lifecycle", sync: "update" };
|
|
762
775
|
|
|
763
776
|
export function resolveCommand(args: ParsedArgs, registry: CommandDef[]): ResolvedCommand | null {
|
|
764
777
|
const command = COMMAND_ALIASES[args.command] ?? args.command;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { createRequire } from "module";
|
|
17
|
-
import { listRuleFiles } from "../lint/discover";
|
|
17
|
+
import { listRuleFiles, ruleModuleBase } from "../lint/discover";
|
|
18
18
|
import { isPostSynthCheck } from "../lint/post-synth";
|
|
19
19
|
|
|
20
20
|
export interface BarrelEntry {
|
|
@@ -34,7 +34,7 @@ export function scanPostSynthChecks(dir: string, importMetaUrl: string): BarrelE
|
|
|
34
34
|
const require = createRequire(importMetaUrl);
|
|
35
35
|
const entries: BarrelEntry[] = [];
|
|
36
36
|
for (const file of listRuleFiles(dir)) {
|
|
37
|
-
const base = file
|
|
37
|
+
const base = ruleModuleBase(file);
|
|
38
38
|
let mod: Record<string, unknown>;
|
|
39
39
|
try {
|
|
40
40
|
mod = require(`${dir}/${base}`) as Record<string, unknown>;
|
|
@@ -402,7 +402,7 @@ function runExampleHarness(lexiconDir: string, verbose: boolean): RegenFailure |
|
|
|
402
402
|
/**
|
|
403
403
|
* Extract the fresh surface from the generated artifacts in the lexicon directory.
|
|
404
404
|
*/
|
|
405
|
-
function extractFreshSurface(lexiconDir: string): SurfaceSnapshot | null {
|
|
405
|
+
export function extractFreshSurface(lexiconDir: string): SurfaceSnapshot | null {
|
|
406
406
|
const generatedDir = join(lexiconDir, "src", "generated");
|
|
407
407
|
if (!existsSync(generatedDir)) return null;
|
|
408
408
|
|