@intentius/chant-lexicon-github 0.124.54 → 0.124.55
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/components/agent-jobs.d.ts +67 -0
- package/dist/components/agent-jobs.d.ts.map +1 -0
- package/dist/components/agent-jobs.js +139 -0
- package/dist/components/control-pipeline.d.ts +34 -0
- package/dist/components/control-pipeline.d.ts.map +1 -0
- package/dist/components/control-pipeline.js +45 -0
- package/dist/components/generate-op-pipeline.d.ts +8 -1
- package/dist/components/generate-op-pipeline.d.ts.map +1 -1
- package/dist/components/generate-op-pipeline.js +75 -12
- package/dist/components/generate-op-waves-pipeline.d.ts +13 -0
- package/dist/components/generate-op-waves-pipeline.d.ts.map +1 -1
- package/dist/components/generate-op-waves-pipeline.js +62 -4
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +133 -14
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/components/agent-jobs.ts +177 -0
- package/src/components/control-pipeline.ts +65 -0
- package/src/components/generate-op-pipeline.agent.test.ts +147 -0
- package/src/components/generate-op-pipeline.notice.test.ts +2 -1
- package/src/components/generate-op-pipeline.ts +82 -11
- package/src/components/generate-op-waves-pipeline.control.test.ts +64 -0
- package/src/components/generate-op-waves-pipeline.test.ts +17 -0
- package/src/components/generate-op-waves-pipeline.ts +81 -4
- package/src/components/generate-pipeline.ts +139 -14
- package/src/components/generate-pr-pipeline.comment-locks.test.ts +53 -0
- package/src/components/generate-pr-pipeline.comment-preview.test.ts +64 -0
- package/src/components/generate-pr-pipeline.control.test.ts +94 -0
- package/src/components/generate-pr-pipeline.pulumi-identity.test.ts +43 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAuCjG,OAAO,KAAK,EACV,oBAAoB,IAAI,YAAY,EACpC,wBAAwB,IAAI,qBAAqB,EACjD,uBAAuB,IAAI,oBAAoB,EAEhD,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;AAE1E;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,mIAAmI;IACnI,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,uEAAuE;IACvE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0DAA0D;IAC1D,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAsmBD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAWpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,iBAAiB,CA+JnB;AA4CD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAGtB"}
|
|
@@ -61,11 +61,16 @@ import { GATED_WAVE_RECORD, gatedWaveJobs } from "@intentius/chant/components/ga
|
|
|
61
61
|
import { CI_IMAGE, ciJobName, dirnameOf, fillCommand, GITHUB_JOB_TOKEN, GITHUB_RUNNER } from "@intentius/chant/ci-jobs";
|
|
62
62
|
import { CHECKOUT_TOKEN_ENV, checkoutScript, dropForgeTokens, handoverOutputs, handoverTrap, takeOverScript } from "@intentius/chant/ci-handover";
|
|
63
63
|
import { OIDC_REQUEST_VARS, TRADE_SPEC_ENV, tradeLine } from "@intentius/chant/ci-identity";
|
|
64
|
-
import {
|
|
64
|
+
import { prApplyComment, GREEN_TAG_PATTERN, GREEN_TICK_WORKFLOW, PR_APPLY_RECORD, PR_LOOP_REPORT_DIR, prApplyGroup, prRecordGroup, prApplyRecordKey, prApplyWaveJobName, prLoopJobs, prLoopPostJob, prLoopSetup, PR_PLAN_NOTE_JOB, PR_LOCK_JOB, PR_UNLOCK_JOB, PR_UNLOCK_COMMAND, prLockCommand, prCommentLockJobs, prCommentPreviewJob, PREVIEW_REF_ENV, PREVIEW_HEAD_OUTPUT, prLoopWaveJobs, prLoopWithWaves, prEnvJobs, PR_ENV_REPORT_DIR, PR_ENV_SWEEP_CRON, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
|
|
65
|
+
import { forgejoRepoLockLines, REPO_APPLY_GROUP } from "@intentius/chant/components/repo-lock";
|
|
65
66
|
import { memberRepoPath } from "@intentius/chant/lexicon";
|
|
67
|
+
import { productStatusContext, resolveProductWords } from "@intentius/chant/ci-product-words";
|
|
68
|
+
import { controlDispatchJob, controlNoteCheckout } from "@intentius/chant/control/dispatch";
|
|
66
69
|
import { afterEarlierWave, CACHE_RESTORE, CACHE_SAVE, CHECKOUT, checkoutStep, concurrency, downloadArtifactStep, emitWorkflowYAML, keepRecordStep, runSteps, UPLOAD_ARTIFACT } from "./ci-jobs.js";
|
|
67
70
|
/** The plan job's last step, whose outputs carry its report to the plan-note job (#4295). */
|
|
68
71
|
const PLAN_STEP_ID = "plan";
|
|
72
|
+
/** The comment preview's checkout step (#4620), whose output names the head it checked out. */
|
|
73
|
+
const PREVIEW_HEAD_STEP_ID = "head";
|
|
69
74
|
/** The output a gated apply wave hands the green commit it applies through, and the step that sets it (#4221). */
|
|
70
75
|
const WAVE_GREEN_OUTPUT = "green-sha";
|
|
71
76
|
const WAVE_STEP_ID = "apply";
|
|
@@ -203,10 +208,12 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
203
208
|
});
|
|
204
209
|
const withIdToken = (permissions) => (identity ? { ...permissions, "id-token": "write" } : permissions);
|
|
205
210
|
const reportName = (job) => `chant-pr-${job.wave ? prApplyWaveJobName(job.wave) : job.jobName}`;
|
|
211
|
+
// #4552: on the repo grain a Forgejo apply takes the lock tag in its own step, before the command, since the runner does not queue on a concurrency group.
|
|
212
|
+
const repoLock = (job) => (loop.lockGrain === "repo" && loop.forge === "forgejo" && job.jobName !== "record" ? forgejoRepoLockLines() : []);
|
|
206
213
|
const steps = (job, name) => [
|
|
207
214
|
...(identity ? [tradeStep(job.jobName === "record" ? identity.plan : identity.apply, job.jobName === "record" ? "plan" : "apply")] : []),
|
|
208
215
|
checkoutStep(true),
|
|
209
|
-
...runSteps(prLoopSetup(image)),
|
|
216
|
+
...runSteps(prLoopSetup(image, loop.engineSetup)),
|
|
210
217
|
...runSteps(beforeScript),
|
|
211
218
|
...(job.build ? [{ name: "Build the templates", run: job.build[0] }, { name: "Lint the project", run: job.build[1] }] : []),
|
|
212
219
|
...(job.jobName === "apply" ? [restoreRecord(job.wave)] : []),
|
|
@@ -218,9 +225,9 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
218
225
|
name,
|
|
219
226
|
id: WAVE_STEP_ID,
|
|
220
227
|
env: { ...job.run.env, ...(job.wave > 1 ? { GREEN_SHA: `\${{ needs.${prApplyWaveJobName(job.wave - 1)}.outputs.${WAVE_GREEN_OUTPUT} }}` } : {}) },
|
|
221
|
-
run: [...(job.checkout ?? []), `echo "${WAVE_GREEN_OUTPUT}=$GREEN_SHA" >> "$GITHUB_OUTPUT"`, ...(job.setup ?? []), job.run.command].join("\n"),
|
|
228
|
+
run: [...(job.checkout ?? []), `echo "${WAVE_GREEN_OUTPUT}=$GREEN_SHA" >> "$GITHUB_OUTPUT"`, ...(job.setup ?? []), ...repoLock(job), job.run.command].join("\n"),
|
|
222
229
|
}
|
|
223
|
-
: { name, env: job.run.env, run: [...(job.checkout ?? []), ...(job.setup ?? []), job.run.command].join("\n") },
|
|
230
|
+
: { name, env: job.run.env, run: [...(job.checkout ?? []), ...(job.setup ?? []), ...repoLock(job), job.run.command].join("\n") },
|
|
224
231
|
...runSteps(extraScript),
|
|
225
232
|
...(job.jobName === "apply" ? [saveRecord(job.wave)] : []),
|
|
226
233
|
{
|
|
@@ -239,7 +246,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
239
246
|
"github.event_name == 'issue_comment'",
|
|
240
247
|
// Forgejo's payload says so with is_pull (terragucci comment-apply.ts:129 at e6cb431).
|
|
241
248
|
forgejo ? "(github.event.issue.pull_request || github.event.is_pull)" : "github.event.issue.pull_request",
|
|
242
|
-
`startsWith(github.event.comment.body, '${
|
|
249
|
+
`startsWith(github.event.comment.body, '${prApplyComment(loop)}')`,
|
|
243
250
|
...(forgejo ? [] : ["contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)"]),
|
|
244
251
|
].join(" && ");
|
|
245
252
|
const applyJob = (job, needs, name) => ({
|
|
@@ -248,7 +255,8 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
248
255
|
"runs-on": GITHUB_RUNNER,
|
|
249
256
|
...(needs.length > 0 ? { needs } : {}),
|
|
250
257
|
container: image,
|
|
251
|
-
|
|
258
|
+
// #4552: on the repo grain every apply of the repository shares one group.
|
|
259
|
+
concurrency: concurrency(loop.lockGrain === "repo" ? REPO_APPLY_GROUP : prApplyGroup(env, member?.name)),
|
|
252
260
|
permissions: withIdToken({ contents: "write", "pull-requests": "write", statuses: "write" }),
|
|
253
261
|
...(job.wave ? { outputs: { [WAVE_GREEN_OUTPUT]: `\${{ steps.${WAVE_STEP_ID}.outputs.${WAVE_GREEN_OUTPUT} }}` } } : {}),
|
|
254
262
|
steps: steps(job, name),
|
|
@@ -281,7 +289,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
281
289
|
env: { [CHECKOUT_TOKEN_ENV]: GITHUB_JOB_TOKEN },
|
|
282
290
|
run: checkoutScript(true, true),
|
|
283
291
|
},
|
|
284
|
-
...[...prLoopSetup(image), ...beforeScript].map((line) => headStep(line)),
|
|
292
|
+
...[...prLoopSetup(image, loop.engineSetup), ...beforeScript].map((line) => headStep(line)),
|
|
285
293
|
...(plan.build ? [headStep(plan.build[0], "Build the templates"), headStep(plan.build[1], "Lint the project")] : []),
|
|
286
294
|
// The job's last step: a step after it would start beside whatever the change's code left running.
|
|
287
295
|
{
|
|
@@ -291,14 +299,40 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
291
299
|
run: [drop, ...handoverTrap(PR_LOOP_REPORT_DIR, forgeName), ...(plan.checkout ?? []), ...(plan.setup ?? []), plan.run.command, ...extraScript].join("\n"),
|
|
292
300
|
},
|
|
293
301
|
];
|
|
294
|
-
|
|
302
|
+
// #4568: once the last apply on merge succeeded, one job tells the control repo this member applied.
|
|
303
|
+
const lastGreen = loop.control && !loop.applyBeforeMerge && greenJobs.length > 0 ? greenJobs[greenJobs.length - 1][0] : undefined;
|
|
304
|
+
const controlJobs = [];
|
|
305
|
+
if (loop.control && lastGreen) {
|
|
306
|
+
// A wave job hands on the commit it applied; a single apply job applies the green commit its trigger names.
|
|
307
|
+
const sha = loop.waveGate ? `\${{ needs.${lastGreen}.outputs.${WAVE_GREEN_OUTPUT} }}` : forgejo ? "${{ github.sha }}" : "${{ github.event.workflow_run.head_sha || github.sha }}";
|
|
308
|
+
const dispatch = controlDispatchJob(forgeName, loop.control.target, sha, {
|
|
309
|
+
...(loop.words ? { words: loop.words } : {}),
|
|
310
|
+
...(loop.control.sentCheck ? { sentCheck: loop.control.sentCheck } : {}),
|
|
311
|
+
...(loop.control.markSent ? { markSent: loop.control.markSent } : {}),
|
|
312
|
+
});
|
|
313
|
+
controlJobs.push([
|
|
314
|
+
dispatch.name,
|
|
315
|
+
{
|
|
316
|
+
needs: [lastGreen],
|
|
317
|
+
if: `success() && (${onGreen})`,
|
|
318
|
+
"runs-on": GITHUB_RUNNER,
|
|
319
|
+
container: image,
|
|
320
|
+
permissions: loop.control.markSent ? { contents: "read", statuses: "write" } : { contents: "read" },
|
|
321
|
+
steps: [{ name: dispatch.stepName, ...(rooted ? {} : { "working-directory": "." }), env: { ...dispatch.env, ...(loop.control.sentCheck || loop.control.markSent ? { GITHUB_TOKEN: GITHUB_JOB_TOKEN } : {}) }, run: dispatch.script }],
|
|
322
|
+
},
|
|
323
|
+
]);
|
|
324
|
+
}
|
|
325
|
+
const post = prLoopPostJob(forgeName, env, member?.name, loop.words ? resolveProductWords(loop.words) : undefined);
|
|
326
|
+
// #4619: with a control repo named, the note job reads its chant/lifecycle, read-only, so pr-post marks the cross-stack edges.
|
|
327
|
+
const controlRead = loop.control ? controlNoteCheckout(forgeName, loop.control.target, loop.words ?? {}) : undefined;
|
|
295
328
|
const noteSteps = [
|
|
296
329
|
// The target branch's commit: the chant this job runs is the target branch's, and none of the change's code is here.
|
|
297
330
|
{ name: "Check out the target branch's commit", uses: CHECKOUT, with: { ref: "${{ github.event.pull_request.base.sha }}" } },
|
|
298
331
|
...runSteps(beforeScript),
|
|
299
332
|
{ name: "Take the plan report from the plan job", run: takeOverScript("plan", PR_LOOP_REPORT_DIR, forgeName) },
|
|
300
333
|
{ name: "Keep the plan report", if: "always()", uses: UPLOAD_ARTIFACT, with: { name: reportName(plan), path: reportPath, "if-no-files-found": "ignore", "include-hidden-files": true } },
|
|
301
|
-
{ name:
|
|
334
|
+
...(controlRead ? [{ name: controlRead.stepName, if: "always()", env: controlRead.env, run: controlRead.script }] : []),
|
|
335
|
+
{ name: `Post the plan note and ${productStatusContext(resolveProductWords(loop.words), "plan")}`, if: "always()", env: post.env, run: post.command },
|
|
302
336
|
];
|
|
303
337
|
// #4298: with a pull request environment the pull_request trigger fires on close too, and the plan skips that event.
|
|
304
338
|
const prEnv = loop.prEnvironment ? prEnvGithubJobs(loop.forge ?? "github", image, beforeScript, extraScript, loop, member) : undefined;
|
|
@@ -339,6 +373,80 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
339
373
|
},
|
|
340
374
|
}
|
|
341
375
|
: {};
|
|
376
|
+
// #4588: `/<product> lock` and `/<product> unlock` comments, at the target branch's commit, so none of the change's code runs.
|
|
377
|
+
const commentLocks = prCommentLockJobs(forgeName, loop);
|
|
378
|
+
const commentLockJobs = Object.fromEntries(commentLocks.map((job) => [
|
|
379
|
+
job.jobName,
|
|
380
|
+
{
|
|
381
|
+
if: [
|
|
382
|
+
"github.event_name == 'issue_comment'",
|
|
383
|
+
forgejo ? "(github.event.issue.pull_request || github.event.is_pull)" : "github.event.issue.pull_request",
|
|
384
|
+
`startsWith(github.event.comment.body, '${job.comment}')`,
|
|
385
|
+
...(forgejo ? [] : ["contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)"]),
|
|
386
|
+
].join(" && "),
|
|
387
|
+
"runs-on": GITHUB_RUNNER,
|
|
388
|
+
container: image,
|
|
389
|
+
permissions: { contents: "write", "pull-requests": "read", statuses: "write" },
|
|
390
|
+
steps: [
|
|
391
|
+
{ name: "Check out the target branch's commit", uses: CHECKOUT, with: { "fetch-depth": 0 } },
|
|
392
|
+
...runSteps(beforeScript),
|
|
393
|
+
{ name: job.command === "lock" ? "Keep the locks the pull request's plan took" : "Release the pull request's locks", env: job.env, run: job.line },
|
|
394
|
+
],
|
|
395
|
+
},
|
|
396
|
+
]));
|
|
397
|
+
// #4620: `/<product> <plan word>` plans the pull request's head again with a read token; the job after it posts the note.
|
|
398
|
+
const preview = prCommentPreviewJob(forgeName, env, loop, member?.name);
|
|
399
|
+
const previewJobs = preview
|
|
400
|
+
? {
|
|
401
|
+
[preview.jobName]: {
|
|
402
|
+
if: [
|
|
403
|
+
"github.event_name == 'issue_comment'",
|
|
404
|
+
forgejo ? "(github.event.issue.pull_request || github.event.is_pull)" : "github.event.issue.pull_request",
|
|
405
|
+
`startsWith(github.event.comment.body, '${preview.comment}')`,
|
|
406
|
+
...(forgejo ? [] : ["contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)"]),
|
|
407
|
+
].join(" && "),
|
|
408
|
+
"runs-on": GITHUB_RUNNER,
|
|
409
|
+
container: image,
|
|
410
|
+
permissions: withIdToken({ contents: "read" }),
|
|
411
|
+
outputs: { ...handoverOutputs(PLAN_STEP_ID, forgeName), [PREVIEW_HEAD_OUTPUT]: `\${{ steps.${PREVIEW_HEAD_STEP_ID}.outputs.${PREVIEW_HEAD_OUTPUT} }}` },
|
|
412
|
+
steps: [
|
|
413
|
+
...(identity ? [tradeStep(identity.plan, "plan")] : []),
|
|
414
|
+
// The head is read here, before any of the change's code runs, so the post job names the commit this job planned.
|
|
415
|
+
{
|
|
416
|
+
name: "Check out the pull request's head",
|
|
417
|
+
id: PREVIEW_HEAD_STEP_ID,
|
|
418
|
+
...(rooted ? {} : { "working-directory": "." }),
|
|
419
|
+
env: { [CHECKOUT_TOKEN_ENV]: GITHUB_JOB_TOKEN, [PREVIEW_REF_ENV]: "ref" in preview.head ? preview.head.ref : "" },
|
|
420
|
+
run: [checkoutScript(true, true, { refEnv: PREVIEW_REF_ENV }), `echo "${PREVIEW_HEAD_OUTPUT}=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"`].join("\n"),
|
|
421
|
+
},
|
|
422
|
+
...[...prLoopSetup(image, loop.engineSetup), ...beforeScript].map((line) => headStep(line)),
|
|
423
|
+
...(preview.plan.build ? [headStep(preview.plan.build[0], "Build the templates"), headStep(preview.plan.build[1], "Lint the project")] : []),
|
|
424
|
+
{
|
|
425
|
+
name: "Preview the members this pull request reaches",
|
|
426
|
+
id: PLAN_STEP_ID,
|
|
427
|
+
env: preview.plan.run.env,
|
|
428
|
+
run: [drop, ...handoverTrap(PR_LOOP_REPORT_DIR, forgeName), preview.base, ...(preview.plan.setup ?? []), preview.plan.run.command, ...extraScript].join("\n"),
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
[preview.noteJobName]: {
|
|
433
|
+
needs: [preview.jobName],
|
|
434
|
+
if: `always() && github.event_name == 'issue_comment' && (needs.${preview.jobName}.result == 'success' || needs.${preview.jobName}.result == 'failure')`,
|
|
435
|
+
"runs-on": GITHUB_RUNNER,
|
|
436
|
+
container: image,
|
|
437
|
+
permissions: { actions: "read", contents: "read", "pull-requests": "write", statuses: "write" },
|
|
438
|
+
steps: [
|
|
439
|
+
// The target branch's commit, which an issue_comment run checks out: none of the change's code is here.
|
|
440
|
+
{ name: "Check out the target branch's commit", uses: CHECKOUT },
|
|
441
|
+
...runSteps(beforeScript),
|
|
442
|
+
{ name: "Take the preview report from the preview job", run: takeOverScript(preview.jobName, PR_LOOP_REPORT_DIR, forgeName) },
|
|
443
|
+
{ name: "Keep the preview report", if: "always()", uses: UPLOAD_ARTIFACT, with: { name: `chant-pr-${preview.jobName}`, path: reportPath, "if-no-files-found": "ignore", "include-hidden-files": true } },
|
|
444
|
+
...(controlRead ? [{ name: controlRead.stepName, if: "always()", env: controlRead.env, run: controlRead.script }] : []),
|
|
445
|
+
{ name: `Post the preview note and ${productStatusContext(resolveProductWords(loop.words), "plan")}`, if: "always()", env: preview.post.env, run: preview.post.command },
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
}
|
|
449
|
+
: {};
|
|
342
450
|
const jobsDoc = {
|
|
343
451
|
plan: {
|
|
344
452
|
if: closes ? "github.event_name == 'pull_request' && github.event.action != 'closed'" : "github.event_name == 'pull_request'",
|
|
@@ -358,7 +466,10 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
358
466
|
steps: noteSteps,
|
|
359
467
|
},
|
|
360
468
|
...lockJobs,
|
|
469
|
+
...commentLockJobs,
|
|
470
|
+
...previewJobs,
|
|
361
471
|
...Object.fromEntries(applyJobs),
|
|
472
|
+
...Object.fromEntries(controlJobs),
|
|
362
473
|
...(prEnv ? prEnv.jobs : {}),
|
|
363
474
|
};
|
|
364
475
|
const applyNames = applyJobs.map(([name]) => name);
|
|
@@ -373,8 +484,8 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
373
484
|
on.pull_request = { branches: [branch], types: ["opened", "synchronize", "reopened", "closed"] };
|
|
374
485
|
on.schedule = [{ cron: PR_ENV_SWEEP_CRON }];
|
|
375
486
|
}
|
|
376
|
-
// #4301: a /chant apply comment starts the apply before merge.
|
|
377
|
-
if (loop.applyBeforeMerge)
|
|
487
|
+
// #4301: a /chant apply comment starts the apply before merge. #4588: and a lock or unlock comment its job.
|
|
488
|
+
if (loop.applyBeforeMerge || commentLocks.length > 0 || preview)
|
|
378
489
|
on.issue_comment = { types: ["created"] };
|
|
379
490
|
return {
|
|
380
491
|
name: member ? `chant-pr.${member.name}.${env}` : `chant-pr-${env}`,
|
|
@@ -383,7 +494,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
383
494
|
...(rooted ? {} : { defaults: { run: { "working-directory": member.dir } } }),
|
|
384
495
|
env: { ...options.variables, CHANT_ENV: env },
|
|
385
496
|
jobsDoc,
|
|
386
|
-
stages: ["plan", PR_PLAN_NOTE_JOB, ...(locks ? [PR_LOCK_JOB, PR_UNLOCK_JOB] : []), ...applyNames, ...(prEnv ? ["env-up", PR_ENV_POST_JOB, "env-down", "env-sweep"] : [])],
|
|
497
|
+
stages: ["plan", PR_PLAN_NOTE_JOB, ...(locks ? [PR_LOCK_JOB, PR_UNLOCK_JOB] : []), ...commentLocks.map((j) => j.jobName), ...(preview ? [preview.jobName, preview.noteJobName] : []), ...applyNames, ...controlJobs.map(([name]) => name), ...(prEnv ? ["env-up", PR_ENV_POST_JOB, "env-down", "env-sweep"] : [])],
|
|
387
498
|
jobs: [
|
|
388
499
|
{ jobName: "plan", component: "pull request plan", stage: "plan", needs: [] },
|
|
389
500
|
{ jobName: PR_PLAN_NOTE_JOB, component: "pull request plan note", stage: PR_PLAN_NOTE_JOB, needs: ["plan"] },
|
|
@@ -393,6 +504,13 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
393
504
|
{ jobName: PR_UNLOCK_JOB, component: "pull request unlock", stage: PR_UNLOCK_JOB, needs: [] },
|
|
394
505
|
]
|
|
395
506
|
: []),
|
|
507
|
+
...commentLocks.map((j) => ({ jobName: j.jobName, component: `pull request ${j.command} comment`, stage: j.jobName, needs: [] })),
|
|
508
|
+
...(preview
|
|
509
|
+
? [
|
|
510
|
+
{ jobName: preview.jobName, component: "pull request preview comment", stage: preview.jobName, needs: [] },
|
|
511
|
+
{ jobName: preview.noteJobName, component: "pull request preview comment note", stage: preview.noteJobName, needs: [preview.jobName] },
|
|
512
|
+
]
|
|
513
|
+
: []),
|
|
396
514
|
...applyNames.map((name, i) => ({
|
|
397
515
|
jobName: name,
|
|
398
516
|
component: name === "record" ? "merge record" : loop.applyBeforeMerge ? "apply before merge" : loop.waveGate ? `green apply, wave ${i}` : "green apply",
|
|
@@ -400,6 +518,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
400
518
|
// The record runs on a push and the apply on a green commit, so only the waves need each other.
|
|
401
519
|
needs: i > 1 ? [applyNames[i - 1]] : [],
|
|
402
520
|
})),
|
|
521
|
+
...controlJobs.map(([name]) => ({ jobName: name, component: "control repo dispatch", stage: name, needs: [lastGreen] })),
|
|
403
522
|
...(prEnv ? prEnv.listed : []),
|
|
404
523
|
],
|
|
405
524
|
};
|
|
@@ -451,7 +570,7 @@ function prEnvGithubJobs(forge, image, beforeScript, extraScript, loop, member)
|
|
|
451
570
|
env: { [CHECKOUT_TOKEN_ENV]: GITHUB_JOB_TOKEN },
|
|
452
571
|
run: checkoutScript(true, true),
|
|
453
572
|
},
|
|
454
|
-
...[...prLoopSetup(image), ...beforeScript].map((line) => headStep(line)),
|
|
573
|
+
...[...prLoopSetup(image, loop.engineSetup), ...beforeScript].map((line) => headStep(line)),
|
|
455
574
|
...(up.build ? [headStep(up.build[0], "Build the templates", { CHANT_ENV: up.env.CHANT_ENV }), headStep(up.build[1], "Lint the project")] : []),
|
|
456
575
|
// The job's last step: a step after it would start beside whatever the change's code left running.
|
|
457
576
|
{
|
|
@@ -487,7 +606,7 @@ function prEnvGithubJobs(forge, image, beforeScript, extraScript, loop, member)
|
|
|
487
606
|
steps: [
|
|
488
607
|
...tradeStep,
|
|
489
608
|
{ uses: CHECKOUT, with: { "fetch-depth": 0, ...(ref ? { ref } : {}) } },
|
|
490
|
-
...runSteps(prLoopSetup(image)),
|
|
609
|
+
...runSteps(prLoopSetup(image, loop.engineSetup)),
|
|
491
610
|
...runSteps(beforeScript),
|
|
492
611
|
{ name, env: j.env, run: j.command },
|
|
493
612
|
...runSteps(extraScript),
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "3513bb8a86e9d8cf394877a70b42741598681301a7c892fa94a65856cd1742ac",
|
|
5
5
|
"meta.json": "c50e0ef3845c370c3ad2210b8bba455980417c0f818aa6ed46e06d4fb8b1b9f9",
|
|
6
6
|
"types/index.d.ts": "0ead95536e947ddfb98a5acf0ddb88547913b5a53a342df67711c59266f84b3e",
|
|
7
7
|
"rules/deprecated-action-version.ts": "a9ba7cc924ff1c7055110416d8989273690415bfb5e8dcc32dc9431b9351f5ca",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"skills/chant-github-patterns.md": "bb3abef289a8fdfcf07d6bb2d7289dcb2f38bc0cb0321ea320b78b45a6f548c0",
|
|
79
79
|
"skills/chant-github-security.md": "aab111cb0871cad30281ce48d7da23663689619351029219e2be019a1a61e394"
|
|
80
80
|
},
|
|
81
|
-
"composite": "
|
|
81
|
+
"composite": "d2ef289d1dabdd39c05090b1dc9e881fa69fd0934c0cb3a197d8ca9abb4f5a45"
|
|
82
82
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-github",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.55",
|
|
4
4
|
"description": "GitHub Actions lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
"typescript": "^5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@intentius/chant": "^0.124.
|
|
64
|
+
"@intentius/chant": "^0.124.55"
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent jobs on GitHub and Forgejo (#4616): `ScheduledOpSpec.agent`.
|
|
3
|
+
* What each job does is @intentius/chant/ci-agent's; this file lays them
|
|
4
|
+
* out so the model never shares a job with a write token:
|
|
5
|
+
*
|
|
6
|
+
* - the agent job holds `contents: read` and `pull-requests: read`. Its
|
|
7
|
+
* checkout script alone sees the job token, for the fetches (the target
|
|
8
|
+
* branch's commit, chant/lifecycle for the run count, and for a comment
|
|
9
|
+
* the pull request's head). `chant agent prepare` reads with the job
|
|
10
|
+
* token; `chant agent run` is the last step and starts without any forge
|
|
11
|
+
* token, with the model's key from its secret, and hands the agent
|
|
12
|
+
* directory on as the job's outputs (`handoverTrap`).
|
|
13
|
+
* - the push job checks out the target branch's commit, takes the agent
|
|
14
|
+
* directory back as data and runs `chant agent land` with the push token:
|
|
15
|
+
* `tokenEnv`'s secret, else the job token. A push made with the job token
|
|
16
|
+
* starts no workflow on GitHub, so the pull request is planned again only
|
|
17
|
+
* with a token of a user or an app.
|
|
18
|
+
*
|
|
19
|
+
* Forgejo's runner ignores `permissions:` and has no author association, so
|
|
20
|
+
* there `chant agent prepare` asks the forge whether the commenter can write,
|
|
21
|
+
* and both jobs drop the token variables before any step that does not need
|
|
22
|
+
* them.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { CHECKOUT_TOKEN_ENV, checkoutScript, dropForgeTokens, handoverOutputs, handoverTrap, takeOverScript } from "@intentius/chant/ci-handover";
|
|
26
|
+
import { githubRunsOn, GITHUB_JOB_TOKEN, githubSecret, githubSecretOrJobToken, type CiRunner } from "@intentius/chant/ci-jobs";
|
|
27
|
+
import {
|
|
28
|
+
AGENT_DECISION_FILE,
|
|
29
|
+
AGENT_DIR,
|
|
30
|
+
AGENT_DRIFT_FILE,
|
|
31
|
+
AGENT_PATCH_FILE,
|
|
32
|
+
AGENT_RC_FILE,
|
|
33
|
+
agentCommandLines,
|
|
34
|
+
agentGitAuthLines,
|
|
35
|
+
agentHeadFetchLine,
|
|
36
|
+
agentSettingsEnv,
|
|
37
|
+
type AgentSettings,
|
|
38
|
+
} from "@intentius/chant/ci-agent";
|
|
39
|
+
import { CHANT_EXIT } from "@intentius/chant/exit-codes";
|
|
40
|
+
|
|
41
|
+
/** The agent step's id, which the agent job's outputs come from. */
|
|
42
|
+
export const AGENT_STEP_ID = "chant-agent";
|
|
43
|
+
/** The drift Op's run step id, whose outputs carry the drift and the code. */
|
|
44
|
+
export const AGENT_DRIFT_STEP_ID = "chant-run";
|
|
45
|
+
/** The variable the push step holds its token in, for git's header and the forge client. */
|
|
46
|
+
const PUSH_TOKEN_ENV = "CHANT_AGENT_TOKEN";
|
|
47
|
+
|
|
48
|
+
/** The files the handover keeps when the agent directory is too large to hand on whole. */
|
|
49
|
+
const HANDED = { label: "chant-agent", noteFiles: [AGENT_DECISION_FILE, AGENT_RC_FILE, AGENT_PATCH_FILE, "prompt.sha256"] } as const;
|
|
50
|
+
const DRIFT_HANDED = { label: "chant-agent", noteFiles: [AGENT_DRIFT_FILE] } as const;
|
|
51
|
+
|
|
52
|
+
/** The name of the push job beside the agent job. */
|
|
53
|
+
export function agentPushJobName(jobName: string): string {
|
|
54
|
+
return `${jobName}-push`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The agent job's name for a drift Op's job. */
|
|
58
|
+
export function agentJobName(jobName: string, on: AgentSettings["on"]): string {
|
|
59
|
+
return on === "drift" ? `${jobName}-agent` : jobName;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface GithubAgentJobsInput {
|
|
63
|
+
forge: "github" | "forgejo";
|
|
64
|
+
/** The agent job's name. */
|
|
65
|
+
jobName: string;
|
|
66
|
+
image: string;
|
|
67
|
+
settings: AgentSettings;
|
|
68
|
+
runner?: CiRunner;
|
|
69
|
+
/** Setup steps, as built by `setupStepDoc`. */
|
|
70
|
+
setup: Array<Record<string, unknown>>;
|
|
71
|
+
beforeScript: readonly string[];
|
|
72
|
+
/** The secret holding the push token; absent: the job token. */
|
|
73
|
+
tokenEnv?: string;
|
|
74
|
+
/** The command in place of `chant`: the product's bin. */
|
|
75
|
+
bin?: string;
|
|
76
|
+
/** For a drift agent: the Op's job, which handed the drift on. */
|
|
77
|
+
after?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The comment condition of the agent job: a pull request comment that starts with the agent's line, from a writer on GitHub. */
|
|
81
|
+
export function agentCommentIf(forge: "github" | "forgejo", comment: string): string {
|
|
82
|
+
return [
|
|
83
|
+
"github.event_name == 'issue_comment'",
|
|
84
|
+
forge === "forgejo" ? "(github.event.issue.pull_request || github.event.is_pull)" : "github.event.issue.pull_request",
|
|
85
|
+
`startsWith(github.event.comment.body, '${comment.replace(/'/g, "''")} ')`,
|
|
86
|
+
...(forge === "forgejo" ? [] : ["contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)"]),
|
|
87
|
+
].join(" && ");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The drift Op's run step (#4616): the run with its output kept as the
|
|
92
|
+
* drift for the agent, `drift=true` among the step's outputs when it exits
|
|
93
|
+
* with chant's `drift` code, and the output handed on. The step still exits
|
|
94
|
+
* with the run's code.
|
|
95
|
+
*/
|
|
96
|
+
export function driftRunStep(invocation: string, env: Record<string, string>, forge: "github" | "forgejo"): Record<string, unknown> {
|
|
97
|
+
return {
|
|
98
|
+
id: AGENT_DRIFT_STEP_ID,
|
|
99
|
+
env,
|
|
100
|
+
run: [
|
|
101
|
+
"set +e",
|
|
102
|
+
`mkdir -p ${AGENT_DIR}`,
|
|
103
|
+
...handoverTrap(AGENT_DIR, forge, DRIFT_HANDED),
|
|
104
|
+
`{ ${invocation}; echo "$?" >"\${RUNNER_TEMP:-/tmp}/chant-run.code"; } 2>&1 | tee ${AGENT_DIR}/${AGENT_DRIFT_FILE}`,
|
|
105
|
+
'code=$(cat "${RUNNER_TEMP:-/tmp}/chant-run.code")',
|
|
106
|
+
`if [ "$code" -eq ${CHANT_EXIT.drift} ]; then echo "drift=true" >>"$GITHUB_OUTPUT"; fi`,
|
|
107
|
+
'exit "$code"',
|
|
108
|
+
].join("\n"),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The drift Op job's outputs: the handed-on drift and the drift flag. */
|
|
113
|
+
export function driftRunOutputs(forge: "github" | "forgejo"): Record<string, string> {
|
|
114
|
+
return { ...handoverOutputs(AGENT_DRIFT_STEP_ID, forge), drift: `\${{ steps.${AGENT_DRIFT_STEP_ID}.outputs.drift }}` };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The agent job and the push job. */
|
|
118
|
+
export function githubAgentJobs(i: GithubAgentJobsInput): { agent: Record<string, unknown>; push: Record<string, unknown> } {
|
|
119
|
+
const s = i.settings;
|
|
120
|
+
const lines = agentCommandLines(i.forge, i.bin);
|
|
121
|
+
const drop = dropForgeTokens("sh");
|
|
122
|
+
const comment = s.on === "comment";
|
|
123
|
+
const settingsEnv = agentSettingsEnv(s);
|
|
124
|
+
const checkout = (head: boolean): Record<string, unknown> => ({
|
|
125
|
+
name: head ? "Check out the target branch's commit and the pull request's head" : "Check out the commit the drift check ran on",
|
|
126
|
+
env: { [CHECKOUT_TOKEN_ENV]: GITHUB_JOB_TOKEN, ...(head ? { PR_NUMBER: "${{ github.event.issue.number }}" } : {}) },
|
|
127
|
+
// The ledger branch too: prepare counts the agent's runs on it, and land appends to it.
|
|
128
|
+
run: [checkoutScript(true, true), ...(head ? [agentHeadFetchLine(i.forge, "PR_NUMBER")] : [])].join("\n"),
|
|
129
|
+
});
|
|
130
|
+
const before = i.beforeScript.map((line) => ({ run: `${drop}\n${line}` }));
|
|
131
|
+
const timeout = s.timeout + 15;
|
|
132
|
+
const agent: Record<string, unknown> = {
|
|
133
|
+
...(comment ? { if: agentCommentIf(i.forge, s.comment!) } : { needs: i.after, if: `always() && needs.${i.after}.outputs.drift == 'true'` }),
|
|
134
|
+
"runs-on": githubRunsOn(i.runner),
|
|
135
|
+
container: i.image,
|
|
136
|
+
"timeout-minutes": timeout,
|
|
137
|
+
permissions: { contents: "read", "pull-requests": "read" },
|
|
138
|
+
env: settingsEnv,
|
|
139
|
+
outputs: handoverOutputs(AGENT_STEP_ID, i.forge),
|
|
140
|
+
steps: [
|
|
141
|
+
checkout(comment),
|
|
142
|
+
...(comment ? [] : [{ name: "Take the drift from the drift check", run: takeOverScript(i.after!, AGENT_DIR, i.forge) }]),
|
|
143
|
+
...i.setup,
|
|
144
|
+
...before,
|
|
145
|
+
// Reads only: the comment, the commenter's access, the pull request's head and the ledger.
|
|
146
|
+
{ name: "Decide whether the agent runs", env: { GITHUB_TOKEN: GITHUB_JOB_TOKEN }, run: lines.prepare },
|
|
147
|
+
// The job's last step: the model's key and no forge token, and the agent directory handed on however it ends.
|
|
148
|
+
{
|
|
149
|
+
id: AGENT_STEP_ID,
|
|
150
|
+
name: "Run the agent",
|
|
151
|
+
env: { [s.keyEnv]: githubSecret(s.keyEnv) },
|
|
152
|
+
run: [drop, ...handoverTrap(AGENT_DIR, i.forge, HANDED), lines.run].join("\n"),
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
const token = i.tokenEnv ? githubSecretOrJobToken(i.tokenEnv) : GITHUB_JOB_TOKEN;
|
|
157
|
+
const push: Record<string, unknown> = {
|
|
158
|
+
needs: i.jobName,
|
|
159
|
+
if: `always() && needs.${i.jobName}.result == 'success'`,
|
|
160
|
+
"runs-on": githubRunsOn(i.runner),
|
|
161
|
+
container: i.image,
|
|
162
|
+
permissions: { contents: "write", "pull-requests": "write", issues: "write" },
|
|
163
|
+
env: settingsEnv,
|
|
164
|
+
steps: [
|
|
165
|
+
checkout(comment),
|
|
166
|
+
{ name: "Take the change from the agent job", run: takeOverScript(i.jobName, AGENT_DIR, i.forge) },
|
|
167
|
+
...i.setup,
|
|
168
|
+
...before,
|
|
169
|
+
{
|
|
170
|
+
name: comment ? "Push the change to the pull request" : "Open or update the agent's pull request",
|
|
171
|
+
env: { [PUSH_TOKEN_ENV]: token, GITHUB_TOKEN: token, ...(i.forge === "forgejo" ? { CHANT_FORGEJO_TOKEN: token } : {}) },
|
|
172
|
+
run: [...agentGitAuthLines(PUSH_TOKEN_ENV, "x-access-token"), lines.land].join("\n"),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
return { agent, push };
|
|
177
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control repo's own workflow (#4568) on GitHub, and on Forgejo through
|
|
3
|
+
* its dialect: a member's dispatch starts it, and it runs the member's
|
|
4
|
+
* dependents with `chant workspace run --dependents-of`. The pieces are
|
|
5
|
+
* core's (`controlPipelineSpec` in `@intentius/chant/control/dispatch`);
|
|
6
|
+
* this file writes them as a workflow. The member's side, the
|
|
7
|
+
* `control-dispatch` job, comes from the pull request loop's `control`
|
|
8
|
+
* option (./generate-pipeline.ts).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { CI_IMAGE, GITHUB_RUNNER } from "@intentius/chant/ci-jobs";
|
|
12
|
+
import { controlPipelineSpec } from "@intentius/chant/control/dispatch";
|
|
13
|
+
import type { CiProductWords, CiProductWordsInput } from "@intentius/chant/ci-product-words";
|
|
14
|
+
import { CHECKOUT, concurrency, emitWorkflowYAML, type GithubWorkflowDoc } from "./ci-jobs";
|
|
15
|
+
|
|
16
|
+
/** A control repo's pipeline file: where it goes, its text, and what a person still sets up. */
|
|
17
|
+
export interface ControlPipelineFile {
|
|
18
|
+
path: string;
|
|
19
|
+
content: string;
|
|
20
|
+
notes: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What {@link controlGithubDoc} and the generators take. */
|
|
24
|
+
export interface ControlPipelineOptions {
|
|
25
|
+
words?: CiProductWordsInput | CiProductWords;
|
|
26
|
+
/** The image the job runs in. Default chant's CI image. */
|
|
27
|
+
image?: string;
|
|
28
|
+
/** The command the job runs chant with. Default `npx --no-install chant`. */
|
|
29
|
+
command?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The control repo's workflow document for a GitHub-shaped forge, before emission. Forgejo transforms it first. */
|
|
33
|
+
export function controlGithubDoc(forge: "github" | "forgejo", options: ControlPipelineOptions = {}): { doc: GithubWorkflowDoc; path: string; notes: string[] } {
|
|
34
|
+
const spec = controlPipelineSpec(forge, options.words ?? {}, options.command ? { command: options.command } : {});
|
|
35
|
+
const doc: GithubWorkflowDoc = {
|
|
36
|
+
header: spec.header,
|
|
37
|
+
name: spec.path.split("/").pop()!.replace(/\.yml$/, ""),
|
|
38
|
+
on: {
|
|
39
|
+
...(forge === "github" ? { repository_dispatch: { types: [spec.event] } } : {}),
|
|
40
|
+
workflow_dispatch: { inputs: spec.inputs },
|
|
41
|
+
},
|
|
42
|
+
// #4617: contents: write pushes the record of each dispatch that carries an outcome to chant/lifecycle.
|
|
43
|
+
permissions: { contents: "write" },
|
|
44
|
+
// One run per member at a time, and none cancelled: a later apply of the same member waits for the one running.
|
|
45
|
+
concurrency: concurrency(spec.group),
|
|
46
|
+
jobsDoc: {
|
|
47
|
+
dependents: {
|
|
48
|
+
"runs-on": GITHUB_RUNNER,
|
|
49
|
+
container: options.image ?? CI_IMAGE,
|
|
50
|
+
steps: [
|
|
51
|
+
// The full history, so the job reads and appends to chant/lifecycle (#4617).
|
|
52
|
+
{ uses: CHECKOUT, with: { "fetch-depth": 0 } },
|
|
53
|
+
{ name: "Run the dependents of the member that applied", shell: "bash", env: spec.env, run: spec.script },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
return { doc, path: spec.path, notes: spec.notes };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The control repo's workflow on GitHub: `.github/workflows/<product>-dependents.yml`. */
|
|
62
|
+
export function generateControlPipeline(options: ControlPipelineOptions = {}): ControlPipelineFile {
|
|
63
|
+
const { doc, path, notes } = controlGithubDoc("github", options);
|
|
64
|
+
return { path, content: emitWorkflowYAML(doc), notes };
|
|
65
|
+
}
|