-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
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)