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 workspace support to pacquet install --frozen-lockfile #431

@zkochan

Description

@zkochan

Background

pacquet install --frozen-lockfile is being driven toward feature parity with pnpm install --frozen-lockfile (Stage 1 of the Roadmap, pnpm/pnpm#11633). Workspace install — multiple importers sharing one pnpm-lock.yaml — is one of the remaining Roadmap checkboxes and is the most common configuration in real pnpm-managed repos. This issue tracks just that: making the frozen-lockfile installer correct for a workspace lockfile. Resolution (workspace:^ → concrete version), --filter, injected-deps syncing, and graph/sorter work are out of scope here — they belong to Stage 2.

#357 is a narrow precursor: the prefix field on pnpm:stage events should become lockfileDir. That can be closed by the work tracked here.

Today

crates/lockfile/src/lib.rs:65 already deserializes importers: HashMap<String, ProjectSnapshot>, but the install pipeline treats only Lockfile::ROOT_IMPORTER_KEY (.) as live:

  • crates/package-manager/src/symlink_direct_dependencies.rs:54 reads only importers["."] and errors with MissingRootImporter otherwise.
  • crates/package-manager/src/install.rs:174 passes the full importers map down, but no code path fans out across non-root importers when materializing node_modules/ or symlinking direct deps.
  • crates/config/src/workspace_yaml.rs finds pnpm-workspace.yaml (walking up via find_workspace_manifest) and applies its settings (WorkspaceSettings) to Config. It does not read packages:, does not enumerate projects, and is not wired to the installer's notion of "where do I install."

So the foundation for discovering the workspace exists for settings purposes; the iteration across projects during install does not.

Upstream layout (pnpm v11 main, 94240bc046)

Stage-1-relevant packages under workspace/:

  • workspace/root-finderfindWorkspaceDir(cwd) walks up to pnpm-workspace.yaml; rejects misnamed variants like pnpm-workspaces.yaml / .pnpm-workspace.yml; respects NPM_CONFIG_WORKSPACE_DIR; resolves realpath for case-insensitive FS.
  • workspace/workspace-manifest-reader — reads pnpm-workspace.yaml into a WorkspaceManifest with packages: string[] plus settings + catalogs.
  • workspace/projects-readerfindWorkspaceProjects(workspaceRoot, { patterns }): glob-expands packages: (via tinyglobby), always includes the workspace root (pnpm#1986), filters by engines/os/cpu, sorts lex by rootDir, warns on resolutions in non-root manifests.
  • workspace/project-manifest-reader — reads package.json / package.yaml / package.json5.

Stage-2 (out of scope here, captured for context): spec-parser, range-resolver, projects-graph, projects-sorter, projects-filter, injected-deps-syncer, state.

Stage 1 plan

A. Discovery

  • Port findWorkspaceDir semantics into pacquet-config (or a new pacquet-workspace crate). The existing find_workspace_manifest at crates/config/src/workspace_yaml.rs:272 is close but does not honor NPM_CONFIG_WORKSPACE_DIR, does not realpath the start dir, and does not reject misnamed variants — match upstream exactly.
  • Extend WorkspaceManifest parsing to expose packages: Vec<String> (currently workspace_yaml.rs deserializes settings only).
  • Port findWorkspaceProjects — glob-expand packages: from the workspace root, always include the root project, filter node_modules/** and bower_components/**, sort lex by rootDir. Use globset / wax / a comparable glob crate already in the workspace if one is present; otherwise raise the dependency question separately before adding a new crate.
  • Skip engines / os / cpu filtering for now if it isn't already shared with the per-snapshot filter; track it as a follow-up if non-trivial.

B. Per-importer install

  • Iterate every importer in Lockfile.importers, not just ROOT_IMPORTER_KEY. Each importer key is the project's path relative to the workspace root; resolve it against the discovered workspace root to get the project's rootDir.
  • For each importer, materialize node_modules/ under that project's rootDir and symlink its direct dependencies from the shared virtual store at <workspace-root>/node_modules/.pnpm. The virtual store stays singular per workspace — only the per-project node_modules/ and its symlinks fan out.
  • Remove the MissingRootImporter hard-failure at symlink_direct_dependencies.rs:54 in favor of "this importer key wasn't in the lockfile" (an error, but per-importer). The function should be invoked once per importer rather than once for the lockfile.
  • Cross-importer workspace: deps in the lockfile appear as link: snapshots pointing to the dependee's rootDir. Verify pacquet's snapshot handling links these as directory symlinks rather than fetching anything — add coverage if it doesn't already.

C. Reporter / log emissions

  • Per-importer pnpm:stage events carry the importer's own prefix (= the importer's rootDir), matching upstream so @pnpm/cli.default-reporter attributes progress correctly. This closes Resolve workspace dir for install prefix #357.
  • pnpm:root events for direct-dep additions/removals also use the per-importer prefix.
  • pnpm:summary fires once per importer (verify against pnpm — it may be once per install, with the importer attribution carried by the contained pnpm:root events).
  • node_modules/.modules.yaml is written at the workspace root, not per importer. Confirm against writeModulesManifest in installing/deps-installer/src/install/index.ts.

D. Tests

  • Integration test: two-package workspace, frozen-lockfile install, assert each project's node_modules/ matches its importer section; assert the shared .pnpm store at the root.
  • Integration test: workspace where one importer depends on another via workspace: — verify the link: snapshot becomes a directory symlink to the dependee's rootDir.
  • Integration test: misnamed workspace manifest (pnpm-workspace.yml) produces the same BAD_WORKSPACE_MANIFEST_NAME error as pnpm.
  • Port relevant TypeScript tests per plans/TEST_PORTING.md conventions.

Out of scope (Stage 2 / later)

  • workspace: resolution during install without a lockfile (pacquet doesn't resolve yet).
  • --filter and --filter-prod, change-based filtering.
  • Topological sort for ordered builds across local packages (current build sequencing is intra-importer).
  • injected-deps-syncer.
  • pnpm publish / pnpm exec / pnpm run -r across workspaces.
  • Catalogs (catalog: specifier resolution) — separate, partially-tracked work.

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