mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-02 11:55:17 -04:00
* feat(pacquet/directory-fetcher,pacquet/package-manager): port directory fetcher for injected workspace deps
Port pnpm's `fetching/directory-fetcher` package as a new
`pacquet-directory-fetcher` crate and wire `LockfileResolution::Directory`
into `InstallPackageBySnapshot::run` so injected workspace deps
(`file:./local-pkg` + `dependenciesMeta[*].injected = true`) actually
install instead of returning `UnsupportedResolution`.
Mirrors upstream's
[`fetching/directory-fetcher/src/index.ts`](https://github.com/pnpm/pnpm/blob/85ceff2383/fetching/directory-fetcher/src/index.ts):
- `walk_all_files` ports `fetchAllFilesFromDir` — recursive walk,
`node_modules` exclusion at any depth, broken-symlink ENOENT skip,
`resolve_symlinks` toggle between `fileStat` (`fs::metadata`) and
`realFileStat` (`symlink_metadata` + `canonicalize`).
- `walk_package_files` ports `fetchPackageFilesFromDir` — delegates
to `pacquet_git_fetcher::packlist` for the npm-packlist filter,
tolerates a missing manifest for the Bit-workspace shape upstream
documents at L63-L66.
- `DirectoryFetcher::run` returns `files_map` / `manifest` /
`requires_build`, matching upstream's `FetchResult` minus
`local: true` (implicit at the call site) and `packageImportMethod`
(encoded by which slot the install dispatcher writes to).
Install dispatch:
- `install_package_by_snapshot.rs` resolves `dir_resolution.directory`
against the newly threaded `workspace_root` (upstream's
`lockfileDir`), calls `DirectoryFetcher::run`, and hands the
source-path `files_map` straight through as `cas_paths`. The
existing `import_indexed_dir` / `link_file` path accepts arbitrary
source paths so no CAFS write is needed — directory deps don't go
through the store at all, matching upstream's
`local: true, packageImportMethod: 'hardlink'` semantics.
- `create_virtual_store.rs`'s `snapshot_cache_key` now returns
`Ok(None)` for directory resolutions: there's no warm-cache key
to recover from (the source dir may have changed since last
install), so every install re-walks. Matches upstream.
Deferred follow-ups (out of scope for this PR):
- `resolveSymlinksInInjectedDirs` / `includeOnlyPackageFiles`
config-knob plumbing — the dispatch hard-codes the upstream
defaults (`false` / `false`) from
[`extendInstallOptions.ts:41`](https://github.com/pnpm/pnpm/blob/85ceff2383/installing/deps-installer/src/install/extendInstallOptions.ts#L41)
for now.
- Injected-dep re-mirror pass — `hoisted_dep_graph.rs` already
records `injection_targets_by_dep_path` for every directory-typed
snapshot location, but the post-install mirror that rebuilds those
copies is not implemented yet.
- `package.json5` / `package.yaml` manifest read — pacquet's
`safe_read_package_json_from_dir` only handles `package.json`; the
other two variants upstream's `safeReadProjectManifestOnly`
supports are a parity gap that affects directory deps the same way
it affects every other manifest read in pacquet.
---
Written by an agent (Claude Code, claude-opus-4-7).
* fix(pacquet/directory-fetcher,pacquet/package-manager): address CodeRabbit review on #11678
Three parity / hardening fixes flagged in the CodeRabbit review on PR
#11678:
1. **Carve out directory snapshots from the current-lockfile-skip
gate** (`create_virtual_store.rs`). Directory-typed snapshots have
`integrity() == None`; without the carve-out `integrity_equal`
collapsed `None == None == true` and the skip filter dropped the
snapshot whenever a slot for it existed on disk, so a second
install never re-walked the (mutable) source dir. Mirrors pnpm's
`!isDirectoryDep` clause in `depIsPresent` at
<https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L226-L228>.
2. **Add `DirectoryFetch` to `is_fetch_side_failure`**
(`create_virtual_store.rs`). Upstream's catch at
[`lockfileToDepGraph.ts:286-298`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L286-L298)
wraps the whole `fetchPackage` dispatch, so directory-fetcher
errors on optional snapshots are swallowed uniformly with tarball
/ git fetch errors. Without this, an optional injected-directory
dep whose source was missing would hard-fail the install instead
of being dropped.
3. **Symlink-cycle guard in `walk_all_inner`** (`directory-fetcher/
src/walker.rs`). A `loop -> .` (or any ancestor-pointing) symlink
previously sank the walker into infinite recursion until either
ENAMETOOLONG or stack overflow fired. Skip-on-revisit keyed off
`fs::canonicalize`, matching the pattern
`pacquet_git_fetcher::packlist` already uses for
`bundleDependencies` cycles. Pnpm's directory-fetcher has the same
vulnerability; the guard is a defensible divergence because the
positive-case behavior is identical to pnpm and the cycle case
degrades from "crash" to "skip with a `tracing::warn`".
Added a regression test
(`walk_all_files_terminates_on_symlink_cycle`) that points a
`loop -> root` symlink at the walk root and asserts the cycle guard
short-circuits before any `loop/` descendant is recorded.
---
Written by an agent (Claude Code, claude-opus-4-7).
* style(pacquet/package-manager): trailing comma on multi-line matches! macro
Dylint's `perfectionist::macro-trailing-comma` lint flagged the
`matches!` invocation added in f2db87c0e3. Add the trailing comma to
clear the lint.
CI: https://github.com/pnpm/pnpm/actions/runs/25958605314/job/76309781526
---
Written by an agent (Claude Code, claude-opus-4-7).
127 lines
5.3 KiB
TOML
127 lines
5.3 KiB
TOML
[workspace]
|
|
resolver = "2"
|
|
members = ["pacquet/crates/*", "pacquet/tasks/*"]
|
|
|
|
[workspace.package]
|
|
authors = ["Yagiz Nizipli <yagiz@nizipli.com"]
|
|
description = "Pacquet"
|
|
edition = "2024"
|
|
homepage = "https://github.com/pnpm/pacquet"
|
|
keywords = ["nodejs", "package", "manager", "pnpm", "npm"]
|
|
license = "MIT"
|
|
repository = "https://github.com/pnpm/pacquet"
|
|
|
|
[workspace.dependencies]
|
|
# Crates
|
|
pacquet-cli = { path = "pacquet/crates/cli" }
|
|
pacquet-cmd-shim = { path = "pacquet/crates/cmd-shim" }
|
|
pacquet-fs = { path = "pacquet/crates/fs" }
|
|
pacquet-registry = { path = "pacquet/crates/registry" }
|
|
pacquet-tarball = { path = "pacquet/crates/tarball" }
|
|
pacquet-testing-utils = { path = "pacquet/crates/testing-utils" }
|
|
pacquet-package-manifest = { path = "pacquet/crates/package-manifest" }
|
|
pacquet-package-manager = { path = "pacquet/crates/package-manager" }
|
|
pacquet-package-is-installable = { path = "pacquet/crates/package-is-installable" }
|
|
pacquet-lockfile = { path = "pacquet/crates/lockfile" }
|
|
pacquet-modules-yaml = { path = "pacquet/crates/modules-yaml" }
|
|
pacquet-network = { path = "pacquet/crates/network" }
|
|
pacquet-config = { path = "pacquet/crates/config" }
|
|
pacquet-executor = { path = "pacquet/crates/executor" }
|
|
pacquet-directory-fetcher = { path = "pacquet/crates/directory-fetcher" }
|
|
pacquet-git-fetcher = { path = "pacquet/crates/git-fetcher" }
|
|
pacquet-diagnostics = { path = "pacquet/crates/diagnostics" }
|
|
pacquet-graph-hasher = { path = "pacquet/crates/graph-hasher" }
|
|
pacquet-store-dir = { path = "pacquet/crates/store-dir" }
|
|
pacquet-reporter = { path = "pacquet/crates/reporter" }
|
|
pacquet-patching = { path = "pacquet/crates/patching" }
|
|
pacquet-real-hoist = { path = "pacquet/crates/real-hoist" }
|
|
pacquet-workspace = { path = "pacquet/crates/workspace" }
|
|
pacquet-workspace-state = { path = "pacquet/crates/workspace-state" }
|
|
|
|
# Tasks
|
|
pacquet-registry-mock = { path = "pacquet/tasks/registry-mock" }
|
|
|
|
# Dependencies
|
|
async-recursion = { version = "1.1.1" }
|
|
clap = { version = "4", features = ["derive", "string"] }
|
|
command-extra = { version = "1.0.0" }
|
|
base64 = { version = "0.22.1" }
|
|
dashmap = { version = "6.1.0" }
|
|
derive_more = { version = "2.1.1", features = ["full"] }
|
|
diffy = { version = "0.5.0" }
|
|
dunce = { version = "1.0.5" }
|
|
home = { version = "0.5.12" }
|
|
httpdate = { version = "1.0.3" }
|
|
ignore = { version = "0.4.25" }
|
|
indexmap = { version = "2.14.0", features = ["serde"] }
|
|
insta = { version = "1.47.2", features = ["yaml", "glob", "walkdir"] }
|
|
itertools = { version = "0.14.0" }
|
|
futures-util = { version = "0.3.32" }
|
|
gethostname = { version = "1" }
|
|
miette = { version = "7.6.0", features = ["fancy"] }
|
|
num_cpus = { version = "1.17.0" }
|
|
os_display = { version = "0.1.4" }
|
|
reflink-copy = { version = "0.1.29" }
|
|
junction = { version = "2.0.0" }
|
|
libc = { version = "0.2.186" }
|
|
reqwest = { version = "0.13", default-features = false, features = [
|
|
"hickory-dns",
|
|
"json",
|
|
"rustls",
|
|
"socks",
|
|
"stream",
|
|
] }
|
|
node-semver = { version = "2.2.0" }
|
|
pathdiff = { version = "0.2.3" }
|
|
pipe-trait = { version = "0.4.0" }
|
|
rayon = { version = "1.12.0" }
|
|
rmp-serde = { version = "1.3.0" }
|
|
rusqlite = { version = "0.39.0", features = ["bundled"] }
|
|
serde = { version = "1.0.228", features = ["derive"] }
|
|
serde_json = { version = "1.0.149", features = ["preserve_order"] }
|
|
serde-saphyr = { version = "0.0.26" }
|
|
# 0.11 removes the LowerHex impl on Output; revisit after upstream/consumers catch up
|
|
sha2 = { version = "0.10.9" }
|
|
smart-default = { version = "0.7.1" }
|
|
split-first-char = { version = "2.0.1" }
|
|
ssri = { version = "9.2.0" }
|
|
strum = { version = "0.28.0", features = ["derive"] }
|
|
sysinfo = { version = "0.39.1" }
|
|
tar = { version = "0.4.45" }
|
|
text-block-macros = { version = "0.2.0" }
|
|
tracing = { version = "0.1.44" }
|
|
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
|
walkdir = { version = "2.5.0" }
|
|
wax = { version = "0.7.0" }
|
|
which = { version = "8.0.2" }
|
|
zip = { version = "8", default-features = false, features = ["deflate"] }
|
|
zune-inflate = { version = "0.2.54" }
|
|
|
|
# Dev dependencies
|
|
assert_cmd = { version = "2.2.1" }
|
|
chrono = { version = "0.4.44", default-features = false, features = ["clock"] }
|
|
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
|
pretty_assertions = { version = "1.4.1" }
|
|
project-root = { version = "0.2.2" }
|
|
tempfile = { version = "3.27.0" }
|
|
mockito = { version = "1.7.2" }
|
|
|
|
[workspace.metadata.workspaces]
|
|
allow_branch = "main"
|
|
|
|
[profile.release]
|
|
opt-level = 3
|
|
lto = "fat"
|
|
codegen-units = 1
|
|
strip = "symbols"
|
|
debug = false
|
|
panic = "abort" # Let it crash and force ourselves to write safe Rust.
|
|
|
|
# Use the `--profile release-debug` flag to show symbols in release mode.
|
|
# e.g. `cargo build --profile release-debug`
|
|
[profile.release-debug]
|
|
inherits = "release"
|
|
strip = false
|
|
debug = true
|