Skip to content

feat(pacquet): initial implementations of run, exec, dlx (first attempt)#11937

Closed
KSXGitHub wants to merge 7 commits into
mainfrom
claude/magical-allen-CJ2aZ
Closed

feat(pacquet): initial implementations of run, exec, dlx (first attempt)#11937
KSXGitHub wants to merge 7 commits into
mainfrom
claude/magical-allen-CJ2aZ

Conversation

@KSXGitHub

@KSXGitHub KSXGitHub commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Ports run, exec, and dlx from the TypeScript pnpm CLI to pacquet (non-recursive).

  • run: reworks the existing stub to run scripts through a new foreground run_script in pacquet-executor, which sets up node_modules/.bin on PATH and the npm_* environment via the existing build_env / extend_path / select_shell. It also runs pre / post scripts under enablePrePostScripts, shell-quotes trailing args, lists scripts when no name is given, filters hidden scripts, honors --if-present, and propagates the script's exit code.
  • exec: runs a command with node_modules/.bin prepended to PATH (makeEnv), supports --shell-mode, and reports a command-not-found error.
  • dlx: installs the requested package(s) into a content-addressed cache dir (reusing the normal install pipeline, redirected to a self-contained per-cache virtual store) and runs a resolved bin. Supports --package, --allow-build, --shell-mode, and a TTL cache (dlxCacheMaxAge).

New settings backing the above: enablePrePostScripts, scriptShell, nodeOptions, extraBinPaths, dlxCacheMaxAge.

Scope caveat

Per pacquet/CONTRIBUTING.md, pacquet's current scope is Stage 1 (the headless installer). New top-level commands and new settings or flags are explicitly out of scope and "will be closed with a pointer to the roadmap." exec and dlx are new commands and this adds new settings, so this PR sits outside Stage 1 and is posted for discussion rather than merge.

Known gaps and deviations from pnpm (deferred)

  • No --filter / --recursive workspace fan-out.
  • run: the /regex/ script selector and verifyDepsBeforeRun are not ported.
  • dlx: the cache is keyed on the raw spec rather than the resolved package id; git, tarball, and file specifiers are unsupported; the interactive approve-builds prompt is not ported.

Test plan

  • cargo clippy --workspace --all-targets and cargo fmt --check are clean.
  • Unit and integration tests for run and exec pass, plus dlx helper unit tests.
  • dlx end-to-end (@pnpm.e2e/hello-world-js-bin) requires the mocked registry, so it runs in CI rather than the sandbox.

Written by an agent (Claude Code).

claude added 2 commits May 25, 2026 15:03
Rework `pacquet run` to match pnpm: it now sets up the script
environment (`node_modules/.bin` on PATH, `npm_*` env stamping) via a
new foreground `run_script` in the executor instead of bare `sh -c`,
runs `pre`/`post` scripts under `enablePrePostScripts`, shell-quotes
trailing args, lists scripts when no name is given, filters hidden
(`.`-prefixed) scripts, honors `--if-present`, and propagates the
script's exit code.

Add `pacquet exec`, which runs a command with `node_modules/.bin`
prepended to PATH (`makeEnv`), supports `--shell-mode`, and surfaces a
"command not found" error.

New config fields `enablePrePostScripts`, `scriptShell`, `nodeOptions`,
and `extraBinPaths` back the script environment.

Non-recursive only: `--filter` / `--recursive` workspace fan-out is not
yet ported. The `/regexp/` script selector is also deferred (no regex
engine in the dependency set).
Add `pacquet dlx`, which installs one or more packages into a
content-addressed cache directory and runs a binary from them in a
throwaway environment, reusing the normal install pipeline. Supports
`--package`, `--allow-build`, `--shell-mode`, a TTL-bounded cache keyed
on the requested specs (`dlxCacheMaxAge`, new config field), and bin
resolution via `cmd-shim`.

The install runs under a config anchored to the caller's working dir
(so the user's registry / store / cache settings are honored) but
redirected into a self-contained per-cache-dir virtual store.

Known gaps vs pnpm, deferred: the cache is keyed on the raw spec rather
than the resolved package id (a standalone resolver entry is needed to
pick up floating tags before the TTL); git / tarball / file specifiers
and the interactive `approve-builds` prompt are not yet supported.
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fbbd5ce0-a651-4ed4-a88f-b11ede0464b0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/magical-allen-CJ2aZ

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 May 25, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      8.4±0.30ms   514.9 KB/sec    1.00      8.3±0.41ms   520.6 KB/sec

@KSXGitHub KSXGitHub changed the title feat(pacquet): initial implementations of run, exec, dlx feat(pacquet): initial implementations of run, exec, dlx (first attempt, cancelled) May 25, 2026
@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.981 ± 0.053 1.920 2.085 1.00
pacquet@main 1.996 ± 0.069 1.902 2.114 1.01 ± 0.04
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.9812834352000004,
      "stddev": 0.05278884537494395,
      "median": 1.9633570281000001,
      "user": 2.66216174,
      "system": 3.3965612399999996,
      "min": 1.9195345606,
      "max": 2.0853644356000003,
      "times": [
        2.0853644356000003,
        1.9195345606,
        2.0522804026,
        1.9547580846000001,
        1.9514779486,
        1.9865711125999999,
        1.9719559716000001,
        1.9340311856,
        2.0052410906,
        1.9516195595999999
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.9956872933000003,
      "stddev": 0.06878374262261144,
      "median": 1.9876643736,
      "user": 2.6916070399999996,
      "system": 3.35846114,
      "min": 1.9024427486,
      "max": 2.1139741106,
      "times": [
        2.0088593546,
        2.0313635116000004,
        1.9999853805999999,
        1.9565730716,
        1.9336211516,
        1.9753433666,
        2.1139741106,
        1.9024427486,
        1.9408869856,
        2.0938232516000004
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 629.0 ± 7.4 618.5 640.0 1.00
pacquet@main 681.8 ± 16.3 659.4 704.1 1.08 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6290374305800002,
      "stddev": 0.007371107320378157,
      "median": 0.6274271515800001,
      "user": 0.36339484000000005,
      "system": 1.341262,
      "min": 0.6185321635800001,
      "max": 0.63996825158,
      "times": [
        0.63996825158,
        0.63562475058,
        0.62355702258,
        0.6185321635800001,
        0.6291743735800001,
        0.62374945258,
        0.6256799295800001,
        0.62204566658,
        0.63616033358,
        0.63588236158
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.68181125848,
      "stddev": 0.01627301933368877,
      "median": 0.68401312408,
      "user": 0.34634304,
      "system": 1.3513131999999997,
      "min": 0.65939122058,
      "max": 0.70414614858,
      "times": [
        0.7013102815800001,
        0.6738622415800001,
        0.70414614858,
        0.6911575975800001,
        0.66052486058,
        0.65939122058,
        0.69312030558,
        0.6802610115800001,
        0.68776523658,
        0.66657368058
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.244 ± 0.021 2.211 2.271 1.01 ± 0.01
pacquet@main 2.232 ± 0.023 2.209 2.277 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.2440691400999997,
      "stddev": 0.02148456380357905,
      "median": 2.247322178,
      "user": 3.79761966,
      "system": 3.0862005,
      "min": 2.210678561,
      "max": 2.270700014,
      "times": [
        2.2243521669999997,
        2.270700014,
        2.210678561,
        2.2131652749999997,
        2.2432478529999997,
        2.2550487429999997,
        2.263598886,
        2.244707724,
        2.249936632,
        2.2652555459999997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.2316940290000002,
      "stddev": 0.02307338950618227,
      "median": 2.2252294834999997,
      "user": 3.75840276,
      "system": 3.0840873,
      "min": 2.2091202759999997,
      "max": 2.2774080349999997,
      "times": [
        2.224137915,
        2.220771058,
        2.2091202759999997,
        2.2774080349999997,
        2.211382551,
        2.262648886,
        2.2298440829999997,
        2.226321052,
        2.244682223,
        2.210624211
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.394 ± 0.010 1.381 1.411 1.00
pacquet@main 1.399 ± 0.021 1.367 1.445 1.00 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.39414471686,
      "stddev": 0.010431897518691904,
      "median": 1.3925871085600001,
      "user": 1.71496422,
      "system": 1.7985807799999995,
      "min": 1.38091404406,
      "max": 1.41138503106,
      "times": [
        1.3858557470600001,
        1.3939392160600002,
        1.3832826040600001,
        1.38091404406,
        1.41138503106,
        1.38774322406,
        1.39123500106,
        1.39574306706,
        1.40455011406,
        1.40679912006
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.3991401093600002,
      "stddev": 0.020596609156852737,
      "median": 1.39854278156,
      "user": 1.6849664199999999,
      "system": 1.83475278,
      "min": 1.36689646306,
      "max": 1.44467097506,
      "times": [
        1.39809376706,
        1.41095662206,
        1.39805049106,
        1.44467097506,
        1.3830500700600001,
        1.3830373440600001,
        1.39899179606,
        1.36689646306,
        1.40304639006,
        1.40460717506
      ]
    }
  ]
}

@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/11937
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
2,244.07 ms
(-32.63%)Baseline: 3,331.08 ms
3,997.30 ms
(56.14%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,394.14 ms
(-41.91%)Baseline: 2,400.11 ms
2,880.13 ms
(48.41%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
1,981.28 ms
(-7.46%)Baseline: 2,141.04 ms
2,569.24 ms
(77.12%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
629.04 ms
(-4.88%)Baseline: 661.28 ms
793.53 ms
(79.27%)
🐰 View full continuous benchmarking report in Bencher

claude added 3 commits May 25, 2026 18:49
- executor: use a raw string in the POSIX arg-quoter to satisfy the
  perfectionist::prefer-raw-string Dylint rule.
- cli: gate the exec test's assert_cmd prelude import so Windows clippy
  does not flag it as unused.
- cli: canonicalize the dlx cache dir so a relative cacheDir (e.g.
  ../cache) cannot make the install's workspace-root walk pass through
  the caller's project dir; also clear workspace_dir for the throwaway
  install.
- tests: prefer cfg_attr(not(unix), ignore) over cfg(unix) for tests
  that compile cross-platform, per CODE_STYLE_GUIDE.
The error-display and help attribute strings quoted identifiers with
escaped double quotes; switch them to raw string literals so the Dylint
job stops flagging avoidable escapes.
perfectionist::prefer-raw-string fires on backslash escapes; the two
remaining offenders were the printf format and the shell-quote
assertion in the run_script tests, which now compile on the Linux
Dylint runner after the cfg_attr change. Convert both to raw strings;
the string values are unchanged.
@codecov-commenter

codecov-commenter commented May 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.65608% with 87 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.87%. Comparing base (b7229f8) to head (53580da).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
pacquet/crates/cli/src/cli_args/run.rs 79.87% 33 Missing ⚠️
pacquet/crates/cli/src/cli_args/dlx.rs 87.72% 27 Missing ⚠️
pacquet/crates/cli/src/cli_args/exec.rs 72.09% 24 Missing ⚠️
pacquet/crates/config/src/workspace_yaml.rs 83.33% 2 Missing ⚠️
pacquet/crates/cli/src/cli_args.rs 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11937      +/-   ##
==========================================
+ Coverage   87.78%   87.87%   +0.08%     
==========================================
  Files         224      230       +6     
  Lines       27295    28278     +983     
==========================================
+ Hits        23961    24848     +887     
- Misses       3334     3430      +96     

☔ View full report in Codecov by Sentry.
📢 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.

Verified locally with cargo-dylint against the perfectionist library.
Two rules were firing:

- single_letter_closure_param: rename `b`/`m`/`d`/`s`/`e` closure
  params with non-trivial bodies to descriptive names in run/exec/dlx.
- prefer-raw-string: convert the `.contains("...\"...\"")` assertion in
  the run tests to a raw string (the rule flags plain string literals,
  not macro/attribute args).
@KSXGitHub KSXGitHub changed the title feat(pacquet): initial implementations of run, exec, dlx (first attempt, cancelled) feat(pacquet): initial implementations of run, exec, dlx (first attempt) May 26, 2026
…e guide

CONTRIBUTING.md's writing-style section bans mid-sentence em dashes;
restructure the affected doc and line comments so each clause stands on
its own. Also link `create_short_hash` as an intra-doc reference.

Verified the run/exec/dlx tests actually catch regressions by
temporarily breaking each subject (quote_arg, PATH extension,
getSpecifiedScripts, hidden-script filter, printProjectCommands,
makeEnv, the cache-key sort) and confirming the matching test fails,
per plans/TEST_PORTING.md.
KSXGitHub pushed a commit that referenced this pull request May 26, 2026
run:
- Add AllHidden error variant for when every matched script is hidden
  (completes the throw_or_filter_hidden_scripts contract for the future
  regex-selector path)
- Emit pnpm's ELIFECYCLE line to stderr before propagating a failing
  script's exit code, matching pnpm's runLifecycleHook behavior
- Fix init_cwd: use current_dir() rather than pkg_root so INIT_CWD
  reflects the user's invocation directory, not the package root
- Pass user_agent = Some("pnpm") to run_script so npm_config_user_agent
  is stamped in the child environment
- Refactor specified_scripts and render_project_commands to take &Value
  directly (removes the PackageManifest wrapper, enables unit testing)
- Change ALL_LIFECYCLE_SCRIPTS from [&str; 31] to &[&str] slice
- Add 8 unit tests for the private run helpers (specified_scripts,
  throw_or_filter_hidden_scripts, render_project_commands)

dlx:
- Add --allow-build flag; entries are inserted into allow_builds on the
  cache config so lifecycle scripts for those packages run during install
- Include named registries (not just the default) in the cache key,
  matching pnpm's createCacheKey inputs
- Include allow_build list in the cache key so different allow-sets
  get distinct cache directories
- Add dunce::canonicalize after creating the cache dir so a relative
  cacheDir setting cannot confuse the install's workspace-root walk
- Replace the custom symlink_overwrite with pacquet_fs::force_symlink_dir
  and treat its result as best-effort (a racing dlx process may win the
  link; both installs are equally fresh, so run from our own prepare dir)
- Add 4 new cache-key unit tests for the expanded signature

https://claude.ai/code/session_01PPwhryEFfN4iyZkVsjy2Hf
@KSXGitHub KSXGitHub closed this Jun 1, 2026
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.

3 participants