Skip to content

fix: route pacquet scoped packages through scoped registries#12340

Merged
zkochan merged 4 commits into
mainfrom
vuln-078
Jun 12, 2026
Merged

fix: route pacquet scoped packages through scoped registries#12340
zkochan merged 4 commits into
mainfrom
vuln-078

Conversation

@zkochan

@zkochan zkochan commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

  • Route pacquet scoped registry config from .npmrc, pnpm-workspace.yaml registries, and --config.@scope:registry into the registry map used by resolution.
  • Pass the resolved registry map through install, add, update, outdated, config dependency resolution, resolution verification, snapshot tarball reconstruction, and .modules.yaml persistence.
  • Add pacquet regressions proving scoped packages use the configured scoped registry and avoid the default registry in install/add/snapshot paths.
  • No changeset: this change is pacquet-only.

Tests

  • cargo fmt --manifest-path pacquet/crates/config/Cargo.toml --check
  • cargo fmt --manifest-path pacquet/crates/package-manager/Cargo.toml --check
  • cargo fmt --manifest-path pacquet/crates/cli/Cargo.toml --check
  • cargo test --manifest-path pacquet/crates/config/Cargo.toml scoped_registry --lib
  • cargo test --manifest-path pacquet/crates/config/Cargo.toml registries --lib
  • cargo test --manifest-path pacquet/crates/cli/Cargo.toml config_overrides::tests --lib
  • cargo test --manifest-path pacquet/crates/resolving-npm-resolver/Cargo.toml named_registry --lib
  • cargo test --manifest-path pacquet/crates/package-manager/Cargo.toml scoped_registry --lib
  • git diff --check
  • pre-push hook

Written by an agent (Codex, GPT-5).

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for scoped package registries, allowing different npm registries to be configured for specific package scopes (e.g., @private). Scoped registry configuration is now recognized from configuration files, workspace settings, and CLI overrides, and is properly used during package operations including add, update, install, and outdated checks.
  • Tests

    • Added comprehensive test coverage for scoped registry parsing and application across configuration sources.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe20906-f41a-4e54-8a46-1c61eaaf8afe

📥 Commits

Reviewing files that changed from the base of the PR and between 01b3d45 and cd31ac7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • pacquet/crates/cli/src/cli_args/dlx.rs
  • pacquet/crates/cli/src/cli_args/outdated.rs
  • pacquet/crates/cli/src/config_deps.rs
  • pacquet/crates/cli/src/config_overrides.rs
  • pacquet/crates/cli/src/config_overrides/tests.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/npmrc_auth.rs
  • pacquet/crates/config/src/npmrc_auth/tests.rs
  • pacquet/crates/config/src/workspace_yaml.rs
  • pacquet/crates/config/src/workspace_yaml/tests.rs
  • pacquet/crates/package-manager/Cargo.toml
  • pacquet/crates/package-manager/src/add.rs
  • pacquet/crates/package-manager/src/add/tests.rs
  • pacquet/crates/package-manager/src/build_resolution_verifiers.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot.rs
  • pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/package-manager/src/update.rs
  • pacquet/tasks/integrated-benchmark/src/latency_proxy/tests.rs

📝 Walkthrough

Walkthrough

This PR implements scoped package registry support across the pnpm/pacquet codebase. It extends the config schema to store per-scope registry routes, parses them from .npmrc files, pnpm-workspace.yaml, and CLI arguments, then updates package-manager operations to select and use the appropriate registry for each package throughout dependency resolution, installation, and updates.

Changes

Scoped registries implementation

Layer / File(s) Summary
Config schema and resolved_registries helper
pacquet/crates/config/src/lib.rs
Adds registries: BTreeMap<String, String> field to store scoped registry routes keyed by @scope, introduces Config::resolved_registries() method returning merged map of default + scoped registries, and updates Config::current documentation to clarify which .npmrc entries are applied.
.npmrc scoped registry parsing
pacquet/crates/config/src/npmrc_auth.rs, npmrc_auth/tests.rs
Extends NpmrcAuth to parse @scope:registry=... entries from .npmrc files with URL normalization, stores in new scoped_registries field, applies into Config.registries during apply_registry_and_warn(), merges with higher-priority precedence via merge_under(), and adds scoped_registry_key() helper with test coverage.
pnpm-workspace.yaml registries support
pacquet/crates/config/src/workspace_yaml.rs, workspace_yaml/tests.rs
Adds optional registries field to WorkspaceSettings for pnpm-workspace.yaml parsing, applies environment variable substitution in trusted mode and filters unsafe placeholders in untrusted mode, routes "default" scope to Config::registry and others to Config::registries with centralized normalize_registry_url() helper, with comprehensive test coverage for YAML parsing and env-var security.
CLI config overrides for scoped registries
pacquet/crates/cli/src/config_overrides.rs, config_overrides/tests.rs
Extends ConfigOverrides to recognize --config.@scope:registry=... tokens, stores in new registries: BTreeMap<String, String> field, applies onto Config.registries during apply() with URL normalization, includes scoped_registry_key() and normalize_registry_url() helpers, and validates CLI override precedence over config defaults.
CLI tool integration
pacquet/crates/cli/src/cli_args/dlx.rs, config_deps.rs
Updates build_registries_map (dlx) and resolve_and_install (config_deps) to use config.resolved_registries() instead of constructing single-entry "default" maps, ensuring all CLI paths use the full scoped registry configuration.
Per-package registry selection in fetch operations
pacquet/crates/cli/src/cli_args/outdated.rs, pacquet/crates/package-manager/src/add.rs, update.rs
Updates outdated, update, and add commands to build registries maps from resolved_registries() and use pick_registry_for_package() to select appropriate registry per package when fetching latest tags and packument data, replacing hardcoded config.registry usage.
Registry routing in install and resolution
pacquet/crates/package-manager/src/install.rs, build_resolution_verifiers.rs, install_with_fresh_lockfile.rs
Updates build_modules_manifest to persist all resolved registries into node_modules/.modules.yaml, updates build_resolution_verifiers and install_with_fresh_lockfile to use config.resolved_registries() for resolver options and pnpmfile hook context instead of single-entry maps.
Scoped registry tarball URL resolution
pacquet/crates/package-manager/src/install_package_by_snapshot.rs, install_package_by_snapshot/tests.rs
Updates tarball_url_and_integrity to use pick_registry_for_package() when constructing tarball URLs for registry resolutions, ensuring scoped packages are fetched from configured scoped registries, with test validating correct tarball base URL selection.
Add command integration tests
pacquet/crates/package-manager/Cargo.toml, src/add/tests.rs
Adds mockito dev-dependency and comprehensive async integration test validating that add command resolves @private/foo via its configured scoped registry, with mock HTTP endpoints and request verification.
Install command integration tests
pacquet/crates/package-manager/src/install_package_from_registry/tests.rs, install/tests.rs
Adds scoped-registry test fixtures (SCOPED_TEST_INTEGRITY constant and scoped_package_body() helper), introduces lockfile-only integration test exercising scoped registry resolution, and updates existing event-sequence and modules.yaml tests to configure and assert scoped registry behavior.
Benchmark test reliability
pacquet/tasks/integrated-benchmark/src/latency_proxy/tests.rs
Updates slow_start_ramps_per_connection_throughput to compute flat baseline as minimum of two runs (reducing CI noise), removes debug output, and tightens assertion margin from 60ms to 25ms.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • pnpm/pnpm#11806: Both PRs extend the CLI ConfigOverrides flow in pacquet/crates/cli/src/config_overrides.rs to apply registry configuration; the referenced PR adds the foundational override layer for registry, while this PR extends it to handle scoped :registry keys and overlay them onto Config.registries.

Suggested reviewers

  • KSXGitHub

Poem

🐇 Hops excitedly through registry routes

Scoped packages hop along their registry trails,
while @private friends find their proper homes—
no more lost in the default alone!
The config now remembers which scope goes where,
from .npmrc to workspace yaml to CLI whispers.
🎯✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vuln-078

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      8.3±0.32ms   524.1 KB/sec    1.00      8.2±0.23ms   528.6 KB/sec

@codecov-commenter

codecov-commenter commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.82%. Comparing base (615c669) to head (cd31ac7).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
pacquet/crates/config/src/npmrc_auth.rs 83.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12340      +/-   ##
==========================================
+ Coverage   87.74%   87.82%   +0.07%     
==========================================
  Files         291      291              
  Lines       36107    36164      +57     
==========================================
+ Hits        31683    31761      +78     
+ Misses       4424     4403      -21     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD.

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.183 ± 0.182 4.019 4.550 1.81 ± 0.14
pacquet@main 4.144 ± 0.156 3.946 4.331 1.79 ± 0.14
pnpr@HEAD 2.391 ± 0.153 2.169 2.609 1.03 ± 0.10
pnpr@main 2.316 ± 0.155 2.183 2.642 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.18349581916,
      "stddev": 0.18241386530856302,
      "median": 4.11296830936,
      "user": 3.544542659999999,
      "system": 3.4279806199999996,
      "min": 4.01850346436,
      "max": 4.54980881236,
      "times": [
        4.0874777913600004,
        4.01850346436,
        4.17621589036,
        4.54980881236,
        4.3686506963600005,
        4.365895210360001,
        4.13845882736,
        4.033596703360001,
        4.055922794360001,
        4.0404280013600005
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.14445978476,
      "stddev": 0.15606580123525268,
      "median": 4.182185497360001,
      "user": 3.54121746,
      "system": 3.40760152,
      "min": 3.94583124036,
      "max": 4.33075705636,
      "times": [
        4.21513139536,
        4.31428327536,
        3.95762035536,
        4.153274903360001,
        3.94583124036,
        4.01265116636,
        4.33075705636,
        3.98720245536,
        4.21109609136,
        4.31674990836
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.3913592048599996,
      "stddev": 0.15308904330915424,
      "median": 2.3888418838599996,
      "user": 2.60058086,
      "system": 2.9930644200000005,
      "min": 2.1689908173599997,
      "max": 2.60859866336,
      "times": [
        2.60859866336,
        2.1689908173599997,
        2.3452426743599997,
        2.52991001936,
        2.5356498953599997,
        2.18898483536,
        2.49925899136,
        2.3565510393599998,
        2.25927238436,
        2.42113272836
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.31626895456,
      "stddev": 0.15458391763898377,
      "median": 2.2664345403599997,
      "user": 2.6385454599999996,
      "system": 3.00427392,
      "min": 2.1832645023599997,
      "max": 2.64177082236,
      "times": [
        2.64177082236,
        2.52903364636,
        2.28880215836,
        2.19186422036,
        2.26830340336,
        2.3672862973599997,
        2.1875505833599997,
        2.2402482343599996,
        2.26456567736,
        2.1832645023599997
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 667.1 ± 20.6 643.4 694.7 1.00
pacquet@main 667.6 ± 17.9 649.8 714.6 1.00 ± 0.04
pnpr@HEAD 777.8 ± 44.3 739.9 879.2 1.17 ± 0.08
pnpr@main 819.3 ± 112.0 758.6 1132.3 1.23 ± 0.17
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6671281044399999,
      "stddev": 0.020560123612667368,
      "median": 0.66377748944,
      "user": 0.40482644000000007,
      "system": 1.31448388,
      "min": 0.64343604144,
      "max": 0.6947005014400001,
      "times": [
        0.66707228344,
        0.6947005014400001,
        0.66048269544,
        0.69447752344,
        0.6887419744400001,
        0.6456792194400001,
        0.6776260724400001,
        0.6536627754400001,
        0.6454019574400001,
        0.64343604144
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.66756786344,
      "stddev": 0.01787661754669341,
      "median": 0.6649068039400001,
      "user": 0.37288943999999996,
      "system": 1.3284759800000001,
      "min": 0.64976722744,
      "max": 0.71464006144,
      "times": [
        0.66832318644,
        0.6619698484400001,
        0.71464006144,
        0.64976722744,
        0.65846442344,
        0.6666214554400001,
        0.65383041444,
        0.66614020244,
        0.6722484094400001,
        0.6636734054400001
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.77777278024,
      "stddev": 0.044265072666794913,
      "median": 0.7595380034400001,
      "user": 0.40653014,
      "system": 1.3347681800000002,
      "min": 0.7399404364400001,
      "max": 0.87918255144,
      "times": [
        0.8330106954400001,
        0.77877175044,
        0.7755723814400001,
        0.74961452044,
        0.75125285444,
        0.7399404364400001,
        0.7589864684400001,
        0.76008953844,
        0.75130660544,
        0.87918255144
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.81934064174,
      "stddev": 0.11196272602917423,
      "median": 0.7806646799400001,
      "user": 0.40701583999999996,
      "system": 1.34815498,
      "min": 0.75861563744,
      "max": 1.1323341074400002,
      "times": [
        0.81692158944,
        0.77909852944,
        0.77177451044,
        0.7763769234400001,
        0.7822308304400001,
        0.75861563744,
        0.78486096344,
        1.1323341074400002,
        0.82510255844,
        0.76609076744
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 3.848 ± 0.055 3.765 3.923 1.86 ± 0.05
pacquet@main 3.816 ± 0.048 3.741 3.898 1.84 ± 0.04
pnpr@HEAD 2.241 ± 0.143 2.006 2.409 1.08 ± 0.07
pnpr@main 2.070 ± 0.043 2.002 2.143 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 3.8484330441000005,
      "stddev": 0.055093567693418465,
      "median": 3.8478239323000003,
      "user": 3.53105578,
      "system": 3.34531804,
      "min": 3.7647411378,
      "max": 3.9232845418,
      "times": [
        3.8250081558,
        3.9041439478,
        3.8309654068000003,
        3.9060735878,
        3.8239803528,
        3.9232845418,
        3.8708913828,
        3.8646824578000003,
        3.7647411378,
        3.7705594698000002
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 3.8159902231000005,
      "stddev": 0.04769330425885132,
      "median": 3.8079108688000005,
      "user": 3.48799388,
      "system": 3.3389624400000004,
      "min": 3.7411284498,
      "max": 3.8980300568,
      "times": [
        3.7774810638000003,
        3.7835621528,
        3.7925585648,
        3.8980300568,
        3.8314533438000002,
        3.7411284498,
        3.8166413018000003,
        3.7991804358000003,
        3.8416392738000003,
        3.8782275878
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.2409733609,
      "stddev": 0.14252745464278127,
      "median": 2.2486159903000003,
      "user": 2.43588778,
      "system": 2.89023324,
      "min": 2.0057364768,
      "max": 2.4088174308,
      "times": [
        2.0677546318,
        2.1810305598,
        2.3460316358,
        2.0057364768,
        2.2187790908,
        2.2784528898,
        2.4088174308,
        2.3722523228,
        2.1343764828,
        2.3965020878
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.0699720256,
      "stddev": 0.04253509728440881,
      "median": 2.0578113103,
      "user": 2.4587775800000005,
      "system": 2.88151434,
      "min": 2.0019535438,
      "max": 2.1433301978,
      "times": [
        2.1322057968,
        2.0414988228,
        2.0019535438,
        2.1433301978,
        2.0847505768000003,
        2.0828919178,
        2.0561397278,
        2.0476005178000003,
        2.0594828928,
        2.0498662618
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.110 ± 0.014 1.088 1.136 1.64 ± 0.05
pacquet@main 1.105 ± 0.029 1.072 1.175 1.63 ± 0.07
pnpr@HEAD 0.706 ± 0.048 0.667 0.808 1.04 ± 0.08
pnpr@main 0.677 ± 0.021 0.656 0.728 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.11036898118,
      "stddev": 0.014086728728168777,
      "median": 1.11063490828,
      "user": 1.1387017,
      "system": 1.6702625199999996,
      "min": 1.08754736878,
      "max": 1.13602706878,
      "times": [
        1.10226977878,
        1.11215172078,
        1.11205886678,
        1.09645549178,
        1.13602706878,
        1.10677012278,
        1.11292018278,
        1.12827826078,
        1.08754736878,
        1.10921094978
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.10537820528,
      "stddev": 0.029004939504132965,
      "median": 1.10232510728,
      "user": 1.0951728000000003,
      "system": 1.6653851199999998,
      "min": 1.07224366278,
      "max": 1.17473428978,
      "times": [
        1.10359907678,
        1.10105113778,
        1.12360251478,
        1.07456994378,
        1.1065933027800001,
        1.07224366278,
        1.17473428978,
        1.11058228478,
        1.08866597978,
        1.09813985978
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.7063753099800001,
      "stddev": 0.047525500141388385,
      "median": 0.6860948072800002,
      "user": 0.36168429999999996,
      "system": 1.2892371199999997,
      "min": 0.66665698778,
      "max": 0.8083251157800001,
      "times": [
        0.66665698778,
        0.6874363897800001,
        0.6763188087800001,
        0.68163820578,
        0.77491848478,
        0.6847532247800001,
        0.6895462267800001,
        0.6763308667800001,
        0.8083251157800001,
        0.7178287887800001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.6767877331800001,
      "stddev": 0.020605817660187126,
      "median": 0.67321782428,
      "user": 0.34930059999999996,
      "system": 1.27334902,
      "min": 0.6556094327800001,
      "max": 0.7284178357800001,
      "times": [
        0.65709101378,
        0.68065322478,
        0.6693140967800001,
        0.6556094327800001,
        0.68198964078,
        0.67297542478,
        0.67346022378,
        0.7284178357800001,
        0.6837283417800001,
        0.6646380967800001
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.602 ± 0.058 2.552 2.751 3.83 ± 0.11
pacquet@main 2.610 ± 0.047 2.562 2.721 3.84 ± 0.10
pnpr@HEAD 0.697 ± 0.017 0.680 0.726 1.03 ± 0.03
pnpr@main 0.679 ± 0.013 0.665 0.706 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.60247687606,
      "stddev": 0.057721837306739236,
      "median": 2.5891677067599996,
      "user": 1.5882862999999998,
      "system": 1.9192335999999997,
      "min": 2.55203958376,
      "max": 2.75054879676,
      "times": [
        2.62991769676,
        2.55311189376,
        2.56843771776,
        2.59856146976,
        2.60949909176,
        2.57830927976,
        2.60456928676,
        2.55203958376,
        2.57977394376,
        2.75054879676
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.6099503132599997,
      "stddev": 0.04651493913614878,
      "median": 2.5993050902599997,
      "user": 1.5769389999999999,
      "system": 1.9366208,
      "min": 2.5618273727600003,
      "max": 2.72125040976,
      "times": [
        2.64140912676,
        2.59928008276,
        2.5618273727600003,
        2.59100339376,
        2.5694300447600003,
        2.61003748276,
        2.57599976276,
        2.59933009776,
        2.62993535876,
        2.72125040976
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.6973549452600001,
      "stddev": 0.0173488344384032,
      "median": 0.6943608322600001,
      "user": 0.3517784,
      "system": 1.3159884999999998,
      "min": 0.68015170776,
      "max": 0.72602998576,
      "times": [
        0.6804322837600001,
        0.70723613776,
        0.68543033176,
        0.6891286187600001,
        0.7242964417600001,
        0.6999881507600001,
        0.68126274876,
        0.72602998576,
        0.69959304576,
        0.68015170776
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.6792177745600001,
      "stddev": 0.012593377553497096,
      "median": 0.67649290526,
      "user": 0.34181690000000003,
      "system": 1.2816205,
      "min": 0.6650854057600001,
      "max": 0.7063614957600001,
      "times": [
        0.7063614957600001,
        0.69362059876,
        0.66928350276,
        0.6720608827600001,
        0.6776422507600001,
        0.67534355976,
        0.66927244976,
        0.68030179676,
        0.6832058027600001,
        0.6650854057600001
      ]
    }
  ]
}

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12340
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
3,848.43 ms
(-55.65%)Baseline: 8,676.71 ms
10,412.05 ms
(36.96%)
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
🚷 view threshold
2,602.48 ms
(-45.30%)Baseline: 4,758.12 ms
5,709.75 ms
(45.58%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,110.37 ms
(-19.54%)Baseline: 1,380.07 ms
1,656.08 ms
(67.05%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
4,183.50 ms
(-56.32%)Baseline: 9,576.71 ms
11,492.05 ms
(36.40%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
667.13 ms
(+0.59%)Baseline: 663.23 ms
795.87 ms
(83.82%)
🐰 View full continuous benchmarking report in Bencher

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12340
Testbedpnpr

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
BenchmarkLatencymilliseconds (ms)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
2,240.97 ms
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
697.35 ms
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
706.38 ms
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
2,391.36 ms
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
777.77 ms
🐰 View full continuous benchmarking report in Bencher

@zkochan zkochan marked this pull request as ready for review June 12, 2026 06:42
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 12, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0)

Grey Divider


Remediation recommended

1. Snapshot registry map cloned 🐞 Bug ➹ Performance
Description
Both tarball_url_and_integrity and collect_outdated rebuild a registries HashMap from
config.resolved_registries() on hot per-item paths (per-package install resolution and
per-dependency outdated checks). This causes repeated cloning/allocation overhead that scales with
package/dependency count and is avoidable because the registry routing configuration is immutable
for the command run.
Code

pacquet/crates/package-manager/src/install_package_by_snapshot.rs[R607-614]

+            let registries: HashMap<String, String> =
+                config.resolved_registries().into_iter().collect();
+            let name = package_key.name.to_string();
+            let registry = pick_registry_for_package(&registries, &name, None);
+            let registry = registry.strip_suffix('/').unwrap_or(&registry);
            let version = package_key.suffix.version();
-            let bare_name = name.bare.as_str();
+            let bare_name = package_key.name.bare.as_str();
            let tarball_url = format!("{registry}/{name}/-/{bare_name}-{version}.tgz");
Evidence
The cited helper for registry/tarball resolution constructs a new registries map each time it runs,
and because it is invoked from the per-package install loop, that cloning/allocation repeats once
per package during snapshot materialization. Similarly, in collect_outdated the registries
HashMap is built inside the dependency-mapped async closure used for the fan-out
(join_all(fetches)), meaning the same map construction runs once per dependency fetch rather than
once per command invocation, multiplying overhead with the number of dependencies.

pacquet/crates/package-manager/src/install_package_by_snapshot.rs[293-297]
pacquet/crates/package-manager/src/install_package_by_snapshot.rs[606-615]
pacquet/crates/cli/src/cli_args/outdated.rs[126-138]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`tarball_url_and_integrity` and `collect_outdated` currently build a fresh `HashMap<String, String>` from `config.resolved_registries()` on per-item execution paths (per-package install resolution and per-dependency outdated checks). This repeatedly clones the same registry routing configuration and allocates a new map many times within a single command, even though the registries mapping is effectively immutable for the duration of the run.

## Issue Context
- In the install/materialization path, `tarball_url_and_integrity` is called as part of handling each package’s registry/tarball resolution, so rebuilding the registries map repeats per package.
- In `outdated`, the registries map is constructed inside the dependency fan-out async closure, so it runs once per dependency fetch; because this command commonly fans out to many concurrent registry requests, the overhead scales with dependency count.
- The registries map may be small, but it is stable across the command, so rebuilding it per package/dependency is unnecessary work.

## Fix Focus Areas
- pacquet/crates/package-manager/src/install_package_by_snapshot.rs[293-316]
- pacquet/crates/package-manager/src/install_package_by_snapshot.rs[592-616]
- pacquet/crates/cli/src/cli_args/outdated.rs[114-158]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Duplicated registry normalization 🐞 Bug ⚙ Maintainability
Description
scoped_registry_key and normalize_registry_url are duplicated across CLI overrides, .npmrc
parsing, and workspace YAML parsing, increasing the risk of inconsistent behavior if one copy
changes. This duplication makes future fixes (e.g., stricter validation or URL normalization
changes) easy to apply incompletely.
Code

pacquet/crates/cli/src/config_overrides.rs[R97-104]

+fn scoped_registry_key(key: &str) -> Option<&str> {
+    key.strip_suffix(":registry")
+        .filter(|scope| scope.starts_with('@') && scope.len() > 1 && !scope.contains('/'))
+}
+
+fn normalize_registry_url(registry: &str) -> String {
+    if registry.ends_with('/') { registry.to_string() } else { format!("{registry}/") }
+}
Evidence
The same helper logic is present in multiple places, so a change to one implementation would not
automatically update the others.

pacquet/crates/cli/src/config_overrides.rs[97-104]
pacquet/crates/config/src/npmrc_auth.rs[725-728]
pacquet/crates/config/src/workspace_yaml.rs[951-953]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Multiple modules define their own versions of `scoped_registry_key` and/or `normalize_registry_url`. This creates a drift risk where one path normalizes differently than another.

## Issue Context
These helpers implement routing-critical normalization (trailing slash) and parsing (`@scope:registry` key extraction). Keeping them consistent matters as the feature set grows.

## Fix Focus Areas
- pacquet/crates/cli/src/config_overrides.rs[97-104]
- pacquet/crates/config/src/npmrc_auth.rs[721-728]
- pacquet/crates/config/src/workspace_yaml.rs[951-953]

### Implementation sketch
- Move `normalize_registry_url` (and optionally `scoped_registry_key`) into a shared crate/module (e.g., `pacquet_config` or `pacquet_resolving_npm_resolver::registry_url`).
- Re-export as needed and replace local implementations with calls to the shared helpers.
- Keep existing behavior identical (especially the scope validation predicate and trailing-slash policy).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@zkochan zkochan merged commit 66a9078 into main Jun 12, 2026
27 of 28 checks passed
@zkochan zkochan deleted the vuln-078 branch June 12, 2026 06:43
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix scoped package routing via configured scoped registries
🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

Walkthroughs

Description
• Parse scoped registry routes from .npmrc, pnpm-workspace.yaml, and --config.@scope:registry.
• Thread resolved registry maps through resolution, install/add/update/outdated, snapshots, and
  .modules.yaml.
• Add regression tests ensuring scoped packages use scoped registries and never hit default.
Diagram
graph TD
  NPMRC[".npmrc parser"] --> CFG["Config"] --> MAP["resolved_registries()"] --> PICK["Registry picker"] --> OPS["Pkg manager ops"] --> MOD[".modules.yaml"]
  WS["pnpm-workspace.yaml"] --> CFG
  CLI["CLI --config overrides"] --> CFG
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Centralize registry helpers in pacquet-config
  • ➕ Deduplicates scoped_registry_key/normalize_registry_url logic currently repeated in multiple crates
  • ➕ Reduces risk of subtle divergence in validation/normalization rules over time
  • ➖ Minor refactor touchpoints across crates
  • ➖ Not required to fix the routing bug
2. Represent registry URLs as Url types in Config
  • ➕ Stronger validation at boundaries; avoids repeated string normalization
  • ➕ Can prevent malformed registry values from propagating into resolution/fetch paths
  • ➖ Broader API surface change (serialization, defaults, tests)
  • ➖ May require additional conversion glue for consumers expecting strings

Recommendation: The PR’s approach (store scoped routes on Config, expose a pnpm-shaped resolved_registries(), and reuse the existing pick_registry_for_package logic everywhere) is the right baseline: it keeps routing rules centralized and makes behavior consistent across commands and persistence. Consider a follow-up to deduplicate helper functions (scope key parsing and URL normalization) to avoid drift, but it’s reasonable to keep this PR focused on correctness plus regressions.

Grey Divider

File Changes

Enhancement (4)
config_overrides.rs Support --config.@scope:registry overrides and URL normalization +22/-2

Support --config.@scope:registry overrides and URL normalization

• Extends CLI dotted-key override parsing to recognize @scope:registry keys, normalize registry URLs with a trailing slash, and apply scoped overrides into Config.registries.

pacquet/crates/cli/src/config_overrides.rs


lib.rs Add scoped registries to Config and expose resolved_registries() +20/-8

Add scoped registries to Config and expose resolved_registries()

• Introduces Config.registries (scoped routes) and a resolved_registries() helper that returns pnpm’s expected map shape: default plus @scope entries. Updates documentation to reflect scoped registry support in .npmrc parsing.

pacquet/crates/config/src/lib.rs


npmrc_auth.rs Parse @scope:registry from .npmrc and apply to Config +18/-4

Parse @scope:registry from .npmrc and apply to Config

• Extends .npmrc parsing to capture scoped registry routes into a BTreeMap, normalizes URLs, merges scoped routes across layered .npmrc sources, and applies them into Config during registry application.

pacquet/crates/config/src/npmrc_auth.rs


workspace_yaml.rs Support workspace registries map including default and scoped routes +20/-1

Support workspace registries map including default and scoped routes

• Adds WorkspaceSettings.registries, substitutes/filters env placeholders for this map, and applies it into Config (treating "default" as the base registry and others as scoped routes) with URL normalization.

pacquet/crates/config/src/workspace_yaml.rs


Bug fix (9)
dlx.rs Use resolved registries map for dlx cache key inputs +1/-2

Use resolved registries map for dlx cache key inputs

• Replaces the hard-coded default registry map with Config::resolved_registries(), ensuring scoped routes participate in dlx’s registries map used for cache keying.

pacquet/crates/cli/src/cli_args/dlx.rs


outdated.rs Pick per-package registry when fetching versions for outdated +6/-1

Pick per-package registry when fetching versions for outdated

• Selects the appropriate registry via pick_registry_for_package using the resolved registries map, and fetches package metadata from that registry instead of always using the default registry.

pacquet/crates/cli/src/cli_args/outdated.rs


config_deps.rs Pass resolved registries into config dependency resolution +1/-2

Pass resolved registries into config dependency resolution

• Builds the registry map from Config::resolved_registries() rather than a single default entry, enabling scoped registry routing for configurational dependencies.

pacquet/crates/cli/src/config_deps.rs


add.rs Route Add latest/version lookup through scoped registry selection +8/-1

Route Add latest/version lookup through scoped registry selection

• Uses Config::resolved_registries() plus pick_registry_for_package to fetch latest versions from the correct registry instead of always hitting the default registry.

pacquet/crates/package-manager/src/add.rs


build_resolution_verifiers.rs Provide verifier full resolved registries map +1/-6

Provide verifier full resolved registries map

• Replaces the previous minimal {default} map with Config::resolved_registries(), enabling verifier behavior consistent with scoped routing configuration.

pacquet/crates/package-manager/src/build_resolution_verifiers.rs


install.rs Persist full resolved registries into .modules.yaml +1/-1

Persist full resolved registries into .modules.yaml

• Writes Modules.registries as the full resolved registries map (default + scopes) rather than only the default registry entry.

pacquet/crates/package-manager/src/install.rs


install_package_by_snapshot.rs Use scoped registry base when reconstructing tarball URLs from snapshots +7/-3

Use scoped registry base when reconstructing tarball URLs from snapshots

• Updates tarball_url_and_integrity to select the correct registry for a registry resolution via pick_registry_for_package, ensuring reconstructed tarball URLs use scoped registry bases for scoped packages.

pacquet/crates/package-manager/src/install_package_by_snapshot.rs


install_with_fresh_lockfile.rs Thread resolved registries through fresh-lockfile resolution and hooks +3/-3

Thread resolved registries through fresh-lockfile resolution and hooks

• Builds the registries map using resolved_registries() for the resolver prefetch phase and passes the full map into the pre-resolution hook context JSON instead of only default.

pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs


update.rs Route update latest fetch through scoped registry selection +5/-1

Route update latest fetch through scoped registry selection

• Updates the update flow’s latest-version fetch to pick the correct registry based on package scope using the resolved registries map.

pacquet/crates/package-manager/src/update.rs


Tests (8)
tests.rs Add tests for scoped registry CLI overrides +29/-0

Add tests for scoped registry CLI overrides

• Adds unit tests verifying that --config.@scope:registry is parsed, normalized (trailing slash), and overrides existing scoped registry config.

pacquet/crates/cli/src/config_overrides/tests.rs


tests.rs Test .npmrc scoped registry parsing and env-placeholder filtering +21/-1

Test .npmrc scoped registry parsing and env-placeholder filtering

• Adds coverage ensuring scoped registry routes are parsed and applied with URL normalization, and updates env-placeholder filtering expectations to target scoped registries.

pacquet/crates/config/src/npmrc_auth/tests.rs


tests.rs Test parsing and application of registries from workspace YAML +35/-0

Test parsing and application of registries from workspace YAML

• Adds tests verifying registries.default and scoped entries are read from YAML, applied to Config with trailing slashes, and that env-placeholder entries are removed while literal scoped entries remain.

pacquet/crates/config/src/workspace_yaml/tests.rs


tests.rs Regression: add uses scoped registry for scoped packages +122/-0

Regression: add uses scoped registry for scoped packages

• Adds an async test with separate default/scoped mock registries asserting @private/foo requests never hit the default registry and are served by the configured scoped registry.

pacquet/crates/package-manager/src/add/tests.rs


tests.rs Regression: install routes scoped packages and persists registries +105/-0

Regression: install routes scoped packages and persists registries

• Adds a lockfile-only install test ensuring scoped packuments are fetched from the scoped registry, updates fixtures to include scoped registries, and asserts .modules.yaml contains the scoped entry.

pacquet/crates/package-manager/src/install/tests.rs


tests.rs Test snapshot tarball URL reconstruction uses scoped registry +19/-2

Test snapshot tarball URL reconstruction uses scoped registry

• Adds a unit test asserting a scoped package’s reconstructed tarball URL uses the configured scoped registry instead of the default registry base.

pacquet/crates/package-manager/src/install_package_by_snapshot/tests.rs


tests.rs Update test config construction for new Config.registries field +1/-0

Update test config construction for new Config.registries field

• Extends the helper config initializer in registry-install tests to populate the new registries field with a default value to match the updated Config struct.

pacquet/crates/package-manager/src/install_package_from_registry/tests.rs


tests.rs Stabilize slow-start timing benchmark under CI jitter +5/-5

Stabilize slow-start timing benchmark under CI jitter

• Makes the baseline measurement take the faster of two flat samples and relaxes the asserted margin, reducing flakiness due to scheduler noise while preserving the slow-start signal.

pacquet/tasks/integrated-benchmark/src/latency_proxy/tests.rs


Other (1)
Cargo.toml Add mockito for package-manager scoped registry tests +1/-0

Add mockito for package-manager scoped registry tests

• Introduces mockito as a dev/test dependency to support new registry-routing regression tests in the package-manager crate.

pacquet/crates/package-manager/Cargo.toml


Grey Divider

Qodo Logo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants