Files
Zoltan Kochan f33838cdd1 feat: workspace support for pacquet install --frozen-lockfile (#431) (#443)
Stage 1 of pnpm/pacquet#299 / closes #431 (Stage 1 scope) / partially closes #357.

- **New crate `pacquet-workspace`** — ports pnpm's `workspace/root-finder`, `workspace/workspace-manifest-reader`, `workspace/projects-reader`, and `workspace/project-manifest-reader` (pinned to [`94240bc046`](https://github.com/pnpm/pnpm/tree/94240bc046)). `find_workspace_dir` honours `NPM_CONFIG_WORKSPACE_DIR` (and its lowercase spelling, with empty-value fall-through), walks up to `pnpm-workspace.yaml`, and rejects misnamed variants (`pnpm-workspace.yml`, `.pnpm-workspace.yaml`, ...) with `BAD_WORKSPACE_MANIFEST_NAME`. Project enumeration glob-expands `packages:` via `wax`, always includes the workspace root (https://github.com/pnpm/pnpm/issues/1986), filters `**/node_modules/**` + `**/bower_components/**`, sorts lex by `rootDir`, and preserves the omitted-vs-explicit-empty distinction so `packages: []` enumerates only the root rather than falling back to the recursive `['.', '**']` default.

- **Per-importer install path** — `SymlinkDirectDependencies` iterates every entry in `Lockfile.importers`, computes per-project `rootDir` and `node_modules/`, and uses `rootDir` as the `prefix` for `pnpm:root added` events (matching upstream's [`linkDirectDeps`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/linking/direct-dep-linker/src/linkDirectDeps.ts#L131)). Install-wide events (`pnpm:stage`, `pnpm:summary`) still use `lockfileDir`, now resolved through `find_workspace_dir`. `MissingRootImporter` is gone — an empty importers map is a silent no-op. A custom `modulesDir` from `pnpm-workspace.yaml` propagates to every importer (the symlink stage reads `config.modules_dir.file_name()` rather than hard-coding `node_modules`).

- **Path-anchoring consistency** — `Config::current` re-anchors `modules_dir` / `virtual_store_dir` to the workspace root when `pnpm-workspace.yaml` is found in an ancestor, mirroring pnpm v11's `pnpmConfig.dir = lockfileDir`. Running `pacquet install` from a workspace subdirectory no longer produces two inconsistent `node_modules` layouts (subdir virtual store vs workspace-root per-importer symlinks). `Config::current` also honours `NPM_CONFIG_WORKSPACE_DIR` so the env-var override drives both `find_workspace_dir` and the path anchors. `workspace_root` is threaded through `InstallFrozenLockfile` as a real `&Path` (no lossy-UTF-8 round-trip), so non-UTF-8 filenames survive the install layer intact.

- **Lockfile `link:` support** — `ResolvedDependencySpec.version` is now an `ImporterDepVersion` enum (`Regular(PkgVerPeer) | Link(String)`), so a v9 workspace lockfile with `version: link:../shared` parses. The symlink stage resolves the link target against the importer's `rootDir` and points `node_modules/<name>` at the sibling project rather than the virtual store; `BuildModules`, `pacquet-real-hoist`, and the side-effects-cache write path all skip `Link` variants when collecting snapshot-key roots.

- **Importer-key validation** — rejects absolute POSIX paths, Windows drive prefixes, backslash separators, `..` segments, and the empty string with a typed `UnsafeImporterPath` error, so a malformed (or hostile) lockfile cannot make `Path::join` create `node_modules` outside the workspace root.

- **Symlink error propagation** — the prior `expect("symlink pkg")` is replaced with `try_for_each` + a `SymlinkDirectDependenciesError::SymlinkPackage` variant carrying the `importer_id`, `name`, and underlying `SymlinkPackageError`. A filesystem failure no longer crashes the rayon pool.

- **Adds `wax = 0.7.0`** to `[workspace.dependencies]` for project globbing.
2026-05-13 15:52:23 +02:00
..