code-foundry 1.53.1 → 1.53.3

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.
@@ -129,8 +129,8 @@ jobs:
129
129
  if-no-files-found: error
130
130
  retention-days: 14
131
131
 
132
- - name: Save Vite+ task cache
133
- uses: ./.github/actions/vite-plus-cache-save
132
+ - name: Save task caches
133
+ uses: ./.github/actions/cache-save
134
134
 
135
135
  lint:
136
136
  name: Lint
@@ -193,8 +193,8 @@ jobs:
193
193
  if-no-files-found: error
194
194
  retention-days: 14
195
195
 
196
- - name: Save Vite+ task cache
197
- uses: ./.github/actions/vite-plus-cache-save
196
+ - name: Save task caches
197
+ uses: ./.github/actions/cache-save
198
198
 
199
199
  type-check:
200
200
  name: Type-Check
@@ -262,8 +262,8 @@ jobs:
262
262
  if-no-files-found: error
263
263
  retention-days: 14
264
264
 
265
- - name: Save Vite+ task cache
266
- uses: ./.github/actions/vite-plus-cache-save
265
+ - name: Save task caches
266
+ uses: ./.github/actions/cache-save
267
267
 
268
268
  build:
269
269
  name: Build
@@ -357,5 +357,5 @@ jobs:
357
357
  if-no-files-found: error
358
358
  retention-days: 14
359
359
 
360
- - name: Save Vite+ task cache
361
- uses: ./.github/actions/vite-plus-cache-save
360
+ - name: Save task caches
361
+ uses: ./.github/actions/cache-save
@@ -125,5 +125,5 @@ jobs:
125
125
  if-no-files-found: error
126
126
  retention-days: 14
127
127
 
128
- - name: Save Vite+ task cache
129
- uses: ./.github/actions/vite-plus-cache-save
128
+ - name: Save task caches
129
+ uses: ./.github/actions/cache-save
@@ -10,6 +10,11 @@ on:
10
10
  required: false
11
11
  type: boolean
12
12
  default: false
13
+ recover-npm-tag:
14
+ description: Requalify and publish the exact archive for a current immutable release missing from npm.
15
+ required: false
16
+ type: string
17
+ default: ''
13
18
 
14
19
  permissions:
15
20
  contents: read
@@ -28,7 +33,7 @@ jobs:
28
33
  # content they merge was already audited on its pull request, and the
29
34
  # release merge re-qualifies the exact tree it publishes.
30
35
  needs: [release, recovery]
31
- if: github.ref == 'refs/heads/main' && (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && (needs.release.outputs.release_created == 'true' || needs.recovery.outputs.found == 'true')
36
+ if: github.ref == 'refs/heads/main' && (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && !(github.event_name == 'workflow_dispatch' && inputs['recover-npm-tag'] != '') && (needs.release.outputs.release_created == 'true' || needs.recovery.outputs.found == 'true')
32
37
  uses: ./.github/workflows/consumer-qualification.yml
33
38
  with:
34
39
  billing-pause-bypass: ${{ github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true }}
@@ -38,7 +43,7 @@ jobs:
38
43
 
39
44
  preflight:
40
45
  name: Verify release prerequisites
41
- if: github.ref == 'refs/heads/main' && (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true))
46
+ if: github.ref == 'refs/heads/main' && (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && !(github.event_name == 'workflow_dispatch' && inputs['recover-npm-tag'] != '')
42
47
  runs-on: ubuntu-24.04
43
48
  timeout-minutes: 5
44
49
  steps:
@@ -76,7 +81,7 @@ jobs:
76
81
  # it. Version pull requests keep flowing even when the candidate is broken;
77
82
  # publication stays gated on qualification success in the stage job.
78
83
  needs: [preflight]
79
- if: github.ref == 'refs/heads/main' && (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true))
84
+ if: github.ref == 'refs/heads/main' && (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && !(github.event_name == 'workflow_dispatch' && inputs['recover-npm-tag'] != '')
80
85
  uses: ./.github/workflows/release.yml
81
86
  with:
82
87
  billing-pause-bypass: ${{ github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true }}
@@ -97,7 +102,7 @@ jobs:
97
102
  recovery:
98
103
  name: Recover qualified draft identity
99
104
  needs: [release]
100
- if: (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && needs.release.result == 'success'
105
+ if: (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && !(github.event_name == 'workflow_dispatch' && inputs['recover-npm-tag'] != '') && needs.release.result == 'success'
101
106
  runs-on: ubuntu-24.04
102
107
  timeout-minutes: 5
103
108
  permissions:
@@ -170,7 +175,7 @@ jobs:
170
175
  # Qualification only runs when this push created a release or recovery
171
176
  # found a stuck one; staging additionally requires its success so an
172
177
  # unqualified tree can never reach publication.
173
- if: (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && github.ref == 'refs/heads/main' && needs.qualification.result == 'success' && (needs.release.outputs.release_created == 'true' || needs.recovery.outputs.found == 'true')
178
+ if: (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && github.ref == 'refs/heads/main' && !(github.event_name == 'workflow_dispatch' && inputs['recover-npm-tag'] != '') && needs.qualification.result == 'success' && (needs.release.outputs.release_created == 'true' || needs.recovery.outputs.found == 'true')
174
179
  runs-on: ubuntu-24.04
175
180
  timeout-minutes: 15
176
181
  permissions:
@@ -244,7 +249,7 @@ jobs:
244
249
  publish:
245
250
  name: Publish verified package
246
251
  needs: [qualification, release, recovery, stage]
247
- if: (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && github.ref == 'refs/heads/main' && needs.stage.result == 'success' && (needs.release.outputs.release_created == 'true' || needs.recovery.outputs.found == 'true')
252
+ if: (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)) && github.ref == 'refs/heads/main' && !(github.event_name == 'workflow_dispatch' && inputs['recover-npm-tag'] != '') && needs.stage.result == 'success' && (needs.release.outputs.release_created == 'true' || needs.recovery.outputs.found == 'true')
248
253
  uses: ./.github/workflows/qualified-foundry-publish.yml
249
254
  permissions:
250
255
  actions: read
@@ -255,3 +260,52 @@ jobs:
255
260
  tag: ${{ needs.release.outputs.tag_name || needs.recovery.outputs.tag }}
256
261
  asset: ${{ needs.qualification.outputs.filename }}
257
262
  billing-pause-bypass: ${{ github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true }}
263
+
264
+ npm_recovery_target:
265
+ name: Verify npm recovery target
266
+ if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && inputs['recover-npm-tag'] != '' && vars.CI_BILLING_PAUSED != 'true'
267
+ runs-on: ubuntu-24.04
268
+ timeout-minutes: 5
269
+ permissions:
270
+ contents: read
271
+ outputs:
272
+ tag: ${{ steps.target.outputs.tag }}
273
+ asset: ${{ steps.target.outputs.asset }}
274
+ source-sha: ${{ steps.target.outputs.source-sha }}
275
+ steps:
276
+ - name: Checkout trusted recovery verifier
277
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
278
+ with:
279
+ persist-credentials: false
280
+ fetch-depth: 0
281
+ ref: ${{ github.sha }}
282
+ - name: Setup Node
283
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
284
+ with:
285
+ node-version: '24'
286
+ - name: Require exact immutable release and absent npm version
287
+ id: target
288
+ env:
289
+ GH_TOKEN: ${{ github.token }}
290
+ RELEASE_TAG: ${{ inputs['recover-npm-tag'] }}
291
+ CURRENT_SHA: ${{ github.sha }}
292
+ run: |
293
+ set -euo pipefail
294
+ node src/commands/release-recovery.mjs verify-target \
295
+ "$GITHUB_REPOSITORY" "$RELEASE_TAG" "$CURRENT_SHA" "$GITHUB_OUTPUT"
296
+
297
+ npm_recovery_publish:
298
+ name: Requalify and publish recovered archive
299
+ needs: npm_recovery_target
300
+ if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && needs.npm_recovery_target.result == 'success' && vars.CI_BILLING_PAUSED != 'true'
301
+ uses: ./.github/workflows/qualified-foundry-publish.yml
302
+ permissions:
303
+ actions: read
304
+ attestations: read
305
+ contents: read
306
+ id-token: write
307
+ with:
308
+ tag: ${{ needs.npm_recovery_target.outputs.tag }}
309
+ asset: ${{ needs.npm_recovery_target.outputs.asset }}
310
+ source-sha: ${{ needs.npm_recovery_target.outputs['source-sha'] }}
311
+ billing-pause-bypass: false
@@ -166,8 +166,8 @@ jobs:
166
166
  if-no-files-found: ignore
167
167
  retention-days: 14
168
168
 
169
- - name: Save Vite+ task cache
170
- uses: ./.github/actions/vite-plus-cache-save
169
+ - name: Save task caches
170
+ uses: ./.github/actions/cache-save
171
171
 
172
172
  performance:
173
173
  name: Performance
@@ -255,8 +255,8 @@ jobs:
255
255
  if-no-files-found: ignore
256
256
  retention-days: 14
257
257
 
258
- - name: Save Vite+ task cache
259
- uses: ./.github/actions/vite-plus-cache-save
258
+ - name: Save task caches
259
+ uses: ./.github/actions/cache-save
260
260
 
261
261
  integration:
262
262
  name: Integration
@@ -332,8 +332,8 @@ jobs:
332
332
  if-no-files-found: error
333
333
  retention-days: 14
334
334
 
335
- - name: Save Vite+ task cache
336
- uses: ./.github/actions/vite-plus-cache-save
335
+ - name: Save task caches
336
+ uses: ./.github/actions/cache-save
337
337
 
338
338
  integration-sharded:
339
339
  name: Integration shard
@@ -412,8 +412,8 @@ jobs:
412
412
  if-no-files-found: error
413
413
  retention-days: 14
414
414
 
415
- - name: Save Vite+ task cache
416
- uses: ./.github/actions/vite-plus-cache-save
415
+ - name: Save task caches
416
+ uses: ./.github/actions/cache-save
417
417
 
418
418
  e2e:
419
419
  name: E2E
@@ -464,6 +464,7 @@ jobs:
464
464
  if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false' && steps.applicability.outputs.browser == 'true'
465
465
  uses: ./.github/actions/cache
466
466
  with:
467
+ id: e2e-browsers
467
468
  save: ${{ github.event_name == 'push' }}
468
469
  path: |
469
470
  ~/.cache/ms-playwright
@@ -508,8 +509,8 @@ jobs:
508
509
  if-no-files-found: error
509
510
  retention-days: 14
510
511
 
511
- - name: Save Vite+ task cache
512
- uses: ./.github/actions/vite-plus-cache-save
512
+ - name: Save task caches
513
+ uses: ./.github/actions/cache-save
513
514
 
514
515
  smoke:
515
516
  name: Smoke
@@ -581,5 +582,5 @@ jobs:
581
582
  if-no-files-found: error
582
583
  retention-days: 14
583
584
 
584
- - name: Save Vite+ task cache
585
- uses: ./.github/actions/vite-plus-cache-save
585
+ - name: Save task caches
586
+ uses: ./.github/actions/cache-save
@@ -543,8 +543,8 @@ jobs:
543
543
  if-no-files-found: error
544
544
  retention-days: 14
545
545
 
546
- - name: Save Vite+ task cache
547
- uses: ./.github/actions/vite-plus-cache-save
546
+ - name: Save task caches
547
+ uses: ./.github/actions/cache-save
548
548
 
549
549
  lint:
550
550
  name: Lint
@@ -614,8 +614,8 @@ jobs:
614
614
  if-no-files-found: error
615
615
  retention-days: 14
616
616
 
617
- - name: Save Vite+ task cache
618
- uses: ./.github/actions/vite-plus-cache-save
617
+ - name: Save task caches
618
+ uses: ./.github/actions/cache-save
619
619
 
620
620
  type-check:
621
621
  name: Type-Check
@@ -686,8 +686,8 @@ jobs:
686
686
  if-no-files-found: error
687
687
  retention-days: 14
688
688
 
689
- - name: Save Vite+ task cache
690
- uses: ./.github/actions/vite-plus-cache-save
689
+ - name: Save task caches
690
+ uses: ./.github/actions/cache-save
691
691
 
692
692
  unit:
693
693
  name: Unit
@@ -782,8 +782,8 @@ jobs:
782
782
  if-no-files-found: ignore
783
783
  retention-days: 14
784
784
 
785
- - name: Save Vite+ task cache
786
- uses: ./.github/actions/vite-plus-cache-save
785
+ - name: Save task caches
786
+ uses: ./.github/actions/cache-save
787
787
 
788
788
  e2e:
789
789
  name: E2E
@@ -838,6 +838,7 @@ jobs:
838
838
  if: steps.applicability.outputs.applicable == 'true' && steps.applicability.outputs.affected != 'false' && steps.applicability.outputs.browser == 'true'
839
839
  uses: ./.github/actions/cache
840
840
  with:
841
+ id: e2e-browsers
841
842
  save: ${{ github.event_name == 'push' }}
842
843
  path: |
843
844
  ~/.cache/ms-playwright
@@ -882,8 +883,8 @@ jobs:
882
883
  if-no-files-found: error
883
884
  retention-days: 14
884
885
 
885
- - name: Save Vite+ task cache
886
- uses: ./.github/actions/vite-plus-cache-save
886
+ - name: Save task caches
887
+ uses: ./.github/actions/cache-save
887
888
 
888
889
  # Profile and dependency audit merged into one job. The split orchestrators
889
890
  # run a profile job that feeds an audit matrix; here the language blocks run
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.53.3](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.2...v1.53.3) (2026-10-30)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * save repository caches through an explicit save action ([#819](https://github.com/0xPlayerOne/code-foundry/issues/819)) ([a21f1d0](https://github.com/0xPlayerOne/code-foundry/commit/a21f1d0c153e14252bc784f301379029026121da))
9
+
10
+ ## [1.53.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.1...v1.53.2) (2026-10-30)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * support reviewed Playwright 1.64.1 UI receipts ([#817](https://github.com/0xPlayerOne/code-foundry/issues/817)) ([814bf68](https://github.com/0xPlayerOne/code-foundry/commit/814bf68818dcf2a80253e133895cbe3e539b5b7f))
16
+
17
+
18
+ ### Maintenance
19
+
20
+ * add verified npm publication recovery ([#815](https://github.com/0xPlayerOne/code-foundry/issues/815)) ([5876361](https://github.com/0xPlayerOne/code-foundry/commit/58763616b3dc38d127680914eeef6151b3375b37))
21
+
3
22
  ## [1.53.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.53.0...v1.53.1) (2026-10-30)
4
23
 
5
24
 
@@ -131,23 +150,9 @@
131
150
 
132
151
  * **release:** sync bun.lock workspace versions on generated release PRs ([#777](https://github.com/0xPlayerOne/code-foundry/issues/777)) ([b24bfae](https://github.com/0xPlayerOne/code-foundry/commit/b24bfae309ac7003fcc4cd57f79ddc418c27b039))
133
152
 
134
- ## [1.48.6](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.5...v1.48.6) (2026-10-23)
135
-
136
-
137
- ### Bug Fixes
138
-
139
- * **release:** audit generated release content for stale snapshots ([#775](https://github.com/0xPlayerOne/code-foundry/issues/775)) ([f842c27](https://github.com/0xPlayerOne/code-foundry/commit/f842c271e6b270b63a6d9b839795a849237c7a7d))
140
-
141
- ## [1.48.5](https://github.com/0xPlayerOne/code-foundry/compare/v1.48.4...v1.48.5) (2026-10-22)
142
-
143
-
144
- ### Bug Fixes
145
-
146
- * **ci:** serialize billing receipt and report uploads ([#773](https://github.com/0xPlayerOne/code-foundry/issues/773)) ([6497225](https://github.com/0xPlayerOne/code-foundry/commit/6497225bf2d672380b6b5231378b424dffb8afbb))
147
-
148
153
  <!-- code-foundry: packaged changelog -->
149
154
 
150
155
  ---
151
156
 
152
- This packaged changelog lists the 20 most recent releases; 272 older releases are omitted to keep the package small.
157
+ This packaged changelog lists the 20 most recent releases; 274 older releases are omitted to keep the package small.
153
158
  See the [full changelog](https://github.com/0xPlayerOne/code-foundry/blob/main/CHANGELOG.md) for the complete history.
package/docs/CACHING.md CHANGED
@@ -11,6 +11,13 @@ Repositories should measure before enabling large installed-tree or build
11
11
  caches. Set repository variables such as `REPO_FOUNDRY_CACHE_PACKAGES` and
12
12
  `REPO_FOUNDRY_CACHE_BUILD` only when a repeatable warm-cache benefit is proven.
13
13
 
14
+ These caches are restored on every event and saved on push only, so pull
15
+ requests and scheduled runs cannot poison them. The setup action restores them
16
+ and publishes a save contract; the `.github/actions/cache-save` action at the
17
+ end of each task job saves every published cache that did not restore an exact
18
+ key. Custom workflows that use `.github/actions/setup` must call it after their
19
+ tasks, or nothing is saved.
20
+
14
21
  ## Turborepo and Vercel Remote Caching
15
22
 
16
23
  Turborepo repositories can opt into Vercel Remote Caching with:
@@ -35,7 +42,8 @@ When opted in, the setup action:
35
42
  - restores `node_modules/.vite/task-cache` as its last step, after Install and
36
43
  after every step that archives `node_modules` (Install can replace it);
37
44
  - publishes a cache path, key, and save-intent contract for the explicit
38
- `.github/actions/vite-plus-cache-save` action to use after repository tasks;
45
+ `.github/actions/cache-save` action, which also saves the repository caches,
46
+ to use after repository tasks;
39
47
  - saves it push-only, so pull requests and scheduled runs cannot poison it;
40
48
  - keeps it out of the generic JavaScript build-state archive.
41
49
 
@@ -350,7 +350,7 @@ behavior:
350
350
  (not a config key). It controls the local Vite+ task cache only when
351
351
  `monorepo_tool: vite-plus` is set.
352
352
  - Custom workflows using the setup action must add
353
- `.github/actions/vite-plus-cache-save` after task execution. The generated
353
+ `.github/actions/cache-save` after task execution. The generated
354
354
  Code Foundry workflows already include this save boundary; see
355
355
  [Caching and remote caching](CACHING.md).
356
356
 
@@ -35,13 +35,11 @@ actual caller workflow filename, `release_self-ci.yml`, in repository
35
35
  environment. Both the caller and reusable publisher grant `id-token: write`.
36
36
  For generated consumer workflows, the caller filename is `release.yml` and the
37
37
  auth mode remains configurable with `npm_publish_auth`.
38
- Existing version publication is not overwritten: retrying an already-published
39
- version fails rather than treating a registry conflict or network error as proof
40
- of identity.
38
+ To recover a GitHub release missing from npm, the self-only dispatch requires the
39
+ current stable tag, immutable release, exact archive, npm 404, fresh Node 24/26
40
+ qualification, and OIDC publication. Existing npm versions are never overwritten.
41
41
 
42
- The publisher never rebuilds the package, publishes a directory, or treats an
43
- already-published version conflict as proof of success. It never accepts or
44
- forwards `NPM_TOKEN`.
42
+ The publisher never rebuilds a package or accepts `NPM_TOKEN`.
45
43
 
46
44
  Qualification and publication are gated on `main` push or an explicitly
47
45
  requested `workflow_dispatch`. The shared billing pause blocks normal runs. A
@@ -123,6 +121,11 @@ state still match the qualified candidate. An already-published release is never
123
121
  restaged or overwritten. Inspect the retained staging and publication identity
124
122
  receipts when recovery is needed.
125
123
 
124
+ To recover a published GitHub release that is missing from npm, dispatch the
125
+ self-only `release_self-ci.yml` workflow on `main` with `recover-npm-tag`. It
126
+ requires the current stable tag, immutable release, exact archive, and exact npm
127
+ 404, then requalifies that archive on Node 24/26 before OIDC publication.
128
+
126
129
  ## Publication prerequisites
127
130
 
128
131
  Enable immutable releases and verify the setting with the actual
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.53.1",
3
+ "version": "1.53.3",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
6
6
  "bugs": {
@@ -17,11 +17,13 @@
17
17
  "files": [
18
18
  "src",
19
19
  "!src/commands/qualification-receipt.mjs",
20
+ "!src/commands/release-recovery.mjs",
20
21
  "!src/lib/qualification-receipt.mjs",
21
22
  "!src/lib/release-package-contract.mjs",
22
23
  "docs",
23
24
  ".github",
24
25
  "!.github/actions/**",
26
+ "!.github/workflows/qualified-foundry-publish.yml",
25
27
  ".githooks",
26
28
  ".editorconfig",
27
29
  ".gitattributes",
@@ -1247,19 +1247,28 @@ function renderWorkflow(content, config, repository, ref, rustCodeql, file, self
1247
1247
  // recovery, staging, and qualified publication. Consumer callers must
1248
1248
  // retain the ordinary reusable Release Please path instead of inheriting
1249
1249
  // self-only jobs whose outputs and environments do not exist for them.
1250
- for (const job of ['qualification', 'preflight', 'recovery', 'stage', 'publish'])
1250
+ for (const job of [
1251
+ 'qualification',
1252
+ 'preflight',
1253
+ 'recovery',
1254
+ 'stage',
1255
+ 'publish',
1256
+ 'npm_recovery_target',
1257
+ 'npm_recovery_publish',
1258
+ ])
1251
1259
  rendered = removeWorkflowBlock(rendered, job)
1260
+ rendered = removeWorkflowBlock(rendered, 'recover-npm-tag', ' ')
1252
1261
  rendered = rendered.replace(/^ needs: \[preflight\]\n/m, '')
1253
1262
  rendered = rendered.replace(/^ config-file: \.github\/release-please-foundry\.json\n/m, '')
1254
1263
  if (config.release_defer_publication !== 'true') {
1255
1264
  rendered = rendered.replace(/^ defer-publication: true\n/m, '')
1256
1265
  }
1257
1266
  rendered = rendered.replace(
1258
- /^ if: github\.ref == 'refs\/heads\/main' && \(vars\.CI_BILLING_PAUSED != 'true' \|\| \(github\.event_name == 'workflow_dispatch' && inputs\['release-while-paused'\] == true\)\)\n/m,
1267
+ /^ if: github\.ref == 'refs\/heads\/main' && \(vars\.CI_BILLING_PAUSED != 'true' \|\| \(github\.event_name == 'workflow_dispatch' && inputs\['release-while-paused'\] == true\)\) && !\(github\.event_name == 'workflow_dispatch' && inputs\['recover-npm-tag'\] != ''\)\n/m,
1259
1268
  " if: vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)\n"
1260
1269
  )
1261
1270
  rendered = rendered.replace(
1262
- /^ if: github\.ref == 'refs\/heads\/main' && \(vars\.CI_BILLING_PAUSED != 'true' \|\| \(github\.event_name == 'workflow_dispatch' && inputs\['release-while-paused'\] == true\)\)\n/m,
1271
+ /^ if: github\.ref == 'refs\/heads\/main' && \(vars\.CI_BILLING_PAUSED != 'true' \|\| \(github\.event_name == 'workflow_dispatch' && inputs\['release-while-paused'\] == true\)\) && !\(github\.event_name == 'workflow_dispatch' && inputs\['recover-npm-tag'\] != ''\)\n/m,
1263
1272
  " if: vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['release-while-paused'] == true)\n"
1264
1273
  )
1265
1274
  // Gate ordinary pushes while allowing the release squash and manual hotfixes.
@@ -1386,19 +1395,25 @@ function renderWorkflow(content, config, repository, ref, rustCodeql, file, self
1386
1395
  }
1387
1396
 
1388
1397
  /**
1389
- * Remove a root-level YAML block by id while preserving the surrounding file.
1390
- * This is used for workflow jobs and triggers whose feature is disabled by
1391
- * configuration.
1398
+ * Remove an indented YAML mapping block by id while preserving the surrounding
1399
+ * file. This is used for workflow jobs, inputs, and triggers whose feature is
1400
+ * disabled by configuration.
1392
1401
  * @param {string} content
1393
1402
  * @param {string} blockId
1403
+ * @param {string} [indentation]
1394
1404
  * @returns {string}
1395
1405
  */
1396
- function removeWorkflowBlock(content, blockId) {
1406
+ function removeWorkflowBlock(content, blockId, indentation = ' ') {
1397
1407
  const lines = content.split('\n')
1398
- const start = lines.findIndex((line) => line === ` ${blockId}:`)
1408
+ const start = lines.findIndex((line) => line === `${indentation}${blockId}:`)
1399
1409
  if (start === -1) return content
1400
1410
  let end = start + 1
1401
- while (end < lines.length && !/^ [A-Za-z0-9_-]+:\s*$/.test(lines[end])) end += 1
1411
+ while (end < lines.length) {
1412
+ const line = lines[end]
1413
+ const level = line.match(/^ */)?.[0].length ?? 0
1414
+ if (line.trim() && level <= indentation.length) break
1415
+ end += 1
1416
+ }
1402
1417
  lines.splice(start, end - start)
1403
1418
  if (content.endsWith('\n') && lines.at(-1) !== '') lines.push('')
1404
1419
  return lines.join('\n')
@@ -1,130 +0,0 @@
1
- name: Qualified Foundry Publication
2
-
3
- on:
4
- workflow_call:
5
- inputs:
6
- tag:
7
- description: Published immutable release tag at the caller commit.
8
- required: true
9
- type: string
10
- asset:
11
- description: Exact pre-attached Code Foundry npm tarball name.
12
- required: true
13
- type: string
14
- billing-pause-bypass:
15
- description: Allow only an explicitly dispatched release while shared CI remains paused.
16
- required: false
17
- type: boolean
18
- default: false
19
-
20
- permissions:
21
- attestations: read
22
- contents: read
23
-
24
- jobs:
25
- qualify:
26
- name: Qualify Node ${{ matrix.node }}
27
- if: (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['billing-pause-bypass'] == true)) && github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
28
- runs-on: ubuntu-24.04
29
- timeout-minutes: 15
30
- strategy:
31
- fail-fast: false
32
- matrix:
33
- node: ['24', '26']
34
- steps:
35
- - name: Checkout trusted qualification runtime
36
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
37
- with:
38
- persist-credentials: false
39
- ref: ${{ github.sha }}
40
- - name: Setup Node
41
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
42
- with:
43
- node-version: ${{ matrix.node }}
44
- - name: Install pinned Actionlint
45
- run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
46
- - name: Download and cryptographically verify the release archive
47
- env:
48
- GH_TOKEN: ${{ github.token }}
49
- RELEASE_TAG: ${{ inputs.tag }}
50
- RELEASE_ASSET: ${{ inputs.asset }}
51
- run: |
52
- set -euo pipefail
53
- node src/commands/qualified-publication.mjs download \
54
- "$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
55
- "$RUNNER_TEMP/candidate" > "$RUNNER_TEMP/download.json"
56
- - name: Qualify those exact bytes
57
- env:
58
- RELEASE_ASSET: ${{ inputs.asset }}
59
- run: |
60
- set -euo pipefail
61
- node src/lib/consumer-qualification.mjs \
62
- "$RUNNER_TEMP/candidate/$RELEASE_ASSET" "$GITHUB_SHA" \
63
- "$RUNNER_TEMP/report.json" "$(go env GOPATH)/bin/actionlint"
64
- - name: Retain this attempt's qualification report
65
- if: always()
66
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
67
- with:
68
- name: qualified-${{ github.run_attempt }}-node-${{ matrix.node }}
69
- path: ${{ runner.temp }}/report.json
70
- if-no-files-found: error
71
- retention-days: 14
72
-
73
- publish:
74
- name: Publish qualified archive
75
- needs: qualify
76
- if: (vars.CI_BILLING_PAUSED != 'true' || (github.event_name == 'workflow_dispatch' && inputs['billing-pause-bypass'] == true)) && github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
77
- runs-on: ubuntu-24.04
78
- timeout-minutes: 15
79
- concurrency:
80
- group: qualified-foundry-npm-${{ github.repository }}-${{ inputs.tag }}
81
- cancel-in-progress: false
82
- permissions:
83
- actions: read
84
- attestations: read
85
- contents: read
86
- id-token: write
87
- env:
88
- RELEASE_TAG: ${{ inputs.tag }}
89
- RELEASE_ASSET: ${{ inputs.asset }}
90
- steps:
91
- - name: Checkout trusted publication runtime
92
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
93
- with:
94
- persist-credentials: false
95
- ref: ${{ github.sha }}
96
- - name: Setup Node for trusted publishing
97
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
98
- with:
99
- node-version: '24'
100
- - name: Retrieve only this run attempt's evidence
101
- env:
102
- GH_TOKEN: ${{ github.token }}
103
- run: |
104
- set -euo pipefail
105
- for node in 24 26; do
106
- gh run download "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" \
107
- --name "qualified-$GITHUB_RUN_ATTEMPT-node-$node" \
108
- --dir "$RUNNER_TEMP/reports/$node"
109
- done
110
- node src/commands/qualified-publication.mjs download \
111
- "$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
112
- "$RUNNER_TEMP/candidate" > "$RUNNER_TEMP/download.json"
113
- - name: Reverify and publish the tested tarball without lifecycle hooks
114
- env:
115
- GH_TOKEN: ${{ github.token }}
116
- run: |
117
- set -euo pipefail
118
- node src/commands/qualified-publication.mjs publish \
119
- "$GITHUB_REPOSITORY" "$RELEASE_TAG" "$GITHUB_SHA" "$RELEASE_ASSET" \
120
- "$RUNNER_TEMP/candidate" \
121
- "$RUNNER_TEMP/reports/24/report.json" \
122
- "$RUNNER_TEMP/reports/26/report.json" > "$RUNNER_TEMP/publication.json"
123
- - name: Retain publication identity
124
- if: always()
125
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
126
- with:
127
- name: qualified-publication-${{ github.run_attempt }}
128
- path: ${{ runner.temp }}/publication.json
129
- if-no-files-found: warn
130
- retention-days: 14