@intentius/chant 0.124.40 → 0.124.42
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/__fixtures__/oidc-emulator.d.ts +49 -0
- package/dist/__fixtures__/oidc-emulator.d.ts.map +1 -0
- package/dist/__fixtures__/oidc-emulator.js +89 -0
- package/dist/ci-handover.d.ts +117 -0
- package/dist/ci-handover.d.ts.map +1 -0
- package/dist/ci-handover.js +217 -0
- package/dist/ci-identity.d.ts +190 -0
- package/dist/ci-identity.d.ts.map +1 -0
- package/dist/ci-identity.js +219 -0
- package/dist/cli/handlers/approve.d.ts +2 -1
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +20 -6
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +18 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +42 -5
- package/dist/cli/handlers/gate-resume.d.ts +3 -0
- package/dist/cli/handlers/gate-resume.d.ts.map +1 -1
- package/dist/cli/handlers/gate-resume.js +15 -2
- package/dist/cli/handlers/lock.d.ts +8 -0
- package/dist/cli/handlers/lock.d.ts.map +1 -0
- package/dist/cli/handlers/lock.js +197 -0
- package/dist/cli/handlers/pr-env.d.ts +34 -0
- package/dist/cli/handlers/pr-env.d.ts.map +1 -0
- package/dist/cli/handlers/pr-env.js +436 -0
- package/dist/cli/handlers/pr.d.ts +76 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +615 -55
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +9 -0
- package/dist/cli/handlers/query.d.ts +6 -0
- package/dist/cli/handlers/query.d.ts.map +1 -0
- package/dist/cli/handlers/query.js +80 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +11 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +49 -4
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +46 -2
- package/dist/cli/registry.d.ts +31 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/capability.d.ts +47 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +44 -0
- package/dist/components/cli-support.d.ts +6 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +8 -1
- package/dist/components/driver.d.ts +10 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +52 -2
- package/dist/components/fan-out-run.d.ts +37 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +44 -4
- package/dist/components/gate-plan.d.ts +2 -0
- package/dist/components/gate-plan.d.ts.map +1 -1
- package/dist/components/gate-plan.js +1 -0
- package/dist/components/pr-environment.d.ts +298 -0
- package/dist/components/pr-environment.d.ts.map +1 -0
- package/dist/components/pr-environment.js +406 -0
- package/dist/components/pr-hooks.d.ts +66 -0
- package/dist/components/pr-hooks.d.ts.map +1 -0
- package/dist/components/pr-hooks.js +119 -0
- package/dist/components/pr-pipeline.d.ts +92 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +150 -21
- package/dist/components/pr-policy.d.ts +109 -0
- package/dist/components/pr-policy.d.ts.map +1 -0
- package/dist/components/pr-policy.js +150 -0
- package/dist/components/pr-run.d.ts +6 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +8 -3
- package/dist/components/wave-plan.d.ts +14 -1
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +34 -2
- package/dist/gate-decision.d.ts +15 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +17 -2
- package/dist/gate-policy.d.ts +140 -0
- package/dist/gate-policy.d.ts.map +1 -0
- package/dist/gate-policy.js +242 -0
- package/dist/lifecycle/gate-checkpoint.d.ts +1 -1
- package/dist/lifecycle/gate-checkpoint.js +1 -1
- package/dist/{workspace/trust → lifecycle}/gate-seal.d.ts +1 -1
- package/dist/lifecycle/gate-seal.d.ts.map +1 -0
- package/dist/lifecycle/query.d.ts +68 -0
- package/dist/lifecycle/query.d.ts.map +1 -0
- package/dist/lifecycle/query.js +246 -0
- package/dist/lifecycle/run-ledger.d.ts +13 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +16 -0
- package/dist/lifecycle/target-lock.d.ts +97 -0
- package/dist/lifecycle/target-lock.d.ts.map +1 -0
- package/dist/lifecycle/target-lock.js +286 -0
- package/dist/op/activities/activity-contracts.d.ts +30 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +26 -0
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +4 -0
- package/dist/op/activities/pr-refs.d.ts +10 -0
- package/dist/op/activities/pr-refs.d.ts.map +1 -0
- package/dist/op/activities/pr-refs.js +26 -0
- package/dist/op/activities/predict-behaviour.d.ts +2 -4
- package/dist/op/activities/predict-behaviour.d.ts.map +1 -1
- package/dist/op/activities/predict-behaviour.js +2 -21
- package/dist/op/activities/review-agent.d.ts +137 -0
- package/dist/op/activities/review-agent.d.ts.map +1 -0
- package/dist/op/activities/review-agent.js +266 -0
- package/dist/op/builders.d.ts +9 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +11 -0
- package/dist/op/cron.d.ts +8 -0
- package/dist/op/cron.d.ts.map +1 -1
- package/dist/op/cron.js +51 -0
- package/dist/op/gate-approval.d.ts +20 -13
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +13 -18
- package/dist/op/gate-quorum.d.ts +131 -0
- package/dist/op/gate-quorum.d.ts.map +1 -0
- package/dist/op/gate-quorum.js +183 -0
- package/dist/op/gate-resume.d.ts +4 -2
- package/dist/op/gate-resume.d.ts.map +1 -1
- package/dist/op/gate-resume.js +5 -4
- package/dist/op/gate-review.js +1 -1
- package/dist/op/gate.d.ts +32 -4
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +40 -24
- package/dist/op/index.d.ts +6 -4
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +4 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +9 -3
- package/dist/op/op-waves-run.d.ts +28 -43
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +170 -120
- package/dist/op/op-waves.d.ts +55 -3
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +63 -8
- package/dist/op/schedule-overdue.d.ts +57 -0
- package/dist/op/schedule-overdue.d.ts.map +1 -0
- package/dist/op/schedule-overdue.js +138 -0
- package/dist/op/stage-hooks.d.ts +132 -0
- package/dist/op/stage-hooks.d.ts.map +1 -0
- package/dist/op/stage-hooks.js +142 -0
- package/dist/override-gate.d.ts +2 -2
- package/dist/override-gate.d.ts.map +1 -1
- package/dist/override-gate.js +1 -1
- package/dist/pr-forge.d.ts +64 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +331 -44
- package/dist/pr-loop.d.ts +107 -1
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +84 -3
- package/dist/pr-post.d.ts +72 -0
- package/dist/pr-post.d.ts.map +1 -0
- package/dist/pr-post.js +134 -0
- package/dist/pr-requires.d.ts +38 -0
- package/dist/pr-requires.d.ts.map +1 -0
- package/dist/pr-requires.js +48 -0
- package/dist/wave-blocked.d.ts +79 -0
- package/dist/wave-blocked.d.ts.map +1 -0
- package/dist/wave-blocked.js +245 -0
- package/dist/wave-engine.d.ts +6 -0
- package/dist/wave-engine.d.ts.map +1 -1
- package/dist/wave-engine.js +2 -0
- package/dist/wave-runtime.mjs +243 -243
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/two-member-workspace.js +1 -1
- package/dist/workspace/checks/ci-workflow.d.ts.map +1 -1
- package/dist/workspace/checks/ci-workflow.js +19 -4
- package/dist/workspace/ci-branches.d.ts +51 -0
- package/dist/workspace/ci-branches.d.ts.map +1 -0
- package/dist/workspace/ci-branches.js +69 -0
- package/dist/workspace/ci-cloud-identity.d.ts +11 -0
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -0
- package/dist/workspace/ci-cloud-identity.js +24 -0
- package/dist/workspace/ci-init.d.ts.map +1 -1
- package/dist/workspace/ci-init.js +5 -4
- package/dist/workspace/ci-locks.d.ts +12 -0
- package/dist/workspace/ci-locks.d.ts.map +1 -0
- package/dist/workspace/ci-locks.js +6 -0
- package/dist/workspace/ci-run.d.ts +17 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +66 -4
- package/dist/workspace/ci-workflow-at.d.ts +14 -0
- package/dist/workspace/ci-workflow-at.d.ts.map +1 -0
- package/dist/workspace/ci-workflow-at.js +17 -0
- package/dist/workspace/ci-workflow.d.ts +11 -3
- package/dist/workspace/ci-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-workflow.js +10 -2
- package/dist/workspace/declaration.d.ts +69 -3
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +72 -1
- package/dist/workspace/declaration.schema.json +149 -2
- package/dist/workspace/identity-gate-rule.d.ts +1 -1
- package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
- package/dist/workspace/identity-gate-rule.js +1 -1
- package/dist/workspace/identity.d.ts +1 -1
- package/dist/workspace/identity.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.js +8 -5
- package/dist/workspace/member-pipeline.d.ts +6 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +23 -0
- package/dist/workspace/pr-report.schema.json +94 -3
- 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 +1 -1
- package/dist/workspace/security.d.ts.map +1 -1
- package/dist/workspace/security.js +4 -0
- package/dist/workspace/status-gates.d.ts +3 -2
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-gates.js +8 -7
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +4 -1
- package/dist/workspace/trust/seal.d.ts +2 -2
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/seal.js +2 -2
- package/package.json +1 -1
- package/src/__fixtures__/oidc-emulator.ts +118 -0
- package/src/ci-handover.test.ts +209 -0
- package/src/ci-handover.ts +237 -0
- package/src/ci-identity.test.ts +201 -0
- package/src/ci-identity.ts +310 -0
- package/src/cli/handlers/approve.ts +18 -5
- package/src/cli/handlers/build.ts +18 -1
- package/src/cli/handlers/fan-out.ts +43 -6
- package/src/cli/handlers/gate-resume.ts +16 -2
- package/src/cli/handlers/lock.test.ts +98 -0
- package/src/cli/handlers/lock.ts +202 -0
- package/src/cli/handlers/operator.test.ts +3 -1
- package/src/cli/handlers/pr-before-merge.test.ts +276 -0
- package/src/cli/handlers/pr-env.ts +462 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +247 -0
- package/src/cli/handlers/pr.test.ts +184 -0
- package/src/cli/handlers/pr.ts +605 -61
- package/src/cli/handlers/promote.ts +9 -0
- package/src/cli/handlers/query.ts +78 -0
- package/src/cli/handlers/run.ts +10 -0
- package/src/cli/main.ts +49 -4
- package/src/cli/parse-args.ts +34 -2
- package/src/cli/registry.ts +31 -2
- package/src/components/capability-targets.test.ts +162 -0
- package/src/components/capability.ts +71 -0
- package/src/components/cli-support.ts +14 -1
- package/src/components/driver.ts +54 -3
- package/src/components/fan-out-run.ts +58 -3
- package/src/components/fan-out-waves.test.ts +45 -1
- package/src/components/gate-plan.ts +3 -0
- package/src/components/pr-environment.test.ts +249 -0
- package/src/components/pr-environment.ts +538 -0
- package/src/components/pr-hooks.test.ts +118 -0
- package/src/components/pr-hooks.ts +157 -0
- package/src/components/pr-pipeline.ts +194 -21
- package/src/components/pr-policy.test.ts +145 -0
- package/src/components/pr-policy.ts +211 -0
- package/src/components/pr-run.ts +14 -2
- package/src/components/wave-plan.ts +38 -3
- package/src/gate-decision-bundle.test.ts +4 -3
- package/src/gate-decision.ts +39 -2
- package/src/gate-policy-bundle.test.ts +28 -0
- package/src/gate-policy.test.ts +162 -0
- package/src/gate-policy.ts +298 -0
- package/src/lifecycle/gate-checkpoint.ts +1 -1
- package/src/{workspace/trust → lifecycle}/gate-seal.ts +1 -1
- package/src/lifecycle/query.test.ts +91 -0
- package/src/lifecycle/query.ts +315 -0
- package/src/lifecycle/run-ledger.ts +18 -0
- package/src/lifecycle/target-lock.test.ts +195 -0
- package/src/lifecycle/target-lock.ts +347 -0
- package/src/op/activities/activity-contracts.ts +31 -0
- package/src/op/activities/index.ts +6 -0
- package/src/op/activities/pr-refs.ts +28 -0
- package/src/op/activities/predict-behaviour.ts +3 -23
- package/src/op/activities/review-agent.test.ts +240 -0
- package/src/op/activities/review-agent.ts +355 -0
- package/src/op/builders.ts +13 -0
- package/src/op/cron.ts +44 -0
- package/src/op/gate-alias.test.ts +2 -2
- package/src/op/gate-approval.test.ts +30 -9
- package/src/op/gate-approval.ts +23 -26
- package/src/op/gate-quorum.test.ts +139 -0
- package/src/op/gate-quorum.ts +248 -0
- package/src/op/gate-resume.ts +6 -3
- package/src/op/gate-review.ts +1 -1
- package/src/op/gate.ts +64 -23
- package/src/op/index.ts +13 -1
- package/src/op/local-executor.ts +9 -2
- package/src/op/op-waves-hook-targets.test.ts +126 -0
- package/src/op/op-waves-policy-hold.test.ts +66 -0
- package/src/op/op-waves-quorum.test.ts +101 -0
- package/src/op/op-waves-run.ts +197 -183
- package/src/op/op-waves.ts +83 -10
- package/src/op/schedule-overdue.test.ts +220 -0
- package/src/op/schedule-overdue.ts +161 -0
- package/src/op/stage-hooks.ts +251 -0
- package/src/override-gate-bundle.test.ts +1 -1
- package/src/override-gate.ts +2 -2
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +33 -0
- package/src/pr-forge.ts +347 -35
- package/src/pr-loop-bundle.test.ts +2 -2
- package/src/pr-loop.test.ts +58 -0
- package/src/pr-loop.ts +170 -4
- package/src/pr-post.test.ts +141 -0
- package/src/pr-post.ts +159 -0
- package/src/pr-requires.test.ts +184 -0
- package/src/pr-requires.ts +79 -0
- package/src/review-agent-bundle.test.ts +35 -0
- package/src/stage-hooks-bundle.test.ts +38 -0
- package/src/wave-blocked-bundle.test.ts +25 -0
- package/src/wave-blocked.test.ts +146 -0
- package/src/wave-blocked.ts +271 -0
- package/src/wave-engine.ts +8 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +2 -2
- package/src/workspace/checks/ci-workflow.test.ts +9 -0
- package/src/workspace/checks/ci-workflow.ts +18 -4
- package/src/workspace/ci-branches.test.ts +62 -0
- package/src/workspace/ci-branches.ts +79 -0
- package/src/workspace/ci-cloud-identity.test.ts +53 -0
- package/src/workspace/ci-cloud-identity.ts +23 -0
- package/src/workspace/ci-init.ts +5 -4
- package/src/workspace/ci-locks.ts +16 -0
- package/src/workspace/ci-run-index.test.ts +2 -1
- package/src/workspace/ci-run.test.ts +107 -8
- package/src/workspace/ci-run.ts +74 -5
- package/src/workspace/ci-workflow-at.ts +23 -0
- package/src/workspace/ci-workflow.ts +13 -2
- package/src/workspace/declaration.schema.json +149 -2
- package/src/workspace/declaration.test.ts +76 -0
- package/src/workspace/declaration.ts +140 -5
- package/src/workspace/identity-gate-rule.ts +1 -1
- package/src/workspace/identity.ts +1 -1
- package/src/workspace/lineage-upgrade.ts +11 -8
- package/src/workspace/member-pipeline.test.ts +29 -0
- package/src/workspace/member-pipeline.ts +30 -0
- package/src/workspace/pr-report.schema.json +94 -3
- package/src/workspace/reason-codes.ts +1 -1
- package/src/workspace/security.ts +4 -0
- package/src/workspace/status-gates.ts +9 -6
- package/src/workspace/status.ts +4 -1
- package/src/workspace/trust/gate-seal.test.ts +1 -1
- package/src/workspace/trust/seal.ts +2 -2
- package/dist/workspace/trust/gate-seal.d.ts.map +0 -1
- /package/dist/{workspace/trust → lifecycle}/gate-seal.js +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant components pr-apply --before-merge` (#4301, ws-106): the workflow
|
|
3
|
+
* read at base and the four requires checks, asked of a fake GitHub before
|
|
4
|
+
* any of the head's code loads, then the pull request's own gate, and the
|
|
5
|
+
* apply locks under ci.locks. Git, the change signal, the derivation and the
|
|
6
|
+
* gate ledger are fakes, as in pr.test.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
14
|
+
import type { DriverComponent } from "../../components/driver";
|
|
15
|
+
import type { DeployContext } from "../../components/capability";
|
|
16
|
+
import type { ParsedArgs } from "../registry";
|
|
17
|
+
|
|
18
|
+
const HEAD = "a".repeat(40);
|
|
19
|
+
const BASE = "b".repeat(40);
|
|
20
|
+
|
|
21
|
+
/** The security groups pr-apply reads from the declaration (#4168); none unless a test sets them. */
|
|
22
|
+
let groups: string[] = [];
|
|
23
|
+
vi.mock("../../workspace/security", async (original) => ({
|
|
24
|
+
...(await original<typeof import("../../workspace/security")>()),
|
|
25
|
+
securityGroupsAt: () => new Set(groups),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
/** The ci.hooks pr-plan and pr-apply read at base (#4288); none unless a test sets them. */
|
|
29
|
+
let hooksAtBase: Record<string, unknown> = {};
|
|
30
|
+
vi.mock("../../components/pr-hooks", async (original) => ({
|
|
31
|
+
...(await original<typeof import("../../components/pr-hooks")>()),
|
|
32
|
+
prLoopHooksAt: (_cwd: string, base: string) => ({ hooks: hooksAtBase, base }),
|
|
33
|
+
}));
|
|
34
|
+
/** `ci.locks` pr-apply reads at base (#4293), and the locks other pull requests hold; none unless a test sets them. */
|
|
35
|
+
let locksMode: "apply" | "plan" | null = null;
|
|
36
|
+
let heldLocks: (gate: string, targets: readonly string[], pr: number | null) => Promise<unknown[]> = async () => [];
|
|
37
|
+
vi.mock("../../workspace/ci-locks", () => ({ ciLocksAt: () => locksMode }));
|
|
38
|
+
let taken: unknown[] = [];
|
|
39
|
+
vi.mock("../../lifecycle/target-lock", async (original) => ({
|
|
40
|
+
...(await original<typeof import("../../lifecycle/target-lock")>()),
|
|
41
|
+
heldTargetLocks: (gate: string, targets: readonly string[], pr: number | null) => heldLocks(gate, targets, pr),
|
|
42
|
+
takeTargetLocks: async (gate: string, targets: readonly string[], holder: unknown) => {
|
|
43
|
+
taken.push({ gate, targets, holder });
|
|
44
|
+
return { ok: true, taken: [...targets].sort(), released: [], expired: [], pushed: false };
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
/** The ci.workflow pr-apply --before-merge reads at base (#4301). */
|
|
48
|
+
let workflowAtBase: string | null = "apply-before-merge";
|
|
49
|
+
vi.mock("../../workspace/ci-workflow-at", () => ({ ciWorkflowAt: () => workflowAtBase }));
|
|
50
|
+
/** Whether the head's code was loaded: the config read and the derivation. */
|
|
51
|
+
let loaded: string[] = [];
|
|
52
|
+
|
|
53
|
+
/** What ci.policy at base says (#4294): no policy unless a test sets a verdict. The engine itself is gate-policy.test.ts's. */
|
|
54
|
+
let policyVerdict: import("../../gate-policy").GatePolicyVerdict | undefined;
|
|
55
|
+
vi.mock("../../components/pr-policy", async (original) => ({
|
|
56
|
+
...(await original<typeof import("../../components/pr-policy")>()),
|
|
57
|
+
prLoopPolicyAt: (_cwd: string, base: string) => ({ base }),
|
|
58
|
+
runPrPolicy: () => policyVerdict,
|
|
59
|
+
}));
|
|
60
|
+
vi.mock("../../override-gate", async (original) => ({
|
|
61
|
+
...(await original<typeof import("../../override-gate")>()),
|
|
62
|
+
overridersAt: (_repo: string, base: string) => ({ base, path: ".chant/overriders", active: true, principals: ["github:carol"] }),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
vi.mock("../../components/changed-files", () => ({
|
|
66
|
+
resolveCommit: async () => HEAD,
|
|
67
|
+
resolveMergeBase: async () => BASE,
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
vi.mock("../../config", async () => {
|
|
71
|
+
const actual = await vi.importActual<typeof import("../../config")>("../../config");
|
|
72
|
+
return { ...actual, loadChantConfig: async () => { loaded.push("config"); return { config: {} }; } };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
vi.mock("./fan-out", () => ({
|
|
76
|
+
resolveChangedUnits: async () => ({ units: { changed: ["net"], indeterminate: [] } }),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
// #4022: pr-apply stores the change set its gate binds on chant/lifecycle and
|
|
80
|
+
// pushes it. These tests run in the chant checkout, so the store is replaced:
|
|
81
|
+
// it must never write or push the real repository's lifecycle branch.
|
|
82
|
+
vi.mock("../../lifecycle/gate-change-set", () => ({
|
|
83
|
+
storeGateChangeSet: async () => ({ written: false }),
|
|
84
|
+
readGraphIds: async () => ({ ids: [] }),
|
|
85
|
+
setGraphIdReader: () => {},
|
|
86
|
+
gateGraphReader: () => async () => ({ ids: [] }),
|
|
87
|
+
}));
|
|
88
|
+
vi.mock("../../lifecycle/git", async () => {
|
|
89
|
+
const actual = await vi.importActual<typeof import("../../lifecycle/git")>("../../lifecycle/git");
|
|
90
|
+
return { ...actual, pushLifecycleReport: async () => { throw new Error("pr.test must not push chant/lifecycle"); } };
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
vi.mock("./run", () => ({
|
|
94
|
+
GATED_EXIT_CODE: 3,
|
|
95
|
+
autoReleaseRun: (id: string) => ({ runId: id, runOrigin: { forge: "local" } }),
|
|
96
|
+
recordAutoReleasesForRun: async () => [],
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
let gateLedger: ReturnType<typeof import("../../op/gate").memoryGateLedgerPort>;
|
|
100
|
+
vi.mock("../../op/gate", async () => {
|
|
101
|
+
const actual = await vi.importActual<typeof import("../../op/gate")>("../../op/gate");
|
|
102
|
+
return { ...actual, gitGateLedgerPort: () => gateLedger, gateIsSealed: async () => false };
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const failing = new Set<string>();
|
|
106
|
+
/** The error a root's apply throws, in place of applying (#3997). */
|
|
107
|
+
const refusal = new Map<string, Error>();
|
|
108
|
+
/** Called as a component's apply starts, before it can fail. */
|
|
109
|
+
let onRun: (root: string) => void = () => {};
|
|
110
|
+
vi.mock("../../components/fan-out-support", async () => {
|
|
111
|
+
const { CapabilityRegistry } = await import("../../components/capability");
|
|
112
|
+
const { planFanOut } = await import("../../components/fan-out");
|
|
113
|
+
const { computePlanDigest } = await import("../../lifecycle/plan-digest");
|
|
114
|
+
const registry = new CapabilityRegistry();
|
|
115
|
+
registry.register({
|
|
116
|
+
kind: "fake-root",
|
|
117
|
+
async plan(_ctx: DeployContext, input: { root: string }) {
|
|
118
|
+
const planDigest = computePlanDigest("fake-root", { root: input.root });
|
|
119
|
+
return {
|
|
120
|
+
member: input.root,
|
|
121
|
+
planDigest,
|
|
122
|
+
artifact: { planDigest },
|
|
123
|
+
changeSet: { member: { member: input.root, lexicon: "terraform", planner: "tofu", status: "planned", planDigest, holes: [] }, entries: [] },
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
async outputs() {
|
|
127
|
+
return {};
|
|
128
|
+
},
|
|
129
|
+
async run(_ctx: DeployContext, input: { root: string }) {
|
|
130
|
+
onRun(input.root);
|
|
131
|
+
if (refusal.has(input.root)) throw refusal.get(input.root);
|
|
132
|
+
if (failing.has(input.root)) throw new Error(`${input.root} failed`);
|
|
133
|
+
return { outputs: {} };
|
|
134
|
+
},
|
|
135
|
+
} as never);
|
|
136
|
+
return {
|
|
137
|
+
fanOutRegistry: async () => registry,
|
|
138
|
+
deriveFanOut: async () => (loaded.push("derive"), {
|
|
139
|
+
success: true,
|
|
140
|
+
components: ESTATE,
|
|
141
|
+
plan: planFanOut({ components: ESTATE, changed: ["net"] }),
|
|
142
|
+
signal: { changed: ["net"], indeterminate: [], unclaimed: [] },
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const root = (name: string, dependsOn: string[] = []): DriverComponent => ({
|
|
148
|
+
name,
|
|
149
|
+
dependsOn,
|
|
150
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "fake-root", root: name }] }],
|
|
151
|
+
});
|
|
152
|
+
const ESTATE: DriverComponent[] = [root("net"), root("app", ["net"])];
|
|
153
|
+
|
|
154
|
+
const { memoryGateLedgerPort } = await import("../../op/gate");
|
|
155
|
+
const { runComponentsPrApply, runComponentsPrPlan } = await import("./pr");
|
|
156
|
+
|
|
157
|
+
let dir: string;
|
|
158
|
+
let out: string[];
|
|
159
|
+
let errors: string[];
|
|
160
|
+
|
|
161
|
+
const ctx = (a: Partial<ParsedArgs>) =>
|
|
162
|
+
({ args: { command: "components", path: "pr-apply", format: "", fix: false, watch: false, verbose: false, help: false, live: false, base: "main", env: "prod", output: dir, pr: 12, forge: "github", beforeMerge: true, ...a } as ParsedArgs, plugins: [] }) as never;
|
|
163
|
+
|
|
164
|
+
beforeEach(async () => {
|
|
165
|
+
dir = await mkdtemp(join(tmpdir(), "pr-before-merge-"));
|
|
166
|
+
taken = [];
|
|
167
|
+
workflowAtBase = "apply-before-merge";
|
|
168
|
+
loaded = [];
|
|
169
|
+
out = [];
|
|
170
|
+
errors = [];
|
|
171
|
+
failing.clear();
|
|
172
|
+
refusal.clear();
|
|
173
|
+
onRun = () => {};
|
|
174
|
+
groups = [];
|
|
175
|
+
hooksAtBase = {};
|
|
176
|
+
locksMode = null;
|
|
177
|
+
heldLocks = async () => [];
|
|
178
|
+
policyVerdict = undefined;
|
|
179
|
+
gateLedger = memoryGateLedgerPort();
|
|
180
|
+
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array) => { out.push(String(chunk)); return true; });
|
|
181
|
+
vi.spyOn(console, "error").mockImplementation((...m: unknown[]) => { errors.push(m.map(String).join(" ")); });
|
|
182
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
afterEach(async () => {
|
|
186
|
+
vi.restoreAllMocks();
|
|
187
|
+
vi.unstubAllGlobals();
|
|
188
|
+
vi.unstubAllEnvs();
|
|
189
|
+
await rm(dir, { recursive: true, force: true });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/** The digest pr-plan printed, read back from its report. */
|
|
193
|
+
async function plannedDigest(): Promise<string> {
|
|
194
|
+
const { readFile } = await import("node:fs/promises");
|
|
195
|
+
return JSON.parse(await readFile(join(dir, "pr-plan.json"), "utf8")).digest as string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Pull request 12 on a fake GitHub, its head HEAD unless `over` says otherwise. */
|
|
199
|
+
function serveGithub(over: { head?: string; reviews?: unknown[]; mergeable?: boolean | null; behind?: number; statuses?: unknown[] } = {}): void {
|
|
200
|
+
const ok = (value: unknown) => ({ ok: true, status: 200, text: async () => JSON.stringify(value) });
|
|
201
|
+
vi.stubEnv("GITHUB_REPOSITORY", "acme/infra");
|
|
202
|
+
vi.stubEnv("GITHUB_TOKEN", "t0k");
|
|
203
|
+
vi.stubEnv("GITHUB_API_URL", "https://api.github.test");
|
|
204
|
+
vi.stubEnv("GITHUB_SERVER_URL", "https://github.com");
|
|
205
|
+
vi.stubGlobal("fetch", async (url: string, init: { method: string }) => {
|
|
206
|
+
const { pathname } = new URL(url);
|
|
207
|
+
if (init.method !== "GET") return ok({ id: 1 });
|
|
208
|
+
const pull = "/repos/acme/infra/pulls/12";
|
|
209
|
+
if (pathname === pull) return ok({ number: 12, state: "open", user: { login: "carol" }, head: { ref: "f", sha: over.head ?? HEAD }, base: { ref: "main" }, mergeable: over.mergeable === undefined ? true : over.mergeable, mergeable_state: "clean" });
|
|
210
|
+
if (pathname === `${pull}/reviews`) return ok(over.reviews ?? [{ user: { login: "bob" }, state: "APPROVED", commit_id: HEAD }]);
|
|
211
|
+
if (pathname === `${pull}/commits`) return ok([{ author: { login: "carol" }, committer: { login: "carol" } }]);
|
|
212
|
+
if (pathname === `/repos/acme/infra/compare/main...${HEAD}`) return ok({ behind_by: over.behind ?? 0 });
|
|
213
|
+
if (pathname === `/repos/acme/infra/commits/${HEAD}/status`) return ok({ statuses: over.statuses ?? [{ context: "chant/plan", state: "success" }, { context: "chant/apply", state: "failure" }] });
|
|
214
|
+
if (pathname === `/repos/acme/infra/commits/${HEAD}/check-runs`) return ok({ check_runs: [] });
|
|
215
|
+
return ok([]);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const applyReport = async () => {
|
|
220
|
+
const { readFile } = await import("node:fs/promises");
|
|
221
|
+
return JSON.parse(await readFile(join(dir, "pr-apply.json"), "utf8")) as Record<string, unknown> & { beforeMerge?: { requires: Array<{ name: string; passed: boolean; reason: string }> } };
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
describe("pr-apply --before-merge (#4301)", () => {
|
|
225
|
+
test("without apply-before-merge at base it refuses before-merge-off, and none of the head's code loads", async () => {
|
|
226
|
+
workflowAtBase = "trunk-curated";
|
|
227
|
+
serveGithub();
|
|
228
|
+
expect(await runComponentsPrApply(ctx({}))).toBe(1);
|
|
229
|
+
const report = await applyReport();
|
|
230
|
+
expect(report).toMatchObject({ stage: "apply", pr: 12, status: "refused", refusal: "before-merge-off", beforeMerge: { requires: [] } });
|
|
231
|
+
expect(report.message).toContain("sets ci.workflow to trunk-curated");
|
|
232
|
+
expect(loaded).toEqual([]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("an unmet require refuses requires-unmet with each check in the report, and none of the head's code loads", async () => {
|
|
236
|
+
serveGithub({ behind: 2, reviews: [{ user: { login: "carol" }, state: "APPROVED", commit_id: HEAD }] });
|
|
237
|
+
expect(await runComponentsPrApply(ctx({}))).toBe(3);
|
|
238
|
+
const report = await applyReport();
|
|
239
|
+
expect(report).toMatchObject({ status: "refused", refusal: "requires-unmet" });
|
|
240
|
+
expect(report.beforeMerge!.requires.map((r) => [r.name, r.passed])).toEqual([["approved", false], ["mergeable", true], ["undiverged", false], ["checks", true]]);
|
|
241
|
+
expect(report.message).toContain("undiverged: aaaaaaaa is 2 commits behind main");
|
|
242
|
+
expect(loaded).toEqual([]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("it needs --pr and --forge, and takes no --green", async () => {
|
|
246
|
+
expect(await runComponentsPrApply(ctx({ pr: undefined }))).toBe(1);
|
|
247
|
+
expect(await runComponentsPrApply(ctx({ forge: undefined }))).toBe(1);
|
|
248
|
+
expect(await runComponentsPrApply(ctx({ green: true }))).toBe(1);
|
|
249
|
+
expect(loaded).toEqual([]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("all four passed: the pull request's own gate decides, and an unapproved plan applies nothing", async () => {
|
|
253
|
+
serveGithub();
|
|
254
|
+
expect(await runComponentsPrApply(ctx({}))).toBe(3);
|
|
255
|
+
const report = await applyReport();
|
|
256
|
+
expect(report).toMatchObject({ status: "refused", refusal: "not-approved", op: "pr-12" });
|
|
257
|
+
expect(report.beforeMerge!.requires.every((r) => r.passed)).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("approved: it applies at the head, and under ci.locks holds apply locks on what it reaches", async () => {
|
|
261
|
+
serveGithub();
|
|
262
|
+
await runComponentsPrPlan(ctx({ beforeMerge: false, forge: undefined, noStatus: true }));
|
|
263
|
+
const { readFile } = await import("node:fs/promises");
|
|
264
|
+
const digest = JSON.parse(await readFile(join(dir, "pr-plan.json"), "utf8")).digest as string;
|
|
265
|
+
gateLedger = memoryGateLedgerPort({
|
|
266
|
+
resolutions: [{ version: 1, kind: "resolution", op: "pr-12", gate: "pr-apply", resolvedBy: "github:bob", timestamp: new Date().toISOString(), planDigest: digest }],
|
|
267
|
+
});
|
|
268
|
+
locksMode = "apply";
|
|
269
|
+
const ran: string[] = [];
|
|
270
|
+
onRun = (r) => void ran.push(r);
|
|
271
|
+
expect(await runComponentsPrApply(ctx({}))).toBe(0);
|
|
272
|
+
expect(ran).toEqual(["net", "app"]);
|
|
273
|
+
expect(taken).toEqual([{ gate: "pr-apply", targets: ["net", "app"], holder: expect.objectContaining({ pr: 12, stage: "apply", head: HEAD }) }]);
|
|
274
|
+
expect(await applyReport()).toMatchObject({ status: "applied", beforeMerge: { requires: expect.any(Array) } });
|
|
275
|
+
});
|
|
276
|
+
});
|