feat(config,network,tarball,registry): per-registry TLS overrides#502
Conversation
Adds `PerRegistryTls` + `RegistryTls` types in `pacquet-network` next to the existing `TlsConfig` / `ProxyConfig`, plus the lookup machinery needed to dispatch HTTP requests to a per-registry reqwest `Client` that carries a different TLS configuration than the default. The shape mirrors pnpm v11 (SHA 94240bc046): - `PerRegistryTls` is a `HashMap<NerfDartUri, RegistryTls>` with the same key shape pnpm writes in `configByUri[<uri>].tls` ([`getNetworkConfigs.ts:34-40`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/getNetworkConfigs.ts#L34-L40)). - `RegistryTls` carries `(ca: Option<String>, cert: Option<String>, key: Option<String>)` — three independently-overridable fields, not a single replace-all blob. Pnpm uses object spread (`{ ...opts, ...sslConfig }` at [`dispatcher.ts:143,264`](https://github.com/pnpm/pnpm/blob/94240bc046/network/fetch/src/dispatcher.ts#L143)) to layer per-registry over top-level field-by-field; pacquet's `merge_tls` helper does the same. - `PerRegistryTls::pick_for_url` ports pnpm's [`pickSettingByUrl`](https://github.com/pnpm/pnpm/blob/94240bc046/network/fetch/src/dispatcher.ts#L338-L375) exactly: exact URL > nerf-darted > host-without-port > progressively shorter nerf-dart path prefixes > recursive retry without port. The 5-step fallback is the contract for how a multi-registry .npmrc resolves to a TLS config at request time. `ThrottledClient::for_installs` gains a third parameter and pre-builds one reqwest `Client` per non-empty per-registry override (each carrying its own merged TLS state on top of the top-level proxy + TLS config). The new `acquire_for_url(url: &str)` routes per-request via `pick_for_url`, falling back to the default client when no override matches. The semaphore is shared across all clients so the global concurrency cap stays intact regardless of which registry a request targets. `pick_for_url` takes `&str` rather than `&Url` so the existing `format!("{registry}{name}")` call sites in `tarball` / `registry` don't need to round-trip through `Url::parse` just for routing. Tests cover: empty-default short-circuit, exact-match precedence, nerf-dart fallback, shorter-prefix walk, port-strip retry, miss on host mismatch, miss on path-prefix mismatch, `strip_port` shapes (plain, with userinfo, IPv6 literal), `for_installs` builds with per-registry overrides, invalid per-registry CA surfaces as `InvalidCa { index: 0 }`, and end-to-end `acquire_for_url` returns distinct clients for scoped vs default URLs. The cli temporarily passes `&PerRegistryTls::default()` so the workspace still compiles in isolation; the next commit wires `Config.tls_by_uri` through so the override actually flows from `.npmrc`.
Extends `NpmrcAuth` to capture the six per-registry TLS suffixes pnpm recognizes in `.npmrc` (`//host[:port]/path/:ca`, `:cafile`, `:cert`, `:certfile`, `:key`, `:keyfile`) into a `tls_by_uri: HashMap<String, RegistryTls>`, then applies the resolved map onto a new `Config.tls_by_uri: PerRegistryTls` field. The cli's `State::init` now passes `&config.tls_by_uri` to `ThrottledClient::for_installs` (replacing the `&PerRegistryTls::default()` placeholder from the preceding commit), so per-registry overrides actually flow from `.npmrc` to the install client. Parity policy follows the upstream brief (pnpm v11, SHA 94240bc046): - Suffix matching mirrors `SSL_SUFFIX_RE = /:(?<id>cert|key|ca)(?<kind>file)?$/` from [`getNetworkConfigs.ts:94`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/getNetworkConfigs.ts#L94). Order matters: `:certfile` matches before `:cert` so the `*file` variants don't get parsed as the inline form with a trailing `file` artifact in the URI prefix. - The `*file` variants are read from disk at parse time (matching pnpm's [`fs.readFileSync` at `getNetworkConfigs.ts:38`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/getNetworkConfigs.ts#L38)). An unreadable `*file` is silently dropped — the entry is simply not written, and `PerRegistryTls::from_map` later filters all-`None` entries so the lookup never returns a hit that would suppress the top-level fallback. - Inline values go through `\\n` → `\n` expansion (matching pnpm's `value.replace(/\\n/g, '\n')` at the same call site). Top-level `ca=` does **not** get this treatment; the divergence is intentional and matches upstream. - `:cert` and `:certfile` write to the same `tls.cert` slot — last-write-wins inside one `.npmrc`. Same for `:key`/`:keyfile` and `:ca`/`:cafile`. - The split is deliberately lax about the `//` prefix: pnpm's regex doesn't enforce it either, so `foo:cert=…` keys land in the map with `uri_prefix = "foo"`. They never match a real nerf-darted URL so the entry is dropped at lookup time, but storing it keeps byte-for-byte parsing parity. Tests cover each suffix arm, `\\n` expansion on inline-only, `*file` reading and silent-drop, the last-wins slot semantics between `:cert` and `:certfile`, and that top-level `ca=` doesn't collide with the per-registry parser (test: `scoped_tls_keys_dont_collide_with_top_level`). Touches `crates/package-manager/src/install_package_from_registry/tests.rs` for the new `Config.tls_by_uri` field (`Default::default()`).
Replaces three production `ThrottledClient::acquire()` call sites with
`acquire_for_url(url)` so per-registry TLS overrides (added in the two
preceding commits) actually route requests through the per-registry
reqwest `Client` when one matches.
- `crates/registry/src/package.rs` (metadata fetch — has `url` from
`format!("{registry}{name}")`).
- `crates/registry/src/package_version.rs` (tag-pinned metadata
fetch — same `url` shape).
- `crates/tarball/src/lib.rs` (two sites: regular tarball download
and the runtime-artifact fetch path). Both have `package_url:
&str` in scope, threaded down from the install graph; tarball
hosts that differ from the metadata host still pick up the right
per-registry client because the 5-step `pickSettingByUrl` lookup
matches on the tarball URL just like it does on the metadata URL.
When no per-registry overrides exist (the common case), the routing
table is empty and `acquire_for_url` short-circuits to the default
client — same observable behavior as the old `acquire()`. The
semaphore is unchanged so the global concurrency cap stays intact.
Test sites that don't care about per-URL routing
(`crates/network/src/tests.rs`, `crates/tarball/src/tests.rs`) keep
using `acquire()` — the API stays available as the implicit default
for callers without a URL.
📝 WalkthroughWalkthroughPer-registry TLS overrides enable registry-scoped CA certificate, client cert, and client key configuration via ChangesPer-Registry TLS Overrides
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
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)
Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #502 +/- ##
==========================================
+ Coverage 88.40% 88.50% +0.09%
==========================================
Files 121 122 +1
Lines 12966 13102 +136
==========================================
+ Hits 11463 11596 +133
- Misses 1503 1506 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/config/src/npmrc_auth.rs (1)
33-35:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove outdated “per-registry TLS unparsed” note.
This comment is no longer true after the new scoped TLS parsing landed, so it should be updated to prevent incorrect maintenance assumptions.
🤖 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 `@crates/config/src/npmrc_auth.rs` around lines 33 - 35, Update the doc-comment in the npmrc parsing module to remove the outdated sentence claiming "per-registry TLS ... remain unparsed" — locate the comment block that currently reads "Other `.npmrc` knobs (scoped `@scope:registry`, per-registry TLS like `//host:cafile=`, etc.) remain unparsed for now." (in crates::config::npmrc_auth module) and either delete that clause or replace it with a short accurate note stating that scoped per-registry TLS settings are now parsed; keep the surrounding context about remaining unparsed knobs if any other knobs still apply.crates/config/src/lib.rs (1)
633-639:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate stale
.npmrcbehavior docs inConfig::current.This section still says per-registry TLS overrides are ignored, but this PR now parses and applies them. Please update/remove this note to avoid future confusion.
🤖 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 `@crates/config/src/lib.rs` around lines 633 - 639, The comment in the docs for Config::current incorrectly says per-registry TLS overrides in `.npmrc` are ignored; update the documentation inside the Config::current docstring to remove or revise that sentence so it reflects the new behavior (per-registry TLS overrides like `//host:cafile=`, `//host:ca=`, `//host:cert=`, `//host:key=` are now parsed and applied). Keep the rest of the note about project-structural settings coming from `pnpm-workspace.yaml` or CLI flags, but delete or reword the clause claiming TLS overrides are silently ignored to prevent future confusion.
🤖 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.
Outside diff comments:
In `@crates/config/src/lib.rs`:
- Around line 633-639: The comment in the docs for Config::current incorrectly
says per-registry TLS overrides in `.npmrc` are ignored; update the
documentation inside the Config::current docstring to remove or revise that
sentence so it reflects the new behavior (per-registry TLS overrides like
`//host:cafile=`, `//host:ca=`, `//host:cert=`, `//host:key=` are now parsed and
applied). Keep the rest of the note about project-structural settings coming
from `pnpm-workspace.yaml` or CLI flags, but delete or reword the clause
claiming TLS overrides are silently ignored to prevent future confusion.
In `@crates/config/src/npmrc_auth.rs`:
- Around line 33-35: Update the doc-comment in the npmrc parsing module to
remove the outdated sentence claiming "per-registry TLS ... remain unparsed" —
locate the comment block that currently reads "Other `.npmrc` knobs (scoped
`@scope:registry`, per-registry TLS like `//host:cafile=`, etc.) remain unparsed
for now." (in crates::config::npmrc_auth module) and either delete that clause
or replace it with a short accurate note stating that scoped per-registry TLS
settings are now parsed; keep the surrounding context about remaining unparsed
knobs if any other knobs still apply.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ee98d90d-5e11-4521-a878-336c40bee3b4
📒 Files selected for processing (12)
crates/cli/src/state.rscrates/config/src/lib.rscrates/config/src/npmrc_auth.rscrates/config/src/npmrc_auth/tests.rscrates/network/src/lib.rscrates/network/src/tests.rscrates/network/src/tls.rscrates/network/src/tls/tests.rscrates/package-manager/src/install_package_from_registry/tests.rscrates/registry/src/package.rscrates/registry/src/package_version.rscrates/tarball/src/lib.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). (6)
- GitHub Check: copilot-pull-request-reviewer
- 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: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/*.rs: Preserve existing method chains andpipe-traitchains; do not break them into intermediateletbindings unless there is a concrete justification such as a compilation failure, borrow checker rejection, meaningful performance improvement, or other technical necessity. Refactoring for style alone is not sufficient justification.
Choose owned vs. borrowed parameters to minimize copies; prefer borrowed types (&Pathover&PathBuf,&strover&String) when it does not force extra copies.
PreferArc::clone(&x)andRc::clone(&x)overx.clone()for reference-counted types to make the cost visible at the call site.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;for any glob whose target is a module you control. External-crate preludes (e.g.,use rayon::prelude::*;) and root-of-module re-exports (e.g.,pub use submodule::*;inlib.rs) are exceptions.
Follow Rust API Guidelines for naming, as documented in https://rust-lang.github.io/api-guidelines/naming.html.
Declare a newtype wrapper for any branded string type being ported from TypeScript pnpm. Do not collapse the brand into a plainStringor&str; give the type its own struct so misuse is a type error.
When porting branded string types where upstream TypeScript always validates before construction, validate in the Rust port too. Construct the wrapper only viaTryFrom<String>and/orFromStr; do not provide an infallible public constructor that takes an arbitrary string.
For branded string types where upstream TypeScript never validates (used purely for type-safety to prevent confusion between string slots), expose an infallibleFrom<String>andFrom<&str>constructor in the Rust wrapper.
When upstream TypeScript occasionally constructs a branded type without validation (via bareasassertion), add afrom_str_unchecked(or similarly named) constructor on the Rust side. Keep the validating constructor as well; `from_str_u...
Files:
crates/registry/src/package_version.rscrates/config/src/lib.rscrates/package-manager/src/install_package_from_registry/tests.rscrates/registry/src/package.rscrates/cli/src/state.rscrates/config/src/npmrc_auth/tests.rscrates/tarball/src/lib.rscrates/network/src/tls/tests.rscrates/config/src/npmrc_auth.rscrates/network/src/lib.rscrates/network/src/tls.rscrates/network/src/tests.rs
🧠 Learnings (5)
📚 Learning: 2026-05-07T23:19:08.272Z
Learnt from: KSXGitHub
Repo: pnpm/pacquet PR: 401
File: tasks/integrated-benchmark/src/work_env.rs:343-344
Timestamp: 2026-05-07T23:19:08.272Z
Learning: When reviewing Rust code in pnpm/pacquet for deprecated API usage, do not automatically treat `serde_saphyr::to_string` as deprecated. In `serde-saphyr` v0.0.25, `serde_saphyr::to_string` has no `#[deprecated]` attribute (the `#[deprecated]` later in `serde-saphyr-0.0.25/src/lib.rs` applies to a different function). Only flag `serde_saphyr::to_string` as deprecated if the resolved dependency version’s source shows `#[deprecated]` on that specific function.
Applied to files:
crates/registry/src/package_version.rscrates/config/src/lib.rscrates/package-manager/src/install_package_from_registry/tests.rscrates/registry/src/package.rscrates/cli/src/state.rscrates/config/src/npmrc_auth/tests.rscrates/tarball/src/lib.rscrates/network/src/tls/tests.rscrates/config/src/npmrc_auth.rscrates/network/src/lib.rscrates/network/src/tls.rscrates/network/src/tests.rs
📚 Learning: 2026-05-01T10:01:33.766Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 349
File: crates/reporter/src/tests.rs:121-121
Timestamp: 2026-05-01T10:01:33.766Z
Learning: In Rust test code, follow the repo’s CODE_STYLE_GUIDE test-logging rule: add logging (e.g., `eprintln!`/`eprintln!(...)`) so that useful diagnostic values are printed when a test fails, unless the assertion is `assert_eq!` (where the differing values are already included). Concretely, if you use assertions like `assert!`, `assert_ne!`, etc., ensure the test logs the relevant actual/expected values (or context) before/around the assertion so failures can be diagnosed without rerunning.
Applied to files:
crates/package-manager/src/install_package_from_registry/tests.rscrates/config/src/npmrc_auth/tests.rscrates/network/src/tls/tests.rscrates/network/src/tests.rs
📚 Learning: 2026-05-13T19:22:48.951Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 478
File: crates/package-manager/src/hoisted_dep_graph.rs:51-55
Timestamp: 2026-05-13T19:22:48.951Z
Learning: When reviewing this Rust codebase, avoid introducing/using a newtype like `PkgIdWithPatchHash` in only one module (e.g., `hoisted_dep_graph.rs`) if other related pacquet modules still represent the upstream pnpm “pkg id with patch hash” as a plain `String` (e.g., `virtual_store_layout`). For type consistency, either keep `pkg_id_with_patch_hash` as `String` here, or require a workspace-wide sweep that defines/extracts the `PkgIdWithPatchHash` newtype once and updates all call sites together (otherwise defer the refactor to a coordinated follow-up PR).
Applied to files:
crates/package-manager/src/install_package_from_registry/tests.rs
📚 Learning: 2026-05-13T20:09:22.171Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 486
File: crates/package-manager/src/hoisted_dep_graph.rs:472-476
Timestamp: 2026-05-13T20:09:22.171Z
Learning: In pnpm/pacquet, when generating serialized `hoisted_locations` strings (used for `.modules.yaml`), normalize path separators to forward slashes (e.g., replace `\\` with `/`). This preserves cross-platform portability: the output must not use OS-native separators because upstream pnpm’s `path.relative` can return platform-specific separators, which would break consistency with pnpm/pacquet’s other serialized formats on Windows.
Applied to files:
crates/package-manager/src/install_package_from_registry/tests.rs
📚 Learning: 2026-05-01T10:01:33.766Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 349
File: crates/reporter/src/tests.rs:121-121
Timestamp: 2026-05-01T10:01:33.766Z
Learning: In this repo’s Rust tests (run via `cargo nextest`), stdout/stderr from passing tests is captured and is only shown when a test fails. As a result, `eprintln!` (and similar stderr logging) in these tests should generally not be flagged as CI “noise” on the happy path; CI output should only appear for failing tests.
Applied to files:
crates/network/src/tests.rs
🔇 Additional comments (25)
crates/network/src/tls.rs (6)
20-131: LGTM!
160-166: LGTM!
172-176: LGTM!
204-244: LGTM!
246-251: LGTM!
263-290: LGTM!crates/network/src/tls/tests.rs (2)
7-21: LGTM!
62-166: LGTM!crates/network/src/lib.rs (6)
7-16: LGTM!
49-63: LGTM!
164-246: LGTM!
253-261: LGTM!
338-349: LGTM!
282-291: LGTM!crates/network/src/tests.rs (2)
157-414: LGTM!
418-515: LGTM!crates/package-manager/src/install_package_from_registry/tests.rs (1)
17-61: LGTM!crates/cli/src/state.rs (1)
65-70: LGTM!crates/registry/src/package.rs (1)
30-61: LGTM!crates/tarball/src/lib.rs (2)
1361-1396: LGTM!
1985-2011: LGTM!crates/config/src/lib.rs (1)
289-298: LGTM!crates/config/src/npmrc_auth/tests.rs (1)
676-789: LGTM!crates/registry/src/package_version.rs (1)
40-43: LGTM!crates/config/src/npmrc_auth.rs (1)
236-254: LGTM!Also applies to: 294-299, 561-610
Micro-Benchmark ResultsLinux |
Integrated-Benchmark Report (Linux)Scenario: Frozen Lockfile
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.72372125168,
"stddev": 0.1934450775057152,
"median": 2.68921932728,
"user": 2.6561994199999996,
"system": 3.7251190999999997,
"min": 2.5211045422800002,
"max": 3.22764497028,
"times": [
2.6892740182800003,
2.81913868028,
2.68916463628,
3.22764497028,
2.62737898628,
2.5211045422800002,
2.70604490428,
2.60328779628,
2.64656594328,
2.70760803928
]
},
{
"command": "pacquet@main",
"mean": 2.6392875518800003,
"stddev": 0.08123439502170784,
"median": 2.6261268612800004,
"user": 2.5917730199999998,
"system": 3.7200777,
"min": 2.56082056628,
"max": 2.80207482828,
"times": [
2.80207482828,
2.61569945528,
2.75205276228,
2.64045350828,
2.66251749128,
2.56082056628,
2.56982950428,
2.63655426728,
2.56672283828,
2.58615029728
]
},
{
"command": "pnpm",
"mean": 6.354169235079999,
"stddev": 0.058258466583422326,
"median": 6.3449898962799995,
"user": 9.324323119999999,
"system": 4.5807832,
"min": 6.2745189542799995,
"max": 6.50103526928,
"times": [
6.2745189542799995,
6.35146134928,
6.361154775279999,
6.33030146428,
6.346254119279999,
6.32344997628,
6.33379567228,
6.37599509728,
6.50103526928,
6.34372567328
]
}
]
}Scenario: Frozen Lockfile (Hot Cache)
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.74631892158,
"stddev": 0.027291944734382644,
"median": 0.74556090668,
"user": 0.37919294,
"system": 1.6122701200000003,
"min": 0.7039093536800001,
"max": 0.7960529146800001,
"times": [
0.7960529146800001,
0.72209898068,
0.73582453268,
0.7039093536800001,
0.7469717126800001,
0.74415010068,
0.72323423468,
0.75726630468,
0.75600722668,
0.77767385468
]
},
{
"command": "pacquet@main",
"mean": 0.8080482158800001,
"stddev": 0.03200593997589197,
"median": 0.80251881368,
"user": 0.40604834,
"system": 1.6232694199999997,
"min": 0.76603662168,
"max": 0.85760172168,
"times": [
0.78830029368,
0.8092201076800001,
0.76603662168,
0.80703612368,
0.85760172168,
0.8453065736800001,
0.79800150368,
0.76767876968,
0.79646257268,
0.84483787068
]
},
{
"command": "pnpm",
"mean": 2.6605204906799997,
"stddev": 0.0936227975421545,
"median": 2.62142731868,
"user": 3.255981539999999,
"system": 2.2513308200000006,
"min": 2.54981912468,
"max": 2.8412781686799997,
"times": [
2.8412781686799997,
2.76324892768,
2.65104284768,
2.62780808068,
2.6031888516799997,
2.54981912468,
2.6113859326799997,
2.61504655668,
2.5867863066799996,
2.75560010968
]
}
]
} |
Closes #497.
Summary
Adds per-registry TLS overrides keyed by nerf-darted
.npmrcURI, the natural follow-up to #490's top-level TLS keys. Corporate environments running a private Verdaccio (or any registry with its own self-signed cert) can now pin scoped:cafile=…/:cert=…/:key=…per host without disabling strict-ssl globally.Three commits, layered:
feat(network)(eff1248): addsRegistryTls+PerRegistryTlstypes inpacquet-networkplus the lookup machinery —pick_for_urlports pnpm's 5-steppickSettingByUrlexactly (exact > nerf-dart > no-port > shorter prefix > recursive no-port retry).ThrottledClient::for_installsgains a third&PerRegistryTlsparameter and pre-builds one reqwestClientper non-empty override. Newacquire_for_url(url: &str)routes per-request;acquire()keeps the default-client behavior for callers without a URL.feat(config)(4e69868):NpmrcAuthparses the six per-registry TLS suffixes (:ca,:cafile,:cert,:certfile,:key,:keyfile) matching pnpm'sSSL_SUFFIX_REand applies ontoConfig.tls_by_uri.*filevariants read from disk at parse time (silent on error); inline values get\\n→\nexpansion.:certand:certfileshare the sametls.certslot — last-write-wins inside one.npmrc.refactor(tarball,registry)(5f9cae9): three production call sites (registry metadata + version-tag fetches, plus two tarball download paths) move fromacquire()toacquire_for_url(url)so the per-registry routing actually fires.Parity policy
Bug-for-bug with pnpm v11 (SHA 94240bc046):
ca/cert/keyoverrides its top-level counterpart independently (mirroring upstream's{ ...opts, ...sslConfig }spread atdispatcher.ts:143,264).strict_sslandlocal_addressstay top-level-only — pnpm's regex doesn't recognize scoped versions.caasOption<String>, notVec<String>: per-registrycais a single string (possibly with concatenated-----END CERTIFICATE-----delimiters) —reqwest::Certificate::from_pemaccepts both shapes.\\nexpansion only on per-registry: pnpm appliesvalue.replace(/\\n/g, '\n')to scoped values but not to top-levelca=. The divergence is intentional and matches upstream.foo:cert=…(no//prefix) is accepted into the map withuri_prefix = "foo". It never matches a real nerf-darted URL so the entry is dropped at lookup time, but storing it keeps byte-for-byte parsing parity withtryParseSslKey.Reviewer flags
reqwest::Clientand therefore its own connection pool. With N per-registry overrides the worker holds N+1 pools instead of one. The semaphore still bounds concurrent in-flight requests globally, but socket churn between registries with different TLS configs is now per-client. In practice most users have ≤2 overrides; if this becomes an issue we'd need to switch to rustls + custom certificate verifier (tracked under feat(network): support PKCS#1 client keys (currently PKCS#8 only) #499).acquire_for_urltakes&strrather than&Urlso the existingformat!("{registry}{name}")call sites don't need to round-trip throughUrl::parse. The lookup itself works on the raw string form vianerf_dart.Test plan
cargo nextest run -p pacquet-network— 64 tests pass (added 11 per-registry routing tests intls/tests.rs+ 4 end-to-end routing tests intests.rs)cargo nextest run -p pacquet-config— 146 tests pass (added 8 per-registry parse tests)cargo nextest run --workspace— 1155 tests passjust readycleantaplo format --checkcleanRUSTDOCFLAGS=-D warnings cargo doc --no-deps --workspace --document-private-itemscleanjust dylintcleanOut of scope
References
Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
.npmrcfiles, allowing users to specify custom CA certificates, client certificates, and keys for individual registries.