Bug
When using pnpm fetch (or pnpm install) with enableGlobalVirtualStore: true and enableModulesDir: false, the Global Virtual Store's links/ directory is never populated. The CAS (files/ and index/) is written correctly, but packages are not materialized into links/.
Expected behavior
enableModulesDir: false should skip creation of project-level node_modules/ directories and symlinks, but should still populate the store's links/ directory when GVS is enabled. The links/ directory is part of the store, not the project's node_modules/.
Actual behavior
Both links/ materialization and node_modules/ creation are skipped.
Root cause
In pkg-manager/headless/src/index.ts, linkAllPkgs is inside the enableModulesDir !== false guard (line ~415):
} else if (opts.enableModulesDir !== false) {
await Promise.all(depNodes.map(async (depNode) => fs.mkdir(depNode.modules, { recursive: true })))
await Promise.all([
linkAllModules(depNodes, { ... }), // project-level symlinks — correctly guarded
linkAllPkgs(opts.storeController, depNodes, { ... }), // CAS → target import — should NOT be guarded for GVS
])
}
linkAllPkgs calls storeController.importPackage(depNode.dir, ...) for each package. When GVS is enabled, depNode.dir resolves to a path inside {storeDir}/v11/links/ (computed via iteratePkgsForVirtualStore). This is store population, not project node_modules/ creation.
Because linkAllPkgs is guarded by enableModulesDir !== false, setting enableModulesDir: false prevents store-level GVS materialization as a side effect.
Suggested fix
Hoist linkAllPkgs out of the enableModulesDir conditional when GVS is enabled:
// Always import packages from CAS into their target dirs when GVS is enabled.
// When GVS is enabled, depNode.dir is in {storeDir}/v11/links/, not node_modules/.
if (opts.enableGlobalVirtualStore || opts.enableModulesDir !== false) {
await linkAllPkgs(opts.storeController, depNodes, { ... })
}
if (opts.enableModulesDir !== false) {
await Promise.all(depNodes.map(async (depNode) => fs.mkdir(depNode.modules, { recursive: true })))
if (opts.symlink !== false) {
await linkAllModules(depNodes, { ... })
}
}
The importPackage implementation (tryImportIndexedDir in fs/indexed-pkg-importer) already creates the target directory via makeEmptyDir(newDir, { recursive: true }), so the mkdir(depNode.modules) at line 416 is only needed for project-level node_modules/ structure, not for GVS.
Use case
Nix-based build systems use pnpm fetch --offline with enableGlobalVirtualStore: true and enableModulesDir: false to pre-build a CAS + GVS store without creating unnecessary project-level node_modules/. Pre-populating links/ enables the GVS zero-fetch fast path (lockfileToDepGraph.ts:259-263) at dev time. Currently the workaround is to omit enableModulesDir: false, which creates an unnecessary node_modules/ that must be discarded.
Version
pnpm 11.0.0-alpha.12 (verified against current main)
Bug
When using
pnpm fetch(orpnpm install) withenableGlobalVirtualStore: trueandenableModulesDir: false, the Global Virtual Store'slinks/directory is never populated. The CAS (files/andindex/) is written correctly, but packages are not materialized intolinks/.Expected behavior
enableModulesDir: falseshould skip creation of project-levelnode_modules/directories and symlinks, but should still populate the store'slinks/directory when GVS is enabled. Thelinks/directory is part of the store, not the project'snode_modules/.Actual behavior
Both
links/materialization andnode_modules/creation are skipped.Root cause
In
pkg-manager/headless/src/index.ts,linkAllPkgsis inside theenableModulesDir !== falseguard (line ~415):linkAllPkgscallsstoreController.importPackage(depNode.dir, ...)for each package. When GVS is enabled,depNode.dirresolves to a path inside{storeDir}/v11/links/(computed viaiteratePkgsForVirtualStore). This is store population, not projectnode_modules/creation.Because
linkAllPkgsis guarded byenableModulesDir !== false, settingenableModulesDir: falseprevents store-level GVS materialization as a side effect.Suggested fix
Hoist
linkAllPkgsout of theenableModulesDirconditional when GVS is enabled:The
importPackageimplementation (tryImportIndexedDirinfs/indexed-pkg-importer) already creates the target directory viamakeEmptyDir(newDir, { recursive: true }), so themkdir(depNode.modules)at line 416 is only needed for project-levelnode_modules/structure, not for GVS.Use case
Nix-based build systems use
pnpm fetch --offlinewithenableGlobalVirtualStore: trueandenableModulesDir: falseto pre-build a CAS + GVS store without creating unnecessary project-levelnode_modules/. Pre-populatinglinks/enables the GVS zero-fetch fast path (lockfileToDepGraph.ts:259-263) at dev time. Currently the workaround is to omitenableModulesDir: false, which creates an unnecessarynode_modules/that must be discarded.Version
pnpm 11.0.0-alpha.12 (verified against current
main)