mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-30 19:46:44 -04:00
Merge branch 'main' into feat/view-command-support-omitted-package-name
This commit is contained in:
17
.changeset-released/bump-versions.txt
Normal file
17
.changeset-released/bump-versions.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
attestation-first-min-release-age
|
||||
auto-collect-minimum-release-age-exclude
|
||||
cache-aware-minimum-release-age-gate
|
||||
clear-password-padding
|
||||
fix-11655-self-update-minimum-release-age
|
||||
fix-global-allow-builds
|
||||
fix-verify-deps-silent-install
|
||||
floppy-parents-teach
|
||||
gvs-engine-name-shell-node
|
||||
gvs-engine-per-snapshot-runtime-pin
|
||||
lockfile-verification-progress-logs
|
||||
oidc-unresolved-env-placeholder
|
||||
pmonfail-default-devengines-11676
|
||||
record-locally-resolved-lockfile-verified
|
||||
revalidate-minimum-release-age
|
||||
sync-env-lockfile-when-missing-11674
|
||||
warn-deprecated-pnpm-field-11677
|
||||
@@ -1,7 +1,10 @@
|
||||
audit-signatures
|
||||
check-deps-status-skip-engine-check
|
||||
clear-resolutions-warning
|
||||
codeql-alert-159-followup
|
||||
codeql-security-fixes
|
||||
credential-rebind-defense
|
||||
deploy-skip-config-dependencies
|
||||
fix-11529-view-published-time
|
||||
fix-11561-publish-web-auth-proxy
|
||||
fix-11587-global-isolated-per-arg
|
||||
@@ -11,12 +14,24 @@ fix-named-catalog-upgrade
|
||||
fix-named-registry-vs-local-resolver
|
||||
fix-optimistic-lockfile-conflict
|
||||
gh-packages-prefix
|
||||
git-fetcher-reject-non-sha-commits
|
||||
honest-moose-grin
|
||||
integrity-mismatch-fails-by-default
|
||||
no-runtime-flag
|
||||
olive-spies-listen
|
||||
patch-path-traversal
|
||||
pnpm-bugs-command
|
||||
pnpm-owner-command
|
||||
policy-handlers-loose-mode-message
|
||||
preserve-published-at-in-fast-path
|
||||
registry-access-client-helper
|
||||
reject-traversal-dependency-aliases
|
||||
require-tarball-integrity
|
||||
runtime-onfail-node-check
|
||||
runtime-set-default-devengines
|
||||
runtime-set-workspace-root
|
||||
split-local-resolver
|
||||
tidy-global-update-summary
|
||||
tidy-trust-publishers
|
||||
update-cmd-shim-9-0-3
|
||||
version-exit-finishes-workers
|
||||
|
||||
10
.changeset-released/new-release.txt
Normal file
10
.changeset-released/new-release.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
dlx-fall-back-to-alias-when-manifest-missing
|
||||
fix-cyclic-peer-determinism
|
||||
fix-unaliased-deps-dropped-from-manifest
|
||||
native-pkg-command
|
||||
native-repo-command
|
||||
native-set-script-command
|
||||
prune-env-lockfile-on-config-dep-update
|
||||
skip-manifest-obfuscation-opt-in
|
||||
stage-publish
|
||||
trust-lockfile-and-verifier-memory
|
||||
3
.changeset-released/release-11-2-1.txt
Normal file
3
.changeset-released/release-11-2-1.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
config-deps-optional-subdep-snapshot-flag
|
||||
pick-registry-unscoped-npm-alias
|
||||
quiet-config-deps
|
||||
2
.changeset-released/release-11-2-2.txt
Normal file
2
.changeset-released/release-11-2-2.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
fix-pacquet-outdated-lockfile-on-update
|
||||
forward-install-flags-to-pacquet
|
||||
11
.changeset-released/release-11-2.txt
Normal file
11
.changeset-released/release-11-2.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
cafile-resolve-against-npmrc-dir
|
||||
config-deps-optional-subdeps
|
||||
config-reader-registry-sync-npmrc
|
||||
global-minimum-release-age-policy
|
||||
injectworkspacepackages-prune-crashes
|
||||
login-scope-flag
|
||||
login-workspace-registry
|
||||
minimum-release-age-modified-shortcut-inclusive
|
||||
outdated-runtimes
|
||||
pacquet-frozen-install-delegation
|
||||
publish-config-access
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
"@pnpm/resolving.resolver-base": minor
|
||||
"@pnpm/resolving.npm-resolver": minor
|
||||
"@pnpm/resolving.default-resolver": minor
|
||||
"@pnpm/installing.client": minor
|
||||
"@pnpm/store.connection-manager": minor
|
||||
"@pnpm/testing.temp-store": minor
|
||||
"@pnpm/installing.deps-installer": minor
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Restructured the `minimumReleaseAge` lockfile revalidation gate around a generic `ResolutionVerifier` interface. Each resolver may now export a sibling verifier factory (today: `createNpmResolutionVerifier`) that re-checks an already-resolved lockfile entry against its policies; `createResolver`'s companion `createResolutionVerifier` combines them and the `Client` exposes the combined `verifyResolution` for the install layer to consume. The npm verifier reuses the same on-disk metadata mirror the resolver writes to, so steady-state installs pay only a headers-only conditional GET per locked package [#11675](https://github.com/pnpm/pnpm/issues/11675).
|
||||
5
.changeset/chilly-meteors-enter.md
Normal file
5
.changeset/chilly-meteors-enter.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/releasing.commands": patch
|
||||
---
|
||||
|
||||
Fix scoped packages without a publishConfig.access setting being published with public access.
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
"@pnpm/config.version-policy": minor
|
||||
"@pnpm/deps.inspection.outdated": patch
|
||||
"@pnpm/engine.pm.commands": patch
|
||||
"@pnpm/exec.commands": patch
|
||||
"@pnpm/installing.deps-resolver": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Make `pnpm self-update` respect `minimumReleaseAge` (and `minimumReleaseAgeExclude`) when resolving which pnpm version to install.
|
||||
|
||||
When the `latest` dist-tag points to a version newer than the configured age threshold, `self-update` now selects the newest mature version instead unless excluded by `minimumReleaseAgeExclude`.
|
||||
|
||||
Also makes `dlx` and `outdated` surface invalid `minimumReleaseAgeExclude` patterns under the same `ERR_PNPM_INVALID_MINIMUM_RELEASE_AGE_EXCLUDE` error code already used by `install`, instead of leaking the internal `ERR_PNPM_INVALID_VERSION_UNION` / `ERR_PNPM_NAME_PATTERN_IN_VERSION_UNION` codes.
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
"@pnpm/config.reader": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
**fix**: global installs respect global config build policy (e.g., `dangerouslyAllowAllBuilds` from config.yaml) when GVS is enabled [#9249](https://github.com/pnpm/pnpm/issues/9249).
|
||||
|
||||
The global virtual-store (GVS) default `allowBuilds = {}` was applied before workspace manifest settings were read and before global config values (stripped by `extractAndRemoveDependencyBuildOptions`) were re-applied via `globalDepsBuildConfig`. This caused `hasDependencyBuildOptions` to return `true` (because `{}` is not null), blocking restoration of global config values like `dangerouslyAllowAllBuilds`. As a result, global installs skipped all build scripts even when the config explicitly allowed them.
|
||||
|
||||
This fix moves the GVS default to **after** workspace manifest reading and `globalDepsBuildConfig` re-application, so that:
|
||||
1. Workspace manifest `allowBuilds` takes precedence (if present)
|
||||
2. Global config `dangerouslyAllowAllBuilds` is properly restored (if set and no workspace policy exists)
|
||||
3. Empty `{}` is only applied as a last resort when no policy is configured anywhere
|
||||
6
.changeset/fix-minimum-release-age.md
Normal file
6
.changeset/fix-minimum-release-age.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/resolving.npm-resolver": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fix `minimumReleaseAgeExclude` handling in npm resolution fast paths so excluded packages do not get pinned to stale versions. Excludes are honored consistently during `publishedBy` metadata selection and cache-mtime shortcuts.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@pnpm/lockfile.fs": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fix lockfile parsing failures when `pnpm-lock.yaml` contains CRLF line endings and multiple YAML documents [#11612](https://github.com/pnpm/pnpm/issues/11612).
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
"@pnpm/building.after-install": patch
|
||||
"@pnpm/building.during-install": patch
|
||||
"@pnpm/deps.graph-builder": patch
|
||||
"@pnpm/deps.graph-hasher": patch
|
||||
"@pnpm/engine.runtime.system-node-version": minor
|
||||
"@pnpm/installing.deps-installer": patch
|
||||
"@pnpm/installing.deps-resolver": patch
|
||||
"@pnpm/installing.deps-restorer": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
**fix**: anchor the side-effects-cache key and global-virtual-store hash to the project's script-runner Node — `engines.runtime` pin when present, shell `node` otherwise — instead of pnpm's own runtime.
|
||||
|
||||
`ENGINE_NAME` (the `<platform>;<arch>;node<major>` prefix used as the side-effects-cache key and the engine portion of the GVS hash) was computed from `process.version` — the Node that runs pnpm itself. That was wrong in two situations:
|
||||
|
||||
1. **`@pnpm/exe` SEA bundle.** The bundle has its own embedded Node, not the `node` on the user's `PATH` that actually spawns lifecycle scripts. Two pnpm installations on the same machine (one SEA, one npm-package) therefore disagreed on the cache key, partitioning the side-effects cache and the global virtual store across two Node majors even though both installs would run scripts on the same shell `node`.
|
||||
2. **`engines.runtime` / `devEngines.runtime` pin.** When a project pins a Node version via `devEngines.runtime` (pnpm v11+), pnpm downloads that Node into `node_modules/node/` and uses it to run lifecycle scripts. But the hash still anchored to whichever Node ran pnpm itself, not to the pinned Node — so two installs of the same project with two different runner Nodes would still disagree on the GVS slot path even though scripts run on the same pinned Node.
|
||||
|
||||
Three changes:
|
||||
|
||||
- `@pnpm/engine.runtime.system-node-version` now exports `engineName(nodeVersion?)` and `findRuntimeNodeVersion(snapshotKeys)`. `engineName()` resolves the version in this order: explicit override → `getSystemNodeVersion()` (which already prefers `node --version` over `process.version` in SEA contexts) → `process.version`. `findRuntimeNodeVersion` scans an iterable of lockfile snapshot keys for a `node@runtime:<version>` entry and returns its bare version string.
|
||||
- `@pnpm/deps.graph-hasher`'s `calcDepState` and `calcGraphNodeHash`/`iterateHashedGraphNodes` now accept a `nodeVersion?` (in the options bag for the first, as a trailing parameter / ctx field for the others), forwarded to `engineName()`. The default (no override) preserves the pre-change behaviour. The legacy `ENGINE_NAME` constant in `@pnpm/constants` is unchanged so external consumers and existing tests keep working; in non-SEA, non-pinned contexts every value lines up.
|
||||
- Every install-side caller of the graph-hasher (`@pnpm/installing.deps-resolver`, `@pnpm/installing.deps-restorer`, `@pnpm/installing.deps-installer`, `@pnpm/building.during-install`, `@pnpm/building.after-install`, `@pnpm/deps.graph-builder`) now derives the project's pinned runtime via `findRuntimeNodeVersion(Object.keys(graph))` once per invocation and threads it through.
|
||||
|
||||
On upgrade, two one-time GVS slot churns are possible:
|
||||
|
||||
- **SEA-pnpm users** without a runtime pin: slots that previously hashed under the embedded-Node major (e.g. `node26`) now hash under the shell-Node major (e.g. `node24`), matching what pacquet, the npm-published `pnpm` package, and any other pnpm-compatible tool already produce.
|
||||
- **Projects with a `devEngines.runtime` pin**: slots that previously hashed under the runner's Node major now hash under the pinned Node major, matching what the lifecycle scripts will actually run on.
|
||||
|
||||
In both cases the old slots become prune-eligible.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@pnpm/config.reader": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fixed `pnpm publish` failing with a 404 when authentication relied on OIDC trusted publishing alongside an `.npmrc` written by `actions/setup-node` (`_authToken=${NODE_AUTH_TOKEN}`) without `NODE_AUTH_TOKEN` being set. Unresolved `${VAR}` placeholders in auth values are now treated as empty rather than passed through verbatim, so the literal placeholder no longer surfaces as a bearer token when OIDC fallback is the intended auth source [#11513](https://github.com/pnpm/pnpm/issues/11513).
|
||||
9
.changeset/reuse-current-lockfile-when-wanted-missing.md
Normal file
9
.changeset/reuse-current-lockfile-when-wanted-missing.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"@pnpm/installing.deps-installer": patch
|
||||
"@pnpm/installing.context": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Skip dependency re-resolution when `pnpm-lock.yaml` is missing but `node_modules/.pnpm/lock.yaml` exists and still satisfies the manifest. `pnpm install` now reuses the materialized snapshot to regenerate `pnpm-lock.yaml` instead of walking the registry to rebuild it from scratch, turning the cache+node_modules variation into a near-no-op for users who deleted the lockfile but kept the install [#11993](https://github.com/pnpm/pnpm/issues/11993).
|
||||
|
||||
`--frozen-lockfile` still refuses to proceed when `pnpm-lock.yaml` is absent — the regenerated lockfile must be committed, so failing loudly is the correct behavior for CI.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@pnpm/installing.deps-installer": minor
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
`minimumReleaseAge` is now re-checked against `pnpm-lock.yaml` before any tarball is installed, so a freshly-published version pinned in the lockfile (e.g. by a developer who bypassed the policy locally) is no longer installed silently by other consumers or CI. Violating entries abort the install with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`; `minimumReleaseAgeExclude` is honored. [#10438](https://github.com/pnpm/pnpm/issues/10438).
|
||||
6
.github/actions/rustup/action.yml
vendored
6
.github/actions/rustup/action.yml
vendored
@@ -7,27 +7,21 @@ inputs:
|
||||
clippy:
|
||||
default: false
|
||||
required: false
|
||||
type: boolean
|
||||
fmt:
|
||||
default: false
|
||||
required: false
|
||||
type: boolean
|
||||
docs:
|
||||
default: false
|
||||
required: false
|
||||
type: boolean
|
||||
restore-cache:
|
||||
default: true
|
||||
required: false
|
||||
type: boolean
|
||||
save-cache:
|
||||
default: false
|
||||
required: false
|
||||
type: boolean
|
||||
shared-key:
|
||||
default: 'warm'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
89
.github/workflows/benchmark.yml
vendored
89
.github/workflows/benchmark.yml
vendored
@@ -18,10 +18,23 @@ on:
|
||||
required: false
|
||||
default: '1'
|
||||
type: string
|
||||
push:
|
||||
# Build Bencher's continuous baseline for the `pnpm` testbed.
|
||||
# Every merge to main re-runs the bench so PR comparisons have
|
||||
# an up-to-date reference; cancel-in-progress stays off below
|
||||
# so we never throw away a partial run.
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
checks: write
|
||||
|
||||
# Don't cancel-in-progress — killing a bench mid-run wastes a long
|
||||
# CI job and produces no usable data.
|
||||
concurrency:
|
||||
group: benchmark-${{ inputs.pr_number || github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
@@ -52,13 +65,15 @@ jobs:
|
||||
with:
|
||||
runtime: node@26.0.0
|
||||
|
||||
- name: Install hyperfine
|
||||
run: |
|
||||
wget -q https://github.com/sharkdp/hyperfine/releases/download/v1.18.0/hyperfine_1.18.0_amd64.deb
|
||||
sudo dpkg -i hyperfine_1.18.0_amd64.deb
|
||||
- name: Install Rust Toolchain
|
||||
uses: ./.github/actions/rustup
|
||||
with:
|
||||
shared-key: pnpm-benchmark
|
||||
|
||||
- name: Compile
|
||||
run: pnpm run compile
|
||||
- name: Install hyperfine
|
||||
uses: ./.github/actions/binstall
|
||||
with:
|
||||
packages: hyperfine@1.18.0
|
||||
|
||||
- name: Run benchmarks
|
||||
id: bench
|
||||
@@ -72,6 +87,68 @@ jobs:
|
||||
RUNS: ${{ inputs.runs }}
|
||||
WARMUP: ${{ inputs.warmup }}
|
||||
|
||||
- name: Install Bencher CLI
|
||||
if: steps.bench.outputs.bench_dir != ''
|
||||
uses: bencherdev/bencher@50fb1e138651a46d2fb704fab1adab38c181552e # v0.6.6
|
||||
|
||||
- name: Upload results to Bencher
|
||||
if: steps.bench.outputs.bench_dir != ''
|
||||
env:
|
||||
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
|
||||
BENCH_DIR: ${{ steps.bench.outputs.bench_dir }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ -z "${BENCHER_API_TOKEN:-}" ]; then
|
||||
echo "::notice::BENCHER_API_TOKEN not set, skipping Bencher upload"
|
||||
exit 0
|
||||
fi
|
||||
if [ ! -f "$BENCH_DIR/bencher-results.json" ]; then
|
||||
echo "::warning::bencher-results.json not found, skipping upload"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# `bencher run --file` takes hyperfine JSON via the
|
||||
# shell_hyperfine adapter. Branch policy:
|
||||
# - push to main → record into the `main` branch (baseline)
|
||||
# - workflow_dispatch with pr_number → record into `pr/<n>`,
|
||||
# forked from main at the latest baseline
|
||||
# - workflow_dispatch without pr_number → record into the
|
||||
# ref's branch name (e.g. a feature branch), forked from main
|
||||
args=(
|
||||
--project pnpm
|
||||
--testbed pnpm
|
||||
--adapter shell_hyperfine
|
||||
--file "$BENCH_DIR/bencher-results.json"
|
||||
--github-actions "$GITHUB_TOKEN"
|
||||
)
|
||||
# `--start-point-clone-thresholds` so the forked branch inherits
|
||||
# the threshold configured on main; `--err` so the workflow fails
|
||||
# when a sample breaches the upper boundary. Main pushes skip
|
||||
# both — by then the regression has already landed.
|
||||
if [ "$EVENT_NAME" = "push" ] || [ "$REF_NAME" = "main" ]; then
|
||||
args+=(--branch main)
|
||||
elif [ -n "$INPUT_PR_NUMBER" ]; then
|
||||
args+=(
|
||||
--branch "pr/$INPUT_PR_NUMBER"
|
||||
--start-point main
|
||||
--start-point-reset
|
||||
--start-point-clone-thresholds
|
||||
--err
|
||||
)
|
||||
else
|
||||
args+=(
|
||||
--branch "$REF_NAME"
|
||||
--start-point main
|
||||
--start-point-reset
|
||||
--start-point-clone-thresholds
|
||||
--err
|
||||
)
|
||||
fi
|
||||
bencher run "${args[@]}"
|
||||
|
||||
- name: Comment on PR
|
||||
if: steps.bench.outputs.bench_dir != ''
|
||||
env:
|
||||
|
||||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -15,9 +15,6 @@ jobs:
|
||||
name: Compile & Lint
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
pnpm_config_verify_deps_before_run: false
|
||||
|
||||
steps:
|
||||
- name: Checkout Commit
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -25,10 +22,6 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Install pnpm
|
||||
uses: pnpm/setup@b1cac37306e39c21283b9dd6cb0ac288fb35ba6b
|
||||
with:
|
||||
install: false
|
||||
- name: pacquet install
|
||||
run: pnx --config.minimum-release-age=0 pacquet install --frozen-lockfile
|
||||
- name: Compile TypeScript
|
||||
run: pn compile-only
|
||||
- name: Lint
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -73,4 +73,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
|
||||
4
.github/workflows/pacquet-cargo-unused.yml
vendored
4
.github/workflows/pacquet-cargo-unused.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: ./.github/actions/rustup
|
||||
|
||||
- name: Install cargo-unused-features
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: cargo-unused-features
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
|
||||
- name: Install cargo-udeps
|
||||
if: steps.filter.outputs.src == 'true'
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: cargo-udeps
|
||||
|
||||
|
||||
27
.github/workflows/pacquet-ci.yml
vendored
27
.github/workflows/pacquet-ci.yml
vendored
@@ -1,5 +1,12 @@
|
||||
name: Pacquet CI
|
||||
|
||||
# Despite the name, this workflow covers every Rust crate in the
|
||||
# repo — pacquet and pnpm-registry alike. They share the same cargo
|
||||
# workspace, so `just test`, dylint, doc, etc. all see both stacks
|
||||
# in one pass; running two workflows would just duplicate the work.
|
||||
# Trigger paths intentionally include `registry/**` for the same
|
||||
# reason.
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -9,6 +16,7 @@ on:
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
- 'pacquet/**'
|
||||
- 'registry/**'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- 'rust-toolchain.toml'
|
||||
@@ -27,6 +35,7 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- 'pacquet/**'
|
||||
- 'registry/**'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- 'rust-toolchain.toml'
|
||||
@@ -83,10 +92,10 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --locked -- -D warnings
|
||||
run: cargo clippy --locked --workspace --all-targets -- -D warnings
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: just
|
||||
|
||||
@@ -94,7 +103,7 @@ jobs:
|
||||
run: just install
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
@@ -140,7 +149,7 @@ jobs:
|
||||
|
||||
- uses: crate-ci/typos@5374cbf686e897b15713110e233094e2874de7ef # v1.46.1
|
||||
with:
|
||||
files: pacquet
|
||||
files: pacquet registry
|
||||
|
||||
deny:
|
||||
name: Cargo Deny
|
||||
@@ -160,7 +169,7 @@ jobs:
|
||||
|
||||
- name: Install cargo-deny
|
||||
if: steps.filter.outputs.src == 'true'
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: cargo-deny
|
||||
|
||||
@@ -256,7 +265,13 @@ jobs:
|
||||
- name: Install cargo-dylint and dylint-link
|
||||
uses: ./.github/actions/binstall
|
||||
with:
|
||||
packages: cargo-dylint dylint-link
|
||||
# Pin to 6.0.0 until trailofbits/dylint cuts a 6.0.2. The
|
||||
# 6.0.1 binstall artifacts (published 2026-05-26 17:51 UTC)
|
||||
# fail at driver bootstrap with `failed to read
|
||||
# /home/runner/work/dylint/dylint/driver/Cargo.toml`, the
|
||||
# path baked in by dylint's own CI checkout. Downstream
|
||||
# runners don't have that workspace so the step aborts.
|
||||
packages: cargo-dylint@6.0.0 dylint-link@6.0.0
|
||||
|
||||
- name: Run dylint
|
||||
# `-D warnings` must come in via `RUSTFLAGS`, not as a trailing
|
||||
|
||||
8
.github/workflows/pacquet-codecov.yml
vendored
8
.github/workflows/pacquet-codecov.yml
vendored
@@ -11,11 +11,13 @@ on:
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
- 'pacquet/**/*.rs'
|
||||
- 'registry/**/*.rs'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'pacquet/**/*.rs'
|
||||
- 'registry/**/*.rs'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -36,12 +38,12 @@ jobs:
|
||||
uses: ./.github/actions/rustup
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
|
||||
- name: Install cargo-nextest
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
|
||||
@@ -64,7 +66,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: just
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ permissions:
|
||||
actions: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
@@ -131,3 +132,41 @@ jobs:
|
||||
edit-mode: replace
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
body-file: benchmark-artifact/SUMMARY.md
|
||||
|
||||
- name: Install Bencher CLI
|
||||
if: hashFiles('benchmark-artifact/bencher-results.json') != ''
|
||||
uses: bencherdev/bencher@50fb1e138651a46d2fb704fab1adab38c181552e # v0.6.6
|
||||
|
||||
- name: Upload PR results to Bencher
|
||||
# workflow_run runs in the base-repo privilege context, so
|
||||
# BENCHER_API_TOKEN is available even for fork PRs. PR number
|
||||
# comes from the trusted workflow_run payload (resolved in
|
||||
# `meta` above), never from the artifact body. `--ci-number`
|
||||
# is required here because Bencher can't infer the PR from a
|
||||
# workflow_run event the way it does on pull_request.
|
||||
if: hashFiles('benchmark-artifact/bencher-results.json') != ''
|
||||
env:
|
||||
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.meta.outputs.pr }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -z "${BENCHER_API_TOKEN:-}" ]; then
|
||||
echo "::notice::BENCHER_API_TOKEN not set, skipping Bencher upload"
|
||||
exit 0
|
||||
fi
|
||||
# `--start-point-clone-thresholds` so the PR branch inherits the
|
||||
# threshold configured on main; `--err` so the action surfaces
|
||||
# a regression on the PR check.
|
||||
bencher run \
|
||||
--project pnpm \
|
||||
--testbed pacquet \
|
||||
--branch "pr/$PR_NUMBER" \
|
||||
--start-point main \
|
||||
--start-point-reset \
|
||||
--start-point-clone-thresholds \
|
||||
--err \
|
||||
--adapter shell_hyperfine \
|
||||
--file benchmark-artifact/bencher-results.json \
|
||||
--ci-number "$PR_NUMBER" \
|
||||
--github-actions "$GITHUB_TOKEN"
|
||||
|
||||
228
.github/workflows/pacquet-integrated-benchmark.yml
vendored
228
.github/workflows/pacquet-integrated-benchmark.yml
vendored
@@ -2,6 +2,25 @@ name: Pacquet Integrated-Benchmark
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
# Update Bencher's baseline for the `pacquet` testbed whenever
|
||||
# something that could move pacquet's install perf lands on main.
|
||||
# Paths mirror the pull_request filter below.
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'pacquet/**/*.rs'
|
||||
- 'pacquet/**/Cargo.toml'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- 'rust-toolchain.toml'
|
||||
- 'justfile'
|
||||
- 'pacquet/tasks/registry-mock/package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- 'pacquet/tasks/integrated-benchmark/src/fixtures/**'
|
||||
- '.github/actions/rustup/**'
|
||||
- '.github/actions/binstall/**'
|
||||
- '.github/workflows/pacquet-integrated-benchmark.yml'
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
@@ -29,6 +48,7 @@ concurrency:
|
||||
# the base-repo privilege context via `workflow_run`.
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
@@ -38,6 +58,13 @@ jobs:
|
||||
os: [ubuntu-latest] # windows is skipped because of complexity, macos is skipped because of inconsistency
|
||||
name: Run benchmark on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
# On main, HEAD *is* main — comparing them is wasted work, so the
|
||||
# benchmark runs a single revision and the result is uploaded to
|
||||
# Bencher as the new baseline. On every other ref (PRs and
|
||||
# workflow_dispatch from a non-main branch), compare HEAD against
|
||||
# main so the report shows the relative shift.
|
||||
BENCHMARK_TARGETS: ${{ github.ref_name == 'main' && 'pacquet@HEAD' || 'pacquet@HEAD pacquet@main' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -67,14 +94,21 @@ jobs:
|
||||
subsection 'Inspecting branches...'
|
||||
git branch
|
||||
|
||||
- name: Cache verdaccio
|
||||
- name: Cache pnpm-registry storage
|
||||
# Persists pnpm-registry's `--storage` dir across CI runs so
|
||||
# cold-cache benchmark scenarios don't refetch ~2.3k unscoped
|
||||
# packages from npmjs on every run. Replaces the older
|
||||
# `~/.local/share/verdaccio/storage` cache from when the mock
|
||||
# was Node + Verdaccio. Path matches `runtime_storage()` in
|
||||
# `pacquet/tasks/registry-mock/src/dirs.rs` (defaults to
|
||||
# `$HOME/.cache/pnpm-registry/storage`).
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
key: integrated-benchmark-verdaccio-${{ hashFiles('pacquet/tasks/integrated-benchmark/src/fixtures/pnpm-lock.yaml') }}
|
||||
key: integrated-benchmark-pnpm-registry-${{ hashFiles('pacquet/tasks/integrated-benchmark/src/fixtures/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
integrated-benchmark-verdaccio-
|
||||
integrated-benchmark-pnpm-registry-
|
||||
path: |
|
||||
~/.local/share/verdaccio/storage
|
||||
~/.cache/pnpm-registry/storage
|
||||
timeout-minutes: 1
|
||||
continue-on-error: true
|
||||
|
||||
@@ -122,7 +156,7 @@ jobs:
|
||||
packages: hyperfine@1.18.0
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: just
|
||||
|
||||
@@ -135,9 +169,9 @@ jobs:
|
||||
- name: Precompile benchmark revisions
|
||||
shell: bash
|
||||
timeout-minutes: 15
|
||||
run: just integrated-benchmark --build-only HEAD main
|
||||
run: just integrated-benchmark --build-only $BENCHMARK_TARGETS
|
||||
|
||||
- name: 'Benchmark: Frozen Lockfile'
|
||||
- name: 'Benchmark: Isolated linker: fresh restore, cold cache + cold store'
|
||||
shell: bash
|
||||
# Hyperfine has no per-command timeout, so a single hanging
|
||||
# install takes the whole step down with the GHA default job
|
||||
@@ -145,30 +179,44 @@ jobs:
|
||||
# headroom, and a failure surfaces fast enough to iterate on.
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
just integrated-benchmark --scenario=frozen-lockfile --verdaccio --with-pnpm HEAD main
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE.json
|
||||
just integrated-benchmark --scenario=isolated-linker.fresh-restore.cold-cache.cold-store --registry=verdaccio $BENCHMARK_TARGETS
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_RESTORE_COLD_CACHE_COLD_STORE.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_RESTORE_COLD_CACHE_COLD_STORE.json
|
||||
|
||||
- name: 'Benchmark: Frozen Lockfile (Hot Cache)'
|
||||
- name: 'Benchmark: Isolated linker: fresh restore, hot cache + hot store'
|
||||
shell: bash
|
||||
# Same timeout rationale as the cold-cache step above.
|
||||
# Mirrors pnpm's "Headless install (frozen lockfile, warm
|
||||
# store+cache)" benchmark: each timed run wipes only
|
||||
# `node_modules`, the per-revision store survives, and
|
||||
# hyperfine's warmup run is what populates it on the first
|
||||
# Same timeout rationale as the cold-cache step above. Each
|
||||
# timed run wipes only `node_modules`; the per-revision store
|
||||
# survives, and hyperfine's warmup populates it on the first
|
||||
# iteration so timed runs all see a hot store.
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
just integrated-benchmark --scenario=frozen-lockfile-hot-cache --verdaccio --with-pnpm HEAD main
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE_HOT_CACHE.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE_HOT_CACHE.json
|
||||
just integrated-benchmark --scenario=isolated-linker.fresh-restore.hot-cache.hot-store --registry=verdaccio $BENCHMARK_TARGETS
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_RESTORE_HOT_CACHE_HOT_STORE.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_RESTORE_HOT_CACHE_HOT_STORE.json
|
||||
|
||||
# - name: 'Benchmark: Clean Install'
|
||||
# shell: bash
|
||||
# run: |
|
||||
# just integrated-benchmark --scenario=clean-install --verdaccio --with-pnpm HEAD main
|
||||
# cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.md
|
||||
# cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.json
|
||||
- name: 'Benchmark: Isolated linker: fresh install, cold cache + cold store'
|
||||
shell: bash
|
||||
# No-lockfile scenario: pacquet does fresh resolution against
|
||||
# the registry (pnpm/pnpm#11832), so a single iteration runs
|
||||
# longer than the restore steps. 20 min leaves headroom for the
|
||||
# default hyperfine 1 warmup + 10 timed runs across both
|
||||
# benchmark targets without losing the per-command timeout
|
||||
# safety net the other steps document.
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
just integrated-benchmark --scenario=isolated-linker.fresh-install.cold-cache.cold-store --registry=verdaccio $BENCHMARK_TARGETS
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_INSTALL_COLD_CACHE_COLD_STORE.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_INSTALL_COLD_CACHE_COLD_STORE.json
|
||||
|
||||
- name: 'Benchmark: Isolated linker: fresh install, hot cache + hot store'
|
||||
shell: bash
|
||||
# Same timeout rationale as the cold-cache install step above.
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
just integrated-benchmark --scenario=isolated-linker.fresh-install.hot-cache.hot-store --registry=verdaccio $BENCHMARK_TARGETS
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_INSTALL_HOT_CACHE_HOT_STORE.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_INSTALL_HOT_CACHE_HOT_STORE.json
|
||||
|
||||
- name: Generate summary
|
||||
shell: bash
|
||||
@@ -176,52 +224,94 @@ jobs:
|
||||
(
|
||||
echo '## Integrated-Benchmark Report (${{ runner.os }})'
|
||||
echo
|
||||
echo '### Scenario: Frozen Lockfile'
|
||||
echo '### Scenario: Isolated linker: fresh restore, cold cache + cold store'
|
||||
echo
|
||||
cat bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE.md
|
||||
cat bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_RESTORE_COLD_CACHE_COLD_STORE.md
|
||||
echo
|
||||
echo '<details><summary>BENCHMARK_REPORT.json</summary>'
|
||||
echo
|
||||
echo '```json'
|
||||
jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE.json
|
||||
jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_RESTORE_COLD_CACHE_COLD_STORE.json
|
||||
echo '```'
|
||||
echo
|
||||
echo '</details>'
|
||||
echo
|
||||
echo '### Scenario: Frozen Lockfile (Hot Cache)'
|
||||
echo '### Scenario: Isolated linker: fresh restore, hot cache + hot store'
|
||||
echo
|
||||
cat bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE_HOT_CACHE.md
|
||||
cat bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_RESTORE_HOT_CACHE_HOT_STORE.md
|
||||
echo
|
||||
echo '<details><summary>BENCHMARK_REPORT.json</summary>'
|
||||
echo
|
||||
echo '```json'
|
||||
jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE_HOT_CACHE.json
|
||||
jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_RESTORE_HOT_CACHE_HOT_STORE.json
|
||||
echo '```'
|
||||
echo
|
||||
echo '</details>'
|
||||
echo
|
||||
echo '### Scenario: Isolated linker: fresh install, cold cache + cold store'
|
||||
echo
|
||||
cat bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_INSTALL_COLD_CACHE_COLD_STORE.md
|
||||
echo
|
||||
echo '<details><summary>BENCHMARK_REPORT.json</summary>'
|
||||
echo
|
||||
echo '```json'
|
||||
jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_INSTALL_COLD_CACHE_COLD_STORE.json
|
||||
echo '```'
|
||||
echo
|
||||
echo '</details>'
|
||||
echo
|
||||
echo '### Scenario: Isolated linker: fresh install, hot cache + hot store'
|
||||
echo
|
||||
cat bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_INSTALL_HOT_CACHE_HOT_STORE.md
|
||||
echo
|
||||
echo '<details><summary>BENCHMARK_REPORT.json</summary>'
|
||||
echo
|
||||
echo '```json'
|
||||
jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_ISOLATED_FRESH_INSTALL_HOT_CACHE_HOT_STORE.json
|
||||
echo '```'
|
||||
echo
|
||||
echo '</details>'
|
||||
# echo
|
||||
# echo '### Scenario: Clean Install'
|
||||
# echo
|
||||
# cat bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.md
|
||||
# echo
|
||||
# echo '<details><summary>BENCHMARK_REPORT.json</summary>'
|
||||
# echo
|
||||
# echo '```json'
|
||||
# jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.json
|
||||
# echo '```'
|
||||
# echo
|
||||
# echo '</details>'
|
||||
) > bench-work-env/SUMMARY.md
|
||||
|
||||
- name: Build Bencher-shaped report
|
||||
# Combine the four scenario JSONs into one hyperfine-shaped
|
||||
# file that Bencher's shell_hyperfine adapter accepts. We
|
||||
# keep only the @HEAD result from each scenario and rename
|
||||
# `.command` to the scenario name, so Bencher names the
|
||||
# benchmark after the scenario instead of `pacquet@HEAD`.
|
||||
shell: bash
|
||||
run: |
|
||||
scenarios=(
|
||||
'ISOLATED_FRESH_RESTORE_COLD_CACHE_COLD_STORE:isolated-linker.fresh-restore.cold-cache.cold-store'
|
||||
'ISOLATED_FRESH_RESTORE_HOT_CACHE_HOT_STORE:isolated-linker.fresh-restore.hot-cache.hot-store'
|
||||
'ISOLATED_FRESH_INSTALL_COLD_CACHE_COLD_STORE:isolated-linker.fresh-install.cold-cache.cold-store'
|
||||
'ISOLATED_FRESH_INSTALL_HOT_CACHE_HOT_STORE:isolated-linker.fresh-install.hot-cache.hot-store'
|
||||
)
|
||||
inputs=()
|
||||
for entry in "${scenarios[@]}"; do
|
||||
file_tag="${entry%%:*}"
|
||||
name="${entry#*:}"
|
||||
src="bench-work-env/BENCHMARK_REPORT_${file_tag}.json"
|
||||
dst="bench-work-env/${name}-bencher.json"
|
||||
jq --arg s "$name" \
|
||||
'.results |= [.[] | select(.command == "pacquet@HEAD") | .command = $s]' \
|
||||
"$src" > "$dst"
|
||||
inputs+=("$dst")
|
||||
done
|
||||
jq -s '{results: map(.results) | add}' \
|
||||
"${inputs[@]}" > bench-work-env/bencher-results.json
|
||||
|
||||
- name: Stage artifact contents
|
||||
# The artifact carries only the rendered report. The PR number
|
||||
# and runner OS are derived from the trusted workflow_run
|
||||
# event payload in the comment-posting workflow, not from
|
||||
# fork-controlled artifact contents.
|
||||
# The artifact carries the rendered report plus the
|
||||
# Bencher-shaped JSON consumed by the comment-posting
|
||||
# workflow. The PR number and runner OS are derived from the
|
||||
# trusted workflow_run event payload in that workflow, not
|
||||
# from fork-controlled artifact contents.
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p benchmark-artifact
|
||||
cp bench-work-env/SUMMARY.md benchmark-artifact/SUMMARY.md
|
||||
cp bench-work-env/bencher-results.json benchmark-artifact/bencher-results.json
|
||||
|
||||
- name: Upload benchmark report
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
@@ -230,3 +320,47 @@ jobs:
|
||||
path: benchmark-artifact/
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
- name: Install Bencher CLI
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
uses: bencherdev/bencher@50fb1e138651a46d2fb704fab1adab38c181552e # v0.6.6
|
||||
|
||||
- name: Upload results to Bencher
|
||||
# Runs on push and workflow_dispatch — both execute in the
|
||||
# base-repo privilege context where secrets are available.
|
||||
# PR runs (including from forks) skip this step and upload
|
||||
# via the workflow_run comment workflow instead.
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -z "${BENCHER_API_TOKEN:-}" ]; then
|
||||
echo "::notice::BENCHER_API_TOKEN not set, skipping Bencher upload"
|
||||
exit 0
|
||||
fi
|
||||
args=(
|
||||
--project pnpm
|
||||
--testbed pacquet
|
||||
--adapter shell_hyperfine
|
||||
--file bench-work-env/bencher-results.json
|
||||
--github-actions "$GITHUB_TOKEN"
|
||||
)
|
||||
# `--start-point-clone-thresholds` so the forked branch inherits
|
||||
# the threshold configured on main; `--err` so the workflow fails
|
||||
# when a sample breaches the upper boundary. Main pushes skip
|
||||
# both — by then the regression has already landed.
|
||||
if [ "$REF_NAME" = "main" ]; then
|
||||
args+=(--branch main)
|
||||
else
|
||||
args+=(
|
||||
--branch "$REF_NAME"
|
||||
--start-point main
|
||||
--start-point-reset
|
||||
--start-point-clone-thresholds
|
||||
--err
|
||||
)
|
||||
fi
|
||||
bencher run "${args[@]}"
|
||||
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install critcmp
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: critcmp
|
||||
|
||||
|
||||
33
.github/workflows/pacquet-release-to-npm.yml
vendored
33
.github/workflows/pacquet-release-to-npm.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install cross
|
||||
uses: taiki-e/install-action@e1c4cd42111751368541a7cb5db3522bd1f846a4 # v2.78.0
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: cross
|
||||
|
||||
@@ -72,6 +72,28 @@ jobs:
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: Validate version input
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "::error::Invalid version: '$VERSION' (expected semver like 0.2.3 or 0.2.3-rc.1)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Inject version into pacquet/crates/cli/src/cli_args.rs
|
||||
# The version reported by `pacquet --version` is a hardcoded clap
|
||||
# attribute. Patch it here so the binary built for the matrix leg
|
||||
# reports the version we're about to publish.
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
file=pacquet/crates/cli/src/cli_args.rs
|
||||
perl -i -pe 's/#\[clap\(version = "[^"]*"\)\]/#[clap(version = "'"$VERSION"'")]/' "$file"
|
||||
grep -F "version = \"$VERSION\"" "$file"
|
||||
|
||||
- name: Build with cross
|
||||
run: cross build -p pacquet-cli --bin pacquet --release --target=${{ matrix.target }}
|
||||
|
||||
@@ -166,7 +188,7 @@ jobs:
|
||||
run: |
|
||||
node pacquet/npm/pacquet/scripts/generate-packages.mjs
|
||||
cat pacquet/npm/pacquet/package.json
|
||||
for package in pacquet/npm/pacquet*; do cat $package/package.json ; echo ; done
|
||||
for package in pacquet/npm/pacquet* pacquet/npm/pnpm-pacquet; do cat $package/package.json ; echo ; done
|
||||
|
||||
- name: Publish npm packages as latest
|
||||
# Auth is via npm's trusted publishing: `id-token: write` above grants
|
||||
@@ -174,7 +196,12 @@ jobs:
|
||||
# so no NPM_TOKEN is needed. `--provenance` attaches the same OIDC
|
||||
# token to a provenance attestation on each tarball.
|
||||
# The trailing slash on $package/ changes it to publishing the directory.
|
||||
# `pnpm-pacquet` is the `@pnpm/pacquet` scoped alias mirror —
|
||||
# same shim and same `@pacquet/<plat>-<arch>` optional deps as the
|
||||
# unscoped `pacquet`. Published alongside so users can adopt
|
||||
# either name; the per-platform binary sub-packages stay under
|
||||
# the `@pacquet/` scope (no need to mirror those).
|
||||
run: |
|
||||
for package in pacquet/npm/pacquet*; do
|
||||
for package in pacquet/npm/pacquet* pacquet/npm/pnpm-pacquet; do
|
||||
pnpm publish "$package/" --tag latest --access public --provenance --no-git-checks
|
||||
done
|
||||
|
||||
206
.github/workflows/pnpr-release-to-npm.yml
vendored
Normal file
206
.github/workflows/pnpr-release-to-npm.yml
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
name: Release @pnpm/pnpr
|
||||
|
||||
# Manual-trigger only. Type the version to publish — the workflow patches
|
||||
# registry/npm/pnpr/package.json with it before generating per-platform
|
||||
# packages and publishing. No git tag is created and no GitHub release
|
||||
# asset is uploaded; npm is the authoritative artifact store.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to publish (e.g. 0.2.3 or 0.2.3-rc.1)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # needed for actions/attest-build-provenance
|
||||
attestations: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
code-target: win32-x64
|
||||
|
||||
- os: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
code-target: win32-arm64
|
||||
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
code-target: linux-x64
|
||||
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
code-target: linux-arm64
|
||||
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
code-target: darwin-x64
|
||||
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
code-target: darwin-arm64
|
||||
|
||||
name: Package ${{ matrix.code-target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install cross
|
||||
uses: taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2.78.1
|
||||
with:
|
||||
tool: cross
|
||||
|
||||
- name: Cache
|
||||
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
shared-key: release-${{ matrix.target }}
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: Validate version input
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "::error::Invalid version: '$VERSION' (expected semver like 0.2.3 or 0.2.3-rc.1)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Inject version into registry/crates/pnpm-registry/Cargo.toml
|
||||
# `pnpm-registry --version` reads CARGO_PKG_VERSION via clap's
|
||||
# derive `version` attribute. Patch the crate's `version = "..."`
|
||||
# field so the binary built for the matrix leg reports the
|
||||
# version we're about to publish.
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
file=registry/crates/pnpm-registry/Cargo.toml
|
||||
perl -i -pe 's/^(version\s*=\s*)"[^"]*"/$1"$ENV{VERSION}"/' "$file"
|
||||
grep -E '^version\s*=\s*"'"$VERSION"'"' "$file"
|
||||
|
||||
- name: Build with cross
|
||||
run: cross build -p pnpm-registry --bin pnpm-registry --release --target=${{ matrix.target }}
|
||||
|
||||
# The binary is zipped to fix permission loss https://github.com/actions/upload-artifact#permission-loss
|
||||
# The Rust crate is `pnpm-registry`, but the npm wrapper exposes
|
||||
# the command as `pnpr` — rename here so the archive that
|
||||
# generate-packages.mjs picks up is already named `pnpr-<target>`.
|
||||
- name: Archive Binary
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
BIN_NAME=pnpr-${{ matrix.code-target }}
|
||||
mv target/${{ matrix.target }}/release/pnpm-registry.exe $BIN_NAME.exe
|
||||
7z a $BIN_NAME.zip $BIN_NAME.exe
|
||||
|
||||
# The binary is zipped to fix permission loss https://github.com/actions/upload-artifact#permission-loss
|
||||
- name: Archive Binary
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
BIN_NAME=pnpr-${{ matrix.code-target }}
|
||||
mv target/${{ matrix.target }}/release/pnpm-registry $BIN_NAME
|
||||
tar czf $BIN_NAME.tar.gz $BIN_NAME
|
||||
|
||||
- name: Attest build provenance
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-path: |
|
||||
pnpr-${{ matrix.code-target }}*
|
||||
|
||||
- name: Upload Binary
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: binaries-${{ matrix.code-target }}
|
||||
path: |
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Required for npm provenance attestations via OIDC.
|
||||
id-token: write
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate version input
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "::error::Invalid version: '$VERSION' (expected semver like 0.2.3 or 0.2.3-rc.1)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Inject version into registry/npm/pnpr/package.json
|
||||
# The committed package.json has no `version` field; the version comes
|
||||
# from the workflow_dispatch input. Patching it here means
|
||||
# generate-packages.mjs (which copies the version into each
|
||||
# per-platform manifest) and `pnpm publish` (which reads from
|
||||
# package.json) both see the right value.
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
jq --arg v "$VERSION" '.version = $v' registry/npm/pnpr/package.json > registry/npm/pnpr/package.json.tmp
|
||||
mv registry/npm/pnpr/package.json.tmp registry/npm/pnpr/package.json
|
||||
cat registry/npm/pnpr/package.json
|
||||
|
||||
- name: Install pnpm and Node
|
||||
uses: pnpm/setup@b1cac37306e39c21283b9dd6cb0ac288fb35ba6b
|
||||
with:
|
||||
runtime: node@22
|
||||
install: false
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: binaries-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Unzip
|
||||
uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28 # v1.0.0
|
||||
with:
|
||||
args: unzip -qq *.zip -d .
|
||||
|
||||
- name: Untar
|
||||
run: ls *.gz | xargs -i tar xf {}
|
||||
|
||||
- name: Generate npm packages
|
||||
run: |
|
||||
node registry/npm/pnpr/scripts/generate-packages.mjs
|
||||
cat registry/npm/pnpr/package.json
|
||||
for package in registry/npm/pnpr*; do cat $package/package.json ; echo ; done
|
||||
|
||||
- name: Publish npm packages as latest
|
||||
# Auth is via npm's trusted publishing: `id-token: write` above grants
|
||||
# this job an OIDC token that pnpm/npm exchange with the registry,
|
||||
# so no NPM_TOKEN is needed. `--provenance` attaches the same OIDC
|
||||
# token to a provenance attestation on each tarball.
|
||||
# The trailing slash on $package/ changes it to publishing the directory.
|
||||
run: |
|
||||
for package in registry/npm/pnpr*; do
|
||||
pnpm publish "$package/" --tag latest --access public --provenance --no-git-checks
|
||||
done
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: garnet-org/action@9e819143e63d6dda04bca2e90ac85e3cf0e5289d # v2
|
||||
- uses: garnet-org/action@2b7fc9d79b54f551b43358c27424a36064b3e078 # v2
|
||||
with:
|
||||
api_token: ${{ secrets.GARNET_API_TOKEN }}
|
||||
- name: Install pnpm and Node
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
# `gh release create` could replace this, but the release pipeline is
|
||||
# sensitive and softprops/action-gh-release is widely battle-tested.
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v2.5.0 # zizmor: ignore[superfluous-actions]
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 # zizmor: ignore[superfluous-actions]
|
||||
with:
|
||||
draft: true
|
||||
files: dist/*
|
||||
|
||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -29,9 +29,6 @@ jobs:
|
||||
|
||||
runs-on: ${{ inputs.platform }}
|
||||
|
||||
env:
|
||||
pnpm_config_verify_deps_before_run: false
|
||||
|
||||
steps:
|
||||
- name: Configure Git
|
||||
run: |
|
||||
@@ -43,16 +40,13 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- if: ${{ inputs.garnet }}
|
||||
uses: garnet-org/action@9e819143e63d6dda04bca2e90ac85e3cf0e5289d # v2
|
||||
uses: garnet-org/action@2b7fc9d79b54f551b43358c27424a36064b3e078 # v2
|
||||
with:
|
||||
api_token: ${{ secrets.GARNET_API_TOKEN }}
|
||||
- name: Install pnpm and Node
|
||||
uses: pnpm/setup@b1cac37306e39c21283b9dd6cb0ac288fb35ba6b
|
||||
with:
|
||||
install: false
|
||||
runtime: node@${{ inputs.node }}
|
||||
- name: pacquet install
|
||||
run: pnx --config.minimum-release-age=0 pacquet install --frozen-lockfile --no-runtime
|
||||
- name: Verify Node version
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
2
.github/workflows/update-latest.yml
vendored
2
.github/workflows/update-latest.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
environment: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: garnet-org/action@9e819143e63d6dda04bca2e90ac85e3cf0e5289d # v2
|
||||
- uses: garnet-org/action@2b7fc9d79b54f551b43358c27424a36064b3e078 # v2
|
||||
with:
|
||||
api_token: ${{ secrets.GARNET_API_TOKEN }}
|
||||
- name: Setup Node
|
||||
|
||||
2
.github/workflows/zizmor.yml
vendored
2
.github/workflows/zizmor.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
uses: zizmorcore/zizmor-action@a16621b09c6db4281f81a93cb393b05dcd7b7165 # v0.5.5
|
||||
with:
|
||||
# Fork PRs run with a read-only GITHUB_TOKEN, so SARIF upload to
|
||||
# Code scanning would fail. In that case, run zizmor anyway and
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
# @pnpm-private/updater
|
||||
|
||||
## 1100.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e8b3ae1]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/workspace.projects-reader@1101.0.8
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/lockfile.fs@1100.1.2
|
||||
- @pnpm/workspace.workspace-manifest-reader@1100.0.5
|
||||
|
||||
## 1100.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/workspace.projects-reader@1101.0.7
|
||||
|
||||
## 1100.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9cb48bb]
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/lockfile.fs@1100.1.1
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/workspace.projects-reader@1101.0.6
|
||||
- @pnpm/workspace.workspace-manifest-reader@1100.0.4
|
||||
|
||||
## 1100.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6e93f35]
|
||||
- Updated dependencies [2a9bd89]
|
||||
- @pnpm/lockfile.fs@1100.1.0
|
||||
- @pnpm/workspace.projects-reader@1101.0.5
|
||||
|
||||
## 1100.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm-private/updater",
|
||||
"version": "1100.0.11",
|
||||
"version": "1100.0.15",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
20
AGENTS.md
20
AGENTS.md
@@ -17,7 +17,7 @@ When you change one side, do the equivalent change on the other in the same PR i
|
||||
|
||||
"User-visible" means anything that affects the CLI surface or the on-disk contract: command-line flags and defaults, environment-variable handling, lockfile/manifest/state-file format, error codes and messages, log emissions parsed by `@pnpm/cli.default-reporter`, store layout, hook semantics. Pure internal refactors, perf wins, and TS-only test cleanups don't need mirroring.
|
||||
|
||||
**Scope caveat:** pacquet currently only implements `install`. Resolution and every other command (`update`, `add`, `remove`, `publish`, `exec`, `run`, `dlx`, `audit`, etc.) live only in the TypeScript code, so changes there don't need a pacquet-side port yet — they're outside pacquet's current surface area. The parity rule will widen as pacquet ports more commands; check what pacquet exposes before deciding whether your change is in scope.
|
||||
**Scope caveat:** pacquet's current surface area is the dependency-management commands — `install`, `add`, `update`, and `remove`. Every other command (`publish`, `exec`, `run`, `dlx`, `audit`, etc.) lives only in the TypeScript code, so changes there don't need a pacquet-side port yet. The parity rule will widen as pacquet ports more commands; check what pacquet exposes before deciding whether your change is in scope.
|
||||
|
||||
The pacquet-side obligation — pnpm is the source of truth, pacquet ports from it, never the other way around — is spelled out at [`pacquet/AGENTS.md`](./pacquet/AGENTS.md#the-cardinal-rule).
|
||||
|
||||
@@ -186,6 +186,24 @@ To ensure your code adheres to the style guide, run:
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
### Comments
|
||||
|
||||
Write code that explains itself. A reader should understand what a function does from its name, parameters, and types — not from prose above the call site.
|
||||
|
||||
Defaults:
|
||||
|
||||
- **Do not write a comment** that restates what the code already says. If renaming a variable, splitting a helper, or moving a check to a more obvious place would carry the information, do that instead.
|
||||
- **Do not repeat documentation** at call sites that already lives on the callee. If the function has a JSDoc, the call site shouldn't re-explain what calling it does. Update the JSDoc once; let every call site benefit.
|
||||
- **JSDoc is for the function's contract** — preconditions, postconditions, edge cases, why the function exists. Not for re-narrating the body.
|
||||
- **Do not record past implementation shape, refactor history, or "the previous code did X" framing.** That's what `git log` and `git blame` are for. Describe the current contract — what the code is and what it guarantees — not what it replaced. Phrasings like "used to", "previously", "the original X", or a parenthetical naming a removed type belong in the commit message, not in the source.
|
||||
|
||||
Write a comment only when:
|
||||
|
||||
- The reason for the code is non-obvious from reading it (a hidden invariant, a workaround for a known bug, a deliberate exception to the surrounding pattern).
|
||||
- The right name doesn't fit — e.g., a temporary technical constraint that's worth flagging but doesn't justify a new symbol.
|
||||
|
||||
Before adding a comment, ask: "Could I rename, restructure, or extract instead?" If yes, do that. The bar for prose-in-code is high; the bar for prose-that-restates-code is "don't."
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
### Error Type Checking in Jest (TypeScript only)
|
||||
|
||||
537
Cargo.lock
generated
537
Cargo.lock
generated
@@ -148,9 +148,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39bae1d3fa576f7c6519514180a72559268dd7d1fe104070956cb687bc6673bd"
|
||||
checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
@@ -217,6 +217,54 @@ dependencies = [
|
||||
"fs_extra",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
@@ -253,6 +301,19 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abaf6da45c74385272ddf00e1ac074c7d8a6c1a1dda376902bd6a427522a8b2c"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"blowfish",
|
||||
"getrandom 0.3.4",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
@@ -268,6 +329,16 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blowfish"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.1"
|
||||
@@ -291,6 +362,12 @@ version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
@@ -365,6 +442,16 @@ dependencies = [
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
@@ -1409,6 +1496,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.47.2"
|
||||
@@ -1668,6 +1764,12 @@ dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
@@ -1727,6 +1829,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@@ -1971,6 +2079,37 @@ version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-catalogs-config"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-catalogs-types",
|
||||
"pacquet-workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-catalogs-protocol-parser"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"pacquet-catalogs-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-catalogs-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-catalogs-protocol-parser",
|
||||
"pacquet-catalogs-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-catalogs-types"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-cli"
|
||||
version = "0.0.1"
|
||||
@@ -2012,6 +2151,7 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-fs",
|
||||
"pipe-trait",
|
||||
"rayon",
|
||||
"serde_json",
|
||||
@@ -2028,6 +2168,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"libc",
|
||||
"miette 7.6.0",
|
||||
"node-semver",
|
||||
"pacquet-network",
|
||||
"pacquet-package-is-installable",
|
||||
"pacquet-patching",
|
||||
@@ -2037,11 +2178,57 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde-saphyr",
|
||||
"serde_json",
|
||||
"smart-default",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-config-parse-overrides"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-catalogs-resolver",
|
||||
"pacquet-catalogs-types",
|
||||
"pacquet-resolving-parse-wanted-dependency",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-crypto-hash"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-crypto-shasums-file"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-network",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-deps-path"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"pacquet-crypto-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-detect-libc"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"pretty_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-diagnostics"
|
||||
version = "0.0.1"
|
||||
@@ -2068,6 +2255,63 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-engine-runtime-bun-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-crypto-shasums-file",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-network",
|
||||
"pacquet-resolving-npm-resolver",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ssri",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-engine-runtime-deno-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-network",
|
||||
"pacquet-resolving-npm-resolver",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ssri",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-engine-runtime-node-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"node-semver",
|
||||
"pacquet-crypto-shasums-file",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-network",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ssri",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-executor"
|
||||
version = "0.0.1"
|
||||
@@ -2082,14 +2326,27 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-exportable-manifest"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-package-manifest",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-fs"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"dunce",
|
||||
"junction",
|
||||
"miette 7.6.0",
|
||||
"pathdiff",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
@@ -2120,6 +2377,7 @@ name = "pacquet-graph-hasher"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"pacquet-detect-libc",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -2148,6 +2406,7 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"node-semver",
|
||||
"pacquet-crypto-hash",
|
||||
"pacquet-diagnostics",
|
||||
"pacquet-package-manifest",
|
||||
"pipe-trait",
|
||||
@@ -2161,6 +2420,42 @@ dependencies = [
|
||||
"text-block-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-lockfile-preferred-versions"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"node-semver",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-package-manifest",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-lockfile-verification"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"insta",
|
||||
"miette 7.6.0",
|
||||
"pacquet-diagnostics",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-reporter",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde-saphyr",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"ssri",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-micro-benchmark"
|
||||
version = "0.0.0"
|
||||
@@ -2190,6 +2485,7 @@ dependencies = [
|
||||
"httpdate",
|
||||
"indexmap",
|
||||
"pacquet-diagnostics",
|
||||
"pacquet-fs",
|
||||
"pacquet-testing-utils",
|
||||
"pathdiff",
|
||||
"pipe-trait",
|
||||
@@ -2231,6 +2527,7 @@ name = "pacquet-package-manager"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"derive_more",
|
||||
"dunce",
|
||||
@@ -2240,14 +2537,24 @@ dependencies = [
|
||||
"insta",
|
||||
"miette 7.6.0",
|
||||
"node-semver",
|
||||
"pacquet-catalogs-config",
|
||||
"pacquet-catalogs-types",
|
||||
"pacquet-cmd-shim",
|
||||
"pacquet-config",
|
||||
"pacquet-config-parse-overrides",
|
||||
"pacquet-crypto-hash",
|
||||
"pacquet-deps-path",
|
||||
"pacquet-directory-fetcher",
|
||||
"pacquet-engine-runtime-bun-resolver",
|
||||
"pacquet-engine-runtime-deno-resolver",
|
||||
"pacquet-engine-runtime-node-resolver",
|
||||
"pacquet-executor",
|
||||
"pacquet-fs",
|
||||
"pacquet-git-fetcher",
|
||||
"pacquet-graph-hasher",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-lockfile-preferred-versions",
|
||||
"pacquet-lockfile-verification",
|
||||
"pacquet-modules-yaml",
|
||||
"pacquet-network",
|
||||
"pacquet-package-is-installable",
|
||||
@@ -2257,6 +2564,13 @@ dependencies = [
|
||||
"pacquet-registry",
|
||||
"pacquet-registry-mock",
|
||||
"pacquet-reporter",
|
||||
"pacquet-resolving-default-resolver",
|
||||
"pacquet-resolving-deps-resolver",
|
||||
"pacquet-resolving-git-resolver",
|
||||
"pacquet-resolving-local-resolver",
|
||||
"pacquet-resolving-npm-resolver",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pacquet-resolving-tarball-resolver",
|
||||
"pacquet-store-dir",
|
||||
"pacquet-tarball",
|
||||
"pacquet-testing-utils",
|
||||
@@ -2305,6 +2619,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
"text-block-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2344,13 +2659,14 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"clap",
|
||||
"home",
|
||||
"pipe-trait",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
"tokio",
|
||||
"which",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2365,6 +2681,152 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-default-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"ssri",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-deps-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"miette 7.6.0",
|
||||
"node-semver",
|
||||
"pacquet-catalogs-resolver",
|
||||
"pacquet-catalogs-types",
|
||||
"pacquet-deps-path",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-package-manifest",
|
||||
"pacquet-patching",
|
||||
"pacquet-resolving-parse-wanted-dependency",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pipe-trait",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-git-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"node-semver",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-network",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-jsr-specifier-parser"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-local-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"home",
|
||||
"miette 7.6.0",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-package-manifest",
|
||||
"pacquet-resolving-default-resolver",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pacquet-testing-utils",
|
||||
"pathdiff",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"ssri",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-npm-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"indexmap",
|
||||
"miette 7.6.0",
|
||||
"mockito",
|
||||
"node-semver",
|
||||
"pacquet-config",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-network",
|
||||
"pacquet-registry",
|
||||
"pacquet-resolving-default-resolver",
|
||||
"pacquet-resolving-jsr-specifier-parser",
|
||||
"pacquet-resolving-local-resolver",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pacquet-workspace-range-resolver",
|
||||
"pacquet-workspace-spec",
|
||||
"pipe-trait",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"ssri",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-parse-wanted-dependency"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-resolver-base"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_more",
|
||||
"pacquet-config",
|
||||
"pacquet-lockfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ssri",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-resolving-tarball-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"mockito",
|
||||
"pacquet-lockfile",
|
||||
"pacquet-network",
|
||||
"pacquet-resolving-resolver-base",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-store-dir"
|
||||
version = "0.0.1"
|
||||
@@ -2373,6 +2835,7 @@ dependencies = [
|
||||
"derive_more",
|
||||
"dunce",
|
||||
"miette 7.6.0",
|
||||
"pacquet-crypto-hash",
|
||||
"pacquet-fs",
|
||||
"pipe-trait",
|
||||
"pretty_assertions",
|
||||
@@ -2438,7 +2901,9 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"miette 7.6.0",
|
||||
"pacquet-catalogs-types",
|
||||
"pacquet-package-manifest",
|
||||
"pathdiff",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde-saphyr",
|
||||
@@ -2446,6 +2911,17 @@ dependencies = [
|
||||
"wax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-workspace-range-resolver"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"node-semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-workspace-spec"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "pacquet-workspace-state"
|
||||
version = "0.0.1"
|
||||
@@ -2556,6 +3032,38 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pnpm-registry"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
"bcrypt",
|
||||
"clap",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"getrandom 0.3.4",
|
||||
"indexmap",
|
||||
"miette 7.6.0",
|
||||
"mockito",
|
||||
"pacquet-network",
|
||||
"pipe-trait",
|
||||
"reqwest",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde-saphyr",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"ssri",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pori"
|
||||
version = "0.0.0"
|
||||
@@ -3179,6 +3687,17 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@@ -3238,6 +3757,16 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.9"
|
||||
@@ -3597,6 +4126,7 @@ dependencies = [
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -3667,6 +4197,7 @@ dependencies = [
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
115
Cargo.toml
115
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["pacquet/crates/*", "pacquet/tasks/*"]
|
||||
members = ["pacquet/crates/*", "pacquet/tasks/*", "registry/crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Yagiz Nizipli <yagiz@nizipli.com"]
|
||||
@@ -13,39 +13,77 @@ repository = "https://github.com/pnpm/pacquet"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Crates
|
||||
pacquet-cli = { path = "pacquet/crates/cli" }
|
||||
pacquet-cmd-shim = { path = "pacquet/crates/cmd-shim" }
|
||||
pacquet-fs = { path = "pacquet/crates/fs" }
|
||||
pacquet-registry = { path = "pacquet/crates/registry" }
|
||||
pacquet-tarball = { path = "pacquet/crates/tarball" }
|
||||
pacquet-testing-utils = { path = "pacquet/crates/testing-utils" }
|
||||
pacquet-package-manifest = { path = "pacquet/crates/package-manifest" }
|
||||
pacquet-package-manager = { path = "pacquet/crates/package-manager" }
|
||||
pacquet-package-is-installable = { path = "pacquet/crates/package-is-installable" }
|
||||
pacquet-lockfile = { path = "pacquet/crates/lockfile" }
|
||||
pacquet-modules-yaml = { path = "pacquet/crates/modules-yaml" }
|
||||
pacquet-network = { path = "pacquet/crates/network" }
|
||||
pacquet-config = { path = "pacquet/crates/config" }
|
||||
pacquet-executor = { path = "pacquet/crates/executor" }
|
||||
pacquet-directory-fetcher = { path = "pacquet/crates/directory-fetcher" }
|
||||
pacquet-git-fetcher = { path = "pacquet/crates/git-fetcher" }
|
||||
pacquet-diagnostics = { path = "pacquet/crates/diagnostics" }
|
||||
pacquet-graph-hasher = { path = "pacquet/crates/graph-hasher" }
|
||||
pacquet-store-dir = { path = "pacquet/crates/store-dir" }
|
||||
pacquet-reporter = { path = "pacquet/crates/reporter" }
|
||||
pacquet-patching = { path = "pacquet/crates/patching" }
|
||||
pacquet-real-hoist = { path = "pacquet/crates/real-hoist" }
|
||||
pacquet-workspace = { path = "pacquet/crates/workspace" }
|
||||
pacquet-workspace-state = { path = "pacquet/crates/workspace-state" }
|
||||
pacquet-catalogs-config = { path = "pacquet/crates/catalogs-config" }
|
||||
pacquet-catalogs-protocol-parser = { path = "pacquet/crates/catalogs-protocol-parser" }
|
||||
pacquet-catalogs-resolver = { path = "pacquet/crates/catalogs-resolver" }
|
||||
pacquet-catalogs-types = { path = "pacquet/crates/catalogs-types" }
|
||||
pacquet-cli = { path = "pacquet/crates/cli" }
|
||||
pacquet-cmd-shim = { path = "pacquet/crates/cmd-shim" }
|
||||
pacquet-crypto-hash = { path = "pacquet/crates/crypto-hash" }
|
||||
pacquet-crypto-shasums-file = { path = "pacquet/crates/crypto-shasums-file" }
|
||||
pacquet-engine-runtime-bun-resolver = { path = "pacquet/crates/engine-runtime-bun-resolver" }
|
||||
pacquet-engine-runtime-deno-resolver = { path = "pacquet/crates/engine-runtime-deno-resolver" }
|
||||
pacquet-engine-runtime-node-resolver = { path = "pacquet/crates/engine-runtime-node-resolver" }
|
||||
pacquet-fs = { path = "pacquet/crates/fs" }
|
||||
pacquet-registry = { path = "pacquet/crates/registry" }
|
||||
pacquet-tarball = { path = "pacquet/crates/tarball" }
|
||||
pacquet-testing-utils = { path = "pacquet/crates/testing-utils" }
|
||||
pacquet-package-manifest = { path = "pacquet/crates/package-manifest" }
|
||||
pacquet-package-manager = { path = "pacquet/crates/package-manager" }
|
||||
pacquet-package-is-installable = { path = "pacquet/crates/package-is-installable" }
|
||||
pacquet-lockfile = { path = "pacquet/crates/lockfile" }
|
||||
pacquet-lockfile-preferred-versions = { path = "pacquet/crates/lockfile-preferred-versions" }
|
||||
pacquet-lockfile-verification = { path = "pacquet/crates/lockfile-verification" }
|
||||
pacquet-modules-yaml = { path = "pacquet/crates/modules-yaml" }
|
||||
pacquet-network = { path = "pacquet/crates/network" }
|
||||
pacquet-config = { path = "pacquet/crates/config" }
|
||||
pacquet-config-parse-overrides = { path = "pacquet/crates/config-parse-overrides" }
|
||||
pacquet-executor = { path = "pacquet/crates/executor" }
|
||||
pacquet-exportable-manifest = { path = "pacquet/crates/exportable-manifest" }
|
||||
pacquet-directory-fetcher = { path = "pacquet/crates/directory-fetcher" }
|
||||
pacquet-git-fetcher = { path = "pacquet/crates/git-fetcher" }
|
||||
pacquet-deps-path = { path = "pacquet/crates/deps-path" }
|
||||
pacquet-detect-libc = { path = "pacquet/crates/detect-libc" }
|
||||
pacquet-diagnostics = { path = "pacquet/crates/diagnostics" }
|
||||
pacquet-graph-hasher = { path = "pacquet/crates/graph-hasher" }
|
||||
pacquet-store-dir = { path = "pacquet/crates/store-dir" }
|
||||
pacquet-reporter = { path = "pacquet/crates/reporter" }
|
||||
pacquet-patching = { path = "pacquet/crates/patching" }
|
||||
pacquet-real-hoist = { path = "pacquet/crates/real-hoist" }
|
||||
pacquet-resolving-default-resolver = { path = "pacquet/crates/resolving-default-resolver" }
|
||||
pacquet-resolving-deps-resolver = { path = "pacquet/crates/resolving-deps-resolver" }
|
||||
pacquet-resolving-git-resolver = { path = "pacquet/crates/resolving-git-resolver" }
|
||||
pacquet-resolving-jsr-specifier-parser = { path = "pacquet/crates/resolving-jsr-specifier-parser" }
|
||||
pacquet-resolving-local-resolver = { path = "pacquet/crates/resolving-local-resolver" }
|
||||
pacquet-resolving-npm-resolver = { path = "pacquet/crates/resolving-npm-resolver" }
|
||||
pacquet-resolving-parse-wanted-dependency = { path = "pacquet/crates/resolving-parse-wanted-dependency" }
|
||||
pacquet-resolving-resolver-base = { path = "pacquet/crates/resolving-resolver-base" }
|
||||
pacquet-resolving-tarball-resolver = { path = "pacquet/crates/resolving-tarball-resolver" }
|
||||
pacquet-workspace = { path = "pacquet/crates/workspace" }
|
||||
pacquet-workspace-range-resolver = { path = "pacquet/crates/workspace-range-resolver" }
|
||||
pacquet-workspace-spec = { path = "pacquet/crates/workspace-spec" }
|
||||
pacquet-workspace-state = { path = "pacquet/crates/workspace-state" }
|
||||
|
||||
# Tasks
|
||||
pacquet-registry-mock = { path = "pacquet/tasks/registry-mock" }
|
||||
|
||||
# Registry (sibling project — pnpm-compatible registry server)
|
||||
pnpm-registry = { path = "registry/crates/pnpm-registry", version = "0.0.1" }
|
||||
|
||||
# Dependencies
|
||||
async-recursion = { version = "1.1.1" }
|
||||
axum = { version = "0.8.7", default-features = false, features = [
|
||||
"http1",
|
||||
"tokio",
|
||||
"json",
|
||||
"matched-path",
|
||||
"original-uri",
|
||||
] }
|
||||
clap = { version = "4", features = ["derive", "string"] }
|
||||
command-extra = { version = "1.0.0" }
|
||||
base64 = { version = "0.22.1" }
|
||||
bcrypt = { version = "0.17.1" }
|
||||
chrono = { version = "0.4.44", default-features = false, features = ["clock"] }
|
||||
dashmap = { version = "6.1.0" }
|
||||
derive_more = { version = "2.1.1", features = ["full"] }
|
||||
diffy = { version = "0.5.0" }
|
||||
@@ -58,6 +96,7 @@ insta = { version = "1.47.2", features = ["yaml", "glob", "walkdir"] }
|
||||
itertools = { version = "0.14.0" }
|
||||
futures-util = { version = "0.3.32" }
|
||||
gethostname = { version = "1" }
|
||||
getrandom = { version = "0.3.4" }
|
||||
miette = { version = "7.6.0", features = ["fancy"] }
|
||||
num_cpus = { version = "1.17.0" }
|
||||
os_display = { version = "0.1.4" }
|
||||
@@ -89,9 +128,11 @@ strum = { version = "0.28.0", features = ["derive"] }
|
||||
sysinfo = { version = "0.39.1" }
|
||||
tar = { version = "0.4.45" }
|
||||
text-block-macros = { version = "0.2.0" }
|
||||
tower = { version = "0.5.3" }
|
||||
tower-http = { version = "0.6.8", features = ["trace"] }
|
||||
tracing = { version = "0.1.44" }
|
||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "fs", "io-util", "net", "signal", "sync"] }
|
||||
walkdir = { version = "2.5.0" }
|
||||
wax = { version = "0.7.0" }
|
||||
which = { version = "8.0.2" }
|
||||
@@ -99,8 +140,7 @@ zip = { version = "8", default-features = false, features = ["def
|
||||
zune-inflate = { version = "0.2.54" }
|
||||
|
||||
# Dev dependencies
|
||||
assert_cmd = { version = "2.2.1" }
|
||||
chrono = { version = "0.4.44", default-features = false, features = ["clock"] }
|
||||
assert_cmd = { version = "2.2.2" }
|
||||
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||
pretty_assertions = { version = "1.4.1" }
|
||||
project-root = { version = "0.2.2" }
|
||||
@@ -110,6 +150,25 @@ mockito = { version = "1.7.2" }
|
||||
[workspace.metadata.workspaces]
|
||||
allow_branch = "main"
|
||||
|
||||
# Declares the `dylint_lib = "perfectionist"` cfg used by the
|
||||
# `cfg_attr(dylint_lib = "perfectionist", feature(register_tool))` /
|
||||
# `register_tool(perfectionist)` lines at each pacquet crate's root.
|
||||
# Those `cfg_attr`s register the perfectionist tool name under dylint's
|
||||
# nightly toolchain (so `#[expect(perfectionist::lint, reason = "...")]`
|
||||
# at use sites compiles cleanly without a wrapper); this `check-cfg`
|
||||
# entry tells stable `cargo check` that the cfg name is expected even
|
||||
# though it's never set off-dylint.
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(dylint_lib, values("perfectionist"))'] }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
clone_on_ref_ptr = "warn"
|
||||
if_then_some_else_none = "warn"
|
||||
needless_collect = "warn"
|
||||
or_fun_call = "warn"
|
||||
redundant_clone = "warn"
|
||||
unnecessary_lazy_evaluations = "warn"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "fat"
|
||||
|
||||
@@ -19,6 +19,15 @@ packages:
|
||||
- '!workspace-has-shared-npm-shrinkwrap-json'
|
||||
sharedWorkspaceLockfile: false
|
||||
|
||||
# The fixture lockfiles are pinned to packages from the local registry-mock
|
||||
# (e.g. `@pnpm.e2e/*`). The v11 default of `minimumReleaseAge: 1440` would
|
||||
# spin up the lockfile verifier here, which can't reach the mock from this
|
||||
# install context and rejects the entries as un-checkable. Disable the
|
||||
# policy for fixture installs — they're test scaffolding, not a real
|
||||
# project. The actual minimumReleaseAge code paths are covered by the
|
||||
# unit and e2e tests in their own packages.
|
||||
minimumReleaseAge: 0
|
||||
|
||||
catalog:
|
||||
# Used in has-outdated-deps-using-catalog-protocol fixture.
|
||||
is-negative: ^1.0.0
|
||||
|
||||
10
__typings__/local.d.ts
vendored
10
__typings__/local.d.ts
vendored
@@ -101,6 +101,16 @@ declare module '@pnpm/patch-package/dist/applyPatches.js' {
|
||||
export function applyPatch (opts: any): boolean
|
||||
}
|
||||
|
||||
declare module '@pnpm/patch-package/dist/patch/parse.js' {
|
||||
export interface PatchFilePart {
|
||||
type: 'file deletion' | 'file creation' | 'patch' | 'mode change' | 'rename'
|
||||
path?: string
|
||||
fromPath?: string
|
||||
toPath?: string
|
||||
}
|
||||
export function parsePatchFile (file: string): PatchFilePart[]
|
||||
}
|
||||
|
||||
declare module 'ramda/src/map' {
|
||||
function map <K extends string | number | symbol, V, U> (fn: (x: V) => U, obj: Record<K, V>): Record<K, U>
|
||||
export = map
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# @pnpm/assert-project
|
||||
|
||||
## 1100.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/installing.modules-yaml@1100.0.6
|
||||
- @pnpm/lockfile.types@1100.0.8
|
||||
- @pnpm/assert-store@1100.0.11
|
||||
|
||||
## 1100.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/lockfile.types@1100.0.7
|
||||
- @pnpm/installing.modules-yaml@1100.0.5
|
||||
- @pnpm/assert-store@1100.0.10
|
||||
|
||||
## 1100.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/lockfile.types@1100.0.6
|
||||
- @pnpm/assert-store@1100.0.9
|
||||
|
||||
## 1100.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@pnpm/assert-project",
|
||||
"description": "Utils for testing projects that use pnpm",
|
||||
"version": "1100.0.8",
|
||||
"version": "1100.0.11",
|
||||
"author": {
|
||||
"name": "Zoltan Kochan",
|
||||
"email": "z@kochan.io",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @pnpm/assert-store
|
||||
|
||||
## 1100.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/store.cafs@1100.1.7
|
||||
|
||||
## 1100.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/store.cafs@1100.1.6
|
||||
|
||||
## 1100.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/store.cafs@1100.1.5
|
||||
|
||||
## 1100.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@pnpm/assert-store",
|
||||
"description": "Utils for testing pnpm store",
|
||||
"version": "1100.0.8",
|
||||
"version": "1100.0.11",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @pnpm/jest-config
|
||||
|
||||
## 1100.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [aa6149d]
|
||||
- @pnpm/worker@1100.1.8
|
||||
- @pnpm/testing.registry-mock@1100.0.1
|
||||
|
||||
## 1100.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/worker@1100.1.7
|
||||
|
||||
## 1100.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/worker@1100.1.6
|
||||
|
||||
## 1100.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
{
|
||||
"name": "@pnpm/jest-config",
|
||||
"version": "1100.0.8",
|
||||
"version": "1100.0.11",
|
||||
"private": true,
|
||||
"main": "jest-preset.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/core": "catalog:",
|
||||
"@babel/plugin-transform-explicit-resource-management": "catalog:",
|
||||
"@pnpm/pnpr": "catalog:",
|
||||
"@pnpm/registry-mock": "catalog:",
|
||||
"@pnpm/testing.registry-mock": "workspace:*",
|
||||
"@pnpm/worker": "workspace:*",
|
||||
"amaro": "catalog:",
|
||||
"get-port": "catalog:",
|
||||
"read-yaml-file": "catalog:",
|
||||
"tree-kill": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,23 +1,50 @@
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createRequire } from 'node:module'
|
||||
import { scheduler } from 'node:timers/promises'
|
||||
import { promisify } from 'node:util'
|
||||
|
||||
import getPort from 'get-port'
|
||||
import { promisify } from 'util'
|
||||
import { readYamlFileSync } from 'read-yaml-file'
|
||||
import treeKill from 'tree-kill'
|
||||
|
||||
const kill = promisify(treeKill)
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
export default async () => {
|
||||
if (!process.env.PNPM_REGISTRY_MOCK_PORT) {
|
||||
process.env.PNPM_REGISTRY_MOCK_PORT = (await getPort({ from: 7700, to: 7800 })).toString()
|
||||
}
|
||||
const { start, prepare } = await import('@pnpm/registry-mock')
|
||||
|
||||
// We still call `prepare()` from `@pnpm/registry-mock`: it copies
|
||||
// the read-only fixture `storage-cache` into a tempy directory
|
||||
// and writes `registry/runtime-config-${port}.yaml` with the
|
||||
// tempy path under `storage:`. That yaml is what
|
||||
// `locations.storage()` reads when `getIntegrity` (also from
|
||||
// registry-mock) is called from tests. We just don't launch
|
||||
// verdaccio against it — we launch pnpm-registry instead.
|
||||
const { prepare, REGISTRY_MOCK_CREDENTIALS } = await import('@pnpm/registry-mock')
|
||||
const { addUser } = await import('@pnpm/testing.registry-mock')
|
||||
prepare()
|
||||
const server = start({
|
||||
// Verdaccio stopped working properly on Node.js 22.
|
||||
// You can test the issue by running:
|
||||
// pnpm --filter=core run test test/install/auth.ts
|
||||
useNodeVersion: '20.16.0',
|
||||
stdio: 'inherit',
|
||||
listen: process.env.PNPM_REGISTRY_MOCK_PORT,
|
||||
})
|
||||
|
||||
const storage = readStoragePath(process.env.PNPM_REGISTRY_MOCK_PORT)
|
||||
const bin = resolvePnpmRegistryBin()
|
||||
|
||||
const server = spawn(
|
||||
bin,
|
||||
[
|
||||
'--listen', `127.0.0.1:${process.env.PNPM_REGISTRY_MOCK_PORT}`,
|
||||
'--storage', storage,
|
||||
'--upstream', process.env.PNPM_REGISTRY_MOCK_UPLINK ?? 'https://registry.npmjs.org',
|
||||
'--public-url', `http://localhost:${process.env.PNPM_REGISTRY_MOCK_PORT}`,
|
||||
// Match registry-mock's verdaccio config: a one-year TTL so
|
||||
// the fixture packuments (mtime: whenever the npm tarball was
|
||||
// built) never look stale and never trigger a re-fetch to
|
||||
// npmjs.org that would 404.
|
||||
'--packument-ttl-secs', '31536000',
|
||||
],
|
||||
{ stdio: 'inherit' }
|
||||
)
|
||||
|
||||
let killed = false
|
||||
server.on('error', (err) => {
|
||||
console.log(err)
|
||||
@@ -33,14 +60,9 @@ export default async () => {
|
||||
return kill(server.pid)
|
||||
}
|
||||
|
||||
// Verdaccio can take a bit of time to come online on Windows and during its
|
||||
// first startup. Some tests will fail immediately if they begin running
|
||||
// before Verdaccio starts. Wait for Verdaccio to become online before running
|
||||
// any tests.
|
||||
await waitForServerOnline()
|
||||
|
||||
// Register the test user and store the auth token for bearer-based tests
|
||||
const { addUser, REGISTRY_MOCK_CREDENTIALS } = await import('@pnpm/registry-mock')
|
||||
const { token } = await addUser({
|
||||
username: REGISTRY_MOCK_CREDENTIALS.username,
|
||||
password: REGISTRY_MOCK_CREDENTIALS.password,
|
||||
@@ -49,7 +71,53 @@ export default async () => {
|
||||
process.env.REGISTRY_MOCK_TOKEN = token
|
||||
}
|
||||
|
||||
const UNUSUAL_VERDACCIO_STARTUP_THRESHOLD = 15 // seconds
|
||||
/**
|
||||
* Read the `storage:` path that `@pnpm/registry-mock`'s `prepare()`
|
||||
* just wrote into the runtime-config yaml. We can't import
|
||||
* `locations.storage` from the registry-mock package — it isn't
|
||||
* re-exported from its `index.ts` — but the file path is stable.
|
||||
*/
|
||||
function readStoragePath (port) {
|
||||
const configPath = require.resolve(
|
||||
`@pnpm/registry-mock/registry/runtime-config-${port}.yaml`
|
||||
)
|
||||
const { storage } = readYamlFileSync(configPath)
|
||||
return storage
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the `pnpm-registry` binary. Lookup order:
|
||||
*
|
||||
* 1. `PNPM_REGISTRY_BIN` env var override (escape hatch for local
|
||||
* Rust work — point it at `target/release/pnpm-registry` to test
|
||||
* in-progress changes to the registry crate).
|
||||
* 2. The platform binary that `pnpm install` pulled in as an
|
||||
* optionalDependency of `@pnpm/pnpr` — i.e.
|
||||
* `@pnpm/pnpr.<platform>-<arch>/pnpr[.exe]`. The resolution goes
|
||||
* through the `@pnpm/pnpr` wrapper's path because the platform
|
||||
* sub-package lives in the wrapper's own `node_modules`, not
|
||||
* anywhere on the parent chain of this file.
|
||||
*/
|
||||
function resolvePnpmRegistryBin () {
|
||||
if (process.env.PNPM_REGISTRY_BIN) {
|
||||
return process.env.PNPM_REGISTRY_BIN
|
||||
}
|
||||
const ext = process.platform === 'win32' ? '.exe' : ''
|
||||
const platformPkg = `@pnpm/pnpr.${process.platform}-${process.arch}`
|
||||
try {
|
||||
const wrapperRequire = createRequire(require.resolve('@pnpm/pnpr/bin/pnpr'))
|
||||
return wrapperRequire.resolve(`${platformPkg}/pnpr${ext}`)
|
||||
} catch {
|
||||
throw new Error(
|
||||
`pnpm-registry binary not found. The test suite expects ${platformPkg} ` +
|
||||
'to be installed (it ships as an optionalDependency of @pnpm/pnpr — ' +
|
||||
'run `pnpm install` at the repo root to pull it in), or set ' +
|
||||
'PNPM_REGISTRY_BIN to an absolute path to a locally-built binary.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const UNUSUAL_REGISTRY_STARTUP_THRESHOLD = 15 // seconds
|
||||
|
||||
async function waitForServerOnline () {
|
||||
const start = performance.now()
|
||||
@@ -59,17 +127,17 @@ async function waitForServerOnline () {
|
||||
await fetch(`http://localhost:${process.env.PNPM_REGISTRY_MOCK_PORT}`, { method: 'HEAD' })
|
||||
|
||||
const totalWait = (performance.now() - start) / 1000
|
||||
if (totalWait > UNUSUAL_VERDACCIO_STARTUP_THRESHOLD) {
|
||||
console.warn(`Verdaccio required an unusually long amount of time to start: ${totalWait} seconds`)
|
||||
if (totalWait > UNUSUAL_REGISTRY_STARTUP_THRESHOLD) {
|
||||
console.warn(`pnpm-registry required an unusually long amount of time to start: ${totalWait} seconds`)
|
||||
}
|
||||
|
||||
return
|
||||
} catch (err) {
|
||||
// If the Verdaccio process hasn't begun listening yet, attempts to
|
||||
// If pnpm-registry hasn't begun listening yet, attempts to
|
||||
// connect to the unbound port should throw ECONNREFUSED. If a different
|
||||
// error is observed, throw an error.
|
||||
if (err?.cause?.code !== 'ECONNREFUSED') {
|
||||
throw new Error('Failed to bring Verdaccio online:', { cause: err })
|
||||
throw new Error('Failed to bring pnpm-registry online:', { cause: err })
|
||||
}
|
||||
|
||||
await scheduler.wait(delay)
|
||||
@@ -77,7 +145,7 @@ async function waitForServerOnline () {
|
||||
}
|
||||
|
||||
const totalWait = (performance.now() - start) / 1000
|
||||
throw new Error(`Verdaccio did not come online after waiting ${totalWait} seconds`)
|
||||
throw new Error(`pnpm-registry did not come online after waiting ${totalWait} seconds`)
|
||||
}
|
||||
|
||||
function *exponentialBackoff (attempts = 15, base = 1.5, initialWait = 100) {
|
||||
@@ -85,3 +153,4 @@ function *exponentialBackoff (attempts = 15, base = 1.5, initialWait = 100) {
|
||||
yield initialWait * Math.pow(base, i)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @pnpm/prepare
|
||||
|
||||
## 1100.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/assert-project@1100.0.11
|
||||
|
||||
## 1100.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/assert-project@1100.0.10
|
||||
|
||||
## 1100.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/assert-project@1100.0.9
|
||||
|
||||
## 1100.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/prepare",
|
||||
"version": "1100.0.8",
|
||||
"version": "1100.0.11",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# @pnpm/scripts
|
||||
|
||||
## 1100.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e8b3ae1]
|
||||
- @pnpm/workspace.projects-reader@1101.0.8
|
||||
- @pnpm/workspace.workspace-manifest-reader@1100.0.5
|
||||
|
||||
## 1100.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/workspace.projects-reader@1101.0.7
|
||||
|
||||
## 1100.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/workspace.projects-reader@1101.0.6
|
||||
- @pnpm/workspace.workspace-manifest-reader@1100.0.4
|
||||
|
||||
## 1100.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/workspace.projects-reader@1101.0.5
|
||||
|
||||
## 1100.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/scripts",
|
||||
"version": "1100.0.7",
|
||||
"version": "1100.0.11",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @pnpm/agent.client
|
||||
|
||||
## 1.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [aa6149d]
|
||||
- @pnpm/worker@1100.1.8
|
||||
- @pnpm/lockfile.types@1100.0.8
|
||||
- @pnpm/store.cafs@1100.1.7
|
||||
|
||||
## 1.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/lockfile.types@1100.0.7
|
||||
- @pnpm/store.cafs@1100.1.6
|
||||
- @pnpm/worker@1100.1.7
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/lockfile.types@1100.0.6
|
||||
- @pnpm/store.cafs@1100.1.5
|
||||
- @pnpm/worker@1100.1.6
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/agent.client",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.8",
|
||||
"description": "Client for pnpm agent server — sends store state, receives resolved lockfile and missing files",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,70 @@
|
||||
# pnpm-agent
|
||||
|
||||
## 0.0.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [aa6149d]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/installing.deps-installer@1101.5.0
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/installing.client@1100.2.3
|
||||
- @pnpm/lockfile.fs@1100.1.2
|
||||
- @pnpm/lockfile.types@1100.0.8
|
||||
- @pnpm/store.cafs@1100.1.7
|
||||
- @pnpm/store.controller@1101.0.9
|
||||
|
||||
## 0.0.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/installing.deps-installer@1101.4.0
|
||||
- @pnpm/installing.client@1100.2.2
|
||||
- @pnpm/store.controller@1101.0.8
|
||||
|
||||
## 0.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/installing.client@1100.2.1
|
||||
- @pnpm/store.controller@1101.0.8
|
||||
- @pnpm/installing.deps-installer@1101.3.1
|
||||
|
||||
## 0.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9cb48bb]
|
||||
- Updated dependencies [1627943]
|
||||
- Updated dependencies [b206a15]
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/lockfile.fs@1100.1.1
|
||||
- @pnpm/installing.client@1100.2.0
|
||||
- @pnpm/installing.deps-installer@1101.3.0
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/store.controller@1101.0.8
|
||||
- @pnpm/lockfile.types@1100.0.7
|
||||
- @pnpm/store.cafs@1100.1.6
|
||||
|
||||
## 0.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4195766]
|
||||
- Updated dependencies [31538bf]
|
||||
- Updated dependencies [6e93f35]
|
||||
- Updated dependencies [3ddde2b]
|
||||
- Updated dependencies [4a79336]
|
||||
- Updated dependencies [2a9bd89]
|
||||
- Updated dependencies [31538bf]
|
||||
- @pnpm/installing.client@1100.1.0
|
||||
- @pnpm/installing.deps-installer@1101.2.0
|
||||
- @pnpm/lockfile.fs@1100.1.0
|
||||
- @pnpm/lockfile.types@1100.0.6
|
||||
- @pnpm/store.controller@1101.0.7
|
||||
- @pnpm/store.cafs@1100.1.5
|
||||
|
||||
## 0.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pnpm-agent",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.19",
|
||||
"description": "pnpm agent server for server-side resolution and store-aware downloads",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,54 @@
|
||||
# @pnpm/auth.commands
|
||||
|
||||
## 1100.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ae21758: Refactor the dist-tag-add and login (classic adduser) handlers to delegate their PUTs to a new shared package `@pnpm/registry-access.client`. Downstream tests in this monorepo now use these helpers (via `@pnpm/testing.registry-mock`) instead of `addDistTag` / `addUser` from `@pnpm/registry-mock`, which relied on the unmaintained `anonymous-npm-registry-client`.
|
||||
- Updated dependencies [a23956e]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/config.reader@1101.4.1
|
||||
- @pnpm/cli.utils@1101.0.8
|
||||
- @pnpm/network.fetch@1100.0.7
|
||||
- @pnpm/registry-access.client@1100.0.1
|
||||
|
||||
## 1100.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3b62f9d]
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/config.reader@1101.4.0
|
||||
- @pnpm/cli.utils@1101.0.7
|
||||
|
||||
## 1100.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 56f3851: Implement the documented `pnpm login --scope <scope>` flag. The scope is normalized (a leading `@` is added if missing; blank values are ignored) and an `@<scope>:registry=<registry>` mapping is written to the pnpm auth file alongside the auth token. Subsequent installs of `@<scope>/*` packages then route to the chosen registry. Previously `pnpm login --scope foo` errored with `Unknown option: 'scope'` despite the flag being listed in the online documentation [#11716](https://github.com/pnpm/pnpm/issues/11716).
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3687b0e]
|
||||
- Updated dependencies [ced20cb]
|
||||
- Updated dependencies [d1b340f]
|
||||
- @pnpm/config.reader@1101.3.3
|
||||
- @pnpm/cli.utils@1101.0.6
|
||||
- @pnpm/network.fetch@1100.0.6
|
||||
|
||||
## 1100.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [020ac45]
|
||||
- Updated dependencies [d3f8408]
|
||||
- Updated dependencies [a62f959]
|
||||
- Updated dependencies [ba2c884]
|
||||
- Updated dependencies [8df408c]
|
||||
- @pnpm/config.reader@1101.3.2
|
||||
- @pnpm/network.fetch@1100.0.5
|
||||
- @pnpm/cli.utils@1101.0.5
|
||||
|
||||
## 1100.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/auth.commands",
|
||||
"version": "1100.0.13",
|
||||
"version": "1100.1.2",
|
||||
"description": "Commands for authentication with npm registries",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
@@ -37,6 +37,7 @@
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/network.fetch": "workspace:*",
|
||||
"@pnpm/network.web-auth": "workspace:*",
|
||||
"@pnpm/registry-access.client": "workspace:*",
|
||||
"enquirer": "catalog:",
|
||||
"normalize-registry-url": "catalog:",
|
||||
"read-ini-file": "catalog:",
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type WebAuthFetchOptions,
|
||||
withOtpHandling,
|
||||
} from '@pnpm/network.web-auth'
|
||||
import { addUser, AddUserHttpError, AddUserNoTokenError } from '@pnpm/registry-access.client'
|
||||
import enquirer from 'enquirer'
|
||||
import normalizeRegistryUrl from 'normalize-registry-url'
|
||||
import { readIniFile } from 'read-ini-file'
|
||||
@@ -24,7 +25,10 @@ import { writeIniFile } from 'write-ini-file'
|
||||
import { getRegistryConfigKey, safeReadIniFile } from './shared.js'
|
||||
|
||||
export function rcOptionsTypes (): Record<string, unknown> {
|
||||
return { registry: allTypes.registry }
|
||||
return {
|
||||
registry: allTypes.registry,
|
||||
scope: allTypes.scope,
|
||||
}
|
||||
}
|
||||
|
||||
export function cliOptionsTypes (): Record<string, unknown> {
|
||||
@@ -46,11 +50,15 @@ export function help (): string {
|
||||
description: 'The registry to log in to',
|
||||
name: '--registry <url>',
|
||||
},
|
||||
{
|
||||
description: 'Associate an operation with a scope for a scoped registry. The scope-to-registry mapping is recorded so future installs in the same scope use the chosen registry.',
|
||||
name: '--scope <scope>',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
url: docsUrl('login'),
|
||||
usages: ['pnpm login [--registry <url>]'],
|
||||
usages: ['pnpm login [--registry <url>] [--scope <scope>]'],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,6 +73,7 @@ export type LoginCommandOptions = Pick<Config,
|
||||
| 'authConfig'
|
||||
> & {
|
||||
registry?: string
|
||||
scope?: string
|
||||
}
|
||||
|
||||
export async function handler (
|
||||
@@ -101,19 +110,7 @@ export interface LoginFetchResponseHeaders {
|
||||
|
||||
export interface LoginFetchOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT'
|
||||
headers?: {
|
||||
accept: 'application/json'
|
||||
'content-type': 'application/json'
|
||||
|
||||
// Q: Why does pnpm send this header unconditionally?
|
||||
// A: This header doesn't say "I prefer web-based authentication";
|
||||
// it only says "I am capable of web-based authentication".
|
||||
// The npm CLI does the same:
|
||||
// <https://github.com/npm/npm-registry-fetch/blob/844230f/lib/index.js#L196-L198>
|
||||
'npm-auth-type': 'web'
|
||||
|
||||
'npm-otp'?: string
|
||||
}
|
||||
headers?: Record<string, string>
|
||||
body?: string
|
||||
retry?: {
|
||||
factor?: number
|
||||
@@ -202,11 +199,28 @@ export async function login ({ context = DEFAULT_CONTEXT, opts }: LoginParams):
|
||||
const settings = await safeReadIniFile(readIniFile, configPath) as Record<string, unknown>
|
||||
const registryConfigKey = getRegistryConfigKey(registry)
|
||||
settings[`${registryConfigKey}:_authToken`] = token
|
||||
// Persist the scope → registry mapping next to the auth token so subsequent
|
||||
// installs for `@scope/*` packages route to this registry. `auth.ini` is
|
||||
// already an allowed source of `@scope:registry=` (see config/reader).
|
||||
const scopeKey = normalizeScope(opts.scope)
|
||||
if (scopeKey != null) {
|
||||
settings[`${scopeKey}:registry`] = registry
|
||||
}
|
||||
await writeIniFile(configPath, settings)
|
||||
|
||||
return `Logged in on ${registry}`
|
||||
}
|
||||
|
||||
// `--scope foo` and `--scope @foo` should both produce `@foo`. Empty / blank
|
||||
// values are treated as unset so accidental whitespace doesn't write a broken
|
||||
// `@:registry=` entry.
|
||||
function normalizeScope (scope: string | undefined): string | undefined {
|
||||
if (scope == null) return undefined
|
||||
const trimmed = scope.trim()
|
||||
if (trimmed === '' || trimmed === '@') return undefined
|
||||
return trimmed.startsWith('@') ? trimmed : `@${trimmed}`
|
||||
}
|
||||
|
||||
interface WebLoginParams {
|
||||
context: Pick<LoginContext, 'Date' | 'setTimeout' | 'createReadlineInterface' | 'fetch' | 'globalInfo' | 'globalWarn' | 'process'>
|
||||
fetchOptions: WebAuthFetchOptions
|
||||
@@ -291,45 +305,32 @@ async function classicLogin ({
|
||||
throw new LoginMissingCredentialsError()
|
||||
}
|
||||
|
||||
const loginUrl = new URL(`-/user/org.couchdb.user:${encodeURIComponent(username)}`, registry).href
|
||||
|
||||
const token = await withOtpHandling({
|
||||
context,
|
||||
fetchOptions,
|
||||
operation: async (otp?: string) => {
|
||||
const response = await fetch(loginUrl, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
accept: 'application/json',
|
||||
'npm-auth-type': 'web',
|
||||
// Conditionally include npm-otp: some HTTP implementations coerce
|
||||
// `undefined` to the string "undefined", which would send a bad header
|
||||
// on the initial attempt (before OTP is known).
|
||||
...(otp != null ? { 'npm-otp': otp } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
_id: `org.couchdb.user:${username}`,
|
||||
name: username,
|
||||
try {
|
||||
const result = await addUser({
|
||||
username,
|
||||
password,
|
||||
email,
|
||||
type: 'user',
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
await throwIfOtpRequired(globalWarn, response)
|
||||
const text = await response.text()
|
||||
throw new ClassicLoginError(response.status, text)
|
||||
otp,
|
||||
registryUrl: registry,
|
||||
fetch,
|
||||
})
|
||||
return result.token
|
||||
} catch (err) {
|
||||
if (err instanceof AddUserHttpError) {
|
||||
if (err.status === 401 && err.responseHeaders.get('www-authenticate')?.includes('otp')) {
|
||||
throw SyntheticOtpError.fromUnknownBody(globalWarn, err.responseJson)
|
||||
}
|
||||
throw new ClassicLoginError(err.status, err.responseText)
|
||||
}
|
||||
if (err instanceof AddUserNoTokenError) {
|
||||
throw new LoginNoTokenError()
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
const body = await response.json() as { token?: string }
|
||||
|
||||
if (!body.token) {
|
||||
throw new LoginNoTokenError()
|
||||
}
|
||||
|
||||
return body.token
|
||||
},
|
||||
})
|
||||
|
||||
@@ -338,25 +339,6 @@ async function classicLogin ({
|
||||
return token
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects a non-ok HTTP response for OTP requirements and throws an EOTP
|
||||
* error when detected. This mirrors the behaviour of npm-registry-fetch,
|
||||
* which checks the `www-authenticate` header for one-time password indicators.
|
||||
*/
|
||||
async function throwIfOtpRequired (globalWarn: LoginContext['globalWarn'], response: LoginFetchResponse): Promise<void> {
|
||||
if (response.status !== 401) return
|
||||
|
||||
const wwwAuth = response.headers.get('www-authenticate')
|
||||
if (!wwwAuth?.includes('otp')) return
|
||||
|
||||
let body: unknown
|
||||
try {
|
||||
body = await response.json()
|
||||
} catch {}
|
||||
|
||||
throw SyntheticOtpError.fromUnknownBody(globalWarn, body)
|
||||
}
|
||||
|
||||
class LoginNonInteractiveError extends PnpmError {
|
||||
constructor () {
|
||||
super('LOGIN_NON_INTERACTIVE', 'The login command requires an interactive terminal')
|
||||
|
||||
@@ -141,6 +141,103 @@ describe('login', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should persist a scope→registry mapping when --scope is provided', async () => {
|
||||
let savedSettings: Record<string, unknown> = {}
|
||||
const context = createMockContext({
|
||||
globalInfo: jest.fn(),
|
||||
readIniFile: async () => ({}),
|
||||
writeIniFile: async (_configPath, settings) => {
|
||||
savedSettings = settings
|
||||
},
|
||||
fetch: async url => {
|
||||
if (url === 'https://my-org.example/-/v1/login') {
|
||||
return createMockResponse({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: {
|
||||
loginUrl: 'https://my-org.example/auth/login',
|
||||
doneUrl: 'https://my-org.example/auth/done',
|
||||
},
|
||||
})
|
||||
}
|
||||
if (url === 'https://my-org.example/auth/done') {
|
||||
return createMockResponse({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: { token: 'scoped-token' },
|
||||
})
|
||||
}
|
||||
throw new Error(`Unexpected call to fetch: ${url}`)
|
||||
},
|
||||
})
|
||||
// `--scope my-org` (no `@`) should be normalized to `@my-org` when written.
|
||||
const opts = { configDir: '/mock/config', dir: '/mock', authConfig: {}, registry: 'https://my-org.example', scope: 'my-org' }
|
||||
const result = await login({ context, opts })
|
||||
expect(result).toBe('Logged in on https://my-org.example/')
|
||||
expect(savedSettings).toMatchObject({
|
||||
'//my-org.example/:_authToken': 'scoped-token',
|
||||
'@my-org:registry': 'https://my-org.example/',
|
||||
})
|
||||
})
|
||||
|
||||
it('should accept --scope with a leading @ and not double-prefix', async () => {
|
||||
let savedSettings: Record<string, unknown> = {}
|
||||
const context = createMockContext({
|
||||
globalInfo: jest.fn(),
|
||||
readIniFile: async () => ({}),
|
||||
writeIniFile: async (_configPath, settings) => {
|
||||
savedSettings = settings
|
||||
},
|
||||
fetch: async url => {
|
||||
if (url === 'https://my-org.example/-/v1/login') {
|
||||
return createMockResponse({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: {
|
||||
loginUrl: 'https://my-org.example/auth/login',
|
||||
doneUrl: 'https://my-org.example/auth/done',
|
||||
},
|
||||
})
|
||||
}
|
||||
return createMockResponse({ ok: true, status: 200, json: { token: 'tok' } })
|
||||
},
|
||||
})
|
||||
const opts = { configDir: '/mock/config', dir: '/mock', authConfig: {}, registry: 'https://my-org.example', scope: '@my-org' }
|
||||
await login({ context, opts })
|
||||
expect(savedSettings['@my-org:registry']).toBe('https://my-org.example/')
|
||||
expect(savedSettings['@@my-org:registry']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should not write a scope mapping when --scope is omitted', async () => {
|
||||
let savedSettings: Record<string, unknown> = {}
|
||||
const context = createMockContext({
|
||||
globalInfo: jest.fn(),
|
||||
readIniFile: async () => ({}),
|
||||
writeIniFile: async (_configPath, settings) => {
|
||||
savedSettings = settings
|
||||
},
|
||||
fetch: async url => {
|
||||
if (url === 'https://example.com/-/v1/login') {
|
||||
return createMockResponse({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: {
|
||||
loginUrl: 'https://example.com/auth/login',
|
||||
doneUrl: 'https://example.com/auth/done',
|
||||
},
|
||||
})
|
||||
}
|
||||
return createMockResponse({ ok: true, status: 200, json: { token: 'tok' } })
|
||||
},
|
||||
})
|
||||
const opts = { configDir: '/mock/config', dir: '/mock', authConfig: {}, registry: 'https://example.com' }
|
||||
await login({ context, opts })
|
||||
// No `@…:registry` key should be added when scope isn't passed.
|
||||
for (const key of Object.keys(savedSettings)) {
|
||||
expect(key.startsWith('@')).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('should fall back to classic login when web login returns 404', async () => {
|
||||
const fetchedUrls: string[] = []
|
||||
const globalInfo = jest.fn()
|
||||
@@ -266,10 +363,10 @@ describe('login', () => {
|
||||
return createMockResponse({
|
||||
ok: false,
|
||||
status: 401,
|
||||
json: {
|
||||
text: JSON.stringify({
|
||||
authUrl: 'https://example.org/auth/web',
|
||||
doneUrl: 'https://example.org/auth/web/done',
|
||||
},
|
||||
}),
|
||||
headers: { get: (name: string) => name === 'www-authenticate' ? 'OTP otp' : null },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../network/web-auth"
|
||||
},
|
||||
{
|
||||
"path": "../../registry-access/client"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,52 +1,77 @@
|
||||
# pnpm Benchmarks
|
||||
|
||||
Compares `pnpm install` performance between the current branch and `main`.
|
||||
Compares `pnpm install` performance between the current branch (`HEAD`) and
|
||||
`main`, across the six scenarios listed below.
|
||||
|
||||
This wrapper builds both pnpm revisions and runs hyperfine through the
|
||||
shared Rust orchestrator at
|
||||
[`pacquet/tasks/integrated-benchmark`](../pacquet/tasks/integrated-benchmark/),
|
||||
so scenario / fixture / workspace / install-script / report generation
|
||||
stay consistent with the pacquet benchmark.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [hyperfine](https://github.com/sharkdp/hyperfine) — install via `brew install hyperfine`
|
||||
- The current branch must be compiled (`pnpm run compile`)
|
||||
- If providing a pre-existing main checkout path, it must also be compiled
|
||||
- `cargo` (install Rust via [rustup](https://rustup.rs) if you don't have it).
|
||||
- `hyperfine`, `pnpm`, `node`, `git` on `$PATH`.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
pnpm run compile
|
||||
./benchmarks/bench.sh
|
||||
```
|
||||
|
||||
If a git worktree with `main` already exists, the script finds and uses it automatically. Otherwise it creates one at `../.pnpm-bench-main` (a sibling of the repo), installs dependencies, and compiles.
|
||||
The script:
|
||||
|
||||
You can also point to a specific checkout of main:
|
||||
|
||||
```sh
|
||||
./benchmarks/bench.sh /path/to/main
|
||||
```
|
||||
1. Builds the `integrated-benchmark` binary in release mode.
|
||||
2. Clones the current repo into the temp work-env once per revision
|
||||
(`HEAD` and `main`) and runs `pnpm install && pnpm run compile-only`
|
||||
in each to produce `pnpm/dist/pnpm.mjs`. `compile-only` skips the
|
||||
`update-manifests` pass that the root `compile` script does — it
|
||||
would rewrite tracked files and trigger a second install per
|
||||
revision, neither of which the bench needs.
|
||||
3. Runs hyperfine on each scenario with `--registry=npm` (hits
|
||||
`registry.npmjs.org` directly, no proxy — same as before).
|
||||
4. Writes a per-scenario `BENCHMARK_REPORT.md` / `.json` and a
|
||||
consolidated `results.md` into the temp work-env. The path is printed
|
||||
at the end of the run.
|
||||
5. Emits `bencher-results.json` — a hyperfine-shaped file with one
|
||||
result per scenario (the `@HEAD` revision only, `command` renamed to
|
||||
the scenario name) that the `Benchmarks` GitHub Actions workflow
|
||||
uploads to [Bencher](https://bencher.dev) for continuous tracking.
|
||||
|
||||
## Scenarios
|
||||
|
||||
| # | Name | Lockfile | Store + Cache | Description |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Headless | ✔ frozen | warm | Repeat install with warm store |
|
||||
| 2 | Re-resolution | ✔ + add dep | warm | Add a new dependency to an existing lockfile |
|
||||
| 3 | Full resolution | ✗ | warm | Resolve everything from scratch with warm store and cache |
|
||||
| 4 | Headless cold | ✔ frozen | cold | Typical CI install — fetch all packages with lockfile |
|
||||
| 5 | Cold install | ✗ | cold | True cold start — nothing cached |
|
||||
Slugs follow `<linker>.<action>.<cache state>.<store state>` so the
|
||||
leading segment groups runs by linker mode. Today there are two
|
||||
groups (`isolated-linker.*` and `gvs-linker.*`); future scenarios
|
||||
will add `hoisted-linker.*` and `pnp-linker.*`.
|
||||
|
||||
All scenarios use `--ignore-scripts` and isolated store/cache directories per variant.
|
||||
Every current scenario starts with `node_modules` wiped — "fresh"
|
||||
names that target state; future variants that begin with a populated
|
||||
`node_modules` will use a different action prefix.
|
||||
|
||||
## Output
|
||||
| # | Slug | Lockfile | Cache | Store | Description |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `isolated-linker.fresh-restore.hot-cache.hot-store` | ✔ frozen | hot | hot | Restore from lockfile with both directories hot (repeat-headless shape) |
|
||||
| 2 | `isolated-linker.fresh-add-dep.hot-cache.hot-store` | ✔ + add dep | hot | hot | `pnpm add <dep>` against an existing lockfile |
|
||||
| 3 | `isolated-linker.fresh-install.hot-cache.hot-store` | ✗ | hot | hot | Resolve from scratch with both directories hot |
|
||||
| 4 | `isolated-linker.fresh-restore.cold-cache.cold-store` | ✔ frozen | cold | cold | Restore from lockfile with cold disks (typical CI shape) |
|
||||
| 5 | `isolated-linker.fresh-install.cold-cache.cold-store` | ✗ | cold | cold | True cold start — no lockfile, nothing cached |
|
||||
| 6 | `gvs-linker.fresh-restore.hot-cache.hot-store` | ✔ frozen | hot | hot + GVS | Frozen-lockfile restore with `enableGlobalVirtualStore: true`, pre-warmed GVS |
|
||||
|
||||
Results are printed to the terminal and saved as:
|
||||
All scenarios use `--ignore-scripts` and isolated store/cache directories per revision.
|
||||
|
||||
- `results.md` — consolidated markdown table
|
||||
- `<scenario>-main.json` / `<scenario>-branch.json` — raw hyperfine data
|
||||
## Fixture
|
||||
|
||||
All files are written to a temp directory printed at the end of the run.
|
||||
The fixture lives at [`fixture/`](./fixture/) — a synthetic
|
||||
`package.json` with ~80 typical front-end dependencies, plus a committed
|
||||
`pnpm-lock.yaml` (generated once with `pnpm install --lockfile-only`).
|
||||
The lockfile is checked in so every CI run starts from the same
|
||||
resolution graph regardless of registry drift.
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit the variables at the top of `bench.sh`:
|
||||
Environment variables read by `bench.sh`:
|
||||
|
||||
- `WARMUP` — number of warmup runs before timing (default: 1)
|
||||
- `RUNS` — number of timed runs per benchmark (default: 10)
|
||||
|
||||
@@ -1,290 +1,153 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Benchmark script for pnpm install performance.
|
||||
# Compares the current (active) branch against a baseline checkout of main.
|
||||
# Thin wrapper around `pacquet/tasks/integrated-benchmark`. Builds two
|
||||
# pnpm revisions (the current branch and `main`) and runs hyperfine for
|
||||
# each of the six scenarios that used to live in this script.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - hyperfine (https://github.com/sharkdp/hyperfine)
|
||||
# - The current branch must be compiled (pnpm run compile)
|
||||
# Scenarios, registry choice, and runner behaviour are preserved exactly
|
||||
# as before; the orchestration logic is shared with the pacquet bench.
|
||||
#
|
||||
# Usage:
|
||||
# ./benchmarks/bench.sh [path-to-main-checkout]
|
||||
# Prerequisites: cargo, hyperfine, pnpm, node, git.
|
||||
#
|
||||
# If no path is given, a git worktree for main is created automatically,
|
||||
# dependencies are installed, and pnpm is compiled in it.
|
||||
# Env vars: WARMUP (default 1), RUNS (default 10).
|
||||
#
|
||||
# Examples:
|
||||
# pnpm run compile
|
||||
# ./benchmarks/bench.sh
|
||||
# ./benchmarks/bench.sh /Volumes/src/pnpm/pnpm/main
|
||||
# Usage: ./benchmarks/bench.sh
|
||||
|
||||
BRANCH_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
if [ -n "${1:-}" ]; then
|
||||
MAIN_DIR="$1"
|
||||
else
|
||||
# Look for an existing worktree that has main checked out
|
||||
EXISTING=$(git -C "$BRANCH_DIR" worktree list --porcelain \
|
||||
| awk '/^worktree /{wt=$2} /^branch refs\/heads\/main$/{print wt}')
|
||||
|
||||
if [ -n "$EXISTING" ]; then
|
||||
MAIN_DIR="$EXISTING"
|
||||
echo "── Using existing main worktree at $MAIN_DIR ──"
|
||||
else
|
||||
MAIN_DIR="$BRANCH_DIR/../.pnpm-bench-main"
|
||||
echo "── Creating main worktree at $MAIN_DIR ──"
|
||||
git -C "$BRANCH_DIR" worktree add "$MAIN_DIR" main
|
||||
fi
|
||||
|
||||
cd "$MAIN_DIR"
|
||||
echo "Installing dependencies..."
|
||||
pnpm install
|
||||
echo "Compiling..."
|
||||
pnpm run compile
|
||||
echo ""
|
||||
cd "$BRANCH_DIR"
|
||||
fi
|
||||
|
||||
BENCH_DIR="$(mktemp -d "${TMPDIR:-/tmp}/pnpm-bench.XXXXXX")"
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
FIXTURE_DIR="$REPO_ROOT/benchmarks/fixture"
|
||||
WARMUP="${WARMUP:-1}"
|
||||
RUNS="${RUNS:-10}"
|
||||
BENCH_DIR="$(mktemp -d "${TMPDIR:-/tmp}/pnpm-bench.XXXXXX")"
|
||||
|
||||
# ── Per-variant configuration ─────────────────────────────────────────────
|
||||
|
||||
resolve_pnpm_bin() {
|
||||
local dir="$1"
|
||||
if [ -f "$dir/pnpm/dist/pnpm.mjs" ]; then
|
||||
echo "$dir/pnpm/dist/pnpm.mjs"
|
||||
else
|
||||
echo "$dir/pnpm/dist/pnpm.cjs"
|
||||
for tool in cargo hyperfine pnpm node git; do
|
||||
if ! command -v "$tool" >/dev/null; then
|
||||
echo "error: $tool not on PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
done
|
||||
|
||||
VARIANTS=("main" "branch")
|
||||
VARIANT_DIRS=("$MAIN_DIR" "$BRANCH_DIR")
|
||||
VARIANT_BINS=("$(resolve_pnpm_bin "$MAIN_DIR")" "$(resolve_pnpm_bin "$BRANCH_DIR")")
|
||||
VARIANT_PROJECTS=("$BENCH_DIR/project-main" "$BENCH_DIR/project-branch")
|
||||
VARIANT_STORES=("$BENCH_DIR/store-main" "$BENCH_DIR/store-branch")
|
||||
VARIANT_CACHES=("$BENCH_DIR/cache-main" "$BENCH_DIR/cache-branch")
|
||||
echo "── Building integrated-benchmark ──"
|
||||
cargo build --release --bin=integrated-benchmark --manifest-path "$REPO_ROOT/Cargo.toml"
|
||||
BIN="$REPO_ROOT/target/release/integrated-benchmark"
|
||||
|
||||
# ── Validation ──────────────────────────────────────────────────────────────
|
||||
|
||||
if ! command -v hyperfine &>/dev/null; then
|
||||
echo "error: hyperfine is required. Install via: brew install hyperfine" >&2
|
||||
exit 1
|
||||
# Ensure `pnpm@main` resolves locally. `actions/checkout` only creates a
|
||||
# local ref for the branch it checked out; on a workflow_dispatch run from
|
||||
# a non-main branch (or after the optional PR-head checkout in
|
||||
# `benchmark.yml`) there's no `refs/heads/main` for `git rev-parse` to
|
||||
# hit. Skip the fetch entirely when the local ref already exists, and
|
||||
# let the fetch surface its real error if it fails.
|
||||
if ! git -C "$REPO_ROOT" rev-parse --verify --quiet refs/heads/main >/dev/null; then
|
||||
echo "── Fetching main into local ref ──"
|
||||
git -C "$REPO_ROOT" fetch --no-tags origin main:main
|
||||
fi
|
||||
|
||||
for bin in "${VARIANT_BINS[@]}"; do
|
||||
if [ ! -f "$bin" ]; then
|
||||
echo "error: compiled pnpm not found at $bin" >&2
|
||||
echo "Run 'pnpm run compile' in both repos first." >&2
|
||||
exit 1
|
||||
# Scenario list: `slug:Display label`. The slug matches the
|
||||
# orchestrator's `--scenario` value (the clap-derived kebab-case name
|
||||
# from `BenchmarkScenario`). All six start with `node_modules` wiped
|
||||
# — "Fresh" names that target state. "Isolated linker" names the
|
||||
# `nodeLinker` mode; alternatives (`hoisted`, `pnp`) and populated-
|
||||
# node_modules counterparts are reserved for future scenarios.
|
||||
SCENARIOS=(
|
||||
"isolated-linker.fresh-restore.hot-cache.hot-store:Isolated linker: fresh restore, hot cache + hot store"
|
||||
"isolated-linker.fresh-add-dep.hot-cache.hot-store:Isolated linker: fresh add new dep, hot cache + hot store"
|
||||
"isolated-linker.fresh-install.hot-cache.hot-store:Isolated linker: fresh install, hot cache + hot store"
|
||||
"isolated-linker.fresh-restore.cold-cache.cold-store:Isolated linker: fresh restore, cold cache + cold store"
|
||||
"isolated-linker.fresh-install.cold-cache.cold-store:Isolated linker: fresh install, cold cache + cold store"
|
||||
"gvs-linker.fresh-restore.hot-cache.hot-store:GVS linker: fresh restore, hot cache + hot store"
|
||||
)
|
||||
|
||||
# Pre-build both revisions once. Subsequent scenario invocations still
|
||||
# re-enter the orchestrator's build step (sync_bench_repo + pnpm install
|
||||
# + pnpm run compile), but `pnpm install` is a no-op on the populated
|
||||
# node_modules and `tsgo --build` is incremental. `pnpm run bundle`
|
||||
# (which produces pnpm/dist/pnpm.mjs) does run each time and is not
|
||||
# incremental — accepted overhead in exchange for keeping the build
|
||||
# path in one consistent place across pacquet and pnpm benches.
|
||||
echo "── Pre-building pnpm revisions ──"
|
||||
"$BIN" \
|
||||
--pnpm-repository "$REPO_ROOT" \
|
||||
--work-env "$BENCH_DIR/work-env" \
|
||||
--build-only \
|
||||
pnpm@HEAD pnpm@main
|
||||
|
||||
# Pull mean ± stddev for each variant out of a hyperfine JSON into one
|
||||
# table cell. Falls back to "n/a" if jq isn't on PATH, the file is
|
||||
# missing, or the target isn't present in the JSON.
|
||||
read_cell() {
|
||||
local target=$1
|
||||
local json=$2
|
||||
if ! command -v jq >/dev/null; then
|
||||
echo "n/a"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
for i in "${!VARIANTS[@]}"; do
|
||||
# Run --version from BENCH_DIR to avoid pnpm's automatic version switching
|
||||
# based on a packageManager field in the current directory.
|
||||
echo "${VARIANTS[$i]}: $(cd "$BENCH_DIR" && node "${VARIANT_BINS[$i]}" --version) (${VARIANT_DIRS[$i]})"
|
||||
done
|
||||
echo "workdir: $BENCH_DIR"
|
||||
echo ""
|
||||
|
||||
# ── Project setup ───────────────────────────────────────────────────────────
|
||||
# Each variant gets its own project directory with isolated store and cache
|
||||
# so there is no shared state between them.
|
||||
|
||||
for i in "${!VARIANTS[@]}"; do
|
||||
dir="${VARIANT_PROJECTS[$i]}"
|
||||
mkdir -p "$dir" "${VARIANT_CACHES[$i]}"
|
||||
cp "$BRANCH_DIR/benchmarks/fixture.package.json" "$dir/package.json"
|
||||
printf "storeDir: %s\ncacheDir: %s\n" "${VARIANT_STORES[$i]}" "${VARIANT_CACHES[$i]}" > "$dir/pnpm-workspace.yaml"
|
||||
done
|
||||
|
||||
# Keep a pristine copy of package.json for the peek benchmark
|
||||
cp "$BRANCH_DIR/benchmarks/fixture.package.json" "$BENCH_DIR/original-package.json"
|
||||
|
||||
# ── Populate stores and caches ─────────────────────────────────────────────
|
||||
# A full install populates both the content-addressable store and the
|
||||
# registry metadata cache for each variant.
|
||||
|
||||
for i in "${!VARIANTS[@]}"; do
|
||||
label="${VARIANTS[$i]}"
|
||||
dir="${VARIANT_PROJECTS[$i]}"
|
||||
bin="${VARIANT_BINS[$i]}"
|
||||
echo "Populating store and cache for $label..."
|
||||
rm -rf "$dir/node_modules" "$dir/pnpm-lock.yaml"
|
||||
cd "$dir" && node "$bin" install --ignore-scripts --no-frozen-lockfile
|
||||
if [ ! -f "$dir/pnpm-lock.yaml" ]; then
|
||||
echo "error: pnpm-lock.yaml was not created for $label in $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$dir/pnpm-lock.yaml" "$BENCH_DIR/saved-lockfile-$label.yaml"
|
||||
done
|
||||
|
||||
# ── Helper ──────────────────────────────────────────────────────────────────
|
||||
# run_bench <name> <prepare_template> <cmd_template>
|
||||
#
|
||||
# Templates use placeholders that are substituted per variant:
|
||||
# {project} → project directory
|
||||
# {bin} → compiled pnpm binary
|
||||
# {store} → store directory
|
||||
# {cache} → cache directory
|
||||
# {lockfile} → saved lockfile path
|
||||
|
||||
run_bench() {
|
||||
local bench_name=$1
|
||||
local prepare_tpl=$2
|
||||
local cmd_tpl=$3
|
||||
|
||||
for i in "${!VARIANTS[@]}"; do
|
||||
local variant="${VARIANTS[$i]}"
|
||||
local project="${VARIANT_PROJECTS[$i]}"
|
||||
local bin="${VARIANT_BINS[$i]}"
|
||||
local store="${VARIANT_STORES[$i]}"
|
||||
local cache="${VARIANT_CACHES[$i]}"
|
||||
local lockfile="$BENCH_DIR/saved-lockfile-$variant.yaml"
|
||||
|
||||
local prepare="$prepare_tpl"
|
||||
prepare="${prepare//\{project\}/$project}"
|
||||
prepare="${prepare//\{bin\}/$bin}"
|
||||
prepare="${prepare//\{store\}/$store}"
|
||||
prepare="${prepare//\{cache\}/$cache}"
|
||||
prepare="${prepare//\{lockfile\}/$lockfile}"
|
||||
|
||||
local cmd="$cmd_tpl"
|
||||
cmd="${cmd//\{project\}/$project}"
|
||||
cmd="${cmd//\{bin\}/$bin}"
|
||||
cmd="${cmd//\{store\}/$store}"
|
||||
cmd="${cmd//\{cache\}/$cache}"
|
||||
cmd="${cmd//\{lockfile\}/$lockfile}"
|
||||
|
||||
echo ""
|
||||
echo " $variant:"
|
||||
hyperfine \
|
||||
--warmup "$WARMUP" \
|
||||
--runs "$RUNS" \
|
||||
--ignore-failure \
|
||||
--prepare "$prepare" \
|
||||
--command-name "$variant" \
|
||||
"$cmd" \
|
||||
--export-json "$BENCH_DIR/${bench_name}-${variant}.json" \
|
||||
|| true
|
||||
done
|
||||
jq -r --arg t "$target" '
|
||||
[.results[] | select(.command == $t)
|
||||
| "\((.mean*1000|round)/1000)s ± \((.stddev*1000|round)/1000)s"]
|
||||
| first // "n/a"
|
||||
' "$json" 2>/dev/null || echo "n/a"
|
||||
}
|
||||
|
||||
# ── Benchmark 1: Headless install ──────────────────────────────────────────
|
||||
# Lockfile present, node_modules deleted, store and cache warm.
|
||||
# This is the common "CI install" or "fresh clone + install" path.
|
||||
results_md="$BENCH_DIR/results.md"
|
||||
{
|
||||
echo "# Benchmark Results"
|
||||
echo
|
||||
echo "| # | Scenario | main | HEAD |"
|
||||
echo "|---|---|---|---|"
|
||||
} > "$results_md"
|
||||
|
||||
echo ""
|
||||
echo "━━━ Benchmark 1: Headless install (frozen lockfile, warm store+cache) ━━━"
|
||||
|
||||
run_bench "headless" \
|
||||
"rm -rf {project}/node_modules && cp {lockfile} {project}/pnpm-lock.yaml" \
|
||||
"cd {project} && node {bin} install --frozen-lockfile --ignore-scripts >/dev/null 2>&1"
|
||||
|
||||
# ── Benchmark 2: Re-resolution with existing lockfile ─────────────────────
|
||||
# Lockfile present, add a new dependency to trigger re-resolution.
|
||||
# Store and cache warm. This exercises the peekManifestFromStore path.
|
||||
|
||||
echo ""
|
||||
echo "━━━ Benchmark 2: Re-resolution (add dep to existing lockfile, warm store+cache) ━━━"
|
||||
|
||||
run_bench "peek" \
|
||||
"rm -rf {project}/node_modules && cp {lockfile} {project}/pnpm-lock.yaml && cp $BENCH_DIR/original-package.json {project}/package.json" \
|
||||
"cd {project} && node {bin} add is-odd --ignore-scripts >/dev/null 2>&1"
|
||||
|
||||
# ── Benchmark 3: Full resolution (warm store+cache) ──────────────────────
|
||||
# No lockfile, no node_modules, store and cache warm.
|
||||
# Resolution runs for all packages using cached registry metadata.
|
||||
|
||||
echo ""
|
||||
echo "━━━ Benchmark 3: Full resolution (no lockfile, warm store+cache) ━━━"
|
||||
|
||||
run_bench "nolockfile" \
|
||||
"rm -rf {project}/node_modules {project}/pnpm-lock.yaml && cp $BENCH_DIR/original-package.json {project}/package.json" \
|
||||
"cd {project} && node {bin} install --ignore-scripts --no-frozen-lockfile >/dev/null 2>&1"
|
||||
|
||||
# ── Benchmark 4: Headless cold (lockfile, no store, no cache) ─────────────
|
||||
# Lockfile present, but store and cache are empty.
|
||||
# This tests the fetch-from-registry + link path guided by a lockfile.
|
||||
|
||||
echo ""
|
||||
echo "━━━ Benchmark 4: Headless install (frozen lockfile, cold store+cache) ━━━"
|
||||
|
||||
run_bench "headless-cold" \
|
||||
"rm -rf {project}/node_modules {store} {cache} && cp {lockfile} {project}/pnpm-lock.yaml" \
|
||||
"cd {project} && node {bin} install --frozen-lockfile --ignore-scripts >/dev/null 2>&1"
|
||||
|
||||
# ── Benchmark 5: Cold install (no store, no cache, no lockfile) ───────────
|
||||
# Everything is deleted before each run. This is the true cold start.
|
||||
|
||||
echo ""
|
||||
echo "━━━ Benchmark 5: Cold install (no store, no cache, no lockfile) ━━━"
|
||||
|
||||
run_bench "cold" \
|
||||
"rm -rf {project}/node_modules {project}/pnpm-lock.yaml {store} {cache} && cp $BENCH_DIR/original-package.json {project}/package.json" \
|
||||
"cd {project} && node {bin} install --ignore-scripts --no-frozen-lockfile >/dev/null 2>&1"
|
||||
|
||||
# ── Benchmark 6: GVS warm reinstall ───────────────────────────────────────
|
||||
# Global virtual store enabled, GVS warm, node_modules deleted.
|
||||
# This tests the reattach fast-path: all packages should be skipped
|
||||
# (no fetch/import) because their GVS hash directories already exist.
|
||||
|
||||
echo ""
|
||||
echo "━━━ Benchmark 6: GVS warm reinstall (frozen lockfile, warm global virtual store) ━━━"
|
||||
|
||||
# Set up separate GVS-enabled project directories per variant
|
||||
GVS_PROJECTS=()
|
||||
for i in "${!VARIANTS[@]}"; do
|
||||
gvs_dir="$BENCH_DIR/project-gvs-${VARIANTS[$i]}"
|
||||
mkdir -p "$gvs_dir"
|
||||
cp "$BRANCH_DIR/benchmarks/fixture.package.json" "$gvs_dir/package.json"
|
||||
printf "storeDir: %s\ncacheDir: %s\nenableGlobalVirtualStore: true\n" \
|
||||
"${VARIANT_STORES[$i]}" "${VARIANT_CACHES[$i]}" > "$gvs_dir/pnpm-workspace.yaml"
|
||||
GVS_PROJECTS+=("$gvs_dir")
|
||||
|
||||
# Warm the GVS with a full install
|
||||
echo "Warming GVS for ${VARIANTS[$i]}..."
|
||||
cd "$gvs_dir" && node "${VARIANT_BINS[$i]}" install --ignore-scripts --no-frozen-lockfile >/dev/null 2>&1
|
||||
cp "$gvs_dir/pnpm-lock.yaml" "$BENCH_DIR/saved-lockfile-gvs-${VARIANTS[$i]}.yaml"
|
||||
done
|
||||
|
||||
for i in "${!VARIANTS[@]}"; do
|
||||
variant="${VARIANTS[$i]}"
|
||||
gvs_project="${GVS_PROJECTS[$i]}"
|
||||
bin="${VARIANT_BINS[$i]}"
|
||||
lockfile="$BENCH_DIR/saved-lockfile-gvs-$variant.yaml"
|
||||
i=1
|
||||
for entry in "${SCENARIOS[@]}"; do
|
||||
scenario="${entry%%:*}"
|
||||
label="${entry#*:}"
|
||||
|
||||
echo ""
|
||||
echo " $variant:"
|
||||
hyperfine \
|
||||
echo "━━━ Benchmark $i: $label ━━━"
|
||||
|
||||
"$BIN" \
|
||||
--scenario "$scenario" \
|
||||
--registry npm \
|
||||
--pnpm-repository "$REPO_ROOT" \
|
||||
--fixture-dir "$FIXTURE_DIR" \
|
||||
--work-env "$BENCH_DIR/work-env" \
|
||||
--warmup "$WARMUP" \
|
||||
--runs "$RUNS" \
|
||||
--ignore-failure \
|
||||
--prepare "rm -rf $gvs_project/node_modules && cp $lockfile $gvs_project/pnpm-lock.yaml" \
|
||||
--command-name "$variant" \
|
||||
"cd $gvs_project && node $bin install --frozen-lockfile --ignore-scripts >/dev/null 2>&1" \
|
||||
--export-json "$BENCH_DIR/gvs-warm-${variant}.json" \
|
||||
|| true
|
||||
pnpm@main pnpm@HEAD
|
||||
|
||||
cp "$BENCH_DIR/work-env/BENCHMARK_REPORT.md" "$BENCH_DIR/${scenario}.md"
|
||||
cp "$BENCH_DIR/work-env/BENCHMARK_REPORT.json" "$BENCH_DIR/${scenario}.json"
|
||||
|
||||
main_cell=$(read_cell "pnpm@main" "$BENCH_DIR/${scenario}.json")
|
||||
head_cell=$(read_cell "pnpm@HEAD" "$BENCH_DIR/${scenario}.json")
|
||||
echo "| $i | $label | $main_cell | $head_cell |" >> "$results_md"
|
||||
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
# ── Summary ─────────────────────────────────────────────────────────────────
|
||||
# Combine the per-scenario hyperfine JSONs into one Bencher-shaped
|
||||
# report. Keep only the @HEAD result from each scenario and rename
|
||||
# `.command` to the scenario name so Bencher's shell_hyperfine adapter
|
||||
# names the benchmark after the scenario instead of `pnpm@HEAD`.
|
||||
if command -v jq >/dev/null; then
|
||||
bencher_inputs=()
|
||||
for entry in "${SCENARIOS[@]}"; do
|
||||
scenario="${entry%%:*}"
|
||||
jq --arg s "$scenario" \
|
||||
'.results |= [.[] | select(.command == "pnpm@HEAD") | .command = $s]' \
|
||||
"$BENCH_DIR/${scenario}.json" > "$BENCH_DIR/${scenario}-bencher.json"
|
||||
bencher_inputs+=("$BENCH_DIR/${scenario}-bencher.json")
|
||||
done
|
||||
jq -s '{results: map(.results) | add}' \
|
||||
"${bencher_inputs[@]}" > "$BENCH_DIR/bencher-results.json"
|
||||
else
|
||||
echo "warning: jq not on PATH; skipping bencher-results.json generation" >&2
|
||||
fi
|
||||
|
||||
RESULTS_MD="$BENCH_DIR/results.md"
|
||||
|
||||
echo ""
|
||||
echo
|
||||
echo "━━━ Results ━━━"
|
||||
node "$BRANCH_DIR/benchmarks/generate-results.js" "$BENCH_DIR" "$RESULTS_MD"
|
||||
echo ""
|
||||
echo "Results saved to: $RESULTS_MD"
|
||||
|
||||
# Cleanup
|
||||
for project in "${VARIANT_PROJECTS[@]}" "${GVS_PROJECTS[@]}"; do
|
||||
rm -rf "$project/node_modules"
|
||||
done
|
||||
echo ""
|
||||
cat "$results_md"
|
||||
echo
|
||||
echo "Results saved to: $results_md"
|
||||
echo "Temp directory kept at: $BENCH_DIR"
|
||||
echo "Remove with: rm -rf $BENCH_DIR"
|
||||
|
||||
12144
benchmarks/fixture/pnpm-lock.yaml
generated
Normal file
12144
benchmarks/fixture/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,45 +0,0 @@
|
||||
const fs = require('fs')
|
||||
|
||||
const benchDir = process.argv[2]
|
||||
const outputFile = process.argv[3]
|
||||
|
||||
const benchmarks = [
|
||||
['headless', 'Headless (warm store+cache)'],
|
||||
['peek', 'Re-resolution (add dep, warm)'],
|
||||
['nolockfile', 'Full resolution (warm, no lockfile)'],
|
||||
['headless-cold', 'Headless (cold store+cache)'],
|
||||
['cold', 'Cold install (nothing warm)'],
|
||||
['gvs-warm', 'GVS warm reinstall (warm global store)'],
|
||||
]
|
||||
|
||||
function readResult (benchDir, name, variant) {
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(`${benchDir}/${name}-${variant}.json`, 'utf8'))
|
||||
const r = data.results[0]
|
||||
return `${r.mean.toFixed(3)}s ± ${r.stddev.toFixed(3)}s`
|
||||
} catch (err) {
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
console.error(`Warning: failed to read ${name}-${variant}: ${err.message}`)
|
||||
}
|
||||
return 'n/a'
|
||||
}
|
||||
}
|
||||
|
||||
const lines = [
|
||||
'# Benchmark Results',
|
||||
'',
|
||||
'| # | Scenario | main | branch |',
|
||||
'|---|---|---|---|',
|
||||
]
|
||||
|
||||
benchmarks.forEach(([name, label], i) => {
|
||||
const mainCell = readResult(benchDir, name, 'main')
|
||||
const branchCell = readResult(benchDir, name, 'branch')
|
||||
lines.push(`| ${i + 1} | ${label} | ${mainCell} | ${branchCell} |`)
|
||||
})
|
||||
|
||||
lines.push('')
|
||||
|
||||
const output = lines.join('\n')
|
||||
fs.writeFileSync(outputFile, output)
|
||||
console.log(output)
|
||||
@@ -1,5 +1,43 @@
|
||||
# @pnpm/link-bins
|
||||
|
||||
## 1100.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a456dc7]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/workspace.project-manifest-reader@1100.0.9
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/bins.resolver@1100.0.5
|
||||
- @pnpm/pkg-manifest.reader@1100.0.5
|
||||
- @pnpm/pkg-manifest.utils@1100.2.1
|
||||
|
||||
## 1100.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d7da112]
|
||||
- @pnpm/workspace.project-manifest-reader@1100.0.8
|
||||
|
||||
## 1100.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1627943]
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/pkg-manifest.utils@1100.2.0
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/workspace.project-manifest-reader@1100.0.7
|
||||
- @pnpm/bins.resolver@1100.0.4
|
||||
- @pnpm/pkg-manifest.reader@1100.0.4
|
||||
|
||||
## 1100.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/pkg-manifest.utils@1100.1.4
|
||||
- @pnpm/workspace.project-manifest-reader@1100.0.6
|
||||
|
||||
## 1100.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/bins.linker",
|
||||
"version": "1100.0.6",
|
||||
"version": "1100.0.10",
|
||||
"description": "Link bins to node_modules/.bin",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# @pnpm/remove-bins
|
||||
|
||||
## 1100.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/bins.resolver@1100.0.5
|
||||
- @pnpm/core-loggers@1100.1.2
|
||||
- @pnpm/pkg-manifest.reader@1100.0.5
|
||||
|
||||
## 1100.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/bins.resolver@1100.0.4
|
||||
- @pnpm/core-loggers@1100.1.1
|
||||
- @pnpm/pkg-manifest.reader@1100.0.4
|
||||
|
||||
## 1100.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4a79336]
|
||||
- @pnpm/core-loggers@1100.1.0
|
||||
|
||||
## 1100.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/bins.remover",
|
||||
"version": "1100.0.3",
|
||||
"version": "1100.0.6",
|
||||
"description": "Remove bins from .bin",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @pnpm/package-bins
|
||||
|
||||
## 1100.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/types@1101.2.0
|
||||
|
||||
## 1100.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/types@1101.1.1
|
||||
|
||||
## 1100.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/bins.resolver",
|
||||
"version": "1100.0.3",
|
||||
"version": "1100.0.5",
|
||||
"description": "Returns bins of a package",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,130 @@
|
||||
# @pnpm/building.after-install
|
||||
|
||||
## 1101.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a23956e]
|
||||
- Updated dependencies [aa6149d]
|
||||
- Updated dependencies [e55f4b5]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/config.reader@1101.4.1
|
||||
- @pnpm/worker@1100.1.8
|
||||
- @pnpm/lockfile.utils@1100.0.10
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/store.connection-manager@1100.2.4
|
||||
- @pnpm/bins.linker@1100.0.10
|
||||
- @pnpm/deps.graph-hasher@1100.2.2
|
||||
- @pnpm/building.pkg-requires-build@1100.0.5
|
||||
- @pnpm/building.policy@1100.0.7
|
||||
- @pnpm/config.normalize-registries@1100.0.5
|
||||
- @pnpm/core-loggers@1100.1.2
|
||||
- @pnpm/deps.path@1100.0.5
|
||||
- @pnpm/exec.lifecycle@1100.0.14
|
||||
- @pnpm/installing.context@1100.0.13
|
||||
- @pnpm/installing.modules-yaml@1100.0.6
|
||||
- @pnpm/lockfile.types@1100.0.8
|
||||
- @pnpm/lockfile.walker@1100.0.8
|
||||
- @pnpm/pkg-manifest.reader@1100.0.5
|
||||
- @pnpm/store.cafs@1100.1.7
|
||||
- @pnpm/store.controller-types@1100.1.2
|
||||
|
||||
## 1101.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3b62f9d]
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/config.reader@1101.4.0
|
||||
- @pnpm/bins.linker@1100.0.9
|
||||
- @pnpm/store.connection-manager@1100.2.3
|
||||
- @pnpm/exec.lifecycle@1100.0.13
|
||||
|
||||
## 1101.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/store.connection-manager@1100.2.2
|
||||
|
||||
## 1101.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3687b0e]
|
||||
- Updated dependencies [ced20cb]
|
||||
- Updated dependencies [9cb48bb]
|
||||
- Updated dependencies [d1b340f]
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/config.reader@1101.3.3
|
||||
- @pnpm/exec.lifecycle@1100.0.12
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/store.connection-manager@1100.2.1
|
||||
- @pnpm/installing.context@1100.0.12
|
||||
- @pnpm/deps.graph-hasher@1100.2.1
|
||||
- @pnpm/lockfile.types@1100.0.7
|
||||
- @pnpm/lockfile.utils@1100.0.9
|
||||
- @pnpm/store.controller-types@1100.1.1
|
||||
- @pnpm/bins.linker@1100.0.8
|
||||
- @pnpm/building.pkg-requires-build@1100.0.4
|
||||
- @pnpm/building.policy@1100.0.6
|
||||
- @pnpm/config.normalize-registries@1100.0.4
|
||||
- @pnpm/core-loggers@1100.1.1
|
||||
- @pnpm/deps.path@1100.0.4
|
||||
- @pnpm/installing.modules-yaml@1100.0.5
|
||||
- @pnpm/lockfile.walker@1100.0.7
|
||||
- @pnpm/pkg-manifest.reader@1100.0.4
|
||||
- @pnpm/store.cafs@1100.1.6
|
||||
- @pnpm/worker@1100.1.7
|
||||
|
||||
## 1101.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3ddde2b: **fix**: anchor the side-effects-cache key and global-virtual-store hash to the project's script-runner Node — `engines.runtime` pin when present, shell `node` otherwise — instead of pnpm's own runtime.
|
||||
|
||||
`ENGINE_NAME` (the `<platform>;<arch>;node<major>` prefix used as the side-effects-cache key and the engine portion of the GVS hash) was computed from `process.version` — the Node that runs pnpm itself. That was wrong in two situations:
|
||||
|
||||
1. **`@pnpm/exe` SEA bundle.** The bundle has its own embedded Node, not the `node` on the user's `PATH` that actually spawns lifecycle scripts. Two pnpm installations on the same machine (one SEA, one npm-package) therefore disagreed on the cache key, partitioning the side-effects cache and the global virtual store across two Node majors even though both installs would run scripts on the same shell `node`.
|
||||
2. **`engines.runtime` / `devEngines.runtime` pin.** When a project pins a Node version via `devEngines.runtime` (pnpm v11+), pnpm downloads that Node into `node_modules/node/` and uses it to run lifecycle scripts. But the hash still anchored to whichever Node ran pnpm itself, not to the pinned Node — so two installs of the same project with two different runner Nodes would still disagree on the GVS slot path even though scripts run on the same pinned Node.
|
||||
|
||||
Three changes:
|
||||
|
||||
- `@pnpm/engine.runtime.system-node-version` now exports `engineName(nodeVersion?)`. Resolves the version in this order: explicit override → `getSystemNodeVersion()` (which already prefers `node --version` over `process.version` in SEA contexts) → `process.version`.
|
||||
- `@pnpm/deps.graph-hasher` now exports `findRuntimeNodeVersion(snapshotKeys)` — scans an iterable of lockfile snapshot keys for a `node@runtime:<version>` entry and returns its bare version string. `calcDepState` and `calcGraphNodeHash`/`iterateHashedGraphNodes` accept a `nodeVersion?` (in the options bag for the first, as a trailing parameter / ctx field for the others), forwarded to `engineName()`. The default (no override) preserves the pre-change behaviour. The legacy `ENGINE_NAME` constant in `@pnpm/constants` is unchanged so external consumers and existing tests keep working; in non-SEA, non-pinned contexts every value lines up.
|
||||
- Every install-side caller of the graph-hasher (`@pnpm/installing.deps-resolver`, `@pnpm/installing.deps-restorer`, `@pnpm/installing.deps-installer`, `@pnpm/building.during-install`, `@pnpm/building.after-install`, `@pnpm/deps.graph-builder`) now derives the project's pinned runtime via `findRuntimeNodeVersion(Object.keys(graph))` once per invocation and threads it through.
|
||||
|
||||
On upgrade, two one-time GVS slot churns are possible:
|
||||
|
||||
- **SEA-pnpm users** without a runtime pin: slots that previously hashed under the embedded-Node major (e.g. `node26`) now hash under the shell-Node major (e.g. `node24`), matching what pacquet, the npm-published `pnpm` package, and any other pnpm-compatible tool already produce.
|
||||
- **Projects with a `devEngines.runtime` pin**: slots that previously hashed under the runner's Node major now hash under the pinned Node major, matching what the lifecycle scripts will actually run on.
|
||||
|
||||
In both cases the old slots become prune-eligible.
|
||||
|
||||
- Updated dependencies [4195766]
|
||||
- Updated dependencies [31538bf]
|
||||
- Updated dependencies [020ac45]
|
||||
- Updated dependencies [d3f8408]
|
||||
- Updated dependencies [3ddde2b]
|
||||
- Updated dependencies [5dc8be8]
|
||||
- Updated dependencies [4a79336]
|
||||
- Updated dependencies [a62f959]
|
||||
- Updated dependencies [ba2c884]
|
||||
- Updated dependencies [8df408c]
|
||||
- @pnpm/store.controller-types@1100.1.0
|
||||
- @pnpm/store.connection-manager@1100.2.0
|
||||
- @pnpm/config.reader@1101.3.2
|
||||
- @pnpm/deps.graph-hasher@1100.2.0
|
||||
- @pnpm/core-loggers@1100.1.0
|
||||
- @pnpm/installing.context@1100.0.11
|
||||
- @pnpm/lockfile.types@1100.0.6
|
||||
- @pnpm/lockfile.utils@1100.0.8
|
||||
- @pnpm/exec.lifecycle@1100.0.11
|
||||
- @pnpm/store.cafs@1100.1.5
|
||||
- @pnpm/building.policy@1100.0.5
|
||||
- @pnpm/lockfile.walker@1100.0.6
|
||||
- @pnpm/worker@1100.1.6
|
||||
- @pnpm/bins.linker@1100.0.7
|
||||
|
||||
## 1101.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/building.after-install",
|
||||
"version": "1101.0.12",
|
||||
"version": "1101.0.17",
|
||||
"description": "Rebuild packages that are already installed by running their lifecycle scripts",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
@@ -41,7 +41,6 @@
|
||||
"@pnpm/deps.graph-hasher": "workspace:*",
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*",
|
||||
"@pnpm/deps.path": "workspace:*",
|
||||
"@pnpm/engine.runtime.system-node-version": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/exec.lifecycle": "workspace:*",
|
||||
"@pnpm/installing.context": "workspace:*",
|
||||
|
||||
@@ -10,10 +10,9 @@ import {
|
||||
WANTED_LOCKFILE,
|
||||
} from '@pnpm/constants'
|
||||
import { skippedOptionalDependencyLogger } from '@pnpm/core-loggers'
|
||||
import { calcDepState, type DepsStateCache, lockfileToDepGraph } from '@pnpm/deps.graph-hasher'
|
||||
import { calcDepState, type DepsStateCache, findRuntimeNodeVersion, lockfileToDepGraph } from '@pnpm/deps.graph-hasher'
|
||||
import { graphSequencer } from '@pnpm/deps.graph-sequencer'
|
||||
import * as dp from '@pnpm/deps.path'
|
||||
import { findRuntimeNodeVersion } from '@pnpm/engine.runtime.system-node-version'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import {
|
||||
runLifecycleHooksConcurrently,
|
||||
|
||||
@@ -39,9 +39,6 @@
|
||||
{
|
||||
"path": "../../deps/path"
|
||||
},
|
||||
{
|
||||
"path": "../../engine/runtime/system-node-version"
|
||||
},
|
||||
{
|
||||
"path": "../../exec/lifecycle"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,91 @@
|
||||
# @pnpm/building.commands
|
||||
|
||||
## 1100.0.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a23956e]
|
||||
- Updated dependencies [aa6149d]
|
||||
- Updated dependencies [572842a]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/config.reader@1101.4.1
|
||||
- @pnpm/installing.commands@1100.6.0
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/building.after-install@1101.0.17
|
||||
- @pnpm/store.connection-manager@1100.2.4
|
||||
- @pnpm/cli.utils@1101.0.8
|
||||
- @pnpm/config.writer@1100.0.10
|
||||
- @pnpm/deps.path@1100.0.5
|
||||
- @pnpm/installing.modules-yaml@1100.0.6
|
||||
- @pnpm/workspace.projects-sorter@1100.0.4
|
||||
|
||||
## 1100.0.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3b62f9d]
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/config.reader@1101.4.0
|
||||
- @pnpm/installing.commands@1100.5.0
|
||||
- @pnpm/cli.utils@1101.0.7
|
||||
- @pnpm/building.after-install@1101.0.16
|
||||
- @pnpm/store.connection-manager@1100.2.3
|
||||
|
||||
## 1100.0.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [881a865]
|
||||
- @pnpm/installing.commands@1100.4.2
|
||||
|
||||
## 1100.0.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/installing.commands@1100.4.1
|
||||
- @pnpm/store.connection-manager@1100.2.2
|
||||
- @pnpm/building.after-install@1101.0.15
|
||||
|
||||
## 1100.0.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3687b0e]
|
||||
- Updated dependencies [ced20cb]
|
||||
- Updated dependencies [a620557]
|
||||
- Updated dependencies [d1b340f]
|
||||
- Updated dependencies [b206a15]
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/config.reader@1101.3.3
|
||||
- @pnpm/installing.commands@1100.4.0
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/building.after-install@1101.0.14
|
||||
- @pnpm/store.connection-manager@1100.2.1
|
||||
- @pnpm/cli.utils@1101.0.6
|
||||
- @pnpm/config.writer@1100.0.9
|
||||
- @pnpm/deps.path@1100.0.4
|
||||
- @pnpm/installing.modules-yaml@1100.0.5
|
||||
- @pnpm/workspace.projects-sorter@1100.0.3
|
||||
|
||||
## 1100.0.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4195766]
|
||||
- Updated dependencies [31538bf]
|
||||
- Updated dependencies [020ac45]
|
||||
- Updated dependencies [d3f8408]
|
||||
- Updated dependencies [3ddde2b]
|
||||
- Updated dependencies [a62f959]
|
||||
- Updated dependencies [ba2c884]
|
||||
- Updated dependencies [8df408c]
|
||||
- @pnpm/installing.commands@1100.3.0
|
||||
- @pnpm/store.connection-manager@1100.2.0
|
||||
- @pnpm/config.reader@1101.3.2
|
||||
- @pnpm/building.after-install@1101.0.13
|
||||
- @pnpm/cli.utils@1101.0.5
|
||||
- @pnpm/config.writer@1100.0.8
|
||||
|
||||
## 1100.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/building.commands",
|
||||
"version": "1100.0.17",
|
||||
"version": "1100.0.23",
|
||||
"description": "Commands for rebuilding and managing dependency builds",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,101 @@
|
||||
# @pnpm/building.during-install
|
||||
|
||||
## 1101.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a23956e]
|
||||
- Updated dependencies [aa6149d]
|
||||
- Updated dependencies [26a7d63]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/config.reader@1101.4.1
|
||||
- @pnpm/worker@1100.1.8
|
||||
- @pnpm/patching.apply-patch@1100.0.1
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/bins.linker@1100.0.10
|
||||
- @pnpm/deps.graph-hasher@1100.2.2
|
||||
- @pnpm/core-loggers@1100.1.2
|
||||
- @pnpm/deps.path@1100.0.5
|
||||
- @pnpm/exec.lifecycle@1100.0.14
|
||||
- @pnpm/pkg-manifest.reader@1100.0.5
|
||||
- @pnpm/store.controller-types@1100.1.2
|
||||
- @pnpm/fs.hard-link-dir@1100.0.1
|
||||
|
||||
## 1101.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3b62f9d]
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/config.reader@1101.4.0
|
||||
- @pnpm/bins.linker@1100.0.9
|
||||
- @pnpm/exec.lifecycle@1100.0.13
|
||||
|
||||
## 1101.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3687b0e]
|
||||
- Updated dependencies [ced20cb]
|
||||
- Updated dependencies [9cb48bb]
|
||||
- Updated dependencies [d1b340f]
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/config.reader@1101.3.3
|
||||
- @pnpm/exec.lifecycle@1100.0.12
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/deps.graph-hasher@1100.2.1
|
||||
- @pnpm/store.controller-types@1100.1.1
|
||||
- @pnpm/bins.linker@1100.0.8
|
||||
- @pnpm/core-loggers@1100.1.1
|
||||
- @pnpm/deps.path@1100.0.4
|
||||
- @pnpm/pkg-manifest.reader@1100.0.4
|
||||
- @pnpm/worker@1100.1.7
|
||||
- @pnpm/fs.hard-link-dir@1100.0.1
|
||||
- @pnpm/patching.apply-patch@1100.0.0
|
||||
|
||||
## 1101.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3ddde2b: **fix**: anchor the side-effects-cache key and global-virtual-store hash to the project's script-runner Node — `engines.runtime` pin when present, shell `node` otherwise — instead of pnpm's own runtime.
|
||||
|
||||
`ENGINE_NAME` (the `<platform>;<arch>;node<major>` prefix used as the side-effects-cache key and the engine portion of the GVS hash) was computed from `process.version` — the Node that runs pnpm itself. That was wrong in two situations:
|
||||
|
||||
1. **`@pnpm/exe` SEA bundle.** The bundle has its own embedded Node, not the `node` on the user's `PATH` that actually spawns lifecycle scripts. Two pnpm installations on the same machine (one SEA, one npm-package) therefore disagreed on the cache key, partitioning the side-effects cache and the global virtual store across two Node majors even though both installs would run scripts on the same shell `node`.
|
||||
2. **`engines.runtime` / `devEngines.runtime` pin.** When a project pins a Node version via `devEngines.runtime` (pnpm v11+), pnpm downloads that Node into `node_modules/node/` and uses it to run lifecycle scripts. But the hash still anchored to whichever Node ran pnpm itself, not to the pinned Node — so two installs of the same project with two different runner Nodes would still disagree on the GVS slot path even though scripts run on the same pinned Node.
|
||||
|
||||
Three changes:
|
||||
|
||||
- `@pnpm/engine.runtime.system-node-version` now exports `engineName(nodeVersion?)`. Resolves the version in this order: explicit override → `getSystemNodeVersion()` (which already prefers `node --version` over `process.version` in SEA contexts) → `process.version`.
|
||||
- `@pnpm/deps.graph-hasher` now exports `findRuntimeNodeVersion(snapshotKeys)` — scans an iterable of lockfile snapshot keys for a `node@runtime:<version>` entry and returns its bare version string. `calcDepState` and `calcGraphNodeHash`/`iterateHashedGraphNodes` accept a `nodeVersion?` (in the options bag for the first, as a trailing parameter / ctx field for the others), forwarded to `engineName()`. The default (no override) preserves the pre-change behaviour. The legacy `ENGINE_NAME` constant in `@pnpm/constants` is unchanged so external consumers and existing tests keep working; in non-SEA, non-pinned contexts every value lines up.
|
||||
- Every install-side caller of the graph-hasher (`@pnpm/installing.deps-resolver`, `@pnpm/installing.deps-restorer`, `@pnpm/installing.deps-installer`, `@pnpm/building.during-install`, `@pnpm/building.after-install`, `@pnpm/deps.graph-builder`) now derives the project's pinned runtime via `findRuntimeNodeVersion(Object.keys(graph))` once per invocation and threads it through.
|
||||
|
||||
On upgrade, two one-time GVS slot churns are possible:
|
||||
|
||||
- **SEA-pnpm users** without a runtime pin: slots that previously hashed under the embedded-Node major (e.g. `node26`) now hash under the shell-Node major (e.g. `node24`), matching what pacquet, the npm-published `pnpm` package, and any other pnpm-compatible tool already produce.
|
||||
- **Projects with a `devEngines.runtime` pin**: slots that previously hashed under the runner's Node major now hash under the pinned Node major, matching what the lifecycle scripts will actually run on.
|
||||
|
||||
In both cases the old slots become prune-eligible.
|
||||
|
||||
- Updated dependencies [4195766]
|
||||
- Updated dependencies [020ac45]
|
||||
- Updated dependencies [d3f8408]
|
||||
- Updated dependencies [3ddde2b]
|
||||
- Updated dependencies [5dc8be8]
|
||||
- Updated dependencies [4a79336]
|
||||
- Updated dependencies [a62f959]
|
||||
- Updated dependencies [ba2c884]
|
||||
- Updated dependencies [8df408c]
|
||||
- @pnpm/store.controller-types@1100.1.0
|
||||
- @pnpm/config.reader@1101.3.2
|
||||
- @pnpm/deps.graph-hasher@1100.2.0
|
||||
- @pnpm/core-loggers@1100.1.0
|
||||
- @pnpm/exec.lifecycle@1100.0.11
|
||||
- @pnpm/worker@1100.1.6
|
||||
- @pnpm/bins.linker@1100.0.7
|
||||
- @pnpm/fs.hard-link-dir@1100.0.1
|
||||
- @pnpm/patching.apply-patch@1100.0.0
|
||||
|
||||
## 1101.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/building.during-install",
|
||||
"version": "1101.0.10",
|
||||
"version": "1101.0.14",
|
||||
"description": "Build packages in node_modules",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
@@ -39,7 +39,6 @@
|
||||
"@pnpm/deps.graph-hasher": "workspace:*",
|
||||
"@pnpm/deps.graph-sequencer": "workspace:*",
|
||||
"@pnpm/deps.path": "workspace:*",
|
||||
"@pnpm/engine.runtime.system-node-version": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/exec.lifecycle": "workspace:*",
|
||||
"@pnpm/fs.hard-link-dir": "workspace:*",
|
||||
|
||||
@@ -6,8 +6,7 @@ import util from 'node:util'
|
||||
import { linkBins, linkBinsOfPackages } from '@pnpm/bins.linker'
|
||||
import { getWorkspaceConcurrency } from '@pnpm/config.reader'
|
||||
import { skippedOptionalDependencyLogger } from '@pnpm/core-loggers'
|
||||
import { calcDepState, type DepsStateCache } from '@pnpm/deps.graph-hasher'
|
||||
import { findRuntimeNodeVersion } from '@pnpm/engine.runtime.system-node-version'
|
||||
import { calcDepState, type DepsStateCache, findRuntimeNodeVersion } from '@pnpm/deps.graph-hasher'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { runPostinstallHooks } from '@pnpm/exec.lifecycle'
|
||||
import { logger } from '@pnpm/logger'
|
||||
|
||||
@@ -36,9 +36,6 @@
|
||||
{
|
||||
"path": "../../deps/path"
|
||||
},
|
||||
{
|
||||
"path": "../../engine/runtime/system-node-version"
|
||||
},
|
||||
{
|
||||
"path": "../../exec/lifecycle"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @pnpm/building.pkg-requires-build
|
||||
|
||||
## 1100.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/types@1101.2.0
|
||||
|
||||
## 1100.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/types@1101.1.1
|
||||
|
||||
## 1100.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/building.pkg-requires-build",
|
||||
"version": "1100.0.3",
|
||||
"version": "1100.0.5",
|
||||
"description": "Checks if a package requires to be built",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @pnpm/building.policy
|
||||
|
||||
## 1100.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/config.version-policy@1100.1.2
|
||||
- @pnpm/deps.path@1100.0.5
|
||||
|
||||
## 1100.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/config.version-policy@1100.1.1
|
||||
- @pnpm/deps.path@1100.0.4
|
||||
|
||||
## 1100.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b6e2c8c]
|
||||
- @pnpm/config.version-policy@1100.1.0
|
||||
|
||||
## 1100.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/building.policy",
|
||||
"version": "1100.0.4",
|
||||
"version": "1100.0.7",
|
||||
"description": "Create a function for filtering out dependencies that are not allowed to be built",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
55
cache/api/CHANGELOG.md
vendored
55
cache/api/CHANGELOG.md
vendored
@@ -1,5 +1,60 @@
|
||||
# @pnpm/cache.api
|
||||
|
||||
## 1100.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a23956e]
|
||||
- Updated dependencies [35d2355]
|
||||
- Updated dependencies [0721d64]
|
||||
- @pnpm/config.reader@1101.4.1
|
||||
- @pnpm/resolving.npm-resolver@1101.3.3
|
||||
- @pnpm/store.cafs@1100.1.7
|
||||
|
||||
## 1100.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3b62f9d]
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/config.reader@1101.4.0
|
||||
- @pnpm/resolving.npm-resolver@1101.3.2
|
||||
|
||||
## 1100.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/resolving.npm-resolver@1101.3.1
|
||||
|
||||
## 1100.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3687b0e]
|
||||
- Updated dependencies [ced20cb]
|
||||
- Updated dependencies [d1b340f]
|
||||
- Updated dependencies [3a54205]
|
||||
- Updated dependencies [1627943]
|
||||
- @pnpm/config.reader@1101.3.3
|
||||
- @pnpm/resolving.npm-resolver@1101.3.0
|
||||
- @pnpm/store.cafs@1100.1.6
|
||||
|
||||
## 1100.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [963861c]
|
||||
- Updated dependencies [4195766]
|
||||
- Updated dependencies [31538bf]
|
||||
- Updated dependencies [020ac45]
|
||||
- Updated dependencies [d3f8408]
|
||||
- Updated dependencies [a62f959]
|
||||
- Updated dependencies [ba2c884]
|
||||
- Updated dependencies [8df408c]
|
||||
- @pnpm/resolving.npm-resolver@1101.2.0
|
||||
- @pnpm/config.reader@1101.3.2
|
||||
- @pnpm/store.cafs@1100.1.5
|
||||
|
||||
## 1100.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
2
cache/api/package.json
vendored
2
cache/api/package.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/cache.api",
|
||||
"version": "1100.0.12",
|
||||
"version": "1100.0.17",
|
||||
"description": "API for controlling the cache",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
50
cache/commands/CHANGELOG.md
vendored
50
cache/commands/CHANGELOG.md
vendored
@@ -1,5 +1,55 @@
|
||||
# @pnpm/cache.commands
|
||||
|
||||
## 1100.0.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a23956e]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/config.reader@1101.4.1
|
||||
- @pnpm/cache.api@1100.0.17
|
||||
- @pnpm/cli.utils@1101.0.8
|
||||
|
||||
## 1100.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3b62f9d]
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/config.reader@1101.4.0
|
||||
- @pnpm/cli.utils@1101.0.7
|
||||
- @pnpm/cache.api@1100.0.16
|
||||
|
||||
## 1100.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/cache.api@1100.0.15
|
||||
|
||||
## 1100.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3687b0e]
|
||||
- Updated dependencies [ced20cb]
|
||||
- Updated dependencies [d1b340f]
|
||||
- @pnpm/config.reader@1101.3.3
|
||||
- @pnpm/cache.api@1100.0.14
|
||||
- @pnpm/cli.utils@1101.0.6
|
||||
|
||||
## 1100.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [020ac45]
|
||||
- Updated dependencies [d3f8408]
|
||||
- Updated dependencies [a62f959]
|
||||
- Updated dependencies [ba2c884]
|
||||
- Updated dependencies [8df408c]
|
||||
- @pnpm/config.reader@1101.3.2
|
||||
- @pnpm/cache.api@1100.0.13
|
||||
- @pnpm/cli.utils@1101.0.5
|
||||
|
||||
## 1100.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
2
cache/commands/package.json
vendored
2
cache/commands/package.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/cache.commands",
|
||||
"version": "1100.0.13",
|
||||
"version": "1100.0.18",
|
||||
"description": "Commands for controlling the cache",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,52 @@
|
||||
# @pnpm/cli.commands
|
||||
|
||||
## 1100.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e8b3ae1]
|
||||
- Updated dependencies [a23956e]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/workspace.projects-reader@1101.0.8
|
||||
- @pnpm/config.reader@1101.4.1
|
||||
- @pnpm/cli.utils@1101.0.8
|
||||
- @pnpm/workspace.workspace-manifest-reader@1100.0.5
|
||||
|
||||
## 1100.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3b62f9d]
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/config.reader@1101.4.0
|
||||
- @pnpm/cli.utils@1101.0.7
|
||||
- @pnpm/workspace.projects-reader@1101.0.7
|
||||
|
||||
## 1100.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3687b0e]
|
||||
- Updated dependencies [ced20cb]
|
||||
- Updated dependencies [d1b340f]
|
||||
- @pnpm/config.reader@1101.3.3
|
||||
- @pnpm/cli.utils@1101.0.6
|
||||
- @pnpm/workspace.projects-reader@1101.0.6
|
||||
- @pnpm/workspace.workspace-manifest-reader@1100.0.4
|
||||
|
||||
## 1100.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [020ac45]
|
||||
- Updated dependencies [d3f8408]
|
||||
- Updated dependencies [a62f959]
|
||||
- Updated dependencies [ba2c884]
|
||||
- Updated dependencies [8df408c]
|
||||
- @pnpm/config.reader@1101.3.2
|
||||
- @pnpm/cli.utils@1101.0.5
|
||||
- @pnpm/workspace.projects-reader@1101.0.5
|
||||
|
||||
## 1100.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/cli.commands",
|
||||
"version": "1100.0.12",
|
||||
"version": "1100.0.16",
|
||||
"description": "Commands for pnpm CLI",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,69 @@
|
||||
# @pnpm/default-reporter
|
||||
|
||||
## 1100.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a23956e]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/config.reader@1101.4.1
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/cli.meta@1100.0.5
|
||||
- @pnpm/core-loggers@1100.1.2
|
||||
- @pnpm/deps.inspection.peers-issues-renderer@1100.0.3
|
||||
|
||||
## 1100.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3b62f9d]
|
||||
- Updated dependencies [212315d]
|
||||
- @pnpm/config.reader@1101.4.0
|
||||
|
||||
## 1100.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3687b0e]
|
||||
- Updated dependencies [ced20cb]
|
||||
- Updated dependencies [d1b340f]
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/config.reader@1101.3.3
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/cli.meta@1100.0.4
|
||||
- @pnpm/core-loggers@1100.1.1
|
||||
- @pnpm/deps.inspection.peers-issues-renderer@1100.0.2
|
||||
|
||||
## 1100.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 4a79336: The lockfile verifier added in #11705 now emits `pnpm:lockfile-verification` log events (`status: 'started' | 'done'`) around the registry round-trip pass, and the default reporter renders them as a transient progress line so users can see that pnpm is doing work — on a cold registry cache the round-trip can take a noticeable beat, and the previous behavior was complete silence followed by either a long pause or an error. The cached short-circuit stays silent (no logs when no work happens), and the `done` line carries the number of distinct entries that were checked plus the elapsed time.
|
||||
|
||||
Pacquet parity: not ported — pacquet doesn't carry the lockfile verifier yet (see the parity note on #11705).
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4195766: Tightened the `minimumReleaseAge` story so the bypass becomes explicit on disk instead of silent, and removed the discover-by-loop dance for strict-mode users:
|
||||
|
||||
1. Fresh resolutions in loose mode (`minimumReleaseAgeStrict: false`) that fall back to a version newer than the cutoff auto-collect the picked `name@version` into the workspace manifest's `minimumReleaseAgeExclude`. A single info message lists the additions; entries already on the list are left alone.
|
||||
2. The post-resolution lockfile verifier introduced in #11583 now runs in loose mode too — every accepted-immature pin must be on `minimumReleaseAgeExclude`, just like strict mode requires. A lockfile produced under a weaker (or absent) policy that still has immature entries is rejected the same way strict mode would reject it.
|
||||
3. **Strict mode (interactive)** no longer aborts on the first immature pick. The resolver gathers every immature direct _and_ transitive in one pass; before peer-dependency resolution runs, pnpm prompts the user with the full list and asks whether to add them all to `minimumReleaseAgeExclude` and proceed. Approve → install continues and the workspace manifest is written at the end. Decline → resolution aborts before the lockfile or package.json is touched (tarballs already in the store stay, since the store is idempotent). This closes the [#10488](https://github.com/pnpm/pnpm/issues/10488) loop where security bumps to packages with platform-specific transitives (e.g. `next` + the `@next/swc-*` shims) made users re-run `pnpm add` once per transitive.
|
||||
4. **Strict mode (non-interactive / CI)** now aborts with the full immature set in the error message instead of the first pick. The resolver always collects every immature direct + transitive; the install command then throws `ERR_PNPM_NO_MATURE_MATCHING_VERSION` listing each entry's `name@version` and publish time. Deterministic CI behavior is preserved (same exit code, same error code), but the error pinpoints every offending entry instead of forcing the discover-by-loop dance. The expected workflow is interactive approval locally → the lockfile + workspace manifest get committed → CI runs cleanly against the populated exclude list.
|
||||
|
||||
5. **The lockfile verifier now also covers `trustPolicy: 'no-downgrade'`.** The same post-resolution gate that re-checks `minimumReleaseAge` on lockfile entries now re-runs `failIfTrustDowngraded` for every npm-registry entry whose name isn't on `trustPolicyExclude`. The two checks share a single full-metadata fetch per package, so the extra coverage doesn't cost an extra round trip when both policies are active. Resolver-time trust checks still run as before — this just closes the gap when an entry bypasses resolution (peek path, `--frozen-lockfile`, restored CI cache).
|
||||
|
||||
Pacquet parity: not ported — pacquet's `minimumReleaseAge` policy is itself only stubbed today (see `pacquet/crates/package-manager/src/version_policy.rs`). The auto-exclude, loose-mode verifier, prompt, and the new trust-policy verifier check will travel with the broader policy port whenever that happens.
|
||||
|
||||
- Updated dependencies [020ac45]
|
||||
- Updated dependencies [d3f8408]
|
||||
- Updated dependencies [4a79336]
|
||||
- Updated dependencies [a62f959]
|
||||
- Updated dependencies [ba2c884]
|
||||
- Updated dependencies [8df408c]
|
||||
- @pnpm/config.reader@1101.3.2
|
||||
- @pnpm/core-loggers@1100.1.0
|
||||
|
||||
## 1100.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/cli.default-reporter",
|
||||
"version": "1100.1.2",
|
||||
"version": "1100.2.3",
|
||||
"description": "The default reporter of pnpm",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -127,6 +127,7 @@ export function toOutput$ (
|
||||
const deprecationPushStream = new Rx.Subject<logs.DeprecationLog>()
|
||||
const summaryPushStream = new Rx.Subject<logs.SummaryLog>()
|
||||
const lifecyclePushStream = new Rx.Subject<logs.LifecycleLog>()
|
||||
const lockfileVerificationPushStream = new Rx.Subject<logs.LockfileVerificationLog>()
|
||||
const statsPushStream = new Rx.Subject<logs.StatsLog>()
|
||||
const packageImportMethodPushStream = new Rx.Subject<logs.PackageImportMethodLog>()
|
||||
const installCheckPushStream = new Rx.Subject<logs.InstallCheckLog>()
|
||||
@@ -170,6 +171,9 @@ export function toOutput$ (
|
||||
case 'pnpm:lifecycle':
|
||||
lifecyclePushStream.next(log)
|
||||
break
|
||||
case 'pnpm:lockfile-verification':
|
||||
lockfileVerificationPushStream.next(log)
|
||||
break
|
||||
case 'pnpm:stats':
|
||||
statsPushStream.next(log)
|
||||
break
|
||||
@@ -243,6 +247,7 @@ export function toOutput$ (
|
||||
ignoredScripts: Rx.from(ignoredScriptsPushStream),
|
||||
lifecycle: Rx.from(lifecyclePushStream),
|
||||
link: Rx.from(linkPushStream),
|
||||
lockfileVerification: Rx.from(lockfileVerificationPushStream),
|
||||
other,
|
||||
packageImportMethod: Rx.from(packageImportMethodPushStream),
|
||||
packageManifest: Rx.from(packageManifestPushStream),
|
||||
|
||||
@@ -72,7 +72,12 @@ function getErrorInfo (logObj: Log, config?: Config): ErrorInfo | null {
|
||||
return { title: err.message, body: 'If you cannot fix this registry issue, then set "resolution-mode" to "highest".' }
|
||||
case 'ERR_PNPM_NO_MATCHING_VERSION':
|
||||
case 'ERR_PNPM_NO_MATURE_MATCHING_VERSION':
|
||||
return formatNoMatchingVersion(err, logObj as unknown as { packageMeta: PackageMeta, immatureVersion?: string })
|
||||
// ERR_PNPM_NO_MATURE_MATCHING_VERSION used to come from the resolver
|
||||
// with `packageMeta` attached; it now comes from the install / dlx /
|
||||
// self-update callers as a plain PnpmError once the resolver has
|
||||
// surfaced the violations. `packageMeta` may be undefined, in which
|
||||
// case the formatter falls back to the bare title+message.
|
||||
return formatNoMatchingVersion(err, logObj as unknown as { packageMeta?: PackageMeta })
|
||||
case 'ERR_PNPM_RECURSIVE_FAIL':
|
||||
return formatRecursiveCommandSummary(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
case 'ERR_PNPM_BAD_TARBALL_SIZE':
|
||||
@@ -134,11 +139,18 @@ interface PackageMeta {
|
||||
time?: Record<string, string>
|
||||
}
|
||||
|
||||
function formatNoMatchingVersion (err: Error, msg: { packageMeta: PackageMeta, immatureVersion?: string }) {
|
||||
const meta: PackageMeta = msg.packageMeta
|
||||
function formatNoMatchingVersion (err: Error, msg: { packageMeta?: PackageMeta }) {
|
||||
// Errors raised by the install/dlx/self-update layer after the resolver
|
||||
// surfaces violations may not carry the original packageMeta. In that
|
||||
// case the error message alone already names every offending entry,
|
||||
// so we just echo it through without the registry-metadata appendix.
|
||||
const meta = msg.packageMeta
|
||||
if (!meta) {
|
||||
return { title: err.message }
|
||||
}
|
||||
const latestVersion = meta['dist-tags'].latest
|
||||
let output = `The latest release of ${meta.name} is "${latestVersion}".`
|
||||
const latestTime = msg.packageMeta.time?.[latestVersion]
|
||||
const latestTime = meta.time?.[latestVersion]
|
||||
if (latestTime) {
|
||||
output += ` Published at ${stringifyDate(latestTime)}`
|
||||
}
|
||||
@@ -150,7 +162,7 @@ function formatNoMatchingVersion (err: Error, msg: { packageMeta: PackageMeta, i
|
||||
if (tag !== 'latest') {
|
||||
const version = meta['dist-tags'][tag]
|
||||
output += ` * ${tag}: ${version}`
|
||||
const time = msg.packageMeta.time?.[version]
|
||||
const time = meta.time?.[version]
|
||||
if (time) {
|
||||
output += ` published at ${stringifyDate(time)}`
|
||||
}
|
||||
@@ -161,10 +173,6 @@ function formatNoMatchingVersion (err: Error, msg: { packageMeta: PackageMeta, i
|
||||
|
||||
output += `${EOL}If you need the full list of all ${Object.keys(meta.versions).length} published versions run "pnpm view ${meta.name} versions".`
|
||||
|
||||
if (msg.immatureVersion) {
|
||||
output += `${EOL}${EOL}If you want to install the matched version ignoring the time it was published, you can add the package name to the minimumReleaseAgeExclude setting. Read more about it: https://pnpm.io/settings#minimumreleaseageexclude`
|
||||
}
|
||||
|
||||
return {
|
||||
title: err.message,
|
||||
body: output,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { reportIgnoredBuilds } from './reportIgnoredBuilds.js'
|
||||
import { reportInstallChecks } from './reportInstallChecks.js'
|
||||
import { reportInstallingConfigDeps } from './reportInstallingConfigDeps.js'
|
||||
import { reportLifecycleScripts } from './reportLifecycleScripts.js'
|
||||
import { reportLockfileVerification } from './reportLockfileVerification.js'
|
||||
import { LOG_LEVEL_NUMBER, reportMisc } from './reportMisc.js'
|
||||
import { reportPeerDependencyIssues } from './reportPeerDependencyIssues.js'
|
||||
import { reportProgress } from './reportProgress.js'
|
||||
@@ -41,6 +42,7 @@ export function reporterForClient (
|
||||
deprecation: Rx.Observable<logs.DeprecationLog>
|
||||
summary: Rx.Observable<logs.SummaryLog>
|
||||
lifecycle: Rx.Observable<logs.LifecycleLog>
|
||||
lockfileVerification: Rx.Observable<logs.LockfileVerificationLog>
|
||||
stats: Rx.Observable<logs.StatsLog>
|
||||
installCheck: Rx.Observable<logs.InstallCheckLog>
|
||||
installingConfigDeps: Rx.Observable<logs.InstallingConfigDepsLog>
|
||||
@@ -130,6 +132,10 @@ export function reporterForClient (
|
||||
}),
|
||||
reportInstallChecks(log$.installCheck, { cwd }),
|
||||
reportInstallingConfigDeps(log$.installingConfigDeps),
|
||||
reportLockfileVerification(log$.lockfileVerification, {
|
||||
cwd,
|
||||
workspaceDir: opts.pnpmConfig?.workspaceDir,
|
||||
}),
|
||||
reportScope(log$.scope, { isRecursive: opts.isRecursive, cmd: opts.cmd }),
|
||||
reportSkippedOptionalDependencies(log$.skippedOptionalDependency, { cwd }),
|
||||
reportHooks(log$.hook, { cwd, isRecursive: opts.isRecursive }),
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import type { LockfileVerificationLog } from '@pnpm/core-loggers'
|
||||
import chalk from 'chalk'
|
||||
import normalize from 'normalize-path'
|
||||
import prettyMs from 'pretty-ms'
|
||||
import * as Rx from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
export interface ReportLockfileVerificationOptions {
|
||||
cwd: string
|
||||
/**
|
||||
* The workspace root, when one exists. Used as the "expected"
|
||||
* location for the lockfile — when the lockfile lives there, the
|
||||
* path is implied and we don't repeat it in the rendered message.
|
||||
* Falls back to `cwd` for single-project installs.
|
||||
*/
|
||||
workspaceDir?: string
|
||||
}
|
||||
|
||||
export function reportLockfileVerification (
|
||||
lockfileVerification$: Rx.Observable<LockfileVerificationLog>,
|
||||
opts: ReportLockfileVerificationOptions
|
||||
): Rx.Observable<Rx.Observable<{ msg: string }>> {
|
||||
const expectedDir = opts.workspaceDir ?? opts.cwd
|
||||
// A single inner observable so the `done` message overwrites the
|
||||
// transient `started` message in ansi-diff mode. In appendOnly mode
|
||||
// both lines are printed.
|
||||
return Rx.of(lockfileVerification$.pipe(
|
||||
map((log) => {
|
||||
const path_ = formatLockfilePath(log.lockfilePath, opts.cwd, expectedDir)
|
||||
const entries = `${log.entries} ${log.entries === 1 ? 'entry' : 'entries'}`
|
||||
switch (log.status) {
|
||||
case 'started':
|
||||
return {
|
||||
msg: `${chalk.cyan('?')} Verifying lockfile${path_} against supply-chain policies (${entries})...`,
|
||||
}
|
||||
case 'done':
|
||||
return {
|
||||
msg: `${chalk.green('✓')} Lockfile${path_} passes supply-chain policies (${entries} in ${prettyMs(log.elapsedMs)})`,
|
||||
}
|
||||
case 'failed':
|
||||
// Brief one-liner so the transient `started` frame doesn't
|
||||
// stay on screen above the detailed PnpmError block that the
|
||||
// error reporter prints next.
|
||||
return {
|
||||
msg: `${chalk.red('✗')} Lockfile${path_} failed supply-chain policy check (${entries} in ${prettyMs(log.elapsedMs)})`,
|
||||
}
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
// Returns a leading-space-prefixed `at <path>` suffix only when the
|
||||
// lockfile sits outside the obvious project/workspace root — otherwise
|
||||
// the path is implied and printing it would just add noise to every
|
||||
// install. Empty string when the path is omitted or matches the
|
||||
// expected location.
|
||||
//
|
||||
// Uses `path.relative` rather than a strict `===` between
|
||||
// `path.dirname(lockfilePath)` and `expectedDir`: relative path
|
||||
// computation normalizes slash direction and trailing separators, so
|
||||
// a workspaceDir like `C:/repo/` correctly matches a lockfilePath at
|
||||
// `C:\repo\pnpm-lock.yaml` on Windows. The lockfile is considered
|
||||
// "inside the expected dir" when the relative path is a bare file
|
||||
// name (no separator) that doesn't escape upward.
|
||||
function formatLockfilePath (
|
||||
lockfilePath: string | undefined,
|
||||
cwd: string,
|
||||
expectedDir: string
|
||||
): string {
|
||||
if (lockfilePath == null) return ''
|
||||
const fromExpected = path.relative(expectedDir, lockfilePath)
|
||||
const isDirectChild = !fromExpected.includes(path.sep) && !fromExpected.startsWith('..')
|
||||
if (isDirectChild) return ''
|
||||
return ` at ${normalize(path.relative(cwd, lockfilePath))}`
|
||||
}
|
||||
150
cli/default-reporter/test/reportingLockfileVerification.ts
Normal file
150
cli/default-reporter/test/reportingLockfileVerification.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import path from 'node:path'
|
||||
import { stripVTControlCharacters as stripAnsi } from 'node:util'
|
||||
|
||||
import { expect, test } from '@jest/globals'
|
||||
import { toOutput$ } from '@pnpm/cli.default-reporter'
|
||||
import type { Config, ConfigContext } from '@pnpm/config.reader'
|
||||
import { lockfileVerificationLogger } from '@pnpm/core-loggers'
|
||||
import { createStreamParser } from '@pnpm/logger'
|
||||
import { firstValueFrom, take, toArray } from 'rxjs'
|
||||
|
||||
test('prints lockfile verification in-progress and completion messages', async () => {
|
||||
const cwd = '/repo'
|
||||
const output$ = toOutput$({
|
||||
context: {
|
||||
argv: ['install'],
|
||||
config: { dir: cwd } as Config & ConfigContext,
|
||||
},
|
||||
streamParser: createStreamParser(),
|
||||
})
|
||||
|
||||
// Subscribe before emitting so we capture both the started and the
|
||||
// done frame in ansi-diff mode.
|
||||
const frames = firstValueFrom(output$.pipe(take(2), toArray()))
|
||||
|
||||
const lockfilePath = path.join(cwd, 'pnpm-lock.yaml')
|
||||
lockfileVerificationLogger.debug({ status: 'started', entries: 234, lockfilePath })
|
||||
lockfileVerificationLogger.debug({
|
||||
status: 'done',
|
||||
entries: 234,
|
||||
elapsedMs: 1234,
|
||||
lockfilePath,
|
||||
})
|
||||
|
||||
const [started, done] = await frames
|
||||
expect(stripAnsi(started)).toBe('? Verifying lockfile against supply-chain policies (234 entries)...')
|
||||
expect(stripAnsi(done)).toBe('✓ Lockfile passes supply-chain policies (234 entries in 1.2s)')
|
||||
})
|
||||
|
||||
test('uses singular noun for one entry', async () => {
|
||||
const output$ = toOutput$({
|
||||
context: { argv: ['install'] },
|
||||
streamParser: createStreamParser(),
|
||||
})
|
||||
|
||||
const frames = firstValueFrom(output$.pipe(take(2), toArray()))
|
||||
|
||||
lockfileVerificationLogger.debug({ status: 'started', entries: 1 })
|
||||
lockfileVerificationLogger.debug({
|
||||
status: 'done',
|
||||
entries: 1,
|
||||
elapsedMs: 42,
|
||||
})
|
||||
|
||||
const [started, done] = await frames
|
||||
expect(stripAnsi(started)).toBe('? Verifying lockfile against supply-chain policies (1 entry)...')
|
||||
expect(stripAnsi(done)).toBe('✓ Lockfile passes supply-chain policies (1 entry in 42ms)')
|
||||
})
|
||||
|
||||
test('prints relative path when lockfile lives outside the workspace root', async () => {
|
||||
const cwd = '/repo/packages/app'
|
||||
const workspaceDir = '/repo'
|
||||
const output$ = toOutput$({
|
||||
context: {
|
||||
argv: ['install'],
|
||||
config: { dir: cwd, workspaceDir } as Config & ConfigContext,
|
||||
},
|
||||
streamParser: createStreamParser(),
|
||||
})
|
||||
|
||||
const frames = firstValueFrom(output$.pipe(take(2), toArray()))
|
||||
|
||||
// Lockfile lives in a sibling dir, not at the workspace root.
|
||||
const lockfilePath = '/repo/locks/pnpm-lock.yaml'
|
||||
lockfileVerificationLogger.debug({ status: 'started', entries: 5, lockfilePath })
|
||||
lockfileVerificationLogger.debug({
|
||||
status: 'done',
|
||||
entries: 5,
|
||||
elapsedMs: 200,
|
||||
lockfilePath,
|
||||
})
|
||||
|
||||
const [started, done] = await frames
|
||||
expect(stripAnsi(started)).toBe('? Verifying lockfile at ../../locks/pnpm-lock.yaml against supply-chain policies (5 entries)...')
|
||||
expect(stripAnsi(done)).toBe('✓ Lockfile at ../../locks/pnpm-lock.yaml passes supply-chain policies (5 entries in 200ms)')
|
||||
})
|
||||
|
||||
test('does not print path when running from workspace subdir and lockfile is at workspace root', async () => {
|
||||
const cwd = '/repo/packages/app'
|
||||
const workspaceDir = '/repo'
|
||||
const output$ = toOutput$({
|
||||
context: {
|
||||
argv: ['install'],
|
||||
config: { dir: cwd, workspaceDir } as Config & ConfigContext,
|
||||
},
|
||||
streamParser: createStreamParser(),
|
||||
})
|
||||
|
||||
const frames = firstValueFrom(output$.pipe(take(1), toArray()))
|
||||
|
||||
const lockfilePath = path.join(workspaceDir, 'pnpm-lock.yaml')
|
||||
lockfileVerificationLogger.debug({ status: 'started', entries: 10, lockfilePath })
|
||||
|
||||
const [started] = await frames
|
||||
expect(stripAnsi(started)).toBe('? Verifying lockfile against supply-chain policies (10 entries)...')
|
||||
})
|
||||
|
||||
test('suppresses path when workspaceDir has a trailing separator', async () => {
|
||||
const cwd = '/repo'
|
||||
// Workspace dir with a trailing slash — strict === against
|
||||
// path.dirname(lockfilePath) would mismatch; path.relative normalizes.
|
||||
const workspaceDir = '/repo/'
|
||||
const output$ = toOutput$({
|
||||
context: {
|
||||
argv: ['install'],
|
||||
config: { dir: cwd, workspaceDir } as Config & ConfigContext,
|
||||
},
|
||||
streamParser: createStreamParser(),
|
||||
})
|
||||
|
||||
const frames = firstValueFrom(output$.pipe(take(1), toArray()))
|
||||
|
||||
lockfileVerificationLogger.debug({
|
||||
status: 'started',
|
||||
entries: 3,
|
||||
lockfilePath: '/repo/pnpm-lock.yaml',
|
||||
})
|
||||
|
||||
const [started] = await frames
|
||||
expect(stripAnsi(started)).toBe('? Verifying lockfile against supply-chain policies (3 entries)...')
|
||||
})
|
||||
|
||||
test('emits a brief failure line on failed status', async () => {
|
||||
const output$ = toOutput$({
|
||||
context: { argv: ['install'] },
|
||||
streamParser: createStreamParser(),
|
||||
})
|
||||
|
||||
const frames = firstValueFrom(output$.pipe(take(2), toArray()))
|
||||
|
||||
lockfileVerificationLogger.debug({ status: 'started', entries: 12 })
|
||||
lockfileVerificationLogger.debug({
|
||||
status: 'failed',
|
||||
entries: 12,
|
||||
elapsedMs: 800,
|
||||
})
|
||||
|
||||
const [started, failed] = await frames
|
||||
expect(stripAnsi(started)).toBe('? Verifying lockfile against supply-chain policies (12 entries)...')
|
||||
expect(stripAnsi(failed)).toBe('✗ Lockfile failed supply-chain policy check (12 entries in 800ms)')
|
||||
})
|
||||
@@ -1,5 +1,19 @@
|
||||
# @pnpm/cli-meta
|
||||
|
||||
## 1100.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/types@1101.2.0
|
||||
|
||||
## 1100.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/types@1101.1.1
|
||||
|
||||
## 1100.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pnpm/cli.meta",
|
||||
"version": "1100.0.3",
|
||||
"version": "1100.0.5",
|
||||
"description": "Reads the metainfo of the currently running pnpm instance",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
|
||||
@@ -1,5 +1,44 @@
|
||||
# @pnpm/cli-utils
|
||||
|
||||
## 1101.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a456dc7]
|
||||
- Updated dependencies [35d2355]
|
||||
- @pnpm/workspace.project-manifest-reader@1100.0.9
|
||||
- @pnpm/types@1101.2.0
|
||||
- @pnpm/config.package-is-installable@1100.0.7
|
||||
- @pnpm/cli.meta@1100.0.5
|
||||
- @pnpm/pkg-manifest.utils@1100.2.1
|
||||
|
||||
## 1101.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d7da112]
|
||||
- @pnpm/workspace.project-manifest-reader@1100.0.8
|
||||
|
||||
## 1101.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1627943]
|
||||
- Updated dependencies [64afc92]
|
||||
- @pnpm/pkg-manifest.utils@1100.2.0
|
||||
- @pnpm/types@1101.1.1
|
||||
- @pnpm/workspace.project-manifest-reader@1100.0.7
|
||||
- @pnpm/cli.meta@1100.0.4
|
||||
- @pnpm/config.package-is-installable@1100.0.6
|
||||
|
||||
## 1101.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @pnpm/config.package-is-installable@1100.0.5
|
||||
- @pnpm/pkg-manifest.utils@1100.1.4
|
||||
- @pnpm/workspace.project-manifest-reader@1100.0.6
|
||||
|
||||
## 1101.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user