@intentius/chant-lexicon-github 0.124.55 → 0.124.56
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/generate-op-pipeline.d.ts.map +1 -1
- package/dist/components/generate-op-pipeline.js +71 -6
- package/dist/components/generate-op-waves-pipeline.d.ts +14 -1
- package/dist/components/generate-op-waves-pipeline.d.ts.map +1 -1
- package/dist/components/generate-op-waves-pipeline.js +74 -13
- package/dist/components/generate-pipeline.d.ts.map +1 -1
- package/dist/components/generate-pipeline.js +30 -4
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/components/__golden__/op-waves-jobs.yml +20 -0
- package/src/components/generate-op-pipeline.cloud.test.ts +96 -0
- package/src/components/generate-op-pipeline.ts +85 -11
- package/src/components/generate-op-waves-pipeline.approval.test.ts +46 -0
- package/src/components/generate-op-waves-pipeline.guard.test.ts +57 -0
- package/src/components/generate-op-waves-pipeline.identity.test.ts +8 -8
- package/src/components/generate-op-waves-pipeline.ts +76 -11
- package/src/components/generate-pipeline.ts +33 -3
- package/src/components/generate-pr-pipeline.comment-targets.test.ts +24 -0
- package/src/components/generate-pr-pipeline.respond.test.ts +47 -0
- package/src/components/generate-pr-pipeline.runners.test.ts +61 -0
- package/src/components/generate-pr-pipeline.stale-notes.test.ts +42 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AAGH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAOhB,gBAAgB,EAIjB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,wBAAwB,IAAI,uBAAuB,EAEnD,aAAa,EACb,gBAAgB,IAAI,sBAAsB,EAG1C,eAAe,EAChB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-op-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AAGH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAOhB,gBAAgB,EAIjB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,wBAAwB,IAAI,uBAAuB,EAEnD,aAAa,EACb,gBAAgB,IAAI,sBAAsB,EAG1C,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAUlC,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,CAAC;AAIhE,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,qFAAqF;AACrF,MAAM,WAAW,oBAAoB;IACnC,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,mBAAmB,CAAC;CAC1B;AAuOD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,EACrC,KAAK,GAAE,QAAQ,GAAG,SAAoB,GACrC;IAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAAC,IAAI,EAAE,aAAa,EAAE,CAAA;CAAE,CAuM1D;AAwCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAMnE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAMxB"}
|
|
@@ -77,6 +77,9 @@ import { assertReviewSplit, reviewJobs, reviewNoteJobName } from "./review-jobs.
|
|
|
77
77
|
import { resolveAgentSpec } from "@intentius/chant/ci-agent";
|
|
78
78
|
import { activeProductWords } from "@intentius/chant/ci-product-words";
|
|
79
79
|
import { agentJobName, agentPushJobName, driftRunOutputs, driftRunStep, githubAgentJobs } from "./agent-jobs.js";
|
|
80
|
+
import { assertScheduledOpCloud, scheduledOpUpdateJobName, withoutTokenRequestLine } from "@intentius/chant/scheduled-op-cloud";
|
|
81
|
+
import { TRADE_SPEC_ENV, tradeLine } from "@intentius/chant/ci-identity";
|
|
82
|
+
import { CHANT_EXIT } from "@intentius/chant/exit-codes";
|
|
80
83
|
// The setup, environment and permission checks are shared with the Op waves
|
|
81
84
|
// generator and the forgejo lexicon (#3765); they stay importable from here.
|
|
82
85
|
export { assertEnvironment, assertSetupSteps, mergePermissions };
|
|
@@ -313,6 +316,8 @@ export function buildGithubOpPipelineDocs(ops, options = {}, forge = "github") {
|
|
|
313
316
|
const jobs = [];
|
|
314
317
|
const words = activeProductWords();
|
|
315
318
|
for (const spec of ops) {
|
|
319
|
+
// #4631: the job's cloud identities and the update on drift, checked before anything renders.
|
|
320
|
+
assertScheduledOpCloud(spec);
|
|
316
321
|
// #4616: the comment agent is a workflow of its own, on pull request comments; the spec's trigger is not read.
|
|
317
322
|
if (spec.agent?.on === "comment") {
|
|
318
323
|
const settings = resolveAgentSpec(spec.name, spec.agent, words.product);
|
|
@@ -364,6 +369,8 @@ export function buildGithubOpPipelineDocs(ops, options = {}, forge = "github") {
|
|
|
364
369
|
throw new Error(`Scheduled Op "${spec.name}" sets a drift agent, which follows a drift check's run; give it a cron or pull_request trigger and no reviewAgent.`);
|
|
365
370
|
}
|
|
366
371
|
const stepEnv = split ? {} : jobTokenEnv(findingMode !== "report");
|
|
372
|
+
const cloud = spec.cloud;
|
|
373
|
+
const update = spec.update;
|
|
367
374
|
const setup = spec.setup ?? [];
|
|
368
375
|
assertSetupSteps(spec.name, setup);
|
|
369
376
|
if (spec.environment)
|
|
@@ -376,13 +383,17 @@ export function buildGithubOpPipelineDocs(ops, options = {}, forge = "github") {
|
|
|
376
383
|
const invocation = gated
|
|
377
384
|
? [...runParts, ...GATED_EXIT_FLAG, "--json"].join(" ")
|
|
378
385
|
: runParts.join(" ");
|
|
379
|
-
const
|
|
380
|
-
|
|
386
|
+
const later = [...setup.map(setupStepDoc), ...runSteps(beforeScript)];
|
|
387
|
+
later.push(gated
|
|
381
388
|
? gatedRunStep(RUN_STEP_ID, spec.name, invocation, stepEnv)
|
|
382
389
|
: driftAgent
|
|
383
390
|
? driftRunStep(invocation, stepEnv, forge)
|
|
384
|
-
:
|
|
385
|
-
|
|
391
|
+
: update
|
|
392
|
+
? driftFlagStep(invocation, stepEnv)
|
|
393
|
+
: { run: invocation, env: stepEnv });
|
|
394
|
+
later.push(...runSteps(extraScript));
|
|
395
|
+
// #4631: the trade before the checkout, and every step after it without the token request variables.
|
|
396
|
+
const steps = cloud ? [tradeStep(cloud), checkoutStep(), ...later.map(withoutTokenRequest)] : [checkoutStep(), ...later];
|
|
386
397
|
const review = split
|
|
387
398
|
? reviewJobs({
|
|
388
399
|
name: spec.name,
|
|
@@ -396,6 +407,7 @@ export function buildGithubOpPipelineDocs(ops, options = {}, forge = "github") {
|
|
|
396
407
|
...(options.member ? { member: options.member } : {}),
|
|
397
408
|
})
|
|
398
409
|
: undefined;
|
|
410
|
+
const workflowPermissions = mergePermissions(spec.name, permissionsFor(findingMode, trigger), spec.permissions ?? {}, trigger);
|
|
399
411
|
const doc = {
|
|
400
412
|
name: spec.name,
|
|
401
413
|
on: onTrigger(trigger),
|
|
@@ -403,7 +415,7 @@ export function buildGithubOpPipelineDocs(ops, options = {}, forge = "github") {
|
|
|
403
415
|
// One run at a time per Op — a slow audit must not overlap its own next
|
|
404
416
|
// scheduled trigger. A push Op's `build` (#4169) widens the group.
|
|
405
417
|
concurrency: concurrency(opConcurrencyGroup(jobName, trigger)),
|
|
406
|
-
permissions:
|
|
418
|
+
permissions: workflowPermissions,
|
|
407
419
|
jobsDoc: {
|
|
408
420
|
[jobName]: {
|
|
409
421
|
"runs-on": githubRunsOn(spec.runner),
|
|
@@ -418,6 +430,8 @@ export function buildGithubOpPipelineDocs(ops, options = {}, forge = "github") {
|
|
|
418
430
|
// behind the same reviewer would hold the message back until
|
|
419
431
|
// somebody had already acted.
|
|
420
432
|
...(spec.environment ? { environment: environmentDoc(spec.environment) } : {}),
|
|
433
|
+
// #4631: the trade needs id-token: write, on this job alone; the finding-mode's scopes stay as they are.
|
|
434
|
+
...(cloud ? { permissions: { ...workflowPermissions, "id-token": "write" } } : {}),
|
|
421
435
|
...(gated
|
|
422
436
|
? {
|
|
423
437
|
outputs: Object.fromEntries(["gated", "op", "gate", "approve"].map((name) => [
|
|
@@ -427,9 +441,21 @@ export function buildGithubOpPipelineDocs(ops, options = {}, forge = "github") {
|
|
|
427
441
|
}
|
|
428
442
|
: driftAgent
|
|
429
443
|
? { outputs: driftRunOutputs(forge) }
|
|
430
|
-
:
|
|
444
|
+
: update
|
|
445
|
+
? { outputs: { drift: `\${{ steps.${DRIFT_FLAG_STEP_ID}.outputs.drift }}` } }
|
|
446
|
+
: {}),
|
|
431
447
|
steps,
|
|
432
448
|
},
|
|
449
|
+
...(update
|
|
450
|
+
? {
|
|
451
|
+
[scheduledOpUpdateJobName(jobName)]: updateJob(jobName, update, {
|
|
452
|
+
image,
|
|
453
|
+
...(spec.runner !== undefined ? { runner: spec.runner } : {}),
|
|
454
|
+
setup: setup.map(setupStepDoc),
|
|
455
|
+
beforeScript,
|
|
456
|
+
}),
|
|
457
|
+
}
|
|
458
|
+
: {}),
|
|
433
459
|
},
|
|
434
460
|
...(gated ? { gatedNoticeDoc: { [`${jobName}-gate-notice`]: gateNoticeJob(jobName, image, spec.runner) } } : {}),
|
|
435
461
|
};
|
|
@@ -524,3 +550,42 @@ export function generateGithubOpPipeline(ops, options = {}) {
|
|
|
524
550
|
jobs,
|
|
525
551
|
};
|
|
526
552
|
}
|
|
553
|
+
/** The id of the run step that says whether an update's drift check found drift (#4631). */
|
|
554
|
+
const DRIFT_FLAG_STEP_ID = "drift-check";
|
|
555
|
+
/** The Op's run step when an update follows it (#4631): `drift=true` as an output on chant's drift code, and the run's own exit code. */
|
|
556
|
+
function driftFlagStep(invocation, env) {
|
|
557
|
+
return {
|
|
558
|
+
id: DRIFT_FLAG_STEP_ID,
|
|
559
|
+
env,
|
|
560
|
+
run: ["set +e", invocation, "code=$?", `if [ "$code" -eq ${CHANT_EXIT.drift} ]; then echo "drift=true" >>"$GITHUB_OUTPUT"; fi`, 'exit "$code"'].join("\n"),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
/** The trade step (#4631), as an op wave job's (#4332): the job's OIDC token for `cloud`, written to GITHUB_ENV. */
|
|
564
|
+
function tradeStep(cloud) {
|
|
565
|
+
return { name: "Trade the job's OIDC token for its cloud identities", env: { [TRADE_SPEC_ENV]: JSON.stringify(cloud) }, run: tradeLine("$GITHUB_ENV", "github-env") };
|
|
566
|
+
}
|
|
567
|
+
/** A run step after the trade, with the token request variables unset first. A `uses:` step is left as it is. */
|
|
568
|
+
function withoutTokenRequest(step) {
|
|
569
|
+
return typeof step.run === "string" ? { ...step, run: withoutTokenRequestLine(step.run) } : step;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* The update job (#4631): after the Op's job, only when its run found
|
|
573
|
+
* drift; its own trade for `update.cloud` before its checkout, then the
|
|
574
|
+
* update's command. Its permissions are `contents: read`, `id-token: write`
|
|
575
|
+
* with a cloud, and the update's own scopes, which may raise `contents` to
|
|
576
|
+
* `write` so the update records on `chant/lifecycle` (2026-10-28 ruling).
|
|
577
|
+
* The Op's own job keeps its scopes.
|
|
578
|
+
*/
|
|
579
|
+
function updateJob(after, update, i) {
|
|
580
|
+
const later = [...i.setup, ...runSteps(i.beforeScript), { name: "Update what drifted", run: update.command }];
|
|
581
|
+
return {
|
|
582
|
+
needs: after,
|
|
583
|
+
if: `always() && needs.${after}.outputs.drift == 'true'`,
|
|
584
|
+
"runs-on": githubRunsOn(i.runner),
|
|
585
|
+
container: i.image,
|
|
586
|
+
permissions: { contents: "read", ...(update.cloud ? { "id-token": "write" } : {}), ...(update.permissions ?? {}) },
|
|
587
|
+
...nonEmpty("env", update.variables),
|
|
588
|
+
...(update.environment ? { environment: environmentDoc(update.environment) } : {}),
|
|
589
|
+
steps: update.cloud ? [tradeStep(update.cloud), checkoutStep(), ...later.map(withoutTokenRequest)] : [checkoutStep(), ...later],
|
|
590
|
+
};
|
|
591
|
+
}
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
* `pull-requests: read`, so a wave can read the merged pull request's
|
|
35
35
|
* reviews, and a second workflow, `<name>-plans.yml`, runs `chant run wave
|
|
36
36
|
* --record-plans` on each pull request to record the digests its head plans.
|
|
37
|
+
* A `pull-request` wave (#4630) gets the plans workflow too, for the approval
|
|
38
|
+
* status before merge; it asks for no `pull-requests: read` on the waves.
|
|
37
39
|
*
|
|
38
40
|
* With `notify` on the spec (#3761), the workflow maps each variable it names
|
|
39
41
|
* from the repository secret of the same name, so a wave job can post to
|
|
@@ -62,6 +64,15 @@
|
|
|
62
64
|
* step holds the control token: the `tokenSecret` secret, mapped into
|
|
63
65
|
* `CHANT_CONTROL_TOKEN` and handed to curl on its stdin
|
|
64
66
|
* (`controlDispatchScript`, #4568).
|
|
67
|
+
*
|
|
68
|
+
* Every wave, deciding and share job starts with the apply-job guard
|
|
69
|
+
* (#4624, `@intentius/chant/ci-job-guard`), before the trade and the
|
|
70
|
+
* checkout: it stops the job unless `GITHUB_REF` is one of the spec's
|
|
71
|
+
* branches, the job runs in its forge environment (read from an OIDC
|
|
72
|
+
* token's claims, so only in a job that may request one), and, for a
|
|
73
|
+
* runner whose labels include `self-hosted`, on a self-hosted runner. The
|
|
74
|
+
* forgejo generator passes `forge: "forgejo"`, which checks the ref and
|
|
75
|
+
* runner only.
|
|
65
76
|
*/
|
|
66
77
|
import { type OpWavesSpec } from "@intentius/chant/op/op-waves";
|
|
67
78
|
import type { OpWavesPipelineOptions, OpWavesPipelineResult } from "@intentius/chant/lexicon";
|
|
@@ -73,7 +84,9 @@ export declare function opWaveRecordArtifact(spec: Pick<OpWavesSpec, "name">, wa
|
|
|
73
84
|
/** The artifact a job keeps its outcome file in (#4322). */
|
|
74
85
|
export declare function opWaveOutcomeArtifact(spec: Pick<OpWavesSpec, "name">, jobName: string): string;
|
|
75
86
|
/** The workflow document, before emission, and the jobs it holds. */
|
|
76
|
-
export declare function buildGithubOpWavesDoc(spec: OpWavesSpec, options: OpWavesPipelineOptions
|
|
87
|
+
export declare function buildGithubOpWavesDoc(spec: OpWavesSpec, options: OpWavesPipelineOptions, dialect?: {
|
|
88
|
+
forge?: "github" | "forgejo";
|
|
89
|
+
}): {
|
|
77
90
|
doc: GithubOpPipelineDoc;
|
|
78
91
|
jobs: OpWavesPipelineResult["jobs"];
|
|
79
92
|
resumeDoc?: GithubOpPipelineDoc;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-op-waves-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-waves-pipeline.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"generate-op-waves-pipeline.d.ts","sourceRoot":"","sources":["../../src/components/generate-op-waves-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH,OAAO,EAYL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAmB9F,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAEtF,yFAAyF;AACzF,eAAO,MAAM,cAAc,YAAW,CAAC;AAEvC,oEAAoE;AACpE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1F;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9F;AAED,qEAAqE;AACrE,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,sBAAsB,EAC/B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAO,GAC7C;IAAE,GAAG,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;CAAE,CA4FpI;AAoID,4EAA4E;AAC5E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAMvH"}
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
* `pull-requests: read`, so a wave can read the merged pull request's
|
|
35
35
|
* reviews, and a second workflow, `<name>-plans.yml`, runs `chant run wave
|
|
36
36
|
* --record-plans` on each pull request to record the digests its head plans.
|
|
37
|
+
* A `pull-request` wave (#4630) gets the plans workflow too, for the approval
|
|
38
|
+
* status before merge; it asks for no `pull-requests: read` on the waves.
|
|
37
39
|
*
|
|
38
40
|
* With `notify` on the spec (#3761), the workflow maps each variable it names
|
|
39
41
|
* from the repository secret of the same name, so a wave job can post to
|
|
@@ -62,11 +64,22 @@
|
|
|
62
64
|
* step holds the control token: the `tokenSecret` secret, mapped into
|
|
63
65
|
* `CHANT_CONTROL_TOKEN` and handed to curl on its stdin
|
|
64
66
|
* (`controlDispatchScript`, #4568).
|
|
67
|
+
*
|
|
68
|
+
* Every wave, deciding and share job starts with the apply-job guard
|
|
69
|
+
* (#4624, `@intentius/chant/ci-job-guard`), before the trade and the
|
|
70
|
+
* checkout: it stops the job unless `GITHUB_REF` is one of the spec's
|
|
71
|
+
* branches, the job runs in its forge environment (read from an OIDC
|
|
72
|
+
* token's claims, so only in a job that may request one), and, for a
|
|
73
|
+
* runner whose labels include `self-hosted`, on a self-hosted runner. The
|
|
74
|
+
* forgejo generator passes `forge: "forgejo"`, which checks the ref and
|
|
75
|
+
* runner only.
|
|
65
76
|
*/
|
|
66
|
-
import { notifyVariables, opWaveGroupOf, opWaveJobs, opWaveOutcomeDir, opWavesControlTarget, opWavesLastJobs, opWavesOutcomeCommand, opWavesRecordPlansCommand, opWavesResumeCommand, } from "@intentius/chant/op/op-waves";
|
|
77
|
+
import { notifyVariables, opWaveGroupOf, opWaveJobs, opWaveOutcomeDir, opWavesControlTarget, opWavesLastJobs, opWavesApprovalStatusCommand, opWavesPostsApprovalStatus, opWavesOutcomeCommand, opWavesRecordPlansCommand, opWavesResumeCommand, } from "@intentius/chant/op/op-waves";
|
|
67
78
|
import { CONTROL_OUTCOME_VAR, controlDispatchJob } from "@intentius/chant/control/dispatch";
|
|
68
79
|
import { assertEnvironment, assertSetupSteps, CI_IMAGE, dirnameOf, environmentDoc, GITHUB_JOB_TOKEN, githubRunsOn, githubSecretEnv, idTokenAudienceEnv, nonEmpty, pushBranches, setupStepDoc, } from "@intentius/chant/ci-jobs";
|
|
69
80
|
import { OIDC_REQUEST_VARS, TRADE_SPEC_ENV, tradeLine } from "@intentius/chant/ci-identity";
|
|
81
|
+
import { JOB_GUARD_ENV, jobGuardLine, jobGuardSpec, opWaveJobLabel } from "@intentius/chant/ci-job-guard";
|
|
82
|
+
import { resolveProductWords } from "@intentius/chant/ci-product-words";
|
|
70
83
|
import { checkoutStep, concurrency, DOWNLOAD_ARTIFACT, downloadArtifactStep, keepRecordStep, runSteps } from "./ci-jobs.js";
|
|
71
84
|
import { emitOpPipelineYAML } from "./generate-op-pipeline.js";
|
|
72
85
|
/** The default image: it carries git, which the runner needs to read the base commit. */
|
|
@@ -80,10 +93,14 @@ export function opWaveOutcomeArtifact(spec, jobName) {
|
|
|
80
93
|
return `${spec.name}-${jobName}-outcome`;
|
|
81
94
|
}
|
|
82
95
|
/** The workflow document, before emission, and the jobs it holds. */
|
|
83
|
-
export function buildGithubOpWavesDoc(spec, options) {
|
|
96
|
+
export function buildGithubOpWavesDoc(spec, options, dialect = {}) {
|
|
84
97
|
const jobs = opWaveJobs(spec, options.specFile, { bin: options.bin });
|
|
98
|
+
const forge = dialect.forge ?? "github";
|
|
99
|
+
const product = resolveProductWords(options.words ?? {}).product;
|
|
85
100
|
const image = options.image ?? OP_WAVES_IMAGE;
|
|
86
101
|
const byReview = spec.waves.some((w) => w.approval === "pr-review");
|
|
102
|
+
// #4630: a pr-review or pull-request wave gets the plans workflow and its approval status.
|
|
103
|
+
const statusBeforeMerge = opWavesPostsApprovalStatus(spec);
|
|
87
104
|
for (const wave of spec.waves) {
|
|
88
105
|
assertSetupSteps(`${spec.name}/${wave.name}`, wave.setup ?? []);
|
|
89
106
|
if (wave.environment)
|
|
@@ -102,7 +119,21 @@ export function buildGithubOpWavesDoc(spec, options) {
|
|
|
102
119
|
const group = opWaveGroupOf(spec, job);
|
|
103
120
|
// #4332: the job's cloud identities, traded before the checkout.
|
|
104
121
|
const cloud = group?.cloud ?? wave.cloud;
|
|
105
|
-
const
|
|
122
|
+
const environment = group?.environment ?? wave.environment;
|
|
123
|
+
const idTokens = group?.idTokens ?? wave.idTokens;
|
|
124
|
+
const idToken = idTokens !== undefined || cloud !== undefined;
|
|
125
|
+
const runner = group?.runner ?? wave.runner ?? spec.runner;
|
|
126
|
+
// #4624: the guard first, before the trade and the checkout.
|
|
127
|
+
const guard = guardStep({
|
|
128
|
+
forge,
|
|
129
|
+
job: opWaveJobLabel(spec.name, job),
|
|
130
|
+
product,
|
|
131
|
+
...(environment && forge === "github" ? { environment: environment.name } : {}),
|
|
132
|
+
branches: pushBranches(spec.branches),
|
|
133
|
+
...(runner !== undefined ? { runner } : {}),
|
|
134
|
+
idToken,
|
|
135
|
+
});
|
|
136
|
+
const steps = [guard, ...(cloud ? [tradeStep(cloud)] : []), checkoutStep(true)];
|
|
106
137
|
if (job.kind === "share") {
|
|
107
138
|
steps.push(downloadArtifactStep(`Download wave ${job.wave}'s decision`, opWaveRecordArtifact(spec, job.wave), dirnameOf(job.record)));
|
|
108
139
|
}
|
|
@@ -119,11 +150,8 @@ export function buildGithubOpWavesDoc(spec, options) {
|
|
|
119
150
|
steps.push(keepRecordStep(`Keep wave ${job.wave}'s decision`, opWaveRecordArtifact(spec, job.wave), job.record));
|
|
120
151
|
}
|
|
121
152
|
steps.push(keepRecordStep(`Keep the job's outcome`, opWaveOutcomeArtifact(spec, job.jobName), job.outcome));
|
|
122
|
-
const environment = group?.environment ?? wave.environment;
|
|
123
|
-
const idTokens = group?.idTokens ?? wave.idTokens;
|
|
124
|
-
const idToken = idTokens !== undefined || cloud !== undefined;
|
|
125
153
|
jobsDoc[job.jobName] = {
|
|
126
|
-
"runs-on": githubRunsOn(
|
|
154
|
+
"runs-on": githubRunsOn(runner),
|
|
127
155
|
...(job.needs.length > 0 ? { needs: job.needs } : {}),
|
|
128
156
|
// #4322: a wave's jobs run after a failure in the wave before; a share job runs only on its deciding job's success.
|
|
129
157
|
...(job.needs.length > 0 && job.kind !== "share" ? { if: "!cancelled()" } : {}),
|
|
@@ -154,7 +182,7 @@ export function buildGithubOpWavesDoc(spec, options) {
|
|
|
154
182
|
doc,
|
|
155
183
|
jobs,
|
|
156
184
|
...(spec.resume ? { resumeDoc: buildResumeDoc(spec, options) } : {}),
|
|
157
|
-
...(
|
|
185
|
+
...(statusBeforeMerge ? { plansDoc: buildPlansDoc(spec, options, forge) } : {}),
|
|
158
186
|
};
|
|
159
187
|
}
|
|
160
188
|
/**
|
|
@@ -213,20 +241,53 @@ function buildResumeDoc(spec, options) {
|
|
|
213
241
|
jobsDoc: { resume: { "runs-on": githubRunsOn(spec.runner), container: options.image ?? OP_WAVES_IMAGE, steps } },
|
|
214
242
|
};
|
|
215
243
|
}
|
|
216
|
-
/**
|
|
217
|
-
|
|
244
|
+
/**
|
|
245
|
+
* The pull request workflow a `pr-review` wave needs (#3684): record the
|
|
246
|
+
* digests each head plans. The `approval-status` job after it (#4630) posts
|
|
247
|
+
* `<product>/approval` on the head, and runs alone on each review submitted
|
|
248
|
+
* or dismissed. It holds a read token that may write statuses and nothing
|
|
249
|
+
* else. A `pull-request` wave gets the same workflow: the record keeps its
|
|
250
|
+
* change set, and the status reads the pull request's own approval of it.
|
|
251
|
+
*/
|
|
252
|
+
function buildPlansDoc(spec, options, forge) {
|
|
218
253
|
const steps = [checkoutStep(true), ...(spec.waves[0]?.setup ?? []).map(setupStepDoc), ...runSteps(options.beforeScript ?? [])];
|
|
219
254
|
steps.push({ name: "Plan every wave at the head and record its digests", run: opWavesRecordPlansCommand(options.specFile, options.bin).join(" ") });
|
|
255
|
+
const statusSteps = [checkoutStep(true), ...runSteps(options.beforeScript ?? [])];
|
|
256
|
+
statusSteps.push({
|
|
257
|
+
name: "Post the approval status on the head",
|
|
258
|
+
run: opWavesApprovalStatusCommand(options.specFile, forge, options.bin).join(" "),
|
|
259
|
+
env: {
|
|
260
|
+
GITHUB_TOKEN: GITHUB_JOB_TOKEN,
|
|
261
|
+
PR_NUMBER: "${{ github.event.pull_request.number }}",
|
|
262
|
+
HEAD_SHA: "${{ github.event.pull_request.head.sha }}",
|
|
263
|
+
BASE_SHA: "${{ github.event.pull_request.base.sha }}",
|
|
264
|
+
},
|
|
265
|
+
});
|
|
220
266
|
return {
|
|
221
267
|
name: `${spec.name}-plans`,
|
|
222
|
-
on: { pull_request: { branches: pushBranches(spec.branches) } },
|
|
268
|
+
on: { pull_request: { branches: pushBranches(spec.branches) }, pull_request_review: { types: ["submitted", "dismissed"] } },
|
|
223
269
|
...nonEmpty("env", options.variables),
|
|
224
|
-
concurrency: concurrency(spec.name + "-plans-${{ github.event.number }}", true),
|
|
270
|
+
concurrency: concurrency(spec.name + "-plans-${{ github.event_name }}-${{ github.event.pull_request.number }}", true),
|
|
225
271
|
// contents: write pushes the record to chant/lifecycle.
|
|
226
272
|
permissions: { contents: "write" },
|
|
227
|
-
jobsDoc: {
|
|
273
|
+
jobsDoc: {
|
|
274
|
+
"record-plans": { if: "github.event_name == 'pull_request'", "runs-on": githubRunsOn(spec.runner), container: options.image ?? OP_WAVES_IMAGE, steps },
|
|
275
|
+
"approval-status": {
|
|
276
|
+
needs: ["record-plans"],
|
|
277
|
+
// On a review the record-plans job is skipped, and the status reads the plans an earlier push recorded.
|
|
278
|
+
if: "${{ always() && (needs.record-plans.result == 'success' || needs.record-plans.result == 'skipped') }}",
|
|
279
|
+
"runs-on": githubRunsOn(spec.runner),
|
|
280
|
+
container: options.image ?? OP_WAVES_IMAGE,
|
|
281
|
+
permissions: { contents: "read", "pull-requests": "read", statuses: "write" },
|
|
282
|
+
steps: statusSteps,
|
|
283
|
+
},
|
|
284
|
+
},
|
|
228
285
|
};
|
|
229
286
|
}
|
|
287
|
+
/** The apply-job guard step (#4624): the job's expectation in CHANT_JOB_GUARD, checked by `node -e` from `/`. */
|
|
288
|
+
function guardStep(input) {
|
|
289
|
+
return { name: "Check the job runs where the spec set it to run", env: { [JOB_GUARD_ENV]: JSON.stringify(jobGuardSpec(input)) }, run: jobGuardLine() };
|
|
290
|
+
}
|
|
230
291
|
/** The step that trades the job's OIDC token for `cloud` (#4332), writing the credentials to GITHUB_ENV. */
|
|
231
292
|
function tradeStep(cloud) {
|
|
232
293
|
return { name: "Trade the job's OIDC token for its cloud identities", env: { [TRADE_SPEC_ENV]: JSON.stringify(cloud) }, run: tradeLine("$GITHUB_ENV", "github-env") };
|
|
@@ -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;AA2CjG,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;AAgoBD;;;;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"}
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
import { resolveComponentGraph } from "@intentius/chant/components/driver";
|
|
59
59
|
import { hasPublishStep, promoteArchivePaths } from "@intentius/chant/components/promote";
|
|
60
60
|
import { GATED_WAVE_RECORD, gatedWaveJobs } from "@intentius/chant/components/gated-wave-pipeline";
|
|
61
|
-
import { CI_IMAGE, ciJobName, dirnameOf, fillCommand, GITHUB_JOB_TOKEN, GITHUB_RUNNER } from "@intentius/chant/ci-jobs";
|
|
61
|
+
import { CI_IMAGE, ciJobName, dirnameOf, fillCommand, GITHUB_JOB_TOKEN, GITHUB_RUNNER, githubRunsOn } 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 { 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";
|
|
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, prLoopJobStage, prStaleJob, PR_STALE_JOB, prLoopRunner, PR_ENV_REPORT_DIR, PR_ENV_SWEEP_CRON, PR_ENV_POST_JOB, } from "@intentius/chant/components/pr-pipeline";
|
|
65
65
|
import { forgejoRepoLockLines, REPO_APPLY_GROUP } from "@intentius/chant/components/repo-lock";
|
|
66
66
|
import { memberRepoPath } from "@intentius/chant/lexicon";
|
|
67
67
|
import { productStatusContext, resolveProductWords } from "@intentius/chant/ci-product-words";
|
|
@@ -322,7 +322,25 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
322
322
|
},
|
|
323
323
|
]);
|
|
324
324
|
}
|
|
325
|
-
const post = prLoopPostJob(forgeName, env, member?.name, loop.words ? resolveProductWords(loop.words) : undefined);
|
|
325
|
+
const post = prLoopPostJob(forgeName, env, member?.name, loop.words ? resolveProductWords(loop.words) : undefined, { ...(loop.staleNotes ? { staleNotes: true } : {}), ...(loop.respond ? { respond: loop.respond } : {}), ...(loop.decide ? { decide: loop.decide } : {}) });
|
|
326
|
+
// #4628: on a push to the target branch, the open pull requests' notes posted against another commit are marked stale.
|
|
327
|
+
const stale = prStaleJob(forgeName, env, loop, member?.name);
|
|
328
|
+
const staleJobs = stale
|
|
329
|
+
? {
|
|
330
|
+
[PR_STALE_JOB]: {
|
|
331
|
+
if: "github.event_name == 'push' && startsWith(github.ref, 'refs/heads/')",
|
|
332
|
+
"runs-on": GITHUB_RUNNER,
|
|
333
|
+
container: image,
|
|
334
|
+
permissions: { contents: "read", "pull-requests": "write", statuses: "write" },
|
|
335
|
+
steps: [
|
|
336
|
+
// The pushed commit: the target branch's, so none of a pull request's code is here.
|
|
337
|
+
{ name: "Check out the target branch's commit", uses: CHECKOUT },
|
|
338
|
+
...runSteps(beforeScript),
|
|
339
|
+
{ name: "Mark the open pull requests' notes stale", ...(rooted ? {} : { "working-directory": "." }), env: stale.env, run: stale.command },
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
: {};
|
|
326
344
|
// #4619: with a control repo named, the note job reads its chant/lifecycle, read-only, so pr-post marks the cross-stack edges.
|
|
327
345
|
const controlRead = loop.control ? controlNoteCheckout(forgeName, loop.control.target, loop.words ?? {}) : undefined;
|
|
328
346
|
const noteSteps = [
|
|
@@ -468,6 +486,7 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
468
486
|
...lockJobs,
|
|
469
487
|
...commentLockJobs,
|
|
470
488
|
...previewJobs,
|
|
489
|
+
...staleJobs,
|
|
471
490
|
...Object.fromEntries(applyJobs),
|
|
472
491
|
...Object.fromEntries(controlJobs),
|
|
473
492
|
...(prEnv ? prEnv.jobs : {}),
|
|
@@ -487,6 +506,12 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
487
506
|
// #4301: a /chant apply comment starts the apply before merge. #4588: and a lock or unlock comment its job.
|
|
488
507
|
if (loop.applyBeforeMerge || commentLocks.length > 0 || preview)
|
|
489
508
|
on.issue_comment = { types: ["created"] };
|
|
509
|
+
// #4625: each job on its stage's runner, the plan side's, the apply side's, or the default.
|
|
510
|
+
if (loop.runners) {
|
|
511
|
+
for (const [name, job] of Object.entries(jobsDoc)) {
|
|
512
|
+
job["runs-on"] = githubRunsOn(prLoopRunner(loop, prLoopJobStage(name)));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
490
515
|
return {
|
|
491
516
|
name: member ? `chant-pr.${member.name}.${env}` : `chant-pr-${env}`,
|
|
492
517
|
environment: env,
|
|
@@ -494,10 +519,11 @@ function prLoopGithubDoc(components, env, image, beforeScript, extraScript, opti
|
|
|
494
519
|
...(rooted ? {} : { defaults: { run: { "working-directory": member.dir } } }),
|
|
495
520
|
env: { ...options.variables, CHANT_ENV: env },
|
|
496
521
|
jobsDoc,
|
|
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"] : [])],
|
|
522
|
+
stages: ["plan", PR_PLAN_NOTE_JOB, ...(locks ? [PR_LOCK_JOB, PR_UNLOCK_JOB] : []), ...commentLocks.map((j) => j.jobName), ...(preview ? [preview.jobName, preview.noteJobName] : []), ...(stale ? [PR_STALE_JOB] : []), ...applyNames, ...controlJobs.map(([name]) => name), ...(prEnv ? ["env-up", PR_ENV_POST_JOB, "env-down", "env-sweep"] : [])],
|
|
498
523
|
jobs: [
|
|
499
524
|
{ jobName: "plan", component: "pull request plan", stage: "plan", needs: [] },
|
|
500
525
|
{ jobName: PR_PLAN_NOTE_JOB, component: "pull request plan note", stage: PR_PLAN_NOTE_JOB, needs: ["plan"] },
|
|
526
|
+
...(stale ? [{ jobName: PR_STALE_JOB, component: "pull request stale notes", stage: PR_STALE_JOB, needs: [] }] : []),
|
|
501
527
|
...(locks
|
|
502
528
|
? [
|
|
503
529
|
{ jobName: PR_LOCK_JOB, component: "pull request lock", stage: PR_LOCK_JOB, needs: ["plan"] },
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "16478bfa3a3a6b6a573c54713b9d6edcef575a934df2b6a2daba331607d0d132",
|
|
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": "9eb8c4e72039a3e7c014959fe55b6846823a49f4097e41c3d4b2b9baebe12ad4"
|
|
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.56",
|
|
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.56"
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -20,6 +20,10 @@ jobs:
|
|
|
20
20
|
- dev
|
|
21
21
|
container: node:22
|
|
22
22
|
steps:
|
|
23
|
+
- name: Check the job runs where the spec set it to run
|
|
24
|
+
env:
|
|
25
|
+
CHANT_JOB_GUARD: '{"forge":"github","job":"wave 1 (dev) of migrations","branches":["main"],"selfHosted":true}'
|
|
26
|
+
run: '(cd / && node -e ''"use strict";const E=process.env,A="chant-check-job";const shown=(v)=>v===undefined||v===null||v===""?"none":v;const fail=(m)=>{console.error("check-job: "+m);process.exit(9)};let x;try{x=JSON.parse(E.CHANT_JOB_GUARD||"")}catch(e){fail("CHANT_JOB_GUARD is not JSON")}if(!x||!["github","gitlab","forgejo"].includes(x.forge)||!Array.isArray(x.branches))fail("CHANT_JOB_GUARD is not a guard spec");const tags=(r)=>{if(r===undefined)return undefined;const t=r.trim();if(t.startsWith("[")){try{const v=JSON.parse(t);if(Array.isArray(v))return v.map(String)}catch(e){}}return t===""?[]:t.split(",").map((s)=>s.trim()).filter((s)=>s!=="")};const claimsOf=(tok)=>{const p=tok.split(".")[1];if(!p)throw new Error("the forge\u0027s OIDC token is not a JWT");const c=JSON.parse(Buffer.from(p.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf-8"));if(!c||typeof c!=="object")throw new Error("the forge\u0027s OIDC token has no claims");return c};async function seenOf(){if(x.forge==="gitlab")return{forgeEnvironment:E.CI_ENVIRONMENT_NAME||null,ref:E.CI_COMMIT_REF_NAME,refProtected:E.CI_COMMIT_REF_PROTECTED,runnerTags:tags(E.CI_RUNNER_TAGS)};const s={ref:E.GITHUB_REF};let c;if(x.forge==="github"&&x.forgeEnvironment!==undefined){try{if(!E.ACTIONS_ID_TOKEN_REQUEST_URL||!E.ACTIONS_ID_TOKEN_REQUEST_TOKEN)throw new Error("the job may request no OIDC token (no ACTIONS_ID_TOKEN_REQUEST_URL); it needs the id-token: write permission");const r=await fetch(E.ACTIONS_ID_TOKEN_REQUEST_URL+"&audience="+encodeURIComponent(A),{headers:{authorization:"bearer "+E.ACTIONS_ID_TOKEN_REQUEST_TOKEN}});if(!r.ok)throw new Error("the OIDC token request answered "+r.status);const v=(await r.json()).value;if(typeof v!=="string")throw new Error("the OIDC token request answered no token");c=claimsOf(v);s.forgeEnvironment=typeof c.environment==="string"&&c.environment!==""?c.environment:null}catch(e){s.forgeEnvironmentError=e&&e.message?e.message:String(e)}}const run=c&&typeof c.runner_environment==="string"?c.runner_environment:E.RUNNER_ENVIRONMENT;if(run)s.runnerEnvironment=run;return s}seenOf().then((s)=>{const P=[],C=[],S=[];const fe=x.forgeEnvironment;if(x.forge==="forgejo")S.push("forge environment (Forgejo has none)");else if(fe!==undefined){if(s.forgeEnvironmentError!==undefined)P.push("forge environment: expected "+fe+", and it could not be read: "+s.forgeEnvironmentError);else if(s.forgeEnvironment!==fe)P.push("forge environment: expected "+fe+", found "+shown(s.forgeEnvironment));else C.push("forge environment "+fe)}const b=x.branches.filter((v)=>v!=="");if(b.length===0)P.push("ref: the job names no branch to run from");else if(x.forge==="gitlab"){if(!s.ref||!b.includes(s.ref))P.push("ref: expected "+b.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref);if(s.refProtected!=="true")P.push("ref: expected a protected ref (CI_COMMIT_REF_PROTECTED true), found "+shown(s.refProtected));else C.push("protected ref")}else{const refs=b.map((v)=>"refs/heads/"+v);if(!s.ref||!refs.includes(s.ref))P.push("ref: expected "+refs.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref)}if(x.forge==="gitlab"){const w=x.runnerTags||[];if(w.length>0){const h=s.runnerTags;const miss=w.filter((t)=>!(h||[]).includes(t));if(h===undefined)P.push("runner: expected tags "+w.join(", ")+", and the job has no CI_RUNNER_TAGS");else if(miss.length>0)P.push("runner: expected tags "+w.join(", ")+", found "+(h.length?h.join(", "):"none"));else C.push("runner tags "+w.join(", "))}}else if(x.selfHosted){if(s.runnerEnvironment===undefined)S.push("runner (the runner says nothing about itself: no RUNNER_ENVIRONMENT)");else if(s.runnerEnvironment!=="self-hosted")P.push("runner: expected self-hosted, found "+s.runnerEnvironment);else C.push("self-hosted runner")}const at=(x.product||"chant")+" check-job: "+x.job;if(P.length>0){for(const p of P)console.error(at+": "+p);console.error(at+": this job does not run where the spec set it to run, so it stops before any credential is taken or code checked out");process.exit(9)}console.log(at+": "+(C.length?C.join(", "):"nothing to check")+(S.length?"; not checked: "+S.join(", "):""))}).catch((e)=>fail(e&&e.message?e.message:String(e)));'')'
|
|
23
27
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
24
28
|
with:
|
|
25
29
|
fetch-depth: 0
|
|
@@ -47,6 +51,10 @@ jobs:
|
|
|
47
51
|
STAGE: apply
|
|
48
52
|
PLAN_TOKEN_AUDIENCE: plan.example
|
|
49
53
|
steps:
|
|
54
|
+
- name: Check the job runs where the spec set it to run
|
|
55
|
+
env:
|
|
56
|
+
CHANT_JOB_GUARD: '{"forge":"github","job":"the deciding job of wave 2 (prod) of migrations","branches":["main"]}'
|
|
57
|
+
run: '(cd / && node -e ''"use strict";const E=process.env,A="chant-check-job";const shown=(v)=>v===undefined||v===null||v===""?"none":v;const fail=(m)=>{console.error("check-job: "+m);process.exit(9)};let x;try{x=JSON.parse(E.CHANT_JOB_GUARD||"")}catch(e){fail("CHANT_JOB_GUARD is not JSON")}if(!x||!["github","gitlab","forgejo"].includes(x.forge)||!Array.isArray(x.branches))fail("CHANT_JOB_GUARD is not a guard spec");const tags=(r)=>{if(r===undefined)return undefined;const t=r.trim();if(t.startsWith("[")){try{const v=JSON.parse(t);if(Array.isArray(v))return v.map(String)}catch(e){}}return t===""?[]:t.split(",").map((s)=>s.trim()).filter((s)=>s!=="")};const claimsOf=(tok)=>{const p=tok.split(".")[1];if(!p)throw new Error("the forge\u0027s OIDC token is not a JWT");const c=JSON.parse(Buffer.from(p.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf-8"));if(!c||typeof c!=="object")throw new Error("the forge\u0027s OIDC token has no claims");return c};async function seenOf(){if(x.forge==="gitlab")return{forgeEnvironment:E.CI_ENVIRONMENT_NAME||null,ref:E.CI_COMMIT_REF_NAME,refProtected:E.CI_COMMIT_REF_PROTECTED,runnerTags:tags(E.CI_RUNNER_TAGS)};const s={ref:E.GITHUB_REF};let c;if(x.forge==="github"&&x.forgeEnvironment!==undefined){try{if(!E.ACTIONS_ID_TOKEN_REQUEST_URL||!E.ACTIONS_ID_TOKEN_REQUEST_TOKEN)throw new Error("the job may request no OIDC token (no ACTIONS_ID_TOKEN_REQUEST_URL); it needs the id-token: write permission");const r=await fetch(E.ACTIONS_ID_TOKEN_REQUEST_URL+"&audience="+encodeURIComponent(A),{headers:{authorization:"bearer "+E.ACTIONS_ID_TOKEN_REQUEST_TOKEN}});if(!r.ok)throw new Error("the OIDC token request answered "+r.status);const v=(await r.json()).value;if(typeof v!=="string")throw new Error("the OIDC token request answered no token");c=claimsOf(v);s.forgeEnvironment=typeof c.environment==="string"&&c.environment!==""?c.environment:null}catch(e){s.forgeEnvironmentError=e&&e.message?e.message:String(e)}}const run=c&&typeof c.runner_environment==="string"?c.runner_environment:E.RUNNER_ENVIRONMENT;if(run)s.runnerEnvironment=run;return s}seenOf().then((s)=>{const P=[],C=[],S=[];const fe=x.forgeEnvironment;if(x.forge==="forgejo")S.push("forge environment (Forgejo has none)");else if(fe!==undefined){if(s.forgeEnvironmentError!==undefined)P.push("forge environment: expected "+fe+", and it could not be read: "+s.forgeEnvironmentError);else if(s.forgeEnvironment!==fe)P.push("forge environment: expected "+fe+", found "+shown(s.forgeEnvironment));else C.push("forge environment "+fe)}const b=x.branches.filter((v)=>v!=="");if(b.length===0)P.push("ref: the job names no branch to run from");else if(x.forge==="gitlab"){if(!s.ref||!b.includes(s.ref))P.push("ref: expected "+b.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref);if(s.refProtected!=="true")P.push("ref: expected a protected ref (CI_COMMIT_REF_PROTECTED true), found "+shown(s.refProtected));else C.push("protected ref")}else{const refs=b.map((v)=>"refs/heads/"+v);if(!s.ref||!refs.includes(s.ref))P.push("ref: expected "+refs.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref)}if(x.forge==="gitlab"){const w=x.runnerTags||[];if(w.length>0){const h=s.runnerTags;const miss=w.filter((t)=>!(h||[]).includes(t));if(h===undefined)P.push("runner: expected tags "+w.join(", ")+", and the job has no CI_RUNNER_TAGS");else if(miss.length>0)P.push("runner: expected tags "+w.join(", ")+", found "+(h.length?h.join(", "):"none"));else C.push("runner tags "+w.join(", "))}}else if(x.selfHosted){if(s.runnerEnvironment===undefined)S.push("runner (the runner says nothing about itself: no RUNNER_ENVIRONMENT)");else if(s.runnerEnvironment!=="self-hosted")P.push("runner: expected self-hosted, found "+s.runnerEnvironment);else C.push("self-hosted runner")}const at=(x.product||"chant")+" check-job: "+x.job;if(P.length>0){for(const p of P)console.error(at+": "+p);console.error(at+": this job does not run where the spec set it to run, so it stops before any credential is taken or code checked out");process.exit(9)}console.log(at+": "+(C.length?C.join(", "):"nothing to check")+(S.length?"; not checked: "+S.join(", "):""))}).catch((e)=>fail(e&&e.message?e.message:String(e)));'')'
|
|
50
58
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
51
59
|
with:
|
|
52
60
|
fetch-depth: 0
|
|
@@ -92,6 +100,10 @@ jobs:
|
|
|
92
100
|
REGION: us
|
|
93
101
|
CLOUD_TOKEN_AUDIENCE: us.example
|
|
94
102
|
steps:
|
|
103
|
+
- name: Check the job runs where the spec set it to run
|
|
104
|
+
env:
|
|
105
|
+
CHANT_JOB_GUARD: '{"forge":"github","job":"share 1 of job group us of wave 2 (prod) of migrations","forgeEnvironment":"prod-us","branches":["main"]}'
|
|
106
|
+
run: '(cd / && node -e ''"use strict";const E=process.env,A="chant-check-job";const shown=(v)=>v===undefined||v===null||v===""?"none":v;const fail=(m)=>{console.error("check-job: "+m);process.exit(9)};let x;try{x=JSON.parse(E.CHANT_JOB_GUARD||"")}catch(e){fail("CHANT_JOB_GUARD is not JSON")}if(!x||!["github","gitlab","forgejo"].includes(x.forge)||!Array.isArray(x.branches))fail("CHANT_JOB_GUARD is not a guard spec");const tags=(r)=>{if(r===undefined)return undefined;const t=r.trim();if(t.startsWith("[")){try{const v=JSON.parse(t);if(Array.isArray(v))return v.map(String)}catch(e){}}return t===""?[]:t.split(",").map((s)=>s.trim()).filter((s)=>s!=="")};const claimsOf=(tok)=>{const p=tok.split(".")[1];if(!p)throw new Error("the forge\u0027s OIDC token is not a JWT");const c=JSON.parse(Buffer.from(p.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf-8"));if(!c||typeof c!=="object")throw new Error("the forge\u0027s OIDC token has no claims");return c};async function seenOf(){if(x.forge==="gitlab")return{forgeEnvironment:E.CI_ENVIRONMENT_NAME||null,ref:E.CI_COMMIT_REF_NAME,refProtected:E.CI_COMMIT_REF_PROTECTED,runnerTags:tags(E.CI_RUNNER_TAGS)};const s={ref:E.GITHUB_REF};let c;if(x.forge==="github"&&x.forgeEnvironment!==undefined){try{if(!E.ACTIONS_ID_TOKEN_REQUEST_URL||!E.ACTIONS_ID_TOKEN_REQUEST_TOKEN)throw new Error("the job may request no OIDC token (no ACTIONS_ID_TOKEN_REQUEST_URL); it needs the id-token: write permission");const r=await fetch(E.ACTIONS_ID_TOKEN_REQUEST_URL+"&audience="+encodeURIComponent(A),{headers:{authorization:"bearer "+E.ACTIONS_ID_TOKEN_REQUEST_TOKEN}});if(!r.ok)throw new Error("the OIDC token request answered "+r.status);const v=(await r.json()).value;if(typeof v!=="string")throw new Error("the OIDC token request answered no token");c=claimsOf(v);s.forgeEnvironment=typeof c.environment==="string"&&c.environment!==""?c.environment:null}catch(e){s.forgeEnvironmentError=e&&e.message?e.message:String(e)}}const run=c&&typeof c.runner_environment==="string"?c.runner_environment:E.RUNNER_ENVIRONMENT;if(run)s.runnerEnvironment=run;return s}seenOf().then((s)=>{const P=[],C=[],S=[];const fe=x.forgeEnvironment;if(x.forge==="forgejo")S.push("forge environment (Forgejo has none)");else if(fe!==undefined){if(s.forgeEnvironmentError!==undefined)P.push("forge environment: expected "+fe+", and it could not be read: "+s.forgeEnvironmentError);else if(s.forgeEnvironment!==fe)P.push("forge environment: expected "+fe+", found "+shown(s.forgeEnvironment));else C.push("forge environment "+fe)}const b=x.branches.filter((v)=>v!=="");if(b.length===0)P.push("ref: the job names no branch to run from");else if(x.forge==="gitlab"){if(!s.ref||!b.includes(s.ref))P.push("ref: expected "+b.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref);if(s.refProtected!=="true")P.push("ref: expected a protected ref (CI_COMMIT_REF_PROTECTED true), found "+shown(s.refProtected));else C.push("protected ref")}else{const refs=b.map((v)=>"refs/heads/"+v);if(!s.ref||!refs.includes(s.ref))P.push("ref: expected "+refs.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref)}if(x.forge==="gitlab"){const w=x.runnerTags||[];if(w.length>0){const h=s.runnerTags;const miss=w.filter((t)=>!(h||[]).includes(t));if(h===undefined)P.push("runner: expected tags "+w.join(", ")+", and the job has no CI_RUNNER_TAGS");else if(miss.length>0)P.push("runner: expected tags "+w.join(", ")+", found "+(h.length?h.join(", "):"none"));else C.push("runner tags "+w.join(", "))}}else if(x.selfHosted){if(s.runnerEnvironment===undefined)S.push("runner (the runner says nothing about itself: no RUNNER_ENVIRONMENT)");else if(s.runnerEnvironment!=="self-hosted")P.push("runner: expected self-hosted, found "+s.runnerEnvironment);else C.push("self-hosted runner")}const at=(x.product||"chant")+" check-job: "+x.job;if(P.length>0){for(const p of P)console.error(at+": "+p);console.error(at+": this job does not run where the spec set it to run, so it stops before any credential is taken or code checked out");process.exit(9)}console.log(at+": "+(C.length?C.join(", "):"nothing to check")+(S.length?"; not checked: "+S.join(", "):""))}).catch((e)=>fail(e&&e.message?e.message:String(e)));'')'
|
|
95
107
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
96
108
|
with:
|
|
97
109
|
fetch-depth: 0
|
|
@@ -134,6 +146,10 @@ jobs:
|
|
|
134
146
|
REGION: us
|
|
135
147
|
CLOUD_TOKEN_AUDIENCE: us.example
|
|
136
148
|
steps:
|
|
149
|
+
- name: Check the job runs where the spec set it to run
|
|
150
|
+
env:
|
|
151
|
+
CHANT_JOB_GUARD: '{"forge":"github","job":"share 2 of job group us of wave 2 (prod) of migrations","forgeEnvironment":"prod-us","branches":["main"]}'
|
|
152
|
+
run: '(cd / && node -e ''"use strict";const E=process.env,A="chant-check-job";const shown=(v)=>v===undefined||v===null||v===""?"none":v;const fail=(m)=>{console.error("check-job: "+m);process.exit(9)};let x;try{x=JSON.parse(E.CHANT_JOB_GUARD||"")}catch(e){fail("CHANT_JOB_GUARD is not JSON")}if(!x||!["github","gitlab","forgejo"].includes(x.forge)||!Array.isArray(x.branches))fail("CHANT_JOB_GUARD is not a guard spec");const tags=(r)=>{if(r===undefined)return undefined;const t=r.trim();if(t.startsWith("[")){try{const v=JSON.parse(t);if(Array.isArray(v))return v.map(String)}catch(e){}}return t===""?[]:t.split(",").map((s)=>s.trim()).filter((s)=>s!=="")};const claimsOf=(tok)=>{const p=tok.split(".")[1];if(!p)throw new Error("the forge\u0027s OIDC token is not a JWT");const c=JSON.parse(Buffer.from(p.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf-8"));if(!c||typeof c!=="object")throw new Error("the forge\u0027s OIDC token has no claims");return c};async function seenOf(){if(x.forge==="gitlab")return{forgeEnvironment:E.CI_ENVIRONMENT_NAME||null,ref:E.CI_COMMIT_REF_NAME,refProtected:E.CI_COMMIT_REF_PROTECTED,runnerTags:tags(E.CI_RUNNER_TAGS)};const s={ref:E.GITHUB_REF};let c;if(x.forge==="github"&&x.forgeEnvironment!==undefined){try{if(!E.ACTIONS_ID_TOKEN_REQUEST_URL||!E.ACTIONS_ID_TOKEN_REQUEST_TOKEN)throw new Error("the job may request no OIDC token (no ACTIONS_ID_TOKEN_REQUEST_URL); it needs the id-token: write permission");const r=await fetch(E.ACTIONS_ID_TOKEN_REQUEST_URL+"&audience="+encodeURIComponent(A),{headers:{authorization:"bearer "+E.ACTIONS_ID_TOKEN_REQUEST_TOKEN}});if(!r.ok)throw new Error("the OIDC token request answered "+r.status);const v=(await r.json()).value;if(typeof v!=="string")throw new Error("the OIDC token request answered no token");c=claimsOf(v);s.forgeEnvironment=typeof c.environment==="string"&&c.environment!==""?c.environment:null}catch(e){s.forgeEnvironmentError=e&&e.message?e.message:String(e)}}const run=c&&typeof c.runner_environment==="string"?c.runner_environment:E.RUNNER_ENVIRONMENT;if(run)s.runnerEnvironment=run;return s}seenOf().then((s)=>{const P=[],C=[],S=[];const fe=x.forgeEnvironment;if(x.forge==="forgejo")S.push("forge environment (Forgejo has none)");else if(fe!==undefined){if(s.forgeEnvironmentError!==undefined)P.push("forge environment: expected "+fe+", and it could not be read: "+s.forgeEnvironmentError);else if(s.forgeEnvironment!==fe)P.push("forge environment: expected "+fe+", found "+shown(s.forgeEnvironment));else C.push("forge environment "+fe)}const b=x.branches.filter((v)=>v!=="");if(b.length===0)P.push("ref: the job names no branch to run from");else if(x.forge==="gitlab"){if(!s.ref||!b.includes(s.ref))P.push("ref: expected "+b.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref);if(s.refProtected!=="true")P.push("ref: expected a protected ref (CI_COMMIT_REF_PROTECTED true), found "+shown(s.refProtected));else C.push("protected ref")}else{const refs=b.map((v)=>"refs/heads/"+v);if(!s.ref||!refs.includes(s.ref))P.push("ref: expected "+refs.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref)}if(x.forge==="gitlab"){const w=x.runnerTags||[];if(w.length>0){const h=s.runnerTags;const miss=w.filter((t)=>!(h||[]).includes(t));if(h===undefined)P.push("runner: expected tags "+w.join(", ")+", and the job has no CI_RUNNER_TAGS");else if(miss.length>0)P.push("runner: expected tags "+w.join(", ")+", found "+(h.length?h.join(", "):"none"));else C.push("runner tags "+w.join(", "))}}else if(x.selfHosted){if(s.runnerEnvironment===undefined)S.push("runner (the runner says nothing about itself: no RUNNER_ENVIRONMENT)");else if(s.runnerEnvironment!=="self-hosted")P.push("runner: expected self-hosted, found "+s.runnerEnvironment);else C.push("self-hosted runner")}const at=(x.product||"chant")+" check-job: "+x.job;if(P.length>0){for(const p of P)console.error(at+": "+p);console.error(at+": this job does not run where the spec set it to run, so it stops before any credential is taken or code checked out");process.exit(9)}console.log(at+": "+(C.length?C.join(", "):"nothing to check")+(S.length?"; not checked: "+S.join(", "):""))}).catch((e)=>fail(e&&e.message?e.message:String(e)));'')'
|
|
137
153
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
138
154
|
with:
|
|
139
155
|
fetch-depth: 0
|
|
@@ -172,6 +188,10 @@ jobs:
|
|
|
172
188
|
STAGE: apply
|
|
173
189
|
PLAN_TOKEN_AUDIENCE: plan.example
|
|
174
190
|
steps:
|
|
191
|
+
- name: Check the job runs where the spec set it to run
|
|
192
|
+
env:
|
|
193
|
+
CHANT_JOB_GUARD: '{"forge":"github","job":"job group eu of wave 2 (prod) of migrations","forgeEnvironment":"prod-eu","branches":["main"]}'
|
|
194
|
+
run: '(cd / && node -e ''"use strict";const E=process.env,A="chant-check-job";const shown=(v)=>v===undefined||v===null||v===""?"none":v;const fail=(m)=>{console.error("check-job: "+m);process.exit(9)};let x;try{x=JSON.parse(E.CHANT_JOB_GUARD||"")}catch(e){fail("CHANT_JOB_GUARD is not JSON")}if(!x||!["github","gitlab","forgejo"].includes(x.forge)||!Array.isArray(x.branches))fail("CHANT_JOB_GUARD is not a guard spec");const tags=(r)=>{if(r===undefined)return undefined;const t=r.trim();if(t.startsWith("[")){try{const v=JSON.parse(t);if(Array.isArray(v))return v.map(String)}catch(e){}}return t===""?[]:t.split(",").map((s)=>s.trim()).filter((s)=>s!=="")};const claimsOf=(tok)=>{const p=tok.split(".")[1];if(!p)throw new Error("the forge\u0027s OIDC token is not a JWT");const c=JSON.parse(Buffer.from(p.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf-8"));if(!c||typeof c!=="object")throw new Error("the forge\u0027s OIDC token has no claims");return c};async function seenOf(){if(x.forge==="gitlab")return{forgeEnvironment:E.CI_ENVIRONMENT_NAME||null,ref:E.CI_COMMIT_REF_NAME,refProtected:E.CI_COMMIT_REF_PROTECTED,runnerTags:tags(E.CI_RUNNER_TAGS)};const s={ref:E.GITHUB_REF};let c;if(x.forge==="github"&&x.forgeEnvironment!==undefined){try{if(!E.ACTIONS_ID_TOKEN_REQUEST_URL||!E.ACTIONS_ID_TOKEN_REQUEST_TOKEN)throw new Error("the job may request no OIDC token (no ACTIONS_ID_TOKEN_REQUEST_URL); it needs the id-token: write permission");const r=await fetch(E.ACTIONS_ID_TOKEN_REQUEST_URL+"&audience="+encodeURIComponent(A),{headers:{authorization:"bearer "+E.ACTIONS_ID_TOKEN_REQUEST_TOKEN}});if(!r.ok)throw new Error("the OIDC token request answered "+r.status);const v=(await r.json()).value;if(typeof v!=="string")throw new Error("the OIDC token request answered no token");c=claimsOf(v);s.forgeEnvironment=typeof c.environment==="string"&&c.environment!==""?c.environment:null}catch(e){s.forgeEnvironmentError=e&&e.message?e.message:String(e)}}const run=c&&typeof c.runner_environment==="string"?c.runner_environment:E.RUNNER_ENVIRONMENT;if(run)s.runnerEnvironment=run;return s}seenOf().then((s)=>{const P=[],C=[],S=[];const fe=x.forgeEnvironment;if(x.forge==="forgejo")S.push("forge environment (Forgejo has none)");else if(fe!==undefined){if(s.forgeEnvironmentError!==undefined)P.push("forge environment: expected "+fe+", and it could not be read: "+s.forgeEnvironmentError);else if(s.forgeEnvironment!==fe)P.push("forge environment: expected "+fe+", found "+shown(s.forgeEnvironment));else C.push("forge environment "+fe)}const b=x.branches.filter((v)=>v!=="");if(b.length===0)P.push("ref: the job names no branch to run from");else if(x.forge==="gitlab"){if(!s.ref||!b.includes(s.ref))P.push("ref: expected "+b.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref);if(s.refProtected!=="true")P.push("ref: expected a protected ref (CI_COMMIT_REF_PROTECTED true), found "+shown(s.refProtected));else C.push("protected ref")}else{const refs=b.map((v)=>"refs/heads/"+v);if(!s.ref||!refs.includes(s.ref))P.push("ref: expected "+refs.join(" or ")+", found "+shown(s.ref));else C.push("ref "+s.ref)}if(x.forge==="gitlab"){const w=x.runnerTags||[];if(w.length>0){const h=s.runnerTags;const miss=w.filter((t)=>!(h||[]).includes(t));if(h===undefined)P.push("runner: expected tags "+w.join(", ")+", and the job has no CI_RUNNER_TAGS");else if(miss.length>0)P.push("runner: expected tags "+w.join(", ")+", found "+(h.length?h.join(", "):"none"));else C.push("runner tags "+w.join(", "))}}else if(x.selfHosted){if(s.runnerEnvironment===undefined)S.push("runner (the runner says nothing about itself: no RUNNER_ENVIRONMENT)");else if(s.runnerEnvironment!=="self-hosted")P.push("runner: expected self-hosted, found "+s.runnerEnvironment);else C.push("self-hosted runner")}const at=(x.product||"chant")+" check-job: "+x.job;if(P.length>0){for(const p of P)console.error(at+": "+p);console.error(at+": this job does not run where the spec set it to run, so it stops before any credential is taken or code checked out");process.exit(9)}console.log(at+": "+(C.length?C.join(", "):"nothing to check")+(S.length?"; not checked: "+S.join(", "):""))}).catch((e)=>fail(e&&e.message?e.message:String(e)));'')'
|
|
175
195
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
176
196
|
with:
|
|
177
197
|
fetch-depth: 0
|