* chore(release): 11.28.7, pacquet 12.15.0, pnpr 0.1.0-alpha.16
* docs(release): curate 11.28.7, pacquet 12.15.0, and pnpr 0.1.0-alpha.16 release notes
Add lead paragraphs and group the patch entries by what the reader was
doing, with security fixes first on each page.
- v11: lead with the registry metadata name check, pinned pnpm
trustPolicy check, maxTarballUnpackedSize, and the removal of registry
revisions; merge overlapping entries for minimumReleaseAge metadata
revalidation, frozen-lockfile directory dependency checks,
syncInjectedDepsAfterScripts, verifyDepsBeforeRun, pnpm sbom, and
unreadable config settings.
- v12: order minor highlights from config includes and
lockfile.integrityTarget down to interactive update styling; drop the
pnpr-only distribution note from the CLI changelog; merge overlapping
entries for workspace and pnpr install speedups, minimumReleaseAge and
If-Modified-Since revalidation, frozen-lockfile and injected package
checks, verifyDepsBeforeRun, pnpm prune, pnpm pack, pnpm licenses
list, pnpm self-update, pinned pnpm flag forwarding, and WebContainer
fixes.
- pnpr: lead the minor section with the breaking ecosystem URL prefix,
hosted storage layout, artifacts.orgs, and strict config schema
changes; fold dist.pnpr tarball metadata into one entry and drop the
redundant /metrics note; lead the patch section with reserved-username
and auth security fixes.
- Use one issue-link label style across the release pages.
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Decide whether `node_modules` is up to date in one place for both `pnpm install` (`optimisticRepeatInstall`) and `verifyDepsBeforeRun`.
- In Rust (`pnpm-package-manager`), combine the static workspace-state and filesystem checks with the content freshness checks into `freshness::check_freshness`, parameterized only by `GatePolicy`, so `check_optimistic_repeat_install_with_state` and `check_deps_status_before_run` call the same decider.
- Share `gvs_build_markers_complete` and `check_strict_dep_builds_for_fast_path` between `install_already_up_to_date` and `repeat_install_verdict`, and share `recorded_workspace_state` with repeat-install test fixtures.
- Fix two Rust gate divergences uncovered by unifying `check_freshness`: `Gate::Install` now rejects drifted `configDependencies`, and `Gate::Run` now rejects a missing branch lockfile when `useGitBranchLockfile` is enabled instead of letting `node_modules/.pnpm/lock.yaml` stand in for it.
- In TypeScript (`@pnpm/deps.status` and `@pnpm/exec.commands`), check `catalogResolutionsAreUpToDate` in `assertWantedLockfileUpToDate`, avoid mutating `config.ignoredOptionalDependencies` in place, share `recordValidatedWorkspaceState` between root-project and workspace checks, respect `opts.lockfileDir` in `checkRootProjectDepsStatus`, and warn when a `verifyDepsBeforeRun` install leaves `node_modules` out of date.
Closes https://github.com/pnpm/tasks/issues/142
Generated with Antigravity (Claude Opus 4.7)
Under `sharedWorkspaceLockfile: false` and `injectWorkspacePackages: true`, an
injected workspace dependency is copied into the dependent project and covered
by that project's own install, whereas a dependency declared with the `link:`
protocol remains a symlink whose target project still needs its own install.
- In `pnpm-cli` (`projects_to_verify`) and `pnpm-package-manager`
(`WorkspaceProjectIndex::dep_targets`), keep walking `link:` edges when
`!config.shares_one_lockfile() && config.inject_workspace_packages` instead
of dropping all workspace dependency edges.
- In `@pnpm/workspace.projects-graph`, rewrite `link:` prefixes to `file:`
before passing specs to `@pnpm/npm-package-arg`, which otherwise throws
`EUNSUPPORTEDPROTOCOL` and drops `link:` workspace edges from the project
graph used by `--filter` and `selectProjectsToVerify`.
Closes https://github.com/pnpm/tasks/issues/179
Unify repeat-install freshness and `verifyDepsBeforeRun` validation across
pnpm v11 and v12 (Closespnpm/tasks#161, pnpm/tasks#157, pnpm/tasks#148):
1. Centralize v11 workspace state updates in `@pnpm/workspace.state`
(`preserveCarriedSettings`) and `@pnpm/installing.commands`
(`workspaceStateUpdate.ts`), preserving carried `dev`/`optional`/`production`
settings when `ignoredWorkspaceStateSettings` ignores them and preserving
v12-recorded `autoDedupe`. A filtered install that prunes direct
dependencies no longer records the workspace root as installed.
2. Hold `{.}...` workspace dependencies for non-recursive `pnpm run`,
`pnpm exec`, and `pnpm restart` inside a workspace project in v11
(`selectProjectsToVerify.ts`).
3. Align v11 recorded validation (`checkRecordedWorkspace`) and legacy
unrecorded validation (`checkModifiedSinceLastValidation`) in
`@pnpm/deps.status` with v12's `check_freshness`, including empty-project
handling, `dedupeDirectDeps`, `dedupePeerDependents`, `autoInstallPeers`,
`excludeLinksFromLockfile`, catalog/override config comparison, and
`opts.lockfileDir` resolution.
4. Forward effective CLI flags (`--lockfile-dir`, `--modules-dir`,
`--virtual-store-dir`, `--store-dir`, `--dir`, `--workspace-root`, `--prod`,
`--dev`, `--no-optional`, `--filter`, `--filter-prod`, `--test-pattern`,
`--changed-files-ignore-pattern`, and `--config.*`) when
`verifyDepsBeforeRun: install` spawns `pnpm install` in both v11 and v12.
5. Align `GatePolicy::dedupe_peers` and empty-project predicates in v12
`pnpm-package-manager`, and run `repeat_install_oracle.rs` across v12, v11,
and mixed v11/v12 writers in CI, with a longer nextest period for the
peer convergence harness that shares the job.
6. Parse `--config.<key>=<value>` with the type the command declares for
`<key>` in v11, so the list and number options forwarded to the spawned
install arrive as lists and numbers; forward `--npmrc-auth-file` in v11
and `--test-pattern` / `--changed-files-ignore-pattern` in v12, so both
forward the same set; and skip the current-lockfile read for a project
whose manifest changed but whose lockfile did not, as v12 does.
7. Resolve relative v11 store overrides against the workspace or project
root before forwarding them to the spawned install. Remove the isolated
linker's duplicate package merge so filtered installs retain unselected
dependencies and drop obsolete selected dependencies from the current
lockfile. Cover both cases with CLI regressions. The restore API also
handles a selected project absent from the previous current lockfile.
8. Record workspace validation from the projects the installers actually
processed. Ignored projects receive no new proof, and empty or entirely
ignored selections leave state untouched. Cover both lockfile modes and
an ignored workspace root.
With sharedWorkspaceLockfile: false, each project records the copies
injected into it in its own node_modules/.modules.yaml, relative to the
project. The sync after a script listed in syncInjectedDepsAfterScripts
read only the workspace root's .modules.yaml, so it found no copies and
they kept the files from the last install. The edit watcher that
publishes while such a script runs had the same gap. Both CLIs had it.
The sync now reads every project's .modules.yaml as well when the
lockfile is not shared, and resolves each listed copy against the
directory of the manifest that lists it. It finds the projects with the
workspace's packages patterns and each project's own modules directory,
as the install does, and syncs a source's bins once across every
project that holds a copy. The sync after lifecycle
scripts during an install keeps reading only the root's: with a lockfile
per project, the install syncs those copies once every project is
installed.
v11's sync after a listed script also looked up the copies of a package
that publishes from publishConfig.directory under the project root,
while the install lists them under the publish directory. It now looks
under both, as v12 does, and skips a publish directory that has not been
built yet.
The new copy-equivalence tests check, in both CLIs, that every writer of
an injected copy leaves the files the install selects: the packlist, or
every file under deployAllFiles. The writers are the install with the
refresh after prepare, the sync after a listed script, the same after a
source edit, and a frozen install into a clean tree, across shared and
per-project lockfiles, both node linkers, and deployAllFiles. Without
the fix, the four per-project-lockfile cases fail in each CLI.
Part of pnpm/tasks#145.
An injected copy is built by the install from the package files only
(the files field and .npmignore) unless deployAllFiles is set. Every
refresh that runs after a project's own scripts read the source with
every file instead, so the copy gained files the install had left out:
- v12: the sync after lifecycle scripts during install, after `pnpm run`
and task-pipeline scripts named in syncInjectedDepsAfterScripts, and
the edit watcher that publishes while such a script runs.
- v11: the same `pnpm run` sync and watcher, and the re-import after
lifecycle scripts during install.
pnpm/pnpm#16684 fixed the same thing for the dedicated-lockfile sync.
These paths now read the source with includeOnlyPackageFiles set to
!deployAllFiles too, so every writer of an injected copy selects the
same files (pnpm/tasks#145, property B).
The packlist lists a symlinked directory as one entry, where the walk
of every file lists the files under it. The syncers expand such an
entry, so a symlinked directory still reaches the copy as its files.
v12 groups the bin-linking settings of SyncInjectedDeps into
BinLinkSettings to stay under the struct field limit.
* chore(release): 11.28.6, pacquet 12.14.0
* docs(release): curate 11.28.6 and pacquet 12.14.0 release notes
Add lead paragraphs and group the patch entries by what the reader was
doing, with security first on the v11 page.
- v11: lead with the NPM_ID_TOKEN audience check and the invalid file
name warning as security fixes; say the `pnpm setup` message was
printed, since v11 reports it as a warning.
- v12: merge the stdout and stderr broken-pipe entries and the two
optional-peer entries; say the Windows Rust toolchain change undoes
the MinGW fallback a MinGW-built pnpm used since 12.13.0; correct the
`browser` packing claim; drop the reference to another release line.
- Use one issue-link label style on both pages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(release): smooth two sentences in the 11.28.6 and 12.14.0 notes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(release): say sideEffectsCache.exclude covers the local and remote cache
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: give sideEffectsCache.exclude an example a cache key cannot cover
An environment variable such as JAVA_HOME could be added to the cache
key, so it does not justify excluding a package. Name the system
libraries a native addon links against instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(release): 11.28.5, pacquet 12.10.0
* docs(release): curate the 11.28.5 and 12.10.0 release notes
Add a lead paragraph to each page and group the patch entries under
subject headings, with the security fixes first.
- Merge the unpublish path-boundary fix into the unpublish entry, the
legacy metadata cache prune into the metadata cache entry, the two
binary size entries, and the two 64 MiB archive limits on the v11 page.
- Drop the manifest-utils saveType entry from the CLI page: the CLI
only passes saveType values derived from its own flags, so no CLI user
can reach the fixed path.
- Rewrite mechanism-only entries (PAX header validation, bundled
dependency symlinks, custom resolutions under name@version keys, proxy
setting validation) in terms of what the user sees.
- Use one link style for pnpm/pnpm issues.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Running `pnpm dlx --package=<pkg>` with no command installed the package
and then crashed trying to run an empty command. The handler now throws
ERR_PNPM_DLX_MISSING_COMMAND before doing any work, matching the v12
CLI MissingCommand error for the same invocation. Only v11 is affected;
v12 already rejects this case.
---------
Co-authored-by: Dextheking1 <Dextheking1@users.noreply.github.com>
The install that verifyDepsBeforeRun spawns before `pnpm run` and `pnpm exec`
now receives the parent's `--config.<key>=<value>` options. Settings from
config files and pnpm_config_* environment variables already reached the
child; command-line ones did not, so e.g. `--config.lockfile-dir` was lost and
the install wrote its lockfile into the project.
Both CLIs forward the options as given, at the single place that spawns the
install, so the recheck after waiting for another gate's install gets them too.
The confirmation prompt shown for `verifyDepsBeforeRun: prompt` omits them,
since a `--config.//registry/:_authToken=...` value would otherwise be printed.
pnpm v11 does not forward `--config.dir`: the install already starts in the
resolved directory, where a relative value would resolve a second time.
pnpm v11 carries the options on the config context (`rawCliConfig`) rather than
on cliOptions, which would make them a setting and an explicitly set key.
Closes https://github.com/pnpm/tasks/issues/60
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(release): 11.28.4, pacquet 12.9.1
* docs(release): curate 11.28.4 and pacquet 12.9.1 release notes
Add a lead paragraph to both pages. Move the two credential-leak fixes
in 11.28.4 to the top and group the remaining 27 entries by what the
reader was doing. Merge entries that described one change: the two
INVALID_SETTING validations, the two metadata revalidation fixes, the
two hoisted filtered-install fixes, and, in pacquet 12.9.1, the two
[<since>] selector fixes. Order pacquet 12.9.1 so the `@pnpm/wasm` move,
which WebContainer users must act on, comes first.
Normalize issue links to the [#N] form and link the ETag fix to
pnpm/pnpm#16506.
Every fix was checked against the code released in 11.28.3 and
pacquet 12.9.0; none fixes a defect that never shipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(release): describe the frozen-lockfile fixes accurately in the 11.28.4 lead
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs(agents): add the documentation and comment principles
Import the principles that transfer from KSXGitHub/perfectionist's
contributor guide, plus four of our own: concise and non-repeating text,
structured text over prose, removal over addition when reviewing or
fixing nitpicks, and a definition of the user-facing text that must not
name implementation details.
The root guide's "Comments" section gains "Register", "Prose that cannot
go stale", "User-facing text", and "Reviewing documentation and
comments". The pacquet style guide adds the Rust specifics (clap and
napi doc comments are user-facing; a default is documented on the
field, not the variant). DOCUMENTATION.md gains the renderer and link
table for each markdown surface.
The guides themselves are brought in line: counts over growable lists
become plain references, a quoted heading becomes an anchor link, and
the size limits are stated once, in the style guide, instead of three
times.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs: keep user-facing text free of implementation names
The `@pnpm/napi` README and type declarations named the Rust engine, the
in-repo package name, and TypeScript internals (`filterPkgsDiff`,
`nameFormatter`) that a user of the addon cannot reach. The pnpmfile
page in both docs trees explained a limitation by the IPC mechanism
behind it. Four changesets named the release line or implementation,
which the package targets already express.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut restating and stale comments in pack, modules-yaml and nine more crates
Apply the comment principles to pack, versioning, pnpr-client,
package-is-installable, fs-packlist, workspace-task-scheduler,
engine-runtime-node-resolver, exportable-manifest, modules-yaml,
pnpr-registry, and pnpr-auth. Removed: call-site comments that repeat
the callee's doc, module rosters of exports that the `pub use` lines
already list, "not yet implemented" notes for behaviour that exists
(`beforePacking`, `hoistedLocations`), body narration, and history
framing. Comment-only; no code changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut restating and stale comments in the small resolver, catalog and pnpr crates
Apply the comment principles to the local, tarball, default, deno and
jsr resolvers, the catalog crates, detect-libc, config-parse-overrides,
env-replace, git-utils, matcher, diagnostics, and the pnpr policy,
cargo, error, osv and fixture crates. Removed: parentheticals that
repeat a `#[diagnostic(code)]` attribute, counts over lists of
variants, callers and entry points, rosters of callers, doc paragraphs
that repeat a sibling's or the module's text, comments narrating the
match arms or condition below them, and history framing. The
`pnpr-prepare --help` text no longer names the test harness.
Comment-only; no code changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut restating and stale comments in publish, workspace, registry and graph-hasher
Apply the comment principles to publish, auth-commands, registry,
workspace, lockfile-verification, default-reporter, the git resolver,
deps-inspection, hooks, graph-hasher, package-manifest, and the pnpr
shared-artifacts and route crates. Removed: a module roster of every
export, counts over lists, plan notes for work that landed ("Phase 5",
"Phase 6"), call-site comments repeating the callee's doc, docs
narrating match arms, a default claim on a `#[default]` variant, a
misattached doc, and history framing. Comment-only; no code changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut restating comments in napi, reporter and pnpr crates
Apply the comment principles to napi, reporter, resolving-resolver-base,
real-hoist, git-fetcher, workspace-manifest-writer, and the pnpr config
and storage crates. Removed: "Mirrors X in index.d.ts" sentences on
`#[napi]` items and other implementation names in addon-facing docs,
stale "not emitted yet" and "when it lands" notes for behaviour that
exists, step-by-step comments narrating the body, emit-site and caller
rosters, docs duplicated from a sibling or the module, a doc attached to
the wrong item, and history framing. Comment-only; no code changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs: cut restating and stale comments across the remaining crates and pnpm11
Apply the comment principles to the rest of the Rust workspace and to the
TypeScript CLI: the cli crate and its clap definitions, package-manager,
config, patching, the deps resolver, lockfile, pnpr, the npm resolver,
tarball, network, executor, cmd-shim, the benchmark and fixture tooling,
and the pnpm11 packages.
Removed: comments narrating the body, docs repeating a callee's, a
sibling's or the module's text, counts over growable lists, rosters of
callers and exports, "not yet implemented" notes for behaviour that
exists, history framing, and `@param` lines restating a signature.
One exception to comment-only: in `pnpr/crates/pnpr/src/resolver/resolve.rs`
the `Workspace` struct sat inside the doc block written for `resolve`, so
the struct moved above it and each doc now attaches to its own item.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut body narration and duplicated docs in resolver, fs and package-manager
Removed: a numbered "Sequence:" list narrating `ensure_file`'s body and
the paragraph restating its backoff constants, the stress-test module
doc's roster of its own tests, refactor-history paragraphs in the peer
walk's finalize pass, call-site and caller-side copies of a callee's doc
(the `optional` AND-fold stays documented on `register_seeded_package`
and on `ResolvedPackage`), a verbatim quotation of a TypeScript test's
expected output in the pnpr search module doc, and field and digest-input
rosters in the package-manager.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(deps-restorer): cut plan references and re-attach misplaced docs
Removed the "slice 4" plan references and the comments narrating the
bodies they sat above. The `Auto` downgrade-chain doc moved from
`copy_and_log` to `auto_link`, the function it describes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(deps-restorer): cut the remaining body narration in the hoist path
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs: cut the last body narration and a history clause in the cmd-shim test
`build_graph.rs` kept a `patches` parameter doc that re-derived what
`BuildOptions::patches` documents. The PowerShell-arguments test on main
said the arguments survived "before the shim ended its batch context
too", which describes the change rather than the behaviour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(cmd-shim): point the PowerShell-shim link at its current module
Main moved `wants_powershell_shim` into `windows_shim_policy`, so the
shim writer's link to it no longer resolved and `cargo doc` failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(cli): link the fixture the ignored-build setup installs
The shortened doc named `workspace_with_unapproved_build` in backticks,
which `perfectionist::bare_identifier_reference` rejects for an item that
resolves in scope. It is an intra-doc link now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): restore two contracts and fix four dangling references
A review of this branch's sweep flagged seven places. Six needed a
change:
- `from_ini` documents again that a relative `cafile=` resolves against
`npmrc_dir`, a precondition its callers have to honour.
- `hash_object` documents again that the private serializer's bytestream
has to match pnpm 11's, because the digest is persisted and read by
both.
- The hooked-manifests note no longer says "above" of a gate that lives
in another module.
- The store-dir race test states its synchronization contract instead of
labelling it "Post-Option-C".
- `GraphNodeHashOptions.nodeVersion` keeps `undefined` as a literal.
- The pnpr streaming module drops the sentence fragment that introduced
its list.
The seventh, a `beforePacking` limitation in exportable-manifest, stays
removed: the note said the pnpmfile bridge cannot supply that hook, and
`pnpm_pack::lifecycle::apply_before_packing` has been running it since
before this branch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(cli): say in `pnpm why --help` that `--exclude-peers` is ignored
Demoting the caveat to an ordinary comment took it out of the help,
which then advertised a flag that does nothing: `WhyArgs::run` never
reads `exclude_peers`, and pnpm 11's `why` handler never forwards its
`excludePeers` either. The doc comment states that behavior in the words
a user sees, and the parity reason stays in a comment beside it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(cli): drop the doc comment that described the wrong module
The comment described the `pnpm_deps_inspection as deps_tree` re-export
above it, but a doc comment attaches to the item that follows, so rustdoc
showed it as the description of `mod dispatch`. The `use` item already
names the alias, so the comment goes rather than moving.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
---------
Co-authored-by: Claude <noreply@anthropic.com>
When verifyDepsBeforeRun is `install` (the default) and node_modules are
out of date, `pnpm run` and `pnpm exec` start `pnpm install` first. In a
sandbox with a read-only store or no network that install cannot succeed,
so no script could run at all.
A failed install now prints a warning that node_modules may be out of sync
and the command continues, the same outcome as the `warn` action. The
install has already printed its own error. The warning is fixed text, so it
echoes no filter selectors, and it names `verifyDepsBeforeRun: false` as the
way to skip the install.
An install ended by a signal, or by Ctrl+C on Windows (exit code
0xC000013A), still stops the command, so an interrupt is never turned into
a script run. The `prompt` action shares the install path, so a confirmed
prompt behaves the same way.
Implemented in both the TypeScript CLI and pacquet, with the settings docs
for both versions updated.
Related to https://github.com/orgs/pnpm/discussions/16212
A filtered install leaves the workspace dependencies of the projects it
selected without a modules directory, but the install the
verifyDepsBeforeRun gate spawns for a filtered run or exec selects them
(each selector gets "..."). The status check only looked at the projects
the command selected, so it reported the state up to date and the
dependency stayed uninstalled.
Both stacks now verify the selected projects plus their transitive
workspace dependencies, computed at the gate from the graphs the
selection was drawn from. A --filter-prod-only project walks the
prod-pruned graph. When a negated selector is present, the set is
intersected with the projects the spawned install selects, because
negated selectors reach that install unchanged; otherwise the gate would
require an excluded project and install on every run. The v12 helper
returns early when verification is disabled.
In v11 the computation lives in exec.commands (selectProjectsToVerify)
and is passed to checkDepsStatus as selectedProjectsGraph, leaving
deps.status unchanged. In v12 the graph walks the recursive commands
share move to cli_args/recursive/dependencies.rs.
Refs pnpm/tasks#45.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(release): 11.28.3
* docs(changelog): curate the 11.28.3 release notes
Merge the eleven entries about names such as `constructor` into one,
group the patch changes by what the reader was doing, and add a lead
paragraph.
Drop the zlib-rs entry: that change only touched Cargo.toml, and the
TypeScript CLI does not use the Rust engine. It belongs to the next
pacquet release.
Rewrite the store index entry for CLI users. `ReadOnlyStoreIndex` and
`ImmutableStoreIndex` are library API documented in the
`@pnpm/store.index` changelog.
Say what the store prune mark-phase fix changes for the user.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Add TypeScript ports of the perfectionist rules that the Rust workspace
enforces through dylint.toml to @pnpm/eslint-config, with the same limits,
and fix every finding that needs no refactor.
The size and shape rules (function length, cognitive complexity, nesting,
local bindings, file length) are enforced for packages that already meet
them. The packages that do not are listed in PENDING_SIZE_AND_SHAPE_REFACTOR
in eslint.config.mjs and are refactored in follow-up changes.
The custom rules mirror perfectionist's semantics rather than reusing
sonarjs or max-lines-per-function, so that one limit means the same thing
in both codebases: a closure is a level of the body that contains it, a
nested function declaration is measured on its own, and a test-framework
callback plays the role of a #[test] function.
Bugs found and fixed along the way:
- a registry error body cut at exactly 64 KiB lost its truncation marker
- removing a dependency could leave dangling .bin shims
- constructor/__proto__ names crashed the projects graph, were skipped by
the injected-deps syncer, and were dropped from writeWantedLockfile's
returned lockfile
The CI chunk runner learns to run the node:test suite of the ESLint
config package.
* chore(release): 11.28.2, pacquet 12.8.1
* docs(release): curate the 11.28.2 and 12.8.1 release notes
Add the lead paragraph the previous release pages carry, order the
pacquet entries by impact (failing installs and damaged files first,
then wrong lockfiles, speed, and command behavior), lead the exec-bit
entry with what the reader sees, and use the same issue-link style in
every entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The verify-deps-before-run gate spawned an install whenever no workspace
state file existed, even for projects that give an install nothing to
do. That install only wrote node_modules and pnpm-lock.yaml, which
became visible in directories the workspace packages patterns leave
out after pnpm/pnpm#15125.
The gate now returns up to date when no state file exists and no
project the install would cover declares a dependency, a required peer
that autoInstallPeers would fetch, or an install lifecycle script
(preinstall through postprepare, or an implicit binding.gyp build) that
ignoreScripts does not disable. pnpm:devPreinstall counts only in the
workspace root, the only place an install runs it. Under a shared
lockfile every workspace project is checked, and the projects are walked
only when the root manifest itself has nothing to install. With separate
lockfiles a non-recursive run checks only its own project. A
non-recursive pnpm 11 command gets no allProjects, so the gate reads the
workspace itself, as checkDepsStatus does.
A readPackage hook can add dependencies, so any pnpmfile the install
would load (looked up from the lockfile directory) keeps the install.
A workspace that cannot be read, or a lockfileDir pinned away from the
project, also keeps it. Projects with lifecycle scripts still
install on the first run, so those scripts keep running on first use.
Closespnpm/pnpm#16313
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(release): 11.28.1, pacquet 12.8.0, pnpr 0.1.0-alpha.14
* docs(release): park Rust changelogs and curate release notes
The release bump ran with pnpm 12.7.0, which since pnpm/pnpm#14934
writes the changelog of every private project to a committed
CHANGELOG.md. pacquet, `@pnpm/napi` and `@pnpm/pnpr` are private
in-repo but published, and release.yml and the publish-time changelog
composer both read their parked section. Move the three sections back
to .changeset/changelogs so the GitHub release bodies and the npm
tarball changelogs are not empty. The tooling follow-up is recorded
on pnpm/tasks#38.
Curate the pnpm 11.28.1, pnpm 12.8.0 and pnpr 0.1.0-alpha.14 pages:
add lead paragraphs, group patch entries by subject, merge entries
describing one change, drop internals no CLI user sees, and fix
"instead of" tails, implementation names and link placement. This
addresses both Qodo findings on the catalog entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
syncInjectedDepsAfterScripts refreshed injected copies only after the
script exited, so a long-running dev server never saw source changes.
While a listed script runs, changed files are now copied into each
injected directory as new files, so watchers see the write. The poll
stops before the hardlink sync that follows the script. Watch setup is
best effort, so an unreadable modules manifest does not stop the script.
In pnpm v12, a mutable local directory that nothing builds is hardlinked
under the auto import method, as the v11 directory fetcher already
does, so an in-place edit reaches the injected copy. Explicit import
methods, packages that will be built, and deploy isolation keep their
existing behavior.
Closespnpm/pnpm#4410
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The verify-deps-before-run gate spawns `pnpm install` when node_modules
are out of sync. When the root package.json still keeps overrides,
packageExtensions, patchedDependencies or ignoredOptionalDependencies in
its `pnpm` field, which pnpm no longer reads, the lockfile records
settings the effective config can never reproduce. Every `pnpm run` then
found the tree stale and rewrote the lockfile without them, silently
dropping security overrides.
The gate now refuses that install (in prompt mode, once the user
confirms it) and fails with ERR_PNPM_VERIFY_DEPS_BEFORE_RUN and asks to move the settings to
pnpm-workspace.yaml. Migrated keys the lockfile does not record keep the
existing behavior. Fixed in both the Rust and the TypeScript CLI.
Closespnpm/pnpm#16278
`pnpm --filter <selector> run|exec` checks the workspace state before it runs
the command and spawns an install when that state is outdated. The spawned
install carried no project selection, so it covered every project of the
workspace instead of the ones the command was limited to. And a state that
records a filtered install exempted every project from the modules-directory
requirement, so the gate answered "already up to date" for a project the
earlier filtered install had not materialized and ran the command without
installing it.
Forward the `--filter` and `--filter-prod` selectors to the install the gate
spawns, each extended with `...` so the workspace projects a selected project
depends on are installed too, and hold the projects the gated command selected to the
modules-directory requirement, in both the TypeScript and the Rust CLI.
Closespnpm/pnpm#11865.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
StackBlitz WebContainers reject asynchronous fs calls with errors that
inherit from Error but were not created by the native constructor, so
util.types.isNativeError() returns false for them. pnpm used that check
to recognize ENOENT and other codes, so a missing pnpm-lock.yaml was
rethrown and pnpm install failed on any project without a lockfile.
Add isError() to @pnpm/error, which accepts a native error or any
instanceof Error, and use it at every non-test call site. The native
check alone is still needed for Jest, where errors from another realm
fail instanceof. Update the AGENTS.md guidance to match.
Closespnpm/pnpm#15649
With `tasks` declared, a recursive run with a RegExp selector now seeds
one task per script the selector matches in each project, so each
resolves the `dependsOn` declared for its own name, as a run of that
script by name does. The selector string itself matched no `tasks`
entry, so its task fell back to `^<selector>` and the matched scripts'
declared dependencies never ran.
Resolving the union of the matched scripts' entries onto one
per-project task was rejected. That task runs its scripts concurrently,
so a matched `test` could not wait for a matched `build`. A `^build`
entry also pulled in a `build` task that the dependency project's
selector task ran again.
A project the selector matches nothing in keeps a requested
pass-through task under the selector name, with no dependencies.
Without `tasks`, or with an exact `tasks` entry under the selector
string, the selector stays a single task per project.
The hidden-script check runs per project over the requested tasks. A
selector that matches only hidden scripts in a project still fails, but
a hidden script alongside visible ones is filtered as before. A
requested task that a `dependsOn` declaration targets is exempt, like
any `dependsOn` reference. The exempt set is every dependency of a task
with a `tasks` entry, since a task without one only has the default
`^<name>` edge. It is read from the full graph before sequencing, in
declaration direction when `--reverse` inverted the edges.
`--resume-from` anchors on every task requested in the anchor project
when the invocation's own task is absent, which is the case for an
expanded selector. Summary keys qualify every task but the one
named by the invocation, so expanded tasks do not share a key.
Closespnpm/pnpm#15596
Co-authored-by: Dex <4175097+Dextheking1@users.noreply.github.com>
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The remaining packages of the pnpm/components Bit workspace now live
under pnpm11/ and are published from here, keeping their npm names:
@pnpm/config.env-replace -> pnpm11/config/env-replace
@pnpm/log.group -> pnpm11/log/group
@pnpm/network.agent -> pnpm11/network/agent
@pnpm/network.ca-file -> pnpm11/network/ca-file
@pnpm/network.config -> pnpm11/network/config
@pnpm/network.proxy-agent -> pnpm11/network/proxy-agent
@pnpm/os.env.path-extender -> pnpm11/os/env/path-extender
@pnpm/os.env.path-extender-posix -> pnpm11/os/env/path-extender-posix
@pnpm/os.env.path-extender-windows -> pnpm11/os/env/path-extender-windows
lex-comparator and nerf-dart were already moved under new names, and the
Bit env component is build tooling with no counterpart here.
The sources are carried over from pnpm/components at 42ec0f7, which
includes the Windows fix that rewrites a PNPM_HOME value stored with the
wrong registry type. pnpm v12 got the same fix in pnpm/pnpm#15746, so
its changeset now also covers pnpm v11, which bundles the workspace copy.
Porting to the monorepo's ES module setup:
- relative imports carry .js and type re-exports are marked `type`;
- tests use jest.unstable_mockModule and import through the package;
- string.prototype.matchall and graceful-fs give way to built-ins;
- safe-execa and lru-cache move to the catalog versions (0.3, 11);
- network.config depends on @pnpm/config.registry-auth-key;
- the strict-ssl proxy test uses node:https and proxy v4 instead of
node-fetch and proxy v1.
The packages start at 1100.0.0 to sit in the pnpm11 version band.
Concurrent installs that resolve to the same global virtual store slot
ran its lifecycle scripts in the one shared directory at once. A script
that rewrites its own files, such as esbuild's install.js, then failed in
one install, and that install removed the slot the other installs had
already linked.
Builds into a global virtual store slot now hold a directory lock at
`<slot>/.pnpm-build.lock`, in both pnpm v11 and v12. A build that finds
the slot's `.pnpm-needs-build` marker gone once it gets the lock skips
the build, since another install finished it. A build marks the slot
"started" before it writes into it, and a waiter that finds that mark
skips the build too, since another install's build of it failed or died. An install also takes
the lock before it re-imports a slot that still carries the marker,
which would otherwise overwrite a build in progress, and so does a
side-effects cache hit that re-imports its overlay into the slot. A
patched package whose scripts --ignore-scripts withheld keeps its marker
until the scripts have run.
A failed build no longer removes the slot. It leaves the "started"
`.pnpm-needs-build` marker instead, which the next install already treats
as an interrupted build: it re-imports the pristine files and builds
again.
The TypeScript directory lock moves from `@pnpm/exec.commands` into a new
`@pnpm/fs.dir-lock` package so the build step can use it.
Closespnpm/pnpm#15568
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(release): 11.28.0, pacquet 12.7.0, pnpr 0.1.0-alpha.13
* docs(release): curate the 11.28.0 and 12.7.0 release pages
Group both pages under subject headings with a lead paragraph that
names the headline changes and the security fixes, and move the
security entries to the top of the patch section.
Merge entries that describe one change: the node shim version files,
ranged selectors without a package.json, SemVer build metadata,
completion injection, custom modulesDir command lookup,
list --only-projects, store status, npm_execpath, catalog and npm-alias
workspace edges, the macOS CA fallback, and the prepare/devPreinstall
skips.
Drop entries that describe no change a published version could show:
the test-only --no-optional entry on both pages, the exported
CreateNewStoreControllerOptions type, the publish availability-probe
fix for a flag first shipping in 12.7.0, and the executable-bit check
the Rust CLI never had wrong.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(release): describe the bin shim part of the modulesDir entry accurately
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
With --loglevel=warn or error (or the loglevel setting), pnpm run still
printed the `$ <command>` echo before a foreground script and the summary of
the install that verifyDepsBeforeRun spawns. Both are info-level output.
Rust: expose the default reporter's configured ceiling as max_log_level(),
suppress the echo in run and run -r when it is below info (as the silent
reporter already does), and forward --loglevel=warn|error to the spawned
verify-deps install.
TypeScript: pass silent to runLifecycleHook when loglevel is silent, warn, or
error in run and runRecursive (loglevel=silent selected the silent reporter
without setting reporter, so the echo still printed), and forward those
levels through runPnpmCli to the spawned install in runDepsStatusCheck
(shared by run and exec).
Closespnpm/pnpm#8944
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
In a workspace reached through a symlinked directory, `pnpm -r exec pwd`
printed the resolved physical path instead of the logical path
(Closespnpm/pnpm#1550).
The spawn cwd already is the logical project path. What leaks the physical
path is the inherited PWD environment variable: it still points at pnpm's own
cwd, disagrees with the child's actual cwd, and shells that trust PWD fall
back to the physical working directory. Set PWD to the directory the command
runs in (unix only). pacquet's command_in_dir gets the same override so both
implementations agree.
External binaries that print the physical directory by default (e.g.
coreutils pwd without -L) are unaffected by any package manager and out of
scope.
verifyDepsBeforeRun spawned its install without a lock, so N concurrent
`pnpm run` or `pnpm exec` against a stale node_modules started N installs
racing in the same directory and failing with filesystem errors.
The gate now takes a per-workspace advisory lock around the install:
pacquet's DirLock in the per-user lock directory, keyed by the canonical
workspace root, and in the TypeScript CLI a mkdir lock now shared with the
task-run journal. A gate that finds the lock held re-checks the dependency
status once it holds the lock, and skips the install when the first gate's
install already brought the tree up to date. A gate that gets the lock
immediately does not re-check, so an uncontended run pays no extra check.
The wait is bounded at 5 minutes. A gate that times out, or cannot
establish the lock, installs without it as before.
The TypeScript mkdir lock also takes over a lock whose holder process on
this host has ended, matching the liveness takeover of pacquet's DirLock.
Closespnpm/pnpm#14551
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
`pnpm <command>` falls back to `pnpm exec <command>` when the project has
no script by that name, but `pnpm --filter <project> <command>` and
`pnpm -r <command>` went straight to the recursive script runner and
failed with ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT, so a bin from the selected
projects' dependencies could not be run through the shorthand.
When the shorthand is used and none of the selected projects has a
matching script, the recursive run now hands the command to a recursive
`exec` over the same selection, with the same recursive options. The
decision is made on the task graph as built, before the run-only resume
and sequencing, which do not apply to `exec`. If at least one selected
project has the script, the scripts run as before. `--if-present` and
`--dry-run` keep their meaning, `test` and `start` keep their
missing-script behavior, and an explicit `pnpm run` still reports the
missing script.
Closespnpm/pnpm#10151
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
The dlx cache key covered the packages, the registries, the allowed
builds and the architectures, but not the Node.js runtime. A command run
under a different Node.js major reused the cache entry installed under
the first one, including packages built by lifecycle scripts, so a
native addon failed to load with ERR_DLOPEN_FAILED.
Both stacks now add the engine name (`<platform>;<arch>;node<major>`)
of the Node.js that runs the builds to the key: `getSystemNodeVersion()`
in v11 and the `node` on PATH in v12. The `nodeVersion` setting is not
used, since it only sets the target of the engines check.
Closespnpm/pnpm#8611
`resolvePnpmSelfCommand` decided whether `process.argv[1]` was pnpm from
its basename and the manifest above it, rejecting a script only when
another package claimed it as a bin. A script that no package claims, a
project's own `scripts/pnpm.mjs` among them, still passed.
pnpm's own code knows where it lives. The entry script is now accepted
only when its real path is the running module itself, as when a bundle
is run directly or copied elsewhere, or lies in the same package as that
module, as with `bin/pnpm.mjs` loading `dist/pnpm.mjs`. The nearest
`package.json` marks the package; the published `pnpm` and `@pnpm/exe`
tarballs carry only the one at their root. The basename set stays to keep
out hosts such as Jest, and a `pnpx` entry maps to the `pnpm.mjs` beside
it, since `pnpx` prepends `dlx` to `process.argv`. An entry that cannot
be resolved now
falls back to the pnpm on PATH, which is safe because the bundle-copy
case no longer depends on failing open.
The resolver moves to `@pnpm/cli.meta`, next to
`detectIfCurrentPkgIsExecutable`, so `npm_execpath` can share it without
`@pnpm/exec.npm-lifecycle` depending on the CLI runner. `npm_execpath`
previously took `process.argv[1]` unchecked, so a script run under
`pnpx` got `pnpx.mjs` and `$npm_execpath install` became `dlx install`.
It now gets the running pnpm's entry or executable, or the pnpm on
pnpm's own PATH when there is none, matching the runner's fallback. The `process.pkg` branch it
replaces was dead since `@pnpm/exe` moved to a Node.js SEA.
The lookup is cached for the process, since lifecycle scripts ask once
per script and neither `process.argv[1]` nor the module moves.
On Windows, `@pnpm/exe` installs `pnpx.exe` and `pnx.exe` as hardlinks
of the pnpm binary, which runs as `pnpm dlx` when launched through
either name. Re-invoking the current executable under one of them
prepended `dlx` again. Both versions now re-invoke the `pnpm` executable
beside the alias: v11 in `resolvePnpmSelfCommand` and `npm_execpath`,
and v12 through `pnpm_executor::current_pnpm_exe()` in `npm_execpath`,
`doctor`, the `verifyDepsBeforeRun` install, the pipeline agent and the
git-dependency build.
The `npm_execpath` fallback is resolved to an absolute path before a
script's PATH puts `node_modules/.bin` first, so a dependency's `pnpm`
bin cannot stand in for it.
`pnpm restart` ran the "stop" script and then failed with
"Missing script: restart" when the package had no "restart" script,
leaving the app stopped. The "restart" step is now skipped when the
script is missing, so the command falls back to "stop" then "start",
as npm does. Fixed in both the TypeScript and the Rust CLI.
A package that defines "restart" still runs "stop", "restart", and
"start". Dropping "stop" and "start" in that case, as npm does, would
change stable behavior.
Closespnpm/pnpm#4750
Share lifecycle package-manager environment construction with exec and dlx
in both implementations. Child tools receive the invoking pnpm path, Node
path and initial working directory, matching npm exec.
Closespnpm/pnpm#7037.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Include the custom project modules directory in command shim NODE_PATH.
For symlinked tools, prepare each project's environment when pnpm runs
run/exec, install lifecycle scripts, and version hooks. Preserve symlinks,
project dependency isolation,
default layouts, and extendNodePath false.
Use the configured bin directory for bin/run/exec and install/version
hooks, including packageConfigs with separate lockfiles. Compare both
canonical paths when deciding whether a modules directory is the default.
Include development preinstall hooks and pnpr materialization. Reject
custom NODE_PATH entries containing path-list separators and parse
package configuration once per command.
Related to pnpm/pnpm#3604. ESM resolution remains outside this change.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Concurrent lifecycle scripts each raised their death signal on pnpm as soon as they exited. The first synthetic SIGINT reached the remaining scripts' relay handlers like a second Ctrl-C, escalated them to SIGTERM, and let recursive run report ordinary lifecycle failures before pnpm exited.
Group overlapping signal relays and raise a child's death signal once, after the whole group has settled. Delay lifecycle error reporting until that signal has had a chance to end pnpm.
pnpm exec and pnpm dlx spawned the command through execa and handled
no signal themselves. A Ctrl+C ended pnpm at once, and execa's exit
cleanup then sent the command a SIGTERM while it was still handling
the terminal's SIGINT, so it never finished shutting down. A signal
sent to pnpm alone never reached the command at all.
The signal relay of the lifecycle runner is now a shared module of
`@pnpm/exec.npm-lifecycle`, and the tracked execa wrapper uses it: the
command gets a process group of its own when pnpm has no terminal,
pnpm relays SIGTERM and a SIGINT it received alone, escalates a second
interrupt to SIGTERM, and waits for the command and its group before
going on. The runner's own relay is unchanged in behavior. Terminating a
still-running child when pnpm exits is an option of the relay: the runner
always takes it, exec and dlx only for a detached command, since execa's
own cleanup terminates an attached one.
A recursive run that a signal reaches stops dispatching: queued
commands never start, a command the signal cut short is not journaled
as passed, and once every running command has settled pnpm re-raises
the signal on itself, so the shell sees an interrupted run. A single
command's exit status stays pnpm's.
Related to pnpm/pnpm#7374
* fix(exec): find the project's bins when run from a subdirectory
`pnpm exec` and the `pnpm <command>` fallback keep the caller's cwd since
pnpm/pnpm#10445, but the only PATH entry pointing at the project,
`./node_modules/.bin`, is relative and resolved against that subdirectory,
so executables installed in the project were no longer found. The
project's `node_modules/.bin` is now added as an absolute entry when the
run directory differs from the project directory, and `PNPM_PACKAGE_NAME`,
`.pnp.cjs`, and the package map are read from the project instead of the
cwd. This matches the Rust CLI, whose `ExecDirs` already separates the run
directory from the project directory.
Closespnpm/pnpm#5068
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(exec): add the project's bin dir to PATH relative to the run dir
An absolute entry is rejected by makeEnv when the project path contains
the PATH delimiter, which the existing "directory with path delimiter"
test guards against for the relative `./node_modules/.bin` entry. The
subdirectory test now runs in such a project. The changeset states each
behavior in its own sentence.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(exec): cover the PnP lookup from a subdirectory
The subdirectory test now asserts that `.pnp.cjs` is found in the project,
not the cwd. The comment above the project-dir decision keeps only the
reasons the code cannot carry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Reuse the install build-approval helper for global installs and dlx.
Keep a lockfile in each isolated dlx cache entry so approved packages
can be rebuilt before running the requested binary. Reload persisted
approval decisions into the rebuild configuration.
Recheck pending builds on cache hits in both v11 and v12. Refresh
caches that do not have the lockfile required for rebuilding in both stacks.
Cover noninteractive installs, approval and build artifacts, caller
settings isolation, and reuse of an approved cache entry.
Closespnpm/pnpm#14943.
Persist passed recursive tasks incrementally in a versioned journal under the workspace's node_modules directory. Hash the invocation's task graph, script commands, arguments, environment, and execution settings so only compatible state is reused.
Use unique per-run journals, cross-process serialization around the atomically published latest-run pointer, open file handles, and newline-committed records to reject stale, superseded, malformed, torn, or unsafe state. Remove each journal after its invocation succeeds and preserve the existing dependency-pruning fallback when no compatible state is available.
Implement the same state format and resume semantics in the TypeScript CLI and Rust pnpm port, with shared identity fixtures guarding cross-implementation compatibility.
Related to pnpm/pnpm#14211.
RegExp script selectors included matching names even when their script bodies
were empty, unlike exact-name selection. Recursive task orchestration could
therefore dispatch tasks pulled in through dependsOn before eventually
reporting that the requested script did not exist.
Filter empty bodies at the shared script-selection boundary in both the
TypeScript CLI and pacquet, so pre-dispatch validation observes the same
missing-script semantics for exact and RegExp selectors.
Related to pnpm/pnpm#14211.
Add an optional positive-integer concurrency limit to workspace task settings and carry it onto resolved task nodes.
Reserve permits in the dependency-ready scheduler rather than inside workers. This lets unrelated task names use available workspace capacity, while tasks waiting on their own limit remain undispatched and do not start after a bailed failure.
Implement matching configuration validation, scheduling behavior, and integration coverage in the TypeScript CLI and pacquet.
Implements the per-task concurrency extension from pnpm/pnpm#14211.
Move workspace task graph construction and scheduling out of the recursive command implementations into dedicated TypeScript and Rust packages.
The Rust crate depends on the lower-level dependency graph sequencer so package-manager code can consume it without creating a dependency cycle. Existing recursive run and exec consumers now import the shared implementations, preserving their current behavior while enabling the follow-up scheduler migrations tracked by pnpm/pnpm#14211.
Related to pnpm/pnpm#14211.
Replace the chunked topological scheduler of recursive run/exec with
per-task scheduling in both stacks: a task — a (project, script) pair —
becomes runnable when every task it depends on has completed
successfully, and runnable tasks are dispatched under the
workspace-concurrency limit with no barrier between
dependency-independent tasks.
A new "tasks" section in pnpm-workspace.yaml declares task dependencies
with the caret convention ("^build" = the task in each workspace
dependency, "build" = the task in the same project). A task with no
entry behaves as depending on its own name in the workspace
dependencies, which is exactly what chunking implied. A project without
the script becomes a pass-through node that is reported skipped and
keeps the chain intact.
Also per the RFC: task-graph cycles are ERR_PNPM_TASK_CYCLE naming the
participating tasks, scoped to the invocation's selected graph, with
ignoreWorkspaceCycles: true downgrading the error to a warning;
--resume-from excludes exactly the anchor's transitive dependencies;
--reverse runs the reverse graph; under --no-bail dependents of a
failed task are reported skipped and do not add to the exit code; with
--bail, the first failure ends the run at once and nothing new is
dispatched; output is inherited only when at most one script can ever
be in flight; and pnpm -r run --dry-run [--json] prints the resolved
task graph without running anything (the verify-deps check included).
Implementation: the projects sorter exposes the tunneled dependency-edge
map (filteredProjectsDependencies / filtered_projects_dependencies)
instead of only its flattened chunks; the recursive summary is
task-keyed (dependsOn-pulled tasks get "<dir>#<task>" keys); pacquet's
inert --sequential now means concurrency 1 and its --no-sort
resume/reverse handling is aligned with the TypeScript CLI; the dead
chunk helpers are removed.
Related to https://github.com/pnpm/rfcs/pull/23