You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on May 14, 2026. It is now read-only.
enableGlobalVirtualStore is on by default in pnpm v11 (set in config/reader; CI is the only auto-detected exception, and explicit user config still wins). Roadmap (#299) lists "Support the global virtual store dir" under Stage 1. This issue scopes it to the minimum needed for pacquet install --frozen-lockfile to be correct when the user has GVS enabled; prune, engine-agnostic hashing, and CONFIG_CONFLICT_* validation are explicitly out of scope and tracked for follow-up.
What GVS changes
With GVS off (pacquet today), packages live at <project>/node_modules/.pnpm/<scope>+<pkg>@<ver>/node_modules/<pkg>/ and the project's direct-dep symlinks point into that local .pnpm.
With GVS on, the virtual store is shared across every project on the machine. Packages live at <storeDir>/links/<scope>/<pkg>/<ver>/<peer-hash>/node_modules/<pkg>/ and the project's node_modules/<dep> symlinks point into <storeDir>/links/... directly. The project keeps a localnode_modules/.pnpm/ for lock.yaml and the hoist dir (node_modules/.pnpm/node_modules/); the package contents themselves are no longer there. Each project that uses the shared store also registers itself at <storeDir>/projects/<shortHash(projectDir)> (symlink back to the project) so the prune sweep can find them later.
crates/config/src/defaults.rs:79 defaults virtual_store_dir to <cwd>/node_modules/.pnpm.
crates/config/src/lib.rs:173-174 exposes a single virtual_store_dir: PathBuf on Config. There is no enable_global_virtual_store flag and no global_virtual_store_dir.
crates/package-manager/src/create_virtual_store.rs and install_package_by_snapshot.rs write package contents into config.virtual_store_dir directly. Switching to a shared dir would also need a hoist-dir distinction (currently implied to live under the same virtual store).
No project-registry write anywhere in crates/store-dir.
So the toggle, the path split, and the registry all have to be added; the installer also needs to know which directory holds package contents vs. which holds lock.yaml + hoisted modules.
Stage 1 plan (frozen-lockfile only)
A. Config
Add enable_global_virtual_store: bool to Config. Default true. CI auto-detect not in this issue — track separately. For now, treat the value as taken verbatim from pnpm-workspace.yaml / CLI / env (whatever pacquet already reads from), with true as the fallback.
Add global_virtual_store_dir: PathBuf to Config. When GVS is enabled and the user did not set virtual_store_dir, point both global_virtual_store_dir and virtual_store_dir at <store_dir>/links (matches extendInstallOptions.ts:350-358). When GVS is disabled, fall back to today's <cwd>/node_modules/.pnpm.
Wire pnpm-workspace.yaml parsing in crates/config/src/workspace_yaml.rs to recognise enableGlobalVirtualStore and globalVirtualStoreDir.
B. Path split during install
Introduce a clear distinction inside the installer: package_store_dir (where unpacked package directories live — <storeDir>/links under GVS, <project>/node_modules/.pnpm otherwise) vs. hoist_dir (where the .pnpm hoist target lives — always <project>/node_modules/.pnpm/node_modules). Today these are conflated under config.virtual_store_dir.
crates/package-manager/src/create_virtual_store.rs writes package contents to package_store_dir.
crates/package-manager/src/install_package_by_snapshot.rs reads/writes the same path.
crates/package-manager/src/symlink_direct_dependencies.rs resolves direct-dep symlink targets against package_store_dir, not the local .pnpm dir.
node_modules/.pnpm/lock.yaml stays at the project, regardless of GVS.
C. Project registry
New helper, likely in crates/store-dir, that writes a directory symlink at <store_dir>/projects/<short_hash(project_dir)> pointing back to project_dir. Port createShortHash (sha256 hex, first 32 chars) — likely a one-liner against an existing sha2 crate already in the workspace; verify before adding any dep. Skip the write when the store dir is a subdir of the project (matches upstream's isSubdir guard at projectRegistry.ts).
Call the helper from Install::run exactly once per project, only when GVS is on.
D. Tests
Frozen-lockfile install with GVS enabled: assert package contents land under <storeDir>/links/..., not under the project. Assert node_modules/<dep> is a symlink whose target resolves into <storeDir>/links.
Same install with GVS explicitly disabled: behavior unchanged from today.
Project registry: after install with GVS on, <store_dir>/projects/<hash> exists and resolves to the project dir. Re-running install is idempotent.
Port the relevant cases from installing/deps-installer/test/install/globalVirtualStore.ts per plans/TEST_PORTING.md.
Out of scope (follow-ups)
pruneGlobalVirtualStore — mark-and-sweep across <storeDir>/projects and <storeDir>/links. Belongs with a dedicated pacquet store prune story.
Engine-agnostic hash key gating (createAllowBuildFunction returning undefined makes ENGINE_NAME part of the hash; upstream defaults allowBuilds to {} when GVS is on and unset, config/reader/src/index.ts:325-330). Pacquet's pacquet_graph_hasher::engine_name plumbing in install_frozen_lockfile.rs:135-137 and build_modules.rs:194 would have to be made conditional. Track as its own issue.
Background
enableGlobalVirtualStoreis on by default in pnpm v11 (set inconfig/reader; CI is the only auto-detected exception, and explicit user config still wins). Roadmap (#299) lists "Support the global virtual store dir" under Stage 1. This issue scopes it to the minimum needed forpacquet install --frozen-lockfileto be correct when the user has GVS enabled; prune, engine-agnostic hashing, andCONFIG_CONFLICT_*validation are explicitly out of scope and tracked for follow-up.What GVS changes
With GVS off (pacquet today), packages live at
<project>/node_modules/.pnpm/<scope>+<pkg>@<ver>/node_modules/<pkg>/and the project's direct-dep symlinks point into that local.pnpm.With GVS on, the virtual store is shared across every project on the machine. Packages live at
<storeDir>/links/<scope>/<pkg>/<ver>/<peer-hash>/node_modules/<pkg>/and the project'snode_modules/<dep>symlinks point into<storeDir>/links/...directly. The project keeps a localnode_modules/.pnpm/forlock.yamland the hoist dir (node_modules/.pnpm/node_modules/); the package contents themselves are no longer there. Each project that uses the shared store also registers itself at<storeDir>/projects/<shortHash(projectDir)>(symlink back to the project) so the prune sweep can find them later.Upstream references at pnpm v11
94240bc046:config/reader/src/index.ts:392-394and CI branch atconfig/reader/src/index.ts:543-548.globalVirtualStoreDirderivation (and thevirtualStoreDir = storeDir/linksflip when GVS is enabled and the user did not set it):installing/deps-installer/src/install/extendInstallOptions.ts:350-358.<modules>/.pnpm/node_moduleswhen GVS is on instead of<virtualStoreDir>/node_modules:installing/deps-restorer/src/index.ts:228-232.store/controller/src/storeController/projectRegistry.ts(registerProject,getProjectsRegistryDir).createShortHash(sha256, first 32 hex chars):crypto/hash/src/index.ts.Today
Pacquet hard-codes the per-project layout:
crates/config/src/defaults.rs:79defaultsvirtual_store_dirto<cwd>/node_modules/.pnpm.crates/config/src/lib.rs:173-174exposes a singlevirtual_store_dir: PathBufonConfig. There is noenable_global_virtual_storeflag and noglobal_virtual_store_dir.crates/package-manager/src/create_virtual_store.rsandinstall_package_by_snapshot.rswrite package contents intoconfig.virtual_store_dirdirectly. Switching to a shared dir would also need a hoist-dir distinction (currently implied to live under the same virtual store).crates/store-dir.So the toggle, the path split, and the registry all have to be added; the installer also needs to know which directory holds package contents vs. which holds
lock.yaml+ hoisted modules.Stage 1 plan (frozen-lockfile only)
A. Config
enable_global_virtual_store: booltoConfig. Defaulttrue. CI auto-detect not in this issue — track separately. For now, treat the value as taken verbatim frompnpm-workspace.yaml/ CLI / env (whatever pacquet already reads from), withtrueas the fallback.global_virtual_store_dir: PathBuftoConfig. When GVS is enabled and the user did not setvirtual_store_dir, point bothglobal_virtual_store_dirandvirtual_store_dirat<store_dir>/links(matchesextendInstallOptions.ts:350-358). When GVS is disabled, fall back to today's<cwd>/node_modules/.pnpm.pnpm-workspace.yamlparsing incrates/config/src/workspace_yaml.rsto recogniseenableGlobalVirtualStoreandglobalVirtualStoreDir.B. Path split during install
package_store_dir(where unpacked package directories live —<storeDir>/linksunder GVS,<project>/node_modules/.pnpmotherwise) vs.hoist_dir(where the.pnpmhoist target lives — always<project>/node_modules/.pnpm/node_modules). Today these are conflated underconfig.virtual_store_dir.crates/package-manager/src/create_virtual_store.rswrites package contents topackage_store_dir.crates/package-manager/src/install_package_by_snapshot.rsreads/writes the same path.crates/package-manager/src/symlink_direct_dependencies.rsresolves direct-dep symlink targets againstpackage_store_dir, not the local.pnpmdir.node_modules/.pnpm/lock.yamlstays at the project, regardless of GVS.C. Project registry
crates/store-dir, that writes a directory symlink at<store_dir>/projects/<short_hash(project_dir)>pointing back toproject_dir. PortcreateShortHash(sha256 hex, first 32 chars) — likely a one-liner against an existing sha2 crate already in the workspace; verify before adding any dep. Skip the write when the store dir is a subdir of the project (matches upstream'sisSubdirguard atprojectRegistry.ts).Install::runexactly once per project, only when GVS is on.D. Tests
<storeDir>/links/..., not under the project. Assertnode_modules/<dep>is a symlink whose target resolves into<storeDir>/links.rm -rf node_moduleswith GVS on:<storeDir>/linksstays warm and the second install only relinks. Mirrorsinstalling/deps-installer/test/install/globalVirtualStore.ts.<store_dir>/projects/<hash>exists and resolves to the project dir. Re-running install is idempotent.installing/deps-installer/test/install/globalVirtualStore.tsperplans/TEST_PORTING.md.Out of scope (follow-ups)
pruneGlobalVirtualStore— mark-and-sweep across<storeDir>/projectsand<storeDir>/links. Belongs with a dedicatedpacquet store prunestory.createAllowBuildFunctionreturningundefinedmakesENGINE_NAMEpart of the hash; upstream defaultsallowBuildsto{}when GVS is on and unset,config/reader/src/index.ts:325-330). Pacquet'spacquet_graph_hasher::engine_nameplumbing ininstall_frozen_lockfile.rs:135-137andbuild_modules.rs:194would have to be made conditional. Track as its own issue.config/reader/src/index.ts:543-548). Depends on whatever CI-detection helper pacquet ends up porting.CONFIG_CONFLICT_VIRTUAL_STORE_DIR_WITH_GLOBALand thevirtualStoreOnly + !enableModulesDir + !enableGlobalVirtualStoreconflict atinstalling/deps-restorer/src/index.ts:252-254.pacquet install --frozen-lockfile#431 lands, GVS still uses one<storeDir>/linksfor all importers, and each importer registers separately under<storeDir>/projects.Related
pacquet install --frozen-lockfile#431Written by an agent (Claude Code, claude-opus-4-7).