@intentius/chant-lexicon-forgejo 0.124.46 → 0.124.48

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.
Files changed (32) hide show
  1. package/dist/actions.d.ts.map +1 -1
  2. package/dist/actions.js +16 -1
  3. package/dist/components/ci-jobs.d.ts.map +1 -1
  4. package/dist/components/ci-jobs.js +1 -0
  5. package/dist/components/generate-pipeline.d.ts +1 -1
  6. package/dist/components/generate-pipeline.js +1 -1
  7. package/dist/dialect.d.ts.map +1 -1
  8. package/dist/dialect.js +5 -1
  9. package/dist/integrity.json +2 -2
  10. package/dist/manifest.json +1 -1
  11. package/dist/plugin.d.ts.map +1 -1
  12. package/dist/plugin.js +2 -1
  13. package/package.json +3 -3
  14. package/src/actions.ts +18 -1
  15. package/src/components/__fixtures__/pr-loop-build.forgejo.golden.yml +8 -9
  16. package/src/components/__fixtures__/pr-loop-record.forgejo.golden.yml +4 -5
  17. package/src/components/__fixtures__/pr-loop-waves.forgejo.golden.yml +12 -13
  18. package/src/components/__fixtures__/pr-loop.forgejo.golden.yml +8 -9
  19. package/src/components/__golden__/op-waves-jobs.yml +48 -7
  20. package/src/components/action-pins.test.ts +54 -0
  21. package/src/components/ci-jobs.ts +1 -0
  22. package/src/components/generate-op-pipeline.review.test.ts +63 -0
  23. package/src/components/generate-op-pipeline.test.ts +3 -2
  24. package/src/components/generate-pipeline.test.ts +2 -1
  25. package/src/components/generate-pipeline.ts +1 -1
  26. package/src/components/generate-pr-pipeline.locks.test.ts +30 -0
  27. package/src/components/generate-pr-pipeline.test.ts +9 -7
  28. package/src/components/generate-pr-waves-pipeline.test.ts +2 -1
  29. package/src/dialect.ts +4 -1
  30. package/src/mcp/context-tools.test.ts +2 -1
  31. package/src/plugin.ts +2 -1
  32. package/src/serializer.test.ts +4 -3
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,kFAAkF;AAClF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAkBtD,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAaD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CA4BrG"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,6BAA6B,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,kFAAkF;AAClF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAkBtD,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAaD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CA4BrG"}
package/dist/actions.js CHANGED
@@ -9,6 +9,7 @@
9
9
  * The resolver rewrites a built-in set of common actions and reports anything it
10
10
  * can't place as a diagnostic (never dropping or silently passing it through).
11
11
  */
12
+ import { pinAtMajor, pinMajor } from "@intentius/chant/action-pins";
12
13
  /**
13
14
  * Default actions root. `code.forgejo.org` hosts the Forgejo `actions/*` mirror
14
15
  * (checkout, setup-*, cache, upload/download-artifact, …). Override per project
@@ -75,10 +76,24 @@ export function resolveActionRef(ref, options = {}) {
75
76
  }
76
77
  const base = target.kind === "mirror" ? `${root}/${target.repo}` : target.url;
77
78
  const withSubpath = subpath ? `${base}/${subpath}` : base;
78
- const pinned = target.kind === "mirror" && target.newest && laterMajor(version, target.newest) ? target.newest : version;
79
+ const pinned = target.kind === "mirror" && target.newest ? olderPin(version, target.newest) : version;
79
80
  const rewritten = pinned ? `${withSubpath}@${pinned}` : withSubpath;
80
81
  return { rewritten };
81
82
  }
83
+ /**
84
+ * `version` held to `newest`: a tag of a later major becomes `newest`, and a
85
+ * pinned commit of a later major becomes the commit of `newest` in chant's pin
86
+ * table (the mirror carries the same commits as GitHub's). Anything else,
87
+ * including a commit the table does not know, passes through.
88
+ */
89
+ function olderPin(version, newest) {
90
+ if (/^[0-9a-f]{40}$/.test(version)) {
91
+ const major = pinMajor(version);
92
+ const target = Number(newest.replace(/^v/, ""));
93
+ return major !== undefined && major > target ? (pinAtMajor(version, newest)?.sha ?? version) : version;
94
+ }
95
+ return laterMajor(version, newest) ? newest : version;
96
+ }
82
97
  /** Whether `version` (`v4`, `v4.1.0`) names a later major than `newest` (`v3`). A SHA or branch never does. */
83
98
  function laterMajor(version, newest) {
84
99
  const major = (v) => {
@@ -1 +1 @@
1
- {"version":3,"file":"ci-jobs.d.ts","sourceRoot":"","sources":["../../src/components/ci-jobs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iEAAiE,CAAC;AAC3G,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEjF,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAElH;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ5F;AAED,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1E;AAED,mHAAmH;AACnH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAOlF;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe;IAAK,iBAAiB;CAAqC,CAAC;AAExF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAYnI"}
1
+ {"version":3,"file":"ci-jobs.d.ts","sourceRoot":"","sources":["../../src/components/ci-jobs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iEAAiE,CAAC;AAC3G,OAAO,EAA2B,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEjF,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAElH;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ5F;AAED,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1E;AAED,mHAAmH;AACnH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAOlF;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe;IAAK,iBAAiB;CAAqC,CAAC;AAExF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,mBAAmB,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAanI"}
@@ -67,5 +67,6 @@ export function forgejoWorkflowDoc(doc, dialect, header) {
67
67
  permissions: {},
68
68
  jobsDoc: forgejoize(withoutEnvironment(doc.jobsDoc), dialect),
69
69
  ...(doc.gatedNoticeDoc ? { gatedNoticeDoc: forgejoize(doc.gatedNoticeDoc, dialect) } : {}),
70
+ ...(doc.reviewNoteDoc ? { reviewNoteDoc: forgejoize(doc.reviewNoteDoc, dialect) } : {}),
70
71
  };
71
72
  }
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * - runner labels: `runs-on: ubuntu-latest` → the project's Forgejo label
16
16
  * (default `docker`, the label a fresh `act_runner` exposes);
17
- * - `uses:` refs (`actions/checkout@v4`, `actions/upload-artifact@v4`, …)
17
+ * - `uses:` refs (`actions/checkout`, `actions/upload-artifact`, ...), each pinned by commit SHA,
18
18
  * rewritten to a Forgejo-resolvable form;
19
19
  * - keys the Forgejo runner ignores (`continue-on-error`, and `permissions` bar `id-token`)
20
20
  * dropped (`permissions` keeps only `id-token`, which OIDC needs).
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * - runner labels: `runs-on: ubuntu-latest` → the project's Forgejo label
16
16
  * (default `docker`, the label a fresh `act_runner` exposes);
17
- * - `uses:` refs (`actions/checkout@v4`, `actions/upload-artifact@v4`, …)
17
+ * - `uses:` refs (`actions/checkout`, `actions/upload-artifact`, ...), each pinned by commit SHA,
18
18
  * rewritten to a Forgejo-resolvable form;
19
19
  * - keys the Forgejo runner ignores (`continue-on-error`, and `permissions` bar `id-token`)
20
20
  * dropped (`permissions` keeps only `id-token`, which OIDC needs).
@@ -1 +1 @@
1
- {"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AA4ClG;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKxD,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,iEAAiE;IACjE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA4GD,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,qBAA0B,GAClC,qBAAqB,CAKvB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAWtB"}
1
+ {"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AA6ClG;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKxD,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,iEAAiE;IACjE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA8GD,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,qBAA0B,GAClC,qBAAqB,CAKvB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAWtB"}
package/dist/dialect.js CHANGED
@@ -27,6 +27,7 @@
27
27
  // be `true`, so the two disagreed about a value carrying `false`. Core's also
28
28
  // accepts an entity a host built, which a local identity check cannot.
29
29
  import { isDeclarable, isResourceDeclarable } from "@intentius/chant/declarable";
30
+ import { ACTION_PINS } from "@intentius/chant/action-pins";
30
31
  import { resolveActionRef } from "./actions.js";
31
32
  /**
32
33
  * Keys the Forgejo runner ignores. Emitting them is misleading (they look
@@ -149,7 +150,10 @@ const V4_ONLY_UPLOAD_INPUTS = new Set(["include-hidden-files", "overwrite"]);
149
150
  function dropV4OnlyArtifactInputs(step) {
150
151
  const uses = step.uses;
151
152
  const withs = step.with;
152
- if (typeof uses !== "string" || !/\/actions\/upload-artifact@v3$/.test(uses))
153
+ if (typeof uses !== "string" || !/\/actions\/upload-artifact@(v3|[0-9a-f]{40})$/.test(uses))
154
+ return;
155
+ const at = uses.slice(uses.lastIndexOf("@") + 1);
156
+ if (at !== "v3" && at !== ACTION_PINS.uploadArtifact3.sha)
153
157
  return;
154
158
  if (withs === null || typeof withs !== "object" || Array.isArray(withs))
155
159
  return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "bab8105ec4c4a11121357a47b4e9354aca5f997e2b424a2e3875cae490402ddb",
4
+ "manifest.json": "7c92f5067d6618de827a23091ad2f9af240116d15538747fb85788b8e8def0cd",
5
5
  "meta.json": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
6
6
  "types/index.d.ts": "66204549b2a864ab5489e3f02c1878e73ab4b3c9d938cfb480b66f71543a43de",
7
7
  "rules/delegate-to-github.ts": "1060cda40f4b73d6cca3ba3fa13b80b4886147d4034c88584c2d80c5510dbe5e",
@@ -9,5 +9,5 @@
9
9
  "rules/wfj011.ts": "fcb8bf6685d744af20ad804ce929488257995c74b9a3353b2eacae3768d1c83e",
10
10
  "skills/chant-forgejo.md": "f28d7fb69fb1cfd992496ef41fda0ea5ccd54a29b84ea1c501a44f6a3992c4d5"
11
11
  },
12
- "composite": "8cc7f304df8b87f97c43a7ab871e160469064649b8d6206ae7d4f8be93035a47"
12
+ "composite": "82b94434307fc1e1a0ebc3522eb258fb67afdbf00f2e0ea55c22ef397d4bbf87"
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgejo",
3
- "version": "0.124.46",
3
+ "version": "0.124.48",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Forgejo",
6
6
  "intrinsics": [],
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAoC,MAAM,0BAA0B,CAAC;AAqBhG,eAAO,MAAM,aAAa,EAAE,aAsM3B,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAoC,MAAM,0BAA0B,CAAC;AAqBhG,eAAO,MAAM,aAAa,EAAE,aAuM3B,CAAC"}
package/dist/plugin.js CHANGED
@@ -118,7 +118,8 @@ export const build = new Job({
118
118
  async transform(content, opts) {
119
119
  const { transform } = await import("./migrate/from-github/index.js");
120
120
  const result = await transform(content, {
121
- emit: opts.emit,
121
+ // "sql" is for sql targets (#4399); chant migrate refuses it for this pair.
122
+ emit: opts.emit === "sql" ? undefined : opts.emit,
122
123
  sourceFile: opts.sourceFile,
123
124
  strict: opts.strict,
124
125
  security: opts.security,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-forgejo",
3
- "version": "0.124.46",
3
+ "version": "0.124.48",
4
4
  "description": "Forgejo / Codeberg / Gitea Actions lexicon for chant — a thin GitHub Actions dialect",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -54,8 +54,8 @@
54
54
  },
55
55
  "peerDependencies": {
56
56
  "zod": "^4.3.6",
57
- "@intentius/chant": "^0.124.46",
58
- "@intentius/chant-lexicon-github": "^0.124.46"
57
+ "@intentius/chant": "^0.124.48",
58
+ "@intentius/chant-lexicon-github": "^0.124.48"
59
59
  },
60
60
  "scripts": {
61
61
  "generate": "tsx src/codegen/generate-cli.ts",
package/src/actions.ts CHANGED
@@ -10,6 +10,8 @@
10
10
  * can't place as a diagnostic (never dropping or silently passing it through).
11
11
  */
12
12
 
13
+ import { pinAtMajor, pinMajor } from "@intentius/chant/action-pins";
14
+
13
15
  /**
14
16
  * Default actions root. `code.forgejo.org` hosts the Forgejo `actions/*` mirror
15
17
  * (checkout, setup-*, cache, upload/download-artifact, …). Override per project
@@ -118,11 +120,26 @@ export function resolveActionRef(ref: string, options: ActionResolveOptions = {}
118
120
 
119
121
  const base = target.kind === "mirror" ? `${root}/${target.repo}` : target.url;
120
122
  const withSubpath = subpath ? `${base}/${subpath}` : base;
121
- const pinned = target.kind === "mirror" && target.newest && laterMajor(version, target.newest) ? target.newest : version;
123
+ const pinned = target.kind === "mirror" && target.newest ? olderPin(version, target.newest) : version;
122
124
  const rewritten = pinned ? `${withSubpath}@${pinned}` : withSubpath;
123
125
  return { rewritten };
124
126
  }
125
127
 
128
+ /**
129
+ * `version` held to `newest`: a tag of a later major becomes `newest`, and a
130
+ * pinned commit of a later major becomes the commit of `newest` in chant's pin
131
+ * table (the mirror carries the same commits as GitHub's). Anything else,
132
+ * including a commit the table does not know, passes through.
133
+ */
134
+ function olderPin(version: string, newest: string): string {
135
+ if (/^[0-9a-f]{40}$/.test(version)) {
136
+ const major = pinMajor(version);
137
+ const target = Number(newest.replace(/^v/, ""));
138
+ return major !== undefined && major > target ? (pinAtMajor(version, newest)?.sha ?? version) : version;
139
+ }
140
+ return laterMajor(version, newest) ? newest : version;
141
+ }
142
+
126
143
  /** Whether `version` (`v4`, `v4.1.0`) names a later major than `newest` (`v3`). A SHA or branch never does. */
127
144
  function laterMajor(version: string, newest: string): boolean {
128
145
  const major = (v: string): number | undefined => {
@@ -68,7 +68,6 @@ jobs:
68
68
  env:
69
69
  BASE_SHA: '${{ github.event.pull_request.base.sha }}'
70
70
  PR_NUMBER: '${{ github.event.pull_request.number }}'
71
- CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
72
71
  run: |
73
72
  if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
74
73
  chant_report="$PWD/.chant/pr"
@@ -83,7 +82,7 @@ jobs:
83
82
  container: node:22
84
83
  steps:
85
84
  - name: Check out the target branch's commit
86
- uses: https://code.forgejo.org/actions/checkout@v7
85
+ uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
87
86
  with:
88
87
  ref: '${{ github.event.pull_request.base.sha }}'
89
88
  - name: Take the plan report from the plan job
@@ -97,7 +96,7 @@ jobs:
97
96
  END_OF_REPORT
98
97
  - name: Keep the plan report
99
98
  if: always()
100
- uses: https://code.forgejo.org/actions/upload-artifact@v3
99
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
101
100
  with:
102
101
  name: chant-pr-plan
103
102
  path: .chant/pr
@@ -118,7 +117,7 @@ jobs:
118
117
  group: chant-record-prod
119
118
  cancel-in-progress: false
120
119
  steps:
121
- - uses: https://code.forgejo.org/actions/checkout@v7
120
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
122
121
  with:
123
122
  fetch-depth: 0
124
123
  - run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
@@ -136,7 +135,7 @@ jobs:
136
135
  chant ci run record --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge forgejo
137
136
  - name: Keep the record report
138
137
  if: always()
139
- uses: https://code.forgejo.org/actions/upload-artifact@v3
138
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
140
139
  with:
141
140
  name: chant-pr-record
142
141
  path: .chant/pr
@@ -149,7 +148,7 @@ jobs:
149
148
  group: chant-apply-prod
150
149
  cancel-in-progress: false
151
150
  steps:
152
- - uses: https://code.forgejo.org/actions/checkout@v7
151
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
153
152
  with:
154
153
  fetch-depth: 0
155
154
  - run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
@@ -158,7 +157,7 @@ jobs:
158
157
  - name: Lint the project
159
158
  run: chant lint
160
159
  - name: Restore the record of an earlier attempt at this commit
161
- uses: https://code.forgejo.org/actions/cache/restore@v4
160
+ uses: https://code.forgejo.org/actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
162
161
  with:
163
162
  path: .chant/pr-resume/pr-apply.json
164
163
  key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
@@ -172,13 +171,13 @@ jobs:
172
171
  chant ci run apply --in-place --env prod --gate pr-apply --forge forgejo
173
172
  - name: Keep the record for a re-run
174
173
  if: always()
175
- uses: https://code.forgejo.org/actions/cache/save@v4
174
+ uses: https://code.forgejo.org/actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
176
175
  with:
177
176
  path: .chant/pr-resume/pr-apply.json
178
177
  key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
179
178
  - name: Keep the apply report
180
179
  if: always()
181
- uses: https://code.forgejo.org/actions/upload-artifact@v3
180
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
182
181
  with:
183
182
  name: chant-pr-apply
184
183
  path: .chant/pr
@@ -58,7 +58,6 @@ jobs:
58
58
  env:
59
59
  BASE_SHA: '${{ github.event.pull_request.base.sha }}'
60
60
  PR_NUMBER: '${{ github.event.pull_request.number }}'
61
- CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
62
61
  run: |
63
62
  if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
64
63
  chant_report="$PWD/.chant/pr"
@@ -73,7 +72,7 @@ jobs:
73
72
  container: node:22
74
73
  steps:
75
74
  - name: Check out the target branch's commit
76
- uses: https://code.forgejo.org/actions/checkout@v7
75
+ uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
77
76
  with:
78
77
  ref: '${{ github.event.pull_request.base.sha }}'
79
78
  - name: Take the plan report from the plan job
@@ -87,7 +86,7 @@ jobs:
87
86
  END_OF_REPORT
88
87
  - name: Keep the plan report
89
88
  if: always()
90
- uses: https://code.forgejo.org/actions/upload-artifact@v3
89
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
91
90
  with:
92
91
  name: chant-pr-plan
93
92
  path: .chant/pr
@@ -108,7 +107,7 @@ jobs:
108
107
  group: chant-record-prod
109
108
  cancel-in-progress: false
110
109
  steps:
111
- - uses: https://code.forgejo.org/actions/checkout@v7
110
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
112
111
  with:
113
112
  fetch-depth: 0
114
113
  - run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
@@ -122,7 +121,7 @@ jobs:
122
121
  chant ci run record --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge forgejo
123
122
  - name: Keep the record report
124
123
  if: always()
125
- uses: https://code.forgejo.org/actions/upload-artifact@v3
124
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
126
125
  with:
127
126
  name: chant-pr-record
128
127
  path: .chant/pr
@@ -60,7 +60,6 @@ jobs:
60
60
  env:
61
61
  BASE_SHA: '${{ github.event.pull_request.base.sha }}'
62
62
  PR_NUMBER: '${{ github.event.pull_request.number }}'
63
- CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
64
63
  run: |
65
64
  if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
66
65
  chant_report="$PWD/.chant/pr"
@@ -75,7 +74,7 @@ jobs:
75
74
  container: node:22
76
75
  steps:
77
76
  - name: Check out the target branch's commit
78
- uses: https://code.forgejo.org/actions/checkout@v7
77
+ uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
79
78
  with:
80
79
  ref: '${{ github.event.pull_request.base.sha }}'
81
80
  - name: Take the plan report from the plan job
@@ -89,7 +88,7 @@ jobs:
89
88
  END_OF_REPORT
90
89
  - name: Keep the plan report
91
90
  if: always()
92
- uses: https://code.forgejo.org/actions/upload-artifact@v3
91
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
93
92
  with:
94
93
  name: chant-pr-plan
95
94
  path: .chant/pr
@@ -110,7 +109,7 @@ jobs:
110
109
  group: chant-record-prod
111
110
  cancel-in-progress: false
112
111
  steps:
113
- - uses: https://code.forgejo.org/actions/checkout@v7
112
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
114
113
  with:
115
114
  fetch-depth: 0
116
115
  - run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
@@ -124,7 +123,7 @@ jobs:
124
123
  chant ci run record --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge forgejo
125
124
  - name: Keep the record report
126
125
  if: always()
127
- uses: https://code.forgejo.org/actions/upload-artifact@v3
126
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
128
127
  with:
129
128
  name: chant-pr-record
130
129
  path: .chant/pr
@@ -139,12 +138,12 @@ jobs:
139
138
  outputs:
140
139
  green-sha: '${{ steps.apply.outputs.green-sha }}'
141
140
  steps:
142
- - uses: https://code.forgejo.org/actions/checkout@v7
141
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
143
142
  with:
144
143
  fetch-depth: 0
145
144
  - run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
146
145
  - name: Restore the newest record of this commit's apply
147
- uses: https://code.forgejo.org/actions/cache/restore@v4
146
+ uses: https://code.forgejo.org/actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
148
147
  with:
149
148
  path: .chant/pr-resume/pr-apply.json
150
149
  key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-1
@@ -160,13 +159,13 @@ jobs:
160
159
  chant ci run apply --in-place --env prod --gate pr-apply --forge forgejo --wave-gate release --wave 1
161
160
  - name: Keep the record for the next wave and a re-run
162
161
  if: always()
163
- uses: https://code.forgejo.org/actions/cache/save@v4
162
+ uses: https://code.forgejo.org/actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
164
163
  with:
165
164
  path: .chant/pr-resume/pr-apply.json
166
165
  key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-1
167
166
  - name: Keep the wave 1 apply report
168
167
  if: always()
169
- uses: https://code.forgejo.org/actions/upload-artifact@v3
168
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
170
169
  with:
171
170
  name: chant-pr-apply-wave-1
172
171
  path: .chant/pr
@@ -183,12 +182,12 @@ jobs:
183
182
  outputs:
184
183
  green-sha: '${{ steps.apply.outputs.green-sha }}'
185
184
  steps:
186
- - uses: https://code.forgejo.org/actions/checkout@v7
185
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
187
186
  with:
188
187
  fetch-depth: 0
189
188
  - run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
190
189
  - name: Restore the newest record of this commit's apply
191
- uses: https://code.forgejo.org/actions/cache/restore@v4
190
+ uses: https://code.forgejo.org/actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
192
191
  with:
193
192
  path: .chant/pr-resume/pr-apply.json
194
193
  key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-2
@@ -205,13 +204,13 @@ jobs:
205
204
  chant ci run apply --in-place --env prod --gate pr-apply --forge forgejo --wave-gate release --wave 2
206
205
  - name: Keep the record for the next wave and a re-run
207
206
  if: always()
208
- uses: https://code.forgejo.org/actions/cache/save@v4
207
+ uses: https://code.forgejo.org/actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
209
208
  with:
210
209
  path: .chant/pr-resume/pr-apply.json
211
210
  key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-wave-2
212
211
  - name: Keep the wave 2 apply report
213
212
  if: always()
214
- uses: https://code.forgejo.org/actions/upload-artifact@v3
213
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
215
214
  with:
216
215
  name: chant-pr-apply-wave-2
217
216
  path: .chant/pr
@@ -60,7 +60,6 @@ jobs:
60
60
  env:
61
61
  BASE_SHA: '${{ github.event.pull_request.base.sha }}'
62
62
  PR_NUMBER: '${{ github.event.pull_request.number }}'
63
- CHANT_RUNNER_KEY: '${{ secrets.CHANT_RUNNER_KEY }}'
64
63
  run: |
65
64
  if [ -z "${CHANT_NO_FORGE_TOKEN:-}" ] && [ "${0#*/}" != "$0" ] && [ -f "$0" ] && ! { [ "${0##*/}" = sh ] || [ "${0##*/}" = bash ] || [ "${0##*/}" = dash ]; } && command -v env >/dev/null 2>&1 && command -v sh >/dev/null 2>&1; then exec env -u CHANT_FORGE_TOKEN -u CHANT_GITHUB_TOKEN -u CHANT_FORGEJO_TOKEN -u CHANT_GITLAB_TOKEN -u CHANT_CHECKOUT_TOKEN -u GITHUB_TOKEN -u GH_TOKEN -u FORGEJO_TOKEN -u GITEA_TOKEN -u GITLAB_TOKEN -u CI_JOB_TOKEN -u ACTIONS_RUNTIME_TOKEN CHANT_NO_FORGE_TOKEN=1 sh -e "$0"; fi; unset CHANT_FORGE_TOKEN CHANT_GITHUB_TOKEN CHANT_FORGEJO_TOKEN CHANT_GITLAB_TOKEN CHANT_CHECKOUT_TOKEN GITHUB_TOKEN GH_TOKEN FORGEJO_TOKEN GITEA_TOKEN GITLAB_TOKEN CI_JOB_TOKEN ACTIONS_RUNTIME_TOKEN
66
65
  chant_report="$PWD/.chant/pr"
@@ -75,7 +74,7 @@ jobs:
75
74
  container: node:22
76
75
  steps:
77
76
  - name: Check out the target branch's commit
78
- uses: https://code.forgejo.org/actions/checkout@v7
77
+ uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
79
78
  with:
80
79
  ref: '${{ github.event.pull_request.base.sha }}'
81
80
  - name: Take the plan report from the plan job
@@ -89,7 +88,7 @@ jobs:
89
88
  END_OF_REPORT
90
89
  - name: Keep the plan report
91
90
  if: always()
92
- uses: https://code.forgejo.org/actions/upload-artifact@v3
91
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
93
92
  with:
94
93
  name: chant-pr-plan
95
94
  path: .chant/pr
@@ -110,7 +109,7 @@ jobs:
110
109
  group: chant-record-prod
111
110
  cancel-in-progress: false
112
111
  steps:
113
- - uses: https://code.forgejo.org/actions/checkout@v7
112
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
114
113
  with:
115
114
  fetch-depth: 0
116
115
  - run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
@@ -124,7 +123,7 @@ jobs:
124
123
  chant ci run record --in-place --base "$BASE_SHA" --env prod --gate pr-apply --forge forgejo
125
124
  - name: Keep the record report
126
125
  if: always()
127
- uses: https://code.forgejo.org/actions/upload-artifact@v3
126
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
128
127
  with:
129
128
  name: chant-pr-record
130
129
  path: .chant/pr
@@ -137,12 +136,12 @@ jobs:
137
136
  group: chant-apply-prod
138
137
  cancel-in-progress: false
139
138
  steps:
140
- - uses: https://code.forgejo.org/actions/checkout@v7
139
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
141
140
  with:
142
141
  fetch-depth: 0
143
142
  - run: command -v tofu >/dev/null 2>&1 || (curl -fsSL https://get.opentofu.org/install-opentofu.sh -o /tmp/install-opentofu.sh && sh /tmp/install-opentofu.sh --install-method standalone)
144
143
  - name: Restore the record of an earlier attempt at this commit
145
- uses: https://code.forgejo.org/actions/cache/restore@v4
144
+ uses: https://code.forgejo.org/actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
146
145
  with:
147
146
  path: .chant/pr-resume/pr-apply.json
148
147
  key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
@@ -156,13 +155,13 @@ jobs:
156
155
  chant ci run apply --in-place --env prod --gate pr-apply --forge forgejo
157
156
  - name: Keep the record for a re-run
158
157
  if: always()
159
- uses: https://code.forgejo.org/actions/cache/save@v4
158
+ uses: https://code.forgejo.org/actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
160
159
  with:
161
160
  path: .chant/pr-resume/pr-apply.json
162
161
  key: chant-apply-prod-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
163
162
  - name: Keep the apply report
164
163
  if: always()
165
- uses: https://code.forgejo.org/actions/upload-artifact@v3
164
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
166
165
  with:
167
166
  name: chant-pr-apply
168
167
  path: .chant/pr
@@ -15,29 +15,48 @@ jobs:
15
15
  runs-on: plan
16
16
  container: node:22
17
17
  steps:
18
- - uses: https://code.forgejo.org/actions/checkout@v7
18
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
19
  with:
20
20
  fetch-depth: 0
21
21
  - name: Plan, gate and apply wave 1 (dev)
22
22
  run: chant run wave --spec waves.json --wave 1
23
+ - name: Keep the job's outcome
24
+ if: always()
25
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
26
+ with:
27
+ name: migrations-wave-1-dev-outcome
28
+ path: .chant/op-waves/migrations/outcomes/outcome-wave-1.json
29
+ if-no-files-found: ignore
23
30
  wave-2-prod-decide:
24
31
  runs-on: plan
25
32
  needs:
26
33
  - wave-1-dev
34
+ if: '!cancelled()'
27
35
  container: node:22
28
36
  steps:
29
- - uses: https://code.forgejo.org/actions/checkout@v7
37
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30
38
  with:
31
39
  fetch-depth: 0
40
+ - name: Download the earlier jobs' outcomes
41
+ uses: https://code.forgejo.org/actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
42
+ with:
43
+ path: .chant/op-waves/migrations/outcomes
32
44
  - name: Plan wave 2 (prod) and decide its gate
33
45
  run: chant run wave --spec waves.json --wave 2 --decide
34
46
  - name: Keep wave 2's decision
35
47
  if: always()
36
- uses: https://code.forgejo.org/actions/upload-artifact@v3
48
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
37
49
  with:
38
50
  name: migrations-wave-2-decision
39
51
  path: .chant/op-waves/migrations/wave-2.json
40
52
  if-no-files-found: ignore
53
+ - name: Keep the job's outcome
54
+ if: always()
55
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
56
+ with:
57
+ name: migrations-wave-2-prod-decide-outcome
58
+ path: .chant/op-waves/migrations/outcomes/outcome-wave-2.json
59
+ if-no-files-found: ignore
41
60
  wave-2-prod-us:
42
61
  runs-on:
43
62
  - self-hosted
@@ -51,29 +70,51 @@ jobs:
51
70
  REGION: us
52
71
  CLOUD_TOKEN_AUDIENCE: us.example
53
72
  steps:
54
- - uses: https://code.forgejo.org/actions/checkout@v7
73
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
55
74
  with:
56
75
  fetch-depth: 0
57
76
  - name: Download wave 2's decision
58
- uses: https://code.forgejo.org/actions/download-artifact@v3
77
+ uses: https://code.forgejo.org/actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
59
78
  with:
60
79
  name: migrations-wave-2-decision
61
80
  path: .chant/op-waves/migrations
81
+ - name: Download the earlier jobs' outcomes
82
+ uses: https://code.forgejo.org/actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
83
+ with:
84
+ path: .chant/op-waves/migrations/outcomes
62
85
  - name: Apply job group us of wave 2 (prod)
63
86
  run: chant run wave --spec waves.json --wave 2 --group us
87
+ - name: Keep the job's outcome
88
+ if: always()
89
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
90
+ with:
91
+ name: migrations-wave-2-prod-us-outcome
92
+ path: .chant/op-waves/migrations/outcomes/outcome-wave-2-group-us.json
93
+ if-no-files-found: ignore
64
94
  wave-2-prod-eu:
65
95
  runs-on: eu-apply
66
96
  needs:
67
97
  - wave-2-prod-decide
68
98
  container: node:22
69
99
  steps:
70
- - uses: https://code.forgejo.org/actions/checkout@v7
100
+ - uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
71
101
  with:
72
102
  fetch-depth: 0
73
103
  - name: Download wave 2's decision
74
- uses: https://code.forgejo.org/actions/download-artifact@v3
104
+ uses: https://code.forgejo.org/actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
75
105
  with:
76
106
  name: migrations-wave-2-decision
77
107
  path: .chant/op-waves/migrations
108
+ - name: Download the earlier jobs' outcomes
109
+ uses: https://code.forgejo.org/actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
110
+ with:
111
+ path: .chant/op-waves/migrations/outcomes
78
112
  - name: Apply job group eu of wave 2 (prod)
79
113
  run: chant run wave --spec waves.json --wave 2 --group eu
114
+ - name: Keep the job's outcome
115
+ if: always()
116
+ uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.2.1
117
+ with:
118
+ name: migrations-wave-2-prod-eu-outcome
119
+ path: .chant/op-waves/migrations/outcomes/outcome-wave-2-group-eu.json
120
+ if-no-files-found: ignore
@@ -0,0 +1,54 @@
1
+ /**
2
+ * #4403: no workflow chant generates names an action by tag or branch, and no
3
+ * generator's source does either outside the pin table.
4
+ */
5
+ import { readFileSync, readdirSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { describe, expect, it } from "vitest";
8
+ import type { DriverComponent } from "@intentius/chant/components/driver";
9
+ import { ACTION_PINS, unpinnedActions } from "@intentius/chant/action-pins";
10
+ import { generateGithubPipeline } from "@intentius/chant-lexicon-github/components/generate-pipeline";
11
+ import { generateForgejoPipeline } from "./generate-pipeline";
12
+
13
+ const ESTATE: DriverComponent[] = [
14
+ { name: "net", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: "net" }] }] },
15
+ { name: "app", dependsOn: ["net"], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: "app" }] }] },
16
+ ];
17
+
18
+ const root = join(import.meta.dirname, "../../../..");
19
+
20
+ describe("the actions a generated workflow uses", () => {
21
+ const variants = [{ env: "prod" }, { env: "prod", prLoop: { applyOnMerge: true } }, { env: "prod", prLoop: {} }] as const;
22
+ for (const [forge, gen] of [["github", generateGithubPipeline], ["forgejo", generateForgejoPipeline]] as const) {
23
+ it(`are each pinned by commit SHA, with the release beside it, on ${forge}`, () => {
24
+ for (const options of variants) {
25
+ const yaml = gen(ESTATE, options as never).yaml;
26
+ expect(unpinnedActions(yaml)).toEqual([]);
27
+ expect(yaml).toMatch(new RegExp(`uses: \\S*actions/checkout@${ACTION_PINS.checkout7.sha} # ${ACTION_PINS.checkout7.tag}$`, "m"));
28
+ }
29
+ });
30
+ }
31
+
32
+ it("are pinned in the committed workflow chant ci workflow wrote", () => {
33
+ const committed = readFileSync(join(root, ".github/workflows/chant-ci-green.yml"), "utf8");
34
+ expect(unpinnedActions(committed)).toEqual([]);
35
+ expect(committed).toMatch(new RegExp(`uses: actions/checkout@${ACTION_PINS.checkout6.sha} # ${ACTION_PINS.checkout6.tag}$`, "m"));
36
+ });
37
+
38
+ it("name no action by tag or branch in a generator's source, outside the pin table", () => {
39
+ const dirs = ["lexicons/github/src/components", "lexicons/forgejo/src/components", "packages/core/src/components"].map((d) => join(root, d));
40
+ const files = [
41
+ ...dirs.flatMap((d) => readdirSync(d).filter((n) => n.endsWith(".ts") && !n.endsWith(".test.ts")).map((n) => join(d, n))),
42
+ join(root, "packages/core/src/workspace/ci-green-workflow.ts"),
43
+ join(root, "packages/core/src/workspace/release-workflow.ts"),
44
+ ];
45
+ const found: string[] = [];
46
+ for (const f of files) {
47
+ for (const m of readFileSync(f, "utf8").matchAll(/["'`]((?:https:\/\/[\w.]+\/)?[\w-]+\/[\w./-]+@[\w.-]+)["'`]/g)) {
48
+ if (!/@[0-9a-f]{40}$/.test(m[1])) found.push(`${f.slice(root.length + 1)}: ${m[1]}`);
49
+ }
50
+ }
51
+ expect(files.length).toBeGreaterThan(10);
52
+ expect(found).toEqual([]);
53
+ });
54
+ });
@@ -76,5 +76,6 @@ export function forgejoWorkflowDoc(doc: GithubOpPipelineDoc, dialect: ForgejoDia
76
76
  permissions: {},
77
77
  jobsDoc: forgejoize(withoutEnvironment(doc.jobsDoc), dialect),
78
78
  ...(doc.gatedNoticeDoc ? { gatedNoticeDoc: forgejoize(doc.gatedNoticeDoc, dialect) } : {}),
79
+ ...(doc.reviewNoteDoc ? { reviewNoteDoc: forgejoize(doc.reviewNoteDoc, dialect) } : {}),
79
80
  };
80
81
  }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The split review on Forgejo (#4326): the github shape under the Forgejo
3
+ * dialect. Forgejo ignores `permissions:`, so the review job's safety is the
4
+ * token variables the step drops and the note job that holds the token alone.
5
+ */
6
+
7
+ import { describe, expect, test } from "vitest";
8
+ import { parseYAML } from "@intentius/chant/yaml";
9
+ import { CHECKOUT_TOKEN_ENV, checkoutScript, FORGE_TOKEN_VARS } from "@intentius/chant/ci-handover";
10
+ import type { ScheduledOpSpec } from "@intentius/chant/lexicon";
11
+ import { generateForgejoOpPipeline } from "./generate-op-pipeline";
12
+
13
+ interface Step {
14
+ id?: string;
15
+ uses?: string;
16
+ run?: string;
17
+ env?: Record<string, string>;
18
+ }
19
+ interface Job {
20
+ needs?: string;
21
+ if?: string;
22
+ permissions?: unknown;
23
+ outputs?: Record<string, string>;
24
+ steps: Step[];
25
+ }
26
+
27
+ const SPEC: ScheduledOpSpec = { name: "pr-review", trigger: { kind: "pull_request" }, reviewAgent: true };
28
+
29
+ describe("generateForgejoOpPipeline: reviewAgent", () => {
30
+ const out = generateForgejoOpPipeline([SPEC]);
31
+ const doc = parseYAML(out.files[0]!.yaml) as { jobs: Record<string, Job> };
32
+
33
+ test("a review job and a note job, with the handoff between them", () => {
34
+ expect(Object.keys(doc.jobs)).toEqual(["pr-review", "pr-review-note"]);
35
+ expect(doc.jobs["pr-review"]!.outputs).toEqual({ report_0: "${{ steps.chant-review.outputs.report_0 }}" });
36
+ expect(doc.jobs["pr-review-note"]!.needs).toBe("pr-review");
37
+ expect(doc.jobs["pr-review-note"]!.if).toBe("needs.pr-review.result == 'success'");
38
+ });
39
+
40
+ test("no step of the review job but the checkout's fetches names a forge token, and the Op's step drops them", () => {
41
+ for (const step of doc.jobs["pr-review"]!.steps) {
42
+ const named = Object.keys(step.env ?? {}).filter((n) => (FORGE_TOKEN_VARS as readonly string[]).includes(n));
43
+ expect(named).toEqual(step.run?.trimEnd() === checkoutScript(true).trimEnd() ? [CHECKOUT_TOKEN_ENV] : []);
44
+ expect(step.uses ?? "").not.toMatch(/^actions\/checkout/);
45
+ }
46
+ expect(doc.jobs["pr-review"]!.steps.at(-1)!.run).toContain("exec env -u CHANT_FORGE_TOKEN");
47
+ });
48
+
49
+ test("the note job checks nothing out and takes the token from the job", () => {
50
+ const note = doc.jobs["pr-review-note"]!;
51
+ expect(note.steps.some((s) => (s.uses ?? "").includes("checkout"))).toBe(false);
52
+ expect(note.steps[1]!.env).toEqual({ GITHUB_TOKEN: "${{ github.token }}", CHANT_OP: "pr-review" });
53
+ });
54
+
55
+ test("the dialect drops permissions: from both jobs, as it does for every Op", () => {
56
+ expect(doc.jobs["pr-review"]!.permissions).toBeUndefined();
57
+ expect(doc.jobs["pr-review-note"]!.permissions).toBeUndefined();
58
+ });
59
+
60
+ test("the same refusals as github's", () => {
61
+ expect(() => generateForgejoOpPipeline([{ ...SPEC, trigger: { kind: "push" } }])).toThrow(/reviewAgent/);
62
+ });
63
+ });
@@ -11,6 +11,7 @@ import { parseYAML } from "@intentius/chant/yaml";
11
11
  import { generateForgejoOpPipeline } from "./generate-op-pipeline";
12
12
  import { generateGithubOpPipeline } from "@intentius/chant-lexicon-github/components/generate-op-pipeline";
13
13
  import type { ScheduledOpSpec } from "@intentius/chant/lexicon";
14
+ import { ACTION_PINS, usesRef } from "@intentius/chant/action-pins";
14
15
 
15
16
  interface ParsedJob {
16
17
  needs?: string;
@@ -214,7 +215,7 @@ describe("generateForgejoOpPipeline: setup steps and additive permissions (#2242
214
215
  const steps = doc.jobs!["app-apply"].steps;
215
216
  // The checkout is rewritten to the Forgejo mirror; an action with no
216
217
  // mapping in ../actions.ts passes through verbatim.
217
- expect(steps[0].uses).toContain("actions/checkout@v7");
218
+ expect(steps[0].uses).toContain(`${usesRef(ACTION_PINS.checkout7)}`);
218
219
  expect(steps[1].uses).toBe("aws-actions/configure-aws-credentials@v6");
219
220
  expect((steps[1] as { with?: Record<string, string> }).with).toEqual({
220
221
  "role-to-assume": "${{ vars.AWS_ROLE_ARN }}",
@@ -336,7 +337,7 @@ describe("generateForgejoOpPipeline: a dropped deployment environment (#2257)",
336
337
  " runs-on: docker",
337
338
  " container: node:22",
338
339
  " steps:",
339
- " - uses: https://code.forgejo.org/actions/checkout@v7",
340
+ ` - uses: https://code.forgejo.org/${usesRef(ACTION_PINS.checkout7)} # ${ACTION_PINS.checkout7.tag}`,
340
341
  " - run: chant run actions-audit",
341
342
  " env:",
342
343
  " GITHUB_TOKEN: '${{ github.token }}'",
@@ -16,6 +16,7 @@ import { generateGithubPipeline } from "@intentius/chant-lexicon-github/componen
16
16
  import { type DriverComponent } from "@intentius/chant/components/driver";
17
17
  import { searchService } from "@intentius/chant/components/pilots/alb-ecs.pilot";
18
18
  import { ordersTable } from "@intentius/chant/components/pilots/dynamodb.pilot";
19
+ import { ACTION_PINS, usesRef } from "@intentius/chant/action-pins";
19
20
 
20
21
  function pilotComponents(): DriverComponent[] {
21
22
  return [
@@ -110,7 +111,7 @@ describe("generateForgejoPipeline: dialect applied", () => {
110
111
  expect(ref).toContain("code.forgejo.org/actions/");
111
112
  }
112
113
  // checkout is present and mirrored.
113
- expect(all.some((r) => r === "https://code.forgejo.org/actions/checkout@v7")).toBe(true);
114
+ expect(all.some((r) => r === `https://code.forgejo.org/${usesRef(ACTION_PINS.checkout7)}`)).toBe(true);
114
115
  });
115
116
  });
116
117
 
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * - runner labels: `runs-on: ubuntu-latest` → the project's Forgejo label
16
16
  * (default `docker`, the label a fresh `act_runner` exposes);
17
- * - `uses:` refs (`actions/checkout@v4`, `actions/upload-artifact@v4`, …)
17
+ * - `uses:` refs (`actions/checkout`, `actions/upload-artifact`, ...), each pinned by commit SHA,
18
18
  * rewritten to a Forgejo-resolvable form;
19
19
  * - keys the Forgejo runner ignores (`continue-on-error`, and `permissions` bar `id-token`)
20
20
  * dropped (`permissions` keeps only `id-token`, which OIDC needs).
@@ -0,0 +1,30 @@
1
+ /** The Forgejo pull-request workflow with `ci.locks: plan` (#4327): the GitHub generator's lock and unlock jobs, in the Forgejo dialect. */
2
+
3
+ import { describe, expect, test } from "vitest";
4
+ import { parseYAML } from "@intentius/chant/yaml";
5
+ import type { DriverComponent } from "@intentius/chant/components/driver";
6
+ import { generateForgejoPipeline } from "./generate-pipeline";
7
+
8
+ const ESTATE: DriverComponent[] = [
9
+ { name: "net", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: "net" }] }] },
10
+ ];
11
+
12
+ interface Step { run?: string }
13
+ type Doc = { jobs: Record<string, { if?: string; needs?: string[]; steps: Step[] }> };
14
+
15
+ describe("the Forgejo workflow with ci.locks: plan", () => {
16
+ const doc = parseYAML(generateForgejoPipeline(ESTATE, { env: "prod", prLoop: { locks: "plan" } }).yaml) as Doc;
17
+
18
+ test("writes the lock and unlock jobs for forgejo", () => {
19
+ expect(doc.jobs.lock!.needs).toEqual(["plan"]);
20
+ expect(doc.jobs.lock!.steps.find((s) => s.run?.startsWith("chant lock"))!.run).toContain("--forge forgejo");
21
+ expect(doc.jobs.unlock!.if).toContain("github.event.action == 'closed'");
22
+ expect(doc.jobs.unlock!.steps.some((s) => s.run === 'chant unlock --pr "$PR_NUMBER"')).toBe(true);
23
+ });
24
+
25
+ test("without ci.locks neither job is written", () => {
26
+ const plain = parseYAML(generateForgejoPipeline(ESTATE, { env: "prod", prLoop: {} }).yaml) as Doc;
27
+ expect(Object.keys(plain.jobs)).not.toContain("lock");
28
+ expect(Object.keys(plain.jobs)).not.toContain("unlock");
29
+ });
30
+ });
@@ -17,6 +17,7 @@ import { generateGithubPipeline } from "@intentius/chant-lexicon-github/componen
17
17
  import { githubBaseFallback, greenCheckout } from "@intentius/chant/components/pr-pipeline";
18
18
  import { dropForgeTokens } from "@intentius/chant/ci-handover";
19
19
  import { generateForgejoPipeline } from "./generate-pipeline";
20
+ import { ACTION_PINS, usesRef } from "@intentius/chant/action-pins";
20
21
 
21
22
  const ESTATE: DriverComponent[] = [
22
23
  { name: "net", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: "net" }] }] },
@@ -42,9 +43,9 @@ describe("the Forgejo pull-request workflow", () => {
42
43
  expect(result.yaml).toBe(readFileSync(GOLDEN, "utf-8"));
43
44
  });
44
45
 
45
- test("both jobs run chant ci run with the runner key secret, as on GitHub (#4156)", () => {
46
+ test("the jobs run chant ci run, with the runner key secret on every job but the plan, as on GitHub (#4156, #4328)", () => {
46
47
  expect(result.yaml).toContain("chant ci run plan --in-place --no-push");
47
- expect(result.yaml.match(/CHANT_RUNNER_KEY: '\$\{\{ secrets\.CHANT_RUNNER_KEY \}\}'/g)).toHaveLength(3);
48
+ expect(result.yaml.match(/CHANT_RUNNER_KEY: '\$\{\{ secrets\.CHANT_RUNNER_KEY \}\}'/g)).toHaveLength(2);
48
49
  expect(result.yaml).not.toContain("chant components pr-plan");
49
50
  expect(result.yaml).not.toContain("chant components pr-apply");
50
51
  // #4295: the plan-note job posts with pr-post, to Forgejo.
@@ -84,21 +85,22 @@ describe("the Forgejo pull-request workflow", () => {
84
85
  });
85
86
 
86
87
  test("the apply keeps its resume record in the runner's cache, through the Forgejo mirror of actions/cache (#3543)", () => {
87
- expect(result.yaml).toContain("uses: https://code.forgejo.org/actions/cache/restore@v4");
88
- expect(result.yaml).toContain("uses: https://code.forgejo.org/actions/cache/save@v4");
88
+ expect(result.yaml).toContain(`uses: https://code.forgejo.org/${usesRef(ACTION_PINS.cacheRestore4)}`);
89
+ expect(result.yaml).toContain(`uses: https://code.forgejo.org/${usesRef(ACTION_PINS.cacheSave4)}`);
89
90
  // ci run's inner pr-apply resumes from it (#4156): the job runs in place, where the cache restored it.
90
91
  expect(result.yaml).toContain("chant ci run apply --in-place --env prod");
91
92
  });
92
93
 
93
94
  test("keeps its reports with upload-artifact v3, the version a Forgejo runner runs, while GitHub keeps v4 (#4008)", () => {
94
95
  // v4 refuses a runner that reads as GitHub Enterprise Server, as Forgejo's does.
95
- expect(result.yaml.match(/uses: https:\/\/code\.forgejo\.org\/actions\/upload-artifact@v3$/gm)).toHaveLength(3);
96
- expect(result.yaml).not.toMatch(/(upload|download)-artifact@v4/);
96
+ expect(result.yaml.match(new RegExp(`uses: https://code\\.forgejo\\.org/actions/upload-artifact@${ACTION_PINS.uploadArtifact3.sha} # ${ACTION_PINS.uploadArtifact3.tag}$`, "gm"))).toHaveLength(3);
97
+ expect(result.yaml).not.toContain(ACTION_PINS.uploadArtifact4.sha);
98
+ expect(result.yaml).not.toContain(ACTION_PINS.downloadArtifact4.sha);
97
99
  // v3 uploads hidden files with no input asking for it, so .chant/pr is still kept.
98
100
  expect(result.yaml).not.toContain("include-hidden-files");
99
101
  expect(result.yaml).toContain("path: .chant/pr");
100
102
  const github = generateGithubPipeline(ESTATE, { env: "prod", prLoop: { applyOnMerge: true } }).yaml;
101
- expect(github.match(/uses: actions\/upload-artifact@v4$/gm)).toHaveLength(3);
103
+ expect(github.match(new RegExp(`uses: actions/upload-artifact@${ACTION_PINS.uploadArtifact4.sha} # ${ACTION_PINS.uploadArtifact4.tag}$`, "gm"))).toHaveLength(3);
102
104
  expect(github).toContain("include-hidden-files: true");
103
105
  });
104
106
 
@@ -10,6 +10,7 @@ import { describe, expect, test } from "vitest";
10
10
  import { parseYAML } from "@intentius/chant/yaml";
11
11
  import type { DriverComponent } from "@intentius/chant/components/driver";
12
12
  import { generateForgejoPipeline } from "./generate-pipeline";
13
+ import { ACTION_PINS, usesRef } from "@intentius/chant/action-pins";
13
14
 
14
15
  const root = (name: string, dependsOn: string[] = []): DriverComponent => ({
15
16
  name,
@@ -56,7 +57,7 @@ describe("the Forgejo pull-request workflow with a gate per wave (#4003)", () =>
56
57
  const job = doc.jobs[`apply-wave-${k}`]!;
57
58
  expect(job["runs-on"]).toBe("docker");
58
59
  expect(job.steps.find((s) => s.run?.includes("ci run apply"))!.run).toContain(`--forge forgejo --wave-gate release --wave ${k}`);
59
- expect(job.steps.some((s) => s.uses === "https://code.forgejo.org/actions/cache/restore@v4")).toBe(true);
60
+ expect(job.steps.some((s) => s.uses === `https://code.forgejo.org/${usesRef(ACTION_PINS.cacheRestore4)}`)).toBe(true);
60
61
  }
61
62
  });
62
63
  });
package/src/dialect.ts CHANGED
@@ -28,6 +28,7 @@
28
28
  // be `true`, so the two disagreed about a value carrying `false`. Core's also
29
29
  // accepts an entity a host built, which a local identity check cannot.
30
30
  import { isDeclarable, isResourceDeclarable, type Declarable } from "@intentius/chant/declarable";
31
+ import { ACTION_PINS } from "@intentius/chant/action-pins";
31
32
  import { resolveActionRef } from "./actions";
32
33
 
33
34
  /**
@@ -183,7 +184,9 @@ const V4_ONLY_UPLOAD_INPUTS = new Set(["include-hidden-files", "overwrite"]);
183
184
  function dropV4OnlyArtifactInputs(step: Record<string, unknown>): void {
184
185
  const uses = step.uses;
185
186
  const withs = step.with;
186
- if (typeof uses !== "string" || !/\/actions\/upload-artifact@v3$/.test(uses)) return;
187
+ if (typeof uses !== "string" || !/\/actions\/upload-artifact@(v3|[0-9a-f]{40})$/.test(uses)) return;
188
+ const at = uses.slice(uses.lastIndexOf("@") + 1);
189
+ if (at !== "v3" && at !== ACTION_PINS.uploadArtifact3.sha) return;
187
190
  if (withs === null || typeof withs !== "object" || Array.isArray(withs)) return;
188
191
  const kept = Object.fromEntries(Object.entries(withs as Record<string, unknown>).filter(([k]) => !V4_ONLY_UPLOAD_INPUTS.has(toKebabCase(k))));
189
192
  step.with = kept;
@@ -80,7 +80,8 @@ describe("forgejo context tools", () => {
80
80
  test("forgejo:references reports the rewritten + unmapped refs", async () => {
81
81
  const refs = (await tool("forgejo:references").handler({ path: join(dir, "src") })) as Array<{ source: string }>;
82
82
  const sources = refs.map((r) => r.source);
83
- expect(sources).toContain("https://code.forgejo.org/actions/checkout@v7");
83
+ // Composites pin by commit SHA since #4410.
84
+ expect(sources.some((s: string) => /^https:\/\/code\.forgejo\.org\/actions\/checkout@[0-9a-f]{40}$/.test(s))).toBe(true);
84
85
  expect(sources).toContain("some-org/custom-action@v1");
85
86
  });
86
87
 
package/src/plugin.ts CHANGED
@@ -131,7 +131,8 @@ export const build = new Job({
131
131
  async transform(content: string, opts) {
132
132
  const { transform } = await import("./migrate/from-github");
133
133
  const result = await transform(content, {
134
- emit: opts.emit,
134
+ // "sql" is for sql targets (#4399); chant migrate refuses it for this pair.
135
+ emit: opts.emit === "sql" ? undefined : opts.emit,
135
136
  sourceFile: opts.sourceFile,
136
137
  strict: opts.strict,
137
138
  security: opts.security,
@@ -88,15 +88,16 @@ describe("forgejoSerializer — uses: action resolution", () => {
88
88
  const { primary } = asResult(
89
89
  forgejoSerializer.serialize(withSteps(Checkout({}).step, SetupNode({ nodeVersion: "22" }).step)),
90
90
  );
91
- expect(primary).toContain(`uses: ${DEFAULT_ACTIONS_ROOT}/actions/checkout@v7`);
92
- expect(primary).toContain(`uses: ${DEFAULT_ACTIONS_ROOT}/actions/setup-node@v7`);
91
+ // Composites pin by commit SHA since #4410; the rewrite still puts them under the actions root.
92
+ expect(primary).toMatch(new RegExp(`uses: ${DEFAULT_ACTIONS_ROOT}/actions/checkout@[0-9a-f]{40}`));
93
+ expect(primary).toMatch(new RegExp(`uses: ${DEFAULT_ACTIONS_ROOT}/actions/setup-node@[0-9a-f]{40}`));
93
94
  });
94
95
 
95
96
  test("forgejo.actionsRoot override changes the rewritten base", () => {
96
97
  const out = forgejoSerializer.serialize(withSteps(Checkout({}).step), undefined, {
97
98
  config: { forgejo: { actionsRoot: "https://codeberg.org" } },
98
99
  });
99
- expect(asResult(out).primary).toContain("uses: https://codeberg.org/actions/checkout@v7");
100
+ expect(asResult(out).primary).toMatch(/uses: https:\/\/codeberg\.org\/actions\/checkout@[0-9a-f]{40}/);
100
101
  });
101
102
 
102
103
  test("an unmapped action ref is passed through (WFJ010 reports it post-synth)", () => {