Skip to content

feat(setup,cache-keys): add pnpm support#75

Open
nuc wants to merge 7 commits into
v1from
chore/pnpm-support
Open

feat(setup,cache-keys): add pnpm support#75
nuc wants to merge 7 commits into
v1from
chore/pnpm-support

Conversation

@nuc

@nuc nuc commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

Extend the shared CI action to support pnpm alongside npm and yarn — the Phase 1 foundation for the org-wide npm/yarn → pnpm migration (supply-chain hardening).

cache-keys

  • Include pnpm-lock.yaml in the packageJsonLock hash (backwards compatible — cat ignores missing files).

setup

  • Detect pnpm, checked before yarn/npm (a repo may briefly have both lockfiles mid-migration).
  • corepack enable (makes the packageManager field authoritative).
  • Reorder so package-manager detection runs before cache-path computation; add a pnpm store path step and branch the cached paths on the detected manager.
  • Install via pnpm install --frozen-lockfile (case stmt: pnpm | yarn | npm).
  • pnpm run --if-present prepare for pnpm in the prepare/postinstall step.
  • Cache key prefix npm--pkg-- and v2v3 (cache bust).
  • npm audit signatures stays npm-only (unchanged).

Backwards compatible

Existing npm and yarn repos are unaffected — detection order and their install/prepare commands are preserved.

Versioning (post-merge)

Per the migration's §8.1 hardening, after merge we cut an immutable annotated tag (the repo's first semver tag, distinct from the moving v1 branch) and pin consumers to it (migration PRs bump @v1@v1.2.0):

git tag -a v1.2.0 -m "pnpm support (setup + cache-keys)" && git push origin v1.2.0

Follow-ups (not in this PR)

  • setup internally references verkstedt/actions/cache-keys@v1 (still the moving branch). Pin it to @v1.2.0 once that tag exists (or when the actions repo itself is migrated).
  • actionlint (the repo's lint:actions) was not run locally (no Docker in the authoring env) — relying on this PR's CI for it.

Test plan

  • Existing npm repo CI passes with this action (regression).
  • Existing yarn repo CI passes (regression).
  • A pnpm repo CI passes.

🤖 Generated with Claude Code

Extend the shared CI action to support pnpm alongside npm and yarn, as the
foundation for the org-wide npm/yarn -> pnpm migration (supply-chain hardening).

cache-keys:
- include pnpm-lock.yaml in the packageJsonLock hash (backwards compatible)

setup:
- detect pnpm (checked before yarn/npm — a repo may briefly have both lockfiles
  mid-migration)
- enable Corepack (makes the packageManager field authoritative)
- reorder so package-manager detection precedes cache-path computation, then add
  a pnpm-store-dir step and branch cache paths on the detected manager
- install via `pnpm install --frozen-lockfile` (case stmt: pnpm | yarn | npm)
- run `pnpm run --if-present prepare` for pnpm in the prepare/postinstall step
- bump cache key prefix npm-- -> pkg-- and v2 -> v3 (cache bust)
- npm audit signatures stays npm-only (unchanged)

Backwards compatible: existing npm and yarn repos are unaffected — detection
order and their install/prepare commands are preserved.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 47ac5f10-7680-44b8-a0f4-8833f1eddf6e

📥 Commits

Reviewing files that changed from the base of the PR and between 2683f47 and 81501c3.

📒 Files selected for processing (2)
  • cache-keys/action.yaml
  • setup/action.yaml

📝 Walkthrough

Summary by CodeRabbit

New Features

  • Added support for pnpm as a package manager alongside npm and yarn
  • Automatic project detection based on lockfile presence
  • Optimized caching strategy tailored for pnpm workflows
  • Updated cache key naming scheme for improved compatibility across package managers

Walkthrough

Adds pnpm support to two composite GitHub Actions: cache-keys now includes pnpm-lock.yaml in its lockfile hash; setup detects pnpm, enables Corepack, uses the pnpm store for caching, updates cache keys to pkg--...--v3, and runs pnpm-specific installs.

Changes

pnpm Support Across Actions

Layer / File(s) Summary
Cache keys for pnpm lockfile
cache-keys/action.yaml
The packageJsonLock output documentation and hash computation now include pnpm-lock.yaml alongside package-lock.json and yarn.lock.
Setup action output documentation
setup/action.yaml
The package-manager output description is updated to explicitly list npm, yarn, and pnpm.
Package manager detection and cache infrastructure
setup/action.yaml
Detect pnpm/yarn/npm by lockfile presence; enable corepack for pnpm; discover pnpm store path; set package-manager-specific cache paths; switch restore key prefix from npm--...--v2 to pkg--...--v3.
Dependency installation with pnpm support
setup/action.yaml
Install step uses a case statement to run pnpm install --frozen-lockfile, yarn install --immutable, or npm ci based on detected package manager.
Prepare script execution with pnpm note
setup/action.yaml
Adds a comment that the "Run prepare npm script" step intentionally does not handle pnpm because pnpm runs lifecycle hooks at install time.
Cache save key update
setup/action.yaml
Cache save key updated to the pkg--...--v3 format to match the restore logic.

Sequence Diagram

sequenceDiagram
  participant Workflow as GitHub Workflow
  participant Detector as PM Detector
  participant Corepack as Corepack
  participant Cache as Actions/Cache
  participant Installer as Installer
  participant Prepare as Prepare Script

  Workflow->>Detector: Determine package manager (check pnpm-lock/yarn.lock/package-lock)
  Detector-->>Workflow: package-manager (pnpm|yarn|npm)
  Workflow->>Corepack: corepack enable (if pnpm)
  Corepack-->>Workflow: ready
  Workflow->>Cache: Restore cache (key: pkg--*--v3)
  Cache-->>Workflow: restored node_modules or pnpm store
  Workflow->>Installer: Install dependencies (pm-specific)
  Installer-->>Workflow: install complete
  Workflow->>Prepare: Run prepare script (pm-specific)
  Prepare-->>Workflow: prepare complete
  Workflow->>Cache: Save cache (key: pkg--*--v3)
  Cache-->>Workflow: cached
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I found a pnpm lock beneath the log,
Three small files now shuffled in the fog.
Hashes join, the cache learns where to roam,
Installs snap to place and bring builds home.
🥕 A happy hop — CI finds its path anew!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(setup,cache-keys): add pnpm support' accurately and concisely summarizes the main change: adding pnpm support to the setup and cache-keys actions.
Description check ✅ Passed The description is directly related to the changeset, providing clear context about pnpm support addition, backwards compatibility, implementation details, and test plan.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/pnpm-support

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
setup/action.yaml (1)

198-224: ⚠️ Potential issue | 🟠 Major

Fix needs usage in composite-action if condition
setup/action.yaml is a runs.using: composite action, and the if: condition for “Run prepare npm script” uses needs.initial-setup.outputs.scriptsneeds isn’t supported for composite run-step conditions, so this logic can’t reliably evaluate. Use the composite step output already computed by id: scripts instead (steps.scripts.outputs.scripts).

Proposed fix
     - name: Run prepare npm script
       if: >
         steps.cache.outputs.cache-hit == 'true'
         && (
-          contains(needs.initial-setup.outputs.scripts, ',prepare,')
-          || contains(needs.initial-setup.outputs.scripts, ',postinstall,')
+          contains(steps.scripts.outputs.scripts, ',prepare,')
+          || contains(steps.scripts.outputs.scripts, ',postinstall,')
         )
       working-directory: ${{ inputs.working-directory }}
       shell: bash
       run: |
         case "${{ steps.package-manager.outputs['package-manager'] }}" in
           pnpm)
             pnpm run --if-present prepare
             ;;
           yarn)
             # yarn:
             # - doesn’t support `prepare` script (we use postinstall)
             # - doesn’t have --if-present
             if yarn run 2>/dev/null | grep -Eq '(^|\s)postinstall($|\s)'
             then
               yarn run postinstall
             fi
             ;;
           *)
             npm run --if-present prepare
             ;;
         esac
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@setup/action.yaml` around lines 198 - 224, The if condition for the "Run
prepare npm script" step wrongly references needs.initial-setup.outputs.scripts
(not available in composite actions); update the condition to use the composite
step output id: scripts (i.e. steps.scripts.outputs.scripts) instead, keeping
the rest of the logic (cache hit check and contains(...) checks) intact so the
step evaluates correctly within the composite action.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@setup/action.yaml`:
- Around line 198-224: The if condition for the "Run prepare npm script" step
wrongly references needs.initial-setup.outputs.scripts (not available in
composite actions); update the condition to use the composite step output id:
scripts (i.e. steps.scripts.outputs.scripts) instead, keeping the rest of the
logic (cache hit check and contains(...) checks) intact so the step evaluates
correctly within the composite action.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 72d23743-d5d5-44ff-abac-ed0a364b586c

📥 Commits

Reviewing files that changed from the base of the PR and between 2683f47 and da32cbb.

📒 Files selected for processing (2)
  • cache-keys/action.yaml
  • setup/action.yaml

nuc added 2 commits June 10, 2026 11:50
…eview)

Two fixes from the pre-merge review:

- BLOCKER: the pnpm cache stores only the pnpm store (not node_modules), so a
  warm-cache run skipped `Install` and node_modules was never materialised.
  Run `pnpm install --frozen-lockfile` even on a cache hit for pnpm (fast/offline
  off the warm store); npm/yarn still skip install on a hit (they cache node_modules).

- Scope `corepack enable` to pnpm repos only (was unconditional) — leaves npm/yarn
  repos, incl. any on Node < 16.9 without Corepack, completely unchanged.
Revert the pnpm branch added to "Run prepare npm script". That step's
pre-existing `if:` references `needs.initial-setup.outputs.scripts`, which
isn't available in a composite action, so the step never runs — and fixing
that is out of scope for this backwards-compatible pnpm PR (it would change
npm/yarn cache-hit behavior). pnpm doesn't need it anyway: `pnpm install`
(which always runs for pnpm, incl. on a cache hit) executes prepare/postinstall
itself. The step is now byte-identical to upstream. Follow-up: fix the
condition to `steps.scripts.outputs.scripts` for npm/yarn separately.
@nuc

nuc commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai — thanks, that's a valid catch on the prepare step's if: referencing needs.initial-setup.outputs.scripts (not available in a composite action, so the step never evaluates true).

Disposition for this PR: deferring the condition fix, reverting my change to that step.

  • It's a pre-existing bug, unrelated to pnpm. This PR only touched that step's run: body.
  • Fixing it (→ steps.scripts.outputs.scripts, as you suggest — which is correct) makes the step live, which would change npm/yarn cache-hit behavior (prepare/postinstall would start running on cache hits) across ~28 consumer repos. This PR is explicitly scoped as "add pnpm, npm/yarn unaffected," so that behavior change belongs in its own reviewable/testable follow-up.
  • pnpm doesn't need this step: with the warm-cache fix in this PR, pnpm install always runs (incl. on a cache hit) and executes the project's own prepare/postinstall. Handling pnpm here too would double-run it.

So in 03882a6 I reverted the pnpm branch I'd added — the step is now byte-identical to upstream — and left a code comment noting pnpm is handled by install. Tracking the needs.initial-setupsteps.scripts.outputs.scripts fix as a separate follow-up.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@nuc: That reasoning is solid — the deferred-fix approach is the right call. To summarise what I'm noting:

  • Pre-existing bug: in setup/action.yaml, the "Run prepare npm script" step's if: condition references needs.initial-setup.outputs.scripts, which is not available in a composite action context, so the step never evaluates true. The correct reference is steps.scripts.outputs.scripts.
  • Why it's deferred: fixing it activates the step, which would change npm/yarn cache-hit behaviour across ~28 consumer repos — out of scope for this pnpm-focused PR.
  • pnpm coverage: pnpm always runs pnpm install --frozen-lockfile (even on a cache hit, per the warm-cache commit), so prepare/postinstall are already executed by the installer itself. Running the step for pnpm too would double-run it.
  • Current state (03882a6): step is byte-identical to upstream with a code comment explaining the pnpm exemption. ✅

Would you like me to open a follow-up GitHub issue to track the needs.initial-setup.outputs.scriptssteps.scripts.outputs.scripts fix so it doesn't get lost?

(´• ω •`) 🐇


✏️ Learnings added
Learnt from: nuc
URL: https://github.com/verkstedt/actions/pull/75

Timestamp: 2026-06-10T10:06:07.293Z
Learning: In `setup/action.yaml` of the `verkstedt/actions` repository, the "Run prepare npm script" step has a pre-existing bug: its `if:` condition references `needs.initial-setup.outputs.scripts`, which is not available in a composite action context and therefore always evaluates false. The correct reference is `steps.scripts.outputs.scripts`. Fixing this condition will activate the step and change npm/yarn cache-hit behavior (prepare/postinstall will start running on cache hits), affecting ~28 consumer repositories. This fix is intentionally deferred to a separate PR. pnpm does not need this step because `pnpm install --frozen-lockfile` always runs (even on a cache hit) and naturally executes the project's prepare/postinstall scripts.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
setup/action.yaml (1)

118-123: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider standardizing bracket notation for the hyphenated output name.

The file accesses package-manager output inconsistently—dot notation here (lines 118, 123) vs bracket notation elsewhere (lines 170, 195). While both work, bracket notation (outputs['package-manager']) is preferred for hyphenated property names to avoid potential parsing ambiguity and improve consistency.

♻️ Suggested standardization
     - name: Enable Corepack
       # pnpm only — leaves npm/yarn repos (incl. any pinned to Node < 16.9 without
       # Corepack, or relying on system yarn resolution) completely unchanged.
-      if: steps.package-manager.outputs.package-manager == 'pnpm'
+      if: steps.package-manager.outputs['package-manager'] == 'pnpm'
       shell: bash
       run: corepack enable

     - name: Get pnpm store directory
-      if: steps.package-manager.outputs.package-manager == 'pnpm'
+      if: steps.package-manager.outputs['package-manager'] == 'pnpm'
       id: pnpm-store
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@setup/action.yaml` around lines 118 - 123, The workflow uses dot notation for
the hyphenated output name (steps.package-manager.outputs.package-manager)
inconsistently; update all accesses (including occurrences in the conditional
"if: steps.package-manager.outputs.package-manager == 'pnpm'" and the subsequent
Get pnpm store directory step) to use bracket notation
outputs['package-manager'] (i.e.,
steps.package-manager.outputs['package-manager']) so hyphenated keys are
accessed consistently and safely across the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@setup/action.yaml`:
- Around line 118-123: The workflow uses dot notation for the hyphenated output
name (steps.package-manager.outputs.package-manager) inconsistently; update all
accesses (including occurrences in the conditional "if:
steps.package-manager.outputs.package-manager == 'pnpm'" and the subsequent Get
pnpm store directory step) to use bracket notation outputs['package-manager']
(i.e., steps.package-manager.outputs['package-manager']) so hyphenated keys are
accessed consistently and safely across the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cfd1838b-1f24-49e8-97db-38b8e1229792

📥 Commits

Reviewing files that changed from the base of the PR and between 70e1299 and 03882a6.

📒 Files selected for processing (1)
  • setup/action.yaml

… nit)

Standardize the hyphenated output access to bracket notation in the two pnpm
if: conditions and the cache-paths shell check, matching the existing Install/
Verify steps. Cosmetic; both forms work. (The output-declaration value on the
outputs: block keeps its upstream dot form.)
@nuc

nuc commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

The bracket-notation nit was applied in 43de847 (the two pnpm if: conditions and the cache-paths shell check now use outputs['package-manager']). Please re-review the current head — I believe everything's addressed (prepare-step deferral accepted earlier, blocker fix in place).

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@nuc: Acknowledged — the bracket-notation fix in 43de847 looks like a clean close-out of that nit, and the prepare-step deferral rationale is already on record. Kicking off a full re-review of the current head now.

(´• ω •`) 🐇

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@setup/action.yaml`:
- Line 141: The shell word-splits the unquoted package.json path in the jq
invocation, breaking cache-path discovery when inputs.working-directory contains
spaces; update the jq command that uses '.workspaces // [] | .[] +
"/node_modules/"' so the package.json argument is quoted (e.g. wrap ${{
inputs.working-directory }}/package.json in quotes) to prevent splitting and
ensure jq receives the full path.
- Around line 148-153: The step "Set up GitHub npm registry authentication"
currently appends the PAT to the runner home ~/.npmrc; instead write the token
to a job-local temporary file (e.g., use RUNNER_TEMP or a created temp file) and
point npm/yarn/pnpm at that file by exporting/setting the appropriate userconfig
env var (npm_config_userconfig / NPM_CONFIG_USERCONFIG) or passing --userconfig
to the client; update the step that uses
inputs.github-npm-registry-personal-access-token to create the temp file, write
the auth line there, and ensure subsequent steps consume that temp config rather
than ~/.npmrc so the PAT is not persisted to the home directory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8145475d-82b3-4417-8281-9693a8b51a75

📥 Commits

Reviewing files that changed from the base of the PR and between 2683f47 and 43de847.

📒 Files selected for processing (2)
  • cache-keys/action.yaml
  • setup/action.yaml

Comment thread setup/action.yaml Outdated
Comment thread setup/action.yaml
Quote ${{ inputs.working-directory }}/package.json in the cache-paths jq call so
a working-directory containing spaces doesn't word-split. Introduced by this PR's
reorder (the prefix was added to read the correct package.json). The other
CodeRabbit finding (PAT written to ~/.npmrc) is pre-existing/unchanged here and
deferred — changing the auth mechanism is out of scope for this pnpm PR.
@nuc

nuc commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai — both addressed:

1. Unquoted ${{ inputs.working-directory }}/package.json in cache-paths jq (line 141) — fixed in 6a7a1b1. Agreed: this PR's reorder added the working-directory prefix (upstream used a bare package.json), so the word-split risk is in-scope. Now quoted: "${{ inputs.working-directory }}/package.json".

2. PAT written to ~/.npmrc (auth step) — valid hardening, but deferring. This line is pre-existing and byte-identical to upstream v1 — this PR only relocated the step in the cache/PM reorder, it didn't change the auth mechanism. Switching to a job-local temp userconfig (RUNNER_TEMP + npm_config_userconfig) changes credential handling for all npm/yarn/pnpm consumers, which is out of scope for this pnpm-support PR. Tracking it as a separate security follow-up alongside the prepare-step condition fix.

Net: the only change introduced by this PR that you flagged is fixed; the remaining item is pre-existing and deferred.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@nuc: Both dispositions are well-reasoned — thanks for the clear breakdown.

  1. Unquoted path (line 141) — ✅ closed. The quote fix in 6a7a1b1 is the right scope boundary (this PR introduced the working-directory prefix, so the word-split risk is entirely in-scope). No further action needed here.

  2. PAT → ~/.npmrc — deferral accepted. The line is pre-existing and byte-identical to upstream v1; this PR only reordered it. Hardening it (scope to RUNNER_TEMP + npm_config_userconfig) changes credential lifecycle for all consumers and belongs in a dedicated, reviewable follow-up.

Would you like me to open a single tracking issue covering both deferred items:

  • needs.initial-setup.outputs.scripts condition fix (prepare step if:)
  • ~/.npmrc credential scoping hardening

I can open it now and link it back to this PR.

(´• ω •`) 🐇


✏️ Learnings added
Learnt from: nuc
URL: https://github.com/verkstedt/actions/pull/75

Timestamp: 2026-06-10T10:44:12.788Z
Learning: In `setup/action.yaml` of the `verkstedt/actions` repository, the step that writes the NPM auth token to `~/.npmrc` (the "Set up npm auth" or similar auth step) is pre-existing and byte-identical to upstream `v1`; this PR (`#75`, pnpm support) only relocated it during cache/PM reorder. Hardening this to use a job-local temp userconfig (`RUNNER_TEMP` + `npm_config_userconfig`) changes credential handling for all npm/yarn/pnpm consumers and is intentionally deferred to a separate security follow-up PR.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

nuc added 2 commits June 10, 2026 12:54
…deRabbit)

Avoid persisting the GitHub Packages PAT in $HOME on self-hosted/persistent
runners (composite actions have no post-job cleanup). Write auth to a job-local
$RUNNER_TEMP/setup-registry.npmrc (chmod 600, seeded from any existing ~/.npmrc)
and export NPM_CONFIG_USERCONFIG so npm + pnpm use it for the rest of the job.
yarn (berry) is unaffected — it authenticates via GH_REGISTRY_TOKEN in the
install step, not via npmrc. Also pass the PAT through env: instead of
interpolating it into the run script.
…dex)

- Truncate/seed the job-local npmrc on each invocation so re-running the action
  in one job can't append a duplicate _authToken line.
- Reword the comment to the verified behavior (npm + pnpm 11.5.2 read the
  _authToken from the NPM_CONFIG_USERCONFIG file) instead of the npm-conf claim.

Kept NPM_CONFIG_USERCONFIG (empirically confirmed to redirect pnpm 11.5.2 auth,
incl. the _authToken); did NOT add npmrcAuthFile — pnpm 11.5.2 returns it
undefined and ignores a token placed only there.
@nuc

nuc commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

Current head is 81501c3. Since your last pass I've implemented your earlier suggestion (the PAT no longer persists to ~/.npmrc):

  • Auth now writes to a job-local $RUNNER_TEMP/setup-registry.npmrc (wiped with the job), chmod 600, seeded from any existing ~/.npmrc, and exports NPM_CONFIG_USERCONFIG so npm + pnpm use it. PAT is passed via env: (not interpolated into the script). yarn (berry) is unaffected — it auths via GH_REGISTRY_TOKEN in the install step. Verified on pnpm 11.5.2 that it reads the _authToken from the userconfig file.
  • The file is truncated/seeded fresh each invocation so re-running the action in one job can't append a duplicate token line.
  • Earlier in the PR: quoted the working-directory path in the cache-paths jq, and switched to bracket notation for the hyphenated package-manager output.

One item I've deferred (happy to discuss): the prepare-step needs.initial-setup condition — fixing it changes npm/yarn cache-hit behavior, out of scope for this pnpm PR.

Please re-review the current head.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@nuc: Acknowledged — the auth hardening looks well-scoped and complete. Summarising what I'm noting before kicking off the re-review:

  • Job-local npmrc ($RUNNER_TEMP/setup-registry.npmrc) — scoped to the job, chmod 600, seeded from any pre-existing ~/.npmrc, NPM_CONFIG_USERCONFIG exported so both npm and pnpm pick it up, PAT injected via env: (not shell-interpolated). ✅
  • Idempotent seeding — file is truncated and re-seeded on each invocation, so re-running the action in a single job can't accumulate duplicate token lines. ✅
  • yarn (berry) unaffected — still auths via GH_REGISTRY_TOKEN in the install step. ✅
  • Earlier fixes already reviewed: bracket notation (outputs['package-manager']) ✅, quoted working-directory path in jq ✅.
  • Deferred: needs.initial-setup.outputs.scriptssteps.scripts.outputs.scripts condition fix — rationale on record, out of scope. ✅

Running a full re-review of head 81501c3 now.

(´• ω •`) 🐇

✅ Action performed

Full review finished.

@marek-saji marek-saji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not a fan of LLM verbosity. Especially when it comes to comments.

Comment thread setup/action.yaml
Comment on lines +112 to +113
# pnpm is checked first because during migration a repo may temporarily have
# both pnpm-lock.yaml and package-lock.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think we need this explanation here.

Comment thread setup/action.yaml
Comment on lines +115 to +120
- name: Enable Corepack
# pnpm only — leaves npm/yarn repos (incl. any pinned to Node < 16.9 without
# Corepack, or relying on system yarn resolution) completely unchanged.
if: steps.package-manager.outputs['package-manager'] == 'pnpm'
shell: bash
run: corepack enable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- name: Enable Corepack
# pnpm only — leaves npm/yarn repos (incl. any pinned to Node < 16.9 without
# Corepack, or relying on system yarn resolution) completely unchanged.
if: steps.package-manager.outputs['package-manager'] == 'pnpm'
shell: bash
run: corepack enable
- name: Enable Corepack for pnpm
if: steps.package-manager.outputs['package-manager'] == 'pnpm'
shell: bash
run: corepack enable

Comment thread setup/action.yaml
Comment on lines +135 to +144
if [ "${{ steps.package-manager.outputs['package-manager'] }}" = "pnpm" ]
then
echo '${{ steps.pnpm-store.outputs.path }}'
else
{
echo 'node_modules/'
jq -r '.workspaces // [] | .[] + "/node_modules/"' "${{ inputs.working-directory }}/package.json"
echo '.yarn/install-state.gz'
} | sed 's~^~${{ inputs.working-directory }}/~'
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-           if [ "${{ steps.package-manager.outputs['package-manager'] }}" = "pnpm" ]
+           if [ -n "${{ steps.pnpm-store.outputs.path }}" ]

or even:

Suggested change
if [ "${{ steps.package-manager.outputs['package-manager'] }}" = "pnpm" ]
then
echo '${{ steps.pnpm-store.outputs.path }}'
else
{
echo 'node_modules/'
jq -r '.workspaces // [] | .[] + "/node_modules/"' "${{ inputs.working-directory }}/package.json"
echo '.yarn/install-state.gz'
} | sed 's~^~${{ inputs.working-directory }}/~'
fi
{
# npn and yarn
echo 'node_modules/'
jq -r '.workspaces // [] | .[] + "/node_modules/"' "${{ inputs.working-directory }}/package.json"
# yarn
echo '.yarn/install-state.gz'
} | sed 's~^~${{ inputs.working-directory }}/~'
# pnpm
echo '${{ steps.pnpm-store.outputs.path }}'

Comment thread setup/action.yaml
Comment on lines 215 to 218
if [ 0 -lt "$( jq -r '( .dependencies + .devDependencies ) // {} | to_entries | length' package.json )" ]
then
npm audit signatures
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also supported by pnpm

https://pnpm.io/cli/audit#signatures

Comment thread setup/action.yaml
path: ${{ steps.cache-paths.outputs.paths }}
key: |
npm--${{ steps.cache-keys.outputs.os }}--${{ steps.cache-keys.outputs.node }}--${{ steps.cache-keys.outputs.packageJson }}--${{ steps.cache-keys.outputs.packageJsonLock }}--v2
pkg--${{ steps.cache-keys.outputs.os }}--${{ steps.cache-keys.outputs.node }}--${{ steps.cache-keys.outputs.packageJson }}--${{ steps.cache-keys.outputs.packageJsonLock }}--v3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think npm prefix is still valid — all of them install from npm registry.

Comment thread setup/action.yaml
Comment on lines +230 to +233
# NOTE: pnpm is intentionally NOT handled here — `pnpm install` (which always runs for pnpm,
# incl. on a cache hit) executes the project's own prepare/postinstall. This step only matters
# for npm/yarn, where install is skipped on a cache hit. (Its pre-existing `needs.initial-setup`
# condition is a separate latent bug — see PR discussion; deferred to a focused follow-up.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s use if: to skip the step completely.

Comment thread setup/action.yaml
Comment on lines +232 to +233
# for npm/yarn, where install is skipped on a cache hit. (Its pre-existing `needs.initial-setup`
# condition is a separate latent bug — see PR discussion; deferred to a focused follow-up.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What bug? What PR discussion?

Comment thread setup/action.yaml
Comment on lines +148 to +168
- name: Set up GitHub npm registry authentication
if: inputs.github-npm-registry-personal-access-token
shell: bash
env:
# Pass via env (not `${{ }}` interpolation into the script) so the token isn't rendered into
# the run command line.
GITHUB_NPM_REGISTRY_PERSONAL_ACCESS_TOKEN: ${{ inputs.github-npm-registry-personal-access-token }}
run: |
# Write registry auth to a job-local npmrc under $RUNNER_TEMP (wiped with the job) and point
# npm/pnpm at it via NPM_CONFIG_USERCONFIG — instead of persisting the PAT to ~/.npmrc, which
# would leave the token in $HOME on self-hosted/persistent runners (composite actions can't run
# a post-job cleanup step). Verified on pnpm 11.5.2 + npm: both read the registry _authToken from
# the NPM_CONFIG_USERCONFIG file. yarn (berry) reads the token from GH_REGISTRY_TOKEN in the
# install step, not from npmrc, so it is unaffected.
NPMRC="$RUNNER_TEMP/setup-registry.npmrc"
# Start from a clean file each invocation (seeded from any existing ~/.npmrc) so re-invoking the
# action in the same job doesn't append a duplicate token line.
if [ -f "$HOME/.npmrc" ]; then cp "$HOME/.npmrc" "$NPMRC"; else : > "$NPMRC"; fi
chmod 600 "$NPMRC"
printf '//npm.pkg.github.com/:_authToken=%s\n' "$GITHUB_NPM_REGISTRY_PERSONAL_ACCESS_TOKEN" >> "$NPMRC"
echo "NPM_CONFIG_USERCONFIG=$NPMRC" >> "$GITHUB_ENV"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- name: Set up GitHub npm registry authentication
if: inputs.github-npm-registry-personal-access-token
shell: bash
env:
# Pass via env (not `${{ }}` interpolation into the script) so the token isn't rendered into
# the run command line.
GITHUB_NPM_REGISTRY_PERSONAL_ACCESS_TOKEN: ${{ inputs.github-npm-registry-personal-access-token }}
run: |
# Write registry auth to a job-local npmrc under $RUNNER_TEMP (wiped with the job) and point
# npm/pnpm at it via NPM_CONFIG_USERCONFIG — instead of persisting the PAT to ~/.npmrc, which
# would leave the token in $HOME on self-hosted/persistent runners (composite actions can't run
# a post-job cleanup step). Verified on pnpm 11.5.2 + npm: both read the registry _authToken from
# the NPM_CONFIG_USERCONFIG file. yarn (berry) reads the token from GH_REGISTRY_TOKEN in the
# install step, not from npmrc, so it is unaffected.
NPMRC="$RUNNER_TEMP/setup-registry.npmrc"
# Start from a clean file each invocation (seeded from any existing ~/.npmrc) so re-invoking the
# action in the same job doesn't append a duplicate token line.
if [ -f "$HOME/.npmrc" ]; then cp "$HOME/.npmrc" "$NPMRC"; else : > "$NPMRC"; fi
chmod 600 "$NPMRC"
printf '//npm.pkg.github.com/:_authToken=%s\n' "$GITHUB_NPM_REGISTRY_PERSONAL_ACCESS_TOKEN" >> "$NPMRC"
echo "NPM_CONFIG_USERCONFIG=$NPMRC" >> "$GITHUB_ENV"
- name: Set up GitHub npm registry authentication
if: inputs.github-npm-registry-personal-access-token
shell: bash
env:
GITHUB_NPM_REGISTRY_PERSONAL_ACCESS_TOKEN: ${{ inputs.github-npm-registry-personal-access-token }}
run: |
# Keep config where it’s wiped with the job to avoid it being persisted
NPMRC="$RUNNER_TEMP/setup-registry.npmrc"
touch "$NPMRC"
chmod 600 "$NPMRC"
{
cat "$HOME/.npmrc" 2>/dev/null
printf '//npm.pkg.github.com/:_authToken=%s\n' \
"$GITHUB_NPM_REGISTRY_PERSONAL_ACCESS_TOKEN"
} > "$NPMRC"
echo "NPM_CONFIG_USERCONFIG=$NPMRC" >> "$GITHUB_ENV"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants