@schalkneethling/toolkit 1.1.0 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schalkneethling/toolkit",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI for managing Agent hooks, skills, and agents across projects.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -299,18 +299,44 @@ Font rendering varies by OS. Options:
299
299
 
300
300
  ## CI/CD Configuration
301
301
 
302
+ When adding GitHub Actions examples or workflow files, declare explicit least-privilege
303
+ `GITHUB_TOKEN` permissions. Use a restrictive top-level default such as `contents: read`, then add
304
+ job-level permissions only for jobs that need to push commits, update pull requests, publish pages,
305
+ or write other repository resources.
306
+
307
+ Pin every third-party or GitHub-owned action to a full-length commit SHA. Do not commit tag-based
308
+ refs such as `@v6` or `@v7`; use those only as temporary input to a pinning tool, then keep the
309
+ version as a comment beside the SHA.
310
+
311
+ When a job checks out the repository, set `persist-credentials: false` on `actions/checkout`.
312
+ Artifact-producing jobs should not keep the repository token persisted for later test, build, or
313
+ upload steps.
314
+
302
315
  ### GitHub Actions Example
303
316
 
304
317
  ```yaml
305
- - name: Run visual tests
306
- run: npx playwright test --project=chromium
307
-
308
- - name: Upload diff artifacts on failure
309
- if: failure()
310
- uses: actions/upload-artifact@v7
311
- with:
312
- name: visual-diff
313
- path: test-results/
318
+ permissions:
319
+ contents: read
320
+
321
+ jobs:
322
+ visual-tests:
323
+ permissions:
324
+ contents: read
325
+ steps:
326
+ - name: Checkout
327
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 - re-resolve before use
328
+ with:
329
+ persist-credentials: false
330
+
331
+ - name: Run visual tests
332
+ run: npx playwright test --project=chromium
333
+
334
+ - name: Upload diff artifacts on failure
335
+ if: failure()
336
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 - re-resolve before use
337
+ with:
338
+ name: visual-diff
339
+ path: test-results/
314
340
  ```
315
341
 
316
342
  ### Generate Baselines in CI
@@ -318,15 +344,31 @@ Font rendering varies by OS. Options:
318
344
  For consistent baselines, generate in CI:
319
345
 
320
346
  ```yaml
321
- - name: Update baselines
322
- run: npx playwright test --update-snapshots
323
-
324
- - name: Commit baselines
325
- run: |
326
- git config user.name "CI Bot"
327
- git add "**/*.png"
328
- git commit -m "Update visual baselines"
329
- git push
347
+ permissions:
348
+ contents: read
349
+
350
+ jobs:
351
+ update-baselines:
352
+ permissions:
353
+ contents: write
354
+ steps:
355
+ - name: Checkout
356
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 - re-resolve before use
357
+ with:
358
+ persist-credentials: false
359
+
360
+ - name: Update baselines
361
+ run: npx playwright test --update-snapshots
362
+
363
+ - name: Commit baselines
364
+ env:
365
+ GITHUB_TOKEN: ${{ github.token }}
366
+ run: |
367
+ git config user.name "CI Bot"
368
+ git config user.email "github-actions[bot]@users.noreply.github.com"
369
+ git add "**/*.png"
370
+ git commit -m "Update visual baselines"
371
+ git -c http.https://github.com/.extraheader="AUTHORIZATION: bearer $GITHUB_TOKEN" push origin HEAD:refs/heads/${GITHUB_REF_NAME}
330
372
  ```
331
373
 
332
374
  ## Organizing Screenshots
@@ -68,7 +68,15 @@ User or authenticated UI/API access required:
68
68
  - Do not store npm publish tokens in GitHub Actions secrets for OIDC-capable publishing.
69
69
  - Do not run install lifecycle scripts in release workflows unless the user explicitly accepts the
70
70
  risk.
71
- - Do not show tag-pinned actions as compliant with SHA pinning; resolve actions to full commit SHAs.
71
+ - Do not show tag-pinned actions as compliant with SHA pinning; resolve all third-party and
72
+ GitHub-owned actions to full commit SHAs. Tag-based refs such as `@v6` or `@v7` weaken
73
+ supply-chain integrity and violate pinned-action policy.
74
+ - Do not create or edit GitHub Actions workflows without explicit least-privilege `permissions:`
75
+ for `GITHUB_TOKEN`. Set a restrictive top-level default such as `contents: read`, then grant
76
+ elevated scopes only on the jobs that need them.
77
+ - Do not use `actions/checkout` with default credential persistence. Always set
78
+ `persist-credentials: false`; if a job must push to git, provide an explicit, narrowly scoped
79
+ credential only for that push step.
72
80
  - Do not say npm/GitHub account settings are complete unless they were actually checked.
73
81
  - Do not use self-hosted runners for npm trusted publishing unless official npm docs currently
74
82
  support them.
@@ -90,6 +98,14 @@ User or authenticated UI/API access required:
90
98
  > Section 2.2's trusted-publishing requirement is npm-specific: the publish step must run with a
91
99
  > supported Node.js version and npm CLI version even when the rest of the workflow uses another
92
100
  > package manager.
101
+ >
102
+ > For pnpm projects on GitHub Actions, prefer the Marketplace action
103
+ > [`pnpm/setup`](https://github.com/marketplace/actions/setup-pnpm-with-runtime) to set up pnpm and
104
+ > the JavaScript runtime in one step. It installs pnpm from `@pnpm/exe`, can read
105
+ > `devEngines.runtime` from `package.json`, and replaces the separate
106
+ > `pnpm/action-setup` + `actions/setup-node` pattern. In release workflows, pin `pnpm/setup` to a
107
+ > full commit SHA, set `install: false`, and run the explicit hardened install command yourself so
108
+ > flags like `--frozen-lockfile --ignore-scripts` remain visible.
93
109
 
94
110
  ---
95
111
 
@@ -186,10 +202,27 @@ test → build → publish
186
202
 
187
203
  | Constraint | Why |
188
204
  | ------------------------------------------------------ | ----------------------------------------------------------- |
189
- | All actions pinned to full-length commit SHA | Prevents supply-chain attacks via action updates |
205
+ | All actions pinned to full-length commit SHA | Prevents supply-chain attacks via mutable tags |
206
+ | Explicit least-privilege `GITHUB_TOKEN` permissions | Avoids inheriting broad repository defaults |
207
+ | `actions/checkout` uses `persist-credentials: false` | Reduces token exposure to later workflow steps |
190
208
  | `npm ci --ignore-scripts` (or `--ignore-scripts` flag) | Prevents malicious lifecycle scripts running during install |
191
209
  | Build and publish in **separate jobs** | Isolates publish permissions from arbitrary build code |
192
210
 
211
+ Always include a top-level `permissions:` block with the workflow-wide minimum, usually
212
+ `contents: read`. Add job-level permissions only when a job requires more access; for npm trusted
213
+ publishing, `id-token: write` belongs on the publish job only.
214
+
215
+ Every checkout step must disable persisted credentials:
216
+
217
+ ```yaml
218
+ - uses: actions/checkout@<full-length-commit-sha>
219
+ with:
220
+ persist-credentials: false
221
+ ```
222
+
223
+ This is especially important for jobs that build packages or upload artifacts, because later steps
224
+ should not inherit a repository token unless they explicitly need one.
225
+
193
226
  ### 3.3 · Suppress lifecycle scripts project-wide
194
227
 
195
228
  Add to `.npmrc` in the repository:
@@ -245,8 +278,9 @@ addressing security vulnerabilities promptly.
245
278
 
246
279
  ### 5.2 · Keep GitHub Actions updated
247
280
 
248
- All actions must be pinned to a commit SHA (not a tag). To migrate existing workflows and keep
249
- them current:
281
+ All third-party and GitHub-owned actions must be pinned to a full commit SHA, not a tag. Tag refs
282
+ such as `@v6` or `@v7` are mutable and must not be committed to workflow files. To migrate existing
283
+ workflows and keep them current:
250
284
 
251
285
  ```bash
252
286
  npx actions-up
@@ -387,6 +421,8 @@ Use this when setting up a new package or auditing an existing one.
387
421
  ### Workflow hygiene
388
422
 
389
423
  - [ ] Build and publish are separate jobs
424
+ - [ ] Workflows declare explicit least-privilege `GITHUB_TOKEN` permissions
425
+ - [ ] Every `actions/checkout` step sets `persist-credentials: false`
390
426
  - [ ] `npm ci --ignore-scripts` used in all install steps
391
427
  - [ ] `ignore-scripts=true` in `.npmrc`
392
428
  - [ ] `zizmor` passes on all workflow files
@@ -4,11 +4,11 @@ description: >
4
4
  Generate, repair, or debug the GitHub Actions workflow FILE that performs an OIDC
5
5
  trusted publish of a pnpm package — the concrete publish.yml, its test → build →
6
6
  publish job shape, the package tarball artifact handoff, Node-version inference from
7
- package.json, pnpm setup via pnpm/action-setup, the npm-CLI-version upgrade step, and
7
+ package.json, pnpm and runtime setup via pnpm/setup, the npm-CLI-version upgrade step, and
8
8
  repository.url/Sigstore provenance matching. Use when the user wants the actual
9
9
  workflow written or fixed, or is debugging a specific CI failure: npm publish
10
10
  E404/E403/422, NODE_AUTH_TOKEN appearing unexpectedly, provenance or id-token errors,
11
- pnpm/action-setup version resolution, or actions/setup-node node-version-file problems.
11
+ pnpm/setup version resolution, or runtime version problems.
12
12
  For the broader publishing SECURITY POSTURE — account 2FA, repository and branch
13
13
  hardening, GitHub environments, changesets versus changelogithub, sole-maintainer risk,
14
14
  or auditing an existing pipeline — use the npm-package-publishing skill instead.
@@ -29,33 +29,51 @@ One number to keep consistent between the two: both skills use Node 24.8.0 or hi
29
29
  ## Workflow
30
30
 
31
31
  1. Inspect `package.json`, `.npmrc`, lockfiles, and existing `.github/workflows/*.yml`.
32
- 2. Resolve every workflow dependency to its latest stable version at the moment the file is created, and pin each to the full-length commit SHA of that version. The SHAs in this skill's template are placeholders that will be out of date; never copy them verbatim. See "Pinning actions to current SHAs" below for the procedure.
32
+ 2. Resolve every workflow dependency to its latest stable version at the moment the file is created, and pin each to the full-length commit SHA of that version. Never leave third-party or GitHub-owned actions pinned to tag-based refs such as `@v4`, `@v6`, or `@v7` in the final workflow; tag refs weaken supply-chain integrity and violate pinned-action policy. The SHAs in this skill's template are placeholders that will be out of date; never copy them verbatim. See "Pinning actions to current SHAs" below for the procedure.
33
33
  3. Preserve pinned action SHAs when they already exist; annotate each with a version comment so Dependabot can bump it.
34
- 4. Drive the test and build jobs' Node version from the project's **existing** target, not from a number invented for this workflow. Read it from the repo's current `.nvmrc`, `.node-version`, `volta.node`, or CI config; if none exists, ask the developer rather than guessing. Use `node-version-file: .nvmrc` for these jobs (do not point them at `package.json`, which falls through to the `engines.node` range an unbounded range like `>=20` resolves to the newest Node release, so CI silently floats away from the version developers actually run). `.nvmrc` is a development and CI file only; npm never reads it during a consumer install, so it does not constrain consumers.
34
+ 4. Drive the test and build jobs' Node version from the project's **existing** target, not from a number invented for this workflow. Read it from the repo's current `devEngines.runtime`, `.nvmrc`, `.node-version`, `volta.node`, or CI config; if none exists, ask the developer rather than guessing. For pnpm projects, prefer `pnpm/setup` and either let it read `devEngines.runtime` from `package.json` or set its `runtime` input to the resolved existing target, such as `node@22`. Do not point runtime selection at `engines.node`, which is the consumer compatibility range an unbounded range like `>=20` can float CI away from the version developers actually run.
35
35
  5. Never raise the project's Node version, create a new `.nvmrc`, or overwrite an existing one to "match" the publish step. The publish step's Node 24.8.0 (step 11) is an isolated requirement of the publish action and must not propagate to `.nvmrc`, to `engines.node`, or to the test and build jobs. A project that targets Node 22 keeps testing and building on Node 22; only the final `npm publish` invocation runs on 24.8.0, and it does not rebuild the artifact. Conflating these two numbers is the most likely way this skill is misapplied — do not do it.
36
36
  6. Ensure every job that reads the repo (including any reading `.nvmrc`) runs `actions/checkout` first.
37
- 7. Install pnpm with `pnpm/action-setup`, omitting the `version` input so the version is read from the `packageManager` field. Do not use Corepack: it is still marked experimental and downloads the package manager from the network on first use, which is an avoidable failure surface in a release pipeline.
38
- 8. `pnpm/action-setup` does not install Node.js, so always run `actions/setup-node` as a separate step.
39
- 9. Disable setup-node package-manager caching for release/publish workflows with `package-manager-cache: false`.
40
- 10. Set `persist-credentials: false` on every `actions/checkout` step unless a later step must push to git.
37
+ 7. For pnpm workflows, use `pnpm/setup` from the GitHub Marketplace instead of combining `pnpm/action-setup` with `actions/setup-node`. It installs pnpm from `@pnpm/exe` and installs the requested JavaScript runtime through `pnpm runtime set` in one step. Pin it to a full commit SHA like every other action.
38
+ 8. Set `install: false` on `pnpm/setup` in release/publish workflows, then run `pnpm install --frozen-lockfile --ignore-scripts` explicitly. The action can auto-install by default, but release workflows should keep install flags visible and hardened.
39
+ 9. Do not use Corepack in release workflows: it is still marked experimental and downloads the package manager from the network on first use, which is an avoidable failure surface in a release pipeline.
40
+ 10. Set `persist-credentials: false` on every `actions/checkout` step. Never rely on checkout's default credential persistence. If a workflow genuinely must push to git, use an explicit, narrowly scoped credential only for that push step.
41
41
  11. Target Node 24.8.0 or higher in the publish step. That floor bundles npm 11.6.0, which already exceeds the npm CLI 11.5.1 minimum trusted publishing requires, so no manual npm upgrade is needed there. Keep a guard step that upgrades npm only when the resolved Node ships an npm below 11.5.1, so the workflow stays correct if a project pins an older Node. An npm that is too old silently falls back to token auth or fails to attempt OIDC at all.
42
42
  12. Pack into a dedicated artifact directory, usually `package/*.tgz`.
43
43
  13. In the publish job, download the artifact to `package`, find the `.tgz`, and publish its resolved path.
44
44
  14. Use GitHub OIDC trusted publishing, not npm tokens. Provenance is generated automatically under trusted publishing, so the `--provenance` flag is not required.
45
45
  15. Add a `concurrency` group keyed on the release so two tag pushes cannot race into overlapping publishes.
46
46
 
47
+ ## GitHub Token Permissions
48
+
49
+ Every GitHub Actions workflow this skill creates or edits must declare explicit least-privilege
50
+ `GITHUB_TOKEN` permissions. Add a top-level `permissions:` block that grants the workflow-wide
51
+ minimum, usually `contents: read`, then add job-level `permissions:` only where a job needs more.
52
+
53
+ For trusted npm publishing, only the publish job should receive `id-token: write`; test and build
54
+ jobs should stay at `contents: read`. If a project genuinely needs another scope, grant it only to
55
+ the specific job that requires it and document why in the workflow review notes. Never rely on
56
+ GitHub's repository default token permissions.
57
+
58
+ Every `actions/checkout` step must include `persist-credentials: false`, including jobs that build
59
+ or upload artifacts. Persisted checkout credentials unnecessarily leave `GITHUB_TOKEN` available to
60
+ later build, test, packaging, and artifact steps.
61
+
47
62
  ## Package Metadata
48
63
 
49
- Three different Node versions live in three different places, and keeping them separate is deliberate — conflating them is the main way this workflow goes wrong. `engines.node` in `package.json` is the _consumer_ floor: the only one that constrains people who install the package, and it should reflect what the package actually supports (npm warns, but does not hard-fail, when a consumer is outside it). The test and build jobs run on the project's _own_ target version, read from the existing `.nvmrc` (or `.node-version`/`volta.node`); this is never read during a consumer install, so it does not leak into the consumer contract. The publish step pins Node 24.8.0 or higher independently, purely because that floor bundles an npm new enough for OIDC. These three are not meant to agree: a repo can develop and test on Node 22, keep `engines.node` at its true support range, and still publish on Node 24 — all without affecting consumers, and without changing what the project builds and tests against.
64
+ Three different Node versions live in three different places, and keeping them separate is deliberate — conflating them is the main way this workflow goes wrong. `engines.node` in `package.json` is the _consumer_ floor: the only one that constrains people who install the package, and it should reflect what the package actually supports (npm warns, but does not hard-fail, when a consumer is outside it). The test and build jobs run on the project's _own_ target version, read from `devEngines.runtime` or the existing `.nvmrc`/`.node-version`/`volta.node`/CI config; these are development and CI targets, so they do not leak into the consumer contract. The publish step pins Node 24.8.0 or higher independently, purely because that floor bundles an npm new enough for OIDC. These three are not meant to agree: a repo can develop and test on Node 22, keep `engines.node` at its true support range, and still publish on Node 24 — all without affecting consumers, and without changing what the project builds and tests against.
50
65
 
51
- The publish-step version must never be copied into the other two. Do not raise `engines.node` to 24.8.0, and do not set or bump `.nvmrc` to 24, to "make things consistent". Doing so would move the test and build jobs onto Node 24, so the package would be validated against a version above its actual target and a Node-22 incompatibility could ship uncaught. The publish job runs `npm publish` on the already-built tarball with scripts ignored, so its Node version never rebuilds or retests the code; it is inert with respect to the artifact.
66
+ The publish-step version must never be copied into the other two. Do not raise `engines.node` to 24.8.0, and do not set or bump `.nvmrc` or `devEngines.runtime` to 24, to "make things consistent". Doing so would move the test and build jobs onto Node 24, so the package would be validated against a version above its actual target and a Node-22 incompatibility could ship uncaught. The publish job runs `npm publish` on the already-built tarball with scripts ignored, so its Node version never rebuilds or retests the code; it plays no role in building the artifact.
52
67
 
53
68
  ```json
54
69
  {
55
70
  "engines": {
56
71
  "node": ">=20"
57
72
  },
58
- "packageManager": "pnpm@10.0.0",
73
+ "packageManager": "pnpm@11.0.4",
74
+ "devEngines": {
75
+ "runtime": { "name": "node", "version": "^22.0.0", "onFail": "download" }
76
+ },
59
77
  "repository": {
60
78
  "type": "git",
61
79
  "url": "git+https://github.com/OWNER/REPO.git"
@@ -63,9 +81,13 @@ The publish-step version must never be copied into the other two. Do not raise `
63
81
  }
64
82
  ```
65
83
 
66
- The `engines.node` value above is the _consumer_ floor and should reflect what the package actually supports; `>=20` is only an example, and a bounded upper limit is sensible if the package genuinely needs one. Do not raise it to 24.8.0 to satisfy CI — the publish step pins its own Node version, and the test and build jobs read theirs from `.nvmrc`, so the trusted-publishing requirement never leaks into the consumer contract.
84
+ The `engines.node` value above is the _consumer_ floor and should reflect what the package actually supports; `>=20` is only an example, and a bounded upper limit is sensible if the package genuinely needs one. Do not raise it to 24.8.0 to satisfy CI — the publish step pins its own Node version, and the test and build jobs read theirs from `devEngines.runtime` or another existing project target, so the trusted-publishing requirement never leaks into the consumer contract.
67
85
 
68
- Because the test and build jobs read `.nvmrc`, that file must exist in the repository root with a single version line matching the project's target (for example `22`). If the repo already has one, use it as-is and do not change it. If it has none, derive the value from the project's existing Node target (`.node-version`, `volta.node`, the previous CI config, or by asking the developer) before creating it — do not default to the publish step's 24.8.0. Alternatively, point those jobs' `node-version` at the project's explicit version instead of using a file.
86
+ For pnpm projects, prefer declaring the development runtime in `devEngines.runtime` so `pnpm/setup`
87
+ can read the runtime and version from `package.json`. If the repo already uses `.nvmrc`,
88
+ `.node-version`, `volta.node`, or existing CI config instead, keep that source of truth and set
89
+ `pnpm/setup`'s `runtime` input to the same resolved version. Do not default to the publish step's
90
+ 24.8.0.
69
91
 
70
92
  The `repository.url` field is not cosmetic. Provenance verification runs through Sigstore, which compares the repository in the OIDC token against `package.json`. A mismatch fails the publish with a 422 error that the user-facing npm docs do not explain. Make sure the owner/name in `repository.url` matches the repository actually running the workflow.
71
93
 
@@ -105,15 +127,13 @@ jobs:
105
127
  with:
106
128
  persist-credentials: false
107
129
 
108
- - name: Install pnpm
109
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 — PLACEHOLDER SHA, re-resolve before use
110
- # version is read from the packageManager field in package.json
111
-
112
- - name: Setup Node.js
113
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 — PLACEHOLDER SHA, re-resolve before use
130
+ - name: Setup pnpm and Node.js
131
+ uses: pnpm/setup@1111111111111111111111111111111111111111 # v1.0.0 — PLACEHOLDER SHA, re-resolve before use
114
132
  with:
115
- node-version-file: .nvmrc # exact dev/CI version; decoupled from engines.node
116
- package-manager-cache: false
133
+ # Runtime is read from devEngines.runtime when present. If the project uses
134
+ # .nvmrc/.node-version/volta instead, set runtime to that exact target
135
+ # (for example, runtime: node@22). Do not use engines.node here.
136
+ install: false
117
137
 
118
138
  - name: Install dependencies
119
139
  run: pnpm install --frozen-lockfile --ignore-scripts
@@ -137,14 +157,13 @@ jobs:
137
157
  with:
138
158
  persist-credentials: false
139
159
 
140
- - name: Install pnpm
141
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 — PLACEHOLDER SHA, re-resolve before use
142
-
143
- - name: Setup Node.js
144
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 — PLACEHOLDER SHA, re-resolve before use
160
+ - name: Setup pnpm and Node.js
161
+ uses: pnpm/setup@1111111111111111111111111111111111111111 # v1.0.0 — PLACEHOLDER SHA, re-resolve before use
145
162
  with:
146
- node-version-file: .nvmrc # exact dev/CI version; decoupled from engines.node
147
- package-manager-cache: false
163
+ # Runtime is read from devEngines.runtime when present. If the project uses
164
+ # .nvmrc/.node-version/volta instead, set runtime to that exact target
165
+ # (for example, runtime: node@22). Do not use engines.node here.
166
+ install: false
148
167
 
149
168
  - name: Install dependencies
150
169
  run: pnpm install --frozen-lockfile --ignore-scripts
@@ -178,14 +197,13 @@ jobs:
178
197
  with:
179
198
  persist-credentials: false
180
199
 
181
- - name: Setup Node.js
182
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 — PLACEHOLDER SHA, re-resolve before use
200
+ - name: Setup pnpm and Node.js
201
+ uses: pnpm/setup@1111111111111111111111111111111111111111 # v1.0.0 — PLACEHOLDER SHA, re-resolve before use
183
202
  with:
184
203
  # Pinned for the publish step only. 24.8.0 bundles npm 11.6.0, new enough
185
204
  # for OIDC; this is independent of engines.node, the consumer floor.
186
- node-version: 24.8.0
187
- package-manager-cache: false
188
- registry-url: https://registry.npmjs.org
205
+ runtime: node@24.8.0
206
+ install: false
189
207
 
190
208
  - name: Ensure npm is new enough for trusted publishing
191
209
  # No-op on Node >= 24.8.0; the guard only matters if Node is pinned lower.
@@ -214,16 +232,16 @@ jobs:
214
232
  exit 1
215
233
  fi
216
234
 
217
- npm publish "$(realpath "$tarball")" --ignore-scripts --access public
235
+ npm publish "$(realpath "$tarball")" --ignore-scripts --access public --registry https://registry.npmjs.org
218
236
  ```
219
237
 
220
238
  ## Pinning actions to current SHAs
221
239
 
222
- The template's SHAs are stale by design. Action versions and their commit SHAs change over time, so resolve them fresh whenever a `publish.yml` is created or reviewed. Pin to the full-length commit SHA, never a tag or branch, because a tag can be moved to point at malicious code after you have reviewed it.
240
+ The template's SHAs are stale by design. Action versions and their commit SHAs change over time, so resolve them fresh whenever a `publish.yml` is created or reviewed. Pin to the full-length commit SHA, never a tag or branch, because a tag can be moved to point at malicious code after you have reviewed it. Tag-based refs such as `@v4`, `@v6`, and `@v7` are acceptable only as temporary input to a pinning tool; they must not survive in committed workflow YAML.
223
241
 
224
242
  There are two reliable ways to produce current pins.
225
243
 
226
- The preferred approach is to let tooling resolve and pin for you. Write the workflow first using human-readable tags (for example `actions/checkout@v4`), then run `npx actions-up` in the repository to rewrite every `uses:` reference to the latest stable release pinned to its commit SHA, with a version comment appended. This is the same tool the `npm-package-publishing` skill recommends, and it removes the chance of a hand-typed SHA being wrong. After it runs, confirm each line carries a `@<40-hex-sha> # vX.Y.Z` form.
244
+ The preferred approach is to let tooling resolve and pin for you. Write the workflow first using human-readable tags only in the temporary draft consumed by the tool (for example `actions/checkout@v4`), then run `npx actions-up` in the repository to rewrite every `uses:` reference to the latest stable release pinned to its commit SHA, with a version comment appended. This is the same tool the `npm-package-publishing` skill recommends, and it removes the chance of a hand-typed SHA being wrong. After it runs, confirm each line carries a `@<40-hex-sha> # vX.Y.Z` form.
227
245
 
228
246
  If resolving manually, for each action find the latest stable release tag, then read the exact commit that tag points to and pin that commit:
229
247
 
@@ -277,8 +295,9 @@ grep -nE "uses: [^@]+@[^ ]+" .github/workflows/publish.yml \
277
295
  - `422 Unprocessable Entity` during publish with provenance: the repository in the OIDC token does not match `package.json`. Check `repository.url` first.
278
296
  - npm silently publishing with a token despite trusted-publisher config: the runner's npm CLI is older than 11.5.1. This should not happen on the pinned Node 24.8.0 (which bundles npm 11.6.0); if the publish step was moved to an older Node, confirm the guard step actually upgraded npm and reported a version at or above 11.5.1.
279
297
  - Tests or build now run on a newer Node than the project targets (for example Node 24 when the project is on 22): `.nvmrc` was created or bumped to match the publish step. Reset it to the project's actual target; the publish step's 24.8.0 must stay confined to the publish job.
280
- - `package.json does not exist` from `setup-node`: the job uses `node-version-file` before checkout, or the publish job only downloaded an artifact.
281
- - `pnpm/action-setup` cannot resolve a version: the `packageManager` field is missing, or the v6 bug in [pnpm/action-setup#227](https://github.com/pnpm/action-setup/issues/227) occasionally fails to read `packageManager` from `package.json` when `package_json_file` is set, causing version resolution to fail. Pin `pnpm/action-setup` to a known-good SHA and, if needed, set the `version` input explicitly as a fallback.
298
+ - `package.json does not exist` from `pnpm/setup`: the job runs setup before checkout, or `package-json-file` points at the wrong path.
299
+ - `pnpm/setup` cannot resolve a pnpm version: the `packageManager` field is missing. Add the correct `packageManager` field or set the action's `version` input explicitly as a fallback.
300
+ - `pnpm/setup` installs the wrong runtime: `devEngines.runtime` is missing or does not match the project's existing CI target. Add or correct `devEngines.runtime`, or set the action's `runtime` input explicitly.
282
301
  - Publishing an already-published version will fail even after the workflow is fixed.
283
302
 
284
303
  ## External Setup Reminder