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-finder — findWorkspaceDir(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-reader — findWorkspaceProjects(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
B. Per-importer install
C. Reporter / log emissions
D. Tests
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).
Background
pacquet install --frozen-lockfileis being driven toward feature parity withpnpm install --frozen-lockfile(Stage 1 of the Roadmap, pnpm/pnpm#11633). Workspace install — multiple importers sharing onepnpm-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
prefixfield onpnpm:stageevents should becomelockfileDir. That can be closed by the work tracked here.Today
crates/lockfile/src/lib.rs:65already deserializesimporters: HashMap<String, ProjectSnapshot>, but the install pipeline treats onlyLockfile::ROOT_IMPORTER_KEY(.) as live:crates/package-manager/src/symlink_direct_dependencies.rs:54reads onlyimporters["."]and errors withMissingRootImporterotherwise.crates/package-manager/src/install.rs:174passes the fullimportersmap down, but no code path fans out across non-root importers when materializingnode_modules/or symlinking direct deps.crates/config/src/workspace_yaml.rsfindspnpm-workspace.yaml(walking up viafind_workspace_manifest) and applies its settings (WorkspaceSettings) toConfig. It does not readpackages:, 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-finder—findWorkspaceDir(cwd)walks up topnpm-workspace.yaml; rejects misnamed variants likepnpm-workspaces.yaml/.pnpm-workspace.yml; respectsNPM_CONFIG_WORKSPACE_DIR; resolvesrealpathfor case-insensitive FS.workspace/workspace-manifest-reader— readspnpm-workspace.yamlinto aWorkspaceManifestwithpackages: string[]plus settings + catalogs.workspace/projects-reader—findWorkspaceProjects(workspaceRoot, { patterns }): glob-expandspackages:(viatinyglobby), always includes the workspace root (pnpm#1986), filters byengines/os/cpu, sorts lex byrootDir, warns onresolutionsin non-root manifests.workspace/project-manifest-reader— readspackage.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
findWorkspaceDirsemantics intopacquet-config(or a newpacquet-workspacecrate). The existingfind_workspace_manifestatcrates/config/src/workspace_yaml.rs:272is close but does not honorNPM_CONFIG_WORKSPACE_DIR, does not realpath the start dir, and does not reject misnamed variants — match upstream exactly.WorkspaceManifestparsing to exposepackages: Vec<String>(currentlyworkspace_yaml.rsdeserializes settings only).findWorkspaceProjects— glob-expandpackages:from the workspace root, always include the root project, filternode_modules/**andbower_components/**, sort lex byrootDir. Useglobset/wax/ a comparable glob crate already in the workspace if one is present; otherwise raise the dependency question separately before adding a new crate.engines/os/cpufiltering 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
Lockfile.importers, not justROOT_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'srootDir.node_modules/under that project'srootDirand 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-projectnode_modules/and its symlinks fan out.MissingRootImporterhard-failure atsymlink_direct_dependencies.rs:54in 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.workspace:deps in the lockfile appear aslink:snapshots pointing to the dependee'srootDir. 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
pnpm:stageevents carry the importer's ownprefix(= the importer'srootDir), matching upstream so@pnpm/cli.default-reporterattributes progress correctly. This closes Resolve workspace dir for installprefix#357.pnpm:rootevents for direct-dep additions/removals also use the per-importer prefix.pnpm:summaryfires once per importer (verify against pnpm — it may be once per install, with the importer attribution carried by the containedpnpm:rootevents).node_modules/.modules.yamlis written at the workspace root, not per importer. Confirm againstwriteModulesManifestininstalling/deps-installer/src/install/index.ts.D. Tests
node_modules/matches its importer section; assert the shared.pnpmstore at the root.workspace:— verify thelink:snapshot becomes a directory symlink to the dependee'srootDir.pnpm-workspace.yml) produces the sameBAD_WORKSPACE_MANIFEST_NAMEerror as pnpm.plans/TEST_PORTING.mdconventions.Out of scope (Stage 2 / later)
workspace:resolution duringinstallwithout a lockfile (pacquet doesn't resolve yet).--filterand--filter-prod, change-based filtering.injected-deps-syncer.pnpm publish/pnpm exec/pnpm run -racross workspaces.catalog:specifier resolution) — separate, partially-tracked work.Related
prefixprecursor: Resolve workspace dir for installprefix#357Written by an agent (Claude Code, claude-opus-4-7).