pnpm 11.28.2 → 11.28.3

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 CHANGED
@@ -1,5 +1,88 @@
1
1
  # pnpm
2
2
 
3
+ ## 11.28.3
4
+
5
+ pnpm 11.28.3 updates `undici` to clear a security advisory, fixes "database disk image is malformed" errors when several pnpm processes share a store, and makes packages, catalogs, projects, and commands named like `constructor` work.
6
+
7
+ ### Patch Changes
8
+
9
+ #### Installing packages
10
+
11
+ - pnpm now ships `undici` 7.29.1, so security scans of pnpm no longer report [GHSA-3wwx-pv8p-q78v](https://github.com/advisories/GHSA-3wwx-pv8p-q78v).
12
+
13
+ - pnpm no longer fails with "database disk image is malformed" or reads stale store entries while another pnpm process writes to the same store.
14
+
15
+ - Names that match built-in JavaScript object properties, such as `constructor`, `toString`, or `__proto__`, now work like any other name. pnpm crashed, wrote a wrong lockfile, or silently skipped such names in:
16
+
17
+ - `pnpm add`, `pnpm install`, and `pnpm import`, for dependencies, peer dependencies, and `file:` dependencies that point to a directory named `constructor`.
18
+ - Catalog entries and catalog names. Pruning unused entries crashed, and a new catalog named `toString` was not written.
19
+ - Workspace projects, project directories, and files inside injected packages.
20
+ - Registry prefixes and override version references such as `$toString`.
21
+ - Hoisting, `pnpm list`, and `pnpm why`.
22
+ - Command names. `pnpm constructor` runs the `constructor` script like any other unknown command, and `pnpm help constructor` no longer crashes.
23
+ - Resolving through a pnpr server when a project lives in a directory named `constructor`.
24
+
25
+ - `pnpm install` no longer re-resolves an up-to-date lockfile on every run when a patched package is a peer in a peer cycle [#16418](https://github.com/pnpm/pnpm/issues/16418).
26
+
27
+ - POSIX bin shims and the `pnpm`, `pn`, `pnpx`, and `pnx` launchers now run inside a Nix build, where the system default path holds none of the utilities they call. Installing again replaces the shims already in `node_modules` [#16377](https://github.com/pnpm/pnpm/issues/16377).
28
+
29
+ - In a project that pins another pnpm version, pnpm now passes a command with an option it does not know to the pinned version. Before, `pnpm install --auto-dedupe` failed with "Unknown option" even though the pinned pnpm supports it [#16353](https://github.com/pnpm/pnpm/issues/16353).
30
+
31
+ - pnpm now fails with `ERR_PNPM_INVALID_ALLOW_BUILDS` when `allowBuilds` is not an object or one of its values is not `true`, `false`, or a string. Such values used to be ignored silently.
32
+
33
+ - Removing a dependency whose bins are declared through `directories.bin` no longer leaves broken shims in `node_modules/.bin`.
34
+
35
+ - A custom resolver's `shouldRefreshResolution` hook that rejects no longer crashes pnpm with an unhandled rejection when another hook has already asked for a refresh.
36
+
37
+ #### Updating dependencies
38
+
39
+ - When `minimumReleaseAge` hides the version that `latest` points to, pnpm now falls back to a prerelease of the same major before a stable version of an older major. A stable version of the same major is still preferred. For example, while a new `1.0.0` is too new, pnpm picks `1.0.0-beta.4` rather than an old `0.0.1` [#16388](https://github.com/pnpm/pnpm/issues/16388).
40
+
41
+ - `pnpm --filter <project> update <pkg>` now fails with `ERR_PNPM_NO_PACKAGE_IN_DEPENDENCIES` when the selected projects do not depend on `<pkg>`, also in a workspace with a shared lockfile and a root project. It used to exit successfully.
42
+
43
+ - `pnpm audit --fix` now updates vulnerable packages in a single project that sets `updateConfig.ignoreDependencies`. It used to leave them on the vulnerable version.
44
+
45
+ - `pnpm update --global` now removes hard-linked executables from `PNPM_HOME` when migrating packages from the old global layout [#16420](https://github.com/pnpm/pnpm/issues/16420).
46
+
47
+ - Updating a pinned GitHub Action now rewrites the version in its `# vX.Y.Z` comment even when the action name contains the same version text. The action name used to change while the comment kept the old version.
48
+
49
+ #### Workspaces and deploy
50
+
51
+ - `pnpm deploy` no longer fails with `ERR_PNPM_DEPLOY_AMBIGUOUS_PEER` in a workspace with `injectWorkspacePackages: true` when a workspace package also lists its peer dependency as a dev dependency [#16375](https://github.com/pnpm/pnpm/issues/16375).
52
+
53
+ - `pnpm deploy` no longer copies the workspace root's `packageManager` and `devEngines.packageManager` fields into the deployed `package.json` [#16403](https://github.com/pnpm/pnpm/issues/16403).
54
+
55
+ - `--filter` fixes:
56
+
57
+ - A `...pkg...` selector combined with another dependents selector, such as `--filter ...a --filter ...b...`, no longer adds the dependencies of the other selector's dependents.
58
+ - `--filter "[<since>]"` now detects changes in projects whose directory names contain non-ASCII characters. The change used to be credited to the parent project.
59
+
60
+ #### Running scripts
61
+
62
+ - After relaying a signal to a script, pnpm keeps waiting for a process in the script's process group whose main thread has exited while its other threads still run. Linux reports such a process as a zombie, so the wait used to end before those threads finished [pnpm/tasks#56](https://github.com/pnpm/tasks/issues/56).
63
+
64
+ - A lifecycle script run with `unsafePerm: false` now fails with an error when pnpm cannot create `node_modules/.tmp`. It used to hang.
65
+
66
+ - `pnpm run` with `verifyDepsBeforeRun` no longer crashes with an unhandled rejection when a lockfile it did not need to compare fails to load.
67
+
68
+ - `pnpm run -r` now closes the collapsible CI log section of a project whose script fails, so the output of later projects is no longer nested inside it.
69
+
70
+ - `pnpm run --resume-from` no longer crashes when a saved run state file contains `null`.
71
+
72
+ #### Store
73
+
74
+ - `pnpm store prune` now removes the packages that only expired `pnpm dlx` cache entries used, as long as the store still has another registered project. They used to stay until the next `pnpm store prune` [#16383](https://github.com/pnpm/pnpm/discussions/16383).
75
+
76
+ - `pnpm store prune` now stops with an error when it cannot read a project directory for a reason other than the directory missing, such as a permission error. It used to skip the directory.
77
+
78
+ #### Publishing and registry output
79
+
80
+ - `pnpm publish` now includes bare `README` files and README files with Markdown extensions such as `readme.markdown` in registry metadata [#12704](https://github.com/pnpm/pnpm/issues/12704).
81
+
82
+ - `pnpm pack-app` now accepts an entry file or output directory inside the project whose name starts with two dots, such as `..build/entry.cjs`. It used to fail with `ERR_PNPM_PACK_APP_ENTRY_OUTSIDE_PROJECT`.
83
+
84
+ - Registry error messages now always say "(response body truncated)" when pnpm cut the response body short. The marker was missing when the body was cut at exactly 64 KiB.
85
+
3
86
  ## 11.28.2
4
87
 
5
88
  pnpm 11.28.2 fixes `pnpm install` skipping every workspace project whose common ancestor is the filesystem root, and stops `pnpm run` from reinstalling or installing when nothing needs it.
@@ -43,6 +43,8 @@ while [ -L "$link" ] && [ "$hops" -lt 40 ]; do
43
43
  done
44
44
  basedir=$(command -p printf '%s\n' "$link" | command -p sed -e 's,\\,/,g')
45
45
  basedir="${basedir%/*}"
46
+ basedir_abs=$(CDPATH= cd -P -- "$basedir" && pwd -P) || exit $?
47
+ basedir="$basedir_abs"
46
48
  basedir_win="$basedir"
47
49
  exe=""
48
50
  msys=""
@@ -73,11 +75,12 @@ case `command -p uname -a` in
73
75
  esac
74
76
  if [ -n "$caller_path_set" ]; then PATH=$caller_path; else unset PATH; fi
75
77
 
76
- basedir_abs=$(CDPATH= cd -P -- "$basedir" && pwd -P) || exit $?
77
78
  if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then
78
79
  exec "$basedir/node.exe" "$basedir_win/../node-gyp/bin/node-gyp.js" "$@"
79
80
  elif [ -x "$basedir/node" ]; then
80
81
  exec "$basedir/node" "$basedir_abs/../node-gyp/bin/node-gyp.js" "$@"
82
+ elif [ -n "$msys" ] && command -v node >/dev/null 2>&1; then
83
+ exec node "$basedir_win/../node-gyp/bin/node-gyp.js" "$@"
81
84
  elif command -v node >/dev/null 2>&1; then
82
85
  exec node "$basedir_abs/../node-gyp/bin/node-gyp.js" "$@"
83
86
  elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then
@@ -43,6 +43,8 @@ while [ -L "$link" ] && [ "$hops" -lt 40 ]; do
43
43
  done
44
44
  basedir=$(command -p printf '%s\n' "$link" | command -p sed -e 's,\\,/,g')
45
45
  basedir="${basedir%/*}"
46
+ basedir_abs=$(CDPATH= cd -P -- "$basedir" && pwd -P) || exit $?
47
+ basedir="$basedir_abs"
46
48
  basedir_win="$basedir"
47
49
  exe=""
48
50
  msys=""
@@ -73,11 +75,12 @@ case `command -p uname -a` in
73
75
  esac
74
76
  if [ -n "$caller_path_set" ]; then PATH=$caller_path; else unset PATH; fi
75
77
 
76
- basedir_abs=$(CDPATH= cd -P -- "$basedir" && pwd -P) || exit $?
77
78
  if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then
78
79
  exec "$basedir/node.exe" "$basedir_win/../which/bin/which.js" "$@"
79
80
  elif [ -x "$basedir/node" ]; then
80
81
  exec "$basedir/node" "$basedir_abs/../which/bin/which.js" "$@"
82
+ elif [ -n "$msys" ] && command -v node >/dev/null 2>&1; then
83
+ exec node "$basedir_win/../which/bin/which.js" "$@"
81
84
  elif command -v node >/dev/null 2>&1; then
82
85
  exec node "$basedir_abs/../which/bin/which.js" "$@"
83
86
  elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then
@@ -43,6 +43,8 @@ while [ -L "$link" ] && [ "$hops" -lt 40 ]; do
43
43
  done
44
44
  basedir=$(command -p printf '%s\n' "$link" | command -p sed -e 's,\\,/,g')
45
45
  basedir="${basedir%/*}"
46
+ basedir_abs=$(CDPATH= cd -P -- "$basedir" && pwd -P) || exit $?
47
+ basedir="$basedir_abs"
46
48
  basedir_win="$basedir"
47
49
  exe=""
48
50
  msys=""
@@ -73,11 +75,12 @@ case `command -p uname -a` in
73
75
  esac
74
76
  if [ -n "$caller_path_set" ]; then PATH=$caller_path; else unset PATH; fi
75
77
 
76
- basedir_abs=$(CDPATH= cd -P -- "$basedir" && pwd -P) || exit $?
77
78
  if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then
78
79
  exec "$basedir/node.exe" "$basedir_win/../nopt/bin/nopt.js" "$@"
79
80
  elif [ -x "$basedir/node" ]; then
80
81
  exec "$basedir/node" "$basedir_abs/../nopt/bin/nopt.js" "$@"
82
+ elif [ -n "$msys" ] && command -v node >/dev/null 2>&1; then
83
+ exec node "$basedir_win/../nopt/bin/nopt.js" "$@"
81
84
  elif command -v node >/dev/null 2>&1; then
82
85
  exec node "$basedir_abs/../nopt/bin/nopt.js" "$@"
83
86
  elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then
@@ -43,6 +43,8 @@ while [ -L "$link" ] && [ "$hops" -lt 40 ]; do
43
43
  done
44
44
  basedir=$(command -p printf '%s\n' "$link" | command -p sed -e 's,\\,/,g')
45
45
  basedir="${basedir%/*}"
46
+ basedir_abs=$(CDPATH= cd -P -- "$basedir" && pwd -P) || exit $?
47
+ basedir="$basedir_abs"
46
48
  basedir_win="$basedir"
47
49
  exe=""
48
50
  msys=""
@@ -73,11 +75,12 @@ case `command -p uname -a` in
73
75
  esac
74
76
  if [ -n "$caller_path_set" ]; then PATH=$caller_path; else unset PATH; fi
75
77
 
76
- basedir_abs=$(CDPATH= cd -P -- "$basedir" && pwd -P) || exit $?
77
78
  if [ -n "$exe" ] && [ -x "$basedir/node.exe" ]; then
78
79
  exec "$basedir/node.exe" "$basedir_win/../semver/bin/semver.js" "$@"
79
80
  elif [ -x "$basedir/node" ]; then
80
81
  exec "$basedir/node" "$basedir_abs/../semver/bin/semver.js" "$@"
82
+ elif [ -n "$msys" ] && command -v node >/dev/null 2>&1; then
83
+ exec node "$basedir_win/../semver/bin/semver.js" "$@"
81
84
  elif command -v node >/dev/null 2>&1; then
82
85
  exec node "$basedir_abs/../semver/bin/semver.js" "$@"
83
86
  elif [ -n "$exe" ] && command -v node.exe >/dev/null 2>&1; then
@@ -263,11 +263,77 @@ class Request {
263
263
  }
264
264
  }
265
265
 
266
- onUpgrade (statusCode, headers, socket) {
266
+ /**
267
+ * @param {number|null} statusCode
268
+ * @param {Buffer[]|null} headers
269
+ * @param {import('node:stream').Duplex} socket
270
+ * @param {string} [statusText]
271
+ */
272
+ onUpgrade (statusCode, headers, socket, statusText = '') {
273
+ this.onFinally()
274
+
267
275
  assert(!this.aborted)
268
276
  assert(!this.completed)
269
277
 
270
- return this[kHandler].onUpgrade(statusCode, headers, socket)
278
+ if (statusCode !== null) {
279
+ this.#publishUpgradeHeaders(statusCode, headers, statusText)
280
+ }
281
+
282
+ const result = this[kHandler].onUpgrade(statusCode, headers, socket)
283
+
284
+ if (!this.aborted) {
285
+ this.completed = true
286
+ if (statusCode !== null) {
287
+ this.#publishUpgradeTrailers()
288
+ }
289
+ }
290
+
291
+ return result
292
+ }
293
+
294
+ /**
295
+ * @param {number} statusCode
296
+ * @param {import('node:http2').IncomingHttpHeaders} headers
297
+ * @param {(headers: import('node:http2').IncomingHttpHeaders) => Buffer[]} parseHeaders
298
+ * @param {string} [statusText]
299
+ */
300
+ onUpgradeResponse (statusCode, headers, parseHeaders, statusText = '') {
301
+ assert(!this.aborted)
302
+ assert(this.completed)
303
+
304
+ if (channels.headers.hasSubscribers) {
305
+ this.#publishUpgradeHeaders(statusCode, parseHeaders(headers), statusText)
306
+ }
307
+ this.#publishUpgradeTrailers()
308
+ }
309
+
310
+ /**
311
+ * @param {Error} error
312
+ */
313
+ onUpgradeError (error) {
314
+ assert(!this.aborted)
315
+ assert(this.completed)
316
+
317
+ if (channels.error.hasSubscribers) {
318
+ channels.error.publish({ request: this, error })
319
+ }
320
+ }
321
+
322
+ /**
323
+ * @param {number} statusCode
324
+ * @param {Buffer[]} headers
325
+ * @param {string} statusText
326
+ */
327
+ #publishUpgradeHeaders (statusCode, headers, statusText) {
328
+ if (channels.headers.hasSubscribers) {
329
+ channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
330
+ }
331
+ }
332
+
333
+ #publishUpgradeTrailers () {
334
+ if (channels.trailers.hasSubscribers) {
335
+ channels.trailers.publish({ request: this, trailers: [] })
336
+ }
271
337
  }
272
338
 
273
339
  onComplete (trailers) {
@@ -432,7 +432,7 @@ class Parser {
432
432
  }
433
433
 
434
434
  onUpgrade (head) {
435
- const { upgrade, client, socket, headers, statusCode } = this
435
+ const { upgrade, client, socket, headers, statusCode, statusText } = this
436
436
 
437
437
  assert(upgrade)
438
438
  assert(client[kSocket] === socket)
@@ -467,9 +467,10 @@ class Parser {
467
467
  client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
468
468
 
469
469
  try {
470
- request.onUpgrade(statusCode, headers, socket)
471
- } catch (err) {
472
- util.destroy(socket, err)
470
+ request.onUpgrade(statusCode, headers, socket, statusText)
471
+ } catch (error) {
472
+ util.errorRequest(client, request, error)
473
+ util.destroy(socket, error)
473
474
  }
474
475
 
475
476
  client[kResume]()
@@ -1050,12 +1051,22 @@ function writeH1 (client, request) {
1050
1051
  const socket = client[kSocket]
1051
1052
  clearIdleSocketValidation(socket)
1052
1053
 
1053
- const abort = (err) => {
1054
- if (request.aborted || request.completed) {
1054
+ /**
1055
+ * @param {Error} [error]
1056
+ */
1057
+ const abort = (error) => {
1058
+ if (request.aborted) {
1059
+ return
1060
+ }
1061
+
1062
+ if (request.completed) {
1063
+ if (request.upgrade || request.method === 'CONNECT') {
1064
+ util.destroy(socket, new InformationalError('aborted'))
1065
+ }
1055
1066
  return
1056
1067
  }
1057
1068
 
1058
- util.errorRequest(client, request, err || new RequestAbortedError())
1069
+ util.errorRequest(client, request, error || new RequestAbortedError())
1059
1070
 
1060
1071
  util.destroy(body)
1061
1072
  util.destroy(socket, new InformationalError('aborted'))
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const assert = require('node:assert')
4
+ const { errorMonitor } = require('node:events')
4
5
  const { pipeline } = require('node:stream')
5
6
  const util = require('../core/util.js')
6
7
  const {
@@ -77,6 +78,15 @@ function parseH2Headers (headers) {
77
78
  return result
78
79
  }
79
80
 
81
+ /**
82
+ * @param {import('node:http2').IncomingHttpHeaders} headers
83
+ * @returns {Buffer[]}
84
+ */
85
+ function parseH2ResponseHeaders (headers) {
86
+ const { [HTTP2_HEADER_STATUS]: _statusCode, ...realHeaders } = headers
87
+ return parseH2Headers(realHeaders)
88
+ }
89
+
80
90
  async function connectH2 (client, socket) {
81
91
  client[kSocket] = socket
82
92
 
@@ -297,22 +307,32 @@ function writeH2 (client, request) {
297
307
  headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
298
308
  headers[HTTP2_HEADER_METHOD] = method
299
309
 
300
- const abort = (err) => {
301
- if (request.aborted || request.completed) {
310
+ /**
311
+ * @param {Error} [error]
312
+ */
313
+ const abort = (error) => {
314
+ if (request.aborted) {
315
+ return
316
+ }
317
+
318
+ if (request.completed) {
319
+ if (method === 'CONNECT' && stream != null) {
320
+ util.destroy(stream, error || new RequestAbortedError())
321
+ }
302
322
  return
303
323
  }
304
324
 
305
- err = err || new RequestAbortedError()
325
+ error = error || new RequestAbortedError()
306
326
 
307
- util.errorRequest(client, request, err)
327
+ util.errorRequest(client, request, error)
308
328
 
309
329
  if (stream != null) {
310
- util.destroy(stream, err)
330
+ util.destroy(stream, error)
311
331
  }
312
332
 
313
333
  // We do not destroy the socket as we can continue using the session
314
334
  // the stream get's destroyed and the session remains to create new streams
315
- util.destroy(body, err)
335
+ util.destroy(body, error)
316
336
  client[kQueue][client[kRunningIdx]++] = null
317
337
  client[kResume]()
318
338
  }
@@ -331,25 +351,57 @@ function writeH2 (client, request) {
331
351
 
332
352
  if (method === 'CONNECT') {
333
353
  session.ref()
334
- // We are already connected, streams are pending, first request
335
- // will create a new stream. We trigger a request to create the stream and wait until
336
- // `ready` event is triggered
337
354
  // We disabled endStream to allow the user to write to the stream
338
355
  stream = session.request(headers, { endStream: false, signal })
356
+ let upgradeResponseFinished = false
357
+
358
+ /**
359
+ * @param {import('node:http2').IncomingHttpHeaders} headers
360
+ */
361
+ const onResponse = (headers) => {
362
+ upgradeResponseFinished = true
363
+ stream.off(errorMonitor, onUpgradeError)
364
+ request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders)
365
+ }
339
366
 
340
- if (stream.id && !stream.pending) {
341
- request.onUpgrade(null, null, stream)
342
- ++session[kOpenStreams]
343
- client[kQueue][client[kRunningIdx]++] = null
344
- } else {
345
- stream.once('ready', () => {
367
+ /**
368
+ * @param {Error} error
369
+ */
370
+ const onUpgradeError = (error) => {
371
+ upgradeResponseFinished = true
372
+ stream.off('response', onResponse)
373
+ request.onUpgradeError(error)
374
+ }
375
+
376
+ const onReady = () => {
377
+ try {
346
378
  request.onUpgrade(null, null, stream)
347
- ++session[kOpenStreams]
348
- client[kQueue][client[kRunningIdx]++] = null
349
- })
379
+ } catch (error) {
380
+ stream.off('response', onResponse)
381
+ abort(error)
382
+ return
383
+ }
384
+
385
+ if (request.aborted) {
386
+ return
387
+ }
388
+
389
+ stream.off('error', abort)
390
+ stream.once(errorMonitor, onUpgradeError)
391
+ client[kQueue][client[kRunningIdx]++] = null
350
392
  }
351
393
 
394
+ stream.once('response', onResponse)
395
+ stream.once('error', abort)
396
+ ++session[kOpenStreams]
397
+ onReady()
398
+
352
399
  stream.once('close', () => {
400
+ if (!upgradeResponseFinished && request.completed) {
401
+ stream.off('response', onResponse)
402
+ stream.off(errorMonitor, onUpgradeError)
403
+ request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
404
+ }
353
405
  session[kOpenStreams] -= 1
354
406
  if (session[kOpenStreams] === 0) session.unref()
355
407
  })
@@ -205,7 +205,10 @@ class RetryHandler {
205
205
  this.retryCount += 1
206
206
 
207
207
  if (statusCode >= 300) {
208
- if (this.retryOpts.statusCodes.includes(statusCode) === false) {
208
+ // Only expose a response if no earlier attempt has reached the caller.
209
+ // Otherwise abort this attempt so the error settles the existing body
210
+ // instead of replacing it with a new response.
211
+ if (!this.headersSent && this.retryOpts.statusCodes.includes(statusCode) === false) {
209
212
  this.headersSent = true
210
213
  this.checkpointResponseEnd(headers, resume)
211
214
  return this.handler.onHeaders(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.28.1",
3
+ "version": "6.29.0",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {