pnpm 12.14.0 → 12.16.0
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 +378 -0
- package/README.md +4 -4
- package/bin/pnpm.mjs +11 -233
- package/dist/node_modules/.pnpm-workspace-state-v1.json +97 -271
- package/dist/node_modules/confbox/LICENSE +27 -0
- package/dist/node_modules/confbox/dist/_chunks/_format.d.mts +22 -0
- package/dist/node_modules/confbox/dist/_chunks/_format.mjs +1 -0
- package/dist/node_modules/confbox/dist/_chunks/libs/detect-indent.mjs +1 -0
- package/dist/node_modules/confbox/dist/_chunks/libs/ini.mjs +3 -0
- package/dist/node_modules/confbox/dist/_chunks/libs/js-yaml.d.mts +33 -0
- package/dist/node_modules/confbox/dist/_chunks/libs/js-yaml.mjs +38 -0
- package/dist/node_modules/confbox/dist/_chunks/libs/json5.mjs +14 -0
- package/dist/node_modules/confbox/dist/_chunks/libs/smol-toml.mjs +11 -0
- package/dist/node_modules/confbox/dist/_chunks/libs/strip-json-comments.mjs +4 -0
- package/dist/node_modules/confbox/dist/_chunks/rolldown-runtime.mjs +1 -0
- package/dist/node_modules/confbox/dist/index.d.mts +7 -0
- package/dist/node_modules/confbox/dist/index.mjs +1 -0
- package/dist/node_modules/confbox/dist/ini.d.mts +59 -0
- package/dist/node_modules/confbox/dist/ini.mjs +1 -0
- package/dist/node_modules/confbox/dist/json.d.mts +25 -0
- package/dist/node_modules/confbox/dist/json.mjs +1 -0
- package/dist/node_modules/confbox/dist/json5.d.mts +55 -0
- package/dist/node_modules/confbox/dist/json5.mjs +1 -0
- package/dist/node_modules/confbox/dist/jsonc.d.mts +29 -0
- package/dist/node_modules/confbox/dist/jsonc.mjs +1 -0
- package/dist/node_modules/confbox/dist/toml.d.mts +20 -0
- package/dist/node_modules/confbox/dist/toml.mjs +1 -0
- package/dist/node_modules/confbox/dist/yaml.d.mts +101 -0
- package/dist/node_modules/confbox/dist/yaml.mjs +1 -0
- package/dist/node_modules/confbox/package.json +68 -0
- package/dist/node_modules/get-pnpm/lib/corepack.js +79 -0
- package/dist/node_modules/get-pnpm/lib/downloadExecutable.js +10 -4
- package/dist/node_modules/get-pnpm/lib/extractTarballMember.js +45 -35
- package/dist/node_modules/get-pnpm/lib/index.js +3 -219
- package/dist/node_modules/get-pnpm/lib/install.js +293 -0
- package/dist/node_modules/get-pnpm/lib/npmConfig.js +280 -0
- package/dist/node_modules/get-pnpm/lib/npmSigningKeys.js +2 -6
- package/dist/node_modules/get-pnpm/lib/platformPackageName.js +11 -7
- package/dist/node_modules/get-pnpm/lib/pnpmConfig.js +297 -0
- package/dist/node_modules/get-pnpm/lib/projectPin.js +201 -0
- package/dist/node_modules/get-pnpm/lib/registry.js +265 -41
- package/dist/node_modules/get-pnpm/lib/resolveVersion.js +19 -2
- package/dist/node_modules/get-pnpm/lib/sameFileContents.js +31 -27
- package/dist/node_modules/get-pnpm/lib/shadowingPnpm.js +174 -0
- package/dist/node_modules/get-pnpm/lib/undoHint.js +12 -0
- package/dist/node_modules/get-pnpm/package.json +20 -14
- package/dist/node_modules/undici/lib/dispatcher/agent.js +6 -2
- package/dist/node_modules/undici/lib/dispatcher/client-h1.js +0 -2
- package/dist/node_modules/undici/lib/dispatcher/client-h2.js +0 -4
- package/dist/node_modules/undici/lib/dispatcher/client.js +3 -2
- package/dist/node_modules/undici/lib/dispatcher/dispatcher-base.js +9 -0
- package/dist/node_modules/undici/lib/web/eventsource/eventsource-stream.js +91 -10
- package/dist/node_modules/undici/lib/web/eventsource/eventsource.js +4 -1
- package/dist/node_modules/undici/lib/web/fetch/index.js +7 -9
- package/dist/node_modules/undici/lib/web/websocket/receiver.js +14 -0
- package/dist/node_modules/undici/package.json +1 -1
- package/package.json +16 -16
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getFormat as e,storeFormat as t,stripBOM as n}from"./_chunks/_format.mjs";import{YAMLException as r,dump as i,load as a}from"./_chunks/libs/js-yaml.mjs";function o(e,r){let i=a(n(e),r);return t(e,i,r),i}function s(t,n){let r=e(t,{preserveIndentation:!1}),a=typeof r.indent==`string`?r.indent.length:r.indent,o=i(t,{indent:a,...n});return r.whitespace.start+o.trim()+r.whitespace.end}export{r as YAMLException,o as parseYAML,s as stringifyYAML};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "confbox",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Compact YAML, TOML, JSONC, JSON5 and INI parser and serializer",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"config",
|
|
7
|
+
"ini",
|
|
8
|
+
"json5",
|
|
9
|
+
"jsonc",
|
|
10
|
+
"toml",
|
|
11
|
+
"unjs",
|
|
12
|
+
"yaml"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "unjs/confbox"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"types": "./dist/index.d.mts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": "./dist/index.mjs",
|
|
27
|
+
"./ini": "./dist/ini.mjs",
|
|
28
|
+
"./json": "./dist/json.mjs",
|
|
29
|
+
"./json5": "./dist/json5.mjs",
|
|
30
|
+
"./jsonc": "./dist/jsonc.mjs",
|
|
31
|
+
"./toml": "./dist/toml.mjs",
|
|
32
|
+
"./yaml": "./dist/yaml.mjs"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/ini": "^4.1.1",
|
|
36
|
+
"@types/node": "^26",
|
|
37
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
38
|
+
"automd": "^0.4.3",
|
|
39
|
+
"changelogen": "^0.6.2",
|
|
40
|
+
"detect-indent": "^7.0.2",
|
|
41
|
+
"ini": "^7.0.0",
|
|
42
|
+
"jiti": "^2.7.0",
|
|
43
|
+
"js-toml": "^2.0.1",
|
|
44
|
+
"js-yaml": "^5.4.1",
|
|
45
|
+
"json5": "^2.2.3",
|
|
46
|
+
"jsonc-parser": "^3.3.1",
|
|
47
|
+
"mitata": "^1.0.34",
|
|
48
|
+
"obuild": "^0.4.39",
|
|
49
|
+
"oxfmt": ">=0.66.0",
|
|
50
|
+
"oxlint": "^1.81.0",
|
|
51
|
+
"smol-toml": "^1.8.0",
|
|
52
|
+
"strip-json-comments": "^5.0.1",
|
|
53
|
+
"toml": "^5.0.0",
|
|
54
|
+
"typescript": "^7",
|
|
55
|
+
"vitest": "^4.1.11",
|
|
56
|
+
"yaml": "^2.9.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "obuild",
|
|
60
|
+
"dev": "vitest dev --coverage",
|
|
61
|
+
"bench": "pnpm build && node test/bench.mjs",
|
|
62
|
+
"lint": "oxlint && oxfmt --check src test",
|
|
63
|
+
"lint:fix": "oxlint --fix && oxfmt src test",
|
|
64
|
+
"release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
|
|
65
|
+
"test": "pnpm lint && pnpm test:types && vitest run --coverage",
|
|
66
|
+
"test:types": "tsc --noEmit --skipLibCheck"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { pnpmProxyConfig, pnpmRegistryConfig } from './pnpmConfig.js';
|
|
2
|
+
import { DEFAULT_REGISTRY } from './registry.js';
|
|
3
|
+
/**
|
|
4
|
+
* The options for `downloadPnpmExecutable` that download pnpm where
|
|
5
|
+
* Corepack would, verified the way Corepack verifies it.
|
|
6
|
+
*
|
|
7
|
+
* - `COREPACK_NPM_REGISTRY` names the registry. Otherwise `pnpmRegistryConfig`
|
|
8
|
+
* reads pnpm's config.
|
|
9
|
+
* - `COREPACK_NPM_TOKEN`, or `COREPACK_NPM_USERNAME` with
|
|
10
|
+
* `COREPACK_NPM_PASSWORD`, are the credentials for Corepack's registry, and
|
|
11
|
+
* for the npm registry when pnpm's config holds none for it.
|
|
12
|
+
* - `COREPACK_INTEGRITY_KEYS` follows Corepack. Unset, npm's own keys are
|
|
13
|
+
* trusted. A key set trusts its `npm` keys. `0` or empty skips the signature
|
|
14
|
+
* check, which a registry that re-publishes packages without npm's
|
|
15
|
+
* signatures already needs for Corepack to have installed pnpm from it.
|
|
16
|
+
* - The project `.npmrc` is read only while the signature is checked; see
|
|
17
|
+
* `PnpmRegistryConfigOptions.readProjectNpmrc`.
|
|
18
|
+
* - `pnpmProxyConfig` reads the proxy from pnpm's config, whichever registry
|
|
19
|
+
* is used.
|
|
20
|
+
*
|
|
21
|
+
* @throws if `COREPACK_ENABLE_NETWORK` is `0`, or `COREPACK_INTEGRITY_KEYS`
|
|
22
|
+
* holds no `npm` key set.
|
|
23
|
+
*/
|
|
24
|
+
export function corepackDownloadOptions(opts = {}) {
|
|
25
|
+
const env = opts.env ?? process.env;
|
|
26
|
+
if (env.COREPACK_ENABLE_NETWORK === '0') {
|
|
27
|
+
throw new Error('Network access is disabled by the environment, so the pnpm binary cannot be downloaded.');
|
|
28
|
+
}
|
|
29
|
+
const signature = signaturePolicy(env.COREPACK_INTEGRITY_KEYS);
|
|
30
|
+
return {
|
|
31
|
+
...registryAccess({ ...opts, env, readProjectNpmrc: signature.verifySignature !== false }),
|
|
32
|
+
proxy: pnpmProxyConfig({ env, homeDir: opts.homeDir }),
|
|
33
|
+
...signature,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function registryAccess(opts) {
|
|
37
|
+
const corepackRegistry = opts.env.COREPACK_NPM_REGISTRY;
|
|
38
|
+
if (corepackRegistry) {
|
|
39
|
+
return withHeaders({ registry: corepackRegistry }, corepackHeaders(opts.env));
|
|
40
|
+
}
|
|
41
|
+
const config = pnpmRegistryConfig(opts);
|
|
42
|
+
if (config.headers == null && new URL(config.registry).origin === new URL(DEFAULT_REGISTRY).origin) {
|
|
43
|
+
return withHeaders(config, corepackHeaders(opts.env));
|
|
44
|
+
}
|
|
45
|
+
return config;
|
|
46
|
+
}
|
|
47
|
+
function withHeaders(config, headers) {
|
|
48
|
+
return headers == null ? config : { ...config, headers };
|
|
49
|
+
}
|
|
50
|
+
function corepackHeaders(env) {
|
|
51
|
+
const { COREPACK_NPM_TOKEN, COREPACK_NPM_USERNAME, COREPACK_NPM_PASSWORD } = env;
|
|
52
|
+
if (COREPACK_NPM_TOKEN) {
|
|
53
|
+
return { authorization: `Bearer ${COREPACK_NPM_TOKEN}` };
|
|
54
|
+
}
|
|
55
|
+
if (COREPACK_NPM_USERNAME && COREPACK_NPM_PASSWORD) {
|
|
56
|
+
return { authorization: `Basic ${Buffer.from(`${COREPACK_NPM_USERNAME}:${COREPACK_NPM_PASSWORD}`).toString('base64')}` };
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
function signaturePolicy(configured) {
|
|
61
|
+
if (configured == null)
|
|
62
|
+
return {};
|
|
63
|
+
if (configured === '' || configured === '0')
|
|
64
|
+
return { verifySignature: false };
|
|
65
|
+
let keys;
|
|
66
|
+
try {
|
|
67
|
+
keys = JSON.parse(configured)?.npm;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
throw new Error(`COREPACK_INTEGRITY_KEYS is not readable as JSON: ${err.message}`, { cause: err });
|
|
71
|
+
}
|
|
72
|
+
// Passing no keys would fall back to npm's own, the opposite of what setting
|
|
73
|
+
// the variable asked for. An empty set names no key to trust, and Corepack
|
|
74
|
+
// reads it the same way, so every download is refused.
|
|
75
|
+
if (!Array.isArray(keys)) {
|
|
76
|
+
throw new Error('COREPACK_INTEGRITY_KEYS holds no "npm" key set to verify the pnpm binary against.');
|
|
77
|
+
}
|
|
78
|
+
return { keys: keys };
|
|
79
|
+
}
|
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { extractTarballMember } from './extractTarballMember.js';
|
|
5
5
|
import { isMusl, platformPackageName } from './platformPackageName.js';
|
|
6
|
-
import { downloadTarball, fetchVersionMeta, normalizeRegistry, useProxyFromEnv } from './registry.js';
|
|
6
|
+
import { downloadTarball, fetchSignatures, fetchVersionMeta, normalizeRegistry, useCertificateAuthorities, useProxyFromEnv } from './registry.js';
|
|
7
7
|
import { majorVersion } from './resolveVersion.js';
|
|
8
8
|
import { sameFileContents } from './sameFileContents.js';
|
|
9
9
|
import { verifyRegistrySignature } from './verifySignature.js';
|
|
@@ -38,9 +38,15 @@ export async function downloadPnpmExecutable(opts) {
|
|
|
38
38
|
arch: process.arch,
|
|
39
39
|
musl: isMusl(),
|
|
40
40
|
});
|
|
41
|
-
const restoreProxy = useProxyFromEnv();
|
|
41
|
+
const restoreProxy = useProxyFromEnv(opts.proxy);
|
|
42
42
|
try {
|
|
43
|
-
|
|
43
|
+
const restoreCertificates = useCertificateAuthorities(opts.ca);
|
|
44
|
+
try {
|
|
45
|
+
return await fetchExecutable({ ...opts, registry, packageName });
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
restoreCertificates();
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
finally {
|
|
46
52
|
restoreProxy();
|
|
@@ -57,7 +63,7 @@ async function fetchExecutable(opts) {
|
|
|
57
63
|
name: packageName,
|
|
58
64
|
version,
|
|
59
65
|
integrity: meta.dist.integrity,
|
|
60
|
-
signatures: meta.
|
|
66
|
+
signatures: await fetchSignatures(meta, { registry, pkgName: packageName, version, headers: opts.headers }),
|
|
61
67
|
keys: opts.keys,
|
|
62
68
|
});
|
|
63
69
|
}
|
|
@@ -27,28 +27,38 @@ export async function extractTarballMember(tarball, memberPath, dest, mode = 0o6
|
|
|
27
27
|
let found = false;
|
|
28
28
|
await pipeline(createReadStream(tarball), createGunzip(), async function (source) {
|
|
29
29
|
const reader = new BlockReader(source);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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);
|
|
30
|
+
const entry = await seekMember(reader, memberPath);
|
|
31
|
+
if (entry == null)
|
|
32
|
+
return;
|
|
33
|
+
const written = await reader.pipe(entry.size, createWriteStream(dest, { flags: 'wx', mode }));
|
|
34
|
+
if (written !== entry.size) {
|
|
35
|
+
throw new Error(`${tarball} ends after ${written} of the ${entry.size} bytes it declares for ${memberPath}.`);
|
|
48
36
|
}
|
|
37
|
+
found = true;
|
|
38
|
+
// The rest of the archive holds nothing this caller asked for, and the
|
|
39
|
+
// checksum that vouches for it was checked before any of it was read.
|
|
49
40
|
});
|
|
50
41
|
return found;
|
|
51
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Reads up to the content of the regular file at `memberPath`.
|
|
45
|
+
*
|
|
46
|
+
* @returns its header, or `null` when the archive has no such file.
|
|
47
|
+
*/
|
|
48
|
+
async function seekMember(reader, memberPath) {
|
|
49
|
+
while (true) {
|
|
50
|
+
// eslint-disable-next-line no-await-in-loop -- a tar archive can only be walked one entry after another
|
|
51
|
+
const header = await reader.read(BLOCK_SIZE);
|
|
52
|
+
// The archive ends with zero-filled blocks; one is enough to stop.
|
|
53
|
+
if (header == null || header[0] === 0)
|
|
54
|
+
return null;
|
|
55
|
+
const entry = parseHeader(header);
|
|
56
|
+
if (FILE_TYPES.has(entry.type) && entry.path === memberPath)
|
|
57
|
+
return entry;
|
|
58
|
+
// eslint-disable-next-line no-await-in-loop -- a tar archive can only be walked one entry after another
|
|
59
|
+
await reader.skip(Math.ceil(entry.size / BLOCK_SIZE) * BLOCK_SIZE);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
52
62
|
function parseHeader(header) {
|
|
53
63
|
const name = readString(header, 0, 100);
|
|
54
64
|
const prefix = readString(header, 345, 155);
|
|
@@ -78,11 +88,8 @@ class BlockReader {
|
|
|
78
88
|
return this.#take(size);
|
|
79
89
|
}
|
|
80
90
|
async skip(size) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!await this.#fill(1))
|
|
84
|
-
return;
|
|
85
|
-
left -= this.#take(Math.min(left, this.#buffedBytes)).length;
|
|
91
|
+
for await (const _chunk of this.#chunks({ left: size })) {
|
|
92
|
+
// Discarded.
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
/**
|
|
@@ -92,22 +99,25 @@ class BlockReader {
|
|
|
92
99
|
* the stream ended early.
|
|
93
100
|
*/
|
|
94
101
|
async pipe(size, destination) {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
const progress = { left: size };
|
|
103
|
+
await pipeline(this.#chunks(progress), destination);
|
|
104
|
+
return size - progress.left;
|
|
105
|
+
}
|
|
106
|
+
/** The next `progress.left` bytes, counting them down as they are yielded. */
|
|
107
|
+
async *#chunks(progress) {
|
|
108
|
+
while (progress.left > 0) {
|
|
109
|
+
// eslint-disable-next-line no-await-in-loop -- the stream is read in order, one chunk at a time
|
|
110
|
+
if (!await this.#fill(1))
|
|
111
|
+
return;
|
|
112
|
+
const chunk = this.#take(Math.min(progress.left, this.#buffedBytes));
|
|
113
|
+
progress.left -= chunk.length;
|
|
114
|
+
yield chunk;
|
|
115
|
+
}
|
|
107
116
|
}
|
|
108
117
|
/** Reads until at least `size` bytes are buffered, or the stream ends. */
|
|
109
118
|
async #fill(size) {
|
|
110
119
|
while (this.#buffedBytes < size && !this.#done) {
|
|
120
|
+
// eslint-disable-next-line no-await-in-loop -- the stream is read in order, one chunk at a time
|
|
111
121
|
const { value, done } = await this.#iterator.next();
|
|
112
122
|
if (done === true) {
|
|
113
123
|
this.#done = true;
|
|
@@ -1,220 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { extractTarball } from './extractTarball.js';
|
|
6
|
-
import { isMusl, platformPackageName } from './platformPackageName.js';
|
|
7
|
-
import { downloadTarball, fetchPackument, fetchVersionMeta, registryFromEnv, useProxyFromEnv } from './registry.js';
|
|
8
|
-
import { majorVersion, resolveVersion } from './resolveVersion.js';
|
|
9
|
-
import { verifyRegistrySignature } from './verifySignature.js';
|
|
1
|
+
export { corepackDownloadOptions } from './corepack.js';
|
|
10
2
|
export { downloadPnpmExecutable } from './downloadExecutable.js';
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export { DEFAULT_REGISTRY, registryFromEnv, useProxyFromEnv } from './registry.js';
|
|
14
|
-
export { majorVersion, resolveVersion } from './resolveVersion.js';
|
|
15
|
-
export { verifyRegistrySignature } from './verifySignature.js';
|
|
16
|
-
/**
|
|
17
|
-
* The package whose dist-tags name every pnpm release. `@pnpm/exe` carries the
|
|
18
|
-
* same versions today, but only `pnpm` is published from v12 onward, so its
|
|
19
|
-
* tags are the ones that cannot go stale.
|
|
20
|
-
*/
|
|
21
|
-
const CLI_PKG_NAME = 'pnpm';
|
|
22
|
-
/** Holds the unpacked tarballs while the installation is assembled beside it. */
|
|
23
|
-
const UNPACK_DIR = '.unpack';
|
|
24
|
-
/** Where the `dist/` tree that ships beside the executable is published. */
|
|
25
|
-
function wrapperPackageName(major) {
|
|
26
|
-
return major >= 12 ? CLI_PKG_NAME : '@pnpm/exe';
|
|
27
|
-
}
|
|
28
|
-
const USAGE = `Usage: npx get-pnpm [version]
|
|
29
|
-
|
|
30
|
-
Installs pnpm as a standalone executable and adds it to your PATH.
|
|
31
|
-
|
|
32
|
-
Arguments:
|
|
33
|
-
version An exact version (11.20.0), a major (12), or a dist-tag
|
|
34
|
-
(latest, next-12). Defaults to $PNPM_VERSION, then "latest".
|
|
35
|
-
|
|
36
|
-
Environment variables:
|
|
37
|
-
PNPM_VERSION Version to install when no argument is given.
|
|
38
|
-
PNPM_HOME Directory to install pnpm into.
|
|
39
|
-
npm_config_registry Registry to download pnpm from.
|
|
40
|
-
HTTPS_PROXY Proxy to download through (HTTP_PROXY for an http://
|
|
41
|
-
registry); NO_PROXY names hosts to reach directly.
|
|
42
|
-
Node 24.14 or later.
|
|
43
|
-
`;
|
|
44
|
-
export async function runCli(argv) {
|
|
45
|
-
const positional = [];
|
|
46
|
-
for (const arg of argv) {
|
|
47
|
-
if (arg === '--help' || arg === '-h') {
|
|
48
|
-
console.log(USAGE);
|
|
49
|
-
return 0;
|
|
50
|
-
}
|
|
51
|
-
if (arg.startsWith('-')) {
|
|
52
|
-
throw new Error(`Unknown option "${arg}".\n\n${USAGE}`);
|
|
53
|
-
}
|
|
54
|
-
positional.push(arg);
|
|
55
|
-
}
|
|
56
|
-
if (positional.length > 1) {
|
|
57
|
-
throw new Error(`Expected at most one version, got ${positional.length}.\n\n${USAGE}`);
|
|
58
|
-
}
|
|
59
|
-
return installPnpm({
|
|
60
|
-
versionSpec: positional[0] ?? process.env.PNPM_VERSION ?? 'latest',
|
|
61
|
-
registry: registryFromEnv(),
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Downloads the pnpm executable and hands over to `pnpm setup`, which installs
|
|
66
|
-
* it globally and puts it on the PATH.
|
|
67
|
-
*
|
|
68
|
-
* Every download is checked against the checksum the registry published for it,
|
|
69
|
-
* and that checksum against npm's signature — see `verifyRegistrySignature`.
|
|
70
|
-
*
|
|
71
|
-
* The temporary directory is assembled to look like the release tarball that
|
|
72
|
-
* https://get.pnpm.io/install.sh downloads — the executable next to its `dist/`
|
|
73
|
-
* tree — because `pnpm setup` installs that directory as-is.
|
|
74
|
-
*
|
|
75
|
-
* @returns the exit code of `pnpm setup`.
|
|
76
|
-
*/
|
|
77
|
-
export async function installPnpm(opts) {
|
|
78
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pnpm-install-'));
|
|
79
|
-
const removeTmpDir = () => {
|
|
80
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
81
|
-
};
|
|
82
|
-
// Registered for the duration of the call and no longer: this is also a
|
|
83
|
-
// library function, and handlers left behind would accumulate and outlive the
|
|
84
|
-
// directory they exist to clean up.
|
|
85
|
-
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
|
|
86
|
-
const onSignal = () => {
|
|
87
|
-
removeTmpDir();
|
|
88
|
-
process.exit(1);
|
|
89
|
-
};
|
|
90
|
-
for (const signal of signals) {
|
|
91
|
-
process.once(signal, onSignal);
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
const { binPath } = await downloadPnpm({ ...opts, dest: tmpDir });
|
|
95
|
-
const { error, status } = spawnSync(binPath, ['setup', '--force'], { stdio: 'inherit' });
|
|
96
|
-
if (error != null)
|
|
97
|
-
throw error;
|
|
98
|
-
return status ?? 1;
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
for (const signal of signals) {
|
|
102
|
-
process.off(signal, onSignal);
|
|
103
|
-
}
|
|
104
|
-
removeTmpDir();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Downloads the pnpm executable into `dest`, laid out the way the release
|
|
109
|
-
* tarball lays it out — the executable next to the `dist/` tree it loads.
|
|
110
|
-
*
|
|
111
|
-
* Every download is checked against the checksum the registry published for it,
|
|
112
|
-
* and that checksum against npm's signature; see `verifyRegistrySignature`.
|
|
113
|
-
* Nothing outside `dest` is touched, so a caller that manages its own PATH — a
|
|
114
|
-
* CI action, say — can use this without the global install `installPnpm` does.
|
|
115
|
-
*
|
|
116
|
-
* @returns the version installed and the path to the executable.
|
|
117
|
-
*/
|
|
118
|
-
export async function downloadPnpm(opts) {
|
|
119
|
-
const restoreProxy = useProxyFromEnv();
|
|
120
|
-
try {
|
|
121
|
-
return await fetchPnpm(opts);
|
|
122
|
-
}
|
|
123
|
-
finally {
|
|
124
|
-
restoreProxy();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
async function fetchPnpm(opts) {
|
|
128
|
-
const packument = await fetchPackument(opts.registry, CLI_PKG_NAME);
|
|
129
|
-
const version = resolveVersion(packument, opts.versionSpec);
|
|
130
|
-
const major = majorVersion(version);
|
|
131
|
-
const platformPkgName = platformPackageName({
|
|
132
|
-
major,
|
|
133
|
-
platform: process.platform,
|
|
134
|
-
arch: process.arch,
|
|
135
|
-
musl: isMusl(),
|
|
136
|
-
});
|
|
137
|
-
const { dest } = opts;
|
|
138
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
139
|
-
try {
|
|
140
|
-
console.log(`==> Downloading pnpm ${version}`);
|
|
141
|
-
const executable = process.platform === 'win32' ? 'pnpm.exe' : 'pnpm';
|
|
142
|
-
const fetchPackage = verifiedPackageFetcher({ dir: dest, registry: opts.registry, version, keys: opts.keys });
|
|
143
|
-
// Settled, not `all`: a rejection there would leave the other fetch writing
|
|
144
|
-
// into the directory the `finally` below is about to remove.
|
|
145
|
-
const [platformResult, wrapperResult] = await Promise.allSettled([
|
|
146
|
-
fetchPackage(platformPkgName),
|
|
147
|
-
// v11 was the first release to keep files next to the executable; up to
|
|
148
|
-
// v10 the executable is self-contained and ships no `dist/`.
|
|
149
|
-
major >= 11 ? fetchPackage(wrapperPackageName(major)) : Promise.resolve(undefined),
|
|
150
|
-
]);
|
|
151
|
-
if (platformResult.status === 'rejected')
|
|
152
|
-
throw platformResult.reason;
|
|
153
|
-
if (wrapperResult.status === 'rejected')
|
|
154
|
-
throw wrapperResult.reason;
|
|
155
|
-
const platformPkg = platformResult.value;
|
|
156
|
-
const wrapperPkg = wrapperResult.value;
|
|
157
|
-
const binPath = path.join(dest, executable);
|
|
158
|
-
fs.renameSync(path.join(platformPkg.dir, executable), binPath);
|
|
159
|
-
fs.chmodSync(binPath, 0o755);
|
|
160
|
-
if (wrapperPkg != null) {
|
|
161
|
-
fs.rmSync(path.join(dest, 'dist'), { recursive: true, force: true });
|
|
162
|
-
fs.renameSync(path.join(wrapperPkg.dir, 'dist'), path.join(dest, 'dist'));
|
|
163
|
-
writeManifest({ dest, executable, version, wrapperDir: wrapperPkg.dir, major });
|
|
164
|
-
}
|
|
165
|
-
return { version, binPath };
|
|
166
|
-
}
|
|
167
|
-
finally {
|
|
168
|
-
// `pnpm setup` installs this directory as a package, so nothing may be left
|
|
169
|
-
// in it that does not belong in the installation.
|
|
170
|
-
fs.rmSync(path.join(dest, UNPACK_DIR), { recursive: true, force: true });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* `pnpm setup` installs the directory as a package, writing a minimal manifest
|
|
175
|
-
* when there is none — which is what the release tarball relies on. That
|
|
176
|
-
* tarball bundles the runtime dependencies inside `dist/`; the registry copy
|
|
177
|
-
* declares them instead, so up to v11 they have to be declared here or the
|
|
178
|
-
* install silently loses them (`@reflink/reflink`, and with it copy-on-write
|
|
179
|
-
* cloning). From v12 the `dist/` tree is self-contained again, so the manifest
|
|
180
|
-
* `setup` writes is left to it.
|
|
181
|
-
*/
|
|
182
|
-
function writeManifest(opts) {
|
|
183
|
-
if (opts.major >= 12)
|
|
184
|
-
return;
|
|
185
|
-
const wrapper = JSON.parse(fs.readFileSync(path.join(opts.wrapperDir, 'package.json'), 'utf8'));
|
|
186
|
-
if (wrapper.dependencies == null)
|
|
187
|
-
return;
|
|
188
|
-
fs.writeFileSync(path.join(opts.dest, 'package.json'), JSON.stringify({
|
|
189
|
-
name: '@pnpm/exe',
|
|
190
|
-
version: opts.version,
|
|
191
|
-
type: 'module',
|
|
192
|
-
bin: { pnpm: opts.executable, pn: opts.executable },
|
|
193
|
-
dependencies: wrapper.dependencies,
|
|
194
|
-
}));
|
|
195
|
-
}
|
|
196
|
-
/** Downloads packages of one version into one directory, verifying each. */
|
|
197
|
-
function verifiedPackageFetcher(opts) {
|
|
198
|
-
return async function fetchPackage(pkgName) {
|
|
199
|
-
const meta = await fetchVersionMeta(opts.registry, pkgName, opts.version);
|
|
200
|
-
if (!meta.dist.integrity) {
|
|
201
|
-
throw new Error(`The npm registry published no checksum for ${pkgName}@${opts.version}, so it cannot be verified.`);
|
|
202
|
-
}
|
|
203
|
-
verifyRegistrySignature({
|
|
204
|
-
name: pkgName,
|
|
205
|
-
version: opts.version,
|
|
206
|
-
integrity: meta.dist.integrity,
|
|
207
|
-
signatures: meta.dist.signatures,
|
|
208
|
-
keys: opts.keys,
|
|
209
|
-
});
|
|
210
|
-
// Unpack away from the directory being assembled: `pnpm` is both a package
|
|
211
|
-
// name and the name of the executable that ends up beside it.
|
|
212
|
-
const unpackDir = path.join(opts.dir, UNPACK_DIR, pkgName.replaceAll('/', '-'));
|
|
213
|
-
const tarball = `${unpackDir}.tgz`;
|
|
214
|
-
fs.mkdirSync(path.dirname(tarball), { recursive: true });
|
|
215
|
-
await downloadTarball(meta, tarball, { registry: opts.registry });
|
|
216
|
-
extractTarball(tarball, unpackDir);
|
|
217
|
-
fs.rmSync(tarball);
|
|
218
|
-
return { dir: path.join(unpackDir, 'package') };
|
|
219
|
-
};
|
|
220
|
-
}
|
|
3
|
+
export { downloadPnpm, runCli } from './install.js';
|
|
4
|
+
export { readProjectPin } from './projectPin.js';
|