Conversation
There was a problem hiding this comment.
Pull request overview
Fixes GVS (global virtual store) symlink/hash inconsistencies when approve-builds changes allowBuilds, ensuring builds land in a new hash directory and node_modules points at the correct location.
Changes:
- Default
allowBuildsto{}in GVS mode so GVS hashing doesn’t depend oncreateAllowBuildFunction()beingundefined. - In GVS mode, make
approve-buildstrigger a headlessinstall()instead ofrebuildto refresh GVS hash directories and symlinks. - Refactor
installing/commandsto callbuildProjects()directly (removing dependency on@pnpm/building.commands).
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm/test/install/globalVirtualStore.ts | Adds e2e coverage for approve-builds updating GVS hash dir + symlinks and persisting allowBuilds. |
| pnpm-lock.yaml | Updates workspace lockfile entries due to package dependency graph changes. |
| installing/deps-restorer/src/index.ts | Defaults allowBuilds to {} in headless (restorer) path when GVS is enabled. |
| installing/deps-installer/test/install/globalVirtualStore.ts | Adds unit test for hash change + artifacts after allowBuilds change in GVS mode. |
| installing/deps-installer/src/install/extendInstallOptions.ts | Defaults allowBuilds to {} in GVS mode during option extension. |
| installing/commands/tsconfig.json | Removes project reference to building/commands to break a circular dependency. |
| installing/commands/src/recursive.ts | Replaces rebuild.handler() call with buildProjects() call and passes store controller/dir explicitly. |
| installing/commands/package.json | Removes dependency on @pnpm/building.commands. |
| building/commands/tsconfig.json | Adds project reference to installing/deps-installer. |
| building/commands/test/policy/approveBuilds.test.ts | Makes helper install command configurable for enableGlobalVirtualStore. |
| building/commands/src/policy/approveBuilds.ts | In GVS mode, runs a headless install after updating allowBuilds. |
| building/commands/package.json | Adds dependency on @pnpm/installing.deps-installer. |
| .changeset/fix-gvs-approve-builds.md | Adds changeset describing the CLI behavior change in GVS mode. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
When `enableGlobalVirtualStore` is true and `allowBuilds` is not
configured, GVS hashes incorrectly included ENGINE_NAME for all
packages. This meant that approving builds later wouldn't change
the hash, so symlinks were never updated and build artifacts ended
up in the wrong directory.
Root cause: `createAllowBuildFunction()` returned `undefined` when
`allowBuilds` was not configured, causing `builtDepPaths` to be
`undefined` and `includeEngine` to be `true` for all packages.
Fix:
- Default `allowBuilds` to `{}` in GVS mode (in extendInstallOptions
and headlessInstall) so hashes are engine-agnostic by default
- In approve-builds handler, call `install()` from deps-installer
in GVS mode (with frozenLockfile) instead of rebuild, so the
headless install detects the allowBuilds change and re-links
- Break circular dependency: replace rebuild.handler() call in
installing/commands with direct buildProjects() from
building/after-install
Closes #11042
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Workspace roots may not have a package.json. Use the manifest already available from config rather than reading from disk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pass dir, lockfileDir, modulesDir to install() for correct context - Throw descriptive error when package.json can't be read - Add deps-installer and deps-restorer to changeset Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous change only passed the root project manifest, which meant workspace project build scripts didn't run. Now passes all workspace projects, matching the original recursiveRebuild behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert to single-project buildProjects call matching original
rebuild.handler() behavior. Use rootProjectManifest from config
(falls back to {} for workspace roots without package.json).
Passing all workspace projects caused wrong build order and
overwrote per-project module settings like hoistPattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…attern Use allProjects for correct workspace builds (topological ordering, all importers walked). Pass hoistPattern/publicHoistPattern as undefined so buildProjects doesn't overwrite per-project module settings that were configured during the install phase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sort projects via selectedProjectsGraph to assign correct buildIndex per topological chunk. Destructure out hoistPattern/publicHoistPattern from opts to prevent buildProjects from overwriting per-project module settings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move rebuild handler and recursive rebuild to a new package to break the circular dependency between installing/commands and building/commands. Now: - installing/commands depends on building.rebuild-command (not building.commands) - building/commands re-exports from building.rebuild-command - building/commands can depend on installing.deps-installer for GVS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Only default allowBuilds to {} in extendInstallOptions (the install()
entry point), not in headlessInstall itself. Direct callers like
installPnpm pass enableGlobalVirtualStore: true without allowBuilds
and expect engine-aware hashes. Defaulting in headlessInstall changed
those hashes and broke pnpm version switching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix grammar in --pending help text ("were not built")
- Use path.join for package.json path construction
- Add @pnpm/building.rebuild-command to changeset
- Add README.md for rebuild-command package
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add the default in both the config reader (for CLI installs) and extendInstallOptions (for programmatic install() calls). This ensures GVS hashes are engine-agnostic by default regardless of entry point. headlessInstall is intentionally not changed since direct callers like installPnpm need engine-aware hashes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Default allowBuilds to {} in headlessInstall, extendInstallOptions,
and the config reader when GVS is enabled
- Fix installPnpm to pass allowBuilds: { '@pnpm/exe': true } to both
findPnpmGvsPath and installFromLockfile so hash computation is
consistent (exe has platform-specific binaries needing engine hashes)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 25 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…bal/commands Pass approveBuilds handler via options instead of importing it directly in global/commands, and use install.handler in approve-builds for proper workspace support instead of the low-level install function. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use dependency injection for rebuild.handler (same pattern as approveBuilds) to avoid the circular dependency between building/commands and installing/commands. The withBuildingHandlers wrapper in the CLI injects both handlers into opts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…a opts Introduce CommandHandler and CommandHandlerMap types in @pnpm/cli.command. The CLI dispatcher passes the commands map as a third argument to handlers. Handlers that need other commands (globalAdd, globalUpdate, recursive) access them from this map instead of receiving callbacks via opts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 30 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)
installing/commands/src/recursive.ts:471
recursive()always runs project installs withignoreScripts: true(see line 410), so the final rebuild step is required to actually execute lifecycle scripts. With the new optionalrebuildHandler, this can silently skip rebuilding when the caller doesn't provide it, leaving installs/adds/updates in workspaces without built artifacts. Either makerebuildHandlerrequired when!opts.ignoreScriptsfor add/install/update, or throw a clear error if it's missing (and ensure all call sites pass it).
await opts.rebuildHandler?.({
...opts,
pending: opts.pending === true,
skipIfHasSideEffectsCache: true,
}, [])
installing/commands/src/update/index.ts:186
- For non-global updates,
commandsis currently ignored andinstallDeps()is called withoutrebuildHandler. In workspace/recursive mode,recursive()installs withignoreScripts: trueand depends onopts.rebuildHandlerto actually run builds. Please threadrebuildHandler: commands?.rebuildthrough the non-global update flow (either by passing it in theinstallDeps()call or by extendingoptsbefore callingupdate()).
if (opts.interactive) {
return interactiveUpdate(params, opts)
}
return update(params, opts) as Promise<undefined>
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Without this, the GVS install triggered by approve-builds wouldn't get the rebuild handler, causing builds to not run after approval. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ensures add, update, and dedupe handlers forward the rebuild command so recursive installs trigger the rebuild step. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…mistic install The optimistic repeat install check in installDeps would bail out with "Already up to date" since the lockfile hasn't changed, causing the GVS reinstall to be skipped entirely. Setting optimisticRepeatInstall: false ensures the install actually runs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Required params are not assignable to the Command type which has commands as optional. Keep it optional in the signature — the CLI always provides it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Fixes #11042
enableGlobalVirtualStoreis true andallowBuildsis not configured,createAllowBuildFunction()returnedundefined, causing all GVS hashes to includeENGINE_NAME. Whenapprove-buildslater configuredallowBuilds, the hash didn't change because the engine was already included.allowBuildsto{}in GVS mode so hashes are engine-agnostic by default, and haveapprove-buildscallinstall.handler()in GVS mode instead of the low-levelinstall()function, so it properly handles workspaces and updates symlinks.building/commands,installing/commands, andglobal/commandsusing dependency injection via acommandsmap passed as the third argument to command handlers. AddedCommandHandlerandCommandHandlerMaptypes to@pnpm/cli.command.Changes
Architecture
commandsmap as an optional third argument(opts, params, commands?)main.tspasses the full commands map to every handlerglobalAddneedsapprove-builds,recursiveneedsrebuild) access them from this mapPackages changed
@pnpm/cli.command— newCommandHandlerandCommandHandlerMaptypes@pnpm/building.commands—approve-buildsusesinstall.handlerfor GVS@pnpm/global.commands— removedbuilding/commandsdependency; receivesapprove-buildsvia commands map@pnpm/installing.commands— receivesrebuildvia commands map instead of direct import@pnpm/installing.deps-installer/@pnpm/installing.deps-restorer— defaultallowBuildsto{}in GVS modepnpmCLI — dispatcher passes commands map to all handlersTest plan
pnpm/test/install/globalVirtualStore.ts): install with GVS →approve-builds --all→ verify new hash directory + build artifacts accessible vianode_modules🤖 Generated with Claude Code