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 +83 -0
- package/dist/node_modules/.bin/node-gyp +4 -1
- package/dist/node_modules/.bin/node-which +4 -1
- package/dist/node_modules/.bin/nopt +4 -1
- package/dist/node_modules/.bin/semver +4 -1
- package/dist/node_modules/undici/lib/core/request.js +68 -2
- package/dist/node_modules/undici/lib/dispatcher/client-h1.js +18 -7
- package/dist/node_modules/undici/lib/dispatcher/client-h2.js +70 -18
- package/dist/node_modules/undici/lib/handler/retry-handler.js +4 -1
- package/dist/node_modules/undici/package.json +1 -1
- package/dist/pnpm.mjs +163533 -154270
- package/dist/worker.js +12013 -11860
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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 (
|
|
472
|
-
util.
|
|
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
|
-
|
|
1054
|
-
|
|
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,
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
325
|
+
error = error || new RequestAbortedError()
|
|
306
326
|
|
|
307
|
-
util.errorRequest(client, request,
|
|
327
|
+
util.errorRequest(client, request, error)
|
|
308
328
|
|
|
309
329
|
if (stream != null) {
|
|
310
|
-
util.destroy(stream,
|
|
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,
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
stream.
|
|
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
|
-
|
|
348
|
-
|
|
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
|
|
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(
|