feat(config): make network settings configurable in pacquet#12047
Conversation
Port pnpm's `networkConcurrency`, `fetchTimeout`, `userAgent`, and `npmrcAuthFile` to pacquet, replacing the hardcoded network-client constants. Each is configurable via `pnpm-workspace.yaml`, the `PNPM_CONFIG_*` env overlay, and a dedicated CLI flag, matching pnpm's config surface. - `pacquet-network`: add `NetworkSettings` (concurrency, timeout, UA) threaded into `ThrottledClient::for_installs`; `fetch_timeout` drives both the response and connect deadlines. `DEFAULT_FETCH_TIMEOUT_MS` (60s) and `DEFAULT_USER_AGENT` are the default sources. - `pacquet-config`: add the four `Config` fields with cascade wiring; resolve `npmrcAuthFile` (and the `--userconfig` alias / env vars) to redirect the user-level `.npmrc` read in `Config::current`. - Relocate the Rust->Node `host_platform`/`host_arch` mappers into `pacquet-detect-libc` (re-exported from `graph-hasher`) and reuse them to build pnpm's `pnpm/<version> npm/? node/? <platform> <arch>` UA. Two intentional behaviour changes for parity: the default request timeout moves from 300s to pnpm's 60s, and the default User-Agent moves from the literal `pnpm` to pnpm's full UA format. Refs #12042
|
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)
📝 WalkthroughWalkthroughAdds network tuning (network_concurrency, fetch_timeout, user_agent) and an explicit npmrc auth-file override across config, workspace/env overlays, CLI, network client construction, npmrc auth rescoping/merging, detection/version defaults, and tests. ChangesNetwork settings and npmrc auth override
Sequence DiagramsequenceDiagram
participant CLI as pacquet CLI
participant Config as pacquet_config::Config
participant State as pacquet::State
participant Network as pacquet_network::ThrottledClient
CLI->>Config: seed npmrc_auth_file + load current (env/YAML applied)
CLI->>Config: apply CLI network overrides for install
State->>Network: construct for_installs with NetworkSettings from Config
Network->>Network: build client (apply fetch_timeout, user_agent, concurrency)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 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 QodoPort pnpm's network settings to pacquet with configurable concurrency, timeout, and user-agent
WalkthroughsDescription• Port pnpm's network settings (networkConcurrency, fetchTimeout, userAgent, npmrcAuthFile) to pacquet • Add NetworkSettings struct threaded into ThrottledClient::for_installs with configurable concurrency, timeout, and user-agent • Wire four new Config fields through yaml, PNPM_CONFIG_* env vars, and CLI flags (--network-concurrency, --fetch-timeout, --user-agent, --npmrc-auth-file) • Relocate Rust→Node platform/arch mappers to pacquet-detect-libc for reuse in pnpm-compatible user-agent format • Change default request timeout from 300s to pnpm's 60s and default user-agent from literal pnpm to full format pnpm/ npm/? node/? Diagramflowchart LR
CLI["CLI flags<br/>--network-concurrency<br/>--fetch-timeout<br/>--user-agent<br/>--npmrc-auth-file"]
YAML["pnpm-workspace.yaml<br/>networkConcurrency<br/>fetchTimeout<br/>userAgent"]
ENV["PNPM_CONFIG_*<br/>env vars"]
CONFIG["Config struct<br/>network_concurrency<br/>fetch_timeout<br/>user_agent<br/>npmrc_auth_file"]
NETWORK["NetworkSettings<br/>struct"]
CLIENT["ThrottledClient<br/>for_installs"]
CLI -->|highest priority| CONFIG
YAML -->|medium priority| CONFIG
ENV -->|medium priority| CONFIG
CONFIG -->|resolved values| NETWORK
NETWORK -->|concurrency/timeout/UA| CLIENT
File Changes1. pacquet/crates/cli/src/cli_args.rs
|
Micro-Benchmark ResultsLinux |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #12047 +/- ##
==========================================
+ Coverage 88.73% 89.07% +0.33%
==========================================
Files 234 235 +1
Lines 30461 31354 +893
==========================================
+ Hits 27031 27928 +897
+ Misses 3430 3426 -4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/config/src/lib.rs`:
- Around line 1333-1336: The environment-variable fallback chain in lib.rs uses
Sys::var for PNPM_CONFIG_NPMRC_AUTH_FILE and PNPM_CONFIG_USERCONFIG but misses
the lowercase alias "pnpm_config_userconfig", causing pnpm_config_userconfig to
be ignored; update the Sys::var chain (the same expression that calls
Sys::var("PNPM_CONFIG_NPMRC_AUTH_FILE").or_else(...).or_else(...)) to also call
Sys::var("pnpm_config_userconfig") (in the same position as the other lowercase
alias) so both casing variants for PNPM_CONFIG_USERCONFIG are honored.
- Around line 1344-1349: The code currently uses the file path itself as the
base dir when parent() is None, which causes relative paths in the npmrc to
resolve incorrectly; change the unwrap_or_else branch to use the caller's
current working directory (e.g., std::env::current_dir() falling back to "." on
error) instead of cloning the file path so NpmrcAuth::from_ini receives the
containing directory; update the map closure that returns (text, dir) (the block
using parent().map(...).unwrap_or_else(...)) to compute dir as the parent
directory or cwd.
In `@pacquet/crates/network/src/lib.rs`:
- Around line 286-287: ThrottledClient::for_installs must guard against
settings.network_concurrency == 0 because Semaphore::new(0) causes
acquire().await to block forever; change the construction path that currently
calls Semaphore::new(settings.network_concurrency) to validate and fail fast or
clamp to at least 1 (e.g., return an Err with a clear message if
network_concurrency == 0, or use usize::max(1, settings.network_concurrency)) so
installs never deadlock; update the code around ThrottledClient::for_installs
where Semaphore::new is invoked and ensure the returned error message references
network_concurrency when failing.
🪄 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: a0f13ce8-0b3e-47f3-954b-87c6b81aedb8
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
pacquet/crates/cli/src/cli_args.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/cli/src/state.rspacquet/crates/config/Cargo.tomlpacquet/crates/config/src/defaults.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/config/src/env_overlay.rspacquet/crates/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/detect-libc/src/lib.rspacquet/crates/graph-hasher/src/engine_name.rspacquet/crates/network/src/lib.rspacquet/crates/network/src/tests.rspacquet/crates/package-manager/src/install_package_from_registry/tests.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). (4)
- GitHub Check: ubuntu-latest / Node.js 24 / Test
- GitHub Check: Code Coverage
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: Warnings are errors (--deny warningsin lint). Do not silence them with#[allow(...)]unless there is a specific, justified reason.
Choose owned vs. borrowed parameters to minimize copies; widen to the most encompassing type (&Pathover&PathBuf,&strover&String) when it doesn't force extra copies.
PreferArc::clone(&x)/Rc::clone(&x)overx.clone()for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming: https://rust-lang.github.io/api-guidelines/naming.html
No star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;, and the same for any other glob whose target is a module you control. Two forms stay allowed: external-crate preludes such asuse rayon::prelude::*;and root-of-module re-exports such aspub use submodule::*;in alib.rs.
Doc comments (///,//!) document the contract: preconditions, postconditions, panics, the reason the function exists. They are not a re-narration of the body.
Do not restate at call sites what the callee's doc comment already says. If///on the function says 'no-op when …', the caller should not repeat that. Update the doc once; let every call site benefit.
Tests are documentation. Do not duplicate them in prose. If a behavioral scenario, edge case, failure mode, or worked example is already captured by a test, do not also narrate it in the doc comment on the implementation. The same applies in reverse: a test's own doc comment should not re-explain what the asserts already say, only the why if it is not obvious.
Use// SAFETY:,// TODO:, and similar prefixes to signal hidden invariants or known follow-ups that a reader cannot recover from the code alone.
When editing existing code, do not break a method chain (includingpipe-trait.pipe(...)chains) into intermediateletbindings unless you can justify the rewrite. Valid justifications include a chain that fails to compile after your...
Files:
pacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/defaults.rspacquet/crates/cli/src/state.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/detect-libc/src/lib.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/network/src/lib.rspacquet/crates/cli/src/cli_args.rspacquet/crates/graph-hasher/src/engine_name.rspacquet/crates/config/src/lib.rspacquet/crates/network/src/tests.rs
🧠 Learnings (3)
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.
Applied to files:
pacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/defaults.rspacquet/crates/cli/src/state.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/detect-libc/src/lib.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/network/src/lib.rspacquet/crates/cli/src/cli_args.rspacquet/crates/graph-hasher/src/engine_name.rspacquet/crates/config/src/lib.rspacquet/crates/network/src/tests.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.
Applied to files:
pacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/defaults.rspacquet/crates/cli/src/state.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/detect-libc/src/lib.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/network/src/lib.rspacquet/crates/cli/src/cli_args.rspacquet/crates/graph-hasher/src/engine_name.rspacquet/crates/config/src/lib.rspacquet/crates/network/src/tests.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.
Applied to files:
pacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/config/src/workspace_yaml/tests.rspacquet/crates/config/src/defaults.rspacquet/crates/cli/src/state.rspacquet/crates/cli/src/cli_args/install.rspacquet/crates/detect-libc/src/lib.rspacquet/crates/config/src/defaults/tests.rspacquet/crates/network/src/lib.rspacquet/crates/cli/src/cli_args.rspacquet/crates/graph-hasher/src/engine_name.rspacquet/crates/config/src/lib.rspacquet/crates/network/src/tests.rs
🔇 Additional comments (14)
pacquet/crates/config/Cargo.toml (1)
15-15: LGTM!pacquet/crates/config/src/defaults.rs (1)
259-282: LGTM!pacquet/crates/detect-libc/src/lib.rs (2)
63-97: LGTM!
136-155: LGTM!pacquet/crates/config/src/defaults/tests.rs (1)
434-453: LGTM!pacquet/crates/graph-hasher/src/engine_name.rs (1)
1-1: ⚡ Quick winRe-check
ENGINE_NAMEcache-key mapper equivalenceThe comparison can’t establish byte-identical output: the repo snapshot/history for
pacquet/crates/graph-hasher/src/engine_name.rsdoesn’t contain priorhost_platform/host_archimplementations, and the attempteddetect-libcpath lookup fails (fdreportsdetect-libcisn’t a directory). Add/adjust a test or small fixture that asserts the exact emitted strings from the old localhost_*functions matchpacquet_detect_libc::{host_platform, host_arch}for the full OS/arch matrix, ensuring pnpm cache interop.pacquet/crates/network/src/tests.rs (2)
17-20: LGTM!Also applies to: 164-164, 176-182, 189-195, 211-217, 227-233, 265-271, 307-313, 348-354, 364-370, 382-388, 405-411, 422-428, 435-441, 453-458, 473-479, 501-507, 528-528, 563-563
631-665: LGTM!pacquet/crates/package-manager/src/install_package_from_registry/tests.rs (1)
71-74: LGTM!pacquet/crates/network/src/lib.rs (2)
50-76: LGTM!
351-363: LGTM!pacquet/crates/cli/src/cli_args.rs (1)
24-24: LGTM!Also applies to: 34-40, 98-139, 175-187
pacquet/crates/cli/src/cli_args/install.rs (1)
197-219: LGTM!Also applies to: 240-242
pacquet/crates/cli/src/state.rs (1)
5-5: LGTM!Also applies to: 73-85
…ce tests The user-level `.npmrc` path resolution applied the empty-string filter once after the whole `or_else` chain, so an exported-but-empty `PNPM_CONFIG_NPMRC_AUTH_FILE` short-circuited resolution instead of falling through. Mirror pnpm's `readEnvVar` / `readNpmEnvVar` exactly: filter `value !== ''` per variable, accept both `pnpm_config_*` / `PNPM_CONFIG_*` cases, and add the `npm_config_userconfig` / `NPM_CONFIG_USERCONFIG` compatibility fallback. Port the translatable pnpm precedence tests from `config/reader/test/index.ts` (resolution from the PNPM_CONFIG_* family, lowercase variant, empty-falls-through, npmrc_auth_file outranks userconfig, npm_config_userconfig compat fallback + pnpm-wins). pnpm's credential-scoping tests that read the workspace .npmrc and the userconfig simultaneously and re-scope per file don't translate yet: pacquet uses a single-file (project-or-user) model, not pnpm's layered merge. Global config.yaml sourcing of npmrcAuthFile remains deferred. Refs #12042
Integrated-Benchmark Report (Linux)Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.09277841818,
"stddev": 0.08909714031331682,
"median": 2.05952879378,
"user": 2.6348738,
"system": 3.41852404,
"min": 2.02822905528,
"max": 2.33451435828,
"times": [
2.11053487928,
2.05889568528,
2.05936758528,
2.05316642528,
2.03467390728,
2.02822905528,
2.33451435828,
2.08393650628,
2.10477577728,
2.05969000228
]
},
{
"command": "pacquet@main",
"mean": 2.1150200452800005,
"stddev": 0.05720151408609704,
"median": 2.11143746778,
"user": 2.6901137,
"system": 3.42461864,
"min": 2.05435649728,
"max": 2.21387818728,
"times": [
2.17645618028,
2.06613788228,
2.13225553528,
2.21387818728,
2.05435649728,
2.14754306328,
2.06007709828,
2.09061940028,
2.05628032528,
2.15259628328
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6484092193000001,
"stddev": 0.038017772063967906,
"median": 0.6371570389000001,
"user": 0.35674789999999995,
"system": 1.3214605599999998,
"min": 0.6292929839,
"max": 0.7553392089000001,
"times": [
0.7553392089000001,
0.6348944449,
0.6393465739,
0.6374337869000001,
0.6303156079000001,
0.6292929839,
0.6310301669,
0.6368802909,
0.6406088449,
0.6489502839
]
},
{
"command": "pacquet@main",
"mean": 0.6787909652999999,
"stddev": 0.02602697648843731,
"median": 0.6724947074000001,
"user": 0.36534969999999994,
"system": 1.36207946,
"min": 0.6601970179000001,
"max": 0.7495545779,
"times": [
0.7495545779,
0.6800668959,
0.6792241359,
0.6801203349,
0.6665650609,
0.6773976529,
0.6643775519,
0.6601970179000001,
0.6628146629,
0.6675917619
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.3420414909,
"stddev": 0.026604181803345103,
"median": 2.3444414655,
"user": 3.7707546799999996,
"system": 3.17916472,
"min": 2.3001129464999996,
"max": 2.3912308185,
"times": [
2.3456084155,
2.3524930415,
2.3617720765,
2.3578520915,
2.3120962105,
2.3362923755,
2.3001129464999996,
2.3432745155,
2.3196824174999997,
2.3912308185
]
},
{
"command": "pacquet@main",
"mean": 2.3851991734999998,
"stddev": 0.03098416002487624,
"median": 2.3789911874999996,
"user": 3.8636414799999996,
"system": 3.20535002,
"min": 2.3311027775,
"max": 2.4387011435,
"times": [
2.3764394285,
2.3779619985,
2.3935099295,
2.3855048695,
2.4387011435,
2.3792299535,
2.3311027775,
2.3609858514999997,
2.4298033615,
2.3787524214999998
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.4871258807999999,
"stddev": 0.020777996159418253,
"median": 1.4800372808,
"user": 1.6874484999999997,
"system": 1.85727006,
"min": 1.4605069613000001,
"max": 1.5354491743,
"times": [
1.4742965053000001,
1.4903802863,
1.4961832473,
1.4797176513,
1.5021796043000002,
1.4605069613000001,
1.4734590163,
1.4787294513,
1.5354491743,
1.4803569103
]
},
{
"command": "pacquet@main",
"mean": 1.5145992562,
"stddev": 0.048504727053240625,
"median": 1.4976636288,
"user": 1.7111889999999998,
"system": 1.8681599600000003,
"min": 1.4690991863,
"max": 1.6403619383,
"times": [
1.5306261283,
1.5009408523,
1.4936591683,
1.4795500563,
1.6403619383,
1.4903087983,
1.4943864053,
1.5266460582999999,
1.4690991863,
1.5204139703
]
}
]
} |
|
| Branch | pr/12047 |
| Testbed | pacquet |
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,342.04 ms(-0.45%)Baseline: 2,352.69 ms | 2,823.23 ms (82.96%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,487.13 ms(+1.25%)Baseline: 1,468.80 ms | 1,762.56 ms (84.37%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,092.78 ms(-0.31%)Baseline: 2,099.30 ms | 2,519.16 ms (83.07%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 648.41 ms(-5.09%)Baseline: 683.20 ms | 819.84 ms (79.09%) |
…scoping Port pnpm's multi-file `.npmrc` layering and the credential-isolation security boundary it provides. `Config::current` now reads the user-level file (npmrcAuthFile / userconfig / ~/.npmrc), the global `auth.ini`, and the project `.npmrc` together and merges them (`user < auth.ini < workspace`) instead of reading just the first one found. Each source is rescoped before the merge (`NpmrcAuth::rescope_unscoped`, ported from pnpm's `rescopeUnscopedCreds`): a file's *unscoped* `_authToken` / `_auth` / `username` / `_password` / `cert` / `key` are pinned to that file's own `registry=` (or the npmjs default when it declares none), nerf-darted into a per-URI key. Once pinned, a credential can never be pulled to a different registry that a higher-priority file — or a later `pnpm-workspace.yaml` registry override — sets. A deprecation warning is queued for each rescoped key. `npmrcAuthFile` is now also sourced from the global `config.yaml` (between `PNPM_CONFIG_USERCONFIG` and `npm_config_userconfig`), completing pnpm's resolution order. Behaviour changes for parity, both covered by ported tests: an unscoped top-level `cert`/`key` becomes per-registry client identity (pinned to its file's registry) rather than a global identity sent to every host; and a user-file credential no longer leaks to a workspace registry override. Ports the credential-scoping suite from `config/reader/test/index.ts`. Refs #12042
…ir safely Address review feedback on #12042: - `ThrottledClient::for_installs` now returns `ForInstallsError::ZeroNetworkConcurrency` when `network_concurrency` is 0 instead of building a zero-permit semaphore that would hang every fetch. Matches pnpm, which rejects the value (p-queue requires a concurrency >= 1). - When `npmrcAuthFile` is a bare filename with no parent, resolve its relative `cafile`/`certfile` entries against the process cwd (empty base path) rather than treating the file itself as the base directory.
Satisfies dylint's `perfectionist::macro-trailing-comma`, which the pre-push hook enforces but `just ready` / clippy don't surface.
Satisfies the Spell Check (crate-ci/typos) CI job.
The pacquet release workflow patched the clap `version` attribute in cli_args.rs, expecting a string literal. Since #12047 that attribute references the `pacquet_config::PACQUET_VERSION` constant, so the perl substitution matched nothing and the verifying grep failed, aborting the whole release. Patch the `PACQUET_VERSION` constant in defaults.rs instead. That single constant feeds both `pacquet --version` and the default User-Agent, so both report the published version. Also tag the default User-Agent as `pnpm/pacquet-<version>` so registries can tell pacquet's traffic apart from the TypeScript pnpm CLI, while keeping the `pnpm` token for UA-keyed allow / rate-limit rules.
* feat(config): make network settings configurable in pacquet Port pnpm's `networkConcurrency`, `fetchTimeout`, `userAgent`, and `npmrcAuthFile` to pacquet, replacing the hardcoded network-client constants. Each is configurable via `pnpm-workspace.yaml`, the `PNPM_CONFIG_*` env overlay, and a dedicated CLI flag, matching pnpm's config surface. - `pacquet-network`: add `NetworkSettings` (concurrency, timeout, UA) threaded into `ThrottledClient::for_installs`; `fetch_timeout` drives both the response and connect deadlines. `DEFAULT_FETCH_TIMEOUT_MS` (60s) and `DEFAULT_USER_AGENT` are the default sources. - `pacquet-config`: add the four `Config` fields with cascade wiring; resolve `npmrcAuthFile` (and the `--userconfig` alias / env vars) to redirect the user-level `.npmrc` read in `Config::current`. - Relocate the Rust->Node `host_platform`/`host_arch` mappers into `pacquet-detect-libc` (re-exported from `graph-hasher`) and reuse them to build pnpm's `pnpm/<version> npm/? node/? <platform> <arch>` UA. Two intentional behaviour changes for parity: the default request timeout moves from 300s to pnpm's 60s, and the default User-Agent moves from the literal `pnpm` to pnpm's full UA format. Refs #12042 * fix(config): correct npmrcAuthFile env resolution; port pnpm precedence tests The user-level `.npmrc` path resolution applied the empty-string filter once after the whole `or_else` chain, so an exported-but-empty `PNPM_CONFIG_NPMRC_AUTH_FILE` short-circuited resolution instead of falling through. Mirror pnpm's `readEnvVar` / `readNpmEnvVar` exactly: filter `value !== ''` per variable, accept both `pnpm_config_*` / `PNPM_CONFIG_*` cases, and add the `npm_config_userconfig` / `NPM_CONFIG_USERCONFIG` compatibility fallback. Port the translatable pnpm precedence tests from `config/reader/test/index.ts` (resolution from the PNPM_CONFIG_* family, lowercase variant, empty-falls-through, npmrc_auth_file outranks userconfig, npm_config_userconfig compat fallback + pnpm-wins). pnpm's credential-scoping tests that read the workspace .npmrc and the userconfig simultaneously and re-scope per file don't translate yet: pacquet uses a single-file (project-or-user) model, not pnpm's layered merge. Global config.yaml sourcing of npmrcAuthFile remains deferred. Refs #12042 * feat(config): merge multiple .npmrc sources with per-file credential scoping Port pnpm's multi-file `.npmrc` layering and the credential-isolation security boundary it provides. `Config::current` now reads the user-level file (npmrcAuthFile / userconfig / ~/.npmrc), the global `auth.ini`, and the project `.npmrc` together and merges them (`user < auth.ini < workspace`) instead of reading just the first one found. Each source is rescoped before the merge (`NpmrcAuth::rescope_unscoped`, ported from pnpm's `rescopeUnscopedCreds`): a file's *unscoped* `_authToken` / `_auth` / `username` / `_password` / `cert` / `key` are pinned to that file's own `registry=` (or the npmjs default when it declares none), nerf-darted into a per-URI key. Once pinned, a credential can never be pulled to a different registry that a higher-priority file — or a later `pnpm-workspace.yaml` registry override — sets. A deprecation warning is queued for each rescoped key. `npmrcAuthFile` is now also sourced from the global `config.yaml` (between `PNPM_CONFIG_USERCONFIG` and `npm_config_userconfig`), completing pnpm's resolution order. Behaviour changes for parity, both covered by ported tests: an unscoped top-level `cert`/`key` becomes per-registry client identity (pinned to its file's registry) rather than a global identity sent to every host; and a user-file credential no longer leaks to a workspace registry override. Ports the credential-scoping suite from `config/reader/test/index.ts`. Refs #12042 * fix(network): reject networkConcurrency=0; resolve user .npmrc base dir safely Address review feedback on #12042: - `ThrottledClient::for_installs` now returns `ForInstallsError::ZeroNetworkConcurrency` when `network_concurrency` is 0 instead of building a zero-permit semaphore that would hang every fetch. Matches pnpm, which rejects the value (p-queue requires a concurrency >= 1). - When `npmrcAuthFile` is a bare filename with no parent, resolve its relative `cafile`/`certfile` entries against the process cwd (empty base path) rather than treating the file itself as the base directory. * style(config): add trailing comma to multi-line format! (dylint) Satisfies dylint's `perfectionist::macro-trailing-comma`, which the pre-push hook enforces but `just ready` / clippy don't surface. * style(config): fix "Unparseable" -> "Unparsable" typo in comment Satisfies the Spell Check (crate-ci/typos) CI job.
The pacquet release workflow patched the clap `version` attribute in cli_args.rs, expecting a string literal. Since #12047 that attribute references the `pacquet_config::PACQUET_VERSION` constant, so the perl substitution matched nothing and the verifying grep failed, aborting the whole release. Patch the `PACQUET_VERSION` constant in defaults.rs instead. That single constant feeds both `pacquet --version` and the default User-Agent, so both report the published version. Also tag the default User-Agent as `pnpm/pacquet-<version>` so registries can tell pacquet's traffic apart from the TypeScript pnpm CLI, while keeping the `pnpm` token for UA-keyed allow / rate-limit rules.
What
Ports the Network settings group from #12042 to pacquet — making the previously hardcoded network-client constants user-configurable, matching pnpm's config surface.
PNPM_CONFIG_*networkConcurrencymin(64, max(cores*3, 16))NETWORK_CONCURRENCY--network-concurrencyfetchTimeoutFETCH_TIMEOUT--fetch-timeoutuserAgentpnpm/<version> npm/? node/? <platform> <arch>USER_AGENT--user-agentnpmrcAuthFile~/.npmrcNPMRC_AUTH_FILE/USERCONFIG/npm_config_userconfig--npmrc-auth-file(alias--userconfig)How
pacquet-network: newNetworkSettingsstruct (concurrency, timeout, UA) threaded intoThrottledClient::for_installs.fetch_timeoutdrives both the response and connect deadlines (mirroring pnpm'sAbortSignal.timeout+connectTimeout = fetchTimeout + 1).DEFAULT_FETCH_TIMEOUT_MS(60s) andDEFAULT_USER_AGENTare the shared default sources; the test-only constructors (new_for_installs/from_client) keep usingNetworkSettings::default().pacquet-config: four newConfigfields wired throughdefaults.rs,WorkspaceSettings/apply!, and thePNPM_CONFIG_*env overlay.npmrcAuthFile(+ the--userconfigalias and env fallbacks) is resolved inConfig::currentto redirect the user-level.npmrcread. SinglePACQUET_VERSIONconst now feeds both clap's--versionand the UA builder.host_platform/host_archmappers into the low-levelpacquet-detect-libccrate (re-exported fromgraph-hasherso its public API is unchanged) and reuse them to build pnpm's UA string.pacquet-cli: dedicated flags + client construction from config instate.rs.Intentional behaviour changes (for parity)
User-Agent: literalpnpm→ pnpm's full UA format. Thepnpm/token is preserved so UA-keyed WAF/rate-limit allow-lists still pass.Out of scope (still tracked on #12042)
maxSockets,fetchingConcurrency— reqwest has no hard per-host connection cap (the semaphore already bounds concurrency), andfetchingConcurrencyis declared in pnpm'sConfigtype but never consumed.fetchWarnTimeoutMs,fetchMinSpeedKiBps— warn-only; pacquet emits no such warnings yet (needs new warning-emission infrastructure first).Testing
New unit tests cover the defaults (
fetch_timeout= 60 000,network_concurrencyformula, UA format), the yaml +PNPM_CONFIG_*overlays, thenpmrcAuthFileoverride (auth reachesauth_headers), the relocated mappers, andfor_installshonoring customNetworkSettings(incl. unencodable-UA fallback). Fullcargo nextest(2476 passed),clippy --deny warnings,cargo fmt --check, andcargo doc -D warningsall clean.Pacquet-only change, so no changeset.
Refs #12042.
Written by an agent (Claude Code, claude-opus-4-8).
Summary by CodeRabbit
New Features
Behavior Change / Security
Tests