Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.
This repository was archived by the owner on May 14, 2026. It is now read-only.

Add global virtual store support to pacquet install --frozen-lockfile #432

@zkochan

Description

@zkochan

Background

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 local node_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.

Upstream references at pnpm v11 94240bc046:

Today

Pacquet hard-codes the per-project layout:

  • 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.
  • Warm-store reinstall after rm -rf node_modules with GVS on: <storeDir>/links stays warm and the second install only relinks. Mirrors installing/deps-installer/test/install/globalVirtualStore.ts.
  • 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.
  • CI-auto-detect default flip (config/reader/src/index.ts:543-548). Depends on whatever CI-detection helper pacquet ends up porting.
  • CONFIG_CONFLICT_VIRTUAL_STORE_DIR_WITH_GLOBAL and the virtualStoreOnly + !enableModulesDir + !enableGlobalVirtualStore conflict at installing/deps-restorer/src/index.ts:252-254.
  • Interaction with workspace installs — once Add workspace support to pacquet install --frozen-lockfile #431 lands, GVS still uses one <storeDir>/links for all importers, and each importer registers separately under <storeDir>/projects.

Related


Written by an agent (Claude Code, claude-opus-4-7).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions