@pnpm/exe 12.2.1 → 12.3.1

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/bin/pnpm.mjs CHANGED
@@ -3,14 +3,17 @@
3
3
  // `./bin/pnpx.mjs` for every pnpm >=11 (see its `config.json`) and loads them
4
4
  // into its own Node.js process, which a native executable cannot be loaded into.
5
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.
6
+ // The `pnpm` placeholder bin runs it too, when the install script that
7
+ // replaces it with the native binary did not run (build scripts blocked). An
8
+ // ordinary `npm install -g pnpm` never pays for a Node.js startup:
9
+ // `package.json#bin` points at the native binary.
8
10
  //
9
11
  // Corepack installs no dependencies and runs no lifecycle scripts, so the
10
12
  // `@pnpm/exe.<target>` package that carries the binary is absent and
11
13
  // `install.js` never ran. The binary is therefore downloaded on first use and
12
14
  // kept next to this wrapper — where the native binary also finds the `dist/`
13
- // payload it ships node-gyp in.
15
+ // payload it ships node-gyp in. The placeholder's installs usually do carry
16
+ // that package, and the binary is taken from there.
14
17
  //
15
18
  // The download itself is `get-pnpm`, the package behind https://get.pnpm.io,
16
19
  // which already knows how to verify one; it travels in that same `dist/`
@@ -1,9 +1,9 @@
1
1
  {
2
- "lastValidatedTimestamp": 1788223005630,
2
+ "lastValidatedTimestamp": 1788394737104,
3
3
  "projects": {
4
4
  "/home/runner/_work/pnpm/pnpm/pnpm/npm/pnpm/temp-deploy": {
5
5
  "name": "pacquet",
6
- "version": "12.2.1"
6
+ "version": "12.3.1"
7
7
  }
8
8
  },
9
9
  "pnpmfiles": [],
@@ -87,7 +87,7 @@
87
87
  "@types/write-file-atomic": "^4.0.3",
88
88
  "@types/yarnpkg__lockfile": "^1.1.9",
89
89
  "@types/zkochan__table": "npm:@types/table@6.3.2",
90
- "@typescript-eslint/utils": "^8.68.0",
90
+ "@typescript-eslint/utils": "^8.69.0",
91
91
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
92
92
  "@yarnpkg/core": "4.9.1",
93
93
  "@yarnpkg/extensions": "2.0.7",
@@ -132,7 +132,7 @@
132
132
  "escape-string-regexp": "^5.0.0",
133
133
  "eslint": "^10.9.1",
134
134
  "eslint-plugin-import-x": "^4.17.1",
135
- "eslint-plugin-jest": "^29.16.5",
135
+ "eslint-plugin-jest": "^29.16.6",
136
136
  "eslint-plugin-n": "^18.3.0",
137
137
  "eslint-plugin-promise": "^7.3.0",
138
138
  "eslint-plugin-regexp": "^3.1.1",
@@ -190,7 +190,7 @@
190
190
  "p-defer": "^4.0.1",
191
191
  "p-every": "^2.0.0",
192
192
  "p-filter": "^4.1.0",
193
- "p-limit": "^7.3.1",
193
+ "p-limit": "^7.3.2",
194
194
  "p-queue": "^9.3.3",
195
195
  "parse-json": "^8.3.0",
196
196
  "parse-npm-tarball-url": "^5.0.0",
@@ -247,7 +247,7 @@
247
247
  "tree-kill": "^1.2.2",
248
248
  "ts-jest-resolver": "2.0.1",
249
249
  "typescript": "6.0.3",
250
- "typescript-eslint": "^8.68.0",
250
+ "typescript-eslint": "^8.69.0",
251
251
  "undici": "^7.29.0",
252
252
  "unified": "^11.0.5",
253
253
  "validate-npm-package-name": "7.0.2",
package/install.js CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // Preinstall for the pnpm v12 wrapper (shared verbatim by `pnpm` and
3
- // `@pnpm/exe`): replace the shebang-less placeholder bins with the host's native
4
- // binary so `pnpm` runs directly, no Node startup per call. A placeholder (not a
5
- // Node launcher) is required because the Windows shim is generated from the bin
6
- // file and npm won't re-read package.json after preinstall; the tradeoff is no
7
- // fallback when build scripts are blocked (`--ignore-scripts`, pnpm/Bun default).
3
+ // `@pnpm/exe`): replace the placeholder bins with the host's native binary so
4
+ // `pnpm` runs directly, no Node startup per call. Package managers write bin
5
+ // shims after preinstall, so the shims are made from the binary; where build
6
+ // scripts are blocked (`--ignore-scripts`, the pnpm/Bun default) the
7
+ // placeholder stays and runs pnpm through Node.js (see the `pnpm` file). npm
8
+ // does not re-read the rewritten `bin` during the same install pass, so on a
9
+ // global Windows install, postinstall asks it to relink.
8
10
  //
9
11
  // `pn`/`pnpx`/`pnx` are committed `#!/bin/sh` scripts on Unix (so only `pnpm` is
10
12
  // relinked); on Windows the native binary is hardlinked onto each and
@@ -14,6 +16,7 @@
14
16
  // Corepack runs no lifecycle scripts, so it never gets here; it enters through
15
17
  // `bin/pnpm.mjs` instead.
16
18
  import console from 'node:console'
19
+ import { spawnSync } from 'node:child_process'
17
20
  import fs from 'node:fs'
18
21
  import path from 'node:path'
19
22
  import process from 'node:process'
@@ -27,7 +30,11 @@ import {
27
30
 
28
31
  const BIN_NAMES = ['pnpm', 'pn', 'pnpx', 'pnx']
29
32
 
30
- setup()
33
+ if (process.env.npm_lifecycle_event === 'postinstall') {
34
+ relinkNpmWindowsShims()
35
+ } else {
36
+ setup()
37
+ }
31
38
 
32
39
  function setup () {
33
40
  // The committed manifest has no `optionalDependencies`; generate-packages.mjs
@@ -93,9 +100,7 @@ function placeBinary (nativeBinary, destPath, mode) {
93
100
  }
94
101
  fs.renameSync(tempPath, destPath)
95
102
  } catch (err) {
96
- try {
97
- fs.rmSync(tempPath, { force: true })
98
- } catch {}
103
+ removeFileIfPossible(tempPath)
99
104
  fail(`Could not install the pnpm binary at ${destPath}: ${err.message}`)
100
105
  }
101
106
  }
@@ -110,9 +115,45 @@ function rewriteBin (binMap) {
110
115
  fs.writeFileSync(tempPath, JSON.stringify(pkg, null, 2))
111
116
  fs.renameSync(tempPath, pkgJsonPath)
112
117
  } catch {
113
- try {
114
- fs.rmSync(tempPath, { force: true })
115
- } catch {}
118
+ removeFileIfPossible(tempPath)
119
+ }
120
+ }
121
+
122
+ function relinkNpmWindowsShims () {
123
+ const npmExecPath = process.env.npm_execpath
124
+ if (
125
+ process.platform !== 'win32' ||
126
+ process.env.npm_config_global !== 'true' ||
127
+ npmExecPath == null ||
128
+ path.basename(npmExecPath).toLowerCase() !== 'npm-cli.js'
129
+ ) {
130
+ return
131
+ }
132
+
133
+ const packageName = readWrapperManifest().name
134
+ if (typeof packageName !== 'string') {
135
+ fail('Could not determine the pnpm wrapper package name when regenerating npm shims.')
136
+ }
137
+ const result = spawnSync(process.execPath, [
138
+ npmExecPath,
139
+ 'rebuild',
140
+ '--global',
141
+ '--ignore-scripts',
142
+ packageName,
143
+ ], { stdio: 'inherit' })
144
+ if (result.error != null) {
145
+ fail(`Could not regenerate the npm shims for pnpm: ${result.error.message}`)
146
+ }
147
+ if (result.status !== 0) {
148
+ fail('npm could not regenerate the shims for pnpm.')
149
+ }
150
+ }
151
+
152
+ function removeFileIfPossible (filePath) {
153
+ try {
154
+ fs.rmSync(filePath, { force: true })
155
+ } catch {
156
+ return
116
157
  }
117
158
  }
118
159
 
package/native-binary.mjs CHANGED
@@ -2,12 +2,10 @@
2
2
  // (`install.js`), which links it over the placeholder bins, and by the Corepack
3
3
  // entry (`bin/pnpm.mjs`), which spawns it.
4
4
  import fs from 'node:fs'
5
- import { createRequire } from 'node:module'
6
5
  import path from 'node:path'
7
6
  import process from 'node:process'
8
7
  import { fileURLToPath } from 'node:url'
9
8
 
10
- const require = createRequire(import.meta.url)
11
9
  const { platform, arch } = process
12
10
 
13
11
  /** Directory of the published wrapper; the native binary lives next to it. */
@@ -69,7 +67,14 @@ export function splitBinSpecifier (specifier) {
69
67
 
70
68
  /**
71
69
  * Path to the native binary of whichever platform package the package manager
72
- * installed, or `null` when none is present (Corepack, `--no-optional`).
70
+ * installed with this wrapper, or `null` when none is present (Corepack,
71
+ * `--no-optional`).
72
+ *
73
+ * Only the wrapper's own `node_modules` and the one it was installed into are
74
+ * searched, which is where every package manager puts an optional dependency
75
+ * of the wrapper. A `require` walk would also reach the ancestors' `node_modules`,
76
+ * which belong to whatever project the wrapper sits under and are not to be
77
+ * run in its name.
73
78
  *
74
79
  * @returns {string | null}
75
80
  */
@@ -77,13 +82,34 @@ export function resolveInstalledBinary () {
77
82
  // Use whichever platform package the package manager installed: it already
78
83
  // filtered by `os`/`cpu`/`libc`, more reliable than re-deriving the host.
79
84
  for (const specifier of getBinCandidates()) {
80
- try {
81
- return require.resolve(specifier)
82
- } catch {}
85
+ const { packageName, binFile } = splitBinSpecifier(specifier)
86
+ for (const modulesDir of installedModulesDirs()) {
87
+ const candidate = path.join(modulesDir, ...packageName.split('/'), binFile)
88
+ if (fs.statSync(candidate, { throwIfNoEntry: false })?.isFile() === true) {
89
+ return candidate
90
+ }
91
+ }
83
92
  }
84
93
  return null
85
94
  }
86
95
 
96
+ /**
97
+ * Where a platform package installed for this wrapper can be: the `node_modules`
98
+ * nested in the wrapper, then the one the wrapper was installed into (two levels
99
+ * up for the scoped `@pnpm/exe`). Neither need exist. Throws only when the
100
+ * wrapper's manifest is unreadable, see {@link readWrapperManifest}.
101
+ *
102
+ * @returns {string[]}
103
+ */
104
+ function installedModulesDirs () {
105
+ const { name } = readWrapperManifest()
106
+ const segments = typeof name === 'string' ? name.split('/').length : 1
107
+ return [
108
+ path.join(wrapperDir, 'node_modules'),
109
+ path.resolve(wrapperDir, ...Array(segments).fill('..')),
110
+ ]
111
+ }
112
+
87
113
  // A successful read with no optionalDependencies is the dev checkout (there is
88
114
  // no native binary to link there); a read/parse failure is a corrupt published
89
115
  // package and must not be silently swallowed into that same path.
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "./dist/node_modules/node-gyp/gyp/gyp"
10
10
  ]
11
11
  },
12
- "version": "12.2.1",
12
+ "version": "12.3.1",
13
13
  "description": "Fast, disk space efficient package manager",
14
14
  "keywords": [
15
15
  "pnpm",
@@ -46,17 +46,18 @@
46
46
  "THIRD-PARTY-NOTICES.md"
47
47
  ],
48
48
  "optionalDependencies": {
49
- "@pnpm/exe.win32-x64": "12.2.1",
50
- "@pnpm/exe.win32-arm64": "12.2.1",
51
- "@pnpm/exe.darwin-x64": "12.2.1",
52
- "@pnpm/exe.darwin-arm64": "12.2.1",
53
- "@pnpm/exe.linux-x64": "12.2.1",
54
- "@pnpm/exe.linux-arm64": "12.2.1",
55
- "@pnpm/exe.linux-x64-musl": "12.2.1",
56
- "@pnpm/exe.linux-arm64-musl": "12.2.1"
49
+ "@pnpm/exe.win32-x64": "12.3.1",
50
+ "@pnpm/exe.win32-arm64": "12.3.1",
51
+ "@pnpm/exe.darwin-x64": "12.3.1",
52
+ "@pnpm/exe.darwin-arm64": "12.3.1",
53
+ "@pnpm/exe.linux-x64": "12.3.1",
54
+ "@pnpm/exe.linux-arm64": "12.3.1",
55
+ "@pnpm/exe.linux-x64-musl": "12.3.1",
56
+ "@pnpm/exe.linux-arm64-musl": "12.3.1"
57
57
  },
58
58
  "scripts": {
59
59
  "preinstall": "node install.js",
60
+ "postinstall": "node install.js",
60
61
  "bundle-node-gyp": "node scripts/bundle-node-gyp.mjs"
61
62
  }
62
63
  }
package/pnpm CHANGED
@@ -1,4 +1,21 @@
1
- This is a placeholder. pnpm's native binary replaces this file during
2
- installation (see ./install.js). If you are reading this, the install/build
3
- script did not run reinstall with build scripts enabled (e.g. allow-list
4
- pnpm's build under pnpm or Bun, or drop --ignore-scripts).
1
+ #!/usr/bin/env node
2
+ // pnpm's native binary replaces this file during installation (see
3
+ // ./install.js). If this is running, that install script did not — build
4
+ // scripts were blocked (pnpm and Bun block them by default) or skipped
5
+ // (`--ignore-scripts`) — so pnpm runs through Node.js instead: `bin/pnpm.mjs`
6
+ // finds the installed binary, or downloads one, and spawns it.
7
+ //
8
+ // Package managers write bin shims after preinstall has had its chance to run,
9
+ // so a shim made from this file's shebang exists only where the binary never
10
+ // arrived. That is also why this file is left in place once it is running: a
11
+ // binary put here under such a shim would be run through `node`.
12
+ import process from 'node:process'
13
+
14
+ if (process.stderr.isTTY) {
15
+ process.stderr.write(
16
+ 'pnpm is running through Node.js because the script that installs its native binary was skipped. ' +
17
+ 'Reinstall pnpm with its build scripts allowed to run the binary directly.\n'
18
+ )
19
+ }
20
+
21
+ await import('./bin/pnpm.mjs')