@intentius/chant 0.124.34 → 0.124.36
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/cli/handlers/graph.js +2 -2
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +147 -51
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +23 -8
- package/dist/cli/handlers/release.d.ts +12 -0
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +125 -10
- package/dist/cli/handlers/run.d.ts +3 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +12 -2
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +19 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +17 -0
- package/dist/cli/registry.d.ts +14 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +8 -1
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +3 -2
- package/dist/components/green-cover.d.ts +77 -0
- package/dist/components/green-cover.d.ts.map +1 -0
- package/dist/components/green-cover.js +153 -0
- package/dist/components/pr-pipeline.d.ts +34 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +39 -5
- package/dist/components/promote.d.ts +4 -1
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +6 -3
- package/dist/components/release-promote.d.ts +9 -0
- package/dist/components/release-promote.d.ts.map +1 -1
- package/dist/components/release-promote.js +15 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/deep-observation.d.ts +11 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/graph-declared.d.ts +5 -1
- package/dist/graph-declared.d.ts.map +1 -1
- package/dist/graph-declared.js +7 -2
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +30 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +13 -2
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +5 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -1
- package/dist/lifecycle/__fixtures__/publish-fakes.js +20 -2
- package/dist/lifecycle/compose-refs.d.ts +52 -0
- package/dist/lifecycle/compose-refs.d.ts.map +1 -0
- package/dist/lifecycle/compose-refs.js +115 -0
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.js +3 -2
- package/dist/lifecycle/green-apply.d.ts +39 -0
- package/dist/lifecycle/green-apply.d.ts.map +1 -0
- package/dist/lifecycle/green-apply.js +51 -0
- package/dist/lifecycle/publish-declaration.d.ts +6 -0
- package/dist/lifecycle/publish-declaration.d.ts.map +1 -1
- package/dist/lifecycle/publish-declaration.js +21 -0
- package/dist/lifecycle/publish-npm.d.ts +11 -4
- package/dist/lifecycle/publish-npm.d.ts.map +1 -1
- package/dist/lifecycle/publish-npm.js +104 -14
- package/dist/lifecycle/release-compose.d.ts +91 -0
- package/dist/lifecycle/release-compose.d.ts.map +1 -0
- package/dist/lifecycle/release-compose.js +235 -0
- package/dist/lifecycle/release-cut.d.ts +8 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -1
- package/dist/lifecycle/release-cut.js +2 -0
- package/dist/lifecycle/release-publish.d.ts +6 -0
- package/dist/lifecycle/release-publish.d.ts.map +1 -1
- package/dist/lifecycle/release-publish.js +2 -1
- package/dist/lifecycle/release-store.d.ts +15 -0
- package/dist/lifecycle/release-store.d.ts.map +1 -1
- package/dist/lifecycle/release-store.js +5 -1
- package/dist/wave-runtime.mjs +225 -223
- package/dist/workspace/checks/ci-workflow.d.ts.map +1 -1
- package/dist/workspace/checks/ci-workflow.js +8 -1
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +5 -3
- package/dist/workspace/ci-green-workflow.d.ts +18 -2
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +27 -5
- package/dist/workspace/ci-green.d.ts +31 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +78 -4
- package/dist/workspace/ci-run.d.ts +6 -0
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +2 -1
- package/dist/workspace/ci-serve.d.ts +94 -13
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +248 -30
- package/dist/workspace/ci-workflow.d.ts +4 -4
- package/dist/workspace/ci-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-workflow.js +2 -2
- package/dist/workspace/delivery-metrics.d.ts +3 -1
- package/dist/workspace/delivery-metrics.d.ts.map +1 -1
- package/dist/workspace/delivery-metrics.js +12 -3
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +4 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +1 -0
- package/dist/workspace/release-candidates.d.ts +28 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -1
- package/dist/workspace/release-candidates.js +38 -2
- package/dist/workspace/release-candidates.schema.json +51 -0
- package/dist/workspace/releases.schema.json +30 -0
- package/package.json +1 -1
- package/src/cli/handlers/graph.ts +2 -2
- package/src/cli/handlers/pr-green.test.ts +230 -0
- package/src/cli/handlers/pr.ts +148 -66
- package/src/cli/handlers/promote.test.ts +34 -0
- package/src/cli/handlers/promote.ts +22 -8
- package/src/cli/handlers/release-compose.e2e.test.ts +133 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +4 -1
- package/src/cli/handlers/release.ts +112 -10
- package/src/cli/handlers/run.ts +12 -1
- package/src/cli/main.ts +19 -1
- package/src/cli/parse-args.ts +13 -0
- package/src/cli/registry.ts +14 -0
- package/src/codegen/release-wiring.test.ts +56 -9
- package/src/components/auto-release.test.ts +22 -0
- package/src/components/auto-release.ts +9 -2
- package/src/components/green-cover.test.ts +170 -0
- package/src/components/green-cover.ts +186 -0
- package/src/components/pr-pipeline.test.ts +14 -4
- package/src/components/pr-pipeline.ts +56 -5
- package/src/components/promote.test.ts +7 -0
- package/src/components/promote.ts +9 -4
- package/src/components/release-promote.test.ts +24 -1
- package/src/components/release-promote.ts +15 -0
- package/src/deep-observation.ts +8 -0
- package/src/graph-declared.test.ts +18 -0
- package/src/graph-declared.ts +8 -1
- package/src/graph-detail.test.ts +68 -1
- package/src/graph-detail.ts +29 -1
- package/src/graph-ir.ts +12 -4
- package/src/lifecycle/__fixtures__/publish-fakes.ts +20 -2
- package/src/lifecycle/compose-refs.ts +125 -0
- package/src/lifecycle/deep-observe.test.ts +20 -0
- package/src/lifecycle/deep-observe.ts +4 -2
- package/src/lifecycle/green-apply.ts +71 -0
- package/src/lifecycle/publish-declaration.ts +21 -0
- package/src/lifecycle/publish-npm.ts +100 -14
- package/src/lifecycle/publish-targets.test.ts +108 -0
- package/src/lifecycle/release-compose.test.ts +125 -0
- package/src/lifecycle/release-compose.ts +312 -0
- package/src/lifecycle/release-cut.ts +6 -0
- package/src/lifecycle/release-publish.ts +8 -1
- package/src/lifecycle/release-store.ts +16 -1
- package/src/workspace/checks/ci-workflow.test.ts +12 -2
- package/src/workspace/checks/ci-workflow.ts +7 -1
- package/src/workspace/ci-green-cli.ts +5 -3
- package/src/workspace/ci-green-workflow.test.ts +52 -0
- package/src/workspace/ci-green-workflow.ts +34 -5
- package/src/workspace/ci-green.test.ts +33 -0
- package/src/workspace/ci-green.ts +85 -4
- package/src/workspace/ci-run.test.ts +18 -13
- package/src/workspace/ci-run.ts +8 -1
- package/src/workspace/ci-serve-land.e2e.test.ts +138 -0
- package/src/workspace/ci-serve-land.test.ts +176 -0
- package/src/workspace/ci-serve.test.ts +77 -43
- package/src/workspace/ci-serve.ts +268 -34
- package/src/workspace/ci-workflow.ts +4 -4
- package/src/workspace/delivery-metrics.ts +12 -3
- package/src/workspace/graph-contract.test.ts +3 -2
- package/src/workspace/member-commands.test.ts +11 -4
- package/src/workspace/member-commands.ts +3 -0
- package/src/workspace/nested-graph.test.ts +4 -0
- package/src/workspace/nested-graph.ts +1 -0
- package/src/workspace/release-candidates.schema.json +51 -0
- package/src/workspace/release-candidates.test.ts +30 -3
- package/src/workspace/release-candidates.ts +54 -2
- package/src/workspace/releases.schema.json +30 -0
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
* {@link ciRun} (./ci-run.ts): `plan` for each new pull request head, and
|
|
6
6
|
* `record` for each new commit on the target branch (#4168), measured from
|
|
7
7
|
* the head the previous poll saw (the push's `before`). The record applies
|
|
8
|
-
* nothing
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* nothing. With `applyOnMerge` (`--apply-on-merge`) the newest green commit
|
|
9
|
+
* of the branch, once no apply ran for it, gets `apply`: `pr-apply --green`
|
|
10
|
+
* at that commit, measured from the one last applied (#4168 step 9). After
|
|
11
|
+
* each job settles it runs `ci tick` from the runs the jobs recorded, when
|
|
12
|
+
* the declaration has `ci.green`, so a green commit follows the record that
|
|
13
|
+
* made its runs. What the list below says of the apply holds for it under
|
|
14
|
+
* `applyOnMerge`.
|
|
12
15
|
*
|
|
13
16
|
* - Dedupe. A state file in the state directory keeps every job it started,
|
|
14
17
|
* keyed by job and commit (`plan:<pr>:<sha>`, `record:<sha>`, `apply:<sha>`), with how it
|
|
@@ -19,10 +22,11 @@
|
|
|
19
22
|
* the one `chant operator` holds) named for `prApplyGroup`, renews it while
|
|
20
23
|
* it runs and releases it after. An apply that finds it held waits for a
|
|
21
24
|
* later poll, as a forge's concurrency group queues it.
|
|
22
|
-
* - Merged pull request.
|
|
23
|
-
* merge commit the new head is (`pullRequestFor`, as pr-
|
|
25
|
+
* - Merged pull request. A record asks the forge for the pull request whose
|
|
26
|
+
* merge commit the new head is (`pullRequestFor`, as pr-record does) and
|
|
24
27
|
* passes it as `--pr`, so its commit status link and run index use
|
|
25
|
-
* `<pr>/<commit>`. With none found it runs without `--pr`.
|
|
28
|
+
* `<pr>/<commit>`. With none found it runs without `--pr`. An apply covers
|
|
29
|
+
* every merge since the last applied green commit, so it passes none.
|
|
26
30
|
* - Resume records. Each apply gets `<state>/resume/<prApplyRecordKey>.json`
|
|
27
31
|
* as its `--resume` record, so a re-run of a failed apply at the same
|
|
28
32
|
* commit finishes under the approval it has. This replaces the forge's CI
|
|
@@ -56,6 +60,24 @@
|
|
|
56
60
|
* forge's keeps its facts, and the job's push sends them. Nothing here
|
|
57
61
|
* pushes the ledger.
|
|
58
62
|
*
|
|
63
|
+
* - Land branches (#4168 4.4). With `land`, each `chant/land/<name>` branch
|
|
64
|
+
* pushed to the remote is a change asking to land without a pull request.
|
|
65
|
+
* A head that does not descend from the target's is refused and left for
|
|
66
|
+
* its author to rebase. Otherwise `buildCommand` runs on it with
|
|
67
|
+
* `CHANT_BUILD_PHASES` set to `ci.green.preMerge` and `CHANT_BUILD_REASON`
|
|
68
|
+
* `land`, and the phases are judged from the Op run records on its head
|
|
69
|
+
* ({@link judgePreMerge}). When the command exits 0 and every phase
|
|
70
|
+
* passes, the target is pushed to the head, a fast-forward the remote
|
|
71
|
+
* refuses if the target moved, and the land branch is deleted. The next
|
|
72
|
+
* poll sees the new head and runs the record job, whose merge record
|
|
73
|
+
* names a work item or a direct push. A refused land stays in the state
|
|
74
|
+
* file under `land:<name>:<sha>`, so a new push to the branch is tried
|
|
75
|
+
* again and the same head is not.
|
|
76
|
+
* - The `git` forge kind (#4016 gap 6). With `forge: "git"` the serve reads
|
|
77
|
+
* only refs, through `git ls-remote` against `origin`, so any git server
|
|
78
|
+
* that takes pushes will do. It lists no pull requests and sets no commit
|
|
79
|
+
* status, and the jobs run without `--forge`.
|
|
80
|
+
*
|
|
59
81
|
* Host mode only: each job runs in a fresh worktree on this host as this
|
|
60
82
|
* user, with the clean environment `ciRun` gives it. A pull request from a
|
|
61
83
|
* fork is never planned, and with `authors` only those people's pull requests
|
|
@@ -65,12 +87,13 @@
|
|
|
65
87
|
* {@link ciServeObserve} is the poll that makes them; a webhook handler would
|
|
66
88
|
* turn a checked payload into the same triggers and schedule them.
|
|
67
89
|
*/
|
|
68
|
-
import { type
|
|
90
|
+
import { type BuildRequestReason } from "./ci-builds.js";
|
|
69
91
|
import { type OpenPullRequest } from "../pr-forge.js";
|
|
70
92
|
import type { CommandContext } from "../cli/registry.js";
|
|
71
93
|
import { type CiJobName, type CiRunRequest, type CiRunResult } from "./ci-run.js";
|
|
72
|
-
|
|
73
|
-
|
|
94
|
+
import { type CommitVerdict } from "./ci-green.js";
|
|
95
|
+
/** The forges `ci serve` polls. GitLab is later. `git` is any git server, read through refs alone (#4016 gap 6). */
|
|
96
|
+
export type CiServeForgeKind = "github" | "forgejo" | "git";
|
|
74
97
|
/** What `ci serve` reads from the forge: the two calls GitHub's and Forgejo's clients have (../pr-forge.ts). */
|
|
75
98
|
export interface CiServeForge {
|
|
76
99
|
openPullRequests(base: string): Promise<OpenPullRequest[]>;
|
|
@@ -80,8 +103,8 @@ export interface CiServeForge {
|
|
|
80
103
|
}
|
|
81
104
|
/**
|
|
82
105
|
* A job the forge's state calls for: the plan of a pull request's head, or
|
|
83
|
-
* for a target-branch commit its record (#4168) or
|
|
84
|
-
*
|
|
106
|
+
* for a target-branch commit its record (#4168), or with `applyOnMerge` the
|
|
107
|
+
* apply of the newest green commit (#4168 step 9).
|
|
85
108
|
*/
|
|
86
109
|
export type CiServeTrigger = {
|
|
87
110
|
job: "plan";
|
|
@@ -95,6 +118,30 @@ export type CiServeTrigger = {
|
|
|
95
118
|
base: string;
|
|
96
119
|
rerun?: boolean;
|
|
97
120
|
};
|
|
121
|
+
/**
|
|
122
|
+
* The `git` forge kind (#4016 gap 6): the branch head from `git ls-remote`
|
|
123
|
+
* against `remote`, and no pull requests. A remote that can't be reached is
|
|
124
|
+
* an error for the pass, as a forge API's would be.
|
|
125
|
+
*/
|
|
126
|
+
export declare function gitCiServeForge(top: string, remote?: string): CiServeForge;
|
|
127
|
+
/** What a build runs: a `ci/build/<sha>` request (#4169), or a land branch's pre-merge phases (#4168 4.4). */
|
|
128
|
+
export interface CiServeBuildInput {
|
|
129
|
+
commit: string;
|
|
130
|
+
phases: string[];
|
|
131
|
+
reason: BuildRequestReason | "land";
|
|
132
|
+
}
|
|
133
|
+
/** Where pushed land branches live on the remote (#4168 4.4). */
|
|
134
|
+
export declare const LAND_BRANCH_PREFIX = "chant/land/";
|
|
135
|
+
/** One land branch head in the state file, by `land:<name>:<sha>`. */
|
|
136
|
+
export interface CiServeLandState {
|
|
137
|
+
branch: string;
|
|
138
|
+
commit: string;
|
|
139
|
+
status: "running" | "landed" | "refused";
|
|
140
|
+
/** Why it was refused, in a line. */
|
|
141
|
+
reason?: string;
|
|
142
|
+
startedAt: string;
|
|
143
|
+
settledAt?: string;
|
|
144
|
+
}
|
|
98
145
|
/** One job in the state file. */
|
|
99
146
|
export interface CiServeJobState {
|
|
100
147
|
job: CiJobName;
|
|
@@ -132,6 +179,8 @@ export interface CiServeState {
|
|
|
132
179
|
startedAt: string;
|
|
133
180
|
settledAt?: string;
|
|
134
181
|
}>;
|
|
182
|
+
/** The land branch heads seen (#4168 4.4), by `land:<name>:<sha>`. */
|
|
183
|
+
lands?: Record<string, CiServeLandState>;
|
|
135
184
|
}
|
|
136
185
|
/** What a pass did, in order, for the log. */
|
|
137
186
|
export type CiServeEvent = {
|
|
@@ -189,6 +238,15 @@ export type CiServeEvent = {
|
|
|
189
238
|
status: "started" | "ok" | "fail" | "held";
|
|
190
239
|
exitCode?: number;
|
|
191
240
|
}
|
|
241
|
+
/** A land branch (#4168 4.4): its pre-merge phases started, the target moved to it, or it was refused and left. */
|
|
242
|
+
| {
|
|
243
|
+
type: "land";
|
|
244
|
+
branch: string;
|
|
245
|
+
commit: string;
|
|
246
|
+
status: "started" | "landed" | "refused" | "held";
|
|
247
|
+
reason?: string;
|
|
248
|
+
warning?: string;
|
|
249
|
+
}
|
|
192
250
|
/** `ci tick` after the pass (#4168): the tags it made from the runs. */
|
|
193
251
|
| {
|
|
194
252
|
type: "tick";
|
|
@@ -244,9 +302,21 @@ export interface CiServeOptions {
|
|
|
244
302
|
/** Runs a build. Default: `sh -c <buildCommand>` in a detached worktree of the commit. */
|
|
245
303
|
build?: (input: {
|
|
246
304
|
top: string;
|
|
247
|
-
request:
|
|
305
|
+
request: CiServeBuildInput;
|
|
248
306
|
command: string;
|
|
249
307
|
}) => Promise<number>;
|
|
308
|
+
/**
|
|
309
|
+
* Land `chant/land/<name>` branches (#4168 4.4): the `ci.green.preMerge`
|
|
310
|
+
* phases each must pass first. Needs `buildCommand`, which runs them.
|
|
311
|
+
*/
|
|
312
|
+
land?: {
|
|
313
|
+
phases: string[];
|
|
314
|
+
};
|
|
315
|
+
/** Judges a land head's phases. Default {@link judgePreMerge} from the Op run records. */
|
|
316
|
+
judgeLand?: (input: {
|
|
317
|
+
commit: string;
|
|
318
|
+
base: string;
|
|
319
|
+
}) => Promise<CommitVerdict>;
|
|
250
320
|
}
|
|
251
321
|
/** The jobs this process runs now, by state key, shared by every pass of one loop. */
|
|
252
322
|
export interface CiServeRuntime {
|
|
@@ -321,6 +391,17 @@ export declare function ciServeBuilds(opts: CiServeOptions, rt: CiServeRuntime):
|
|
|
321
391
|
started: Promise<void>[];
|
|
322
392
|
events: CiServeEvent[];
|
|
323
393
|
};
|
|
394
|
+
/**
|
|
395
|
+
* Serve the land branches (#4168 4.4): for each `chant/land/<name>` head not
|
|
396
|
+
* in the state file, refuse it unless it descends from the target's head,
|
|
397
|
+
* else run `buildCommand` on it with the pre-merge phases, judge them, and
|
|
398
|
+
* when they pass push the target to it and delete the branch. At most
|
|
399
|
+
* `maxJobs` builds and jobs run at once.
|
|
400
|
+
*/
|
|
401
|
+
export declare function ciServeLands(opts: CiServeOptions, rt: CiServeRuntime): Promise<{
|
|
402
|
+
started: Promise<void>[];
|
|
403
|
+
events: CiServeEvent[];
|
|
404
|
+
}>;
|
|
324
405
|
/**
|
|
325
406
|
* The loop: a pass every `intervalMs` until `signal` aborts, jobs running
|
|
326
407
|
* across passes up to `maxJobs`. A pass the forge fails is logged and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ci-serve.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-serve.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ci-serve.d.ts","sourceRoot":"","sources":["../../src/workspace/ci-serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
* {@link ciRun} (./ci-run.ts): `plan` for each new pull request head, and
|
|
6
6
|
* `record` for each new commit on the target branch (#4168), measured from
|
|
7
7
|
* the head the previous poll saw (the push's `before`). The record applies
|
|
8
|
-
* nothing
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* nothing. With `applyOnMerge` (`--apply-on-merge`) the newest green commit
|
|
9
|
+
* of the branch, once no apply ran for it, gets `apply`: `pr-apply --green`
|
|
10
|
+
* at that commit, measured from the one last applied (#4168 step 9). After
|
|
11
|
+
* each job settles it runs `ci tick` from the runs the jobs recorded, when
|
|
12
|
+
* the declaration has `ci.green`, so a green commit follows the record that
|
|
13
|
+
* made its runs. What the list below says of the apply holds for it under
|
|
14
|
+
* `applyOnMerge`.
|
|
12
15
|
*
|
|
13
16
|
* - Dedupe. A state file in the state directory keeps every job it started,
|
|
14
17
|
* keyed by job and commit (`plan:<pr>:<sha>`, `record:<sha>`, `apply:<sha>`), with how it
|
|
@@ -19,10 +22,11 @@
|
|
|
19
22
|
* the one `chant operator` holds) named for `prApplyGroup`, renews it while
|
|
20
23
|
* it runs and releases it after. An apply that finds it held waits for a
|
|
21
24
|
* later poll, as a forge's concurrency group queues it.
|
|
22
|
-
* - Merged pull request.
|
|
23
|
-
* merge commit the new head is (`pullRequestFor`, as pr-
|
|
25
|
+
* - Merged pull request. A record asks the forge for the pull request whose
|
|
26
|
+
* merge commit the new head is (`pullRequestFor`, as pr-record does) and
|
|
24
27
|
* passes it as `--pr`, so its commit status link and run index use
|
|
25
|
-
* `<pr>/<commit>`. With none found it runs without `--pr`.
|
|
28
|
+
* `<pr>/<commit>`. With none found it runs without `--pr`. An apply covers
|
|
29
|
+
* every merge since the last applied green commit, so it passes none.
|
|
26
30
|
* - Resume records. Each apply gets `<state>/resume/<prApplyRecordKey>.json`
|
|
27
31
|
* as its `--resume` record, so a re-run of a failed apply at the same
|
|
28
32
|
* commit finishes under the approval it has. This replaces the forge's CI
|
|
@@ -56,6 +60,24 @@
|
|
|
56
60
|
* forge's keeps its facts, and the job's push sends them. Nothing here
|
|
57
61
|
* pushes the ledger.
|
|
58
62
|
*
|
|
63
|
+
* - Land branches (#4168 4.4). With `land`, each `chant/land/<name>` branch
|
|
64
|
+
* pushed to the remote is a change asking to land without a pull request.
|
|
65
|
+
* A head that does not descend from the target's is refused and left for
|
|
66
|
+
* its author to rebase. Otherwise `buildCommand` runs on it with
|
|
67
|
+
* `CHANT_BUILD_PHASES` set to `ci.green.preMerge` and `CHANT_BUILD_REASON`
|
|
68
|
+
* `land`, and the phases are judged from the Op run records on its head
|
|
69
|
+
* ({@link judgePreMerge}). When the command exits 0 and every phase
|
|
70
|
+
* passes, the target is pushed to the head, a fast-forward the remote
|
|
71
|
+
* refuses if the target moved, and the land branch is deleted. The next
|
|
72
|
+
* poll sees the new head and runs the record job, whose merge record
|
|
73
|
+
* names a work item or a direct push. A refused land stays in the state
|
|
74
|
+
* file under `land:<name>:<sha>`, so a new push to the branch is tried
|
|
75
|
+
* again and the same head is not.
|
|
76
|
+
* - The `git` forge kind (#4016 gap 6). With `forge: "git"` the serve reads
|
|
77
|
+
* only refs, through `git ls-remote` against `origin`, so any git server
|
|
78
|
+
* that takes pushes will do. It lists no pull requests and sets no commit
|
|
79
|
+
* status, and the jobs run without `--forge`.
|
|
80
|
+
*
|
|
59
81
|
* Host mode only: each job runs in a fresh worktree on this host as this
|
|
60
82
|
* user, with the clean environment `ciRun` gives it. A pull request from a
|
|
61
83
|
* fork is never planned, and with `authors` only those people's pull requests
|
|
@@ -77,9 +99,31 @@ import { acquireLease, currentHolderId, releaseLease, DEFAULT_LEASE_TTL_MS } fro
|
|
|
77
99
|
import { parseDuration } from "../op/duration.js";
|
|
78
100
|
import { reportUrlOf } from "./ci-report-url.js";
|
|
79
101
|
import { CI_JOB_OPS, ciJobPlace, ciLoopOptions, ciSecretsOf, ciRun, ciStatusFromEnv, CiRunError } from "./ci-run.js";
|
|
80
|
-
import { CiGreenError, ciTick, RUNNER_KEY_ENV, signedRunsRequired } from "./ci-green.js";
|
|
102
|
+
import { CiGreenError, ciTick, judgePreMerge, lastGreen, RUNNER_KEY_ENV, signedRunsRequired } from "./ci-green.js";
|
|
81
103
|
import { gitTop } from "./tree.js";
|
|
104
|
+
import { readDeclaration } from "./declaration.js";
|
|
105
|
+
import { locateWorkspace } from "./which-chant.js";
|
|
82
106
|
import { ciServeRunnerKey, formatRunnerKeySetup, setupRunnerKey } from "./ci-runner-key.js";
|
|
107
|
+
/**
|
|
108
|
+
* The `git` forge kind (#4016 gap 6): the branch head from `git ls-remote`
|
|
109
|
+
* against `remote`, and no pull requests. A remote that can't be reached is
|
|
110
|
+
* an error for the pass, as a forge API's would be.
|
|
111
|
+
*/
|
|
112
|
+
export function gitCiServeForge(top, remote = "origin") {
|
|
113
|
+
return {
|
|
114
|
+
openPullRequests: async () => [],
|
|
115
|
+
async branchHead(branch) {
|
|
116
|
+
const r = gitStatus(top, ["ls-remote", "--exit-code", remote, `refs/heads/${branch}`]);
|
|
117
|
+
if (r.code === 2)
|
|
118
|
+
return undefined;
|
|
119
|
+
if (r.code !== 0)
|
|
120
|
+
throw new CiRunError(`git ls-remote ${remote} could not read refs/heads/${branch}`);
|
|
121
|
+
return r.out.split(/\s+/)[0] || undefined;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Where pushed land branches live on the remote (#4168 4.4). */
|
|
126
|
+
export const LAND_BRANCH_PREFIX = "chant/land/";
|
|
83
127
|
export const newCiServeRuntime = () => ({ inflight: new Map() });
|
|
84
128
|
/** The state key of a job. */
|
|
85
129
|
export function ciServeKey(t) {
|
|
@@ -224,8 +268,8 @@ export async function ciServeObserve(opts, rt = newCiServeRuntime()) {
|
|
|
224
268
|
const triggers = [];
|
|
225
269
|
const events = [];
|
|
226
270
|
const [head, prs] = await Promise.all([opts.client.branchHead(place.branch), opts.client.openPullRequests(place.branch)]);
|
|
227
|
-
// #4168: a new target commit is recorded
|
|
228
|
-
const onMerge =
|
|
271
|
+
// #4168: a new target commit is recorded. With applyOnMerge the newest green commit is applied, below.
|
|
272
|
+
const onMerge = "record";
|
|
229
273
|
if (head === undefined) {
|
|
230
274
|
events.push({ type: "no-branch", branch: place.branch });
|
|
231
275
|
}
|
|
@@ -261,6 +305,17 @@ export async function ciServeObserve(opts, rt = newCiServeRuntime()) {
|
|
|
261
305
|
if (opts.rerunFailed && failedEntry(entry, key, rt))
|
|
262
306
|
triggers.push({ job: onMerge, commit: head, base: entry.base, rerun: true });
|
|
263
307
|
}
|
|
308
|
+
if (opts.applyOnMerge && head !== undefined) {
|
|
309
|
+
const green = newestGreen(place.top, opts.cwd, place.branch);
|
|
310
|
+
if (green) {
|
|
311
|
+
const key = ciServeKey({ job: "apply", commit: green });
|
|
312
|
+
const entry = state.jobs[key];
|
|
313
|
+
if (!entry)
|
|
314
|
+
triggers.push({ job: "apply", commit: green, base: green });
|
|
315
|
+
else if (opts.rerunFailed && failedEntry(entry, key, rt))
|
|
316
|
+
triggers.push({ job: "apply", commit: green, base: entry.base, rerun: true });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
264
319
|
const authors = opts.authors?.length ? new Set(opts.authors.map((a) => a.toLowerCase())) : undefined;
|
|
265
320
|
for (const pr of prs) {
|
|
266
321
|
if (pr.fork) {
|
|
@@ -358,7 +413,7 @@ export async function ciServeSchedule(opts, triggers, rt = newCiServeRuntime())
|
|
|
358
413
|
const entry = { job: t.job, commit: t.commit, base: t.base, ...pr, status: "running", runId, attempts: attempt, startedAt: now().toISOString() };
|
|
359
414
|
update((s) => {
|
|
360
415
|
s.jobs[key] = entry;
|
|
361
|
-
if (t.job
|
|
416
|
+
if (t.job === "record" && !t.rerun)
|
|
362
417
|
s.target = t.commit;
|
|
363
418
|
});
|
|
364
419
|
emit({ type: "started", job: t.job, commit: t.commit, ...pr, base: t.base, runId, attempt, ...(resume && existsSync(resume) ? { resume } : {}) });
|
|
@@ -395,7 +450,7 @@ async function runJob(opts, place, t, paths, held) {
|
|
|
395
450
|
tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", t.job === "plan" ? `refs/pull/${t.pr}/head` : place.branch]);
|
|
396
451
|
const hostEnv = { ...(opts.job?.hostEnv ?? process.env), GITHUB_REPOSITORY: opts.repo };
|
|
397
452
|
// An apply is for a merge: pass the pull request whose merge commit it is, so the commit status link and the run index use `<pr>/<commit>`. None found, or a forge that errs, is today's behaviour.
|
|
398
|
-
if (t.job
|
|
453
|
+
if (t.job === "record" && opts.client.pullRequestFor)
|
|
399
454
|
merged = (await opts.client.pullRequestFor(t.commit).catch(() => null)) ?? undefined;
|
|
400
455
|
const run = opts.run ?? ciRun;
|
|
401
456
|
const r = await run({
|
|
@@ -404,7 +459,8 @@ async function runJob(opts, place, t, paths, held) {
|
|
|
404
459
|
cwd: opts.cwd,
|
|
405
460
|
job: t.job,
|
|
406
461
|
env: opts.env,
|
|
407
|
-
|
|
462
|
+
// #4016 gap 6: a plain git server has no API, so the job runs without --forge, shaped as a GitHub job.
|
|
463
|
+
...(opts.forge === "git" ? { forge: "github", noForge: true } : { forge: opts.forge }),
|
|
408
464
|
base: t.base,
|
|
409
465
|
...(t.job === "plan" ? { pr: t.pr } : merged !== undefined ? { pr: merged } : {}),
|
|
410
466
|
commit: t.commit,
|
|
@@ -425,6 +481,21 @@ async function runJob(opts, place, t, paths, held) {
|
|
|
425
481
|
await releaseLease(place.group, held.holder, held.lease.token, { cwd: opts.cwd }).catch(() => false);
|
|
426
482
|
}
|
|
427
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* The newest green commit of the target branch (#4168 step 9), after
|
|
486
|
+
* fetching the ci tags, or undefined when none is green or the workspace
|
|
487
|
+
* declares no `ci.green`.
|
|
488
|
+
*/
|
|
489
|
+
function newestGreen(top, cwd, branch) {
|
|
490
|
+
tryGit(top, ["fetch", "--quiet", "origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`, "+refs/tags/ci/*:refs/tags/ci/*"]);
|
|
491
|
+
try {
|
|
492
|
+
const doc = lastGreen({ cwd });
|
|
493
|
+
return "commit" in doc && doc.commit ? doc.commit.sha : undefined;
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
428
499
|
/** One pass: observe, schedule, serve build requests, and with `wait` wait for the jobs it started. */
|
|
429
500
|
export async function ciServePass(opts, rt = newCiServeRuntime(), wait = true) {
|
|
430
501
|
const observed = await ciServeObserve(opts, rt);
|
|
@@ -432,10 +503,11 @@ export async function ciServePass(opts, rt = newCiServeRuntime(), wait = true) {
|
|
|
432
503
|
opts.log?.(e);
|
|
433
504
|
const scheduled = await ciServeSchedule(opts, observed.triggers, rt);
|
|
434
505
|
const builds = opts.buildCommand ? ciServeBuilds(opts, rt) : { started: [], events: [] };
|
|
506
|
+
const lands = opts.land && opts.buildCommand ? await ciServeLands(opts, rt) : { started: [], events: [] };
|
|
435
507
|
const jobs = wait ? await Promise.all(scheduled.started) : [];
|
|
436
508
|
if (wait)
|
|
437
|
-
await Promise.all(builds.started);
|
|
438
|
-
return { events: [...observed.events, ...scheduled.events, ...builds.events], jobs };
|
|
509
|
+
await Promise.all([...builds.started, ...lands.started]);
|
|
510
|
+
return { events: [...observed.events, ...scheduled.events, ...builds.events, ...lands.events], jobs };
|
|
439
511
|
}
|
|
440
512
|
/**
|
|
441
513
|
* Serve the build requests (#4169): fetch the `ci/build/*` tags, and start
|
|
@@ -489,6 +561,110 @@ export function ciServeBuilds(opts, rt) {
|
|
|
489
561
|
}
|
|
490
562
|
return { started, events };
|
|
491
563
|
}
|
|
564
|
+
/** The land branches on `origin`, by name, with their heads (#4168 4.4). */
|
|
565
|
+
function landBranches(top) {
|
|
566
|
+
const r = gitStatus(top, ["ls-remote", "origin", `refs/heads/${LAND_BRANCH_PREFIX}*`]);
|
|
567
|
+
if (r.code !== 0)
|
|
568
|
+
return [];
|
|
569
|
+
return r.out
|
|
570
|
+
.split("\n")
|
|
571
|
+
.map((line) => line.split(/\s+/))
|
|
572
|
+
.filter(([sha, ref]) => sha && ref?.startsWith(`refs/heads/${LAND_BRANCH_PREFIX}`))
|
|
573
|
+
.map(([head, ref]) => ({ name: ref.slice(`refs/heads/${LAND_BRANCH_PREFIX}`.length), head: head }))
|
|
574
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
575
|
+
}
|
|
576
|
+
/** Why a land's phases don't let it land, or undefined when every one passed. */
|
|
577
|
+
function landRefusal(verdict) {
|
|
578
|
+
if (verdict.verdict === "pass")
|
|
579
|
+
return undefined;
|
|
580
|
+
const failed = verdict.phases.filter((p) => p.verdict === "fail").map((p) => p.phase);
|
|
581
|
+
if (failed.length > 0)
|
|
582
|
+
return `the pre-merge phase${failed.length > 1 ? "s" : ""} ${failed.join(", ")} failed`;
|
|
583
|
+
const open = verdict.phases.filter((p) => p.verdict !== "pass").map((p) => p.phase);
|
|
584
|
+
return `the build recorded no finished run for ${open.join(", ")}`;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Serve the land branches (#4168 4.4): for each `chant/land/<name>` head not
|
|
588
|
+
* in the state file, refuse it unless it descends from the target's head,
|
|
589
|
+
* else run `buildCommand` on it with the pre-merge phases, judge them, and
|
|
590
|
+
* when they pass push the target to it and delete the branch. At most
|
|
591
|
+
* `maxJobs` builds and jobs run at once.
|
|
592
|
+
*/
|
|
593
|
+
export async function ciServeLands(opts, rt) {
|
|
594
|
+
const place = servePlace(opts);
|
|
595
|
+
const fresh = { forge: opts.forge, repo: opts.repo, env: opts.env, branch: place.branch, ...(place.member ? { member: place.member } : {}) };
|
|
596
|
+
const update = (fn) => {
|
|
597
|
+
const st = readCiServeState(place.stateFile, fresh);
|
|
598
|
+
fn(st);
|
|
599
|
+
writeState(place.stateFile, st);
|
|
600
|
+
};
|
|
601
|
+
const events = [];
|
|
602
|
+
const started = [];
|
|
603
|
+
const now = opts.now ?? (() => new Date());
|
|
604
|
+
const log = (e) => {
|
|
605
|
+
events.push(e);
|
|
606
|
+
opts.log?.(e);
|
|
607
|
+
};
|
|
608
|
+
const phases = opts.land.phases;
|
|
609
|
+
const seen = readCiServeState(place.stateFile, fresh).lands ?? {};
|
|
610
|
+
for (const { name, head } of landBranches(place.top)) {
|
|
611
|
+
const key = `land:${name}:${head}`;
|
|
612
|
+
const branch = `${LAND_BRANCH_PREFIX}${name}`;
|
|
613
|
+
if (seen[key] || rt.inflight.has(key))
|
|
614
|
+
continue;
|
|
615
|
+
if (rt.inflight.size >= (opts.maxJobs ?? 1)) {
|
|
616
|
+
log({ type: "land", branch, commit: head, status: "held", reason: "--max-jobs jobs run" });
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
const settle = (status, reason, warning) => {
|
|
620
|
+
update((st) => {
|
|
621
|
+
st.lands = { ...st.lands, [key]: { branch, commit: head, status, ...(reason ? { reason } : {}), startedAt: st.lands?.[key]?.startedAt ?? now().toISOString(), settledAt: now().toISOString() } };
|
|
622
|
+
});
|
|
623
|
+
log({ type: "land", branch, commit: head, status, ...(reason ? { reason } : {}), ...(warning ? { warning } : {}) });
|
|
624
|
+
};
|
|
625
|
+
const target = await opts.client.branchHead(place.branch);
|
|
626
|
+
if (target === undefined) {
|
|
627
|
+
log({ type: "error", message: `${branch} waits: the remote has no branch ${place.branch} to land it on` });
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", place.branch, `+refs/heads/${branch}:refs/chant/ci-serve/land/${name}`]);
|
|
631
|
+
// #4168 4.4: a land only ever fast-forwards the target, so its head must descend from the target's.
|
|
632
|
+
if (gitStatus(place.top, ["merge-base", "--is-ancestor", target, head]).code !== 0) {
|
|
633
|
+
settle("refused", `${head.slice(0, 12)} does not descend from ${place.branch} at ${target.slice(0, 12)}; rebase ${branch} onto it and push again`);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
update((st) => {
|
|
637
|
+
st.lands = { ...st.lands, [key]: { branch, commit: head, status: "running", startedAt: now().toISOString() } };
|
|
638
|
+
});
|
|
639
|
+
log({ type: "land", branch, commit: head, status: "started" });
|
|
640
|
+
const build = opts.build ?? runBuild;
|
|
641
|
+
const judge = opts.judgeLand ?? ((input) => judgePreMerge({ cwd: opts.cwd, ...input }));
|
|
642
|
+
const job = (async () => {
|
|
643
|
+
const code = await build({ top: place.top, request: { commit: head, phases, reason: "land" }, command: opts.buildCommand }).catch(() => 1);
|
|
644
|
+
if (code !== 0)
|
|
645
|
+
return settle("refused", `the build of ${phases.join(", ")} exited ${code}`);
|
|
646
|
+
let refusal;
|
|
647
|
+
try {
|
|
648
|
+
refusal = landRefusal(await judge({ commit: head, base: target }));
|
|
649
|
+
}
|
|
650
|
+
catch (err) {
|
|
651
|
+
refusal = `the pre-merge phases could not be judged: ${message(err)}`;
|
|
652
|
+
}
|
|
653
|
+
if (refusal)
|
|
654
|
+
return settle("refused", refusal);
|
|
655
|
+
// A plain push: the remote refuses it unless it fast-forwards the target, so a target that moved meanwhile refuses the land.
|
|
656
|
+
const pushed = spawnSync("git", ["push", "--quiet", "origin", `${head}:refs/heads/${place.branch}`], { cwd: place.top, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
657
|
+
if (pushed.status !== 0)
|
|
658
|
+
return settle("refused", `the push to ${place.branch} was refused: ${(pushed.stderr ?? "").trim().split("\n").pop() ?? ""}`);
|
|
659
|
+
const deleted = gitStatus(place.top, ["push", "--quiet", "origin", "--delete", `refs/heads/${branch}`]);
|
|
660
|
+
tryGit(place.top, ["update-ref", "-d", `refs/chant/ci-serve/land/${name}`]);
|
|
661
|
+
settle("landed", undefined, deleted.code === 0 ? undefined : `${branch} could not be deleted on the remote; delete it by hand`);
|
|
662
|
+
})().finally(() => rt.inflight.delete(key));
|
|
663
|
+
rt.inflight.set(key, job);
|
|
664
|
+
started.push(job);
|
|
665
|
+
}
|
|
666
|
+
return { started, events };
|
|
667
|
+
}
|
|
492
668
|
/** `sh -c <command>` in a fresh detached worktree of the request's commit, removed after. Resolves to the exit code. */
|
|
493
669
|
async function runBuild(input) {
|
|
494
670
|
const dir = mkdtempSync(join(tmpdir(), "chant-ci-build-"));
|
|
@@ -579,6 +755,16 @@ export function formatCiServeEvent(e) {
|
|
|
579
755
|
return `${what} waits: --max-jobs jobs run`;
|
|
580
756
|
return e.status === "started" ? `${what} started` : e.status === "ok" ? `${what} passed` : `${what} failed${e.exitCode !== undefined ? ` with exit code ${e.exitCode}` : ""}`;
|
|
581
757
|
}
|
|
758
|
+
case "land": {
|
|
759
|
+
const what = `${e.branch} at ${e.commit.slice(0, 12)}`;
|
|
760
|
+
if (e.status === "started")
|
|
761
|
+
return `${what}: the pre-merge phases started`;
|
|
762
|
+
if (e.status === "held")
|
|
763
|
+
return `${what} waits: ${e.reason ?? "--max-jobs jobs run"}`;
|
|
764
|
+
if (e.status === "refused")
|
|
765
|
+
return `${what} was refused and left on the remote: ${e.reason ?? "no reason given"}`;
|
|
766
|
+
return `${what} landed${e.warning ? `; ${e.warning}` : ", and the land branch was deleted"}`;
|
|
767
|
+
}
|
|
582
768
|
case "tick":
|
|
583
769
|
return e.made.length > 0 ? `ci tick made ${e.made.join(", ")}` : "ci tick: no tag due";
|
|
584
770
|
case "error":
|
|
@@ -614,8 +800,8 @@ export function runnerKeySetupCommand(ctx) {
|
|
|
614
800
|
return 0;
|
|
615
801
|
}
|
|
616
802
|
const USAGE = "chant ci serve --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>] | " +
|
|
617
|
-
"chant ci serve --forge forgejo|github --repo <owner/repo> --env <env> [--interval <s>] [--max-jobs <n>] [--state <dir>] " +
|
|
618
|
-
"[--author <login>]... [--once] [--rerun-failed] [--build-command <line>] [--gate <name>] [--wave-gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--no-status] [--status-url <url template>] " +
|
|
803
|
+
"chant ci serve --forge forgejo|github|git --repo <owner/repo> --env <env> [--interval <s>] [--max-jobs <n>] [--state <dir>] " +
|
|
804
|
+
"[--author <login>]... [--once] [--rerun-failed] [--build-command <line>] [--land] [--gate <name>] [--wave-gate <name>] [--branch <name>] [--events-json] [--report-url <url>] [--no-status] [--status-url <url template>] " +
|
|
619
805
|
"[--image <image>] [--before-script <line>]... [--pr-build] [--pr-build-command <line>] [--chant <command>] [--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>] [--unsigned]";
|
|
620
806
|
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
621
807
|
/** `--interval`: seconds, or a duration such as `30s` or `2m`. Default 30 seconds. */
|
|
@@ -627,6 +813,19 @@ export function ciServeInterval(value) {
|
|
|
627
813
|
throw new CiRunError(`--interval takes seconds or a duration of a second or more, not ${JSON.stringify(value)}`);
|
|
628
814
|
return ms;
|
|
629
815
|
}
|
|
816
|
+
/** `ci.green.preMerge` where `cwd` is, for `--land` (#4168 4.4). Throws when the declaration names none. */
|
|
817
|
+
function preMergeOf(cwd) {
|
|
818
|
+
let pre;
|
|
819
|
+
try {
|
|
820
|
+
pre = readDeclaration(locateWorkspace(cwd).tree).ci?.green?.preMerge;
|
|
821
|
+
}
|
|
822
|
+
catch (err) {
|
|
823
|
+
throw new CiRunError(`--land reads ci.green.preMerge from the workspace declaration, which could not be read: ${message(err)}`);
|
|
824
|
+
}
|
|
825
|
+
if (!pre || pre.length === 0)
|
|
826
|
+
throw new CiRunError("--land runs the ci.green.preMerge phases on each land branch, and the declaration names none; add them to ci.green");
|
|
827
|
+
return pre;
|
|
828
|
+
}
|
|
630
829
|
/** `chant ci serve` (#4095). */
|
|
631
830
|
export async function runCiServe(ctx) {
|
|
632
831
|
const { args } = ctx;
|
|
@@ -634,10 +833,14 @@ export async function runCiServe(ctx) {
|
|
|
634
833
|
if (args.setupKey)
|
|
635
834
|
return runnerKeySetupCommand(ctx);
|
|
636
835
|
const forge = args.forge;
|
|
637
|
-
if (forge !== "forgejo" && forge !== "github")
|
|
638
|
-
throw new CiRunError(`--forge names the forge ci serve polls: --forge forgejo|github${forge ? `, not ${JSON.stringify(forge)}` : ""}`);
|
|
639
|
-
|
|
836
|
+
if (forge !== "forgejo" && forge !== "github" && forge !== "git")
|
|
837
|
+
throw new CiRunError(`--forge names the forge ci serve polls: --forge forgejo|github|git${forge ? `, not ${JSON.stringify(forge)}` : ""}`);
|
|
838
|
+
// #4016 gap 6: a plain git server has no repository name to ask an API for, so --repo only labels the state file.
|
|
839
|
+
if (!args.ciRepo && forge !== "git")
|
|
640
840
|
throw new CiRunError("--repo names the repository whose pull requests ci serve polls: --repo <owner/repo>");
|
|
841
|
+
// #4168 4.4: landing runs the pre-merge phases through the build command.
|
|
842
|
+
if (args.ciLand && !args.ciBuildCommand)
|
|
843
|
+
throw new CiRunError("--land runs each land branch's pre-merge phases with --build-command, so it needs one: --build-command '<line>'");
|
|
641
844
|
if (!args.env)
|
|
642
845
|
throw new CiRunError("--env names the environment the pipeline is for: --env <env>");
|
|
643
846
|
for (const name of args.passEnv ?? [])
|
|
@@ -653,6 +856,8 @@ export async function runCiServe(ctx) {
|
|
|
653
856
|
throw new CiRunError("ci serve runs in a clone of the repository it serves, and this directory is not in a git repository");
|
|
654
857
|
const stateDir = args.statePath ? resolve(args.statePath) : defaultCiServeStateDir(cwd);
|
|
655
858
|
const branch = args.branch ?? "main";
|
|
859
|
+
const repo = args.ciRepo ?? (tryGit(top, ["remote", "get-url", "origin"]) || "origin");
|
|
860
|
+
const landPhases = args.ciLand ? preMergeOf(cwd) : undefined;
|
|
656
861
|
// #4099: the host's key, checked against .chant/trust.json at the target's head before the first poll.
|
|
657
862
|
// #4156: signing is opt-in. Without ci.green.signedSince no key is quiet, and an untrusted one warns once and serves unsigned.
|
|
658
863
|
const keyed = ciServeRunnerKey({
|
|
@@ -678,28 +883,41 @@ export async function runCiServe(ctx) {
|
|
|
678
883
|
const reportUrl = await reportUrlOf(args.reportUrl, cwd);
|
|
679
884
|
if (reportUrl && !args.reportUrl)
|
|
680
885
|
console.error(`ci serve: reports and the run index go to ${reportUrl}, the declaration's ci.reportUrl`);
|
|
681
|
-
const hostEnv = { ...process.env, GITHUB_REPOSITORY:
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
886
|
+
const hostEnv = { ...process.env, GITHUB_REPOSITORY: repo };
|
|
887
|
+
let client;
|
|
888
|
+
let status;
|
|
889
|
+
if (forge === "git") {
|
|
890
|
+
// #4016 gap 6: refs only, and no commit status to set.
|
|
891
|
+
client = gitCiServeForge(top);
|
|
892
|
+
if (!args.noStatus)
|
|
893
|
+
console.error(formatWarning({ message: "the git forge kind has no API, so no commit status is set; each job's result is in the state file and its report" }));
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
const api = forgeFromEnv(forge, hostEnv);
|
|
897
|
+
const fromEnv = ciStatusFromEnv({ forge, hostEnv, noStatus: args.noStatus, statusUrl: args.statusUrl });
|
|
898
|
+
if (fromEnv.warning)
|
|
899
|
+
console.error(formatWarning({ message: fromEnv.warning }));
|
|
900
|
+
status = fromEnv.status;
|
|
901
|
+
if (!api.openPullRequests || !api.branchHead)
|
|
902
|
+
throw new CiRunError(`the ${forge} client cannot list pull requests`);
|
|
903
|
+
client = { openPullRequests: api.openPullRequests.bind(api), branchHead: api.branchHead.bind(api), pullRequestFor: api.pullRequestFor.bind(api) };
|
|
904
|
+
}
|
|
688
905
|
// #4168: a host that applied each merge before the upgrade hears that it no longer does.
|
|
689
906
|
if (!args.applyOnMerge)
|
|
690
|
-
console.error("ci serve: each new commit on the target branch gets the record job, which applies nothing (#4168); --apply-on-merge applies
|
|
907
|
+
console.error("ci serve: each new commit on the target branch gets the record job, which applies nothing (#4168); --apply-on-merge also applies each newest green commit");
|
|
691
908
|
const opts = {
|
|
692
909
|
cwd,
|
|
693
910
|
forge,
|
|
694
|
-
repo
|
|
911
|
+
repo,
|
|
695
912
|
env: args.env,
|
|
696
|
-
client
|
|
913
|
+
client,
|
|
697
914
|
stateDir,
|
|
698
915
|
...(args.maxJobs ? { maxJobs: args.maxJobs } : {}),
|
|
699
916
|
...(args.ciAuthors?.length ? { authors: args.ciAuthors } : {}),
|
|
700
917
|
...(args.rerunFailed ? { rerunFailed: true } : {}),
|
|
701
918
|
...(args.ciBuildCommand ? { buildCommand: args.ciBuildCommand } : {}),
|
|
702
919
|
...(args.applyOnMerge ? { applyOnMerge: true } : {}),
|
|
920
|
+
...(landPhases ? { land: { phases: landPhases } } : {}),
|
|
703
921
|
// #4168: the tick from the runs the jobs recorded, signed with the runs' key. No ci.green, no tick.
|
|
704
922
|
tick: async () => {
|
|
705
923
|
try {
|