Commit Graph

12286 Commits

Author SHA1 Message Date
Zoltan Kochan
4ddaac20f9 fix(release): publish verified TypeScript build 2026-07-16 02:15:02 +02:00
Zoltan Kochan
566864e3d1 fix(release): verify and package TypeScript CLI artifacts (#13063)
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.
v11.13.1
2026-07-16 02:04:23 +02:00
Zoltan Kochan
fb4639f8d2 fix(release): install executable smoke-test tarballs (#13061) 2026-07-16 01:07:08 +02:00
Zoltan Kochan
ca66b76fb2 chore(release): 11.13.1 (#13058)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 00:36:19 +02:00
Zoltan Kochan
303bfa380b chore: update lockfile, Node.js, and pnpm versions (#13057)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 00:34:41 +02:00
Zoltan Kochan
ec976bebdf fix(release): configure MSVC for Windows ARM64 (#13055) v12.0.0-alpha.13 2026-07-16 00:07:41 +02:00
Zoltan Kochan
aa0940d9c7 fix(release): add clang to Windows ARM64 path (#13054) 2026-07-15 23:51:28 +02:00
Zoltan Kochan
66663575f7 fix(release): use x64 host clang for Windows ARM64 (#13053) 2026-07-15 23:42:42 +02:00
Zoltan Kochan
49cd3e015f fix(release): select clang for Windows ARM64 builds (#13052)
* fix(release): select clang for Windows ARM64 builds

* fix(release): wait for clang installation
2026-07-15 23:33:00 +02:00
Zoltan Kochan
cfe58bf52f fix(release): install clang for Windows ARM64 builds (#13051) 2026-07-15 23:19:00 +02:00
Zoltan Kochan
e114fcbfdd chore(release): prepare pacquet 12.0.0-alpha.13 (#13049) 2026-07-15 23:06:25 +02:00
Rayan Salhab
d919e527cd fix(reporter): keep interactive prompts visible (#13035)
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>
2026-07-15 23:03:06 +02:00
Zoltan Kochan
aef537bd14 refactor(fs): flatten Windows junction commit into named helpers (#13048)
`create_junction` had grown to ~90 lines nested up to five levels deep,
with the "inspect the destination, then decide" logic duplicated between
the pre-rename and post-rename-failure paths. Extract the staging loop,
the commit step, a `Destination` inspection enum, and one helper per
failure outcome, so control flow reads top-to-bottom and each error-kind
invariant lives in one documented place: a completed destination reuses
via `AlreadyExists` (carrying a `ConcurrentCleanupWarning` only when
staging cleanup also fails); an inspection or rename failure never reports
`AlreadyExists`, so the caller can't mistake it for a reuse.

Isolating the rename-failure branch surfaced a latent bug: it returned the
raw rename error, which on Windows can be `AlreadyExists` when the rename
lost a race. If the winning link then vanished before the re-inspection,
`force_symlink_inner` would treat the now-missing link as reusable and
abort with a confusing error. The genuine-failure branch now strips an
`AlreadyExists` rename error to `Other`; every other kind (including
`NotFound`, which drives a mkdir + retry) passes through unchanged. Added a
Windows regression test for the invariant.
2026-07-15 23:02:35 +02:00
Benjamin Staneck
c7060baf23 fix(fs): make concurrent Windows junction creation atomic (#13009)
## Summary by CodeRabbit

* **Bug Fixes**
  * Resolved concurrent package linking failures on Windows when directory symlinks fall back to junctions.
  * Improved junction creation reliability and recovery under contention and missing parent directories.

* **Tests**
  * Added Windows concurrency regression coverage to stress simultaneous junction creation and verify correct reuse behavior.
  * Added Windows regression coverage for junction creation when intermediate parent directories are missing.

* **Chores**
  * Updated the Windows-focused changeset with a patch bump for the affected package.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-15 22:09:38 +02:00
Benjamin Staneck
43be121184 fix(cli): make pnpm setup install the native CLI globally (#13006)
The native setup command installs its downloaded executable by recursively
running `pnpm add -g --ignore-scripts file:<directory>`. The Rust add parser
did not expose the script flags, so setup failed before installation.

Apply the paired script override to both local and global add configuration.
For aliasless global `file:` directory selectors, read the local manifest and
use its package name before invoking the add pipeline. Normalize the directory
fetch path lexically so relative lockfile paths remain valid when temporary
directories cross symlinked filesystem roots, such as `/var` on macOS.

Add an integration test that exercises the exact setup command shape and uses
a failing install script to prove that `--ignore-scripts` reaches the install
configuration.

Related to https://github.com/pnpm/pnpm/issues/12955.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-15 22:08:00 +02:00
Mateusz Burzyński
888a6f38b9 fix(cli): match pnpm's JSON error output for publish --json failures (#13029)
Made pacquet's `--json` error reporting print to `stdout` in the raw form (without human-readable formatting). This allows the output to be machine-processed.
2026-07-15 22:05:54 +02:00
Yashas Gunderia
3c80449626 fix(self-update): link platform binaries from GVS dependency slots (#13037)
Self-update installs wrappers with lifecycle scripts disabled and then links the
native platform binary manually. With the global virtual store enabled, the
wrapper and its platform dependency can resolve to different hashed slots, so
searching only beside the wrapper's real path misses the binary and leaves the
published placeholder in place.

Prefer the wrapper's exact real-adjacent dependency, then fall back to its
install-root link. This supports sibling GVS slots without a parent-directory
module-resolution search and preserves flat and legacy virtual-store layouts.

Closes pnpm/pnpm#12962 and pnpm/pnpm#13036.
2026-07-15 21:27:49 +02:00
Victor Sumner
9948c5ada5 fix(pacquet): keep side effects out of frozen stores (#13013)
A disabled store index writer is not enough to make the side-effects-cache path read-only. Pacquet uploads lifecycle-script output to the content-addressable store before it queues the index update, so frozenStore installs could still add store files even though the queued update was discarded.

Skip side-effects-cache write preparation and upload when frozenStore is enabled. Keep lifecycle scripts running in the installed package. Add a regression test that compares the complete set and contents of regular store files before and after the build.

Related to pnpm/pnpm#11703.
2026-07-15 21:20:39 +02:00
Zoltan Kochan
124f2a50f0 fix(release): verify all npm release tarballs (#13045)
Release jobs could package and publish generated native or executable payloads
without a release-time signal if a packlist regression omitted a required file.
Add read-only verification jobs before each trusted-publishing job. They pack every
generated package, verify its declared literal payload files against the source
artifacts, and smoke-test the v11 plain-JS CLI, the v11 executable wrapper
with its native package, and the pnpr wrapper with its Linux native package.

Keeping the smoke tests outside OIDC-enabled publishing jobs prevents an
artifact executed during verification from obtaining a trusted-publishing token.
The verification tarballs live outside package directories so they cannot become
part of subsequently published archives.
2026-07-15 21:11:57 +02:00
Zoltan Kochan
b49c22471b fix(cli): render clean/purge Removing paths relative to cwd on Windows (#13046)
* fix(cli): render clean/purge Removing paths relative to cwd on Windows

`pnpm clean` / `pnpm purge` built its `Removing <path>` lines by stripping
the raw `current_dir()` from targets that are anchored on the
symlink-canonicalized `--dir`. On Windows those two representations diverge
(casing, 8.3 short names, junctions), so the prefix strip found no common
base and leaked the full absolute path into the output, failing the Windows
`clean` integration tests on main.

Derive the relative path against the canonicalized working directory via
`pathdiff::diff_paths` (which mirrors Node's `path.relative`, including
walk-ups), so the base shares the targets' representation. The workspace
test now builds its expected `Removing pkg/node_modules` text with the
platform separator instead of a hard-coded `/`.

* refactor(fs): extract root-guarded relative_path helper and reuse in clean

Address PR review feedback on the clean/purge Windows path fix:

- Hoist the `current_dir()` + `dunce::canonicalize` out of `print_removing`
  into `clean_builtin`, so the cwd is canonicalized once per command instead
  of once per removed path.
- Replace the bare `pathdiff::diff_paths` call with a shared
  `pacquet_fs::relative_path`, extracted from `symlink_dir`'s
  `relative_target_inner`. It carries the Windows `same_path_root` guard,
  so a target on a different drive/UNC share renders as the absolute path
  (matching Node's `path.relative`) instead of a re-anchored garbage path.
  `symlink_dir` now delegates to the same helper rather than duplicating it.
- Move the canonicalization rationale from the `print_removing` rustdoc into
  an implementation `//` comment at the cwd computation.
2026-07-15 20:57:56 +02:00
Zoltan Kochan
b75311cea7 ci: migrate workflows to Blacksmith runners (#13043)
Move non-npm-trusted-publishing GitHub Actions jobs to Blacksmith runner labels.

Use larger Blacksmith runners for heavier build, Docker, benchmark, Windows, and macOS jobs, while leaving the npm OIDC trusted-publishing publish jobs on GitHub-hosted runners. npm trusted publishing currently does not support self-hosted runner providers, so the TypeScript pnpm publish job, Rust pnpm publish job, and pnpr publish job keep their GitHub-hosted labels.

Keep benchmark artifact names stable so follow-up workflow_run jobs on the default branch keep matching the producing benchmark jobs.
2026-07-15 20:56:23 +02:00
Mon
8ded73fed4 docs: fix broken pacquet path in README (#13011)
The directory was renamed in #12913. Updating its reference in README to reflect this change.
2026-07-15 20:43:12 +02:00
Yashas Gunderia
4c3d0ed0f6 fix(pack): let package npmignore override workspace ignores (#13038)
A package-level .npmignore replaces inherited workspace ignore rules.
Guard both TypeScript and Rust paths and cover pnpm/pnpm#13032 without a negation.

Constraint: Preserve inheritance when no package .npmignore exists.
Rejected: Negation ordering | npm replaces ancestor ignores without a negation.
Confidence: high
Scope-risk: narrow
Directive: Keep TypeScript and pacquet precedence aligned.
Tested: 42 TS pack tests; 33 Rust packlist tests; compile, lint, Clippy, rustfmt.
Not-tested: Full monorepo suites.

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
2026-07-15 20:29:12 +02:00
John Doe
d768a8491a fix(fs-packlist): honor npm-packlist ignore-file priority when files field is set (#13005)
* fix(fs-packlist): honor npm-packlist ignore-file priority when files field is set

collect_own_files unconditionally enabled .gitignore and .npmignore via
WalkBuilder, ignoring npm-packlist's three-tier priority: (a) files field
present disables both, (b) root .npmignore present disables .gitignore,
(c) neither falls back to .gitignore. This caused platform packages
whose package.json lists files: ["pnpm"] alongside a .gitignore
containing "pnpm" to produce empty tarballs (the binary was excluded
by the walker before the files allowlist could include it).

Apply the three-tier priority conditionally in collect_own_files. The
ignore crate's WalkBuilder toggles are global, so the priority is
applied at the package root only; subdirectory behavior is documented
as a known divergence.

* style(fs-packlist): use raw string for assert message with inner quotes

Satisfies the perfectionist::avoidable_string_escapes dylint lint.
2026-07-15 19:45:43 +02:00
coyaSONG
9685e3af1e fix(exe): correct package source links (#12996)
The `@pnpm/exe` package metadata still pointed to the pre-reorganization
pnpm/artifacts/exe directory, so its repository and homepage links returned
404.

Point both fields at pnpm11/pnpm/artifacts/exe.

Closes pnpm/pnpm#12843.
2026-07-15 19:44:08 +02:00
Alessio Attilio
d02da20d4d feat(cli): add clean/purge commands (#12999)
Native Rust implementation of the clean and purge commands, achieving
100% parity with the legacy TypeScript implementation.

Both commands safely remove node_modules contents from the current
project or every workspace project. A clean/purge script in package.json
overrides the built-in command when present. Lockfile removal is
supported via --lockfile. Virtual store directories outside the project
root are never removed.

related to: #11633
2026-07-15 19:38:40 +02:00
Zoltan Kochan
0f19557421 chore: update lockfile, Node.js, and pnpm versions, and fix Windows store-cache installs (#13026)
* chore: update lockfile, Node.js, and pnpm versions

* ci: force a cold pnpm store to confirm the Windows cache corruption

The Windows Rust CI install fails with ERROR_DIRECTORY (os error 267)
creating symlinks over a store restored from actions/cache. With
enableGlobalVirtualStore on, the cached store holds the global-virtual-store
`links/` reparse points, which tar can't round-trip, so the restored store
is corrupt and a warm install fails on symlink creation. The failure moves
between symlinks run to run (parallel linking picks a different victim),
which is why the alpha.12 symlink-writer fix could not cover it.

Bump the cache key to force a cold store. If Windows goes green, the cache
is confirmed as the cause and the durable fix is to stop caching `links/`.

* fix(fs): heal a dangling junction parent when materializing a symlink

alpha.12 tried to recover from a dangling `node_modules` junction (e.g. a
store restored from a tar-based CI cache) by keying on ERROR_DIRECTORY
(os error 267), but the real path is different: `CreateSymbolicLinkW`
through a dangling junction returns NotFound, and the NotFound recovery's
`create_dir_all` then fails with AlreadyExists (os error 183) because the
junction still occupies the slot. So the 267 branch never fired and the
install aborted.

Handle it where it actually happens: when `create_dir_all` on the
symlink's parent fails and the parent is a reparse point, remove the
dangling junction (unlinking only the junction, never a live target),
rebuild a real directory, and retry. Drops the dead 267 branch.

* ci: exclude the global-virtual-store links/ tree from the pnpm store cache

The GVS `links/` tree is Windows directory junctions, which tar
(actions/cache) can't round-trip: a restored store came back with
dangling junctions and installs failed creating symlinks. Exclude
`links/` from the cached path so only the CAS content is cached; pnpm
regenerates `links/` fresh on every install. The `nolinks` key abandons
the old caches that still hold `links/`.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-15 13:49:42 +02:00
Zoltan Kochan
d5b4b7bd5d chore(release): pacquet 12.0.0-alpha.12, pnpr 0.1.0-alpha.3 (#13023)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
pnpr@0.1.0-alpha.3 v12.0.0-alpha.12
2026-07-15 11:42:38 +02:00
Zoltan Kochan
5b696ae2c3 ci: auto-tag released versions when the release PR merges (#13004)
* ci: auto-tag released versions when the release PR merges

The release tag was pushed by hand after merging a release PR, which let
the tag name drift from the version committed on the merge commit. When
they disagree, release.yml's plan job publishes the committed version but
github-release-rust drafts a GitHub release for `v<committed-version>` — a
tag that doesn't exist as a git ref — and the create-release call returns
403 "Resource not accessible by integration" even with contents: write.

Add tag-release.yml: on a merged `release-pr/*` PR it derives each
product's tag from the committed manifest (the same manifests plan reads),
skips versions already tagged on the remote, and pushes the new tags with
UPDATE_LOCKFILE_TOKEN so the push triggers release.yml (a tag pushed with
the default GITHUB_TOKEN would not). Deriving the tag from the committed
version makes the off-by-one impossible, and the tag exists as a real ref
before release.yml runs, so the draft-release step no longer 403s.

A release PR can bump several products at once, landing several tags and
starting a release run apiece; add a `release` concurrency group so they
serialize and plan's already-published gate makes the later runs no-ops.

Drop the now-obsolete "push the version tag after merging" line from the
release PR body.

* ci: harden the auto-tag workflow per review

- Gate on create-release-pr.yml's exact contract (same-repo head branch
  named `release-pr/<base>`) instead of a `release-pr/` prefix, so a fork
  PR or an unrelated same-prefixed branch can't reach the PAT tag push.
- `set -euo pipefail` so a failed `git ls-remote` aborts instead of
  yielding an empty tag set that makes every version look new.
- `git ls-remote --refs` to drop annotated tags' peeled `^{}` entries.
- Reword the header comment to state the current contract rather than
  what it replaces.

* ci: tag pnpr in its own namespace to avoid pnpm tag collisions

pnpr's version starts at 0.x, and pnpm's tag history already holds 166
v0.x tags (v0.1.0, v0.2.0, …) plus the v1.x line. Tagging pnpr as
`v<version>` shares that namespace: once pnpr cuts a stable 0.x that
matches an old pnpm tag, the existence check would treat the ancient
pnpm tag as pnpr's and silently skip it, so pnpr would never get its
trigger tag or release.

Give pnpr its own `pnpr-v<version>` tag (it has no GitHub release and no
external consumer of the tag — it ships via npm and GHCR — so it's free
to move) and trigger/validate release.yml on `pnpr-v*.*.*` alongside
`v*.*.*`. plan reads committed manifests regardless of the triggering
tag, so a pnpr-only release still publishes correctly.

* ci: adopt the Changesets-style pnpr tag scheme

Use the widely-recognized `pnpr@<version>` form (the Changesets/Lerna
convention) for pnpr's release tag instead of the ad hoc `pnpr-v<version>`
from the previous commit. release.yml triggers and validates
`pnpr@*.*.*` alongside `v*.*.*`; pnpm (v11.x) and pacquet (v12.x) keep
`v<version>`, which get.pnpm.io's install scripts depend on.
2026-07-15 11:39:52 +02:00
Zoltan Kochan
7f57469f71 fix(fs): repair dangling junction parent before symlinking on Windows (#13020)
A warm `pnpm install` that re-links over a global store restored from a
CI cache fails on Windows with ERROR_DIRECTORY (os error 267): tar can't
round-trip a Windows reparse point, so a slot's `node_modules` junction
comes back dangling. `create_dir_all` accepts it because a reparse point
keeps the directory attribute, but `CreateSymbolicLinkW` can't create a
child link through a dangling junction, and the error is neither NotFound
nor AlreadyExists, so `force_symlink_inner` aborted the install.

On ERROR_DIRECTORY, when the link's parent is a reparse point, remove the
broken junction, rebuild a real directory, and retry once. A dangling
reparse point has no live children to lose, so the rebuild is safe. The
parent is the right target: `create_dir_all` runs before the symlink and
would have failed earlier if any ancestor above `node_modules` were the
broken component. Reparse points are detected via the raw attribute
because `FileType::is_symlink` is false for junctions.

This is a different trigger of the same errno as the scoped-alias
forward-slash fix in pnpm/pnpm#12991 (no forward slash is involved here),
so that normalization does not cover it.
2026-07-15 11:37:16 +02:00
Khải
86e613c2f4 docs(rust/cli): clean up --help texts (#13008)
Make pacquet's --help read like user-facing help rather than developer API
docs, and re-enable the perfectionist::clap_help_markdown dylint rule.

The clap doc comments are dual-purpose: cargo doc API docs and the text
clap prints for --help. They had accumulated developer and parity prose
that leaked into the terminal (mirrors-pnpm notes, config-field and
store-layout mechanics, overrides_with pairing, env-var names, ERR_PNPM_*
codes, internal type references, not-yet-ported caveats). Rewrite them
across the whole CLI into concise user-facing help and drop those notes;
no flag behavior changes, only doc-comment text moved.

Clean out the markdown the rule flags in the same pass: intra-doc links to
internal types become the CLI flag they describe or plain words, one inline
issue citation leaves the help text, and an angle-bracketed path placeholder
that parsed as an HTML tag is wrapped in a code span. Re-enable the rule by
dropping it from the dylint.toml disable list; keep code spans via the
rule's ignore_constructs setting since they read fine in a terminal.

Closes pnpm/pnpm#12718
2026-07-15 11:36:34 +02:00
Zoltan Kochan
30a59d0209 chore: update lockfile, Node.js, and pnpm versions (#13003)
* chore: update lockfile, Node.js, and pnpm versions

* chore: pin packageManagerDependencies specifier to the resolved pnpm version

The update-lockfile workflow recorded the next-12 dist-tag as the
packageManagerDependencies specifier, but the manifest pins pnpm to
12.0.0-alpha.11. pacquet validates the specifier against the manifest, so a
later --frozen-lockfile install failed with
ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE. Record the resolved pin so the
specifier matches the wanted version.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 22:04:18 +02:00
Zoltan Kochan
338dda6f30 ci: run Rust CI on root package metadata changes (#13002) 2026-07-14 21:19:01 +02:00
Zoltan Kochan
e36df0a7a3 chore(release): pacquet 12.0.0-alpha.11 (#13001)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 20:51:26 +02:00
Zoltan Kochan
a9b75f4d51 ci: disable git hooks in the release-pr and update-lockfile workflows (#12938)
* ci: disable git hooks in the create-release-pr workflow

The pre-push hook ran the full TS compile/lint and the Rust clippy/doc
sweep inside the workflow's "Commit and push" step, because the shallow
clone and URL-based push remote make the hook's change detection always
conclude that Rust sources changed. Setting HUSKY=0 on the job keeps
husky from wiring any hooks on the runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: disable git hooks in the update-lockfile workflow too

Same failure mode as create-release-pr: the full install wires the husky
hooks and the push step runs the pre-push TS compile/lint on every daily
run. It only skips the Rust sweep when the remote chore/update-lockfile
branch already exists, because the shallow clone lacks the remote tip and
the hook's git log fails silently; on a fresh branch it does the full
cargo clippy/doc pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:43:38 +02:00
Zoltan Kochan
e0c790c5e9 fix(self-update): store the resolved pnpm pin as the packageManagerDependencies specifier (#12992)
`pnpm self-update <dist-tag>` resolved the tag to an exact version and wrote it
into devEngines.packageManager.version, but recorded the raw dist-tag as the
packageManagerDependencies specifier in pnpm-lock.yaml. pacquet validates that
specifier against the manifest under --frozen-lockfile, so the mismatch made a
later frozen install fail with FrozenLockfileOutdated (the cause of the invalid
lockfile in pnpm/pnpm#12989, hot-fixed in pnpm/pnpm#12990).

Both self-update call sites now record the resolved pin: the project-pin path
stores the updated devEngines constraint (the value a later install reads back)
via a new package_manager_pin_specifier helper, and the global-switch path
stores the resolved version. The TypeScript CLI already derived the specifier
from the resolved version and is unaffected.
2026-07-14 20:42:17 +02:00
Zoltan Kochan
d52abd73ba fix(fs): rewrite symlink paths to native separators on Windows (#12991)
A scoped dependency's node_modules/@scope/name link path is built by
joining the whole @scope/name alias as one segment. Rust's Path::join
appends segments verbatim (unlike Node's path.join, which normalizes),
so the / inside the alias survived into an otherwise \-separated Windows
path and reached CreateSymbolicLinkW, which rejects forward-slash paths
— the long store paths reach it in verbatim \\?\ form where / is a
literal filename byte — with ERROR_DIRECTORY (os error 267), aborting
the install.

This is the same failure class as the global-virtual-store slot-path fix
in pnpm/pnpm#12976, which normalized the slot suffix at its construction
sites. Rather than chase every join site, rewrite paths to native
separators at the symlink writer's own choke points: symlink_dir
(covering the hoisted linker's direct calls) and force_symlink_dir's
entry (so its read_link / remove_dir / rename / create_dir_all retry
steps all see a native path too). The rewrite only allocates when a /
is actually present and is a no-op on Unix.

The TypeScript CLI is unaffected: Node's path.join already normalizes
separators on Windows, so no counterpart change is needed.
2026-07-14 19:53:10 +02:00
Zoltan Kochan
6e65538a8a chore: fix lockfile (#12990) 2026-07-14 00:17:46 +02:00
Zoltan Kochan
d3ed5da76c chore: update lockfile, Node.js, and pnpm versions (#12989)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 00:10:37 +02:00
Zoltan Kochan
6dcfadd5ae fix(release): sync Rust product versions via the meta-updater (#12988)
The Rust CLI and pnpr embed the versions their release builds report and
verify (`PNPM_VERSION` in pnpm/crates/config/src/defaults.rs, the crate
version in pnpr/crates/pnpr/Cargo.toml, and its Cargo.lock entry). These
were mirrored from the npm wrappers by `syncRustVersions` in bump.ts — a
step separate from the meta-updater, so running `pnpm version -r` without
the full bump flow bumped the wrappers (pacquet 12.0.0-alpha.10, pnpr
0.1.0-alpha.2) while the Rust sources stayed at the previous versions. The
release workflow's "Verify the committed version" step then failed, and
nothing caught the drift before the tag.

Move the sync into the meta-updater as a set of Rust-source file handlers,
so it is written by `pnpm update-manifests` and, crucially, validated by
`meta-updater --test` in pre-push and CI — a missed sync now fails locally
instead of at release time. bump.ts drops the redundant `syncRustVersions`
and runs `pnpm update-manifests` after `pnpm version -r`; the root `bump`
script no longer needs its own trailing `update-manifests`.

Regenerating brings the Rust sources up to the already-bumped wrapper
versions (alpha.10 / alpha.2), unblocking the release.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v11.13.0 v12.0.0-alpha.10
2026-07-13 23:24:21 +02:00
Zoltan Kochan
1c887b2e98 chore(release): don't re-lint the pnpm CLI at publish time (#12987)
* chore(release): don't re-lint the pnpm CLI at publish time

The pnpm package's `prepublishOnly` ran `compile`, which includes
`eslint --fix` over the src and test files. On the macOS release runner
the `import-x/no-extraneous-dependencies` allow-list for test files did
not take effect, so linting reported hundreds of spurious errors and
aborted the publish of the `pnpm` wrapper — the last package the release
job publishes — leaving 11.13.0 unpublished.

Split the artifact build out of `compile` into a new `build` script and
point `prepublishOnly` at it, so publishing no longer re-lints. Lint is
still run by `compile` (used for local development and `pnpm test`) and
enforced by CI on every PR, so a merged, tagged release commit is already
linted.

`build` runs the same tsgo build, bundle, and asset-copy steps `compile`
did; only the lint step is removed from the publish path. The scripts are
generated by the meta-updater, so the change is made there and the
generated `pnpm/package.json` is regenerated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: bump pnpm 12 and pnpr

* fix: update pnpm v12

* chore: bump versions

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 23:05:14 +02:00
Zoltan Kochan
682f57e773 chore(release): 11.13.0, pacquet 12.0.0-alpha.9, pnpr 0.1.0-alpha.1 (#12986)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-13 22:19:04 +02:00
Zoltan Kochan
ddd90eaff8 fix(pacquet): base repeat-install freshness on filesystem mtime at nanosecond precision (#12981)
Pacquet's repeat-install fast path recorded lastValidatedTimestamp from
the wall clock but compared it against filesystem mtimes. On a runner
whose wall clock runs ahead of the filesystem's mtime clock (~2-3 ms on
Blacksmith Linux), a file edited just after an install got an mtime below
the baseline and the edit was missed; and because installs are fast, the
lockfile and a same-instant edit could share a millisecond, which the
ms-truncated comparison also missed.

Record the baseline from filesystem mtimes (max of the lockfile and the
validated manifests) so it shares a clock with the files it is compared
against, and compare the subject at nanosecond precision against the
rounded-down millisecond reference. Mirrors pnpm's checkDepsStatus, which
already keys single-project freshness off the lockfile mtime.
2026-07-13 22:06:22 +02:00
Zoltan Kochan
ed445b51dd feat(versioning): add versioning.epics for banded monorepo versioning (#12979)
Implements epics from the native monorepo versioning RFC in both the
TypeScript release engine (`@pnpm/releasing.versioning`) and the Rust one
(`pacquet-versioning`), kept behaviorally identical.

An epic ties a group of member packages to a lead package, constraining
each member's major version to the band derived from the lead's major
(while the lead is on major M, members live in M*100 .. M*100+99). Members
move independently inside the band; when a release plan takes the lead to a
new stable major, every member re-bases to the band floor in the same plan
(deferred while the lead sits on a prerelease lane). Membership is matched
with pnpm's package selectors: name globs, ./-prefixed directory globs, and
!-prefixed negations.

Conflicting configuration is rejected: a package matched by two epics, a
fixed group straddling an epic boundary, and an unknown or non-releasable
lead.
2026-07-13 22:02:04 +02:00
Zoltan Kochan
45f682fe46 test: opt version -r changelog test into repository storage (#12984)
The "bare version -r applies the release plan and cleans up the intent" test
asserts the committed-CHANGELOG flow: it reads lib/CHANGELOG.md and expects the
consumed intent to be deleted at version time. Since composing changelogs at
publish time (registry storage) became the default, `pnpm version -r` no longer
commits CHANGELOG.md — it parks the section under .changeset/changelogs/ and
defers intent GC until the registry confirms publication. With no verifyPublished
registry in the test, the changelog file is never written (ENOENT) and the
intent is never collected, so the test failed.

Opt the test into `versioning.changelog.storage: repository` so it keeps
exercising the committed-changelog + intent-cleanup path, matching the pattern
the versioning package's lifecycle tests already use. Registry-storage behavior
(parked section, deferred GC) is covered by those lifecycle tests.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 21:04:01 +02:00
Zoltan Kochan
a72c2b1953 test: stabilize lockfile-verify cache content-change test on coarse-mtime runners (#12980)
`tryLockfileVerificationCache` has a stat fast path that trusts the cached
hash when a lockfile's (size, mtimeNs, inode) still match the recorded values,
skipping the content hash. The "miss when content changed even if size happens
to match" test rewrote the lockfile in place (rm + writeFile) with identical
byte length, expecting the stat shortcut to bail so the hash check rejects the
new content.

On CI runners with coarse (>=1s) filesystem mtime granularity, both writes land
in the same mtime tick, and the freed inode gets reused, so all three stat
fields match. The stat shortcut then fires and returns a hit before the hash
check runs, so the test saw `hit: true` instead of `false`.

Write the replacement to a sibling file and rename it in, as the sibling test
already does. Two files that coexist must have distinct inodes, so the renamed
replacement is guaranteed a different inode from the original regardless of
mtime granularity — the stat shortcut always bails and the content hash is what
rejects the change, which is what the test means to exercise.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 21:02:47 +02:00
Zoltan Kochan
0a68c77935 test: fix process-cleanup fixtures aborting on implicit install in CI (#12978)
The errorHandler process-cleanup tests run `pnpm exec`/`pnpm run` directly
inside the shared exec-error-exit and multiple-scripts-error-exit fixtures.
`prepareFixtures` pre-installs every fixture with the default store, so the
committed `node_modules/.modules.yaml` records that store. At test time the
CLI runs with `store_dir=../store`, and the default `verifyDepsBeforeRun:
install` spawns an implicit `pnpm install` whose `checkCompatibility` throws
`UnexpectedStoreError` on the store mismatch and tries to purge the modules
directory. In the non-TTY test subprocess (which also never inherits `CI`),
that purge aborts with `ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY` before the
scripts ever run, so the expected `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL` is never
produced.

These fixtures test process-tree cleanup, not dependency verification, and have
no dependencies, so set `verifyDepsBeforeRun: false` to skip the implicit
install entirely.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:33:52 +02:00
Zoltan Kochan
29efb4ae49 fix: honor same-second edits on whole-second-mtime filesystems (#12975)
The optimistic repeat-install fast path and verify-deps-before-run decide
whether a package.json, .pnpmfile.cjs, or patch file changed since the
last install by comparing its mtime against the last-validated timestamp
with a strict `>`. On a filesystem that records mtimes at whole-second
resolution (ext4 with 128-byte inodes, HFS+, some CI runner disks) a file
edited in the same second as the install gets an mtime that rounds down
below the timestamp, so the edit looks unchanged, the fast path
short-circuits, and re-resolution is skipped — the second install is a
no-op and keeps the stale result.

Detect a whole-second mtime (no sub-second component) and treat its whole
second as possibly-after the reference, falling through to the
authoritative content check instead of trusting the rounded-down mtime.
Erring toward "modified" only ever runs the content check; it never skips
a needed install. On sub-second filesystems the mtime carries a
fractional part, so the comparison stays exact and the common case is
completely unaffected — the existing deps-status unit tests pass
unchanged.

Landed in both stacks: pacquet's optimistic_repeat_install and pnpm's
checkDepsStatus, with a regression test on each side that forces a
whole-second mtime so the coarse-filesystem path is exercised on any
filesystem.
2026-07-13 18:32:16 +02:00
Zoltan Kochan
4124f481ba test: opt change/version integration tests into repository changelog storage (#12977)
`compose changelogs at publish time (registry storage)` (#12971) made
`changelog.storage: registry` the default, so `pnpm version -r` no longer writes
a committed CHANGELOG.md — it parks each release's section under
`.changeset/changelogs/` for publish-time composition. The `change` command
integration tests (added in #12953) still assert committed CHANGELOG.md files,
so they broke on `main`. #12971 gave the versioning unit tests the same
treatment (a `repository()` settings helper); this does the equivalent for the
CLI integration tests by opting their workspace into `repository` storage.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:31:55 +02:00
Zoltan Kochan
c03b2d45a5 fix: create global virtual store symlinks with native path separators on Windows (#12976)
The global-virtual-store slot suffix is formatted as `@/<name>/<version>/<hash>`
with `/` separators because it doubles as a cross-platform canonical id (it
feeds hashing and lockfile comparison). Joining it onto the store dir with a
plain `PathBuf::join` preserved those forward slashes on Windows, and they then
reached `CreateSymbolicLinkW`, which rejects forward-slash paths with
`ERROR_DIRECTORY` (os error 267) — aborting `pnpm install` on Windows whenever
the global virtual store is enabled.

Add a `join_global_virtual_store_path` helper that expands the `/`-separated
suffix into native path components, and route the slot-path join sites through
it (`VirtualStoreLayout::slot_dir` plus the two config-deps installer joins).
No-op on Unix; produces `\` paths on Windows.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:30:33 +02:00