pnpm 12.0.0-rc.4 → 12.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/THIRD-PARTY-NOTICES.md +42 -0
- package/bin/pnpm.mjs +141 -0
- package/bin/pnpx.mjs +9 -0
- package/dist/node_modules/.bin/get-pnpm +50 -0
- package/dist/node_modules/.package-map.json +1 -1
- package/dist/node_modules/.pnpm-workspace-state-v1.json +5 -4
- package/dist/node_modules/get-pnpm/bin/get-pnpm.js +11 -0
- package/dist/node_modules/get-pnpm/lib/downloadExecutable.js +99 -0
- package/dist/node_modules/get-pnpm/lib/extractTarball.js +23 -0
- package/dist/node_modules/get-pnpm/lib/extractTarballMember.js +128 -0
- package/dist/node_modules/get-pnpm/lib/index.js +208 -0
- package/dist/node_modules/get-pnpm/lib/npmSigningKeys.js +23 -0
- package/dist/node_modules/get-pnpm/lib/platformPackageName.js +60 -0
- package/dist/node_modules/get-pnpm/lib/registry.js +120 -0
- package/dist/node_modules/get-pnpm/lib/resolveVersion.js +36 -0
- package/dist/node_modules/get-pnpm/lib/sameFileContents.js +45 -0
- package/dist/node_modules/get-pnpm/lib/verifySignature.js +51 -0
- package/dist/node_modules/get-pnpm/package.json +45 -0
- package/dist/node_modules/node-gyp/.release-please-manifest.json +1 -1
- package/dist/node_modules/node-gyp/gyp/.release-please-manifest.json +1 -1
- package/dist/node_modules/node-gyp/gyp/pyproject.toml +4 -4
- package/dist/node_modules/node-gyp/lib/download.js +3 -3
- package/dist/node_modules/node-gyp/package.json +1 -1
- package/install.js +20 -97
- package/native-binary.mjs +116 -0
- package/package.json +13 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# pnpm
|
|
2
2
|
|
|
3
|
+
## 12.0.0-rc.6
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added `pnpm cache path`, which prints the directory pnpm uses for its metadata cache. CI setups can use it to cache that directory — including the lockfile verification log, which lets a job skip re-checking an unchanged lockfile against the configured supply-chain policies.
|
|
8
|
+
|
|
9
|
+
- pnpm installs the other package managers now, not just itself: npm, Yarn Classic, Yarn Berry, Yarn 6 (`yarnpkg/zpm`), and Bun. Each is resolved and fetched through the trusted package-manager registries, and an npm-published one is verified against npm's signature for its exact version before it is executed.
|
|
10
|
+
|
|
11
|
+
Three things use it:
|
|
12
|
+
|
|
13
|
+
- A git-hosted dependency is prepared with the package manager it asks for. Its `packageManager` / `devEngines.packageManager` pin is honored, and a `yarn.lock` written by Yarn Classic no longer gets installed by Yarn Berry. pnpm provides that package manager when the dependency pinned a version, or when the host cannot satisfy what the dependency needs — so a repository built with Yarn now installs on a machine that has only pnpm, while a host that already has a suitable one keeps using its own.
|
|
14
|
+
- `pnpm dlx` (`pnx`) runs one of them for a single command: `pnx yarn@4 install`, `pnx npm@11 ci`, `pnx bun@1.3.0 install`. Naming a package manager, or a runtime (`node`, `deno`, `bun`), there now provisions the real thing instead of installing the npm package that shares its name — unless the specifier locates a package rather than asking for a released version (`pnx yarn@npm:yarn@1.22.22`, `pnx yarn@yarnpkg/berry`), which installs what it names — `pnx yarn@4` was previously a missing version, since Yarn 4 is published as `@yarnpkg/cli-dist`, and `pnx node@22` now runs that Node.js release rather than a wrapper that downloads one. `--package` naming a package manager picks which of its commands to run, so `pnx --package npm@11 npx create-something` runs that npm's `npx`.
|
|
15
|
+
- `pnpm shim add yarn` links a `yarn` command that runs whatever version the current project pins, and `pnpm shim rm` / `pnpm shim ls` manage those shims. It works for any package, not only package managers. Shims are never created as a side effect of `pnpm setup` or an install — a shim shadows the rest of your `PATH`, so pnpm only writes one when asked.
|
|
16
|
+
|
|
17
|
+
Installing a package manager globally (`pnpm add -g yarn`) now makes it follow a project's pin too, the way a globally installed Node.js already follows `devEngines.runtime`: the pinned version runs where a project pins one, and the globally installed copy is the fallback everywhere else. An explicit `globalShims` entry, including `false`, is left as you set it.
|
|
18
|
+
|
|
19
|
+
`pnpm add` follows the same rule about what a name means. `pnpm add -g yarn@4` installs Yarn Berry — it used to fail, because npm's `yarn` package stops at Classic — and `pnpm add -g node@22` / `pnpm add -g deno@2` install that Node.js or Deno release rather than a wrapper package that downloads one. In a project, naming a package manager records which one the project uses instead of installing it as a dependency, and naming a runtime records it under `engines.runtime` as `node@runtime:22` already did.
|
|
20
|
+
|
|
21
|
+
The declaration goes where the package manager reads it. Yarn is started from a project pin by corepack, which reads only `packageManager` and only accepts an exact version there, so `pnpm add yarn@4` resolves the line and writes `"packageManager": "yarn@4.18.0"` — the same thing `corepack use yarn@4` writes, down to the `+sha512.…` integrity for the Yarn Classic line that corepack pins its tarball with. Every other package manager is recorded in `devEngines.packageManager`, which holds a range. Only one of the two fields is ever left behind: they declare the same thing, and corepack refuses to run a project whose declarations disagree.
|
|
22
|
+
|
|
23
|
+
A JavaScript package manager on a machine without Node.js gets a managed LTS runtime to run on.
|
|
24
|
+
|
|
25
|
+
What changes for a project coming from v11: `pnpm add yarn` records the project's package manager instead of installing the npm package that shares the name (that package is still reachable as `pnpm add yarn@npm:yarn@1.22.22`), `pnpm add -g yarn` installs the current Yarn line rather than Classic, `pnpm add -g node` / `pnpm add -g deno` and `pnx node` / `pnx deno` install a Node.js or Deno release rather than a wrapper package, and a globally installed package manager defers to a project's pin where there is one.
|
|
26
|
+
|
|
27
|
+
- Resolving a Node.js runtime version (`devEngines.runtime` / `runtime:` specifiers) is now much faster: the per-version release metadata is cached in the pnpm cache directory after its signature is verified, and an exact stable version such as `runtime:22.23.2` no longer downloads the Node.js release index. A pinned runtime whose metadata was fetched once resolves without any network access, which removes the noticeable delay on the first `node` invocation in a project pinning an already-downloaded runtime [#13899](https://github.com/pnpm/pnpm/issues/13899).
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- Corepack can run pnpm 12 again [#13018](https://github.com/pnpm/pnpm/issues/13018). Corepack installs no dependencies and runs no lifecycle scripts, so the native binary that the `pnpm` package normally receives from its platform-specific optional dependency was never there, and `corepack use pnpm@next-12` failed with `MODULE_NOT_FOUND`. The package now ships the `bin/pnpm.mjs` and `bin/pnpx.mjs` entry points Corepack looks for; they fetch the pinned native binary on first use — verified against npm's signature and checksum, honouring `COREPACK_NPM_REGISTRY` and the rest of Corepack's registry environment — and hand over to it. Installing pnpm with a package manager is unaffected and still runs the binary directly, with no Node.js startup in between.
|
|
32
|
+
|
|
33
|
+
- With a configured `pnprServer`, `pnpm install` skips the server exchanges it does not need, closing the gap where an up-to-date project paid a full resolve round trip that a direct install answered locally [pnpm/pnpm#13904](https://github.com/pnpm/pnpm/issues/13904):
|
|
34
|
+
|
|
35
|
+
- The repeat-install "Already up to date" fast path now runs with a pnpr server configured.
|
|
36
|
+
- An install whose `pnpm-lock.yaml` still satisfies every manifest skips the server resolve exchange and materializes `node_modules` from the on-disk lockfile.
|
|
37
|
+
- The input-lockfile verification round trip is skipped when the local `lockfile-verified.jsonl` cache already covers the lockfile under the current policy; server-verified and server-resolved lockfiles are now recorded into that cache.
|
|
38
|
+
- Changing the `trustPolicy*`, `minimumReleaseAgeStrict`, or `minimumReleaseAgeExclude` settings now invalidates the repeat-install fast path, matching the TypeScript CLI's workspace-state check.
|
|
39
|
+
|
|
40
|
+
- The published packages now ship a `THIRD-PARTY-NOTICES.md` file carrying the BSD 2-Clause license of the Yarn code that pnpm's hoisted-layout algorithm and built-in package-compatibility database are derived from.
|
|
41
|
+
|
|
42
|
+
## 12.0.0-rc.5
|
|
43
|
+
|
|
44
|
+
### Minor Changes
|
|
45
|
+
|
|
46
|
+
- **Breaking change.** Dependency cycles are now broken canonically during peer resolution: the members of each cycle are ordered by package id, and the edges that close a cycle are always cut at the same place, no matter where the installation walks into the cycle from. Previously the cut depended on the walk path, so installing the same dependencies could produce different lockfiles depending on importer order or resolution order [#13846](https://github.com/pnpm/pnpm/issues/13846), and a peer-resolution verdict computed for one occurrence of a cyclic package could be wrongly reused at another [#13865](https://github.com/pnpm/pnpm/issues/13865).
|
|
47
|
+
|
|
48
|
+
With canonical cycle breaking the lockfile is a pure function of the dependency graph: repeated installs, reordered importers, and reordered dependencies all produce byte-identical lockfiles. Peer dependencies of packages inside a cycle keep nearest-wins resolution along the canonical order, and a dependency edge that closes a cycle references an occurrence of its target resolved at the importer level. On large cycle-heavy workspaces peer resolution is 2–3× faster, uses about 25% less memory, and produces a substantially smaller lockfile (fewer redundant peer variants).
|
|
49
|
+
|
|
50
|
+
Existing lockfiles keep working: headless (`--frozen-lockfile`) installs consume them unchanged, and installs that skip resolution leave them untouched. The first install that actually re-resolves (for example after a dependency change) re-keys walk-order-dependent peer variants of cyclic packages once.
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- Auto-installed peer dependencies are no longer resolved to their lowest satisfying versions under `resolutionMode: lowest-direct` or `time-based`. A hoisted peer is not a dependency the project declares, so it resolves like a transitive dependency — to the highest version satisfying the peer range (under `time-based`, the highest within the publish-date cutoff) [#13871](https://github.com/pnpm/pnpm/pull/13871).
|
|
55
|
+
|
|
56
|
+
- The resolved dependency graph and lockfile no longer depend on the order in which workspace projects are listed or discovered: importers are processed in project-id order, so reordering the `packages` globs in `pnpm-workspace.yaml` (or any other change to project listing order) produces a byte-identical lockfile [#13846](https://github.com/pnpm/pnpm/issues/13846). This also makes auto-installed peer placement, deprecation-warning attribution, and cycle back-edge bindings a function of the project set alone.
|
|
57
|
+
|
|
58
|
+
- Fixed non-deterministic lockfiles on cold installs of projects with cyclic peer dependencies: resolved peer variants could silently drop from the lockfile depending on traversal order [#13846](https://github.com/pnpm/pnpm/issues/13846), [#13865](https://github.com/pnpm/pnpm/issues/13865).
|
|
59
|
+
|
|
60
|
+
- A lockfile entry whose resolution is unchanged no longer loses its recorded `deprecated` marker when a registry serves the package's metadata inconsistently — re-resolving to the same version keeps the deprecation instead of silently dropping the line [#13846](https://github.com/pnpm/pnpm/issues/13846).
|
|
61
|
+
|
|
62
|
+
- `pnpm update` now writes the new version range back to `package.json` (and to the `catalog:` entry a dependency points at), instead of only updating the lockfile [#13879](https://github.com/pnpm/pnpm/issues/13879). The range operator the dependency already declared is preserved, and a dependency declared through a dist-tag (`"foo": "latest"`) keeps tracking the tag under both `pnpm update` and `pnpm update --latest`.
|
|
63
|
+
|
|
3
64
|
## 12.0.0-rc.4
|
|
4
65
|
|
|
5
66
|
### Minor Changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This product includes software derived from third-party sources. The
|
|
4
|
+
components below, and their license terms, are listed here in satisfaction
|
|
5
|
+
of those terms.
|
|
6
|
+
|
|
7
|
+
## Yarn (`@yarnpkg/nm`, `@yarnpkg/extensions`)
|
|
8
|
+
|
|
9
|
+
- Source: <https://github.com/yarnpkg/berry>
|
|
10
|
+
- License: BSD 2-Clause
|
|
11
|
+
|
|
12
|
+
The `nodeLinker: hoisted` layout is produced by a Rust port of the hoisting
|
|
13
|
+
algorithm in `@yarnpkg/nm`, and the built-in package-compatibility database
|
|
14
|
+
is a copy of the one in `@yarnpkg/extensions`.
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
BSD 2-Clause License
|
|
18
|
+
|
|
19
|
+
Copyright (c) 2016-present, Yarn Contributors.
|
|
20
|
+
All rights reserved.
|
|
21
|
+
|
|
22
|
+
Redistribution and use in source and binary forms, with or without
|
|
23
|
+
modification, are permitted provided that the following conditions are met:
|
|
24
|
+
|
|
25
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
26
|
+
list of conditions and the following disclaimer.
|
|
27
|
+
|
|
28
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
29
|
+
this list of conditions and the following disclaimer in the documentation
|
|
30
|
+
and/or other materials provided with the distribution.
|
|
31
|
+
|
|
32
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
33
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
34
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
35
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
36
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
37
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
38
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
39
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
40
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
41
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
42
|
+
```
|
package/bin/pnpm.mjs
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Corepack's entry point into pnpm. Corepack hardcodes `./bin/pnpm.mjs` and
|
|
3
|
+
// `./bin/pnpx.mjs` for every pnpm >=11 (see its `config.json`) and loads them
|
|
4
|
+
// into its own Node.js process, which a native executable cannot be loaded into.
|
|
5
|
+
//
|
|
6
|
+
// Nothing else runs this file: `package.json#bin` still points at the native
|
|
7
|
+
// binary, so an ordinary `npm install -g pnpm` never pays for a Node.js startup.
|
|
8
|
+
//
|
|
9
|
+
// Corepack installs no dependencies and runs no lifecycle scripts, so the
|
|
10
|
+
// `@pnpm/exe.<target>` package that carries the binary is absent and
|
|
11
|
+
// `install.js` never ran. The binary is therefore downloaded on first use and
|
|
12
|
+
// kept next to this wrapper — where the native binary also finds the `dist/`
|
|
13
|
+
// payload it ships node-gyp in.
|
|
14
|
+
//
|
|
15
|
+
// The download itself is `get-pnpm`, the package behind https://get.pnpm.io,
|
|
16
|
+
// which already knows how to verify one; it travels in that same `dist/`
|
|
17
|
+
// payload. What is left here is Corepack's environment, which it does not know:
|
|
18
|
+
// where to download from, what credentials to use, and whose signature to trust.
|
|
19
|
+
import { Buffer } from 'node:buffer'
|
|
20
|
+
import { spawnSync } from 'node:child_process'
|
|
21
|
+
import console from 'node:console'
|
|
22
|
+
import fs from 'node:fs'
|
|
23
|
+
import os from 'node:os'
|
|
24
|
+
import path from 'node:path'
|
|
25
|
+
import process from 'node:process'
|
|
26
|
+
import { URL } from 'node:url'
|
|
27
|
+
import { readWrapperManifest, resolveInstalledBinary, wrapperDir } from '../native-binary.mjs'
|
|
28
|
+
|
|
29
|
+
// Deliberately not the `pnpm` placeholder that `install.js` overwrites: a name
|
|
30
|
+
// of its own is what tells a downloaded binary apart from the placeholder.
|
|
31
|
+
const DOWNLOADED_BINARY = path.join(
|
|
32
|
+
wrapperDir,
|
|
33
|
+
process.platform === 'win32' ? 'pnpm-native.exe' : 'pnpm-native'
|
|
34
|
+
)
|
|
35
|
+
const GET_PNPM = new URL('../dist/node_modules/get-pnpm/lib/index.js', import.meta.url)
|
|
36
|
+
const DEFAULT_REGISTRY = 'https://registry.npmjs.org'
|
|
37
|
+
|
|
38
|
+
run(await nativeBinary())
|
|
39
|
+
|
|
40
|
+
function run (binary) {
|
|
41
|
+
// Ctrl-C reaches the whole foreground process group, so the binary gets its
|
|
42
|
+
// own SIGINT; exiting here first would hand the terminal back while it is
|
|
43
|
+
// still shutting down.
|
|
44
|
+
process.on('SIGINT', () => {})
|
|
45
|
+
|
|
46
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: 'inherit' })
|
|
47
|
+
if (result.error != null) {
|
|
48
|
+
fail(`Could not run the pnpm binary at ${binary}: ${result.error.message}`)
|
|
49
|
+
}
|
|
50
|
+
process.exitCode = result.signal == null
|
|
51
|
+
? result.status ?? 1
|
|
52
|
+
: 128 + (os.constants.signals[result.signal] ?? 0)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function nativeBinary () {
|
|
56
|
+
const installed = resolveInstalledBinary()
|
|
57
|
+
if (installed != null) {
|
|
58
|
+
return installed
|
|
59
|
+
}
|
|
60
|
+
// A plain file, not merely something at that path: what a previous run left
|
|
61
|
+
// is a file, and a directory there would be spawned as if it were a binary.
|
|
62
|
+
if (fs.lstatSync(DOWNLOADED_BINARY, { throwIfNoEntry: false })?.isFile() === true) {
|
|
63
|
+
return DOWNLOADED_BINARY
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (process.env.COREPACK_ENABLE_NETWORK === '0') {
|
|
67
|
+
fail('Network access is disabled by the environment, so the pnpm binary cannot be downloaded.')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { version } = readWrapperManifest()
|
|
71
|
+
console.error(`Downloading the pnpm ${version} binary for ${process.platform}-${process.arch}...`)
|
|
72
|
+
const { downloadPnpmExecutable } = await import(GET_PNPM).catch((err) => {
|
|
73
|
+
fail(`This copy of the pnpm package is missing the downloader it needs: ${err.message}`)
|
|
74
|
+
})
|
|
75
|
+
try {
|
|
76
|
+
await downloadPnpmExecutable({
|
|
77
|
+
version,
|
|
78
|
+
registry: process.env.COREPACK_NPM_REGISTRY || DEFAULT_REGISTRY,
|
|
79
|
+
destPath: DOWNLOADED_BINARY,
|
|
80
|
+
headers: registryHeaders(),
|
|
81
|
+
...signaturePolicy(),
|
|
82
|
+
})
|
|
83
|
+
} catch (err) {
|
|
84
|
+
fail(`Could not download the pnpm ${version} binary: ${err.message}`)
|
|
85
|
+
}
|
|
86
|
+
return DOWNLOADED_BINARY
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Credentials for the registry, read the way Corepack reads its own. `get-pnpm`
|
|
91
|
+
* keeps them on that registry's origin, so a download host it names never
|
|
92
|
+
* receives them.
|
|
93
|
+
*/
|
|
94
|
+
function registryHeaders () {
|
|
95
|
+
const { COREPACK_NPM_TOKEN, COREPACK_NPM_USERNAME, COREPACK_NPM_PASSWORD } = process.env
|
|
96
|
+
if (COREPACK_NPM_TOKEN) {
|
|
97
|
+
return { authorization: `Bearer ${COREPACK_NPM_TOKEN}` }
|
|
98
|
+
}
|
|
99
|
+
if (COREPACK_NPM_USERNAME && COREPACK_NPM_PASSWORD) {
|
|
100
|
+
const credentials = Buffer.from(`${COREPACK_NPM_USERNAME}:${COREPACK_NPM_PASSWORD}`, 'utf8')
|
|
101
|
+
return { authorization: `Basic ${credentials.toString('base64')}` }
|
|
102
|
+
}
|
|
103
|
+
return undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whose signature over the download to trust, following `COREPACK_INTEGRITY_KEYS`
|
|
108
|
+
* exactly as Corepack does: npm's own keys when it is unset, the keys it names
|
|
109
|
+
* when it holds a key set, and no signature check at all when it is `0` or
|
|
110
|
+
* empty — which is the state a registry that re-publishes packages, and
|
|
111
|
+
* therefore carries no npm signatures, already has to be in for Corepack to
|
|
112
|
+
* have installed this wrapper from it.
|
|
113
|
+
*/
|
|
114
|
+
function signaturePolicy () {
|
|
115
|
+
const configured = process.env.COREPACK_INTEGRITY_KEYS
|
|
116
|
+
if (configured == null) {
|
|
117
|
+
return {}
|
|
118
|
+
}
|
|
119
|
+
if (configured === '' || configured === '0') {
|
|
120
|
+
return { verifySignature: false }
|
|
121
|
+
}
|
|
122
|
+
let keys
|
|
123
|
+
try {
|
|
124
|
+
keys = JSON.parse(configured).npm
|
|
125
|
+
} catch (err) {
|
|
126
|
+
fail(`COREPACK_INTEGRITY_KEYS is not readable as JSON: ${err.message}`)
|
|
127
|
+
}
|
|
128
|
+
// An absent or malformed `npm` entry would otherwise be passed on as no keys
|
|
129
|
+
// at all, which falls back to npm's own — the opposite of what setting the
|
|
130
|
+
// variable asked for. An empty set is left alone: it names no key to trust,
|
|
131
|
+
// and Corepack reads it the same way, so every download is refused.
|
|
132
|
+
if (!Array.isArray(keys)) {
|
|
133
|
+
fail('COREPACK_INTEGRITY_KEYS holds no "npm" key set to verify the pnpm binary against.')
|
|
134
|
+
}
|
|
135
|
+
return { keys }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function fail (message) {
|
|
139
|
+
console.error(message)
|
|
140
|
+
process.exit(1)
|
|
141
|
+
}
|
package/bin/pnpx.mjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Corepack's `pnpx` entry point; see ./pnpm.mjs. The native binary infers the
|
|
3
|
+
// `dlx` subcommand from the name it was launched under, which does not survive
|
|
4
|
+
// being spawned from here, so inject it the way the `pnpx` shell script does.
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
|
|
7
|
+
process.argv = [...process.argv.slice(0, 2), 'dlx', ...process.argv.slice(2)]
|
|
8
|
+
|
|
9
|
+
await import('./pnpm.mjs')
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Resolve $0 through symlinks so basedir is the shim's real directory.
|
|
3
|
+
# Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim.
|
|
4
|
+
link="$0"
|
|
5
|
+
hops=0
|
|
6
|
+
while [ -L "$link" ] && [ "$hops" -lt 40 ]; do
|
|
7
|
+
hops=$((hops+1))
|
|
8
|
+
target=$(readlink "$link")
|
|
9
|
+
case "$target" in
|
|
10
|
+
/*) link="$target" ;;
|
|
11
|
+
*) link="$(dirname "$link")/$target" ;;
|
|
12
|
+
esac
|
|
13
|
+
done
|
|
14
|
+
basedir=$(dirname "$(echo "$link" | sed -e 's,\\,/,g')")
|
|
15
|
+
basedir_win="$basedir"
|
|
16
|
+
exe=""
|
|
17
|
+
msys=""
|
|
18
|
+
|
|
19
|
+
case `uname -a` in
|
|
20
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
21
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
22
|
+
basedir_win=`cygpath -w "$basedir"`
|
|
23
|
+
fi
|
|
24
|
+
exe=".exe"
|
|
25
|
+
msys="true"
|
|
26
|
+
;;
|
|
27
|
+
*WSL2*)
|
|
28
|
+
if command -v wslpath > /dev/null 2>&1; then
|
|
29
|
+
basedir_win="$(wslpath -w "$basedir" 2> /dev/null)"
|
|
30
|
+
if [ $? -ne 0 ] || [ -z "$basedir_win" ]; then
|
|
31
|
+
basedir_win="$basedir"
|
|
32
|
+
else
|
|
33
|
+
exe=".exe"
|
|
34
|
+
fi
|
|
35
|
+
fi
|
|
36
|
+
;;
|
|
37
|
+
esac
|
|
38
|
+
|
|
39
|
+
if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then
|
|
40
|
+
exec "$basedir/node.exe" "$basedir_win/../get-pnpm/bin/get-pnpm.js" "$@"
|
|
41
|
+
elif [ -x "$basedir/node" ]; then
|
|
42
|
+
exec "$basedir/node" "$basedir/../get-pnpm/bin/get-pnpm.js" "$@"
|
|
43
|
+
elif command -v node >/dev/null 2>&1; then
|
|
44
|
+
exec node "$basedir/../get-pnpm/bin/get-pnpm.js" "$@"
|
|
45
|
+
elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then
|
|
46
|
+
exec node.exe "$basedir_win/../get-pnpm/bin/get-pnpm.js" "$@"
|
|
47
|
+
else
|
|
48
|
+
exec node "$basedir/../get-pnpm/bin/get-pnpm.js" "$@"
|
|
49
|
+
fi
|
|
50
|
+
# cmd-shim-target=/home/runner/_work/pnpm/pnpm/pnpm/npm/pnpm/temp-deploy/node_modules/get-pnpm/bin/get-pnpm.js
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"packages":{".":{"url":"..","dependencies":{"node-gyp":"node-gyp","pacquet":"."}},"@isaacs/fs-minipass":{"url":"./@isaacs/fs-minipass","dependencies":{"@isaacs/fs-minipass":"@isaacs/fs-minipass","minipass":"minipass"}},"abbrev":{"url":"./abbrev","dependencies":{"abbrev":"abbrev"}},"chownr":{"url":"./chownr","dependencies":{"chownr":"chownr"}},"env-paths":{"url":"./env-paths","dependencies":{"env-paths":"env-paths"}},"exponential-backoff":{"url":"./exponential-backoff","dependencies":{"exponential-backoff":"exponential-backoff"}},"fdir":{"url":"./fdir","dependencies":{"fdir":"fdir","picomatch":"picomatch"}},"graceful-fs":{"url":"./graceful-fs","dependencies":{"graceful-fs":"graceful-fs"}},"isexe":{"url":"./isexe","dependencies":{"isexe":"isexe"}},"minipass":{"url":"./minipass","dependencies":{"minipass":"minipass"}},"minizlib":{"url":"./minizlib","dependencies":{"minipass":"minipass","minizlib":"minizlib"}},"node-gyp":{"url":"./node-gyp","dependencies":{"env-paths":"env-paths","exponential-backoff":"exponential-backoff","graceful-fs":"graceful-fs","node-gyp":"node-gyp","nopt":"nopt","proc-log":"proc-log","semver":"semver","tar":"tar","tinyglobby":"tinyglobby","undici":"undici","which":"which"}},"nopt":{"url":"./nopt","dependencies":{"abbrev":"abbrev","nopt":"nopt"}},"picomatch":{"url":"./picomatch","dependencies":{"picomatch":"picomatch"}},"proc-log":{"url":"./proc-log","dependencies":{"proc-log":"proc-log"}},"semver":{"url":"./semver","dependencies":{"semver":"semver"}},"tar":{"url":"./tar","dependencies":{"@isaacs/fs-minipass":"@isaacs/fs-minipass","chownr":"chownr","minipass":"minipass","minizlib":"minizlib","tar":"tar","yallist":"yallist"}},"tinyglobby":{"url":"./tinyglobby","dependencies":{"fdir":"fdir","picomatch":"picomatch","tinyglobby":"tinyglobby"}},"undici":{"url":"./undici","dependencies":{"undici":"undici"}},"which":{"url":"./which","dependencies":{"isexe":"isexe","which":"which"}},"yallist":{"url":"./yallist","dependencies":{"yallist":"yallist"}}}}
|
|
1
|
+
{"packages":{".":{"url":"..","dependencies":{"get-pnpm":"get-pnpm","node-gyp":"node-gyp","pacquet":"."}},"@isaacs/fs-minipass":{"url":"./@isaacs/fs-minipass","dependencies":{"@isaacs/fs-minipass":"@isaacs/fs-minipass","minipass":"minipass"}},"abbrev":{"url":"./abbrev","dependencies":{"abbrev":"abbrev"}},"chownr":{"url":"./chownr","dependencies":{"chownr":"chownr"}},"env-paths":{"url":"./env-paths","dependencies":{"env-paths":"env-paths"}},"exponential-backoff":{"url":"./exponential-backoff","dependencies":{"exponential-backoff":"exponential-backoff"}},"fdir":{"url":"./fdir","dependencies":{"fdir":"fdir","picomatch":"picomatch"}},"get-pnpm":{"url":"./get-pnpm","dependencies":{"get-pnpm":"get-pnpm"}},"graceful-fs":{"url":"./graceful-fs","dependencies":{"graceful-fs":"graceful-fs"}},"isexe":{"url":"./isexe","dependencies":{"isexe":"isexe"}},"minipass":{"url":"./minipass","dependencies":{"minipass":"minipass"}},"minizlib":{"url":"./minizlib","dependencies":{"minipass":"minipass","minizlib":"minizlib"}},"node-gyp":{"url":"./node-gyp","dependencies":{"env-paths":"env-paths","exponential-backoff":"exponential-backoff","graceful-fs":"graceful-fs","node-gyp":"node-gyp","nopt":"nopt","proc-log":"proc-log","semver":"semver","tar":"tar","tinyglobby":"tinyglobby","undici":"undici","which":"which"}},"nopt":{"url":"./nopt","dependencies":{"abbrev":"abbrev","nopt":"nopt"}},"picomatch":{"url":"./picomatch","dependencies":{"picomatch":"picomatch"}},"proc-log":{"url":"./proc-log","dependencies":{"proc-log":"proc-log"}},"semver":{"url":"./semver","dependencies":{"semver":"semver"}},"tar":{"url":"./tar","dependencies":{"@isaacs/fs-minipass":"@isaacs/fs-minipass","chownr":"chownr","minipass":"minipass","minizlib":"minizlib","tar":"tar","yallist":"yallist"}},"tinyglobby":{"url":"./tinyglobby","dependencies":{"fdir":"fdir","picomatch":"picomatch","tinyglobby":"tinyglobby"}},"undici":{"url":"./undici","dependencies":{"undici":"undici"}},"which":{"url":"./which","dependencies":{"isexe":"isexe","which":"which"}},"yallist":{"url":"./yallist","dependencies":{"yallist":"yallist"}}}}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"lastValidatedTimestamp":
|
|
2
|
+
"lastValidatedTimestamp": 1786812811685,
|
|
3
3
|
"projects": {
|
|
4
4
|
"/home/runner/_work/pnpm/pnpm/pnpm/npm/pnpm/temp-deploy": {
|
|
5
5
|
"name": "pacquet",
|
|
6
|
-
"version": "12.0.0-rc.
|
|
6
|
+
"version": "12.0.0-rc.6"
|
|
7
7
|
}
|
|
8
8
|
},
|
|
9
9
|
"pnpmfiles": [],
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"escape-string-regexp": "^5.0.0",
|
|
132
132
|
"eslint": "^10.8.1",
|
|
133
133
|
"eslint-plugin-import-x": "^4.17.1",
|
|
134
|
-
"eslint-plugin-jest": "^29.16.
|
|
134
|
+
"eslint-plugin-jest": "^29.16.1",
|
|
135
135
|
"eslint-plugin-n": "^18.3.0",
|
|
136
136
|
"eslint-plugin-promise": "^7.3.0",
|
|
137
137
|
"eslint-plugin-regexp": "^3.1.1",
|
|
@@ -142,6 +142,7 @@
|
|
|
142
142
|
"fast-glob": "^3.3.3",
|
|
143
143
|
"fs-extra": "^11.4.0",
|
|
144
144
|
"fuse-native": "^2.2.6",
|
|
145
|
+
"get-pnpm": "^0.0.3",
|
|
145
146
|
"get-port": "^7.2.0",
|
|
146
147
|
"ghooks": "2.0.4",
|
|
147
148
|
"graceful-fs": "^4.2.11",
|
|
@@ -248,7 +249,7 @@
|
|
|
248
249
|
"ts-jest-resolver": "2.0.1",
|
|
249
250
|
"typescript": "6.0.3",
|
|
250
251
|
"typescript-eslint": "^8.67.0",
|
|
251
|
-
"undici": "^7.
|
|
252
|
+
"undici": "^7.29.0",
|
|
252
253
|
"unified": "^11.0.5",
|
|
253
254
|
"validate-npm-package-name": "7.0.2",
|
|
254
255
|
"version-selector-type": "^3.0.0",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runCli } from '../lib/index.js'
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
process.exitCode = await runCli(process.argv.slice(2))
|
|
6
|
+
} catch (err) {
|
|
7
|
+
// Anything can be thrown, and reading `.message` off a non-Error would either
|
|
8
|
+
// print `undefined` or throw again, burying the reason under a stack trace.
|
|
9
|
+
console.error(err instanceof Error ? err.message : String(err))
|
|
10
|
+
process.exitCode = 1
|
|
11
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { extractTarballMember } from './extractTarballMember.js';
|
|
5
|
+
import { isMusl, platformPackageName } from './platformPackageName.js';
|
|
6
|
+
import { downloadTarball, fetchVersionMeta, normalizeRegistry } from './registry.js';
|
|
7
|
+
import { majorVersion } from './resolveVersion.js';
|
|
8
|
+
import { sameFileContents } from './sameFileContents.js';
|
|
9
|
+
import { verifyRegistrySignature } from './verifySignature.js';
|
|
10
|
+
/** Name the executable is published under inside its platform package. */
|
|
11
|
+
function executableName() {
|
|
12
|
+
return process.platform === 'win32' ? 'pnpm.exe' : 'pnpm';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Places the pnpm executable for this host at `destPath`, and nothing else.
|
|
16
|
+
*
|
|
17
|
+
* The narrow half of {@link downloadPnpm}, for a caller that already knows the
|
|
18
|
+
* exact version and already has whatever else it needs: no dist-tag lookup (so
|
|
19
|
+
* no packument download), no `dist/` tree, no directory to assemble. Corepack
|
|
20
|
+
* is the case it exists for — it unpacks the `pnpm` package itself but installs
|
|
21
|
+
* none of its dependencies, so the executable has to arrive separately.
|
|
22
|
+
*
|
|
23
|
+
* The download is checked against the checksum the registry published for it,
|
|
24
|
+
* and that checksum against npm's signature, exactly as {@link downloadPnpm}
|
|
25
|
+
* does; nothing is written to `destPath` until both pass. See
|
|
26
|
+
* {@link DownloadExecutableOptions.verifySignature} for the one waiver.
|
|
27
|
+
*
|
|
28
|
+
* Placement is atomic and tolerates losing a race: a concurrent call that got
|
|
29
|
+
* there first keeps its copy, since both placed the same verified bytes.
|
|
30
|
+
*
|
|
31
|
+
* @returns the package the executable came from.
|
|
32
|
+
*/
|
|
33
|
+
export async function downloadPnpmExecutable(opts) {
|
|
34
|
+
const { version, destPath } = opts;
|
|
35
|
+
const registry = normalizeRegistry(opts.registry);
|
|
36
|
+
const packageName = platformPackageName({
|
|
37
|
+
major: majorVersion(version),
|
|
38
|
+
platform: process.platform,
|
|
39
|
+
arch: process.arch,
|
|
40
|
+
musl: isMusl(),
|
|
41
|
+
});
|
|
42
|
+
const meta = await fetchVersionMeta(registry, packageName, version, opts.headers);
|
|
43
|
+
if (!meta.dist.integrity) {
|
|
44
|
+
throw new Error(`The npm registry published no checksum for ${packageName}@${version}, so it cannot be verified.`);
|
|
45
|
+
}
|
|
46
|
+
if (opts.verifySignature !== false) {
|
|
47
|
+
verifyRegistrySignature({
|
|
48
|
+
name: packageName,
|
|
49
|
+
version,
|
|
50
|
+
integrity: meta.dist.integrity,
|
|
51
|
+
signatures: meta.dist.signatures,
|
|
52
|
+
keys: opts.keys,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const scratch = `${destPath}.${randomBytes(6).toString('hex')}`;
|
|
56
|
+
const tarball = `${scratch}.tgz`;
|
|
57
|
+
const staged = `${scratch}.tmp`;
|
|
58
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
59
|
+
try {
|
|
60
|
+
await downloadTarball(meta, tarball, { registry, headers: opts.headers });
|
|
61
|
+
const member = `package/${executableName()}`;
|
|
62
|
+
if (!await extractTarballMember(tarball, member, staged, 0o755)) {
|
|
63
|
+
throw new Error(`The ${packageName}@${version} tarball contains no ${member}.`);
|
|
64
|
+
}
|
|
65
|
+
place(staged, destPath);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
fs.rmSync(tarball, { force: true });
|
|
69
|
+
fs.rmSync(staged, { force: true });
|
|
70
|
+
}
|
|
71
|
+
return { packageName };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The codes Windows fails a rename with when the destination is held open —
|
|
75
|
+
* which is what a concurrent call executing the copy it just placed looks like.
|
|
76
|
+
* POSIX replaces the destination instead, so a failure there is a real one.
|
|
77
|
+
*/
|
|
78
|
+
const DESTINATION_IN_USE = new Set(['EPERM', 'EACCES', 'EBUSY']);
|
|
79
|
+
/**
|
|
80
|
+
* Moves the verified executable into place, keeping the copy a concurrent call
|
|
81
|
+
* placed if that is what stops the rename.
|
|
82
|
+
*
|
|
83
|
+
* A lost race is not assumed from the failure alone: the copy already there has
|
|
84
|
+
* to hold the same bytes as the one just verified, or this call has no idea
|
|
85
|
+
* what it would be reporting success for. Anything else — no permission on the
|
|
86
|
+
* directory, a directory or an unrelated file at `destPath` — is a failure.
|
|
87
|
+
*/
|
|
88
|
+
function place(staged, destPath) {
|
|
89
|
+
try {
|
|
90
|
+
fs.renameSync(staged, destPath);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const code = err.code ?? '';
|
|
94
|
+
if (!DESTINATION_IN_USE.has(code))
|
|
95
|
+
throw err;
|
|
96
|
+
if (!sameFileContents(staged, destPath))
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
/**
|
|
4
|
+
* Unpacks a package tarball into `dest`, leaving its `package/` root in place.
|
|
5
|
+
*
|
|
6
|
+
* Shells out to `tar`, which every supported host has: macOS and Linux ship it,
|
|
7
|
+
* and Windows has had bsdtar since Windows 10 1803. Member selection and
|
|
8
|
+
* `--strip-components` are avoided because busybox tar and bsdtar disagree
|
|
9
|
+
* about them.
|
|
10
|
+
*/
|
|
11
|
+
export function extractTarball(tarball, dest) {
|
|
12
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
13
|
+
const { error, status, stderr } = spawnSync('tar', ['-xzf', tarball, '-C', dest], { encoding: 'utf8' });
|
|
14
|
+
if (error != null) {
|
|
15
|
+
if (error.code === 'ENOENT') {
|
|
16
|
+
throw new Error('This installer needs the `tar` command, which was not found on your PATH.');
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
if (status !== 0) {
|
|
21
|
+
throw new Error(`Could not extract ${tarball}: ${stderr.trim()}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
2
|
+
import { pipeline } from 'node:stream/promises';
|
|
3
|
+
import { createGunzip } from 'node:zlib';
|
|
4
|
+
const BLOCK_SIZE = 512;
|
|
5
|
+
/** Regular file, in both the old (`\0`) and the ustar (`0`) spelling. */
|
|
6
|
+
const FILE_TYPES = new Set(['0', '\0']);
|
|
7
|
+
/**
|
|
8
|
+
* Writes one file out of a package tarball to `dest`, and nothing else.
|
|
9
|
+
*
|
|
10
|
+
* Reads the archive in-process rather than shelling out to `tar` the way
|
|
11
|
+
* {@link extractTarball} does: a caller that wants a single executable, in an
|
|
12
|
+
* environment it does not control (a Corepack cache in a minimal image), should
|
|
13
|
+
* not need a `tar` on the PATH for it. Only regular files are considered, which
|
|
14
|
+
* is all an npm tarball holds beyond directories, and the parse stays streaming
|
|
15
|
+
* so the archive never lands in memory.
|
|
16
|
+
*
|
|
17
|
+
* `dest` is created exclusively — an existing file, or a symlink planted at
|
|
18
|
+
* that path, fails the write rather than being followed.
|
|
19
|
+
*
|
|
20
|
+
* @param tarball Path to the gzipped archive.
|
|
21
|
+
* @param memberPath Path of the wanted file inside it, e.g. `package/pnpm`.
|
|
22
|
+
* @param dest Path to write it to.
|
|
23
|
+
* @param mode Permissions for `dest`.
|
|
24
|
+
* @returns whether the member was there.
|
|
25
|
+
*/
|
|
26
|
+
export async function extractTarballMember(tarball, memberPath, dest, mode = 0o644) {
|
|
27
|
+
let found = false;
|
|
28
|
+
await pipeline(createReadStream(tarball), createGunzip(), async function (source) {
|
|
29
|
+
const reader = new BlockReader(source);
|
|
30
|
+
while (true) {
|
|
31
|
+
const header = await reader.read(BLOCK_SIZE);
|
|
32
|
+
// The archive ends with zero-filled blocks; one is enough to stop.
|
|
33
|
+
if (header == null || header[0] === 0)
|
|
34
|
+
return;
|
|
35
|
+
const entry = parseHeader(header);
|
|
36
|
+
if (FILE_TYPES.has(entry.type) && entry.path === memberPath) {
|
|
37
|
+
const written = await reader.pipe(entry.size, createWriteStream(dest, { flags: 'wx', mode }));
|
|
38
|
+
if (written !== entry.size) {
|
|
39
|
+
throw new Error(`${tarball} ends after ${written} of the ${entry.size} bytes it declares for ${memberPath}.`);
|
|
40
|
+
}
|
|
41
|
+
found = true;
|
|
42
|
+
// The rest of the archive holds nothing this caller asked for, and
|
|
43
|
+
// the checksum that vouches for it was checked before any of it was
|
|
44
|
+
// read.
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await reader.skip(Math.ceil(entry.size / BLOCK_SIZE) * BLOCK_SIZE);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
function parseHeader(header) {
|
|
53
|
+
const name = readString(header, 0, 100);
|
|
54
|
+
const prefix = readString(header, 345, 155);
|
|
55
|
+
return {
|
|
56
|
+
path: prefix === '' ? name : `${prefix}/${name}`,
|
|
57
|
+
size: parseInt(readString(header, 124, 12).trim() || '0', 8),
|
|
58
|
+
type: String.fromCharCode(header[156]),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function readString(header, start, length) {
|
|
62
|
+
const field = header.subarray(start, start + length);
|
|
63
|
+
const end = field.indexOf(0);
|
|
64
|
+
return field.toString('utf8', 0, end === -1 ? field.length : end);
|
|
65
|
+
}
|
|
66
|
+
class BlockReader {
|
|
67
|
+
#iterator;
|
|
68
|
+
#buffered = [];
|
|
69
|
+
#buffedBytes = 0;
|
|
70
|
+
#done = false;
|
|
71
|
+
constructor(source) {
|
|
72
|
+
this.#iterator = source[Symbol.asyncIterator]();
|
|
73
|
+
}
|
|
74
|
+
/** The next `size` bytes, or `null` once the stream ends. */
|
|
75
|
+
async read(size) {
|
|
76
|
+
if (!await this.#fill(size))
|
|
77
|
+
return null;
|
|
78
|
+
return this.#take(size);
|
|
79
|
+
}
|
|
80
|
+
async skip(size) {
|
|
81
|
+
let left = size;
|
|
82
|
+
while (left > 0) {
|
|
83
|
+
if (!await this.#fill(1))
|
|
84
|
+
return;
|
|
85
|
+
left -= this.#take(Math.min(left, this.#buffedBytes)).length;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Hands the next `size` bytes to `destination`, without collecting them.
|
|
90
|
+
*
|
|
91
|
+
* @returns how many bytes there were, which is fewer than `size` only when
|
|
92
|
+
* the stream ended early.
|
|
93
|
+
*/
|
|
94
|
+
async pipe(size, destination) {
|
|
95
|
+
const self = this;
|
|
96
|
+
let left = size;
|
|
97
|
+
await pipeline(async function* () {
|
|
98
|
+
while (left > 0) {
|
|
99
|
+
if (!await self.#fill(1))
|
|
100
|
+
return;
|
|
101
|
+
const chunk = self.#take(Math.min(left, self.#buffedBytes));
|
|
102
|
+
left -= chunk.length;
|
|
103
|
+
yield chunk;
|
|
104
|
+
}
|
|
105
|
+
}, destination);
|
|
106
|
+
return size - left;
|
|
107
|
+
}
|
|
108
|
+
/** Reads until at least `size` bytes are buffered, or the stream ends. */
|
|
109
|
+
async #fill(size) {
|
|
110
|
+
while (this.#buffedBytes < size && !this.#done) {
|
|
111
|
+
const { value, done } = await this.#iterator.next();
|
|
112
|
+
if (done === true) {
|
|
113
|
+
this.#done = true;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.#buffered.push(value);
|
|
117
|
+
this.#buffedBytes += value.length;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return this.#buffedBytes >= size;
|
|
121
|
+
}
|
|
122
|
+
#take(size) {
|
|
123
|
+
const joined = this.#buffered.length === 1 ? this.#buffered[0] : Buffer.concat(this.#buffered);
|
|
124
|
+
this.#buffered = joined.length > size ? [joined.subarray(size)] : [];
|
|
125
|
+
this.#buffedBytes = joined.length - size;
|
|
126
|
+
return joined.subarray(0, size);
|
|
127
|
+
}
|
|
128
|
+
}
|