mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-14 09:42:37 -04:00
ddbb4899c252efabce5bbf4e519df83c07b891bf
12095 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ddbb4899c2 |
feat(pacquet): implement bugs command (#12687)
Port the `pnpm bugs` command to pacquet, following the structure of the TypeScript handler at `pnpm11/deps/inspection/commands/src/bugs/index.ts` and matching its error codes (`ERR_PNPM_NO_BUGS_URL`, `ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND`). The command supports local manifest lookup and registry lookup by package name, with repository URL normalization for GitHub/GitLab/Bitbucket shorthand, hosted git URLs, and self-hosted git servers. Unit tests cover all URL derivation branches (36 tests). Integration tests cover the CLI entry points with local manifests and a mocked registry (9 tests). Related to pnpm/pnpm#11633. |
||
|
|
397f9783f4 |
fix: avoid passing infinite width to cli-truncate when streaming lifecycle output (#12750)
* fix: avoid passing infinite width to cli-truncate when streaming lifecycle output cli-truncate 6.1.0 rejects a non-finite width, which crashed the reporter when streaming lifecycle script output (streamLifecycleOutput passes Infinity to mean "do not truncate"). * chore: drop changeset The broken build this fixes was never released, so no release note is needed. |
||
|
|
b6d29d212d |
chore: update lockfile, Node.js, pnpm, and pacquet versions (#12692)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
f57f5af8bd |
docs(pacquet): remove TypeScript-code references from comments and document the parity paradigm (#12737)
pacquet and the TypeScript pnpm CLI are co-developed parallel implementations at near-complete feature parity, with neither stack downstream of the other. Remove the GitHub permalinks and the "mirrors upstream's X" framing that pointed pacquet's comments at the pnpm TypeScript source, and rewrite the affected prose to describe pacquet's own behavior. This also resolves the dead permalinks the PR originally set out to repair: the links are gone rather than re-pinned, so they cannot rot again. Removed every /blob/ and /tree/ permalink to the pnpm-org TypeScript repos (over 1,700 links across ~330 files) plus the cited TS symbols and "Mirrors/Ports/matches upstream's X" prose. Kept issue/PR/CI references, pnpm/pacquet self-references, third-party attributions, Rust intra-doc links, shared-contract identifiers (ERR_PNPM_* codes, the pnpm-prefixed reporter wire format), and generic pnpm-product mentions. Updated the contributor guides to state the new paradigm: root and pacquet AGENTS.md (cardinal rule rewritten, permalink-citation guidance removed), pacquet CODE_STYLE_GUIDE.md (reporter section reframed around the shared wire contract), pacquet README, and a few plan/task docs. The Rust changes are comment-only except for three #[expect(reason = "...")] attribute strings that themselves cited a TypeScript symbol. |
||
|
|
a6f303c2ff |
chore(rust/dylint): upgrade perfectionist 0.0.0-rc.22 (#12719)
* chore(dylint): upgrade perfectionist to 0.0.0-rc.21
Bump the perfectionist dylint library from 0.0.0-rc.17 to 0.0.0-rc.21 and
migrate the configuration to the rc.21 rule names and semantics.
Renamed config keys (perfectionist renamed every lint to name the
anti-pattern it flags):
- macro_argument_binding -> impure_macro_arguments
- derive_ordering -> unordered_derives
- import_granularity -> import_granularity_mismatch
- unit_test_file_layout -> excessive_inline_tests
Remove three lint suppressions that upstream bug fixes made unnecessary
(each now reported an unfulfilled #[expect]):
- two bare_url suppressions on include_str!-ed fixtures (comment scanners
now ignore include_str!-ed files)
- the import_granularity suppression in resolve_importer tests (the rule
no longer folds a named item into self; the both-module-and-item case
is left unflagged by default)
flat_module_pattern was removed from perfectionist in favor of
clippy::mod_module_files, which this workspace already enables; update
CODE_STYLE_GUIDE.md to match.
Tune the heavy new active-by-default rules for this change:
- needless_borrowed_parameters: disabled pending a parity-reviewed pass
(https://github.com/pnpm/pnpm/issues/12715)
- bare_identifier_reference: narrowed to own-module, multi-word names
(https://github.com/pnpm/pnpm/issues/12716)
- clap_help_markdown: code spans ignored; link leaks still flagged
Simplify the executor test gate now that excessive_inline_tests accepts a
compound #[cfg(all(test, unix))] on an external test module.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTm1H87D62ECyf9nLgPZkZ
* fix(lint): comply with new perfectionist rc.21 rules
Apply the rc.21 rule changes across the Rust workspace and tune the
heaviest new active-by-default rules in dylint.toml.
Code compliance:
- bare_identifier_reference: wrap backticked identifiers that resolve in
the documenting item's own module as intra-doc links (~197 sites). The
rule is narrowed to own-module, multi-word names; widening is tracked in
https://github.com/pnpm/pnpm/issues/12716.
- avoidable_string_escapes: convert escape-only string literals to raw
strings (74 sites).
- allow_attributes: convert #[allow] to #[expect] where the lint
deterministically fires (8 sites). Drop three suppressions whose lint
does not actually fire — a too_many_arguments on a 2-arg fn and two
large_stack_frames blocks already neutralised by Box::pin (the rule
flagged dead #[allow]s under its driver; #[expect] is unfulfilled under
stable clippy).
- wildcard_imports: expand `use super::*` to explicit imports in six test
modules (pacquet-cli, pnpr).
Config (dylint.toml): disable needless_borrowed_parameters
(https://github.com/pnpm/pnpm/issues/12715), unpinned_repo_ref
(https://github.com/pnpm/pnpm/issues/12717), and clap_help_markdown
(https://github.com/pnpm/pnpm/issues/12718) pending dedicated follow-up
passes.
Edits are limited to doc comments, raw-string conversions, lint
attributes, and import lists — no runtime behaviour changes. Verified
locally: cargo dylint, cargo doc, cargo clippy (all -D warnings), taplo
format --check, and the affected crates' unit tests all pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTm1H87D62ECyf9nLgPZkZ
* test(executor): restore the original Unix-only test gate
The rc.21 upgrade moved the gate from a plain `#[cfg(test)] mod tests;`
(with `#![cfg(unix)]` inside the test file) to `#[cfg(all(test, unix))]`
on the module declaration. No lint required that move: rc.21's
`excessive_inline_tests` recognises the external test module regardless
of where the Unix gate sits, so the original form was already clean
(verified with `cargo dylint`). Revert it and trim the now-obsolete
workaround rationale from the comment — the rc.17 `unit_test_file_layout`
compound-`cfg` misclassification it dodged is fixed in rc.21.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTm1H87D62ECyf9nLgPZkZ
* chore(dylint): trim stale-prone comments in dylint.toml
Remove the verbose rationale comment above the `disable` list — its
per-rule counts and narrative would drift out of date; the disabled rules
are tracked in their follow-up issues and the commit history. Shorten the
`bare_identifier_reference` comment to the configured intent plus its
tracking issue, dropping the firing-count and default-behaviour narrative.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTm1H87D62ECyf9nLgPZkZ
* chore(dylint): disable perfectionist bare_identifier_reference
Its autofix blindly rewrites every doc code span into an intra-doc link,
which silently produces false intra-links when the span names an upstream
(TypeScript) type that only shares a name with a local Rust item. The rule
itself is sound, so disable it rather than keep narrowing it until the
autofix is removed or downgraded to MaybeIncorrect.
Keep the one site corrected by hand (graph-hasher dep_state) as a reference
for the right pattern: an explicit reference link to the upstream source
instead of a self-resolving intra-doc link.
Re-enable tracked in https://github.com/pnpm/pnpm/issues/12722.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTm1H87D62ECyf9nLgPZkZ
* chore(executor): drop the doc-comment churn in the test-module gate
The earlier revert rewrote the gate comment in tests.rs instead of
restoring it, leaving a documentation-only diff against the base. Restore
the original comment verbatim so this file is unchanged by the PR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WTm1H87D62ECyf9nLgPZkZ
* docs(pacquet): link false intra-doc links to their upstream source
Several doc comments wrapped an upstream (pnpm TypeScript / yarn) type or
const name in [`Name`], which resolved to a same-named local Rust item — a
false intra-link that reads as "upstream's Name" but points at the port.
Convert each to an explicit reference link to the upstream definition
([`Name`][ts-Name] plus a commit-pinned [ts-Name]: <url> permalink),
sourcing the URL from the doc block's existing citation, the file's
module-level reference, or (for the handful with no in-repo citation) the
upstream definition located at pnpm main
|
||
|
|
c4acfadd86 |
fix(pnpr): stream proxied tarballs instead of buffering-then-verifying (and benchmark the cold pnpr cache) (#12729)
On a cache miss the proxy buffered the whole tarball into the cache, verified its SRI, and only then streamed the finished file — a head-of-line stall on every cold fetch: the client waited for the full upstream fetch plus a verify pass before the first byte, with no overlap. Buffer-then-verify is the wrong model for a streaming proxy. Stream the upstream response to the client while teeing it into the cache and hashing it (stream_verified_to_cache); promote the cache entry only once the full body matches dist.integrity. A mismatched/truncated/oversize body is never cached and the client re-verifies what it receives, so a bad upstream tarball can't poison a later client — it just can't be turned into a BAD_GATEWAY, since SRI is only known after the body is already streamed. Converge the namespaced /~<uplink>/ route onto the same model (the integrity is known from the packument either way), removing the now-dead uplink integrity sidecar machinery. Stop forwarding the upstream Content-Length on streamed responses (attacker-controlled, unverifiable before streaming; kept only for the internal early oversize check). Add a cold-pnpr-cache benchmark scenario that actually exercises the cold download/serve path (every other scenario warms the mock); it runs frozen so resolution variance doesn't mask the serve-path delta. The measured speedup on that scenario is modest and within noise (this fixture is small tarballs over a latency-bound link); the change is a correctness fix, and the benchmark confirms no regression. Out of scope: packuments with no usable dist.integrity (pnpm/pnpm#12727). |
||
|
|
cc04eb174e |
feat(cli): port the bin command to pacquet (#12703)
Port pnpm's `bin` command, which prints the directory where executables are installed. Locally it prints `<dir>/node_modules/.bin`: the `node_modules/.bin` leaf is hardcoded, so a configured `modules-dir` is ignored, and the path is anchored on the already-canonicalized `--dir`, mirroring pnpm's config reader. `--global` resolves `global-bin-dir ?? <pnpm-home>/bin`, creates it, and runs `check_global_bin_dir` (PATH membership + writability, since `globalDirShouldAllowWrite` is true for every command except `root`) before printing — the same `mkdir` + `checkGlobalBinDir` pnpm's config reader performs for every `--global` command. Errors mirror pnpm: `ERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATH`, `ERR_PNPM_NO_PATH_ENV`, `ERR_PNPM_PNPM_DIR_NOT_WRITABLE`, and `ERR_PNPM_NO_GLOBAL_BIN_DIR`. Ported from pnpm's bin.ts and config reader. Related to pnpm/pnpm#11633. |
||
|
|
da9f834e34 |
feat(pacquet-cli): implement pnpm repo command (#12702)
Implement the pnpm repo command in pacquet-cli, which opens a package's repository URL in the browser. Supports local manifest lookup and registry-based lookup with version selection via dist-tags and semver ranges. Handles SSH, git://, git+https, and shorthand repository URL formats, strips .git suffixes, and expands shorthands to proper web URLs. Uses the structured Reporter trait for browser-open error logging. Includes 32 unit tests and 3 integration tests covering all URL formats, error paths, and registry interaction. |
||
|
|
d22f277613 |
feat(pacquet/cli): promote --filter/--filter-prod to recursive mode (#12726)
* feat(pacquet/cli): promote `--filter`/`--filter-prod` to recursive mode
A bare `--filter` / `--filter-prod` (without `-r`/`--recursive`) now puts
the command into recursive mode CLI-wide, matching pnpm's parse-cli-args
promotion. This is the prerequisite for driving release.yml, which runs
`pn publish --filter=<pkg>` without `-r`.
Also ports the `!{<workspace-root>}` augmentation from pnpm's main
dispatch: for run/exec, an all-exclusion (or unfiltered) selection
auto-excludes the workspace root unless the workspace is root-only
(patterns === ['.']). The recursive selection now filters with glob
directory matching (pnpm's useGlobDirFiltering default), which is
load-bearing for that augmentation: it excludes only the project at the
workspace root, not every nested package. pack stays out of the
auto-exclusion command set, matching pnpm.
Refs https://github.com/pnpm/pnpm/issues/12723
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBAmoo35Kr6K96ie7vG8tF
* test(pacquet/cli): cover pack root-inclusion and subdir root-exclusion
Strengthens coverage of the recursive root auto-exclusion, surfaced by review:
- `recursive_pack_includes_workspace_root` pins the
`AutoExcludeRoot::Disabled` contract — a recursive `pack` keeps the
workspace root. This was previously indistinguishable from `Enabled`
because no test set up a root package.
- `recursive_run_from_subdirectory_still_excludes_root` exercises the
non-trivial relative-path branch of the `!{<workspace-root>}`
augmentation (running `-r run` with `--dir packages/<pkg>`), which had
no coverage.
Also documents that pnpm's `--include-workspace-root` / `--workspace-root`
gates are not ported because pacquet surfaces neither flag yet.
Refs https://github.com/pnpm/pnpm/issues/12723
* test(pacquet/cli): cover --filter-prod all-exclusion root drop
Pins the load-bearing `follow_prod_deps_only` flag on the
`!{<workspace-root>}` augmentation, surfaced by review. A
`--filter-prod`-only all-exclusion must route the root exclusion into the
same production-only selection pass as the user's exclusion; otherwise the
union of the prod and non-prod passes re-adds both the workspace root and
the excluded package, silently running the script everywhere.
Refs https://github.com/pnpm/pnpm/issues/12723
* test(pacquet/cli): cover the select_recursive_projects error path
The `select_recursive_projects(...)?` in the recursive `run` / `exec`
runners propagates the filter engine's error, which a coverage report
flagged as uncovered. A `[<since>]` changed-packages selector is rejected
with `UnsupportedDiffSelector` (pacquet has not ported git-diff project
selection), and that error travels out through the `?`. Add integration
tests that pass `--filter [main]` to recursive `run` / `exec` and assert
the error surfaces (non-zero exit plus the explanatory message) rather
than being swallowed.
A real fixture (a `[<since>]` selector string) reaches this branch, so per
pacquet/AGENTS.md these are plain integration tests, not the dependency-
injection seam (which is reserved for branches a real fixture cannot
reach, e.g. filesystem error kinds or time).
Refs https://github.com/pnpm/pnpm/issues/12723
* test(pacquet/cli): assert recursive run/exec don't dispatch on a rejected selector
Strengthen the `[<since>]` diff-selector regression tests so they prove the
command short-circuits before dispatch, not merely that stderr names the
error. `run` asserts the build script's `ran.txt` marker is absent, and
`exec` now runs a marker-writing command (`touch ran.txt`) and asserts the
marker is absent. Per REVIEW_GUIDE.md, regression assertions must observe
the changed behavior rather than only the error message.
Refs https://github.com/pnpm/pnpm/issues/12723
* style(pacquet/cli): drop call-site comment duplicating the callee doc
The comment above `promote_recursive_for_filter()` in main restated the
function's own doc comment clause-for-clause (the recursive promotion, the
`parse-cli-args` parity, and the place-once-before-dispatch rationale all
already live on the `///`). Per the repo comment convention, a call site
must not re-explain what the callee documents; the method name carries the
intent and the doc comment carries the why.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
|
||
|
|
a6f04d4ae5 |
perf(pnpr): restore cold-store install speed on the proxy tarball serve path (#12709)
pnpm/pnpm#12570 made every tarball serve — warm cache hits included — both re-hash the cached bytes against dist.integrity and load+parse the packument to bind the request to a version. The benchmark mock is pnpr and a cold-store install pulls ~1300 tarballs through it, so both costs are paid per tarball: together the cold-store regression from ~2.15s to ~3.15s on the Bencher pnpr testbed (pnpm/pnpm#12700 recovered only the resolution-cache part). Both are redundant on a cache hit. A tarball only enters the proxy cache via download_verified_to_cache, which resolves the version and verifies the bytes against dist.integrity as they are written, so the cache only ever holds correct bytes for a (name, filename); the install client re-verifies whatever it receives anyway. Serve cached bytes directly and defer the packument load to the cache-miss download path, which still verifies freshly-fetched bytes before caching them. The OSV screen on the filename version still runs first. Write-time verification is preserved, so GHSA-5f9g-98vq-2jxw stays mitigated — the bytes that enter the cache are still verified, which the pre-regression serve path did not even do. Drops the now-dead per-serve integrity sidecar and helpers. |
||
|
|
6fadd7def9 |
feat(pacquet): port @pnpm/network.web-auth (#12569)
* feat(network-web-auth): port web-based authentication flow from pnpm Port the `@pnpm/network.web-auth` package to a new `pacquet-network-web-auth` crate, in preparation for the registry-auth commands (login / publish) that pacquet has not ported yet. The crate provides: - `poll_for_web_auth_token` — polls a registry "done" URL for an auth token, honoring `Retry-After` and an overall timeout (`ERR_PNPM_WEBAUTH_TIMEOUT`). - `with_otp_handling` — runs an operation and, on an EOTP challenge, drives either the web-auth flow (QR code + browser open + poll) or a classic OTP prompt, then retries once. Surfaces `ERR_PNPM_OTP_NON_INTERACTIVE` and `ERR_PNPM_OTP_SECOND_CHALLENGE`. - `prompt_browser_open`, `generate_qr_code`, and the `SyntheticOtpError` helpers. Rather than porting the TypeScript context-of-closures verbatim, every side effect is a self-less capability trait composed on a single `Sys` parameter (`Clock`, `Sleep`, `WebAuthFetch`, `OpenUrl`, `EnterKeyListener`, `PromptOtp`, plus the TTY probes), with the real OS behind `Host` and fn-bound unit-struct fakes in tests. User-facing messages flow through the `Reporter` seam on a new `pnpm:global` channel, matching pnpm's `globalInfo` / `globalWarn` and rendered by `pacquet-default-reporter`. New workspace dependencies: `qrcode` (no default features) and `open`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWejcxTU8n144a1KK4nRj8 * style(network-web-auth): satisfy perfectionist dylint lints CI's dylint job flagged the new crate against the `perfectionist` lint library (which clippy does not run). Fix every finding: - Reorder derive lists to the configured `prefix_then_alphabetical` order (`Debug, Default, Clone, ...`). - Rename the single-letter `R` reporter generic to `Reporter`, bound as `self::Reporter`, matching the existing pacquet convention. - Move the inline `mod tests { ... }` blocks for `capabilities`, `generate_qr_code`, and `web_auth_timeout_error` into external `tests.rs` files (the crate's `unit_test_file_layout` is `external_only`). - Add trailing commas to multi-line macro invocations. No behavior change. Verified clean with the same command CI runs: `RUSTFLAGS='-D warnings' cargo dylint --all -- --all-targets --workspace`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWejcxTU8n144a1KK4nRj8 * fix(network-web-auth): cancel the web-auth Enter listener on drop The production `EnterKeyListener` used `spawn_blocking` + a blocking `stdin().read_line()`, which cannot be cancelled: if the user never pressed Enter (e.g. authentication completed via a phone QR scan), the reader thread lingered until process exit. Replace it with a dedicated thread that polls stdin with a 100ms timeout via `crossterm::event::poll` and re-checks a cancel flag each tick, so dropping the handle stops the thread within one poll interval. `crossterm` reads in the terminal's default (cooked) mode — no raw mode — matching pnpm's plain `readline.createInterface({ input: process.stdin })`, which reacts to a submitted line rather than individual keypresses. In Node, stdin is event-loop-driven so `rl.close()` needs no thread to cancel; the poll loop is the Rust equivalent. Adds `crossterm` to `[workspace.dependencies]`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWejcxTU8n144a1KK4nRj8 * fix(network-web-auth): don't let a dropped Enter listener steal stdin A code review of the cancellable web-auth listener surfaced two items: - The polling reader thread could consume one keystroke in the window between the handle being dropped (cancel flag set) and the thread noticing it: `event::poll` reports input, then `event::read` swallows a key meant for whatever reads stdin next. Re-check the cancel flag after poll and before read so a dropped handle leaves the input buffered, matching pnpm's clean `readline` close. - Drop the redundant `Option<sender>`: the Enter arm returns right after sending, so the oneshot sender moves directly into `send()`. Also document that the handle is meant to be raced and never resolves on a stdin read error, so it must not be awaited on its own. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWejcxTU8n144a1KK4nRj8 * docs(network-web-auth): de-duplicate Enter-listener comments Review follow-up: keep the polling/cancel rationale on `HostEnterHandle` (where `Drop` lives) and trim `listen`'s doc to its unique cooked-mode detail; drop a select-arm comment sentence that restated automatic `Drop` and the function's own doc comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWejcxTU8n144a1KK4nRj8 * docs(network-web-auth): correct the Enter-listener cancellation rationale Round-3 review follow-up: - The `HostEnterHandle` doc blamed `std`'s blocking `read_line` for needing a poll loop, but the listener uses crossterm's `event::read()` (`read_line` was the pre-crossterm implementation). Name the real primitive. - Drop the `listen` doc's reference to the private `HostEnterHandle` type (a public method's docs must not name a more-private item); point to "the returned handle" instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWejcxTU8n144a1KK4nRj8 * docs(network-web-auth): state the why in the clock test doc The doc comment on host_clock_reads_a_non_zero_time restated the test name and its assertion instead of the non-obvious reason behind it. Replace it with the rationale: 0 is Host::now_ms's pre-epoch fallback, so a non-zero read proves the real wall clock was queried. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
4cd45a3e15 |
feat(pacquet/cli): apply --filter selection in recursive run and exec (#12712)
* feat(pacquet-cli): apply --filter selection in recursive run and exec The recursive `run` / `exec` command path operated on every workspace project; the `--filter` / `--filter-prod` selectors were parsed into `Config::filter` / `Config::filter_prod` but never narrowed the set. Add `select_recursive_projects`, a shared helper that builds the workspace graph over all projects and restricts it to the selectors' selection — include selectors unioned, `!`-prefixed excludes subtracted — mirroring the `selectedProjectsGraph` pnpm hands its recursive handlers in main.ts. With no selectors every project is selected, so the unfiltered behavior is unchanged. The recursive `run` no-script error now distinguishes "None of the packages" from "None of the selected packages" the way pnpm does, keyed on whether the filter narrowed the set. Refs https://github.com/pnpm/pnpm/issues/12711 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgEs4dECzHdR3aj19DLxiD * fix(pacquet-cli): no-op recursive run/exec on an empty --filter selection Address review feedback on the --filter wiring: - When --filter narrows a non-empty workspace to no projects, recursive run/exec now exit 0 instead of erroring on --resume-from or writing an empty --report-summary summary, matching pnpm's main-dispatch exit-0 for an empty selectedProjectsGraph (main.ts). - Resolve the selectors against the same graph options used for the topological sort, so the selected set and the run order agree on edges (upstream reads one linkWorkspacePackages-aware graph for both). Empty-workspace handling (recursive run -> no-script error, exec -> NO_PACKAGE) is left as-is; it predates --filter and is orthogonal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgEs4dECzHdR3aj19DLxiD * feat(pacquet-cli): apply --filter to recursive pack via the shared path Route `pack -r`'s workspace sweep through the shared `select_recursive_projects` helper that `run -r` / `exec -r` use, so `pack -r --filter` narrows which projects are packed instead of silently packing every project. Keeps `--filter` a single cross-cutting selection path across all recursive commands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgEs4dECzHdR3aj19DLxiD * test(pacquet-cli): cover the --filter-prod selector path in recursive run Add a recursive-run integration test that narrows the workspace with `--filter-prod`, exercising the `follow_prod_deps_only: true` branch of `select_recursive_projects` that the `--filter`-only tests never reached (flagged uncovered by Codecov). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgEs4dECzHdR3aj19DLxiD * docs(pacquet-cli): trim verbose comments in the recursive filter path Per review, make the comments added for `--filter` selection succinct: keep the non-obvious "why", drop restatement of self-documenting code and narration of pnpm's behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgEs4dECzHdR3aj19DLxiD * docs(pacquet-cli): correct stale "every project" recursive doc comments The recursive `run` / `exec` / `pack` function- and module-level doc comments still said the command runs over "every workspace project"; with `--filter` wired in they run over the selected subset. Update them to match the code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgEs4dECzHdR3aj19DLxiD * docs(pacquet-cli): correct remaining "every project" recursive dispatcher docs Follow-up: the `RunArgs::run_recursive` / `ExecArgs::run_recursive` dispatcher docs and the `PackArgs::run` summary still said the recursive command runs over "every project"; with `--filter` wired in they run over the selected subset. The global `--recursive` flag help is left as-is — it describes `-r`'s workspace-wide-vs-single behavior, and `--filter` narrowing is a separate flag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgEs4dECzHdR3aj19DLxiD * test(pacquet-cli): prove --filter-prod prod-only walk in recursive run The flat-workspace fixture passed even if `follow_prod_deps_only` were ignored: with no dependency edges, `--filter-prod app` and `--filter app` select the same set, so the test could not tell them apart. Replace it with a dev-only edge (`app` devDependencies `lib`) and the `app...` ellipsis selector, so `--filter-prod`'s production-only dependency walk must drop the dev edge. The test runs `app` and asserts `lib` is skipped, failing if `--filter-prod` collapses to `--filter`. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
4dabe4c3d1 |
ci(benchmark): double the precompile-revisions timeout (#12713)
Bump the "Precompile benchmark revisions" step from 25 to 50 minutes; the superset build of pacquet + pnpr revisions has been hitting the limit. Claude-Session: https://claude.ai/code/session_01KtBQzmLLDU3RcGzzCMopPB Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
517ea5d510 |
chore(cargo): bump getrandom from 0.4.2 to 0.4.3 (#12683)
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.4.2 to 0.4.3. - [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-random/getrandom/compare/v0.4.2...v0.4.3) --- updated-dependencies: - dependency-name: getrandom dependency-version: 0.4.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
99af755b6e | docs: pacquet has ported nearly all commands, so mirror every user-visible change (#12708) | ||
|
|
1dd12bd515 |
feat(pnpr): make resolver cache authorization-aware (#12700)
Make the pnpr resolver cache authorization-aware and route private dependencies through per-uplink registry endpoints. Add route classification at the single fetch/auth-selection point: pnpr selects its own server-owned upstream credentials instead of forwarding client upstream auth, records a per-resolve footprint, and rejects inline URL credentials. A uplinks: entry that declares an access: policy becomes the private-route credential (folding in the former upstreamAliases block), matched by registry origin and exposed as a read-only registry endpoint at /~<uplink>/. access reuses bearer-token-backed pnpr identities, package access policy, and static groups; a SHA-256 digest of the uplink's credential participates in the footprint, so rotating the upstream credential automatically moves new resolves to a fresh namespace. The credential is attached only over a matching scheme (no token over plain http). Gate every server-side fetch behind a default-deny allowlist (built-in npm host, public routes, configured uplinks and their /~<uplink>/ endpoints, and pnpr itself). A registry/namedRegistries matching none is rejected at the request boundary, closing the resolver's SSRF surface at the source and superseding the link-local denylist. The boundary also covers direct-URL (http(s)/git, incl. scp-style) dependency specs, overrides, and lockfile tarballs; a `..` path segment is rejected; and the same allowlist re-validates every redirect hop. The official npm registry is a built-in host-level public route (scoped names included), so the npmjsPublic toggle is gone. With no off-allowlist route to resolve, every route is public or carries a private descriptor: RouteClass::Unknown, the non-shareable footprint tier, and the MetadataCacheScope::Bypass tier are removed. Transitive deps fetched during the tree walk are a connect-time-guard follow-up (pnpm/pnpm#12705). Route resolver tarball URLs through those endpoints. A proxied route emits its /~<uplink>/ endpoint URL; public routes keep their upstream URL (fetched directly from the registry/CDN); pnpr-hosted packages use pnpr-hosted URLs. An endpoint URL is canonical for a client whose scope points there, so the lockfile entry stays integrity-only and the host comes from the client's registry config rather than the lockfile — a project resolves to the same lockfile through /resolve or a direct/proxied install. verification_lockfile reverses endpoint URLs to upstream for input-lockfile verification, and classification recognizes pnpr's own /~<uplink>/ URLs. The opaque per-tarball gateway scheme and its in-memory key->URL map are removed. Serve each access-bearing uplink as a /~<uplink>/ registry endpoint (packument + tarball, gated by the uplink access policy) with a private cache namespaced by an HMAC of (uplink, credential), so a private install caches like a public one, a rotation re-keys automatically, and a private uplink's content never lands in the shared mirror. Store bounded candidate lists under an auth-excluded base key instead of a single lockfile. Public candidates match every caller; private candidates carry the footprint and descriptor HMAC and are reused only when the caller still satisfies the stored uplink-or-hosted gates. Compute the base key for both no-lockfile and lockfile-seeded requests, using hash_lockfile() for input lockfiles, and evict expired/LRU private candidates before public ones. Record metadata fast-path routes into the footprint. The hook only fires at the auth-selection point, which the npm resolver's metadata fast paths (in-memory hit, offline disk read, version-spec exact match, publishedBy mtime shortcut) bypass. AuthHeaders::record_route drives the hook without a request, called up front in pick_package so every layer contributes to the footprint. Scope the npm metadata mirror by private access descriptor. A MetadataCacheScope (Public / Private) classified per (registry, package) fetch threads through pick_package, fetch_full_metadata_cached, the mirror path, in-memory/fetch-lock keys, and the verifier's local-mirror read. A private route stores its packument under v11/metadata-private/<descriptor-id>/. Fail closed on 401/403/private-404. The CLI installs no hook, so every fetch stays Public and the global mirror is unchanged. Related to pnpm/pnpm#12699 and pnpm/rfcs#11. |
||
|
|
c00400da70 |
feat(pacquet-cli): port the with command (#12696)
Port pnpm's `with` command to pacquet. `pnpm with <version> <args...>` resolves the spec through the trusted package-manager bootstrap registry, installs that pnpm engine into the global virtual store (matching pnpm's installPnpmToStore — shared across invocations and, unlike self-update, not registered in the global packages directory), and spawns it with the remaining args and the child's packageManager check disabled. `pnpm with current <args...>` is rewritten before clap parses argv into a direct in-process dispatch of `<args>`, forcing pmOnFail to ignore via the pnpm_config_pm_on_fail env var. Wire the pmOnFail setting into Config and the env overlay so the override is honored once the packageManager check is ported. Reuse the self-update engine installer (now global-virtual-store aware) and the engine-identity verifier rather than duplicating them. The end-to-end `with <version>` download path has no automated test because the mock registry does not serve the pnpm / `@pnpm/exe` engine packages, the same limitation self-update has. |
||
|
|
66c531e382 |
feat(pacquet): logout (#12568)
Port `pnpm logout` to pacquet as a new pacquet-auth-commands crate and a `pacquet logout [--registry <url>]` CLI command, mirroring the TypeScript @pnpm/auth.commands behavior, flags, error codes, and messages. The command revokes the registry auth token (DELETE -/user/token/<token>) and removes it from auth.ini. It warns when the token lives in .npmrc or another source pnpm does not own, and fails only when the registry rejects revocation and no local token is left to remove. Upstream wires side effects through a LogoutContext object of functions; this port uses pacquet's capability-trait DI seam instead: FsReadToString / FsWrite / RevokeToken on Sys (production provider Host, test fakes are unit structs), and the global* log channels through the Reporter seam. Config gains auth_tokens_by_uri (raw _authToken per nerf-darted registry URI) and config_dir, since Config previously baked credentials into Authorization headers and discarded the raw token that logout must revoke. Tests: all 13 upstream logout.test.ts cases ported as DI unit tests, plus real-fixture integration tests (the Host provider via TempDir + mockito, and a binary test spawning `pacquet logout`) covering the production code paths the DI fakes bypass. Part of the Rust Roadmap (pnpm/pnpm#11633). --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
e96c2ef229 |
feat(pacquet-cli): port the setup command (#12697)
Port pnpm's `setup` command to pacquet, making `pnpm` available for global use. Mirrors pnpm11/engine/pm/commands/src/setup/setup.ts and the `@pnpm/os.env.path-extender` package family it depends on. The command installs the CLI into the global packages directory (spawning `<exe> add -g --ignore-scripts file:<execDir>` with a temporary `@pnpm/exe` package.json, as pnpm does), writes the `pn` / `pnpx` / `pnx` alias scripts into `$PNPM_HOME/bin`, and persists `PNPM_HOME` plus `$PNPM_HOME/bin` into the user's environment — the current shell's rc file on POSIX (bash/zsh/ksh/dash/sh/fish/nushell) or the registry on Windows. The path-extender is inlined as a private module under the command. The shell rc-block editing and the Windows `reg query` parsing use plain string operations rather than a regex dependency. Error codes mirror pnpm's PnpmError codes, including the `--force` hints. Unit tests port the upstream path-extender-posix spec cases and cover the Windows registry parser/renderer and the output rendering. |
||
|
|
f8e5a0de8c |
feat(pacquet-cli): port the self-update command (#12693)
Ports pnpm's self-update command to the Rust pacquet CLI. Resolves the target version from the trusted package-manager bootstrap registry (honoring minimumReleaseAge / trustPolicy, strict resolution failing closed), guards against corepack, refuses to downgrade on an implicit `latest`, and emits the major-upgrade migration hint. When the project pins pnpm via packageManager / devEngines.packageManager, the pin is updated in place (range-style preserved, integrities persisted). Otherwise the engine is installed into the global packages directory: integrities are resolved into the env lockfile, the engine's npm registry signature is verified against npm's embedded public keys, the host's native platform binary is hard-linked into the wrapper (the preinstall is skipped because the engine installs with scripts disabled), and the bins are linked into the global bin directory with a cache-keyed hash symlink. New modules: self_update/install_pnpm (installPnpm + linkExePlatformBinary) and self_update/verify_engine (verifyPnpmEngineIdentity). config_deps gains resolve_pnpm_version, the policy-aware version probe. |
||
|
|
4e18e44e9b |
refactor(pacquet/cli): split cli_args.rs (#12690)
cli_args.rs had grown to ~1500 lines mixing the CLI argument types, the command-dispatch match, the install/deploy/dedupe/prune pipelines, reporter wiring, and packageManager/devEngines parsing — a frequent merge-conflict hotspot. Split it into focused submodules (cli_command, dispatch, pipelines, package_manager, reporter), leaving cli_args.rs as a thin module index. Then split the ~600-line dispatch match: dispatch.rs keeps a RunCtx context and a thin route() match, and the per-command handler bodies move into three modules grouped by behavior — dispatch_install (mutate the install graph), dispatch_query (read-only queries), and dispatch_script (package.json scripts). The refactor is behavior-preserving (visibility widenings, import collapsing for the import-granularity dylint lint, and a `+ Sync` bound on ApproveBuildsArgs::prepare's closures so the shared config/state closures keep the async handler futures Send). Two pre-existing pnpm-parity gaps surfaced by review are also fixed: the `i` alias for install, and scope-aware packageManager parsing. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
687e92b68a |
chore(cargo): bump tower-http from 0.6.11 to 0.7.0 (#12685)
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.6.11 to 0.7.0. - [Release notes](https://github.com/tower-rs/tower-http/releases) - [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.6.11...tower-http-0.7.0) --- updated-dependencies: - dependency-name: tower-http dependency-version: 0.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
d5fdeff3d1 |
chore(cargo): bump bytes from 1.11.1 to 1.12.0 (#12684)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.11.1 to 1.12.0. - [Release notes](https://github.com/tokio-rs/bytes/releases) - [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/bytes/compare/v1.11.1...v1.12.0) --- updated-dependencies: - dependency-name: bytes dependency-version: 1.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
de3ee84106 |
fix(pnpr): cap distinct interned configs to bound resolver memory (#12674)
The resolve and verify-lockfile endpoints intern one `&'static Config` per distinct client configuration, keyed by the canonical JSON of the caller-supplied registry, namedRegistries, overrides, and verification policy. Each config is leaked because pacquet's install path requires a `&'static Config`, and the cache was unbounded. An authenticated caller could exhaust the server's memory by sending a request with a fresh registry/policy combination every time: each distinct key leaks another config that can never be reclaimed. Cap the cache at MAX_INTERNED_CONFIGS (1024, matching MAX_RESOLUTION_CACHE_ENTRIES); past the cap, both endpoints return 503 instead of interning more. Eviction is not an option — a leaked config can't be freed, and removing its key would just let it be re-leaked, so a hard cap is the only real bound. Reclaiming memory instead would require threading Arc<Config> through pacquet's install path in place of `&'static Config`, which is out of scope. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6bd7ba8adb |
feat(pacquet-cli): implement pnpm docs / pnpm home command (#12671)
This change adds a DocsArgs struct registered as the Docs variant on CliCommand, visible as both docs and home through clap's visible_alias. At runtime the command fetches package metadata from the configured registry, extracts the homepage field (validated as an http/https URL), and opens it via the platform browser launcher: xdg-open on Linux, open on macOS, cmd /c start on Windows. When the homepage is missing or not an http/https URL, it falls back to https://npmx.dev/package/<name>. When the browser cannot be launched (non-interactive CI, unsupported platform), the URL is printed to stdout instead. The is_http_url helper validates URL strings and is unit-tested for valid https, http, empty, non-url, ftp, and spaced inputs. Integration tests verify that omitting the package name produces the expected clap error, that the home alias routes correctly, and that help output is generated. The url crate was added as a dependency of pacquet-cli, already available in the workspace dependencies. Related to #11633. |
||
|
|
8b1b537493 |
fix(lockfile): match pnpm's __ separator for nested peer-group boundaries (#12658)
`to_virtual_store_name` removed all `)` instead of only stripping the trailing one and replacing inner `)` with `_`. For nested peers like `(eslint@9.35.0(jiti@2.6.1))(typescript@6.0.3)`, the inner `)` closing the nested group was deleted rather than becoming `_`, losing the `__` that pnpm produces at the group boundary. Fixes https://github.com/pnpm/pnpm/issues/12452 |
||
|
|
96da7c557d |
fix: pack node-gyp's gyp entrypoints as executable (#12652)
gyp's make generator execs gyp/gyp_main.py and gyp/gyp directly through their shebangs when it regenerates the Makefile during a from-source native build. They were missing from publishConfig.executableFiles, so they got packed at 0644 and the build failed with "Permission denied" (make error 126). pnpm/pnpm#11485 fixed the node-gyp bin shims but not these two. Add them to PUBLISH_EXECUTABLE_FILES so the pnpm and `@pnpm/exe` tarballs pack them at 0755. Closes pnpm/pnpm#12455 |
||
|
|
a33eeec9cd |
feat(pnpm): publish pnpm v12 (the Rust port) as pnpm and @pnpm/exe (#12670)
Make the pacquet Rust port installable from npm as pnpm v12, published with equal content under two names — `pnpm` and `@pnpm/exe` — on the `next-12` dist-tag (never `latest`, which keeps serving the production TypeScript pnpm v11). First release is 12.0.0-alpha.0. CLI presents as pnpm: clap name/bin_name are `pnpm`; PACQUET_VERSION holds the pnpm 12.x version; the default User-Agent is the plain `pnpm/<version>` form; the reporter footer reads `Done in ... using pnpm v<version>`; the `pnpm link` / `pnpm patch` / `pnpm patch-commit` hints now name pnpm (parity fixes); and launched as `pnpx`/`pnx`, the binary injects the `dlx` subcommand itself by detecting its current_exe name (porting pnpm's buildArgv). npm packaging mirrors how `@pnpm/exe` ships pnpm: both the `pnpm` and `@pnpm/exe` wrappers ship root-level placeholder bins (pnpm/pn/pnpx/pnx) plus an install.js preinstall that hardlinks the host's native binary over the placeholder (no Node launcher). Native binaries publish as `@pnpm/exe.<platform>-<arch>[-musl]` (file `pnpm` inside). `pacquet`/`@pnpm/pacquet` are no longer published. installPnpm initializes v12 exactly like `@pnpm/exe`: linkExePlatformBinary takes the wrapper name and resolves the native binary via require, and PNPM_ALLOW_BUILDS includes both names so the GVS hash is per-platform. resolvePackageManagerIntegrities already resolves both `pnpm` and `@pnpm/exe`, so publishing `@pnpm/exe@12` makes self-update / version-switch work with no v12-specific logic. From v12 the install always uses the `pnpm` package (even from a `@pnpm/exe` build), via pnpmPackageNameToInstall(). The configDependencies install-engine path still installs the published `pacquet`/`@pnpm/pacquet` releases (which ship `@pacquet/<target>` binaries) and is left untouched. |
||
|
|
103b99bdb4 |
perf(npm): ship pacquet and pnpr as placeholder bins relinked to the native binary on install (#12507)
The published pacquet/@pnpm/pacquet and @pnpm/pnpr bins were #!/usr/bin/env node launchers that spawned the real native binary, so every invocation paid full Node startup (~170ms) on top of the ~30ms the binary needs. Ship the native binary as the bin instead, mirroring @pnpm/exe. bin/pacquet and bin/pnpr are published as non-executable placeholders, and an install.js preinstall replaces the placeholder with the platform native binary (hard link, copy fallback across filesystems, atomic temp-file + rename). On Windows it adds a .exe twin, overwrites the original-name file the existing shim points at, and repoints bin at the .exe (npm doesn't re-read package.json after preinstall). Binary selection resolves whichever @pacquet/* / @pnpm/pnpr.* package the package manager installed; the guarded process.report glibc probe only orders the linux glibc/musl pair for the --force both-installed case. A placeholder (not a launcher overwritten in place) is required because on Windows the shim is generated from the launcher's node shebang and would keep calling node; a placeholder has no shebang so the generated shim runs the file directly. Trade-off: no Node-launcher fallback. With build scripts blocked (--ignore-scripts, pnpm/Bun default) the bin stays a placeholder until allow-listed. The configDependencies install-engine path is unaffected: pnpm resolves and spawns the @pacquet/<platform>-<arch> native binary directly, never bin/pacquet. A stale comment in runPacquet.ts is corrected to match. |
||
|
|
b98cbf8d89 |
chore: update lockfile, Node.js, pnpm, and pacquet versions (#12680)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
6beda88be8 | ci: pin oz-agent-action to the exact tag for zizmor's version check (#12678) | ||
|
|
aea6aceb68 |
feat(pacquet-cli): add dynamic completion server (#12661)
* feat: add pacquet completion shell parser * test: cover completion shell precedence * test: cover completion diagnostics * feat: generate pacquet completion scripts * fix: hide pacquet completion parser plumbing * test: suggest pacquet completion shells * feat: add dynamic pacquet completion server * fix(pacquet-cli): handle completion server edge cases |
||
|
|
f38e696da7 |
feat(pacquet): port deploy command (#12668)
Port the existing TypeScript deploy command to pacquet. The new pacquet deploy command validates workspace selection and target directory rules, copies the selected project using the package packlist by default, generates deploy-local manifest and lockfile files for shared-lockfile deploys, and runs the pacquet install engine in deploy mode. This also wires deploy config defaults and config overrides, keeps install root discovery configurable so generated deploy lockfiles are resolved from the deploy directory, and adds integration coverage for shared-lockfile deploys, legacy deploys, and non-empty target validation. |
||
|
|
ec76ca3cf3 |
feat(pacquet): port the "ping" command (#12666)
Add `pacquet ping`, ported from pnpm's ping command.
`ping` GETs `<registry>-/ping?write=true` and prints pnpm's PING/PONG
report, plus a pretty-printed `PONG <details>` line when the registry
returns a non-empty JSON body. It resolves the registry from `--registry`
or the configured default, adds a trailing slash before joining so a
registry path prefix is preserved (matching pnpm's
`new URL('./-/ping', ...)`), sends the optional `Authorization` header when
one is configured (unlike whoami it does not require auth), and issues a
single attempt with no retries (pnpm's `retry: { retries: 0 }`). Timing
wraps the request and body read, matching pnpm's `Date.now()` measurement.
Both a transport failure and a non-success status map to the single
`ERR_PNPM_PING_ERROR` code with pnpm's "Failed to reach registry" message;
the transport error message is credential-redacted before it reaches
stderr / CI logs, the same precaution as whoami. Unlike whoami's username
(printed raw, hence sanitized), ping's JSON details are re-serialized
through serde_json, which escapes control characters, so no raw registry
bytes reach the terminal.
Extract the registry-client builder duplicated from whoami into a shared
`cli_args::registry_client::build_registry_client` and switch whoami to it.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
|
||
|
|
2c7369dbab |
feat(pacquet-cli): port the pack-app command (#12669)
Ports pnpm's `pack-app` command into pacquet. It packs a CommonJS entry file into a standalone executable for one or more target platforms, embedding a Node.js binary through the Node.js Single Executable Applications API. Reuses pacquet's node-version resolution, network client, detect-libc, and `node@runtime:` installs. Two intentional divergences from pnpm, both forced by pacquet being a Rust binary rather than a Node.js script: the SEA builder is always downloaded (pnpm reuses its running interpreter), and the runtime install spawns the pacquet binary (mirroring pnpm's runPnpmCli). Ports the upstream Jest validation suite (17 tests) covering the fail-fast branches. The download / SEA-injection / signing paths need network, Node.js, and subprocesses and are out of unit-test scope, matching upstream coverage. Ported from pnpm/pnpm@9f3df6b9b4. |
||
|
|
6312916595 |
feat(pacquet-cli): port the config command (#12672)
Port pnpm's `config` command (set, get, delete, list; alias `c`) from `@pnpm/config.commands`, matching its file routing, key validation, value casting, and output. Supporting infrastructure ported into `pacquet-config`: - `config_types`: the merged `types` registry plus `is_ini_config_key` / `is_config_file_key` predicates. - `naming_cases`, `property_path`, `protected_settings`. - `Config::explicit_settings` / `raw_auth_config` / `config_dir` as the stand-ins for pnpm's `explicitlySetKeys` / `authConfig` / `configDir`, populated while loading the merged config. `pacquet-workspace-manifest-writer` gains a generic, format-preserving `update_manifest_field` (scalar/object set + delete), and a small INI read-modify-write helper backs `.npmrc` / `auth.ini` writes. |
||
|
|
3fe10ac744 |
ci: add Oz cloud triage workflow for new issues (#12673)
* ci: add Oz cloud triage workflow for new issues Wire Warp's Oz "cloud factory" issue-triage stage onto pnpm/pnpm as a GitHub-triggered cloud agent (part of the pnpm x Warp OSS partnership). - .github/workflows/triage-issues.yml runs on issues:[opened], skips Bot-authored issues, and invokes warpdotdev/oz-agent-action@v1 with the triage skill. Minimal permissions: contents:read, issues:write. - .agents/skills/triage/SKILL.md adapts the canonical triage rubric to pnpm: it maps the four readiness states onto existing pnpm "state:" labels (accepted / needs design / needs steps to repro / blocked / rejected) instead of creating new ones, is aware of the TypeScript/pacquet/pnpr split and the install/add/update/remove parity rule, and is conservative about removing maintainer-applied labels. Triage-only for now; the implementation/auto-PR stage is intentionally not included. Activation also requires the WARP_API_KEY secret and the Oz by Warp GitHub App with the org enabled in Warp's Admin Panel. * ci: address CodeRabbit review on Oz triage PR - Pin actions to commit SHAs (actions/checkout to the repo's v7.0.0 pin, warpdotdev/oz-agent-action to the v1 tag commit) with version comments, matching the repo convention and satisfying zizmor. - Harden the triage skill against prompt injection: treat all fetched issue content (title, body, comments, attachments, linked docs) as untrusted data, never as instructions, and forbid actions beyond the defined read/label/comment steps. - Make the non-bug "Needs info" path consistent across the decision rule, the label-application step, and the result template: apply no state label and post clarifying questions instead of forcing an ill-fitting label. |
||
|
|
3d7d2d17e7 |
feat(pacquet): port dist-tag command (#12667)
Port the native dist-tag command to pacquet so the Rust CLI can manage package distribution tags without delegating to another client. The new command mirrors pnpm's TypeScript behavior for subcommand dispatch, default list handling, exact semver validation on add, latest-tag removal refusal, scoped registry/auth selection, dist-tag URL encoding, OTP headers, and sorted list output. Add spawned-CLI integration coverage against a mock registry for list/add/remove behavior, alias handling, registry override, scoped auth, empty output, versioned package specs, and error cases. |
||
|
|
af5827376b |
feat(pacquet-cli): add list, ls, ll, and la commands for local and global package inspection (#12664)
* feat(pacquet-cli): add list, ls, ll, and la commands for local and global package inspection * chore(pacquet-cli): split list tests into separate test file * fix(pacquet-cli): align list with pnpm flags, filtering, peers, and error handling - Changed --production to --prod (matching pnpm CLI surface) - lockfile_only now branches between current/wanted lockfile - Selects correct workspace importer instead of always root_project() - Filters params after full tree build, preserving transitive matches - Walks optional_dependencies in snapshot traversal - Skips peer DepNode push when --exclude-peers is set - Propagates read_root_manifest errors instead of swallowing --- Written by an agent (opencode, big-pickle). * fix(pacquet-cli): address dylint violations and Qodo review comments - Replace single-letter closure params with descriptive names - Move inline tests to external file for dylint compliance - Fix prod/dev tri-state logic to match pnpm computeInclude - Resolve workspace importer by relative path instead of root fallback - Normalize importer path separators for cross-platform lockfile lookup * fix(pacquet-cli): replace remaining single-letter closures and star import in tests * fix(pacquet-cli): propagate current lockfile load errors instead of swallowing * fix(pacquet-cli): use standard mod tests; resolution instead of #[path] * refactor(pacquet-cli): move list tests from src to tests/list_unit * fix(pacquet-cli): address Qodo sanitize, tri-state, and depth reviews - Apply sanitize() in print_label, color helpers, and parseable output - Fix prod/dev tri-state: use has_both pattern like install.rs - Replace i32 depth with RecursionLimit (Levels, ProjectsOnly, Unlimited) - Move tests/list_unit/mod.rs to src/list_unit.rs (dylint compliance) * fix(pacquet-cli): inline list unit tests instead of external module Dylint perfrectionist::unit-test-file-layout rejects external test modules not in the canonical nested location. Move tests inline in list.rs as #[cfg(test)] mod tests to satisfy both dylint rules. * fix(pacquet-cli): move list tests to list/tests.rs and fix link: DepNode.path Dylint perfectionist::unit-test-file-layout with inline_style=external_only requires tests in an external module at the canonical nested location: list/tests.rs (sibling directory of list.rs in Rust 2024). Also fix resolve_importer_dep and resolve_snapshot_children to populate DepNode.path with link:target for linked workspace siblings instead of leaving it as an empty string. |
||
|
|
159bd27ecb |
feat(pacquet): pack (#12567)
* feat(pack): port the pnpm pack command to pacquet Port `pnpm pack` to Rust: create a reproducible gzipped tarball from a project, running the prepack / prepare / postpack lifecycle scripts, building the publish manifest, computing the file list, and writing the archive. - Extract npm-packlist into a shared `pacquet-fs-packlist` crate so the git and directory fetchers and the new pack command share one copy, matching pnpm's standalone `@pnpm/fs.packlist` package. - Extend `pacquet-exportable-manifest` with the full createExportableManifest: manifest obfuscation, workspace / catalog / jsr specifier rewriting, publishConfig hoisting, README embedding, and the bin / peerDependenciesMeta / repository transform. - Add the `pacquet-pack` crate (single-project api plus JSON / text output) and wire `pacquet pack` into the CLI, including the recursive (-r) workspace sweep in topological order. The dependency-converter composition is a straight function sequence rather than upstream's higher-order combineConverters, and the tarball write phase uses the capability-trait DI seam (a Host provider plus Fs* traits) so its filesystem error branches stay testable without process-global mutation. Ported from https://github.com/pnpm/pnpm/blob/cab1c11c69/releasing/commands/src/publish/pack.ts https://github.com/pnpm/pnpm/blob/ef87f3ccff/releasing/exportable-manifest/src/index.ts Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4k3hPXaosUkKBPbnChg4q * style(pack): rustfmt the pack test fixture Apply `cargo fmt` to the `fixture` helper's `package.json` write, which drifted after a manual edit. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4k3hPXaosUkKBPbnChg4q * style(exportable-manifest): satisfy perfectionist dylint lints - Add trailing commas to two multi-line macro invocations in the transform tests (perfectionist::macro_trailing_comma). - Replace a Unicode ellipsis with ASCII "..." in a create.rs doc comment (perfectionist::unicode_ellipsis_in_docs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4k3hPXaosUkKBPbnChg4q * style(pacquet): satisfy perfectionist single-letter and layout lints - Rename the reporter generic `R` to `Reporter` (bounded `self::Reporter`) in pack's `api` / `run_scripts_if_present` and the CLI `pack` command, matching the convention already used in executor, git-fetcher, and package-manager (perfectionist::single_letter_generic). - Rename `locale_compare_en`'s `a` / `b` parameters to `left` / `right` (perfectionist::single_letter_function_param). - Move the inline `transform` test module into an external `transform/tests.rs` (perfectionist::unit_test_file_layout). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4k3hPXaosUkKBPbnChg4q * test(pack): cover the prepack/prepare/postpack lifecycle path Port pnpm's `pack: runs prepack, prepare, and postpack` (releasing/commands/test/publish/pack.ts) so `run_scripts_if_present` and `script_body` are exercised. The existing pack tests all pass `ignore_scripts: true`, so that branch was never entered. - runs_prepack_prepare_and_postpack (Unix-gated, like the executor's other script-running tests): with scripts enabled, each of prepack / prepare / postpack runs and leaves a marker file. - pack_succeeds_when_scripts_enabled_but_absent: an empty `prepack` makes `script_body` yield `None` and `run_scripts_if_present` return early, so packing still succeeds without shelling out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4k3hPXaosUkKBPbnChg4q * test(pack): port bundleDependencies, custom-directory, and peer/jsr tests Port the in-scope pnpm pack / createExportableManifest tests that close coverage holes in this PR's new code: - pack: bundleDependencies (array) and bundleDependencies: true cover the fs-packlist bundle recursion and the node_linker: hoisted allow-path. - pack: publishConfig.directory covers reading the manifest from the build-output dir while resolving workspace: deps against the project root's node_modules. - createExportableManifest: a peerDependencies workspace: rewrite and a jsr: specifier without a version selector. Ported from releasing/commands/test/publish/pack.ts and releasing/exportable-manifest/test/index.test.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4k3hPXaosUkKBPbnChg4q * docs(pack): pin pack.ts source citations to a valid commit The `cab1c11c69` SHA the crate docs referenced does not exist upstream. Repoint every `pack.ts` source link in `pack`'s module docs to `54c5c0e028`, where pnpm's releasing command now lives under `pnpm11/releasing/commands/...`, so the citations resolve. * test(pack): port the transitive bundleDependencies pack test Port pnpm's `pack: bundles transitive dependencies of bundled dependencies (hoisted)` test. `top` is the only directly-bundled package, but it depends on `nested`, which is hoisted to the root `node_modules`; both must end up in the tarball. This exercises `fs-packlist`'s transitive bundled-closure walk that landed via pnpm/pnpm#12620 (resolving pnpm/pnpm#12602) and arrived on this branch through the latest merge from `main`. Also repoint the existing pack-test citations to the valid `54c5c0e028` SHA, matching the docs fix in the surrounding crate. * docs(cli): document the pacquet pack command Add a `pacquet pack` section to the CLI README listing its flag support (`--dry-run`, `--pack-destination`, `--json`, `--out`, `--pack-gzip-level`, `--skip-manifest-obfuscation`, recursive mode), so the new command ships with the same flag-status table the other commands carry. Also pin the last `pack.ts` citation in `cli_args/pack` to the valid `54c5c0e028` SHA, matching the rest of the crate. * test(pack): assert the empty-prepack guard skips, not just succeeds Mutation-testing the pack tests surfaced that `pack_succeeds_when_scripts_enabled_but_absent` did not actually guard the behavior its name and doc claim. It asserted only that packing succeeds with an empty `prepack` script — but an empty lifecycle script runs as a successful no-op, so removing `script_body`'s `.filter(|s| !s.is_empty())` left the test green while the empty script was now executed. Record the emitted log events with the crate's `RecordingReporter` idiom and assert no `pnpm:lifecycle` event fires, pinning the "returns early without shelling out" half of the contract. With the empty-string filter removed the empty `prepack` shells out and emits a lifecycle event, so the test now fails — making it a real regression guard for `script_body`. * fix(pack): align manifest validation with pnpm and harden license injection Address review feedback on the pack port: - Validate `--pack-gzip-level` against 0-9 at the CLI boundary. - Skip recursive-pack targets whose name/version are empty strings, so they match the pacquet_pack::api precondition. - Stop coercing a non-object peerDependencies into an empty object in create_exportable_manifest; leave it in place like the other dep fields. - Gate transform_required_fields on JavaScript truthiness to match upstream's `if (!manifest.name)` check instead of requiring a non-empty string. - Gate the bundledDependencies guard on JS truthiness so an explicit `false` packs cleanly under the default non-hoisted linker. - Only inject a workspace LICENSE when the entry resolves to a regular file, mirroring fast-glob's onlyFiles default and avoiding an "Is a directory" failure in the later read/size pass. * fix(pack): skip symlinked workspace LICENSE to prevent file leakage A symlinked workspace-root LICENSE was followed via fs::metadata, so a symlink pointing outside the workspace could leak its target's bytes into a sub-package's tarball. Use DirEntry::file_type (which does not follow symlinks) so only a regular file is injected, mirroring the symlink-skipping read_readme_file in exportable-manifest. Add a unix regression test. Also pin the empty-string version case in the required-field truthiness test. * fix(pack): write the tarball atomically to avoid symlink-redirected overwrite Host::write used std::fs::write, which follows a symlink at the output path and truncates in place. A repo-controlled symlink planted at the tarball's output path could therefore redirect the write to clobber an arbitrary file, and a crash mid-write could leave a truncated .tgz. Write a sibling temp file, fsync it, then rename it over the target. The rename replaces a symlink instead of following it and is atomic, matching the write-file-atomic pattern pacquet-package-manifest already uses for package.json. The mode is preserved on overwrite (else 0o644) so the archive is not left owner-only. Add a unix regression test. * fix(pack): reject path-traversal in version and main/bin packlist fields Two manifest-controlled path-escape vectors, since manifests are attacker-controlled per REVIEW_GUIDE: - pacquet-pack interpolated the version into the default tarball name (<name>-<version>.tgz) after only a non-empty check, so a version containing a path separator could write the tarball outside the destination directory. Reject a version with a / or \. - pacquet-fs-packlist force-included main/bin after only stripping a leading ./ or /, so ../secret (or a Windows drive/UNC path) escaped the package root and was read into the tarball/CAS — the packlist also feeds the git fetcher on untrusted packages. Require the normalized field to stay inside the package (only normal components, no backslash) and to be a regular file via symlink_metadata so a symlinked main/bin can't leak an out-of-tree target. Add regression tests for both. * fix(pack): reject --out with --pack-destination in recursive mode Recursive pack resolved a shared destination before `api` ran, so the `--out` branch silently dropped `pack_destination` and the mutually- exclusive pair was accepted — unlike single-project pack, which rejects it with ERR_PNPM_INVALID_OPTION. Validate the pair up front in the recursive path so both modes behave the same. * fix(pack): stream the tarball, honor unsafe_perm, and harden output Address the remaining Qodo findings on the pack port: - Stream the gzip+tar archive into the atomic temp file instead of buffering the whole archive in a Vec, so peak memory no longer scales with the packed size. Per-file reads stay in memory, matching pnpm's readFileSync. The FsWrite seam becomes FsAtomicWrite, taking a streaming write body. - Thread Config.unsafe_perm into PackOptions instead of hardcoding unsafe_perm = true for prepack/prepare/postpack, so packing honors the same policy (and TMPDIR isolation) as pnpm. - Strip control characters from the text pack output, so a filename with raw ANSI escapes can't spoof the terminal. JSON output is left as data. - Reject an --out value that resolves to no file name (., .., "") up front instead of failing later with a confusing OS write error. - Sort the contents list with one cached lowercase key per item instead of recomputing to_lowercase on both sides per comparison, and check bin executability against a HashSet instead of a linear scan. Add regression tests for the --out and control-character cases. * fix(pack): canonicalize main/bin containment and harden tarball mode Two follow-ups from review: - The main/bin force-include verified containment only on the path string, so an intermediate symlinked directory (subdir -> /outside) could still resolve a host file into the packlist. Verify the canonical resolved path is strictly inside the canonical package root instead, which also follows symlinks that stay in-package (matching npm-packlist) while failing closed on any escape. - The atomic tarball write read the destination's mode with metadata (follows symlinks), so a symlink at the output path could donate its target's or a directory's mode to the archive. Use symlink_metadata and only inherit from an existing regular file, and set the mode before syncing so content and metadata flush together. * test(package-manager): adapt patch-commit escape test to packlist filtering Hardening the shared packlist to drop `..`-escaping main/bin fields means patch-commit's `prepare_pkg_files_for_diff` no longer receives an escaping entry, so the old integration test's `expect_err` no longer fires. Unit-test patch-commit's own `safe_package_file_path` guard directly (defense in depth), and rework the integration test to assert the escaping `main` is filtered and never materialized into the diff view. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
6aa5b70595 |
feat(pnpr): multiple-uplink fallback ordering per package (#12648)
Let a `proxy:` rule name an ordered list of uplinks (`proxy: npmjs private`, or the YAML sequence form) and walk it as a fallback chain, matching verdaccio's serial first-success semantics: try uplinks in declared order, stop at the first that answers, and fall through to the next on a 404 or an availability failure. A later private uplink can host a package the primary 404s. - config: PackageAccess.proxy becomes an ordered AccessSpec list; Config::resolve_uplinks returns the chain (skipping unknown names); resolve_uplink stays as a primary-only convenience. - packument: walk the chain, sending each uplink only its own cached validators. The cache holds one shared body, so its validators are scoped to that body's origin uplink (the validator map is replaced, not merged, on every write) — a conditional GET only ever reaches the origin, so a 304 can only confirm the bytes actually on disk and can't revalidate another origin's body. The freshness window (maxage) comes from the primary uplink only. - tarball: try each uplink in order; integrity binds the streamed bytes, so the serving uplink decides whether they are mirrored (caches()) while the cache read is gated on whether any uplink caches. - fallthrough is restricted to availability failures (RegistryError::allows_uplink_fallthrough): transport errors, an open circuit, and upstream 5xx. Any authoritative 4xx (401/403 auth, 429 throttle, 400/410, ...) stops the walk immediately so a later uplink can't mask the primary's rejection of a scoped or rate-limited request. Out of scope (left to a later change): verdaccio's full cross-uplink metadata merge and per-version origin tracking. pnpr stays first-success fallback with one cache entry per package. Part of pnpm/pnpm#11973. |
||
|
|
9f3df6b9b4 |
ci(pacquet): shrink test build debuginfo to avoid ENOSPC on the ubuntu runner (#12665)
The Rust CI test job's `cargo nextest` build holds a full set of `--all-targets` debug binaries for the whole workspace (~60 crates plus heavy C deps such as aws-lc-sys, ring, libsql-ffi, and tree-sitter) in target/. That had grown close to the hosted ubuntu runner's free-disk ceiling even after the fixed `Free up runner disk space` step, and a run crossed it: the linker died with "No space left on device" while building an unrelated crate's test binary (macOS and Windows were unaffected). Build the test artifacts with line-tables-only debug info via job-level CARGO_PROFILE_DEV_DEBUG / CARGO_PROFILE_TEST_DEBUG env. This drops most of the debuginfo bytes that fill target/ while keeping panic backtraces file:line-accurate. Setting it in the workflow rather than Cargo.toml leaves local `just test` builds with full debug info. |
||
|
|
c50c6aaf6b |
feat(pacquet-cli): port audit signatures (#12663)
Port pnpm's `pnpm audit signatures` registry-signature verification to pacquet. For every installed package, the package's own registry is queried for its signing keys (`/-/npm/v1/keys`) and full packument; the package is verified as soon as one of its `dist.signatures` validates over the message `name@version:integrity` against a trusted ECDSA-P256 key. Registries that advertise no signing keys are skipped (no trust root); a package whose registry provides keys but whose signature is absent is reported as missing, and one whose signature is present but does not validate as invalid (a tamper signal). Exit code 1 when any package is missing or invalid; `--json` emits the structured report. Mirrors pnpm's deps.security.signatures package and the auditSignatures command handler, including the per-package packument-error handling, the "one valid signature wins / prefer the tamper reason" logic, the key-expiry consistency check, encodeURIComponent-faithful packument URLs, the report wording, the error codes, and the JSON result shape. Adds the `p256` (RustCrypto) crate for ECDSA-P256/SHA-256 verification; it was already present transitively in the lockfile. |
||
|
|
b0304429b0 |
feat(pacquet): port whoami command (#12649)
* feat(pacquet): port whoami command * fix(cli): redact registry URL from whoami error context * fix(cli): sanitize whoami username before printing |
||
|
|
32d4d3f1f0 |
feat(cli): port the root command to pacquet (#12632)
* feat(cli): port the `root` command to pacquet Add `pacquet root`, ported from pnpm11/pnpm/src/cmd/root.ts. Prints `<dir>/node_modules` built from the canonicalized `--dir`. It hardcodes the `node_modules` leaf and deliberately does not read `config.modules_dir`: pnpm's handler ignores a configured modules-dir, and pacquet re-anchors `config.modules_dir` to the workspace root inside a workspace, whereas pnpm's `root` uses `config.dir` (the cwd). Anchoring on `--dir` keeps parity in both cases. `--global` / `-g` is rejected with a "not supported yet" error: pacquet has no global packages directory yet, deferred to a shared follow-up that also unblocks `bin -g`. Related to pnpm/pnpm#11633. * fix(cli): gate `root` differential test on Windows and type its `--global` error The `root_matches_pnpm_from_a_workspace_subdir` differential test spawns the real `pnpm`, which on Windows is a `pnpm.cmd` shim that `std::process::Command` cannot resolve via `PATHEXT` ("program not found"). Gate it with `#[cfg_attr(target_os = "windows", ignore)]`, matching the `cfg(unix)` gating in `pnpm_compatibility` and `hoist`. The other three `root` tests spawn only `pacquet` and keep running on Windows. Replace the bare `miette::miette!` rejection of `pacquet root --global` with a typed `RootError::GlobalUnsupported` diagnostic (code `pacquet_cli::root_global_unsupported`), mirroring `runtime`'s `GlobalUnsupported`, so the refusal carries a stable error code. |
||
|
|
47685fddab |
fix(pnpr): allow metadata-only re-publish so pnpm deprecate works (#12659)
pnpm/pnpm#12646 rejects any re-PUT of an already-hosted version with 409 to keep published content immutable. But `pnpm deprecate`/`undeprecate` work by GET-ing the packument, flipping the `deprecated` flag, and re-PUT-ting it with no attachments and the hosted `dist` unchanged. That metadata-only update tripped the 409 guard, breaking test/deprecate.ts across the e2e suite (on every PR, via the merge commit). Narrow the guard to reject only a content re-publish of a clashing version: one that carries a new tarball (an attachment) or changes `dist.integrity`. Integrity is the content anchor clients verify against; the `tarball` URL is rewritten on read, so a faithful deprecate re-PUT carries a different URL but the same integrity and must be allowed. A metadata-only clash now passes, and merge_manifest preserves the hosted version's `dist`, so a metadata update can never repoint a version's integrity or tarball. Adds pnpr integration tests: a deprecate-style update is allowed and keeps the hosted `dist`; the attachment re-publish and the no-attachment integrity smuggle stay 409. |
||
|
|
208b6921b1 |
feat(pacquet-cli): add unlink command (#12590)
Ports the unlink command from pnpm to pacquet. UnlinkArgs::run builds a list of packages to remove: the explicitly provided names or all link:-versioned dependencies across all groups (prod, dev, optional). If the list is empty the command is a no-op. It calls PackageManifest::remove_dependencies, saves the manifest, and delegates to Install which cleans up the stale symlink. No changeset because pacquet is not a published package. Related to pnpm/pnpm#11633 --------- Co-authored-by: Zoltan Kochan <z@kochan.io> |
||
|
|
dd8ea85662 |
fix(package-manifest): write package.json atomically via a temp file (#12642)
PackageManifest::save truncated package.json in place with File::create + write_all. A crash, OOM, or ENOSPC between the truncate and the completed write left the file empty or partial, destroying the user's dependency declarations with no recovery path. add/remove/update and set-script all go through this path. Write the serialized manifest to a sibling temp file, fsync it, then rename it over the target, matching the write-file-atomic guarantee the TypeScript pnpm CLI relies on. The crate's sibling workspace-manifest-writer already uses this pattern. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
114e4f5aac | ci: gate the pacquet release publish job behind the release environment (#12641) | ||
|
|
0c44bb94e6 |
ci: gate the pnpr release publish and docker jobs behind the release environment (#12640)
pnpr-release-to-npm.yml can be triggered via workflow_dispatch. Its publish job uses npm OIDC trusted publishing to publish the `@pnpm/pnpr` package, and its docker job pushes the mutable ghcr.io/pnpm/pnpr:latest tag. Neither had an environment gate, so any collaborator with write access could publish to npm and overwrite the mutable Docker tag without reviewer approval. Add an environment: release gate to the publish and docker jobs (the build job produces no externally visible artifacts and is intentionally left ungated), matching the protection used by release.yml. Co-authored-by: Claude <noreply@anthropic.com> |