@pnpm/exe 12.0.0-rc.5 → 12.0.0-rc.7

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.
@@ -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": 1786637313614,
2
+ "lastValidatedTimestamp": 1787046162762,
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.5"
6
+ "version": "12.0.0-rc.7"
7
7
  }
8
8
  },
9
9
  "pnpmfiles": [],
@@ -22,9 +22,9 @@
22
22
  "default": {
23
23
  "@babel/core": "^7.29.7",
24
24
  "@babel/plugin-transform-explicit-resource-management": "^7.29.7",
25
- "@commitlint/cli": "^21.2.1",
26
- "@commitlint/config-conventional": "^21.2.0",
27
- "@commitlint/prompt-cli": "^21.2.0",
25
+ "@commitlint/cli": "^21.2.2",
26
+ "@commitlint/config-conventional": "^21.2.2",
27
+ "@commitlint/prompt-cli": "^21.2.2",
28
28
  "@cyclonedx/cyclonedx-library": "10.1.1",
29
29
  "@eslint/js": "^10.0.1",
30
30
  "@inquirer/prompts": "^8.5.2",
@@ -47,7 +47,7 @@
47
47
  "@pnpm/tabtab": "^0.5.4",
48
48
  "@pnpm/tgz-fixtures": "0.0.0",
49
49
  "@reflink/reflink": "0.1.19",
50
- "@rushstack/worker-pool": "0.7.22",
50
+ "@rushstack/worker-pool": "0.7.23",
51
51
  "@stylistic/eslint-plugin": "^5.10.0",
52
52
  "@types/adm-zip": "^0.5.8",
53
53
  "@types/archy": "0.0.36",
@@ -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.0",
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",
@@ -183,7 +184,7 @@
183
184
  "npm-packlist": "10.0.4",
184
185
  "npm-registry-fetch": "^19.0.0",
185
186
  "object-hash": "3.0.0",
186
- "open": "^11.0.0",
187
+ "open": "^11.0.1",
187
188
  "openpgp": "^6.3.1",
188
189
  "p-defer": "^4.0.1",
189
190
  "p-every": "^2.0.0",
@@ -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.27.2",
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",
@@ -277,7 +278,60 @@
277
278
  "injectWorkspacePackages": false,
278
279
  "linkWorkspacePackages": false,
279
280
  "minimumReleaseAge": 1440,
281
+ "minimumReleaseAgeExclude": [
282
+ "@pnpm/*",
283
+ "@rushstack/worker-pool@0.7.18",
284
+ "@zkochan/*",
285
+ "better-path-resolve",
286
+ "body-parser@2.2.1",
287
+ "can-link",
288
+ "can-write-to-dir",
289
+ "cmd-extension",
290
+ "comver-to-semver",
291
+ "dir-is-case-sensitive",
292
+ "esbuild@0.28.1",
293
+ "@esbuild/*",
294
+ "express@4.22.1",
295
+ "glob@11.1.0",
296
+ "handlebars@4.7.9",
297
+ "is-inner-link",
298
+ "is-subdir",
299
+ "jws@3.2.3",
300
+ "lodash@4.17.23",
301
+ "make-empty-dir",
302
+ "normalize-registry-url",
303
+ "p-map-values",
304
+ "parse-npm-tarball-url@5.0.0",
305
+ "path-absolute",
306
+ "path-temp",
307
+ "pnpm",
308
+ "preferred-pm",
309
+ "promise-share",
310
+ "qs@6.14.2 || 6.15.2",
311
+ "read-ini-file",
312
+ "read-json5-file",
313
+ "read-yaml-file",
314
+ "realpath-missing",
315
+ "rename-overwrite",
316
+ "render-help",
317
+ "resolve-link-target",
318
+ "root-link-target",
319
+ "run-groups",
320
+ "safe-execa",
321
+ "safe-promise-defer",
322
+ "symlink-dir",
323
+ "tar@7.5.10",
324
+ "which-pm",
325
+ "which-pm-runs",
326
+ "write-ini-file",
327
+ "write-json5-file",
328
+ "write-yaml-file",
329
+ "tmp@0.2.6",
330
+ "http-proxy-middleware@3.0.7",
331
+ "get-pnpm@0.0.2 || 0.0.3"
332
+ ],
280
333
  "minimumReleaseAgeIgnoreMissingTime": true,
334
+ "minimumReleaseAgeStrict": true,
281
335
  "nodeLinker": "hoisted",
282
336
  "optional": true,
283
337
  "patchedDependencies": {
@@ -287,6 +341,16 @@
287
341
  "peersSuffixMaxLength": 1000,
288
342
  "preferWorkspacePackages": false,
289
343
  "production": true,
290
- "publicHoistPattern": []
344
+ "publicHoistPattern": [],
345
+ "trustPolicy": "no-downgrade",
346
+ "trustPolicyExclude": [
347
+ "@pnpm/*",
348
+ "@yarnpkg/libzip@3.2.2",
349
+ "pnpm",
350
+ "rxjs@7.8.2",
351
+ "tinyexec@1.2.2",
352
+ "undici-types@6.21.0"
353
+ ],
354
+ "trustPolicyIgnoreAfter": 10080
291
355
  }
292
356
  }
@@ -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
+ }