refactor(registry): adopt verdaccio-shaped YAML config#11970
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplace single-upstream registry config with a Verdaccio-shaped YAML system: bundled default YAML, IndexMap-backed uplinks, ordered per-package routing rules, pattern matching and resolve_uplink, CLI --config loading, server wiring for multiple upstreams, and updated tests/launcher to use uplink URLs. ChangesVerdaccio-shaped YAML-driven registry configuration
Sequence DiagramsequenceDiagram
participant Client
participant RegistryServer
participant npmjs as npmjsUplink
Client->>RegistryServer: request package metadata / tarball (package name)
RegistryServer->>RegistryServer: Config::resolve_uplink(package)
alt proxy rule found
RegistryServer->>npmjs: proxy request to uplink URL
npmjs-->>RegistryServer: proxied response
RegistryServer-->>Client: return proxied response
else no proxy rule
RegistryServer-->>Client: return local response / 404
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 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 QodoAdopt verdaccio-shaped YAML config with named uplinks and package routing
WalkthroughsDescription• Reshape Config to match verdaccio's YAML schema (storage, uplinks, packages) • Replace single upstream field with named uplinks and per-package proxy rules • Add YAML parsing via serde-saphyr with relative path resolution • Bundle verdaccio-shaped config.yaml and expose via DEFAULT_CONFIG_YAML • Update CLI: add -c/--config flag, remove --upstream and --static flags • Pre-build upstreams at router construction, resolve per-request via pattern matching Diagramflowchart LR
A["Config YAML<br/>storage, uplinks, packages"] -->|parse| B["ConfigFile struct"]
B -->|resolve paths| C["Config struct<br/>uplinks IndexMap<br/>packages IndexMap"]
C -->|build routers| D["AppState<br/>upstreams IndexMap"]
E["Package request"] -->|resolve_uplink| F["Pattern matching<br/>first-match wins"]
F -->|lookup| D
D -->|fetch| G["Upstream client"]
File Changes1. registry/crates/pnpm-registry/src/config.rs
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
registry/crates/pnpm-registry/src/config.rs (1)
240-254: 💤 Low value
@*/*pattern matches malformed scoped names without a slash.The
@*/*pattern is documented as matching "all scoped packages", but the checkname.starts_with('@')would also match malformed inputs like@foo(no slash). Verdaccio's actual behavior requires the full@scope/namestructure.♻️ Proposed fix to tighten the match
if pattern == "@*/*" { - return name.starts_with('@'); + return name.starts_with('@') && name.contains('/'); }🤖 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 `@registry/crates/pnpm-registry/src/config.rs` around lines 240 - 254, The pattern_matches function's handling of the "@*/*" pattern is too permissive because it returns true for any name starting with '@' (e.g., "`@foo`"); update pattern_matches so the "@*/*" branch checks that name has a proper scoped form (starts with '@' and contains a '/' with non-empty scope and package segments) before returning true; modify the "@*/*" check in pattern_matches to validate the presence of a slash and that both scope and package portions are non-empty (use existing name parsing helpers or explicit checks) so only well-formed "`@scope/name`" strings match.
🤖 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 `@registry/crates/pnpm-registry/src/config.rs`:
- Around line 240-254: The pattern_matches function's handling of the "@*/*"
pattern is too permissive because it returns true for any name starting with '@'
(e.g., "`@foo`"); update pattern_matches so the "@*/*" branch checks that name has
a proper scoped form (starts with '@' and contains a '/' with non-empty scope
and package segments) before returning true; modify the "@*/*" check in
pattern_matches to validate the presence of a slash and that both scope and
package portions are non-empty (use existing name parsing helpers or explicit
checks) so only well-formed "`@scope/name`" strings match.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fe3cb2d4-8557-4de7-a483-67a9336e49ae
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
pacquet/tasks/registry-mock/src/pnpm_registry_command.rsregistry/crates/pnpm-registry/Cargo.tomlregistry/crates/pnpm-registry/config.yamlregistry/crates/pnpm-registry/src/config.rsregistry/crates/pnpm-registry/src/lib.rsregistry/crates/pnpm-registry/src/main.rsregistry/crates/pnpm-registry/src/server.rsregistry/crates/pnpm-registry/tests/auth_publish.rsregistry/crates/pnpm-registry/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). (7)
- GitHub Check: Agent
- GitHub Check: Code Coverage
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (4)
registry/crates/**/Cargo.toml
📄 CodeRabbit inference engine (registry/AGENTS.md)
registry/crates/**/Cargo.toml: When using existing pacquet-* crates, declare them in the root [workspace.dependencies] and use { workspace = true } in the crate's Cargo.toml
New registry-only crates must be placed under registry/crates// and named pnpm-registry- in Cargo.toml, with the package name using the pnpm-registry- prefix exclusively
Do not reach for the pacquet- prefix to name new registry-side crates; use pnpm-registry- prefix exclusively for registry-only crates
Files:
registry/crates/pnpm-registry/Cargo.toml
registry/**/Cargo.toml
📄 CodeRabbit inference engine (registry/AGENTS.md)
Add new registry-only crates to [workspace.dependencies] at the root with the pnpm-registry- prefix so other crates can use { workspace = true }
Files:
registry/crates/pnpm-registry/Cargo.toml
registry/crates/**/*.rs
📄 CodeRabbit inference engine (registry/AGENTS.md)
Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, error handling, and test layout
Files:
registry/crates/pnpm-registry/src/lib.rsregistry/crates/pnpm-registry/tests/auth_publish.rsregistry/crates/pnpm-registry/src/main.rsregistry/crates/pnpm-registry/tests/server.rsregistry/crates/pnpm-registry/src/server.rsregistry/crates/pnpm-registry/src/config.rs
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: When porting a function that firespnpm:<channel>events throughglobalLogger,logger.debug(), orstreamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plainStringor&str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only viaTryFrom<String>and/orFromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallibleFrom<String>(andFrom<&str>when convenient).
If upstream occasionally constructs without validation, exposefrom_str_uncheckedas an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization.
Use#[derive(derive_more::From)]and#[derive(derive_more::Into)]for mechanical conversion impls. Fall back to manualimplonly when conversion needs custom logic.
String-literal unions should becomeenums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
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 conventions.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;. Two forms stay allowed: external-crate preludes likeuse rayon::prelude::*;and root-of-module re-...
Files:
pacquet/tasks/registry-mock/src/pnpm_registry_command.rs
🧠 Learnings (1)
📚 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/tasks/registry-mock/src/pnpm_registry_command.rs
🔇 Additional comments (17)
registry/crates/pnpm-registry/src/config.rs (8)
1-9: LGTM!
10-16: LGTM!
18-58: LGTM!
60-77: LGTM!
79-93: LGTM!
95-137: LGTM!
138-218: LGTM!
220-232: LGTM!registry/crates/pnpm-registry/config.yaml (1)
1-73: LGTM!registry/crates/pnpm-registry/Cargo.toml (1)
28-32: LGTM!registry/crates/pnpm-registry/src/lib.rs (1)
22-22: LGTM!registry/crates/pnpm-registry/src/main.rs (2)
1-8: LGTM!Also applies to: 10-38
40-60: LGTM!registry/crates/pnpm-registry/src/server.rs (1)
9-10: LGTM!Also applies to: 47-50, 66-70, 74-74, 450-452, 718-720, 1066-1074, 1088-1094
registry/crates/pnpm-registry/tests/server.rs (1)
17-21: LGTM!registry/crates/pnpm-registry/tests/auth_publish.rs (1)
678-678: LGTM!Also applies to: 722-722
pacquet/tasks/registry-mock/src/pnpm_registry_command.rs (1)
86-89: LGTM!
There was a problem hiding this comment.
Pull request overview
This PR refactors pnpm-registry to load Verdaccio-shaped YAML configuration for storage, uplinks, and package proxy routing, replacing the previous single-upstream CLI-driven configuration.
Changes:
- Adds YAML-backed
Configparsing with ordereduplinksandpackages. - Updates server routing to prebuild and resolve named upstreams per request.
- Adjusts CLI/tests/mock launcher wiring for the new config model.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
registry/crates/pnpm-registry/src/config.rs |
Adds Verdaccio-shaped config structs, YAML loading, default config inclusion, and package uplink resolution. |
registry/crates/pnpm-registry/src/server.rs |
Replaces single upstream with named prebuilt upstreams resolved from package rules. |
registry/crates/pnpm-registry/src/main.rs |
Adds --config, changes storage override handling, and removes old upstream/static CLI flags. |
registry/crates/pnpm-registry/src/lib.rs |
Re-exports new config types and bundled YAML constant. |
registry/crates/pnpm-registry/config.yaml |
Adds bundled default Verdaccio-shaped registry configuration. |
registry/crates/pnpm-registry/Cargo.toml |
Adds indexmap and serde-saphyr dependencies. |
registry/crates/pnpm-registry/tests/server.rs |
Updates test helper to mutate the default npmjs uplink URL. |
registry/crates/pnpm-registry/tests/auth_publish.rs |
Updates upstream-based search tests for the new uplink config shape. |
pacquet/tasks/registry-mock/src/pnpm_registry_command.rs |
Stops passing --upstream and relies on bundled config defaults. |
Cargo.lock |
Records new pnpm-registry dependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Path to a verdaccio-shaped YAML config (storage, uplinks, | ||
| /// packages). When omitted, the bundled default config is used. | ||
| #[arg(short = 'c', long)] | ||
| config: Option<PathBuf>, |
There was a problem hiding this comment.
Fixed in 59f4b41: dropped the --upstream arg from __utils__/jest-config/with-registry/globalSetup.js. The bundled config already declares the npmjs uplink at https://registry.npmjs.org/, which is what that line was supplying. The PNPM_REGISTRY_MOCK_UPLINK env hatch had no other call sites in the repo, so dropping it is safe — callers needing a non-npmjs uplink can use -c <yaml>.
Written by an agent (Claude Code, claude-opus-4-7).
There was a problem hiding this comment.
Reverted in 722b17f after a closer look. CI installs @pnpm/pnpr from npm — the pinned 0.0.0-26052601 build still accepts --upstream (with a default of https://registry.npmjs.org), so passing the flag from globalSetup.js against that binary works fine; clap does not reject it. The Rust source change in this PR doesn't affect the binary CI actually runs.
Dropping --upstream from globalSetup.js now would be cosmetic until a new @pnpm/pnpr is published from this PR's source. Keeping it avoids a flag-rejection break the moment that release ships, so the cleanup will land in the same PR that bumps @pnpm/pnpr. pacquet/tasks/registry-mock still drops its --upstream — that command spawns target/release/pnpm-registry, which is this PR's source, so the drop there is required and stays.
Written by an agent (Claude Code, claude-opus-4-7).
Micro-Benchmark ResultsLinux |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #11970 +/- ##
==========================================
+ Coverage 87.95% 88.04% +0.08%
==========================================
Files 228 228
Lines 27819 28068 +249
==========================================
+ Hits 24469 24713 +244
- Misses 3350 3355 +5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.04641976048,
"stddev": 0.13230352685673397,
"median": 2.00886275598,
"user": 2.7690584599999997,
"system": 3.27427718,
"min": 1.8941216784800001,
"max": 2.27560470648,
"times": [
2.19494284948,
2.18929069148,
1.8941216784800001,
1.96304181848,
2.06060241848,
1.9739672594800002,
1.9039771224800002,
2.27560470648,
2.04375825248,
1.96489080748
]
},
{
"command": "pacquet@main",
"mean": 1.99042432908,
"stddev": 0.05964330241993459,
"median": 1.99351534698,
"user": 2.7995307599999997,
"system": 3.2682464800000006,
"min": 1.8982688334800002,
"max": 2.10873127248,
"times": [
2.01026400148,
1.9055676824800003,
2.01742422248,
1.9734471874800001,
1.8982688334800002,
1.98843499248,
1.98461148548,
2.10873127248,
1.99859570148,
2.01889791148
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6718605823400001,
"stddev": 0.01947773224080718,
"median": 0.6706701212400001,
"user": 0.3693597599999999,
"system": 1.38071646,
"min": 0.6425196082400001,
"max": 0.70438528824,
"times": [
0.70438528824,
0.6736634852400001,
0.6569794402400001,
0.70269734924,
0.6425196082400001,
0.6730745382400001,
0.6594730522400001,
0.67693390224,
0.6682657042400001,
0.66061345524
]
},
{
"command": "pacquet@main",
"mean": 0.71791405304,
"stddev": 0.052152984641406254,
"median": 0.69670688924,
"user": 0.37974206,
"system": 1.34897496,
"min": 0.6840368262400001,
"max": 0.8589381462400001,
"times": [
0.8589381462400001,
0.7290523492400001,
0.68799670824,
0.69588894924,
0.7312810402400001,
0.6903772732400001,
0.69562080624,
0.69752482924,
0.6840368262400001,
0.70842360224
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.3563851950199997,
"stddev": 0.05771201846787757,
"median": 2.35209543212,
"user": 3.86961964,
"system": 3.05091266,
"min": 2.22775194762,
"max": 2.43262212162,
"times": [
2.39409691662,
2.31733042862,
2.40372963562,
2.35630224962,
2.43262212162,
2.40037268862,
2.34133511662,
2.34788861462,
2.34242223062,
2.22775194762
]
},
{
"command": "pacquet@main",
"mean": 2.3077030498199997,
"stddev": 0.04384544801158406,
"median": 2.29604525612,
"user": 3.94387774,
"system": 3.0461457599999995,
"min": 2.24287622062,
"max": 2.38671265262,
"times": [
2.30148601362,
2.2871954686200002,
2.27231628662,
2.33800197262,
2.24287622062,
2.32836505662,
2.27344993062,
2.38671265262,
2.35602239762,
2.29060449862
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.5054859187400003,
"stddev": 0.02272088716276475,
"median": 1.50473686694,
"user": 1.7590028,
"system": 1.91499362,
"min": 1.47403630794,
"max": 1.53311207094,
"times": [
1.51870721594,
1.52026369394,
1.5320247199400001,
1.53311207094,
1.48544724294,
1.49076651794,
1.5271587929400001,
1.47403630794,
1.48618591394,
1.4871567109400001
]
},
{
"command": "pacquet@main",
"mean": 1.49002647074,
"stddev": 0.03169171534239999,
"median": 1.48729215994,
"user": 1.7866272000000003,
"system": 1.8384866199999998,
"min": 1.42799833694,
"max": 1.54036523994,
"times": [
1.42799833694,
1.4902426879400001,
1.54036523994,
1.5025294949399999,
1.4838913229400001,
1.48131369094,
1.52123421294,
1.5097976179400001,
1.48434163194,
1.45855047094
]
}
]
} |
|
| Branch | pr/11970 |
| 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,356.39 ms(-12.08%)Baseline: 2,680.22 ms | 3,216.27 ms (73.26%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,505.49 ms(-16.93%)Baseline: 1,812.37 ms | 2,174.84 ms (69.22%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,046.42 ms(-0.29%)Baseline: 2,052.38 ms | 2,462.85 ms (83.09%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 671.86 ms(+2.32%)Baseline: 656.60 ms | 787.92 ms (85.27%) |
Reshape pnpm-registry's Config to match verdaccio's `config.yaml`
schema (storage, uplinks, packages) so the same file can drive
either server. The previous Config exposed a single
`upstream: Option<String>` resolved at startup; this replaces it
with named uplinks plus per-package `proxy:` rules walked in
declared order — same semantics as verdaccio, minus the surface
pnpm-registry does not implement (auth, web, plugins,
middlewares, logs routing, secret), which are accepted and
ignored.
Highlights:
* `Config { listen, public_url, storage, uplinks, packages,
packument_ttl }` with `UplinkConfig { url }` and
`PackageAccess { access, publish, unpublish, proxy }`.
`packages` is an `IndexMap` so first-match wins in declared
order — `**` cannot accidentally shadow `@*/*`.
* `Config::from_yaml(path, ...)` loads via `serde-saphyr` and
resolves a relative `storage:` against the config file's
parent. The verdaccio-only sections in the YAML are skipped
silently so `registry-mock`'s upstream `config.yaml` parses
untouched.
* `DEFAULT_CONFIG_YAML` — the bundled file mirrored from
`@pnpm/registry-mock` — is `include_str!`-ed and re-exported
from `lib.rs` so other crates (tests, benchmarks, future
embedders) can use the same defaults without reading from
disk. `Config::from_default_yaml(base_dir, ...)` parses it.
* CLI: `-c` / `--config <path>` overrides the bundled default.
`--storage` survives as a runtime override (handy for tests
without a custom YAML); `--upstream` and `--static` are gone
because the YAML now drives both. The mock orchestrator at
`pacquet/tasks/registry-mock` drops its `--upstream` flag —
the bundled config already declares the `npmjs` uplink.
* `server.rs` pre-builds one `Upstream` per declared uplink at
router construction (keyed by name, in an `IndexMap`) and
resolves the right client per request via
`Config::resolve_uplink`. No per-request `ThrottledClient`
allocations.
Existing tests are kept working by retaining the `Config::proxy`
/ `Config::static_serve` constructors and switching the test
helper from `config.upstream = ...` to mutating
`config.uplinks["npmjs"].url`. All 38 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
f04f62e to
548869f
Compare
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 `@registry/crates/pnpm-registry/src/main.rs`:
- Around line 36-37: The CLI arg packument_ttl_secs is defined with a default
and thus always overwrites the YAML packument_ttl; change the CLI arg
declaration (packument_ttl_secs) to be an Option<u64> (no default) and update
the code that applies config so you only set the runtime packument_ttl when
packument_ttl_secs.is_some(), leaving the YAML packument_ttl intact when the CLI
flag is not provided; refer to the packument_ttl_secs argument and the place
where packument_ttl from the YAML config is assigned/overwritten in main.rs to
implement this conditional assignment.
🪄 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: d5e5264f-e753-4a9f-bb29-a0e522ce3a21
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
pacquet/tasks/registry-mock/src/pnpm_registry_command.rsregistry/crates/pnpm-registry/Cargo.tomlregistry/crates/pnpm-registry/config.yamlregistry/crates/pnpm-registry/src/config.rsregistry/crates/pnpm-registry/src/lib.rsregistry/crates/pnpm-registry/src/main.rsregistry/crates/pnpm-registry/src/server.rsregistry/crates/pnpm-registry/tests/auth_publish.rsregistry/crates/pnpm-registry/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). (8)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Code Coverage
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Analyze (javascript)
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (4)
registry/crates/**/*.rs
📄 CodeRabbit inference engine (registry/AGENTS.md)
Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, error handling, and test layout
Files:
registry/crates/pnpm-registry/src/lib.rsregistry/crates/pnpm-registry/tests/server.rsregistry/crates/pnpm-registry/src/server.rsregistry/crates/pnpm-registry/src/main.rsregistry/crates/pnpm-registry/tests/auth_publish.rsregistry/crates/pnpm-registry/src/config.rs
registry/crates/**/Cargo.toml
📄 CodeRabbit inference engine (registry/AGENTS.md)
registry/crates/**/Cargo.toml: When using existing pacquet-* crates, declare them in the root [workspace.dependencies] and use { workspace = true } in the crate's Cargo.toml
New registry-only crates must be placed under registry/crates// and named pnpm-registry- in Cargo.toml, with the package name using the pnpm-registry- prefix exclusively
Do not reach for the pacquet- prefix to name new registry-side crates; use pnpm-registry- prefix exclusively for registry-only crates
Files:
registry/crates/pnpm-registry/Cargo.toml
registry/**/Cargo.toml
📄 CodeRabbit inference engine (registry/AGENTS.md)
Add new registry-only crates to [workspace.dependencies] at the root with the pnpm-registry- prefix so other crates can use { workspace = true }
Files:
registry/crates/pnpm-registry/Cargo.toml
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: When porting a function that firespnpm:<channel>events throughglobalLogger,logger.debug(), orstreamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plainStringor&str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only viaTryFrom<String>and/orFromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallibleFrom<String>(andFrom<&str>when convenient).
If upstream occasionally constructs without validation, exposefrom_str_uncheckedas an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization.
Use#[derive(derive_more::From)]and#[derive(derive_more::Into)]for mechanical conversion impls. Fall back to manualimplonly when conversion needs custom logic.
String-literal unions should becomeenums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
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 conventions.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse super::*;. Two forms stay allowed: external-crate preludes likeuse rayon::prelude::*;and root-of-module re-...
Files:
pacquet/tasks/registry-mock/src/pnpm_registry_command.rs
🧠 Learnings (1)
📚 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/tasks/registry-mock/src/pnpm_registry_command.rs
🔇 Additional comments (15)
registry/crates/pnpm-registry/src/lib.rs (1)
22-22: LGTM!registry/crates/pnpm-registry/src/server.rs (1)
9-9: LGTM!Also applies to: 47-50, 66-70, 74-74, 450-452, 718-719, 1066-1074, 1088-1094
pacquet/tasks/registry-mock/src/pnpm_registry_command.rs (1)
86-89: LGTM!registry/crates/pnpm-registry/Cargo.toml (1)
28-28: LGTM!Also applies to: 32-32
registry/crates/pnpm-registry/config.yaml (1)
1-73: LGTM!registry/crates/pnpm-registry/src/config.rs (8)
1-16: LGTM!
18-58: LGTM!
60-77: LGTM!
79-93: LGTM!
95-139: LGTM!
141-220: LGTM!
222-256: LGTM!
258-472: LGTM!registry/crates/pnpm-registry/tests/auth_publish.rs (1)
678-678: LGTM!Also applies to: 722-722
registry/crates/pnpm-registry/tests/server.rs (1)
17-17: LGTM!
Match verdaccio's first-match-wins semantics: when a `packages` pattern matches but has no `proxy:`, the package is storage-only — resolution stops there and returns `None` instead of falling through to a later catch-all. Without this, storage-only rules in the bundled config (`@private/*`, `plugin-example`, the unscoped fixtures, etc.) were silently being proxied to npm via the `**` rule. Also drop the `--upstream` flag from the jest globalSetup; the bundled config already declares the `npmjs` uplink with the same URL. --- Written by an agent (Claude Code, claude-opus-4-7).
`perfectionist::macro-argument-binding` rejects impure expressions passed directly into a macro because macros may evaluate their args more than once. Bind `path.display()` to a local first so the format call sees a single, already-evaluated value. --- Written by an agent (Claude Code, claude-opus-4-7).
--- Written by an agent (Claude Code, claude-opus-4-7).
There was a problem hiding this comment.
🧹 Nitpick comments (1)
registry/crates/pnpm-registry/src/config.rs (1)
220-220: ⚡ Quick winConsider validating proxy name references.
If a package rule specifies a
proxyname that doesn't exist inuplinks, this returnsNonesilently. While this might be intentional for graceful degradation, a misconfigured proxy name (e.g., typo) could be difficult to debug since packages would silently fall back to storage-only mode.Consider adding a validation pass or debug logging to warn when a referenced proxy name is not found in the uplinks map.
🤖 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 `@registry/crates/pnpm-registry/src/config.rs` at line 220, The lookup self.uplinks.get_key_value(proxy_name).map(|(k, v)| (k.as_str(), v)) can silently return None for a misspelled or missing proxy; add validation and a debug/warn when a package rule references a proxy name not present in self.uplinks. Modify the code path in config.rs (the uplinks lookup) to check for existence (e.g., match or if let) and emit a process-level warning or a logger call identifying the missing proxy_name and the affected package/rule context, or return a Result/Err so callers (where package rules are processed) can surface an error; ensure the message includes the proxy_name and the rule identifier for easier debugging.
🤖 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 `@registry/crates/pnpm-registry/src/config.rs`:
- Line 220: The lookup self.uplinks.get_key_value(proxy_name).map(|(k, v)|
(k.as_str(), v)) can silently return None for a misspelled or missing proxy; add
validation and a debug/warn when a package rule references a proxy name not
present in self.uplinks. Modify the code path in config.rs (the uplinks lookup)
to check for existence (e.g., match or if let) and emit a process-level warning
or a logger call identifying the missing proxy_name and the affected
package/rule context, or return a Result/Err so callers (where package rules are
processed) can surface an error; ensure the message includes the proxy_name and
the rule identifier for easier debugging.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cf959742-09fe-4722-b760-fbf137bb25e7
📒 Files selected for processing (2)
__utils__/jest-config/with-registry/globalSetup.jsregistry/crates/pnpm-registry/src/config.rs
💤 Files with no reviewable changes (1)
- utils/jest-config/with-registry/globalSetup.js
📜 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)
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Lint and Test (windows-latest)
- GitHub Check: Lint and Test (ubuntu-latest)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Code Coverage
- GitHub Check: Run benchmark on ubuntu-latest
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
registry/crates/**/*.rs
📄 CodeRabbit inference engine (registry/AGENTS.md)
Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, error handling, and test layout
Files:
registry/crates/pnpm-registry/src/config.rs
🔇 Additional comments (7)
registry/crates/pnpm-registry/src/config.rs (7)
1-16: LGTM!
18-93: LGTM!
95-139: LGTM!
141-205: LGTM!
207-222: LGTM!
224-258: LGTM!
260-475: LGTM!
The clap default was unconditionally clobbering config.packument_ttl on every run, defeating any value the loaded config supplies. Make the flag `Option<u64>` and only override when the caller passed one. --- Written by an agent (Claude Code, claude-opus-4-7).
…w binary CI installs @pnpm/pnpr from npm; the pinned 0.0.0-26052601 build still accepts --upstream (with a default), so dropping the flag here is premature. Defer the change to the same PR that bumps @pnpm/pnpr to a version built from this PR's source. pacquet/tasks/registry-mock keeps its drop because that command spawns the locally-built binary. --- Written by an agent (Claude Code, claude-opus-4-7).
…rop --upstream
Two fixes for the pnpm CLI test harness:
- @pnpm/testing.registry-mock imported @pnpm/network.fetch and
@pnpm/registry-access.client at module top, which transitively load
@pnpm/core-loggers/@pnpm/logger. Helpers like @pnpm/testing.command-defaults
import this module's constants and are pulled in statically by tests before
they call jest.unstable_mockModule('@pnpm/logger') — so the logger was loaded
before the mock, breaking it (e.g. exec.logs' recursive-prefix test saw 0
lifecycleLogger.debug calls). Import those deps lazily inside addDistTag/addUser.
- globalSetup no longer passes --upstream: the pnpm-registry binary dropped that
flag in pnpm#11970 (uplink is configured via the bundled verdaccio-shaped config,
which already proxies npmjs).
Reshape pnpm-registry's Config to match verdaccio's
config.yamlschema (storage, uplinks, packages) so the same file can drive either server. The previous Config exposed a singleupstream: Option<String>resolved at startup; this replaces it with named uplinks plus per-packageproxy:rules walked in declared order — same semantics as verdaccio, minus the surface pnpm-registry does not implement (auth, web, plugins, middlewares, logs routing, secret), which are accepted and ignored.Highlights:
Config { listen, public_url, storage, uplinks, packages, packument_ttl }withUplinkConfig { url }andPackageAccess { access, publish, unpublish, proxy }.packagesis anIndexMapwalked in declared order, first-match-wins: the first pattern matching a request is the rule that applies, and if that rule has noproxy:the package is storage-only (resolution returnsNoneinstead of falling through to a later catch-all). That makes the bundled@private/*/@pnpm.e2e/needs-auth/ unscoped-fixture rules behave the way the YAML says they should.Config::from_yaml(path, ...)loads viaserde-saphyrand resolves a relativestorage:against the config file's parent. The verdaccio-only sections in the YAML are skipped silently soregistry-mock's upstreamconfig.yamlparses untouched.DEFAULT_CONFIG_YAML— the bundled file mirrored from@pnpm/registry-mock— isinclude_str!-ed and re-exported fromlib.rsso other crates (tests, benchmarks, future embedders) can use the same defaults without reading from disk.Config::from_default_yaml(base_dir, ...)parses it.-c/--config <path>overrides the bundled default.--storagesurvives as a runtime override (handy for tests without a custom YAML);--upstreamand--staticare gone because the YAML now drives both.--packument-ttl-secsis optional — the loaded config's value wins when the flag is not supplied. The mock orchestrator atpacquet/tasks/registry-mockdrops its--upstreamflag — that command spawns the locally-built binary so it tracks this PR's source directly. The jest harness at__utils__/jest-config/with-registry/globalSetup.jskeeps--upstreamfor now because CI installs@pnpm/pnprfrom npm; the flag will be dropped in the same PR that bumps@pnpm/pnprto a build that lacks it.server.rspre-builds oneUpstreamper declared uplink at router construction (keyed by name, in anIndexMap) and resolves the right client per request viaConfig::resolve_uplink. No per-requestThrottledClientallocations.Existing tests are kept working by retaining the
Config::proxy/Config::static_serveconstructors and switching the test helper fromconfig.upstream = ...to mutatingconfig.uplinks["npmjs"].url. All 107 tests inpnpm-registrypass (55 unit + 26 + 9 + 17 integration).Ref: #11973
Summary by CodeRabbit
New Features
Chores