Both utilities lived in the pnpm/components Bit workspace, where their
component names collide with same-named components elsewhere in the Bit
registry. Move the code here and publish it under new names:
`@pnpm/util.lex-comparator` -> `@pnpm/text.ordinal-comparator`
`@pnpm/config.nerf-dart` -> `@pnpm/config.registry-auth-key`
The new names describe what the utilities do: the comparator is ordinal
rather than locale-aware, and the mapped URL is the key that registry
settings are stored under in `.npmrc`. The exported functions keep their
names, so consumers only change their import specifiers.
Implementations and tests are carried over unchanged. The rationale from
the components' docs pages moves into doc comments: why `localeCompare`
cannot be used for values compared across machines, and where `nerfDart`
originates.
No pacquet counterpart is needed. The Rust stack has its own
implementations of both and no user-visible behavior changes.
Six independent pacquet-side defects found while installing and building
n8n with pnpm 12. Each is a divergence from the TypeScript CLI, so every
fix moves pacquet onto pnpm's existing behavior rather than inventing new
behavior.
Finding 2 — an `allowBuilds` placeholder written by pnpm made
`WorkspaceSettings` refuse to load the config at all. The value is now an
`AllowBuild` enum; only decided entries reach `Config::allow_builds`,
matching `createAllowBuildFunction`. The raw value stays on
`WorkspaceSettings` so `pnpm config list` and the `updateConfig` hook
still see what the file says.
Finding 3 — `diffy`'s matcher is byte-exact, while `@pnpm/patch-package`
compares lines with trailing whitespace stripped and retries a hunk
within twenty lines of its recorded position. `pacquet-patching` now
applies hunks itself with those tolerances, keeping `diffy` for parsing.
The file is modeled as `split('\n')` throughout, so untouched CRLF lines
keep their `\r` and a file without a final newline keeps that shape.
Finding 4 — `Config::user_agent` is threaded to install lifecycle
scripts, `pnpm run`, `exec`, and `dlx`, which previously saw nothing or
the bare string `pnpm`. It still reports `node/?` rather than the host
Node version; filling that in costs a `node --version` spawn on every
command, which is a separate trade-off to make.
Finding 5 — `/pattern/` script selectors select every matching script in
single-project and recursive runs, mirroring `tryBuildRegExpFromCommand`
+ `getSpecifiedScripts`. This adds `regex` to the workspace
dependencies; it was already in the lock transitively.
Finding 6 — concurrent `packageManager` switches raced on the shared
global-virtual-store slot: the destructive re-stage removed a directory
another process was still writing, and the native-binary relink used
unlink-then-hardlink, pulling the executable out from under a sibling
running it. The install is now serialized by an advisory lock (a new
`pacquet_fs::DirLock`, which gives up rather than failing so a lost lock
is never worse than today), and the relink skips an already-correct
destination and otherwise swaps via rename.
Finding 8 — settings drift under a frozen install reports
`ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` naming the one field instead of
`ERR_PNPM_OUTDATED_LOCKFILE` with the whole map dumped; ignored build
scripts keep their `(patch_hash=…)` suffix; and a deprecated package is
reported once, since pacquet's resolver re-emits one it later meets at a
shallower depth.
All six are pacquet-only bugs; the TypeScript CLI already behaves this
way, so there is nothing to mirror.
Closespnpm/pnpm#13322
Reverts the lexicographical sorting of scripts introduced in pnpm/pnpm#13074, as this was a breaking change in behaviour. When using `pnpm run --sequential /regex/`, scripts will now be executed in the order they are listed in `package.json`, giving developers control over the order without needing odd script prefixes. Fixespnpm/pnpm#13174.
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.
Main thread panicked: begin > end (105 > 28) when slicing
`@pnpm/npm-lifecycle@1100.0.0(patch_hash=e3541c…)(supports-color@10.2.2)`
at pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:2490:51
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
The recursive command runners sorted opts.selectedProjectsGraph, which keeps
only the selected projects as keys. Edges to unselected projects were dropped,
so a transitive dependency between two selected projects (a -> b -> c with only
a and c selected) was lost and the two ran out of order.
sortFilteredProjects resolves the order through the full workspace graph: for
each sorted project it walks dependencies, tunneling past unselected projects
and stopping at selected ones, so a transitive relationship becomes a direct
edge. Projects without a real dependency stay in the same chunk, preserving
concurrency.
Prod-only filters (--filter-prod) prune dev edges. Their selected projects are
sorted through the prod-pruned full graph, so transitive prod deps are honored
without reintroducing the dropped dev edges. In a mixed selection each project
is sorted through the graph that matches how it was selected: regularly filtered
projects through the full graph, prod-only ones through the prod-pruned graph.
Carrying prod-only projects on the full graph would let a dev-only reverse edge
form a cycle and collapse a real prod dependency and its dependent into one
chunk.
Fixespnpm/pnpm#8335
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
The dlx cache path is <cacheDir>/dlx/<key>/<prepare>/node_modules/.pnpm/
<pkgId>/node_modules/<pkg>. The <key> (64-char sha256 hex) and <prepare>
(<time>-<pid> in hex) segments are dlx overhead on top of pnpm's already-
deep virtual-store layout. For a transitive dep with a long name this tips
the package directory over Windows' MAX_PATH (260) — measured at 259 chars
for @pnpm.e2e/pre-and-postinstall-scripts-example in the dlx e2e test. A
lifecycle script then runs with that directory as its cwd, and CreateProcess
fails to resolve an over-length cwd, which Node surfaces as the confusing
"spawn C:\Windows\system32\cmd.exe ENOENT". Flaky rather than constant
because the <prepare> and temp-dir segments vary in length, straddling 260.
Shorten both dlx-specific segments:
- cache key: createShortHash (32 hex, 128 bits) instead of createHexHash
(64). pacquet already truncated to 32, so this also restores parity.
- prepare dir: encode time and pid in base36 instead of hex.
* fix(dlx): make failed-install cache cleanup best-effort
On Windows, `pnpm dlx` could fail with a spurious "EBUSY: resource busy
or locked, rmdir" error. When an install into the dlx cache failed, the
catch block removed the partially-populated prepare dir with
`fs.promises.rm(cachedDir, { recursive: true, force: true })`. That call
has no retries, so it died on the same lingering Windows handle (a
just-run install script's child process, or antivirus scanning freshly
written files) and threw EBUSY — which then replaced and masked the
original install error.
Make the cleanup best-effort: swallow its failure so the original error
always surfaces, and add `maxRetries`/`retryDelay` so the removal itself
succeeds once the transient lock clears. A leftover prepare dir is
harmless — it has a unique name and findCache only trusts the `pkg`
symlink.
The pacquet port already removes the prepare dir best-effort (`let _ =
fs::remove_dir_all(...)`) and returns the original error, so the
user-visible behavior already matches; only the (non-observable) retry
is absent there.
* fix(dlx): log dlx cache cleanup failures instead of swallowing them
Catch the best-effort cache cleanup with a narrow handler that logs the
failure via logger.warn (mirroring tryRemovePkg in modules-cleaner's
prune) instead of a blanket `.catch(() => {})`. The original install
error is still the one rethrown, so cleanup failures stay visible without
ever masking the real cause.
The TypeScript pnpm CLI freezes at v11; pnpm 12 will be the Rust pacquet
port. To make that split legible, all TypeScript source, test, and build
directories move under a new top-level pnpm11/ directory. The name states
the version boundary rather than implying a behavioral fork, since the two
stacks are meant to behave identically.
Scope is source-only: the shared workspace root stays at the repo root.
pnpm-workspace.yaml, package.json, pnpm-lock.yaml, .pnpmfile.cjs,
.meta-updater, __patches__, .changeset, .husky, and the lint/spell configs
remain in place, so one pnpm workspace and one Cargo workspace still span
all three products. pnpr/client and pacquet/tasks/registry-mock stay as
cross-product workspace members.
Rewiring the move required:
- pnpm-workspace.yaml globs prefixed with pnpm11/
- root package.json script paths, eslint.config.mjs, tsconfig.lint.json,
.gitignore, and CODEOWNERS updated
- .meta-updater/src/index.ts literals repointed (pnpm11/pnpm/package.json,
pnpm11/__utils__, pnpm11/__typings__, and the main package directory)
- regenerated every moved package's repository/homepage URL via meta-updater
- pnpm11/pnpm/bundle-deps.ts and __utils__/scripts/src/typecheck-only.ts
climb one more level to reach the repo root
.meta-updater stays at the repo root because @pnpm/meta-updater resolves
its config at <cwd>/.meta-updater/main.mjs.
TS CI (.github/workflows/ci.yml) now only runs when pnpm11/-relevant paths
change, via a dorny/paths-filter changes job plus a TS CI / Success
aggregate gate; branch protection should require only that gate.