The Rust render_ledger wrote an empty intent list as a bare 'intents:'
key, which parses as YAML null - a shape neither stack's ledger reader
accepted. The 11.14.0 / 12.0.0-alpha.14 release run (pnpm/pnpm#13113)
re-rendered the committed ledger through that path, turning the
pacquet@12.0.0-alpha.13 entry's 'intents: []' into the bare form, and
every 'pnpm version -r' after it failed with
ERR_PNPM_INVALID_VERSIONING_LEDGER.
- Render empty intent lists as '[]' in both ledger entry shapes,
matching the TypeScript side's yaml.stringify byte for byte.
- Accept null where a list belongs when reading, in both stacks: a
bare entry key or bare 'intents:' key now reads as an empty list,
so ledgers written before this fix stay readable.
- Repair the committed .changeset/ledger.yaml entry.
- Drop the stale meta_only field from a package-manager PeerDep test
fixture (pnpm/pnpm#13030 added it while pnpm/pnpm#13120 removed the
field, breaking main's --all-targets build).
During a non-frozen re-install, a node reused from the prior lockfile
registered its synthesized ResolveResult in the workspace-shared
resolved_by_wanted cache. That result's manifest deliberately omits
dependencies (a reused node's children come from the snapshot graph), so
when another edge with the identical wanted key was denied reuse - by
the changed-direct-dep gate in a different importer, or by
subtree_fully_reusable's provisional-false cycle guard racing a
concurrent check - its fresh resolve read the dependency-less result
back, classified the package as a leaf, and (when that occurrence was
shallow enough to win children ownership) recorded it with no children:
the package's lockfile snapshot collapsed to an empty map, its peer
suffix was dropped, its dependents were re-pointed at the bare instance,
and none of its dependencies were linked.
This is what broke the Compile & Lint job on the pnpm@12.0.0-alpha.14
bump PR (pnpm/pnpm#13070): the alpha.14 per-importer freshness check
regression (since fixed by pnpm/pnpm#13117) pushed CI's plain install
onto the re-resolution path, which then emptied the snapshots of
`@yarnpkg/shell@4.0.0` and `normalize-package-data@3.0.3`, leaving
`pnpm/dist` bundling unable to resolve their dependencies. The
corruption itself predates alpha.14 (reproducible on alpha.13 by
forcing a re-resolution with any manifest change).
Drop the cache insert: only the fresh-resolve path ever read it, and a
denied edge must see the real registry manifest to walk its children.
The regression test drives the deterministic cross-importer variant
using new reuse-chain-* registry fixtures: an unchanged importer reuses
the target at depth 2 (caching the synthesized result under the
exact-pin wanted key), then a later importer whose changed direct dep
overlaps the target's snapshot is denied reuse at depth 1 and must
re-resolve fresh.
The TypeScript CLI has no equivalent cache of synthesized reuse
results, so the fix is pacquet-only.
An optional peer declared only via peerDependenciesMeta (no peerDependencies
entry -- the debug -> supports-color shape) is an implied "*" peer. Peer
resolution and the lockfile writer already materialize it, but missing-peer
collection read the raw manifest in the TypeScript stack, and a meta_only flag
kept such peers out of the optional-peer hoist in pacquet, so on a fresh
resolve the implied peer was never deduped onto a version already in the
graph. pacquet's lockfile-reuse path, however, synthesizes manifests from
lockfile snapshots, where the implied peer is already materialized into
peerDependencies -- after a lockfile round-trip the distinction was lost and
the hoist fired. The stacks disagreed, and within pacquet a fresh install and
an incremental one produced different lockfiles: one unrelated catalog change
could rewrite peer suffixes across the whole lockfile.
Treat implied optional peers exactly like explicitly declared ones in both
stacks: the TypeScript getMissingPeers now reads the materialized
ResolvedPackage.peerDependencies (which also stops reporting peers satisfied
by the package's own dependencies, matching pacquet), and pacquet drops the
meta_only flag -- the hoist gate in partition_missing_peers, the
hidden-provider gate in resolve_peers, and all plumbing. Fresh and incremental
installs now produce identical lockfiles in both stacks, and a graph that
contains a satisfying version of the implied peer resolves it everywhere,
e.g. debug@4.4.3 gets its supports-color peer.
The meta_only gate originally landed for pnpm/pnpm#12266 to mirror the
TypeScript stack's fresh-install behavior; this change aligns both stacks on
the hoisting side instead, which also removes the npm-compat gap where debug
could not see a supports-color present in the graph.
Remove the legacy repository changelog files now that release changelog storage defaults to the registry. The publish path composes and injects CHANGELOG.md into release tarballs, so keeping historical copies in source control duplicates generated release data.
Update adm-zip to the patched 0.6 release and override vulnerable transitive versions after the dependency audit began rejecting versions below 0.6.0.
Pacquet's recursive command pipeline previously dispatched selected projects independently or lost the selection before package-manager execution. This could overwrite earlier manifest mutations, truncate workspace lockfile state, or materialize projects outside the requested filter.
Thread one workspace selection through command dispatch and package-manager execution, batch manifest mutations into a single install, retain the complete wanted lockfile, and derive current-lockfile and node_modules state from the selected dependency closure. Apply the same workspace root and selection semantics to query, state, global, deploy, and pnpr-backed paths.
Keep full-workspace recursive installs on the ordinary unfiltered fast and frozen paths, and add regression coverage for filtering, lockfile preservation, dependency closure materialization, pnpr multi-importer responses, and TypeScript parity edge cases.
Depends on pnpm/pnpm#13016.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
pacquet's per-importer frozen-lockfile freshness check compared each
lockfile importer's dependencies against only the manifest's
dependencies/devDependencies/optionalDependencies. With auto-install-peers
enabled (pnpm's default), pnpm materializes every missing non-optional
peer dependency into the importer's `dependencies` in the lockfile, so a
workspace member that declares a dependency only under `peerDependencies`
had that materialized entry misread as a removed dependency, aborting a
`--frozen-lockfile` install with ERR_PNPM_OUTDATED_LOCKFILE.
This surfaced once pnpm/pnpm#13081 extended the check from the root
importer to every workspace importer (v12.0.0-alpha.14): importers such
as `pnpm11/building/after-install` declare `@pnpm/logger` and
`@pnpm/worker` only as peers.
`satisfies_package_manifest` now takes `auto_install_peers` and folds the
peer-only dependencies into both the flat-record diff and the per-field
`dependencies` comparison, mirroring the TypeScript
`satisfiesPackageManifest`. The TypeScript CLI already handled this, so
the fix is pacquet-only.
Report each scenario's minimum latency to Bencher instead of the mean, so the
threshold tracks the min. On shared CI runners the timed work is CPU/disk-bound
(the simulated latency only adds a deterministic floor, and the noisiest
scenario runs --offline with no network), so the mean drifts run-to-run with
host contention — byte-identical binaries differed ~30% on the offline scenario
and tripped a spurious alert.
Contention only ever adds time, so the minimum is the run least perturbed by a
noisy neighbour and the truest regression signal. Emit Bencher Metric Format
and upload the integrated benchmark with --adapter json, reporting the min
honestly as the value (nanoseconds, min seconds x 1e9, to match the latency
measure's unit) rather than relabelling hyperfine's mean field. The TS
test-duration and micro-benchmark uploads keep shell_hyperfine (single-sample,
so min equals mean there). Because the min only needs one uncontended run,
hyperfine's default sampling suffices. The human comment still shows mean +/- σ.
Note: the workflow_run-triggered comment workflow runs from the default branch,
so this PR's own benchmark-comment upload fails (main still uses
--adapter shell_hyperfine); it self-heals once this lands on main.
Port the pnpm licenses command to pacquet to mirror the behavior of the TypeScript CLI. The command supports standard tabular and JSON output formats. It correctly parses both direct and transitive dependencies and extracts author and homepage information from package.json files. Additionally, this implements recursive workspace project filtering, allowing the command to evaluate and combine licenses across the entire workspace according to user configuration.
Related to: #11633
refactor(add): drop the vestigial Fn() -> DependencyGroupList generic from Add
list_dependency_groups existed only because the dependency-group list
used to be consumed more than once. pnpm/pnpm#12995 collapsed that to a
single collect-and-reuse, so the Fn() -> DependencyGroupList closure
indirection no longer buys anything: take dependency_groups directly
instead.
Threads the same simplification through add_package/add_packages and
their five call sites (add, global, dlx, runtime,
self_update/install_pnpm), each of which drops a trivial
closure/std::iter::once wrapper for a plain value or array literal.
Pure internal refactor: no user-visible behavior change, so no
changeset and no TypeScript counterpart.
Closespnpm/pnpm#13088
peerDependencies previously rejected any value that was not a semver range or a
`workspace:`/`catalog:` spec with ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION.
This blocked named-registry specifiers (`<registry>:<version>`), which are
otherwise usable in dependencies/devDependencies (pnpm/pnpm#13095).
Accept any specifier that carries a scheme (named-registry, `npm:`, `file:`,
git/URL). A shared getPeerVersionRange helper extracts the comparable range
everywhere a peer is matched — the satisfaction check, the hoistPeers
auto-install selection, and the peers checker — matching `work:5.x.x` as
`5.x.x` and `npm:bar@^5` as `^5`, or `*` when no version is carried, while the
original specifier still selects the package to auto-install. isAcceptablePeerSpec
relaxes validation while still rejecting bare `name@version` typos.
The same change lands in the Rust port: a shared `peer_range` module in
resolving-resolver-base (which also absorbs the two duplicated
`is_valid_peer_range` copies), resolve_peers, hoist_peers, and the `pnpm peers`
command. MissingPeer carries the extracted range for matching/display and the
original specifier for the hoist source, so both stacks show the same warning
text and auto-install from the right source.
Closespnpm/pnpm#13095.
Lockfile freshness compared importer and catalog Git specifiers as raw strings
in both the Rust (pacquet) and TypeScript stacks, so a canonical git+https
lockfile entry was rejected when a manifest or catalog used git:// or a hosted
shortcut.
Add a Git specifier equivalence check and apply it at every freshness
comparison gate — importer specifiers, per-dependency checks, and catalog
entries — in both stacks. Preserve host, repository, and ref differences while
accepting supported protocol, shortcut, and .git suffix representations.
Closespnpm/pnpm#13039.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Treat a 304 response without a matching metadata cache entry as a recoverable cache race. Retry the registry request once without conditional validators and feed the fresh response through the normal cache persistence path.
Implement the same bounded recovery in the TypeScript resolver and pacquet, including Rust retries that occur while reading or decoding the response body. Add regression coverage for both resolver implementations.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
whichVersionIsPinned had no case for the `=` range operator, so `pnpm
update` fell back to the default pinning and rewrote exact pins like
`=3.5.1` to `^3.5.2` in the manifest. A bare `=` now classifies the same
way as the plain version it prefixes: `=x.y.z` is an exact pin written
back as the bare version (node-semver's canonical spelling of that
range), `=x.y` pins to minor, `=x` to major. Compound operators (`>=`,
`^=`, `~=`) stay unpinned, and the `*` -> `^x.y.z` rewrite is intentional
per #5436 and unchanged.
The Rust port gets the same classification fix. Its comparator scan also
counts dangling `||` / `-` separators as range elements, mirroring how
semver-utils emits them as elements of their own; without that, a valid
range like `=1.2.3||` would classify as a pin in Rust while the
TypeScript implementation bails on the multi-element parse.
Closespnpm/pnpm#12745
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Resolve package selectors concurrently during pacquet add instead of waiting for
each selector's registry request before starting the next one.
Share packument metadata state across the concurrent resolutions. Buffer catalog
warnings and process the completed results in selector order to keep warnings and
error selection deterministic.
Add regression coverage that proves registry requests overlap while preserving
selector-ordered warnings and errors.
Restrict the Rust CI pnpm cache to CAS files so Windows runners do not restore
invalid global-virtual-store junctions.
Closespnpm/pnpm#13089.
Registry-provided dist.unpackedSize was forwarded unbounded to zune-inflate as an eager output-buffer allocation hint. zune-inflate reserves it as an infallible zero-filled `vec![0; hint]`, so an arbitrary registry figure could abort the process instead of failing gracefully, multiplied across concurrent post-download tasks.
Clamp the hint to the tarball module's existing 64 MiB untrusted-preallocation ceiling, now shared with the zip entry reservation that already bounded against the same figure. The ceiling bounds the eager reservation only, never the output: zune-inflate grows its buffer on demand, so an archive larger than the ceiling still decodes in full.
Clamping preserves the pre-sizing the hint exists for. Omitting an oversized hint instead would leave an honest large package growing from the decoder's small default, measured ~6x slower on an 80 MiB archive. A clamped reservation is lazily backed, so a package that overstates its size holds address space rather than resident memory: 20 concurrent tiny packages each claiming usize::MAX hold +0.7 MiB RSS.
Add boundary coverage for absent, ordinary, exact-limit, over-limit, and maximum-size hints, plus a test that decompress_gzip routes the registry figure through the bound rather than forwarding it raw.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
The installer emits separate added and removed statistics events. The Rust
append-only reporter rendered both immediately, which printed the same package
statistics block twice when the removed count was zero.
Buffer both statistics events and render their combined result once, matching
the TypeScript reporter. Flush partial statistics when the run summary arrives
so a missing event cannot suppress the output. Add append-only regression tests
for both the normal pair and incomplete-pair fallback.
Broken lockfile errors rendered parser-provided source snippets. A repository can commit pnpm-lock.yaml as a symlink, so the snippet could quote content from a readable file outside the repository.
Format parse failures from their structured reason and location instead. Pacquet also stops exposing the snippet-bearing parser error through its diagnostic source chain.
Closespnpm/pnpm#13094
`pacquet add <name>` without a version and `pacquet update --latest`
fetched the raw `latest` dist-tag, so with an active
`minimumReleaseAge` they could write a range into `package.json` that
the follow-up install rejects (related to pnpm/pnpm#11165).
Resolve the tag through the same package-picking path as install. The
maturity filter repoints `latest` to the newest mature version, keeping
the manifest and lockfile consistent and matching the TypeScript CLI.
Share one lazily constructed `LatestPicker` across every selector in a
command. This preserves command-validation and unmatched-selector error
ordering while reusing the policy, metadata cache, fetch locker, and
registries map. Report invalid `minimumReleaseAgeExclude` values with
`ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE`, matching install and the
TypeScript CLI.
Add TypeScript and Rust regression coverage for bare update, update
with `--latest`, and add without a version.
---------
Co-authored-by: Manuel Pelloni <manuel.pelloni@m4ss.net>
Restored `--sequential` (`-s`) CLI flag for `pnpm run` to force `workspaceConcurrency=1` for matched scripts across workspace packages and within individual packages. Added `.sort((a, b) => a.localeCompare(b))` to `getSpecifiedScripts` so that multi-script execution triggered by a RegExp selector executes in deterministic lexicographical order. Updated `cliOptionsTypes` in TypeScript and `RunArgs` across all Rust CLI commands (`run`, `clean`, `restart`, `stop`, `dispatch_script`) to ensure seamless parsing and parity.
A git dep's specifier names a repo, not a package, so its name is only
readable from the package.json in the host's archive. The git resolver
left `manifest` unset, so `build_pkg_id_with_patch_hash` had no name to
prefix and emitted a bare archive URL as the dep path, which no lockfile
key parses — the graph-to-lockfile conversion then panicked on it.
Read the manifest during resolution, as the tarball resolver already
does for remote tarballs and as upstream's package requester does when a
resolver returns no manifest. Dep paths then carry the `<name>@` prefix
and parse as ordinary lockfile keys, so the graph-to-lockfile adapter
needs no git-specific key reconstruction.
The fetch stops at the raw archive: the prepare/prepublish pass and its
packlist filtering stay in the install pass, so no package script runs
during resolution. The install pass re-fetches the archive to run them,
so a git dep costs one extra archive download per install.
`real_name` now covers git-hosted tarballs, matching upstream's
`depPathToRef`: an unaliased git dep records `version: <url>`, while a
renamed one keeps the `<name>@<ref>` alias form that composes back to
its snapshot key.
Record the archive's integrity from the bytes that fetch already
hashes. The install pass refuses a tarball resolution without one, so a
git dep otherwise resolved into a lockfile it could never install from.
With it, a pacquet lockfile for a git dep is byte-identical to pnpm
11's for the same manifest.
Replace the `.expect()` on the importer dep path with a propagated,
contextual error so an unexpected shape degrades gracefully instead of
aborting the process.
A repo with no archive endpoint (self-hosted, `file:`, or any ssh URL)
has its name read from a throwaway checkout instead; the clone is
extracted out of `GitFetcher` so both passes share one implementation.
Without it such a dep resolved with no name, failed `PackageKey`
parsing, and was silently dropped from `packages:` / `snapshots:` while
the importer still pointed at it — a frozen install off that lockfile
exited 0 and left a dangling symlink.
Also strip the leading slash from a resolution's `path` before joining
it onto the checkout: `Path::join` discards the root on an absolute
argument, unlike upstream's `path.join`, so every sub-directory git dep
failed to install.
Guard the two git arguments this newly reaches from resolution. `repo`
was passed to `git clone` / `git remote add` as a positional, and git
reads a `-`-leading value as an option, so `--upload-pack=<cmd>` ran
`<cmd>` on a local or SSH transport; `commit` was already guarded for
this, `repo` was not. Reject the shape and pass `--` before the
positional. The resolve-time manifest read joined its sub-path with no
containment check, letting `#path:/../..` read an arbitrary
`package.json` off the host and stamp its name onto the dep; it reuses
the install pass's `safe_join_path` instead of a second weaker copy.
Closespnpm/pnpm#13040.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Allowing symlinked reads did not fixpnpm/pnpm#13073. The reported
`--frozen-lockfile --lockfile-only` install fails on the *write*:
tryFrozenInstall rewrites an already up-to-date pnpm-lock.yaml, and the
write path refuses a symlink before it writes.
Skip a byte-identical rewrite instead. An up-to-date install -- every
frozen run -- no longer touches the lockfile at all, so one staged as a
symlink (Bazel, Nix) installs cleanly, while a write that changes bytes
still refuses the symlink: write-file-atomic resolves the target with
realpath, so writing through one redirects onto any file the user can
write.
Reading through a symlink stays allowed. That guard never protected
anything: readWantedLockfile reads the same file with a plain readFile and
has always followed the link, so refusing it on the env-document read only
broke sandboxes. A hostile repo can commit a hostile lockfile as a plain
file regardless -- lockfile content is untrusted either way. The guard
arrived as a "consider this too" aside alongside the write fix in
pnpm/pnpm#12821.
Close the same clobber in pacquet: save_value_to_path wrote the wanted
lockfile with fs::write, which follows symlinks and had no guard. It now
refuses a symlinked lockfile and writes atomically, carrying the target's
mode across the rename and flushing before it, matching write-file-atomic.
Share the symlink guard between both write paths rather than duplicate it.
Add install-level regression coverage for the reported command; the
existing unit tests passed while the bug remained.
Error codes are a public contract — users match on them in CI and scripts, and
pnpm documents them at https://pnpm.io/errors. 284 of pacquet's
`#[diagnostic(code(...))]` sites reported something else:
$ pnpm root -g
Error: pacquet_cli::root_global_unsupported
`pacquet` is an in-repo crate name that means nothing to a user of the `pnpm`
binary. A second group reported unprefixed codes (`GIT_CHECKOUT_FAILED`), which
are the *short* names pnpm passes to `PnpmError`; its constructor prepends
`ERR_PNPM_` before the code is ever observed, so pacquet was emitting a code pnpm
never emits.
Rename all 259 offending codes across 284 sites under two rules:
1. Where pnpm defines a code for the same error, use pnpm's exact code.
`pacquet_package_manager::outdated_lockfile` becomes
`ERR_PNPM_OUTDATED_LOCKFILE` — which its own doc comment already claimed it
was — and `GIT_CHECKOUT_FAILED` becomes `ERR_PNPM_GIT_CHECKOUT_FAILED`.
2. Otherwise derive from the full path minus the crate prefix, so
`pacquet::patch_state_read` becomes `ERR_PNPM_PATCH_STATE_READ`. The namespace
is retained rather than the leaf alone: six distinct `create_dir` errors would
otherwise collapse onto one code.
Three merge onto codes pacquet already used elsewhere for the same condition,
which is the intent, not a clash. pnpm has a single `ERR_PNPM_TRUST_DOWNGRADE`
and matches on it by name in `deps-resolver`, so pacquet's resolver-side and
lockfile-side trust downgrades now share it.
Two errors were mislabelled and are corrected rather than renamed:
- `frozen_lockfile_with_outdated_lockfile` is not pnpm's outdated-lockfile error.
The variant is `FrozenLockfileWithUpdateChecksums` and the message is about
combining --frozen-lockfile with --update-checksums. It takes
`ERR_PNPM_CONFIG_CONFLICT_FROZEN_LOCKFILE_WITH_UPDATE_CHECKSUMS`, following the
existing ERR_PNPM_CONFIG_CONFLICT_* convention, and its doc comment no longer
claims to be the error it isn't.
- Three package-manifest errors have pnpm equivalents under a different name,
which rule 1 cannot see because it matches on the name: "package.json file
already exists" is pnpm's `PACKAGE_JSON_EXISTS` (thrown by `pnpm init`), "No
package.json was found" is `NO_IMPORTER_MANIFEST_FOUND`, and "Missing script"
is `NO_SCRIPT`.
All 610 diagnostic codes are now ERR_PNPM_*.
Two published v11 releases were broken in ways nothing in the pipeline
checked, and both only surfaced once users upgraded onto them.
11.12.0 was packed by a pnpm that ignores the .pnpmfile.cjs beforePacking
hook, so the bundled dependency fields survived into the published
manifest. Resolving node-gyp then pulled a peer-suffixed snapshot into the
env lockfile, and every upgrade onto 11.12.0 died in
buildLockfileFromEnvLockfile (pnpm/pnpm#12955, pnpm/pnpm#12959).
11.12.0 and 11.13.0 also shipped `@pnpm/exe` platform packages with no
native binary; setup.js exits 0 when the binary is missing, so the
placeholder bin survived and only a real invocation caught it.
Assert on the packed tarball that the published pnpm manifest declares no
dependency fields, rather than trusting either stripper, since npm
publishes are immutable. Then, in the Tag workflow, upgrade from the
release line's current version onto the new one for both the `pnpm` and
`@pnpm/exe` wrappers and run the resulting binary. The release workflow has
already published under next-<major> at that point, so this reads the real
registry artifact, and it runs before the dist-tags move — the last gate
before a version reaches everyone.
Verified against the registry: the assert rejects pnpm@11.12.0 and accepts
11.11.0/11.13.1; the upgrade gate passes 11.13.0 -> 11.13.1 and catches
both `pnpm@11.12.0` and `@pnpm/exe@11.12.0`/11.13.0.
Related to pnpm/pnpm#12959.
Pacquet collected trailing add arguments but only forwarded the first package selector. Carry the full selector list through regular, global, and configuration-dependency dispatch paths.
Parse and validate all configuration-dependency selectors before initializing installation state so an invalid later selector cannot leave partial filesystem changes. Batch dependency and workspace-manifest updates and persist each result once per command.
Add integration and unit coverage for multi-selector regular adds, configuration-dependency adds, validation atomicity, and batched workspace-manifest writes.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
`pacquet` is an in-repo name only; the shipped binary is `pnpm`. Error messages,
diagnostic help text, and `--help` output leaked the internal name, and several
told users to run commands that do not exist:
help: ... then re-run `pacquet install --frozen-lockfile`.
Replace the leaked name across user-facing strings — typeable commands (`pacquet
install`, `pacquet add --config`, `pacquet why`, `pacquet create <name>`,
`pacquet outdated --recursive`, `"pacquet pack-app"`, and peers) and
product-name prose ("pacquet cannot verify the download", "this build of
pacquet", "not yet ported to pacquet").
Two were also factually wrong, not just misnamed:
- `pnpm add --help` documented the virtual store directory default as
`node_modules/.pacquet` in both the doc comment and clap's `default_value`,
while the effective default from `default_virtual_store_dir()` is
`node_modules/.pnpm`. The field is unused (`TODO: make use of this`), so only
the help output changed.
- The tarball integrity error offered `pnpm install --update-checksums` and then
repeated it as `(or `pacquet install --update-checksums`)`. Drop the redundant
parenthetical.
Internal uses stay as they are: tracing targets, thread names, temp-file
prefixes, the mirror cache magic, panic messages, and test fixtures are not
user-facing.
The TypeScript CLI has no counterpart to change — it never refers to pacquet.
`.modules.yaml` recorded `packageManager: "pacquet@0.0.1"`, which is wrong
on both halves. `pacquet` is an in-repo crate name that has no meaning
outside this repository — the CLI ships as `pnpm`. And `CARGO_PKG_VERSION`
is the crate's version (`0.0.1`), not the release version the user
installed (`12.0.0-alpha.13`).
The TypeScript CLI writes `${name}@${version}` sourced from its own
package.json, so it records `pnpm@11.x.x`. It also branches on
`packageManager.name === 'pnpm'` when deriving `pnpmVersion`
(`installing/deps-installer/src/install/index.ts`), which shows the name
half is meaningful and not decorative.
Use `pacquet_config::PNPM_VERSION`, the constant `pnpm --version` already
prints. Its doc comment records that `pnpm bump` keeps it in sync with the
npm wrapper package's version and that the release workflow verifies the
two match, so it is the release version by construction.
Nothing reads the field back in either stack — it is informational
metadata — so this changes no behavior. It is still part of the on-disk
contract that external tooling can inspect, and it should not name a crate
the user has never heard of.
Tighten the test from a `starts_with("pacquet@")` prefix check to an exact
comparison against `pnpm@{PNPM_VERSION}`, so a future drift between the
recorded name/version and the release version fails.
Fresh isolated installs constructed an installability host only when optional dependencies carried platform or engine constraints. Required dependencies therefore skipped strict engine validation, allowing an incompatible engines.node range to install successfully under engineStrict.
Use one installability gate for any constrained package and reuse it when computing skipped snapshots. Keep host detection disabled for --force and constraint-free graphs, while preserving the existing configured nodeVersion, supported architecture, and optional-dependency behavior.
Add deterministic local-dependency regressions covering fresh strict rejection, fresh non-strict acceptance, and frozen strict rejection.
Strict minimumReleaseAge resolution already collects every immature pick, but pacquet could only abort. Add the interactive approval path used by the TypeScript CLI, persist approved exclusions before install writes begin, and keep noninteractive installs fail-closed with the complete violation list.
Run the blocking confirmation off the async worker and bracket it with pnpm:prompt events. The default reporter holds append-only lines or the latest live frame while the question is active, then replays that output when prompting ends. Reject update --no-save up front because approval requires a durable workspace exclusion.
Closespnpm/pnpm#13047.
The comment justifying `--no-prefer-frozen-lockfile` in
`lockfile_only_updates_importers_when_a_project_is_added` claimed the
auto-frozen freshness gate validates only the root importer. That stopped
being true in pnpm/pnpm#13081, which made `check_lockfile_freshness` loop
over every project manifest.
The flag is still required, for a reason that only became true in that
same change: project-2 declares no dependencies, and the auto-frozen gate
deliberately tolerates a missing importer entry for a dependency-free
project (`allow_missing_dependency_free_importers`, matching the
TypeScript CLI's `allProjectsAreUpToDate`). Without the flag the run
short-circuits to the frozen path and never records the new importer, so
the `packages/project-2:` assertion fails.
Restate the comment against the actual reason so the next reader doesn't
drop the flag on the assumption it is leftover scaffolding.
* fix: share raw metadata across in-flight requests
* test: cover the metadata re-serialization fan-out
Add a regression test at the pickPackage/memo-cache boundary: 20 projects
joining one in-flight fetch must mirror the fetched body rather than each
re-serialize it. Against the pre-fix implementation it fails with 19
re-serializations, one per non-initiating caller.
Cover the rejection-path identity guard too, which no test exercised.
Correct the jsonText contract docs, which still described the pre-fix
behaviour, and restore the rationale for dropping the body once the
request settles.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
A project pinned to 11.12.0 through `packageManager` or
`devEngines.packageManager` still failed on every command with `Cannot use
'in' operator to search for 'integrity' in undefined`. That is the report in
pnpm/pnpm#12959, and it is the shape most people hit: the pin is committed,
so one bad pin breaks everyone who clones the repo.
pnpm/pnpm#13076 refuses these releases, but only in `self-update`. The
version switch is the other way in, and it reached the installer instead,
where a release whose manifest declares dependencies crashes resolving them.
Refuse the same releases there. The check sits after the "already this
version" early return, deliberately: switching *to* a broken release is
refused, but running one that is already installed is not — a developer
whose pnpm is a broken release still needs it to work well enough to move
off it.
11.13.0 is refused too, though it runs for the JS wrapper. The pin is shared
and the wrapper is not, so pinning it works for whoever wrote the pin and
breaks every teammate on `@pnpm/exe`, whose native is missing.
Check every workspace project manifest against its matching lockfile importer before taking the
frozen or prefer-frozen path. The project list and importer ID helper are shared with the existing
multi-importer resolver, so workspace members use the same lockfile keys during resolution and
freshness validation.
This makes normal installs refresh stale workspace member importers and makes frozen installs fail
instead of silently accepting a lockfile that no longer matches a member's package.json. It also
matches the TypeScript freshness edge cases for aliases, dependency metadata, missing dependency-free
importers, distribution tags, and direct resolutions outside their declared semver ranges.
Persist importer `dependenciesMeta` and `publishDirectory` during fresh resolution so those parity
checks compare against complete lockfile snapshots. Restore the empty intent list in the versioning
ledger as well. The previous YAML spelling parsed
as null and prevented release-plan validation from reading the committed ledger.
Closespnpm/pnpm#13080.
A self-update that installs a working pnpm and one that installs a broken
one were indistinguishable. Installing `@pnpm/exe@11.13.0` printed
"Successfully updated pnpm to v11.13.0", exited 0, and left the user with
no working pnpm at all: its platform package shipped without a native, so
setup.js kept the tarball's placeholder bin, and nothing between the
download and the relink of PNPM_HOME ever executed the result.
Run the installed CLI before the caller makes it active. The install
already happens in a throwaway directory and the relink is downstream, so
failing here keeps the version that was working and removes the directory
rather than needing to restore anything. This is what deno's check_exe and
rustup's self-update do — they run the downloaded binary and refuse to
replace the current one if it does not start.
Only that it runs is asserted, not what it prints: matching --version
output exactly would fail on anything else a release writes to stdout, and
the test fixtures show why — one tarball stands in for several mocked
versions.
pacquet gets the same check for native engines, which is the whole set that
can install yet fail to execute: the legacy JS engine has no binary of its
own to be missing, and running it would mean locating a Node.js first.
A release that installs but cannot run still reaches users; this only stops
it from taking their working pnpm with it. Rejecting it before publication
is pnpm/pnpm#13072.
Native Rust implementation of the owner/owners CLI command.
Registry API endpoints used:
- GET /-/package/{name}/owners (list)
- PUT /-/package/{name}/owners (add)
- DELETE /-/package/{name}/owners/{user} (remove)
Features matching TypeScript parity:
- Subcommands: ls, list, add, rm
- Default to ls when first arg is a package name
- --otp flag sends npm-otp header for add/rm
- Scoped package registry resolution via pick_registry_for_package
- Auth header resolution via nerf-dart URL matching
- Retry with exponential backoff
- Body size limits (1MB for reads, 64KB for error bodies)
- Error codes: ERR_PNPM_OWNER_LS_PACKAGE_REQUIRED, ERR_PNPM_OWNER_ADD_ARGS_REQUIRED, ERR_PNPM_OWNER_RM_ARGS_REQUIRED, ERR_PNPM_PACKAGE_NOT_FOUND, ERR_PNPM_UNAUTHORIZED, ERR_PNPM_FORBIDDEN, ERR_PNPM_REGISTRY_ERROR
related to: #11633
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
The native release flow parks registry-mode changelog sections under
`.changeset/changelogs`, so use that as the only source for generated release
notes instead of the stale committed `CHANGELOG.md`.
Release-note metadata must not block publishing valid artifacts. If generation
fails, write a diagnostic release body with a link to the workflow run. Create
the GitHub release as a draft, compare its persisted body exactly with the
generated or diagnostic file, and publish it without resending the body.
The TypeScript release preflight rejected valid empty files and allowed the
workspace root gitignore to remove the CLI dist directory from the pnpm
tarball. Its executable installation test also ran npm from inside a checkout
whose devEngines requires pnpm.
Accept empty regular files while retaining byte-for-byte hash verification,
isolate the pnpm package from workspace ignore rules before building, and run
the npm lifecycle smoke test from its temporary install directory. Add a
manual verification-only dispatch that exercises the complete artifact build
and smoke test while keeping every publish job disabled.
The strict minimumReleaseAge approval prompt is shown mid-install while
background resolution keeps emitting progress. The default reporter redrew
each frame in place and erased below it, moving the cursor into the prompt's
lines and wiping the question, so the install hung on input the user could
not see.
Bracket the prompt with pnpm:prompt start/end events and hold every frame
redraw while it is open, recreating the differ on resume so the next frame
renders in full below the answered prompt. The erase-below cleanup for
external prompts (e.g. an SSH passphrase) stays intact for the normal case.
Ref: pnpm/pnpm#13019
Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>