feat(pacquet): port directory fetcher for injected workspace deps#11678
Conversation
…ry 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).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
📝 WalkthroughWalkthroughAdds a new pacquet-directory-fetcher crate (walker + fetcher), unit and integration tests, and integrates it into package-manager by threading workspace_root and enabling LockfileResolution::Directory via the cold install path. ChangesDirectory-Fetcher Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
pacquet/crates/package-manager/src/create_virtual_store.rs (2)
374-385:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDo not current-lockfile-skip directory snapshots.
Directory resolutions currently pass the skip gate when deps are unchanged and slot exists, because integrity compares as
None == None. That prevents re-walking mutable workspace sources on subsequent installs.Suggested fix
let current_metadata = current_packages.and_then(|p| p.get(&snapshot_key.without_peer())); let wanted_metadata = packages.get(&snapshot_key.without_peer()); + if matches!( + wanted_metadata.map(|m| &m.resolution), + Some(LockfileResolution::Directory(_)) + ) { + // Directory-backed snapshots are mutable local sources. + // Always re-fetch/re-link. + return true; + } if !integrity_equal(current_metadata, wanted_metadata) { return true; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pacquet/crates/package-manager/src/create_virtual_store.rs` around lines 374 - 385, The current skip logic treats None==None as unchanged (integrity_equal returns true) which lets directory resolutions be skipped; change the check after computing current_metadata and wanted_metadata so that if integrity_equal(...) is true you still force a rebuild when the snapshot represents a directory resolution—detect this via snapshot_key (e.g., snapshot_key.is_directory() or by inspecting wanted_metadata/current_metadata resolution type) and return true for directory snapshots instead of skipping; update the block around current_metadata, wanted_metadata and the integrity_equal(...) call (and not the later layout.slot_dir(...) check) to ensure directory-based snapshots are always considered changed.
1041-1046:⚠️ Potential issue | 🟠 Major | ⚡ Quick winClassify
DirectoryFetchas fetch-side for optional snapshots.Optional snapshot failure swallowing excludes
InstallPackageBySnapshotError::DirectoryFetch, so optional injected-directory failures currently hard-fail installs instead of being dropped like other fetch-side failures.Suggested fix
fn is_fetch_side_failure(err: &InstallPackageBySnapshotError) -> bool { matches!( err, InstallPackageBySnapshotError::DownloadTarball(_) - | InstallPackageBySnapshotError::GitFetch(_), + | InstallPackageBySnapshotError::GitFetch(_) + | InstallPackageBySnapshotError::DirectoryFetch(_), ) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pacquet/crates/package-manager/src/create_virtual_store.rs` around lines 1041 - 1046, The is_fetch_side_failure function currently only treats DownloadTarball and GitFetch variants as fetch-side; add InstallPackageBySnapshotError::DirectoryFetch to the matches so optional snapshot failures from injected directories are classified as fetch-side and can be dropped like the others; update the matches pattern inside is_fetch_side_failure to include DirectoryFetch alongside DownloadTarball and GitFetch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pacquet/crates/directory-fetcher/src/walker.rs`:
- Around line 37-43: The walker currently follows directory symlinks without
tracking visited directories, causing infinite recursion on symlink cycles;
modify walk_all_files and the internal walker (walk_all_inner / any helper that
recurses into directories) to accept and maintain a visited-set of canonical
directory keys (use std::fs::canonicalize(or equivalent) when resolve_symlinks
is true) and before recursing into a directory compute its canonical path, check
the visited set and skip recursion if already present, otherwise insert it and
continue; ensure the visited-set is threaded through all recursive calls so the
same guard is applied where the code currently recurses into directories
(including the other similar walker entry points mentioned).
---
Outside diff comments:
In `@pacquet/crates/package-manager/src/create_virtual_store.rs`:
- Around line 374-385: The current skip logic treats None==None as unchanged
(integrity_equal returns true) which lets directory resolutions be skipped;
change the check after computing current_metadata and wanted_metadata so that if
integrity_equal(...) is true you still force a rebuild when the snapshot
represents a directory resolution—detect this via snapshot_key (e.g.,
snapshot_key.is_directory() or by inspecting wanted_metadata/current_metadata
resolution type) and return true for directory snapshots instead of skipping;
update the block around current_metadata, wanted_metadata and the
integrity_equal(...) call (and not the later layout.slot_dir(...) check) to
ensure directory-based snapshots are always considered changed.
- Around line 1041-1046: The is_fetch_side_failure function currently only
treats DownloadTarball and GitFetch variants as fetch-side; add
InstallPackageBySnapshotError::DirectoryFetch to the matches so optional
snapshot failures from injected directories are classified as fetch-side and can
be dropped like the others; update the matches pattern inside
is_fetch_side_failure to include DirectoryFetch alongside DownloadTarball and
GitFetch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ac41f0c9-ed91-4ce7-8eea-048fa56605b6
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
Cargo.tomlpacquet/crates/directory-fetcher/Cargo.tomlpacquet/crates/directory-fetcher/src/error.rspacquet/crates/directory-fetcher/src/fetcher.rspacquet/crates/directory-fetcher/src/lib.rspacquet/crates/directory-fetcher/src/walker.rspacquet/crates/directory-fetcher/src/walker/tests.rspacquet/crates/directory-fetcher/tests/fetcher.rspacquet/crates/package-manager/Cargo.tomlpacquet/crates/package-manager/src/create_virtual_store.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/package-manager/src/install_package_by_snapshot.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Agent
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Code Coverage
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
No star imports inside module bodies — write
use super::{Foo, bar}instead ofuse super::*;and the same for any other glob. Exception: external-crate preludes likeuse rayon::prelude::*;and root-of-module re-exports likepub use submodule::*;inlib.rs
Files:
pacquet/crates/directory-fetcher/src/lib.rspacquet/crates/directory-fetcher/src/fetcher.rspacquet/crates/directory-fetcher/src/walker/tests.rspacquet/crates/directory-fetcher/tests/fetcher.rspacquet/crates/package-manager/src/install_package_by_snapshot.rspacquet/crates/package-manager/src/create_virtual_store.rspacquet/crates/directory-fetcher/src/error.rspacquet/crates/directory-fetcher/src/walker.rspacquet/crates/package-manager/src/install_frozen_lockfile.rs
pacquet/crates/*/tests/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
Tests live alongside the code they exercise (standard Cargo layout) plus integration tests under each crate's
tests/— shared test fixtures live undercrates/testing-utils/src/fixtures/
Files:
pacquet/crates/directory-fetcher/tests/fetcher.rs
🔇 Additional comments (5)
Cargo.toml (1)
30-30: LGTM!pacquet/crates/directory-fetcher/Cargo.toml (1)
1-29: LGTM!pacquet/crates/directory-fetcher/src/error.rs (1)
1-32: LGTM!pacquet/crates/directory-fetcher/src/lib.rs (1)
1-15: LGTM!pacquet/crates/directory-fetcher/src/walker/tests.rs (1)
1-206: LGTM!
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #11678 +/- ##
==========================================
- Coverage 89.12% 89.10% -0.03%
==========================================
Files 127 129 +2
Lines 14470 14590 +120
==========================================
+ Hits 12897 13000 +103
- Misses 1573 1590 +17 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Micro-Benchmark ResultsLinux |
Integrated-Benchmark Report (Linux)Scenario: Frozen Lockfile
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.40500251908,
"stddev": 0.08587046502617665,
"median": 2.38828746248,
"user": 2.7132019,
"system": 3.6234844399999995,
"min": 2.27871210848,
"max": 2.55540654548,
"times": [
2.55540654548,
2.43185934848,
2.34682322448,
2.5191348004800003,
2.36597901148,
2.39115814748,
2.45088856348,
2.38541677748,
2.3246466634800003,
2.27871210848
]
},
{
"command": "pacquet@main",
"mean": 2.43193101088,
"stddev": 0.10099066768577186,
"median": 2.39287686698,
"user": 2.6945631999999997,
"system": 3.60440924,
"min": 2.3536231834800003,
"max": 2.67102731248,
"times": [
2.35831062248,
2.38369831448,
2.52483841048,
2.67102731248,
2.48193863248,
2.37657023248,
2.4058666574800003,
2.3536231834800003,
2.4020554194800003,
2.3613813234800003
]
},
{
"command": "pnpm",
"mean": 4.63032025238,
"stddev": 0.11147027941919106,
"median": 4.60156582598,
"user": 7.7986065,
"system": 4.01984844,
"min": 4.51412682048,
"max": 4.85831962548,
"times": [
4.6213888144799995,
4.56167876148,
4.59522942948,
4.51412682048,
4.56430111148,
4.60926252448,
4.56522647948,
4.80576673448,
4.85831962548,
4.60790222248
]
}
]
}Scenario: Frozen Lockfile (Hot Cache)
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.7484913258800001,
"stddev": 0.04830459949056074,
"median": 0.7333133365800001,
"user": 0.38173436,
"system": 1.6393193400000001,
"min": 0.7141284535800001,
"max": 0.8700861185800001,
"times": [
0.8700861185800001,
0.7212942975800001,
0.7272560825800001,
0.7141284535800001,
0.7366529175800001,
0.7335235615800001,
0.7331031115800001,
0.7943560245800001,
0.7381397295800001,
0.7163729615800001
]
},
{
"command": "pacquet@main",
"mean": 0.7727917494800001,
"stddev": 0.051224003343681844,
"median": 0.76804807708,
"user": 0.37581406,
"system": 1.63994844,
"min": 0.7084302385800001,
"max": 0.8996088265800001,
"times": [
0.77058861458,
0.76915891558,
0.79991348358,
0.7368073725800001,
0.76524094358,
0.7374460525800001,
0.8996088265800001,
0.77378580858,
0.7669372385800001,
0.7084302385800001
]
},
{
"command": "pnpm",
"mean": 2.4826345729799995,
"stddev": 0.10344117310843659,
"median": 2.49332283558,
"user": 2.97188316,
"system": 2.18996014,
"min": 2.29948185758,
"max": 2.67472127258,
"times": [
2.52341231758,
2.53147649058,
2.46120285058,
2.67472127258,
2.37484600258,
2.42405754358,
2.29948185758,
2.54033969758,
2.5335743435799998,
2.4632333535799997
]
}
]
} |
…abbit 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).
|
Addressed all three CodeRabbit findings in f2db87c:
Tests: 15 directory-fetcher tests pass, 268 package-manager unit tests pass, clippy clean. Written by an agent (Claude Code, claude-opus-4-7). |
… macro Dylint's `perfectionist::macro-trailing-comma` lint flagged the `matches!` invocation added in f2db87c. 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).
…pm#11678) * 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 pnpm#11678 Three parity / hardening fixes flagged in the CodeRabbit review on PR pnpm#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 f2db87c. 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).
Summary
fetching/directory-fetcheras a newpacquet-directory-fetchercrate (file walker, manifest read,requires_builddetection, broken-symlink ENOENT skip,resolveSymlinkstoggle).LockfileResolution::Directoryend-to-end throughInstallPackageBySnapshot::runandcreate_virtual_store.rs, so injected workspace deps (file:./local-pkg+dependenciesMeta[*].injected = true) actually install instead of returningUnsupportedResolution { resolution_kind: "directory" }.local: true, packageImportMethod: 'hardlink'semantics: the fetcher returns source-path entries andimport_indexed_dir/link_filehardlink-or-copy straight from the source.What's in scope
pacquet-directory-fetchercrate:walk_all_files↔ upstream'sfetchAllFilesFromDir(recursive,node_modulesexclusion at any depth,fs::metadatavssymlink_metadata + canonicalizefork, broken-symlink ENOENT skip).walk_package_files↔ upstream'sfetchPackageFilesFromDir(delegates topacquet_git_fetcher::packlist, tolerates missing manifest for the Bit-workspace shape).DirectoryFetcher::runreturnsfiles_map/manifest/requires_build.install_package_by_snapshot.rs: newworkspace_root: &Pathfield threaded down fromInstallFrozenLockfile; directory arm resolvesdir_resolution.directoryagainst it, calls the fetcher, and returns itsfiles_mapascas_paths.create_virtual_store.rs::snapshot_cache_key: returnsOk(None)for directory resolutions (no warm-cache key — every install re-walks the source).What's deferred to follow-ups
resolveSymlinksInInjectedDirs/includeOnlyPackageFilesconfig knobs — currently hard-coded to upstream's defaults (false/false) fromextendInstallOptions.ts:41.hoisted_dep_graph.rsalready collectsinjection_targets_by_dep_pathfor every directory-typed snapshot location, but the post-install mirror that rebuilds those copies still needs implementing.package.json5/package.yamlmanifest read —safe_read_package_json_from_dironly handlespackage.json. Same parity gap as every other manifest read in pacquet; not specific to this PR.Test plan
cargo nextest run -p pacquet-directory-fetcher(14 tests) — passes locally.cargo nextest run(1277 tests) — passes locally.cargo clippy --locked -- --deny warnings— clean.cargo check --locked— clean.file:./local-pkginjected dep (suggested validation; not covered by unit tests).Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
New Features
Tests