feat(pacquet): initial implementations of run, exec, dlx (first attempt)#11937
feat(pacquet): initial implementations of run, exec, dlx (first attempt)#11937KSXGitHub wants to merge 7 commits into
run, exec, dlx (first attempt)#11937Conversation
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.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 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 |
Micro-Benchmark ResultsLinux |
run, exec, dlxrun, exec, dlx (first attempt, cancelled)
Integrated-Benchmark Report (Linux)Scenario: Isolated linker: fresh restore, cold cache + cold store
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
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
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
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
]
}
]
} |
|
| Branch | pr/11937 |
| 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,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%) |
- 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 Report❌ Patch coverage is 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. 🚀 New features to boost your workflow:
|
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).
run, exec, dlx (first attempt, cancelled)run, exec, dlx (first attempt)
…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.
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
Summary
Ports
run,exec, anddlxfrom the TypeScript pnpm CLI to pacquet (non-recursive).run: reworks the existing stub to run scripts through a new foregroundrun_scriptinpacquet-executor, which sets upnode_modules/.binonPATHand thenpm_*environment via the existingbuild_env/extend_path/select_shell. It also runspre/postscripts underenablePrePostScripts, 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 withnode_modules/.binprepended toPATH(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."execanddlxare 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)
--filter/--recursiveworkspace fan-out.run: the/regex/script selector andverifyDepsBeforeRunare 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 interactiveapprove-buildsprompt is not ported.Test plan
cargo clippy --workspace --all-targetsandcargo fmt --checkare clean.runandexecpass, plusdlxhelper unit tests.dlxend-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).