@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,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stage-hook runner (#4288): one moment's hooks, with the `targets`,
|
|
3
|
+
* `perRun`, `onFailure` and argv filling a wave job uses. Its own module so
|
|
4
|
+
* a product that runs hooks around its own stages (terragucci) bundles it
|
|
5
|
+
* without the wave loop, the gate or the ledger. `op-waves-run.ts` runs
|
|
6
|
+
* the same hooks inside a wave job.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { opWaveHookRuns, type OpWaveHook, type OpWaveHookMoment } from "./op-waves";
|
|
13
|
+
|
|
14
|
+
/** What one moment's hooks came to, before any hold on another gate is settled. */
|
|
15
|
+
export interface HookListOutcome {
|
|
16
|
+
/** Set when a hook with `onFailure: fail` failed; the hooks after it did not run. */
|
|
17
|
+
failed?: string;
|
|
18
|
+
/** The `onFailure: approve` hooks that failed. */
|
|
19
|
+
holds: string[];
|
|
20
|
+
outcomes: OpWaveHookOutcome[];
|
|
21
|
+
/** The hook calls that held the wave on another gate. */
|
|
22
|
+
otherGates: OtherGateHold[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Pending-fact keys chant writes itself, which a plans file's `pending` may not set (#3821). */
|
|
26
|
+
export const OP_WAVE_PENDING_RESERVED = [
|
|
27
|
+
"version", "kind", "op", "gate", "timestamp", "expiresAt", "planDigest", "runId", "url", "resume",
|
|
28
|
+
"environment", "origin", "neverOverMcp", "approval", "approvalMode", "answer", "wave", "heldBy",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
/** What a hook asks for when it holds a wave on another gate (#3821): it writes this to `CHANT_HOLD` and exits non-zero. */
|
|
32
|
+
export interface OpWaveHoldRequest {
|
|
33
|
+
/** The op the gate is recorded under. Default: the spec's name. */
|
|
34
|
+
op?: string;
|
|
35
|
+
gate: string;
|
|
36
|
+
/** The digest an approval binds. */
|
|
37
|
+
digest: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
/** Fields for the pending fact, as a plans file's `pending`. */
|
|
40
|
+
pending?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One hook's run (#3762). */
|
|
44
|
+
export interface OpWaveHookOutcome {
|
|
45
|
+
moment: OpWaveHookMoment;
|
|
46
|
+
name: string;
|
|
47
|
+
/** The run, for a `perRun` hook. */
|
|
48
|
+
target?: string;
|
|
49
|
+
exit: number;
|
|
50
|
+
outcome: "passed" | "failed" | "held" | "skipped";
|
|
51
|
+
/** Why the moment was skipped, for `skipped` (#3811). */
|
|
52
|
+
reason?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One run as a hook sees it (#3804). */
|
|
56
|
+
export interface OpWaveHookRun {
|
|
57
|
+
target: string;
|
|
58
|
+
op: string;
|
|
59
|
+
/** The run's plan file, once planned. */
|
|
60
|
+
plan?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Runs one argv; returns its exit code. `env` is added to the job's environment. Injected so tests run no processes. */
|
|
64
|
+
export type OpWaveExec = (argv: string[], cwd: string, env?: Record<string, string>) => number;
|
|
65
|
+
|
|
66
|
+
export const spawnExec: OpWaveExec = (argv, cwd, env) => {
|
|
67
|
+
const result = spawnSync(argv[0]!, argv.slice(1), { cwd, stdio: "inherit", ...(env ? { env: { ...process.env, ...env } } : {}) });
|
|
68
|
+
if (result.error) throw result.error;
|
|
69
|
+
return result.status ?? 1;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Fill one argv template. A placeholder whose value is not given is left as it is. */
|
|
73
|
+
export function opWaveArgv(
|
|
74
|
+
template: readonly string[],
|
|
75
|
+
values: { op: string; wave: string; target?: string; plan?: string; runs?: string; plans?: string },
|
|
76
|
+
): string[] {
|
|
77
|
+
const pairs: Array<[string, string | undefined]> = [
|
|
78
|
+
["{op}", values.op],
|
|
79
|
+
["{target}", values.target],
|
|
80
|
+
["{wave}", values.wave],
|
|
81
|
+
["{plans}", values.plans],
|
|
82
|
+
["{plan}", values.plan],
|
|
83
|
+
["{runs}", values.runs],
|
|
84
|
+
];
|
|
85
|
+
return template.map((part) => pairs.reduce((out, [key, value]) => (value === undefined ? out : out.replaceAll(key, value)), part));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A plans file's `pending` (#3821): an object whose keys chant does not write itself, `description` aside. Undefined when absent or empty. */
|
|
89
|
+
export function pendingFields(value: unknown, where: string): Record<string, unknown> | undefined {
|
|
90
|
+
if (value === undefined) return undefined;
|
|
91
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${where}: \`pending\` must be an object of fields for the pending fact`);
|
|
92
|
+
const reserved = Object.keys(value).filter((key) => (OP_WAVE_PENDING_RESERVED as readonly string[]).includes(key));
|
|
93
|
+
if (reserved.length > 0) throw new Error(`${where}: \`pending\` sets ${reserved.join(", ")}, which chant writes itself`);
|
|
94
|
+
if ("description" in value && typeof (value as { description?: unknown }).description !== "string") throw new Error(`${where}: pending.description must be a string`);
|
|
95
|
+
return Object.keys(value).length > 0 ? (value as Record<string, unknown>) : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A hook call that held the wave on another gate (#3821), kept so it can run again once that gate is approved. */
|
|
99
|
+
export interface OtherGateHold {
|
|
100
|
+
label: string;
|
|
101
|
+
name: string;
|
|
102
|
+
moment: OpWaveHookMoment;
|
|
103
|
+
target?: string;
|
|
104
|
+
argv: string[];
|
|
105
|
+
env: Record<string, string>;
|
|
106
|
+
request: OpWaveHoldRequest;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Read the hold file a hook wrote (#3821), or undefined when it wrote none or one that names no gate and digest. */
|
|
110
|
+
export function readHoldRequest(path: string, log: (line: string) => void, label: string): OpWaveHoldRequest | undefined {
|
|
111
|
+
if (!existsSync(path)) return undefined;
|
|
112
|
+
try {
|
|
113
|
+
const r = JSON.parse(readFileSync(path, "utf-8")) as Partial<OpWaveHoldRequest>;
|
|
114
|
+
if (typeof r.gate !== "string" || r.gate === "" || typeof r.digest !== "string" || r.digest === "") throw new Error("it names no gate and digest");
|
|
115
|
+
if (r.op !== undefined && (typeof r.op !== "string" || r.op === "")) throw new Error("its op is not a name");
|
|
116
|
+
return { gate: r.gate, digest: r.digest, ...(r.op ? { op: r.op } : {}), ...(typeof r.description === "string" ? { description: r.description } : {}), ...(r.pending ? { pending: pendingFields(r.pending, `the hold ${label} wrote`) ?? {} } : {}) };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
log(`${label} wrote a hold file that cannot be read (${err instanceof Error ? err.message : String(err)}), so it holds the wave on its set digest`);
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Where hooks run: the wave they belong to and the job's tools. */
|
|
124
|
+
export interface HookPlace {
|
|
125
|
+
/** The spec's name and `op`. */
|
|
126
|
+
name: string;
|
|
127
|
+
op: string;
|
|
128
|
+
waveName: string;
|
|
129
|
+
k: number;
|
|
130
|
+
cwd: string;
|
|
131
|
+
exec: OpWaveExec;
|
|
132
|
+
log: (line: string) => void;
|
|
133
|
+
/** Where a hook writes a hold on another gate (`CHANT_HOLD`). Default: under the wave's directory. */
|
|
134
|
+
holdPath?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Run one moment's hooks, in order: each once for the wave, or once per run
|
|
139
|
+
* for a `perRun` hook. A hook that fails stops the rest, unless its
|
|
140
|
+
* `onFailure` is `approve`: then it holds the wave and the hooks after it run.
|
|
141
|
+
*/
|
|
142
|
+
export function runHookList(
|
|
143
|
+
at: HookPlace,
|
|
144
|
+
moment: OpWaveHookMoment,
|
|
145
|
+
hooks: readonly OpWaveHook[] | undefined,
|
|
146
|
+
runs: readonly OpWaveHookRun[],
|
|
147
|
+
runsPath: string,
|
|
148
|
+
extraEnv: Record<string, string> = {},
|
|
149
|
+
): HookListOutcome {
|
|
150
|
+
const out: HookListOutcome = { holds: [], outcomes: [], otherGates: [] };
|
|
151
|
+
if (!hooks || hooks.length === 0) return out;
|
|
152
|
+
const { name: specName, op, waveName, k, cwd, exec, log } = at;
|
|
153
|
+
// #3821: a hook that holds the wave on another gate names it in this file.
|
|
154
|
+
const holdPath = at.holdPath ?? `.chant/op-waves/${specName}/wave-${k}/hold.json`;
|
|
155
|
+
const env = { CHANT_OP_WAVES: specName, CHANT_WAVE: waveName, CHANT_WAVE_NUMBER: String(k), CHANT_HOOK: moment, CHANT_RUNS: runsPath, CHANT_HOLD: holdPath, ...extraEnv };
|
|
156
|
+
for (const hook of hooks) {
|
|
157
|
+
const name = hook.name ?? (hook.command.join(" ").length > 60 ? `${hook.command.join(" ").slice(0, 57)}...` : hook.command.join(" "));
|
|
158
|
+
// #4288: a hook with `targets` is for the runs they match. Once for the wave, it runs when one matches or no run is known yet.
|
|
159
|
+
const mine = opWaveHookRuns(hook, runs);
|
|
160
|
+
if (hook.targets && (hook.perRun ? mine.length === 0 : mine.length === 0 && runs.length > 0)) {
|
|
161
|
+
log(`${moment} hook ${name}: no run matches its targets (${hook.targets.join(", ")}), so it did not run`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const calls = hook.perRun
|
|
165
|
+
? mine.map((run) => ({
|
|
166
|
+
target: run.target as string | undefined,
|
|
167
|
+
argv: opWaveArgv(hook.command, { op: run.op, wave: waveName, target: run.target, runs: runsPath, ...(run.plan ? { plan: run.plan } : {}) }),
|
|
168
|
+
env: { ...env, CHANT_TARGET: run.target, ...(run.plan ? { CHANT_PLAN: run.plan } : {}) } as Record<string, string>,
|
|
169
|
+
}))
|
|
170
|
+
: [{ target: undefined, argv: opWaveArgv(hook.command, { op, wave: waveName, runs: runsPath }), env: env as Record<string, string> }];
|
|
171
|
+
for (const call of calls) {
|
|
172
|
+
const label = `${moment} hook ${name}${call.target ? ` (${call.target})` : ""}`;
|
|
173
|
+
let code: number;
|
|
174
|
+
rmSync(join(cwd, holdPath), { force: true });
|
|
175
|
+
try {
|
|
176
|
+
code = exec(call.argv, cwd, call.env);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
log(`${label} did not run: ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
+
code = 127;
|
|
180
|
+
}
|
|
181
|
+
const base = { moment, name, ...(call.target ? { target: call.target } : {}), exit: code };
|
|
182
|
+
if (code === 0) {
|
|
183
|
+
out.outcomes.push({ ...base, outcome: "passed" });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (hook.onFailure === "approve") {
|
|
187
|
+
out.outcomes.push({ ...base, outcome: "held" });
|
|
188
|
+
const request = readHoldRequest(join(cwd, holdPath), log, label);
|
|
189
|
+
if (request) {
|
|
190
|
+
out.otherGates.push({ label, name, moment, ...(call.target ? { target: call.target } : {}), argv: call.argv, env: call.env, request });
|
|
191
|
+
log(`${label} exited ${code} and holds wave ${k} (${waveName}) on gate ${request.op ?? specName} / ${request.gate}, digest ${request.digest}`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
out.holds.push(label);
|
|
195
|
+
log(`${label} exited ${code}, so wave ${k} (${waveName}) waits for an approval of its set digest`);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
out.outcomes.push({ ...base, outcome: "failed" });
|
|
199
|
+
out.failed = `${label} exited ${code}`;
|
|
200
|
+
log(out.failed);
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The options of {@link runStageHooks} (#4288). */
|
|
208
|
+
export interface RunStageHooksOptions {
|
|
209
|
+
/** What the hooks belong to: `CHANT_OP_WAVES`, and the `{op}` of a hook that runs once. */
|
|
210
|
+
name: string;
|
|
211
|
+
op: string;
|
|
212
|
+
/** `{wave}` and `CHANT_WAVE`. */
|
|
213
|
+
wave: string;
|
|
214
|
+
/** `CHANT_WAVE_NUMBER`. Default 0. */
|
|
215
|
+
k?: number;
|
|
216
|
+
/** The runs a `perRun` hook runs for and `targets` match against. */
|
|
217
|
+
runs: readonly OpWaveHookRun[];
|
|
218
|
+
/** `{runs}` and `CHANT_RUNS`, relative to `cwd`; the caller writes it. */
|
|
219
|
+
runsFile: string;
|
|
220
|
+
/** `CHANT_HOLD`, relative to `cwd`. */
|
|
221
|
+
holdFile: string;
|
|
222
|
+
/** Added to every hook's environment. */
|
|
223
|
+
env?: Record<string, string>;
|
|
224
|
+
cwd?: string;
|
|
225
|
+
exec?: OpWaveExec;
|
|
226
|
+
log?: (line: string) => void;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** What {@link runStageHooks} gave. */
|
|
230
|
+
export interface StageHooksRun {
|
|
231
|
+
outcomes: OpWaveHookOutcome[];
|
|
232
|
+
/** Set when a hook with `onFailure: fail` failed; the hooks after it did not run. */
|
|
233
|
+
failed?: string;
|
|
234
|
+
/** The `onFailure: approve` hooks that failed. A hold on another gate counts as one here. */
|
|
235
|
+
holds: string[];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Run one moment's hooks outside an Op waves job (#4288): the pull-request
|
|
240
|
+
* jobs, or a product's own stage, with the same `targets`, `perRun`,
|
|
241
|
+
* `onFailure` and argv filling a wave job uses. A hook that holds on another
|
|
242
|
+
* gate (`CHANT_HOLD`) is counted as a plain hold; settling that gate is the
|
|
243
|
+
* caller's.
|
|
244
|
+
*/
|
|
245
|
+
export function runStageHooks(moment: OpWaveHookMoment, hooks: readonly OpWaveHook[] | undefined, options: RunStageHooksOptions): StageHooksRun {
|
|
246
|
+
const cwd = options.cwd ?? process.cwd();
|
|
247
|
+
const log = options.log ?? ((line: string) => console.error(line));
|
|
248
|
+
const place: HookPlace = { name: options.name, op: options.op, waveName: options.wave, k: options.k ?? 0, cwd, exec: options.exec ?? spawnExec, log, holdPath: options.holdFile };
|
|
249
|
+
const out = runHookList(place, moment, hooks, options.runs, options.runsFile, options.env);
|
|
250
|
+
return { outcomes: out.outcomes, ...(out.failed ? { failed: out.failed } : {}), holds: [...out.holds, ...out.otherGates.map((g) => `${g.label} (on ${g.request.op ?? options.name} / ${g.request.gate})`)] };
|
|
251
|
+
}
|
|
@@ -34,10 +34,10 @@ describe("the override-gate bundle", () => {
|
|
|
34
34
|
"packages/core/src/effect-receipt.ts",
|
|
35
35
|
"packages/core/src/intrinsic.ts",
|
|
36
36
|
"packages/core/src/lifecycle/gate-ledger-select.ts",
|
|
37
|
+
"packages/core/src/lifecycle/gate-seal.ts",
|
|
37
38
|
"packages/core/src/lifecycle/plan-digest.ts",
|
|
38
39
|
"packages/core/src/op/gate-name.ts",
|
|
39
40
|
"packages/core/src/override-gate.ts",
|
|
40
|
-
"packages/core/src/workspace/trust/gate-seal.ts",
|
|
41
41
|
"packages/core/src/workspace/trust/trust-files.ts",
|
|
42
42
|
]);
|
|
43
43
|
// About 16 KB today.
|
package/src/override-gate.ts
CHANGED
|
@@ -38,14 +38,14 @@ import { spawnSync } from "node:child_process";
|
|
|
38
38
|
import { OVERRIDE_GATE_OP } from "./op/gate-name";
|
|
39
39
|
import { latestPendingGate } from "./lifecycle/gate-ledger-select";
|
|
40
40
|
import { samePlanDigest } from "./lifecycle/plan-digest";
|
|
41
|
-
import { gateSealRefusal, type GateSigners } from "./
|
|
41
|
+
import { gateSealRefusal, type GateSigners } from "./lifecycle/gate-seal";
|
|
42
42
|
import { DEFAULT_SIGNERS_PATH } from "./workspace/trust/trust-files";
|
|
43
43
|
import type { GateResolutionRecord, PendingGateRecord } from "./lifecycle/gate-ledger";
|
|
44
44
|
import type { GateApprovalSource } from "./op/op-waves";
|
|
45
45
|
import type { GateAuditApply } from "./lifecycle/gate-audit";
|
|
46
46
|
|
|
47
47
|
export type { GateResolutionRecord, PendingGateRecord } from "./lifecycle/gate-ledger";
|
|
48
|
-
export type { GateSigners } from "./
|
|
48
|
+
export type { GateSigners } from "./lifecycle/gate-seal";
|
|
49
49
|
|
|
50
50
|
/** The op every override and denial is recorded under. `./lifecycle/gate-audit.ts` reads this op as overrides by default. */
|
|
51
51
|
export const OVERRIDE_OP = OVERRIDE_GATE_OP;
|
|
@@ -34,7 +34,7 @@ describe("the pr-forge bundle", () => {
|
|
|
34
34
|
expect(inputs.filter((i) => pattern.test(i)), String(pattern)).toEqual([]);
|
|
35
35
|
}
|
|
36
36
|
expect(inputs.map((i) => i.replace(/^.*?packages\//, "packages/")).sort()).toEqual(["packages/core/src/pr-forge.ts"]);
|
|
37
|
-
// About
|
|
38
|
-
expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(
|
|
37
|
+
// About 47 KB today, after the pull request authors (#4000), pullRequestOpen for target locks (#4293) and the four requires checks of an apply before merge (#4301).
|
|
38
|
+
expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(52 * 1024);
|
|
39
39
|
});
|
|
40
40
|
});
|
package/src/pr-forge.test.ts
CHANGED
|
@@ -127,6 +127,17 @@ describe("github", () => {
|
|
|
127
127
|
expect(await githubForge({ ...opts, fetch }).pullRequestFor(SHA)).toBe(9);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
test("a pull request is open while its state is open and it has not merged; an unknown one is not (#4293)", async () => {
|
|
131
|
+
const { fetch } = fakeFetch({
|
|
132
|
+
"GET /repos/acme/infra/pulls/1": { number: 1, state: "open", merged: false },
|
|
133
|
+
"GET /repos/acme/infra/pulls/2": { number: 2, state: "closed", merged: true },
|
|
134
|
+
});
|
|
135
|
+
const forge = githubForge({ ...opts, fetch });
|
|
136
|
+
expect(await forge.pullRequestOpen!(1)).toBe(true);
|
|
137
|
+
expect(await forge.pullRequestOpen!(2)).toBe(false);
|
|
138
|
+
expect(await forge.pullRequestOpen!(3)).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
130
141
|
test("resolves to the note's web URL", async () => {
|
|
131
142
|
const { fetch } = fakeFetch({
|
|
132
143
|
"GET /repos/acme/infra/issues/12/comments": [],
|
|
@@ -237,6 +248,11 @@ describe("forgejo", () => {
|
|
|
237
248
|
expect(await forgejoForge({ ...opts, fetch: none.fetch }).pullRequestFor(SHA)).toBeNull();
|
|
238
249
|
});
|
|
239
250
|
|
|
251
|
+
test("reads whether a pull request is open from the same pulls endpoint (#4293)", async () => {
|
|
252
|
+
const { fetch } = fakeFetch({ "GET /api/v1/repos/acme/infra/pulls/4": { number: 4, state: "open", merged: false } });
|
|
253
|
+
expect(await forgejoForge({ ...opts, fetch }).pullRequestOpen!(4)).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
240
256
|
test("names reviewers on its host", () => {
|
|
241
257
|
expect(forgejoForge({ ...opts, fetch: fakeFetch({}).fetch }).principalOf("Ana")).toBe("forgejo@codeberg.org:ana");
|
|
242
258
|
});
|
|
@@ -295,6 +311,16 @@ describe("gitlab", () => {
|
|
|
295
311
|
expect(await forge.pullRequestFor(SHA)).toBe(5);
|
|
296
312
|
expect(forge.principalOf("ana")).toBe("gitlab@gitlab.example.com:ana");
|
|
297
313
|
});
|
|
314
|
+
|
|
315
|
+
test("a merge request is open while its state is opened (#4293)", async () => {
|
|
316
|
+
const { fetch } = fakeFetch({
|
|
317
|
+
"GET /api/v4/projects/42/merge_requests/5": { iid: 5, state: "opened" },
|
|
318
|
+
"GET /api/v4/projects/42/merge_requests/6": { iid: 6, state: "merged" },
|
|
319
|
+
});
|
|
320
|
+
const forge = gitlabForge({ ...opts, fetch });
|
|
321
|
+
expect(await forge.pullRequestOpen!(5)).toBe(true);
|
|
322
|
+
expect(await forge.pullRequestOpen!(6)).toBe(false);
|
|
323
|
+
});
|
|
298
324
|
});
|
|
299
325
|
|
|
300
326
|
describe("naming reviewers", () => {
|
|
@@ -891,6 +917,13 @@ describe("pull requests over REST (#3798)", () => {
|
|
|
891
917
|
expect(l.calls.map((c) => c.body)).toEqual([{ description: "d" }, { add_labels: "minor,breaking" }]);
|
|
892
918
|
});
|
|
893
919
|
|
|
920
|
+
test("labels reads the names a pull request carries on each forge (#4292)", async () => {
|
|
921
|
+
const f = fakeFetch({ "GET /api/v1/repos/acme/infra/issues/10/labels": [{ name: "plan:prod" }, { name: "bug" }, { id: 3 }] });
|
|
922
|
+
expect(await forgejoForge({ ...fjOpts, fetch: f.fetch }).labels!(10)).toEqual(["plan:prod", "bug"]);
|
|
923
|
+
const l = fakeFetch({ "GET /api/v4/projects/group%2Finfra/merge_requests/7": { iid: 7, labels: ["plan:prod", 4] } });
|
|
924
|
+
expect(await gitlabForge({ ...gl, fetch: l.fetch }).labels!(7)).toEqual(["plan:prod"]);
|
|
925
|
+
});
|
|
926
|
+
|
|
894
927
|
test("Forgejo's 404 on the pull list of a just-pushed empty repository is no pull request (#3845)", async () => {
|
|
895
928
|
const empty = fakeFetch({ "GET /api/v1/repos/acme/infra": { default_branch: "main" } });
|
|
896
929
|
expect(await forgejoForge({ ...fjOpts, fetch: empty.fetch }).findPullRequest("chant/x")).toBeUndefined();
|