Skip to content

Fix vendored pnpm deployments calling pnpm install instead of pnpm rebuild - #964

Open
ivanovac wants to merge 2 commits into
masterfrom
fix-pnpm-vendored-deployments
Open

ivanovac wants to merge 2 commits into
masterfrom
fix-pnpm-vendored-deployments

Conversation

@ivanovac

@ivanovac ivanovac commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #957

Vendored pnpm deployments (projects with a pre-populated node_modules/) were incorrectly running pnpm install --no-frozen-lockfile instead of pnpm rebuild. There were two bugs in BuildDependencies():

Bug 1 — Switch case ordering

case s.UsePNPM was evaluated before case s.IsVendored. When both flags are set (i.e. pnpm-lock.yaml exists and node_modules/ is pre-populated), the pnpm install path always won.

Bug 2 — Vendored case hardcoded NPM

Even with correct ordering, case s.IsVendored called s.NPM.Rebuild() unconditionally, meaning a vendored pnpm project would have invoked npm instead of pnpm.

Changes

  • src/nodejs/pnpm/pnpm.go: add Rebuild(buildDir string) error method that runs pnpm rebuild with NODE_HOME set, mirroring the npm implementation.
  • src/nodejs/supply/supply.go: add Rebuild(string) error to the PNPM interface; split case s.UsePNPM into two cases — s.UsePNPM && s.IsVendored (calls PNPM.Rebuild) and s.UsePNPM (calls PNPM.Build) — so vendored detection takes priority over the package manager install path.

Test plan

  • Unit test: vendored pnpm project (pnpm-lock.yaml + node_modules/ present) calls PNPM.Rebuild, not PNPM.Build
  • Unit test: non-vendored pnpm project calls PNPM.Build as before
  • Unit test: vendored npm project still calls NPM.Rebuild (no regression)
  • Integration test: push a CF app with a pre-populated node_modules/ and pnpm-lock.yaml; confirm pnpm rebuild is called and the app stages successfully

…build

When a pnpm project has a pre-populated node_modules/ (vendored deployment),
the buildpack should skip the install phase and only rebuild native addons.
Two bugs prevented this:

1. The BuildDependencies switch matched `case s.UsePNPM` before `case s.IsVendored`,
   so vendored pnpm projects always ran `pnpm install --no-frozen-lockfile`.

2. The vendored case hardcoded `NPM.Rebuild()`, so even with corrected ordering
   a pnpm vendored project would have run npm instead of pnpm.

Fix by splitting `case s.UsePNPM` into two cases (vendored and non-vendored),
adding `Rebuild()` to the PNPM interface, and implementing it in pnpm.go
mirroring the npm rebuild approach.

Fixes #957
- supply_test.go: add MockPNPM, wire it into the Supplier, and add three
  BuildDependencies cases for pnpm: non-vendored calls PNPM.Build, vendored
  calls PNPM.Rebuild, and vendored does NOT fall through to NPM.
- supply/mocks_test.go: add MockPNPM (Build + Rebuild) matching the updated
  PNPM interface.
- pnpm/pnpm_test.go + pnpm_suite_test.go: new test suite covering
  Rebuild (verifies args, dir, NODE_HOME env, error propagation) and
  Build (online and offline modes).
@tnikolova82

Copy link
Copy Markdown
Contributor

🔗 Related: PR #965 - Yarn Vendored Fix

During code review of this PR, I identified that Yarn has the identical bug (as mentioned in issue #957).

I've opened PR #965 which applies the same fix pattern to Yarn:

  • Adds Yarn.Rebuild() method
  • Adds case s.UseYarn && s.IsVendored before case s.UseYarn
  • Includes comprehensive unit tests

Summary of Fixes

Package Manager Status PR
npm ✅ Always worked Built-in
pnpm ✅ Fixed This PR (#964)
yarn ✅ Fixed PR #965

Both PRs follow the same implementation pattern and should be reviewed/merged together to ensure all three package managers have consistent vendored deployment behavior.

@bvincent bvincent left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for taking care of the issue. Some remarks, let me know if any more informations is needed.

Comment thread src/nodejs/pnpm/pnpm.go

func (p *PNPM) Rebuild(buildDir string) error {
p.Log.Info("Rebuilding any native modules (pnpm)")
cmd := exec.Command("pnpm", "rebuild")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

As pnpm workspace is supported, we should maybe use recursive option in order to try to rebuild all packaged modules.

https://pnpm.io/cli/rebuild#--recursive--r


It("does NOT call npm when pnpm is vendored", func() {
supplier.IsVendored = true
mockPNPM.EXPECT().Rebuild(buildDir).Return(nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test not fit the it description, he no test npm calls.

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.

Vendored pnpm deployments

3 participants