feat(pnpr): gzip-compress package metadata over the wire#12170
Conversation
|
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 ignored due to path filters (1)
📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📜 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). (7)
📝 WalkthroughWalkthroughGzip for packument metadata is enabled: workspace dependencies updated, the reqwest client now requests gzip, pnpr adds a CompressionLayer that excludes binary/NDJSON/install types, and tests verify gzip behavior for packuments and tarballs. ChangesGzip metadata compression
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 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 |
Review Summary by QodoEnable gzip compression for package metadata over the wire
WalkthroughsDescription• Enable gzip compression for package metadata responses in pnpr server • Add gzip decompression support to pacquet client via reqwest feature • Scope compression to JSON endpoints, excluding binary payloads • Add comprehensive tests for gzip compression behavior Diagramflowchart LR
A["Client Request<br/>Accept-Encoding: gzip"] -->|reqwest gzip feature| B["Pacquet Client"]
B -->|HTTP Request| C["pnpr Server"]
C -->|CompressionLayer<br/>JSON only| D["Gzip Compress<br/>Packuments"]
D -->|Content-Encoding: gzip| E["Client Receives<br/>Compressed Metadata"]
E -->|Auto-decompress| F["Transparent to App"]
G["Binary Payloads<br/>Tarballs, Streams"] -->|Excluded| C
File Changes1. Cargo.toml
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@pnpr/crates/pnpr/Cargo.toml`:
- Line 63: Remove the redundant dev-dependency entry for flate2 in Cargo.toml:
delete the line "flate2 = { workspace = true }" from the [dev-dependencies]
section because flate2 is already declared under [dependencies] (used by
install_accelerator.rs via flate2::write::GzEncoder) and runtime dependencies
are available to tests.
🪄 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: ab68b007-bd1a-4c03-a2ae-0614d914820e
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
Cargo.tomlpnpr/crates/pnpr/Cargo.tomlpnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/tests/server.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). (9)
- GitHub Check: Code Coverage
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Dylint
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Analyze (javascript)
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
pnpr/**/pnpr/**/*.rs
📄 CodeRabbit inference engine (pnpr/AGENTS.md)
pnpr/**/pnpr/**/*.rs: Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, and error handling
Follow the pacquet contributing guide (../pacquet/CONTRIBUTING.md) for test layout and Rust conventions
Files:
pnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/tests/server.rs
pnpr/**/pnpr/**/Cargo.toml
📄 CodeRabbit inference engine (pnpr/AGENTS.md)
Declare new shared dependencies in the root [workspace.dependencies] and use { workspace = true } in pnpr crate's Cargo.toml
Files:
pnpr/crates/pnpr/Cargo.toml
🧠 Learnings (17)
📓 Common learnings
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-25T12:36:42.202Z
Learning: User-visible changes (CLI flags, defaults, environment variables, lockfile/manifest/state-file formats, error codes/messages, log emissions, store layout, hook semantics) in pnpm must be mirrored to pacquet in the same PR
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Match how the same feature is implemented in the TypeScript pnpm CLI — any change in pacquet must match pnpm's behavior, logic, edge cases, config resolution, error messages, file/lockfile formats, and existing tests
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11773
File: pacquet/crates/resolving-tarball-resolver/src/tarball_resolver.rs:115-117
Timestamp: 2026-05-20T20:41:30.632Z
Learning: In the pacquet Rust port of pnpm, the `is_http_url` helper in `pacquet/crates/resolving-tarball-resolver/src/tarball_resolver.rs` intentionally uses `bare.starts_with("http:") || bare.starts_with("https:")` (not `"http://"` / `"https://"`) to match upstream pnpm's `startsWith('http:')` / `startsWith('https:')` check byte-for-byte. Pacquet's cardinal rule (pacquet/AGENTS.md) requires matching pnpm even on quirks; malformed non-URL inputs are rejected downstream by `reqwest::Url::parse` as a `ResolveError`.
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Prefer existing pacquet-* crates over writing new code; check pacquet-tarball, pacquet-crypto-hash, pacquet-crypto-shasums-file, pacquet-package-manifest, pacquet-network, pacquet-registry, pacquet-fs, and pacquet-diagnostics before implementing non-trivial functionality
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:08:06.093Z
Learning: Pacquet (pnpm's Rust port) has a cardinal rule: "match pnpm exactly — do not fix pnpm quirks unless the same fix has landed in pnpm first." Review comments should not suggest behavioral deviations from upstream pnpm, even when the upstream behavior appears buggy. If a real bug is identified, it must be fixed upstream first.
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:11:04.272Z
Learning: In the pacquet Rust port (pnpm/pnpm repo), the `ResolvedPackage.optional` AND-folding on revisit intentionally mirrors pnpm's `resolveDependencies.ts:1627-1648` behavior: only the directly-revisited package's `optional` flag is updated; transitive descendants are not re-walked. pnpm CLI corrects stale optional flags downstream via `copyDependencySubGraph` BFS in `lockfile/pruner/src/index.ts:160-205`, which tracks a `nonOptional` set and re-stamps any package reachable by an all-non-optional path. Pacquet does not yet have this pruner equivalent, so the stale flags flow directly through `dependencies_graph_to_lockfile.rs:409` → `create_virtual_store.rs:762` → `installability.rs:394`. A follow-up to port `copyDependencySubGraph` is planned; until then, do not flag the resolver-layer optional propagation gap as a bug in pacquet PRs — it is intentional parity with pnpm's resolver layer.
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Do not add a dependency not already declared in `[workspace.dependencies]` without explicit human request; ask for approval and request addition to the workspace root `Cargo.toml`
Applied to files:
Cargo.tomlpnpr/crates/pnpr/Cargo.toml
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/**/Cargo.toml : Declare new shared dependencies in the root [workspace.dependencies] and use { workspace = true } in pnpr crate's Cargo.toml
Applied to files:
Cargo.tomlpnpr/crates/pnpr/Cargo.toml
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/Cargo.toml : Keep dependencies at the right level — add a new dependency to the specific crate that needs it, not to the workspace root or shared crate unless multiple crates depend on it
Applied to files:
Cargo.tomlpnpr/crates/pnpr/Cargo.toml
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/Cargo.toml : Check whether the workspace already depends on something suitable in `[workspace.dependencies]` in the root `Cargo.toml` before adding a new dependency
Applied to files:
Cargo.tomlpnpr/crates/pnpr/Cargo.toml
📚 Learning: 2026-05-20T19:41:03.063Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:41:03.063Z
Learning: In the pacquet project (Rust), the semver crate used is `node-semver` (version ~2.2.0), NOT `nodejs-semver`. These are two distinct crates. `node-semver` only exposes `Range::satisfies` as its public satisfaction API — there is no separate `satisfies_with_prerelease` method. Prerelease-tolerant matching is handled inline by retrying against the stripped `MAJOR.MINOR.PATCH` base when `Range::satisfies` rejects a prerelease version.
Applied to files:
Cargo.toml
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Prefer existing pacquet-* crates over writing new code; check pacquet-tarball, pacquet-crypto-hash, pacquet-crypto-shasums-file, pacquet-package-manifest, pacquet-network, pacquet-registry, pacquet-fs, and pacquet-diagnostics before implementing non-trivial functionality
Applied to files:
Cargo.tomlpnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Port relevant pnpm tests to Rust tests whenever they translate when porting behavior from pnpm
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/**/*.rs : Follow the pacquet contributing guide (../pacquet/CONTRIBUTING.md) for test layout and Rust conventions
Applied to files:
pnpr/crates/pnpr/tests/server.rspnpr/crates/pnpr/Cargo.toml
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/*.rs : Tests are documentation — do not duplicate test scenarios, edge cases, failure modes, or worked examples in prose when they are already captured by tests
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/**/*.rs : Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, and error handling
Applied to files:
pnpr/crates/pnpr/tests/server.rspnpr/crates/pnpr/Cargo.toml
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Use snapshot tests with `insta` and carefully review diffs when intentional changes alter snapshots; accept with `cargo insta review` only after careful review
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Tests that need the mocked registry should start `pnpr` through `pacquet-testing-utils`; `cargo test` / `cargo nextest run` should not require a separate `just registry-mock launch` step
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-20T20:41:30.632Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11773
File: pacquet/crates/resolving-tarball-resolver/src/tarball_resolver.rs:115-117
Timestamp: 2026-05-20T20:41:30.632Z
Learning: In the pacquet Rust port of pnpm, the `is_http_url` helper in `pacquet/crates/resolving-tarball-resolver/src/tarball_resolver.rs` intentionally uses `bare.starts_with("http:") || bare.starts_with("https:")` (not `"http://"` / `"https://"`) to match upstream pnpm's `startsWith('http:')` / `startsWith('https:')` check byte-for-byte. Pacquet's cardinal rule (pacquet/AGENTS.md) requires matching pnpm even on quirks; malformed non-URL inputs are rejected downstream by `reqwest::Url::parse` as a `ResolveError`.
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Prefer real fixtures over dependency-injection seams — use `tempfile::TempDir`, the mocked registry, or integration tests spawning the actual binary for happy paths and most error paths; use the DI seam only for filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths
Applied to files:
pnpr/crates/pnpr/tests/server.rspnpr/crates/pnpr/Cargo.toml
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/crates/**/Cargo.toml : Cargo.toml files for new registry-only crates must use the pnpr- prefix for the package name
Applied to files:
pnpr/crates/pnpr/Cargo.toml
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/crates/**/Cargo.toml : New registry-only crates must be placed under pnpr/crates/<short-name>/ and named pnpr-<short-name> in Cargo.toml, never using the pacquet- prefix
Applied to files:
pnpr/crates/pnpr/Cargo.toml
🔇 Additional comments (9)
Cargo.toml (2)
117-124: LGTM!
145-145: LGTM!pnpr/crates/pnpr/src/server.rs (2)
30-36: LGTM!
130-148: LGTM!pnpr/crates/pnpr/tests/server.rs (5)
5-5: LGTM!Also applies to: 9-9
571-586: LGTM!
588-627: LGTM!
629-654: LGTM!
656-691: LGTM!
Micro-Benchmark ResultsLinux |
Neither side used gzip for package metadata: pacquet fetched packuments uncompressed from every registry (unlike pnpm-TS, which gets gzip via undici), and pnpr served them uncompressed. Packuments are the largest payloads pulled during resolution and gzip ~5-10x, so this was a real resolution-time cost and a divergence from how a CDN-fronted registry behaves. Closes #12169. Both halves are needed and land together: - Client (pacquet): enable reqwest`s `gzip` feature and set `.gzip(true)` explicitly on the network client builder, so it sends `Accept-Encoding: gzip` and transparently decompresses. Tarballs are unaffected (served as `application/octet-stream` with no `Content-Encoding`, so reqwest leaves them alone and store-integrity verification is unchanged). It also transparently handles the install accelerator's already-gzipped `/v1/files` stream — the client's existing magic-byte check covers the now-auto-decompressed case. - Server (pnpr): add a `tower-http` `CompressionLayer`, scoped to JSON via `NotForContentType` so it compresses packuments / version manifests / dist-tags / search but never re-gzips an already-compressed payload: tarballs (`application/octet-stream`), the file stream (`application/x-pnpm-install`), or the resolve NDJSON (`application/x-ndjson`). pnpr is commonly hit directly with no CDN or nginx in front, so the application is the only layer that can compress; where a proxy/CDN is present, the `Content-Encoding: gzip` is passed through (no double compression). Tests assert a packument is gzipped when `Accept-Encoding: gzip` is sent, served plain otherwise, and a tarball is never re-gzipped.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
Cargo.toml (1)
117-124: ⚡ Quick winScope
reqwest’sgzipfeature to the crates that actually need it
reqwest0.13’sgzipfeature is the right knob for transparent gzip decompression of response bodies.- Adding
gzipin[workspace.dependencies]makes every workspace crate that usesreqwestpull in gzip-related transitive dependencies even if they don’t need it.- Keep the workspace
reqwestfeatures minimal and enablegziponly where required (e.g., in the specific crate’sCargo.toml:reqwest = { workspace = true, features = ["gzip"] }).🤖 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 `@Cargo.toml` around lines 117 - 124, The workspace Cargo.toml currently enables reqwest's "gzip" feature globally which forces all workspace crates that depend on reqwest to pull in gzip-related transitive deps; update the workspace dependency declaration for reqwest to remove "gzip" from the features list, keep only minimal features needed workspace-wide (e.g., "json","rustls",etc.), and then enable "gzip" selectively in the individual crate Cargo.toml files that actually need transparent gzip decompression by adding reqwest = { workspace = true, features = ["gzip"] } there; look for the workspace dependency entry named "reqwest" and the crate-level Cargo.toml files that declare reqwest to make this change.pnpr/crates/pnpr/tests/server.rs (1)
659-659: 💤 Low valuePotentially misleading comment in test body.
The comment references "compression-size-floor," but tower-http's
CompressionLayeris configured to excludeapplication/octet-streamviaNotForContentType(see context snippet 1), so the body size is irrelevant—tarballs are never compressed regardless of size. The longer body doesn't hurt, but the comment might confuse future readers.Simplify the test body or clarify the comment
Option 1: Remove the comment and use a simpler body:
- let bytes = b"fake-tarball-bytes-long-enough-to-clear-the-compression-size-floor"; + let bytes = b"fake-tarball-bytes";Option 2: Clarify the comment to reference content-type exclusion:
- let bytes = b"fake-tarball-bytes-long-enough-to-clear-the-compression-size-floor"; + // Body size doesn't matter; the middleware excludes application/octet-stream entirely. + let bytes = b"fake-tarball-bytes";🤖 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 `@pnpr/crates/pnpr/tests/server.rs` at line 659, The test's comment about "compression-size-floor" is misleading because tower-http's CompressionLayer is configured with NotForContentType to exclude application/octet-stream, so body length doesn't affect compression; update the test around the bytes variable (let bytes = b"fake-tarball-bytes-long-enough-to-clear-the-compression-size-floor") to either (a) simplify the body to a shorter representative payload, or (b) replace the comment to explicitly state that CompressionLayer + NotForContentType excludes application/octet-stream so tarballs are never compressed regardless of size (reference CompressionLayer and NotForContentType and the bytes variable to locate the code).
🤖 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.
Nitpick comments:
In `@Cargo.toml`:
- Around line 117-124: The workspace Cargo.toml currently enables reqwest's
"gzip" feature globally which forces all workspace crates that depend on reqwest
to pull in gzip-related transitive deps; update the workspace dependency
declaration for reqwest to remove "gzip" from the features list, keep only
minimal features needed workspace-wide (e.g., "json","rustls",etc.), and then
enable "gzip" selectively in the individual crate Cargo.toml files that actually
need transparent gzip decompression by adding reqwest = { workspace = true,
features = ["gzip"] } there; look for the workspace dependency entry named
"reqwest" and the crate-level Cargo.toml files that declare reqwest to make this
change.
In `@pnpr/crates/pnpr/tests/server.rs`:
- Line 659: The test's comment about "compression-size-floor" is misleading
because tower-http's CompressionLayer is configured with NotForContentType to
exclude application/octet-stream, so body length doesn't affect compression;
update the test around the bytes variable (let bytes =
b"fake-tarball-bytes-long-enough-to-clear-the-compression-size-floor") to either
(a) simplify the body to a shorter representative payload, or (b) replace the
comment to explicitly state that CompressionLayer + NotForContentType excludes
application/octet-stream so tarballs are never compressed regardless of size
(reference CompressionLayer and NotForContentType and the bytes variable to
locate the code).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 1f6d399e-8031-43a5-8a77-d8ee86ccc858
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
Cargo.tomlpnpr/crates/pnpr/src/server.rspnpr/crates/pnpr/tests/server.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- pnpr/crates/pnpr/src/server.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). (10)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Dylint
- GitHub Check: Doc
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Analyze (javascript)
- GitHub Check: Code Coverage
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pnpr/**/pnpr/**/*.rs
📄 CodeRabbit inference engine (pnpr/AGENTS.md)
pnpr/**/pnpr/**/*.rs: Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, and error handling
Follow the pacquet contributing guide (../pacquet/CONTRIBUTING.md) for test layout and Rust conventions
Files:
pnpr/crates/pnpr/tests/server.rs
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-25T12:36:42.202Z
Learning: User-visible changes (CLI flags, defaults, environment variables, lockfile/manifest/state-file formats, error codes/messages, log emissions, store layout, hook semantics) in pnpm must be mirrored to pacquet in the same PR
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Match how the same feature is implemented in the TypeScript pnpm CLI — any change in pacquet must match pnpm's behavior, logic, edge cases, config resolution, error messages, file/lockfile formats, and existing tests
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:08:06.093Z
Learning: Pacquet (pnpm's Rust port) has a cardinal rule: "match pnpm exactly — do not fix pnpm quirks unless the same fix has landed in pnpm first." Review comments should not suggest behavioral deviations from upstream pnpm, even when the upstream behavior appears buggy. If a real bug is identified, it must be fixed upstream first.
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Reference the upstream pnpm commit/PR when porting code from pnpm in commit messages
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Do not add a dependency not already declared in `[workspace.dependencies]` without explicit human request; ask for approval and request addition to the workspace root `Cargo.toml`
Applied to files:
Cargo.toml
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/Cargo.toml : Keep dependencies at the right level — add a new dependency to the specific crate that needs it, not to the workspace root or shared crate unless multiple crates depend on it
Applied to files:
Cargo.toml
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/**/Cargo.toml : Declare new shared dependencies in the root [workspace.dependencies] and use { workspace = true } in pnpr crate's Cargo.toml
Applied to files:
Cargo.toml
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/Cargo.toml : Check whether the workspace already depends on something suitable in `[workspace.dependencies]` in the root `Cargo.toml` before adding a new dependency
Applied to files:
Cargo.toml
📚 Learning: 2026-05-20T19:41:03.063Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:41:03.063Z
Learning: In the pacquet project (Rust), the semver crate used is `node-semver` (version ~2.2.0), NOT `nodejs-semver`. These are two distinct crates. `node-semver` only exposes `Range::satisfies` as its public satisfaction API — there is no separate `satisfies_with_prerelease` method. Prerelease-tolerant matching is handled inline by retrying against the stripped `MAJOR.MINOR.PATCH` base when `Range::satisfies` rejects a prerelease version.
Applied to files:
Cargo.toml
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Prefer existing pacquet-* crates over writing new code; check pacquet-tarball, pacquet-crypto-hash, pacquet-crypto-shasums-file, pacquet-package-manifest, pacquet-network, pacquet-registry, pacquet-fs, and pacquet-diagnostics before implementing non-trivial functionality
Applied to files:
Cargo.tomlpnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Port relevant pnpm tests to Rust tests whenever they translate when porting behavior from pnpm
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/*.rs : Tests are documentation — do not duplicate test scenarios, edge cases, failure modes, or worked examples in prose when they are already captured by tests
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/**/*.rs : Follow the pacquet contributing guide (../pacquet/CONTRIBUTING.md) for test layout and Rust conventions
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Use snapshot tests with `insta` and carefully review diffs when intentional changes alter snapshots; accept with `cargo insta review` only after careful review
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Tests that need the mocked registry should start `pnpr` through `pacquet-testing-utils`; `cargo test` / `cargo nextest run` should not require a separate `just registry-mock launch` step
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:24.797Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pnpr/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:24.797Z
Learning: Applies to pnpr/**/pnpr/**/*.rs : Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, and error handling
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-29T18:03:15.372Z
Learnt from: CR
Repo: pnpm/pnpm PR: 0
File: pacquet/AGENTS.md:0-0
Timestamp: 2026-05-29T18:03:15.372Z
Learning: Applies to pacquet/**/tests/**/*.rs : Prefer real fixtures over dependency-injection seams — use `tempfile::TempDir`, the mocked registry, or integration tests spawning the actual binary for happy paths and most error paths; use the DI seam only for filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths
Applied to files:
pnpr/crates/pnpr/tests/server.rs
📚 Learning: 2026-05-20T20:41:30.632Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11773
File: pacquet/crates/resolving-tarball-resolver/src/tarball_resolver.rs:115-117
Timestamp: 2026-05-20T20:41:30.632Z
Learning: In the pacquet Rust port of pnpm, the `is_http_url` helper in `pacquet/crates/resolving-tarball-resolver/src/tarball_resolver.rs` intentionally uses `bare.starts_with("http:") || bare.starts_with("https:")` (not `"http://"` / `"https://"`) to match upstream pnpm's `startsWith('http:')` / `startsWith('https:')` check byte-for-byte. Pacquet's cardinal rule (pacquet/AGENTS.md) requires matching pnpm even on quirks; malformed non-URL inputs are rejected downstream by `reqwest::Url::parse` as a `ResolveError`.
Applied to files:
pnpr/crates/pnpr/tests/server.rs
🔇 Additional comments (6)
Cargo.toml (1)
145-145: ⚡ Quick winUse
tower-http’scompression-gzipfeature where it’s actually needed
tower-httpv0.6.11 supports thecompression-gzipfeature (used byCompressionLayerfor gzip responses). Keep the feature scoped to the specific crate(s) that useCompressionLayer(if more thanpnprconsumestower-httpvia workspace dependencies); otherwise the current workspace-level enablement is fine.# workspace Cargo.toml tower-http = { version = "0.6.11", features = ["trace"] } # pnpr/crates/pnpr/Cargo.toml [dependencies] tower-http = { workspace = true, features = ["compression-gzip"] }pnpr/crates/pnpr/tests/server.rs (5)
5-5: LGTM!Also applies to: 9-9
571-586: LGTM!
588-627: LGTM!
629-654: LGTM!
656-691: LGTM!
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #12170 +/- ##
==========================================
- Coverage 87.58% 87.58% -0.01%
==========================================
Files 269 269
Lines 30807 30814 +7
==========================================
+ Hits 26982 26988 +6
- Misses 3825 3826 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Each scenario has pacquet rows (direct install) and pnpr rows (the same client through the pnpr install accelerator), so pnpr@HEAD vs pacquet@HEAD is the pnpr-vs-direct ratio. Cold-store scenarios wipe the client store between runs (warm server); hot-store scenarios keep it warm. The pacquet@HEAD rows feed the pacquet Bencher testbed; the pnpr@HEAD rows feed the pnpr testbed. Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.80701957108,
"stddev": 0.08538669815009696,
"median": 4.772740564479999,
"user": 2.49091934,
"system": 3.7323969800000008,
"min": 4.74630576548,
"max": 5.02543070548,
"times": [
4.78497789548,
4.7629250694800005,
4.82215673948,
4.77584326548,
4.76963786348,
4.87048953948,
4.75409372948,
4.7583351374800005,
5.02543070548,
4.74630576548
]
},
{
"command": "pacquet@main",
"mean": 4.799306362579999,
"stddev": 0.03004846241487064,
"median": 4.7865031169800005,
"user": 2.50235984,
"system": 3.72430078,
"min": 4.76588108348,
"max": 4.85269175748,
"times": [
4.78011671548,
4.78080083948,
4.82823280548,
4.7760069634799995,
4.82862274948,
4.85269175748,
4.79220539448,
4.76588108348,
4.77071875748,
4.81778655948
]
},
{
"command": "pnpr@HEAD",
"mean": 2.0085962152800003,
"stddev": 0.05527478553207941,
"median": 2.02231790198,
"user": 2.70550474,
"system": 3.23022208,
"min": 1.92142312948,
"max": 2.08243758948,
"times": [
1.9570593224800001,
2.02986526148,
1.92142312948,
2.03022588348,
2.02124465648,
2.08243758948,
2.01164653548,
2.07660204648,
2.02339114748,
1.93206658048
]
},
{
"command": "pnpr@main",
"mean": 2.06365564508,
"stddev": 0.0981522044439346,
"median": 2.00831398848,
"user": 2.65967504,
"system": 3.27441988,
"min": 1.99105302748,
"max": 2.29285537848,
"times": [
2.29285537848,
2.07179604748,
2.10593956448,
1.9979577424800001,
1.99105302748,
2.00484418748,
2.15752842748,
2.00434095648,
1.9984573294799999,
2.01178378948
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6877080606800001,
"stddev": 0.021200945301639185,
"median": 0.6821600184800001,
"user": 0.39473642,
"system": 1.3290748799999998,
"min": 0.66537931098,
"max": 0.73841654398,
"times": [
0.73841654398,
0.6952708039800001,
0.6822270609800001,
0.70442012998,
0.66783124598,
0.6820929759800001,
0.66537931098,
0.67652656298,
0.68143370798,
0.68348226398
]
},
{
"command": "pacquet@main",
"mean": 0.6884318269799999,
"stddev": 0.01637260347982969,
"median": 0.68484677598,
"user": 0.39261401999999995,
"system": 1.3517216799999998,
"min": 0.6611917539800001,
"max": 0.71677048598,
"times": [
0.71677048598,
0.68303469198,
0.68138303398,
0.67778750698,
0.6611917539800001,
0.6776228589800001,
0.7097498719800001,
0.6925051009800001,
0.69761410498,
0.68665885998
]
},
{
"command": "pnpr@HEAD",
"mean": 0.69834662128,
"stddev": 0.03465244615066035,
"median": 0.68666501698,
"user": 0.38802941999999996,
"system": 1.36033958,
"min": 0.67063064798,
"max": 0.78639533898,
"times": [
0.72555109298,
0.68582516098,
0.69875863998,
0.69449668298,
0.67388819598,
0.67960702198,
0.6808085579800001,
0.67063064798,
0.6875048729800001,
0.78639533898
]
},
{
"command": "pnpr@main",
"mean": 0.72769094798,
"stddev": 0.03695797311832049,
"median": 0.72990901048,
"user": 0.39318672,
"system": 1.3527690799999998,
"min": 0.67774306698,
"max": 0.7891103849800001,
"times": [
0.74659826098,
0.7512350439800001,
0.7469708269800001,
0.67896285498,
0.7613645329800001,
0.67774306698,
0.71180064998,
0.71321975998,
0.6999040979800001,
0.7891103849800001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.22204073766,
"stddev": 0.04833327267142016,
"median": 2.20667436086,
"user": 3.5708206,
"system": 3.0266350999999996,
"min": 2.18473396236,
"max": 2.34914926336,
"times": [
2.20667306636,
2.18473396236,
2.20988585636,
2.1953331043600004,
2.34914926336,
2.1888956363600003,
2.23126849036,
2.24572104336,
2.2020712983600004,
2.20667565536
]
},
{
"command": "pacquet@main",
"mean": 2.20894545426,
"stddev": 0.028315035054633682,
"median": 2.2063308793600003,
"user": 3.5594250999999995,
"system": 2.9918217,
"min": 2.16916022436,
"max": 2.25833905736,
"times": [
2.18403558536,
2.25833905736,
2.20173295736,
2.21258593636,
2.18412522736,
2.19737593936,
2.2226606553600003,
2.16916022436,
2.21092880136,
2.2485101583600002
]
},
{
"command": "pnpr@HEAD",
"mean": 2.2091435147600005,
"stddev": 0.01893134486423863,
"median": 2.20887171636,
"user": 3.542442299999999,
"system": 2.9970532,
"min": 2.1883365983600003,
"max": 2.25120392636,
"times": [
2.2133554273600002,
2.19384606736,
2.21265959636,
2.2132397693600003,
2.2253265143600003,
2.1883365983600003,
2.25120392636,
2.19712244736,
2.20508383636,
2.19126096436
]
},
{
"command": "pnpr@main",
"mean": 2.211546146460001,
"stddev": 0.030244074128081767,
"median": 2.20938211786,
"user": 3.5761952999999997,
"system": 2.9938182999999996,
"min": 2.17298140736,
"max": 2.2686363053600003,
"times": [
2.18507727236,
2.2187132533600002,
2.1849214093600002,
2.24295966336,
2.20936112436,
2.2343836823600003,
2.2094031113600003,
2.1890242353600002,
2.17298140736,
2.2686363053600003
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.4239389454600002,
"stddev": 0.019314553174934537,
"median": 1.4209462579600003,
"user": 1.5293084999999997,
"system": 1.8053105999999999,
"min": 1.38420911846,
"max": 1.45913740446,
"times": [
1.4377992424600001,
1.42958658246,
1.41721014146,
1.41999589046,
1.45913740446,
1.4200511474600002,
1.4356033674600002,
1.38420911846,
1.4218413684600002,
1.4139551914600001
]
},
{
"command": "pacquet@main",
"mean": 1.4572398962600004,
"stddev": 0.04246245310762424,
"median": 1.4460615959600003,
"user": 1.5705858,
"system": 1.7981643000000003,
"min": 1.41667931746,
"max": 1.5506282984600002,
"times": [
1.41667931746,
1.5506282984600002,
1.44188319946,
1.4210452584600002,
1.45314642146,
1.44242000346,
1.43108970046,
1.45111327246,
1.4497031884600002,
1.5146903024600002
]
},
{
"command": "pnpr@HEAD",
"mean": 1.40157110796,
"stddev": 0.042984497404747364,
"median": 1.3852319029600002,
"user": 1.5351025,
"system": 1.7435771999999996,
"min": 1.36313831546,
"max": 1.49649366246,
"times": [
1.39111327346,
1.37456255546,
1.36313831546,
1.49649366246,
1.3749448664600001,
1.3798765614600002,
1.36896365046,
1.4261218074600002,
1.4499091424600001,
1.39058724446
]
},
{
"command": "pnpr@main",
"mean": 1.4612571035600002,
"stddev": 0.07083349027852179,
"median": 1.44706706796,
"user": 1.5594819,
"system": 1.7736645,
"min": 1.36386049346,
"max": 1.59106739246,
"times": [
1.3949307424600002,
1.36386049346,
1.52665533146,
1.42622657746,
1.59106739246,
1.5391952954600001,
1.46437931346,
1.44430297746,
1.4121217534600001,
1.44983115846
]
}
]
} |
|
| Branch | pr/12170 |
| Testbed | pacquet |
🚨 1 Alert
| Benchmark | Measure Units | View | Benchmark Result (Result Δ%) | Upper Boundary (Limit %) |
|---|---|---|---|---|
| isolated-linker.fresh-restore.cold-cache.cold-store | Latency seconds (s) | 📈 plot 🚷 threshold 🚨 alert (🔔) | 4.81 s(+125.10%)Baseline: 2.14 s | 2.56 s (187.58%) |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,222.04 ms(-4.21%)Baseline: 2,319.59 ms | 2,783.51 ms (79.83%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,423.94 ms(-4.76%)Baseline: 1,495.14 ms | 1,794.17 ms (79.36%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold 🚨 view alert (🔔) | 4,807.02 ms(+125.10%)Baseline: 2,135.50 ms | 2,562.60 ms (187.58%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 687.71 ms(+5.79%)Baseline: 650.08 ms | 780.10 ms (88.16%) |
Closes #12169.
What
Package metadata (packuments) now travels gzip-compressed between pacquet and pnpr — matching how a real, CDN-fronted registry serves it. Previously neither side used gzip for metadata: pacquet fetched packuments uncompressed from every registry (unlike pnpm-TS, which gets gzip transparently via undici), and pnpr served them uncompressed. Packuments are the largest payloads pulled during resolution and gzip ~5–10×, so this was a real resolution-time cost — and it's especially visible on the frozen-install lockfile-verification path, which fetches full packuments.
Both halves are needed and land together (server-only would be a no-op for pacquet, which never asked for gzip):
Client (pacquet)
Enable reqwest's
gzipfeature → it sendsAccept-Encoding: gzipand transparently decompresses.application/octet-streamwith noContent-Encoding, so reqwest leaves them alone — store-integrity verification is unchanged./v1/filesunaffected: that stream is already gzipped with an explicitContent-Encoding: gzip; reqwest now auto-decompresses it, and the pnpr-client's existing gzip-magic-byte check already handles the decompressed case.Server (pnpr)
Add a
tower-httpCompressionLayer, scoped to JSON viaNotForContentTypeso it compresses packuments / version manifests / dist-tags / search but never re-gzips an already-compressed payload — tarballs (application/octet-stream), the file stream (application/x-pnpm-install), or the resolve NDJSON (application/x-ndjson).On the architecture question of why in the app vs. a CDN/nginx: pnpr's common shape is a warm server hit directly over a LAN/CI network with no proxy in front — there the application is the only layer that can compress. Where a CDN/nginx is present, the
Content-Encoding: gzipis simply passed through (no double compression), or origin compression is disabled by config.Tests
pnpr/crates/pnpr/tests/server.rsasserts a packument is gzipped whenAccept-Encoding: gzipis sent, served plain otherwise, and a tarball is never re-gzipped. End-to-end coverage comes for free from the existing suites: the pnpr-client integration tests (the pnpr server's own resolver fetches gzipped packuments) and thepacquet adde2e tests (direct-client resolution) both pass with the feature on.Notes
async-compression(+compression-codecs/-core) transitively via reqwest/tower-http — standard, MIT/Apache crates.Written by an agent (Claude Code, claude-opus-4-8).
Summary by CodeRabbit
New Features
Tests