pnpm 12.3.4 → 12.4.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 +101 -0
- package/README.md +11 -15
- package/dist/node_modules/.pnpm-workspace-state-v1.json +4 -5
- package/dist/node_modules/undici/lib/dispatcher/client-h1.js +12 -4
- package/dist/node_modules/undici/lib/handler/retry-handler.js +29 -3
- package/dist/node_modules/undici/lib/web/eventsource/eventsource-stream.js +245 -150
- package/dist/node_modules/undici/lib/web/websocket/connection.js +1 -1
- package/dist/node_modules/undici/lib/web/websocket/permessage-deflate.js +5 -0
- package/dist/node_modules/undici/package.json +1 -1
- package/install.js +2 -1
- package/native-binary.mjs +52 -5
- package/package.json +15 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,106 @@
|
|
|
1
1
|
# pnpm
|
|
2
2
|
|
|
3
|
+
## 12.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- pnpm can now manage npm, Python, and Cargo dependencies in the same workspace. Enable `python.enabled` or `cargo.enabled` in `pnpm-workspace.yaml`, then use `pnpm install` to install them together.
|
|
8
|
+
|
|
9
|
+
- Add Python packages with `pnpm add pypi:<package>`. pnpm uses `pyproject.toml`, `pylock.toml`, and a managed `.venv`. Frozen and offline installs are supported, and `pnpm run` and `pnpm exec` make the environment's executables available [#14566](https://github.com/pnpm/pnpm/issues/14566).
|
|
10
|
+
- Add Rust crates with `pnpm add crate:<package>`. pnpm supports crates.io and custom sparse registries configured with `cargo.indexUrl`. Registry authentication supports pnpm credentials and, for crates.io, `CARGO_REGISTRY_TOKEN` or `$CARGO_HOME/credentials.toml`.
|
|
11
|
+
|
|
12
|
+
Both ecosystems support faster dependency resolution through `pnprServer`, with local resolution as a fallback when the server does not support it.
|
|
13
|
+
|
|
14
|
+
- Added `pnpm pipeline [name]` to install frozen dependencies and run workspace tasks declared in `pipelines`. It selects affected projects, runs their task graph, and continues running tasks after a task fails.
|
|
15
|
+
|
|
16
|
+
Tasks support `inputs`, `outputs`, `env`, and `cache` settings. Cached results restore task outputs and replay logs. Cargo tasks can reuse local build state between worktrees with `tasks.<name>.cargoTargetDir`. Set `includeWorkspaceRoot: true` to include root tasks.
|
|
17
|
+
|
|
18
|
+
Use `pnpm pipeline --dry-run` to preview the task graph without installing configuration dependencies or running workspace hooks.
|
|
19
|
+
|
|
20
|
+
- Added support for Android on arm64 and x64, FreeBSD on x64, and Linux on ppc64le, s390x, and RISC-V (riscv64 with glibc) [#14431](https://github.com/pnpm/pnpm/issues/14431), [#14597](https://github.com/pnpm/pnpm/issues/14597), [#7582](https://github.com/pnpm/pnpm/issues/7582).
|
|
21
|
+
|
|
22
|
+
- Added `trustPolicyExcludePrune` to automatically remove unused versions and packages from `trustPolicyExclude` when running `pnpm add`, `pnpm update`, or `pnpm remove`. It is disabled by default. Package name patterns such as `@scope/*` are kept, and cleanup is skipped when `sharedWorkspaceLockfile` is `false`.
|
|
23
|
+
|
|
24
|
+
- Added `pnpm change check` for CI validation of package versions against the `versioning.epics` bands and `versioning.fixed` groups in `pnpm-workspace.yaml`. It reports all violations, including packages that are not part of the current release.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Registry metadata is now kept separate for registries with different URL paths or schemes. This prevents installs from using another registry's package versions or tarball URLs, and keeps metadata fetched over HTTP from being reused for HTTPS [#13558](https://github.com/pnpm/pnpm/issues/13558).
|
|
29
|
+
|
|
30
|
+
The first install after upgrading refetches registry metadata. The package store is unchanged. `pnpm cache view` now shows full registry URLs. Scripts that parse the directory names from `pnpm cache list-registries` or `pnpm cache list` need updating.
|
|
31
|
+
|
|
32
|
+
- Patches that add build scripts or a `binding.gyp` now trigger a build, subject to build approval. Unapproved builds appear under "Ignored build scripts" [#14648](https://github.com/pnpm/pnpm/issues/14648).
|
|
33
|
+
|
|
34
|
+
- Build scripts can now be rejected before installing a package with `pnpm add --allow-build=!<pkg>`, including global installs. `pnpm approve-builds <pkg>` and `pnpm approve-builds !<pkg>` also save decisions when no packages are awaiting approval. They warn if the named package is not awaiting approval [#14067](https://github.com/pnpm/pnpm/issues/14067).
|
|
35
|
+
|
|
36
|
+
- A registry configured in `.npmrc` now takes precedence over registry settings saved by `pnpm login` in the global `config.yaml`. This fixes installs using the wrong registry after login [#14614](https://github.com/pnpm/pnpm/issues/14614).
|
|
37
|
+
|
|
38
|
+
- Large downloads over slow connections no longer time out while data is still arriving. `fetch-timeout` now limits how long a request can go without making progress [#14604](https://github.com/pnpm/pnpm/issues/14604).
|
|
39
|
+
|
|
40
|
+
- Sped up installs in workspaces with many projects when reusing a warm global virtual store [#14540](https://github.com/pnpm/pnpm/issues/14540).
|
|
41
|
+
|
|
42
|
+
- `pnpm deploy` is faster in large workspaces and no longer fails with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` when the project includes a `.pnpmfile.mjs` [#14539](https://github.com/pnpm/pnpm/issues/14539), [#14671](https://github.com/pnpm/pnpm/issues/14671).
|
|
43
|
+
|
|
44
|
+
- `pnpm add --workspace <pkg>` works again. It saves the dependency with the `workspace:` protocol and links it from the workspace. The command fails if no workspace project provides the package [#14602](https://github.com/pnpm/pnpm/issues/14602).
|
|
45
|
+
|
|
46
|
+
- `pnpm add` and `pnpm install` now accept protocol-prefixed selectors such as `jsr:@scope/pkg`, `npm:pkg@^1.0.0`, and `workspace:pkg@*` [#14590](https://github.com/pnpm/pnpm/issues/14590). Installs with JSR dependencies in the lockfile also no longer fail with `ERR_PNPM_META_FETCH_FAIL` [#14649](https://github.com/pnpm/pnpm/issues/14649).
|
|
47
|
+
|
|
48
|
+
- Boolean flags now accept explicit inline values. For example, `pnpm install --prod=false` installs devDependencies, while `--prod=true` skips them [#14553](https://github.com/pnpm/pnpm/issues/14553).
|
|
49
|
+
|
|
50
|
+
- `pnpm install <pkg>` now accepts `--offline` and `--prefer-offline`, as `pnpm add <pkg>` already did [#14194](https://github.com/pnpm/pnpm/pull/14194).
|
|
51
|
+
|
|
52
|
+
- Fixed `pnpm install --frozen-lockfile` rejecting a freshly generated lockfile when overrides use relative `file:` or `link:` paths in a workspace [#14555](https://github.com/pnpm/pnpm/issues/14555).
|
|
53
|
+
|
|
54
|
+
- Fixed installs with config dependencies failing on symlinked lockfiles, such as those used by Bazel and Nix, when the config dependencies have not changed. Updates that would write through a symlink remain disallowed. Updating config dependencies also preserves lockfiles that start with a byte order mark [#14372](https://github.com/pnpm/pnpm/issues/14372).
|
|
55
|
+
|
|
56
|
+
- Fixed package manager version pins being written to the wrong lockfile when `lockfileDir` is set. The pins also remain consistent across commands when version switching is disabled, avoiding unnecessary lockfile changes [#14633](https://github.com/pnpm/pnpm/issues/14633), [#14575](https://github.com/pnpm/pnpm/issues/14575).
|
|
57
|
+
|
|
58
|
+
- `pnpm import` now respects `lockfileDir` and branch lockfiles without modifying other lockfiles. Failed imports restore the destination lockfile [#14563](https://github.com/pnpm/pnpm/issues/14563).
|
|
59
|
+
|
|
60
|
+
- `pnpm patch-commit` now produces valid patches when files are added or deleted. `pnpm install` also accepts patches that delete files without listing their contents, and patch files with CRLF line endings [#14559](https://github.com/pnpm/pnpm/issues/14559), [#14557](https://github.com/pnpm/pnpm/issues/14557).
|
|
61
|
+
|
|
62
|
+
- Fixed version ranges with partial upper bounds. For example, `<=16` now includes all 16.x versions, and `>=0.11 <=3` correctly accepts 3.0.1 [#14419](https://github.com/pnpm/pnpm/issues/14419).
|
|
63
|
+
|
|
64
|
+
- Workspace package patterns now support `.` and `..` segments and repeated slashes. Patterns such as `./packages/*` and exclusions such as `!./packages/foo` now match correctly [#14571](https://github.com/pnpm/pnpm/issues/14571).
|
|
65
|
+
|
|
66
|
+
- `packageConfigs` settings now apply to the specified projects when `sharedWorkspaceLockfile` is `false`, including `overrides`, `hoist`, `modulesDir`, `saveExact`, and `savePrefix`. Workspaces with a shared lockfile report which entries were ignored [#14556](https://github.com/pnpm/pnpm/issues/14556).
|
|
67
|
+
|
|
68
|
+
- `pnpm run` and `pnpm exec` no longer report a changed workspace structure after a successful install when `sharedWorkspaceLockfile` is `false` and `verifyDepsBeforeRun` is enabled [#14588](https://github.com/pnpm/pnpm/issues/14588).
|
|
69
|
+
|
|
70
|
+
- Commands run from a project's subdirectory now find the nearest ancestor with a manifest. This fixes commands such as `pnpm bin` returning paths under the wrong directory. `pnpm init` still creates its manifest in the current directory, and `pnpm exec` still runs there [#14622](https://github.com/pnpm/pnpm/issues/14622).
|
|
71
|
+
|
|
72
|
+
- Relative `scriptShell` paths in `pnpm-workspace.yaml` now resolve from the workspace root, including when scripts run in nested packages. Bare command names such as `bash` still use `PATH` [#14422](https://github.com/pnpm/pnpm/issues/14422).
|
|
73
|
+
|
|
74
|
+
- Fixed installing the pnpm version pinned in `packageManager` when `nodeLinker` is `hoisted`. Managed Node.js, Deno, and Bun installations also work when the global config uses `nodeLinker: hoisted` [#14595](https://github.com/pnpm/pnpm/issues/14595).
|
|
75
|
+
|
|
76
|
+
- The JavaScript pnpm can again switch to a project's pinned pnpm version on platforms without a native binary for that version, such as Alpine Linux with pnpm 10 or Intel Macs with pnpm 11. If a native pnpm version does not support the platform, the error now names the missing target [#13622](https://github.com/pnpm/pnpm/issues/13622).
|
|
77
|
+
|
|
78
|
+
- Provisioning Yarn 6 now uses `GH_TOKEN` or `GITHUB_TOKEN` when available to avoid GitHub's anonymous API rate limit in CI. Tokens are only sent when `strict-ssl` is enabled.
|
|
79
|
+
|
|
80
|
+
- Fixed concurrent installs sharing a global virtual store on macOS failing with "failed to import ... No such file or directory" [#14560](https://github.com/pnpm/pnpm/issues/14560).
|
|
81
|
+
|
|
82
|
+
- Fixed `pnpm setup` failing with `ERR_PNPM_DIRECTORY_FETCHER_PATH_ESCAPE` on Windows. Local `file:` dependencies whose directories are symlinks or junctions are now packed correctly [#14618](https://github.com/pnpm/pnpm/issues/14618).
|
|
83
|
+
|
|
84
|
+
- On Windows, installs now retry replacing command shims temporarily locked by another process [#14549](https://github.com/pnpm/pnpm/issues/14549).
|
|
85
|
+
|
|
86
|
+
- Fixed argument forwarding on Windows with `shellEmulator` enabled. Paths ending in a backslash, line breaks, and literal shell expressions are preserved [#14548](https://github.com/pnpm/pnpm/issues/14548).
|
|
87
|
+
|
|
88
|
+
- Windows store paths now consistently use backslashes in `pnpm store path` output and in the `storeDir` and `virtualStoreDir` fields of `node_modules/.modules.yaml`.
|
|
89
|
+
|
|
90
|
+
- Invalid certificates in `ca` or `cafile` no longer cause an `Invalid CA certificate` error. Valid certificates still apply, and blank `cert` or `key` values are treated as unset [#14646](https://github.com/pnpm/pnpm/issues/14646).
|
|
91
|
+
|
|
92
|
+
- Installs now respect the archive extraction concurrency limit even after a download is abandoned [#14585](https://github.com/pnpm/pnpm/issues/14585).
|
|
93
|
+
|
|
94
|
+
- `pnpm audit` summaries now exclude advisories ignored through `auditConfig.ignoreGhsas` and report them separately. When all advisories are ignored, the summary says so [#14535](https://github.com/pnpm/pnpm/issues/14535).
|
|
95
|
+
|
|
96
|
+
- `pnpm pack --json` now reports errors as JSON. Lifecycle script output appears before the final JSON output.
|
|
97
|
+
|
|
98
|
+
- `pnpm outdated -r` now wraps the `Dependents` column, keeping the table readable when many workspace projects use the same dependency [#14591](https://github.com/pnpm/pnpm/issues/14591).
|
|
99
|
+
|
|
100
|
+
- Shell completions now support the `pn` alias in bash, fish, pwsh, and zsh [#11955](https://github.com/pnpm/pnpm/issues/11955).
|
|
101
|
+
|
|
102
|
+
- `pnpm version` now accepts `-m` as a short alias for `--message` [#14567](https://github.com/pnpm/pnpm/issues/14567).
|
|
103
|
+
|
|
3
104
|
## 12.3.4
|
|
4
105
|
|
|
5
106
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -52,11 +52,16 @@ To quote the [Rush](https://rushjs.io/) team:
|
|
|
52
52
|
</a>
|
|
53
53
|
</td>
|
|
54
54
|
<td align="center" valign="middle">
|
|
55
|
-
<a href="https://notion.com/?utm_source=pnpm&utm_medium=readme" target="_blank" rel="noopener noreferrer">
|
|
55
|
+
<a href="https://notion.com/?utm_source=pnpm&utm_medium=readme" target="_blank" rel="noopener noreferrer"><img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"></a>
|
|
56
|
+
</td>
|
|
57
|
+
</tr>
|
|
58
|
+
<tr>
|
|
59
|
+
<td align="center" valign="middle">
|
|
60
|
+
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=readme" target="_blank" rel="noopener noreferrer">
|
|
56
61
|
<picture>
|
|
57
|
-
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/
|
|
58
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/
|
|
59
|
-
<img src="https://pnpm.io/img/users/
|
|
62
|
+
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
|
|
63
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
|
|
64
|
+
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
|
|
60
65
|
</picture>
|
|
61
66
|
</a>
|
|
62
67
|
</td>
|
|
@@ -101,15 +106,6 @@ To quote the [Rush](https://rushjs.io/) team:
|
|
|
101
106
|
</picture>
|
|
102
107
|
</a>
|
|
103
108
|
</td>
|
|
104
|
-
<td align="center" valign="middle">
|
|
105
|
-
<a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=readme" target="_blank" rel="noopener noreferrer">
|
|
106
|
-
<picture>
|
|
107
|
-
<source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
|
|
108
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
|
|
109
|
-
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
|
|
110
|
-
</picture>
|
|
111
|
-
</a>
|
|
112
|
-
</td>
|
|
113
109
|
<td align="center" valign="middle">
|
|
114
110
|
<a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=readme" target="_blank" rel="noopener noreferrer">
|
|
115
111
|
<picture>
|
|
@@ -119,8 +115,6 @@ To quote the [Rush](https://rushjs.io/) team:
|
|
|
119
115
|
</picture>
|
|
120
116
|
</a>
|
|
121
117
|
</td>
|
|
122
|
-
</tr>
|
|
123
|
-
<tr>
|
|
124
118
|
<td align="center" valign="middle">
|
|
125
119
|
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=readme" target="_blank" rel="noopener noreferrer">
|
|
126
120
|
<picture>
|
|
@@ -130,6 +124,8 @@ To quote the [Rush](https://rushjs.io/) team:
|
|
|
130
124
|
</picture>
|
|
131
125
|
</a>
|
|
132
126
|
</td>
|
|
127
|
+
</tr>
|
|
128
|
+
<tr>
|
|
133
129
|
<td align="center" valign="middle">
|
|
134
130
|
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=readme" target="_blank" rel="noopener noreferrer">
|
|
135
131
|
<picture>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"lastValidatedTimestamp":
|
|
2
|
+
"lastValidatedTimestamp": 1788864536446,
|
|
3
3
|
"projects": {
|
|
4
4
|
"/home/runner/_work/pnpm/pnpm/pnpm/npm/pnpm/temp-deploy": {
|
|
5
5
|
"name": "pacquet",
|
|
6
|
-
"version": "12.
|
|
6
|
+
"version": "12.4.0"
|
|
7
7
|
}
|
|
8
8
|
},
|
|
9
9
|
"pnpmfiles": [],
|
|
@@ -127,10 +127,9 @@
|
|
|
127
127
|
"dint": "^5.1.0",
|
|
128
128
|
"dir-is-case-sensitive": "^3.0.0",
|
|
129
129
|
"empathic": "^2.0.1",
|
|
130
|
-
"encode-registry": "^3.0.1",
|
|
131
130
|
"esbuild": "^0.28.2",
|
|
132
131
|
"escape-string-regexp": "^5.0.0",
|
|
133
|
-
"eslint": "^10.
|
|
132
|
+
"eslint": "^10.10.0",
|
|
134
133
|
"eslint-plugin-import-x": "^4.17.1",
|
|
135
134
|
"eslint-plugin-jest": "^29.16.6",
|
|
136
135
|
"eslint-plugin-n": "^18.3.0",
|
|
@@ -200,7 +199,7 @@
|
|
|
200
199
|
"path-temp": "^3.0.0",
|
|
201
200
|
"pidtree": "^1.0.0",
|
|
202
201
|
"preferred-pm": "^5.0.0",
|
|
203
|
-
"pretty-bytes": "^7.1.
|
|
202
|
+
"pretty-bytes": "^7.1.3",
|
|
204
203
|
"pretty-ms": "^9.3.1",
|
|
205
204
|
"promise-share": "^2.0.1",
|
|
206
205
|
"proxyquire": "^2.1.3",
|
|
@@ -876,7 +876,7 @@ async function connectH1 (client, socket) {
|
|
|
876
876
|
|
|
877
877
|
function clearIdleSocketValidation (socket) {
|
|
878
878
|
if (socket[kIdleSocketValidationTimeout]) {
|
|
879
|
-
|
|
879
|
+
clearImmediate(socket[kIdleSocketValidationTimeout])
|
|
880
880
|
socket[kIdleSocketValidationTimeout] = null
|
|
881
881
|
}
|
|
882
882
|
|
|
@@ -885,15 +885,23 @@ function clearIdleSocketValidation (socket) {
|
|
|
885
885
|
|
|
886
886
|
function scheduleIdleSocketValidation (client, socket) {
|
|
887
887
|
socket[kIdleSocketValidation] = 1
|
|
888
|
-
|
|
888
|
+
// Yield to the check phase (after poll) so unsolicited bytes / FIN / RST
|
|
889
|
+
// already pending on this idle keep-alive socket are processed before the
|
|
890
|
+
// next request is written (GHSA-35p6-xmwp-9g52).
|
|
891
|
+
//
|
|
892
|
+
// setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse
|
|
893
|
+
// (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll
|
|
894
|
+
// block for ~500ms when the event loop is otherwise idle (#5600 / #5606).
|
|
895
|
+
// A ref'd Immediate both keeps the pending request alive and makes poll
|
|
896
|
+
// return immediately — the hybrid those issues asked for.
|
|
897
|
+
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
|
|
889
898
|
socket[kIdleSocketValidationTimeout] = null
|
|
890
899
|
socket[kIdleSocketValidation] = 2
|
|
891
900
|
|
|
892
901
|
if (client[kSocket] === socket && !socket.destroyed) {
|
|
893
902
|
client[kResume]()
|
|
894
903
|
}
|
|
895
|
-
}
|
|
896
|
-
socket[kIdleSocketValidationTimeout].unref?.()
|
|
904
|
+
})
|
|
897
905
|
}
|
|
898
906
|
|
|
899
907
|
/**
|
|
@@ -90,6 +90,7 @@ class RetryHandler {
|
|
|
90
90
|
this.end = null
|
|
91
91
|
this.etag = null
|
|
92
92
|
this.resume = null
|
|
93
|
+
this.headersSent = false
|
|
93
94
|
|
|
94
95
|
// Handle possible onConnect duplication
|
|
95
96
|
this.handler.onConnect(reason => {
|
|
@@ -102,6 +103,20 @@ class RetryHandler {
|
|
|
102
103
|
})
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
checkpointResponseEnd (headers, resume) {
|
|
107
|
+
if (this.end == null && this.opts.method !== 'HEAD') {
|
|
108
|
+
const contentLength = headers['content-length']
|
|
109
|
+
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
|
110
|
+
|
|
111
|
+
assert(
|
|
112
|
+
this.end == null || Number.isFinite(this.end),
|
|
113
|
+
'invalid content-length'
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.resume = this.end != null ? resume : null
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
onRequestSent () {
|
|
106
121
|
if (this.handler.onRequestSent) {
|
|
107
122
|
this.handler.onRequestSent()
|
|
@@ -191,6 +206,8 @@ class RetryHandler {
|
|
|
191
206
|
|
|
192
207
|
if (statusCode >= 300) {
|
|
193
208
|
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
209
|
+
this.headersSent = true
|
|
210
|
+
this.checkpointResponseEnd(headers, resume)
|
|
194
211
|
return this.handler.onHeaders(
|
|
195
212
|
statusCode,
|
|
196
213
|
rawHeaders,
|
|
@@ -259,8 +276,15 @@ class RetryHandler {
|
|
|
259
276
|
|
|
260
277
|
const { start, size, end = size - 1 } = contentRange
|
|
261
278
|
|
|
262
|
-
|
|
263
|
-
|
|
279
|
+
if (this.start !== start || (this.end != null && this.end !== end)) {
|
|
280
|
+
this.abort(
|
|
281
|
+
new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
282
|
+
headers,
|
|
283
|
+
data: { count: this.retryCount }
|
|
284
|
+
})
|
|
285
|
+
)
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
264
288
|
|
|
265
289
|
this.resume = resume
|
|
266
290
|
return true
|
|
@@ -272,6 +296,7 @@ class RetryHandler {
|
|
|
272
296
|
const range = parseRangeHeader(headers['content-range'])
|
|
273
297
|
|
|
274
298
|
if (range == null) {
|
|
299
|
+
this.headersSent = true
|
|
275
300
|
return this.handler.onHeaders(
|
|
276
301
|
statusCode,
|
|
277
302
|
rawHeaders,
|
|
@@ -310,6 +335,7 @@ class RetryHandler {
|
|
|
310
335
|
)
|
|
311
336
|
|
|
312
337
|
this.resume = resume
|
|
338
|
+
this.headersSent = true
|
|
313
339
|
this.etag = headers.etag != null ? headers.etag : null
|
|
314
340
|
|
|
315
341
|
// Weak etags are not useful for comparison nor cache
|
|
@@ -349,7 +375,7 @@ class RetryHandler {
|
|
|
349
375
|
}
|
|
350
376
|
|
|
351
377
|
onError (err) {
|
|
352
|
-
if (this.aborted || isDisturbed(this.opts.body)) {
|
|
378
|
+
if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) {
|
|
353
379
|
return this.handler.onError(err)
|
|
354
380
|
}
|
|
355
381
|
|
|
@@ -23,6 +23,49 @@ const COLON = 0x3A
|
|
|
23
23
|
*/
|
|
24
24
|
const SPACE = 0x20
|
|
25
25
|
|
|
26
|
+
const DATA = Buffer.from('data')
|
|
27
|
+
const EVENT = Buffer.from('event')
|
|
28
|
+
const ID = Buffer.from('id')
|
|
29
|
+
const RETRY = Buffer.from('retry')
|
|
30
|
+
|
|
31
|
+
function isASCIINumberBytes (buffer, start) {
|
|
32
|
+
if (start >= buffer.length) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (let i = start; i < buffer.length; i++) {
|
|
37
|
+
if (buffer[i] < 0x30 || buffer[i] > 0x39) {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isValidLastEventIdBytes (buffer, start) {
|
|
46
|
+
for (let i = start; i < buffer.length; i++) {
|
|
47
|
+
if (buffer[i] === 0x00) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isFieldName (line, length, field) {
|
|
56
|
+
if (length !== field.length) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < length; i++) {
|
|
61
|
+
if (line[i] !== field[i]) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
|
|
26
69
|
/**
|
|
27
70
|
* @typedef {object} EventSourceStreamEvent
|
|
28
71
|
* @type {object}
|
|
@@ -63,11 +106,14 @@ class EventSourceStream extends Transform {
|
|
|
63
106
|
eventEndCheck = false
|
|
64
107
|
|
|
65
108
|
/**
|
|
66
|
-
* @type {Buffer}
|
|
109
|
+
* @type {Buffer[]}
|
|
67
110
|
*/
|
|
68
|
-
|
|
111
|
+
chunks = []
|
|
69
112
|
|
|
113
|
+
chunkIndex = 0
|
|
70
114
|
pos = 0
|
|
115
|
+
lineChunkIndex = 0
|
|
116
|
+
linePos = 0
|
|
71
117
|
|
|
72
118
|
event = {
|
|
73
119
|
data: undefined,
|
|
@@ -106,92 +152,20 @@ class EventSourceStream extends Transform {
|
|
|
106
152
|
return
|
|
107
153
|
}
|
|
108
154
|
|
|
109
|
-
|
|
110
|
-
// processing it
|
|
111
|
-
// TODO: Investigate if there is a more performant way to handle
|
|
112
|
-
// incoming chunks
|
|
113
|
-
// see: https://github.com/nodejs/undici/issues/2630
|
|
114
|
-
if (this.buffer) {
|
|
115
|
-
this.buffer = Buffer.concat([this.buffer, chunk])
|
|
116
|
-
} else {
|
|
117
|
-
this.buffer = chunk
|
|
118
|
-
}
|
|
155
|
+
this.chunks.push(chunk)
|
|
119
156
|
|
|
120
157
|
// Strip leading byte-order-mark if we opened the stream and started
|
|
121
158
|
// the processing of the incoming data
|
|
122
159
|
if (this.checkBOM) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (this.buffer[0] === BOM[0]) {
|
|
127
|
-
// If it is, we need to wait for more data
|
|
128
|
-
callback()
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
// Set the checkBOM flag to false as we don't need to check for the
|
|
132
|
-
// BOM anymore
|
|
133
|
-
this.checkBOM = false
|
|
134
|
-
|
|
135
|
-
// The buffer only contains one byte so we need to wait for more data
|
|
136
|
-
callback()
|
|
137
|
-
return
|
|
138
|
-
case 2:
|
|
139
|
-
// Check if the first two bytes are the same as the first two bytes
|
|
140
|
-
// of the BOM
|
|
141
|
-
if (
|
|
142
|
-
this.buffer[0] === BOM[0] &&
|
|
143
|
-
this.buffer[1] === BOM[1]
|
|
144
|
-
) {
|
|
145
|
-
// If it is, we need to wait for more data, because the third byte
|
|
146
|
-
// is needed to determine if it is the BOM or not
|
|
147
|
-
callback()
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Set the checkBOM flag to false as we don't need to check for the
|
|
152
|
-
// BOM anymore
|
|
153
|
-
this.checkBOM = false
|
|
154
|
-
break
|
|
155
|
-
case 3:
|
|
156
|
-
// Check if the first three bytes are the same as the first three
|
|
157
|
-
// bytes of the BOM
|
|
158
|
-
if (
|
|
159
|
-
this.buffer[0] === BOM[0] &&
|
|
160
|
-
this.buffer[1] === BOM[1] &&
|
|
161
|
-
this.buffer[2] === BOM[2]
|
|
162
|
-
) {
|
|
163
|
-
// If it is, we can drop the buffered data, as it is only the BOM
|
|
164
|
-
this.buffer = Buffer.alloc(0)
|
|
165
|
-
// Set the checkBOM flag to false as we don't need to check for the
|
|
166
|
-
// BOM anymore
|
|
167
|
-
this.checkBOM = false
|
|
168
|
-
|
|
169
|
-
// Await more data
|
|
170
|
-
callback()
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
// If it is not the BOM, we can start processing the data
|
|
174
|
-
this.checkBOM = false
|
|
175
|
-
break
|
|
176
|
-
default:
|
|
177
|
-
// The buffer is longer than 3 bytes, so we can drop the BOM if it is
|
|
178
|
-
// present
|
|
179
|
-
if (
|
|
180
|
-
this.buffer[0] === BOM[0] &&
|
|
181
|
-
this.buffer[1] === BOM[1] &&
|
|
182
|
-
this.buffer[2] === BOM[2]
|
|
183
|
-
) {
|
|
184
|
-
// Remove the BOM from the buffer
|
|
185
|
-
this.buffer = this.buffer.subarray(3)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Set the checkBOM flag to false as we don't need to check for the
|
|
189
|
-
this.checkBOM = false
|
|
190
|
-
break
|
|
160
|
+
if (this.handleBOM()) {
|
|
161
|
+
callback()
|
|
162
|
+
return
|
|
191
163
|
}
|
|
192
164
|
}
|
|
193
165
|
|
|
194
|
-
while (this.
|
|
166
|
+
while (this.hasCurrentByte()) {
|
|
167
|
+
const byte = this.currentByte()
|
|
168
|
+
|
|
195
169
|
// If the previous line ended with an end-of-line, we need to check
|
|
196
170
|
// if the next character is also an end-of-line.
|
|
197
171
|
if (this.eventEndCheck) {
|
|
@@ -204,10 +178,9 @@ class EventSourceStream extends Transform {
|
|
|
204
178
|
if (this.crlfCheck) {
|
|
205
179
|
// If the current character is a line feed, we can remove it
|
|
206
180
|
// from the buffer and reset the crlfCheck flag
|
|
207
|
-
if (
|
|
208
|
-
this.buffer = this.buffer.subarray(this.pos + 1)
|
|
209
|
-
this.pos = 0
|
|
181
|
+
if (byte === LF) {
|
|
210
182
|
this.crlfCheck = false
|
|
183
|
+
this.consumeCurrentByte()
|
|
211
184
|
|
|
212
185
|
// It is possible that the line feed is not the end of the
|
|
213
186
|
// event. We need to check if the next character is an
|
|
@@ -223,19 +196,17 @@ class EventSourceStream extends Transform {
|
|
|
223
196
|
this.crlfCheck = false
|
|
224
197
|
}
|
|
225
198
|
|
|
226
|
-
if (
|
|
199
|
+
if (byte === LF || byte === CR) {
|
|
227
200
|
// If the current character is a carriage return, we need to
|
|
228
201
|
// set the crlfCheck flag to true, as we need to check if the
|
|
229
202
|
// next character is a line feed so we can remove it from the
|
|
230
203
|
// buffer
|
|
231
|
-
if (
|
|
204
|
+
if (byte === CR) {
|
|
232
205
|
this.crlfCheck = true
|
|
233
206
|
}
|
|
234
207
|
|
|
235
|
-
this.
|
|
236
|
-
this.
|
|
237
|
-
if (
|
|
238
|
-
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
|
|
208
|
+
this.consumeCurrentByte()
|
|
209
|
+
if (this.hasPendingEvent()) {
|
|
239
210
|
this.processEvent(this.event)
|
|
240
211
|
}
|
|
241
212
|
this.clearEvent()
|
|
@@ -249,22 +220,18 @@ class EventSourceStream extends Transform {
|
|
|
249
220
|
|
|
250
221
|
// If the current character is an end-of-line, we can process the
|
|
251
222
|
// line
|
|
252
|
-
if (
|
|
223
|
+
if (byte === LF || byte === CR) {
|
|
253
224
|
// If the current character is a carriage return, we need to
|
|
254
225
|
// set the crlfCheck flag to true, as we need to check if the
|
|
255
226
|
// next character is a line feed
|
|
256
|
-
if (
|
|
227
|
+
if (byte === CR) {
|
|
257
228
|
this.crlfCheck = true
|
|
258
229
|
}
|
|
259
230
|
|
|
260
231
|
// In any case, we can process the line as we reached an
|
|
261
232
|
// end-of-line character
|
|
262
|
-
this.parseLine(this.
|
|
263
|
-
|
|
264
|
-
// Remove the processed line from the buffer
|
|
265
|
-
this.buffer = this.buffer.subarray(this.pos + 1)
|
|
266
|
-
// Reset the position as we removed the processed line from the buffer
|
|
267
|
-
this.pos = 0
|
|
233
|
+
this.parseLine(this.readLine(), this.event)
|
|
234
|
+
this.consumeCurrentByte()
|
|
268
235
|
// A line was processed and this could be the end of the event. We need
|
|
269
236
|
// to check if the next line is empty to determine if the event is
|
|
270
237
|
// finished.
|
|
@@ -272,7 +239,7 @@ class EventSourceStream extends Transform {
|
|
|
272
239
|
continue
|
|
273
240
|
}
|
|
274
241
|
|
|
275
|
-
this.
|
|
242
|
+
this.advanceCursor()
|
|
276
243
|
}
|
|
277
244
|
|
|
278
245
|
callback()
|
|
@@ -297,64 +264,53 @@ class EventSourceStream extends Transform {
|
|
|
297
264
|
return
|
|
298
265
|
}
|
|
299
266
|
|
|
300
|
-
let
|
|
301
|
-
let
|
|
267
|
+
let fieldLength = line.length
|
|
268
|
+
let valueStart = line.length
|
|
302
269
|
|
|
303
270
|
// If the line contains a U+003A COLON character (:)
|
|
304
271
|
if (colonPosition !== -1) {
|
|
305
|
-
|
|
306
|
-
// character (:), and let field be that string.
|
|
307
|
-
// TODO: Investigate if there is a more performant way to extract the
|
|
308
|
-
// field
|
|
309
|
-
// see: https://github.com/nodejs/undici/issues/2630
|
|
310
|
-
field = line.subarray(0, colonPosition).toString('utf8')
|
|
272
|
+
fieldLength = colonPosition
|
|
311
273
|
|
|
312
274
|
// Collect the characters on the line after the first U+003A COLON
|
|
313
275
|
// character (:), and let value be that string.
|
|
314
276
|
// If value starts with a U+0020 SPACE character, remove it from value.
|
|
315
|
-
|
|
277
|
+
valueStart = colonPosition + 1
|
|
316
278
|
if (line[valueStart] === SPACE) {
|
|
317
279
|
++valueStart
|
|
318
280
|
}
|
|
319
|
-
// TODO: Investigate if there is a more performant way to extract the
|
|
320
|
-
// value
|
|
321
|
-
// see: https://github.com/nodejs/undici/issues/2630
|
|
322
|
-
value = line.subarray(valueStart).toString('utf8')
|
|
323
|
-
|
|
324
|
-
// Otherwise, the string is not empty but does not contain a U+003A COLON
|
|
325
|
-
// character (:)
|
|
326
|
-
} else {
|
|
327
|
-
// Process the field using the steps described below, using the whole
|
|
328
|
-
// line as the field name, and the empty string as the field value.
|
|
329
|
-
field = line.toString('utf8')
|
|
330
|
-
value = ''
|
|
331
281
|
}
|
|
332
282
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
283
|
+
if (isFieldName(line, fieldLength, DATA)) {
|
|
284
|
+
const value = line.toString('utf8', valueStart)
|
|
285
|
+
|
|
286
|
+
if (event.data === undefined) {
|
|
287
|
+
event.data = value
|
|
288
|
+
} else {
|
|
289
|
+
event.data += `\n${value}`
|
|
290
|
+
}
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (isFieldName(line, fieldLength, RETRY)) {
|
|
295
|
+
if (isASCIINumberBytes(line, valueStart)) {
|
|
296
|
+
event.retry = line.toString('utf8', valueStart)
|
|
297
|
+
}
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (isFieldName(line, fieldLength, ID)) {
|
|
302
|
+
if (isValidLastEventIdBytes(line, valueStart)) {
|
|
303
|
+
event.id = line.toString('utf8', valueStart)
|
|
304
|
+
}
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (isFieldName(line, fieldLength, EVENT)) {
|
|
309
|
+
const value = line.toString('utf8', valueStart)
|
|
310
|
+
|
|
311
|
+
if (value.length > 0) {
|
|
312
|
+
event.event = value
|
|
313
|
+
}
|
|
358
314
|
}
|
|
359
315
|
}
|
|
360
316
|
|
|
@@ -384,12 +340,151 @@ class EventSourceStream extends Transform {
|
|
|
384
340
|
}
|
|
385
341
|
|
|
386
342
|
clearEvent () {
|
|
387
|
-
this.event =
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
343
|
+
this.event.data = undefined
|
|
344
|
+
this.event.event = undefined
|
|
345
|
+
this.event.id = undefined
|
|
346
|
+
this.event.retry = undefined
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
hasPendingEvent () {
|
|
350
|
+
return this.event.data !== undefined ||
|
|
351
|
+
this.event.event !== undefined ||
|
|
352
|
+
this.event.id !== undefined ||
|
|
353
|
+
this.event.retry !== undefined
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
hasCurrentByte () {
|
|
357
|
+
return this.chunkIndex < this.chunks.length &&
|
|
358
|
+
this.pos < this.chunks[this.chunkIndex].length
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
currentByte () {
|
|
362
|
+
return this.chunks[this.chunkIndex][this.pos]
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
consumeCurrentByte () {
|
|
366
|
+
this.advanceCursor()
|
|
367
|
+
this.syncLineStartToCursor()
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
advanceCursor () {
|
|
371
|
+
this.pos++
|
|
372
|
+
|
|
373
|
+
while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) {
|
|
374
|
+
this.chunkIndex++
|
|
375
|
+
this.pos = 0
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
syncLineStartToCursor () {
|
|
380
|
+
this.lineChunkIndex = this.chunkIndex
|
|
381
|
+
this.linePos = this.pos
|
|
382
|
+
this.dropConsumedChunks()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
dropConsumedChunks () {
|
|
386
|
+
while (this.lineChunkIndex > 0) {
|
|
387
|
+
this.chunks.shift()
|
|
388
|
+
this.lineChunkIndex--
|
|
389
|
+
this.chunkIndex--
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (this.chunkIndex === this.chunks.length) {
|
|
393
|
+
this.chunks.length = 0
|
|
394
|
+
this.chunkIndex = 0
|
|
395
|
+
this.pos = 0
|
|
396
|
+
this.lineChunkIndex = 0
|
|
397
|
+
this.linePos = 0
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
readLine () {
|
|
402
|
+
if (this.lineChunkIndex === this.chunkIndex) {
|
|
403
|
+
return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const chunks = []
|
|
407
|
+
let length = 0
|
|
408
|
+
|
|
409
|
+
for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) {
|
|
410
|
+
const chunk = this.chunks[i]
|
|
411
|
+
const start = i === this.lineChunkIndex ? this.linePos : 0
|
|
412
|
+
const end = i === this.chunkIndex ? this.pos : chunk.length
|
|
413
|
+
const slice = chunk.subarray(start, end)
|
|
414
|
+
length += slice.length
|
|
415
|
+
chunks.push(slice)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return Buffer.concat(chunks, length)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
peekBufferedByte (offset) {
|
|
422
|
+
let chunkIndex = this.lineChunkIndex
|
|
423
|
+
let pos = this.linePos
|
|
424
|
+
|
|
425
|
+
while (chunkIndex < this.chunks.length) {
|
|
426
|
+
const chunk = this.chunks[chunkIndex]
|
|
427
|
+
const remaining = chunk.length - pos
|
|
428
|
+
|
|
429
|
+
if (offset < remaining) {
|
|
430
|
+
return chunk[pos + offset]
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
offset -= remaining
|
|
434
|
+
chunkIndex++
|
|
435
|
+
pos = 0
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
discardLeadingBytes (count) {
|
|
440
|
+
while (count > 0 && this.lineChunkIndex < this.chunks.length) {
|
|
441
|
+
const chunk = this.chunks[this.lineChunkIndex]
|
|
442
|
+
const remaining = chunk.length - this.linePos
|
|
443
|
+
|
|
444
|
+
if (count < remaining) {
|
|
445
|
+
this.linePos += count
|
|
446
|
+
count = 0
|
|
447
|
+
} else {
|
|
448
|
+
count -= remaining
|
|
449
|
+
this.lineChunkIndex++
|
|
450
|
+
this.linePos = 0
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
this.chunkIndex = this.lineChunkIndex
|
|
455
|
+
this.pos = this.linePos
|
|
456
|
+
this.dropConsumedChunks()
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
handleBOM () {
|
|
460
|
+
const first = this.peekBufferedByte(0)
|
|
461
|
+
const second = this.peekBufferedByte(1)
|
|
462
|
+
const third = this.peekBufferedByte(2)
|
|
463
|
+
|
|
464
|
+
if (second === undefined) {
|
|
465
|
+
if (first === BOM[0]) {
|
|
466
|
+
return true
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
this.checkBOM = false
|
|
470
|
+
return true
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (third === undefined) {
|
|
474
|
+
if (first === BOM[0] && second === BOM[1]) {
|
|
475
|
+
return true
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
this.checkBOM = false
|
|
479
|
+
return false
|
|
392
480
|
}
|
|
481
|
+
|
|
482
|
+
if (first === BOM[0] && second === BOM[1] && third === BOM[2]) {
|
|
483
|
+
this.discardLeadingBytes(3)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
this.checkBOM = false
|
|
487
|
+
return !this.hasCurrentByte()
|
|
393
488
|
}
|
|
394
489
|
}
|
|
395
490
|
|
|
@@ -192,7 +192,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|
|
192
192
|
// is specified, the server needs to include the same field and one of
|
|
193
193
|
// the selected subprotocol values in its response for the connection to
|
|
194
194
|
// be established.
|
|
195
|
-
if (!requestProtocols.includes(secProtocol)) {
|
|
195
|
+
if (requestProtocols === null || !requestProtocols.includes(secProtocol)) {
|
|
196
196
|
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
|
|
197
197
|
return
|
|
198
198
|
}
|
|
@@ -63,7 +63,12 @@ class PerMessageDeflate {
|
|
|
63
63
|
|
|
64
64
|
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
|
65
65
|
callback(new MessageSizeExceededError())
|
|
66
|
+
// The inflater may still hold buffered input that can emit a late
|
|
67
|
+
// zlib error. Remove the data listener, then deterministically stop
|
|
68
|
+
// the stream so a subsequent 'error' cannot fire without a listener
|
|
69
|
+
// (which would terminate the process as an unhandled error event).
|
|
66
70
|
this.#inflate.removeAllListeners()
|
|
71
|
+
this.#inflate.destroy()
|
|
67
72
|
this.#inflate = null
|
|
68
73
|
return
|
|
69
74
|
}
|
package/install.js
CHANGED
|
@@ -23,6 +23,7 @@ import path from 'node:path'
|
|
|
23
23
|
import process from 'node:process'
|
|
24
24
|
import {
|
|
25
25
|
getBinCandidates,
|
|
26
|
+
hostTarget,
|
|
26
27
|
readWrapperManifest,
|
|
27
28
|
resolveInstalledBinary,
|
|
28
29
|
splitBinSpecifier,
|
|
@@ -47,7 +48,7 @@ function setup () {
|
|
|
47
48
|
|
|
48
49
|
const candidates = getBinCandidates()
|
|
49
50
|
if (candidates.length === 0) {
|
|
50
|
-
fail(`pnpm does not ship a prebuilt binary for ${
|
|
51
|
+
fail(`pnpm does not ship a prebuilt binary for ${hostTarget()}.`)
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const nativeBinary = resolveInstalledBinary()
|
package/native-binary.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
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 os from 'node:os'
|
|
5
6
|
import path from 'node:path'
|
|
6
7
|
import process from 'node:process'
|
|
7
8
|
import { fileURLToPath } from 'node:url'
|
|
@@ -29,13 +30,35 @@ const PLATFORMS = {
|
|
|
29
30
|
glibc: '@pnpm/exe.linux-arm64/pnpm',
|
|
30
31
|
musl: '@pnpm/exe.linux-arm64-musl/pnpm',
|
|
31
32
|
},
|
|
33
|
+
// Only a glibc build is released for these three.
|
|
34
|
+
riscv64: {
|
|
35
|
+
glibc: '@pnpm/exe.linux-riscv64/pnpm',
|
|
36
|
+
},
|
|
37
|
+
ppc64: {
|
|
38
|
+
glibc: '@pnpm/exe.linux-ppc64/pnpm',
|
|
39
|
+
},
|
|
40
|
+
s390x: {
|
|
41
|
+
glibc: '@pnpm/exe.linux-s390x/pnpm',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
freebsd: {
|
|
45
|
+
x64: '@pnpm/exe.freebsd-x64/pnpm',
|
|
32
46
|
},
|
|
47
|
+
// Android is bionic, which is neither of the two libcs the linux entries
|
|
48
|
+
// above are keyed on, so it takes a bare specifier of its own.
|
|
49
|
+
android: {
|
|
50
|
+
arm64: '@pnpm/exe.android-arm64/pnpm',
|
|
51
|
+
x64: '@pnpm/exe.android-x64/pnpm',
|
|
52
|
+
},
|
|
53
|
+
// Android is bionic, which is neither of the two libcs the linux entries
|
|
54
|
+
// above are keyed on, so it takes a bare specifier of its own.
|
|
33
55
|
}
|
|
34
56
|
|
|
35
57
|
/**
|
|
36
|
-
* Native binary specifiers to try, most-preferred first; empty when
|
|
37
|
-
*
|
|
38
|
-
* only decides the winner when both are installed (e.g.
|
|
58
|
+
* Native binary specifiers to try, most-preferred first; empty when no released
|
|
59
|
+
* binary runs on the host. The linux glibc/musl pair is ordered by detected
|
|
60
|
+
* libc, which only decides the winner when both are installed (e.g.
|
|
61
|
+
* `npm install --force`).
|
|
39
62
|
*
|
|
40
63
|
* @returns {string[]}
|
|
41
64
|
*/
|
|
@@ -45,12 +68,36 @@ export function getBinCandidates () {
|
|
|
45
68
|
if (platformEntry == null) {
|
|
46
69
|
return []
|
|
47
70
|
}
|
|
71
|
+
// Node reports both POWER endiannesses as `ppc64` and npm's `cpu` field
|
|
72
|
+
// cannot tell them apart, so a big-endian host installs the little-endian
|
|
73
|
+
// package it cannot run. Only the little-endian build is released.
|
|
74
|
+
if (arch === 'ppc64' && os.endianness() !== 'LE') {
|
|
75
|
+
return []
|
|
76
|
+
}
|
|
48
77
|
if (typeof platformEntry === 'string') {
|
|
49
78
|
return [platformEntry]
|
|
50
79
|
}
|
|
51
80
|
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
// An unprobeable libc (`detectLinuxLibc` returns null) counts as glibc.
|
|
82
|
+
const detected = detectLinuxLibc() === 'musl' ? 'musl' : 'glibc'
|
|
83
|
+
const preferred = platformEntry[detected]
|
|
84
|
+
// An architecture released for one libc only has no entry for the other, and
|
|
85
|
+
// the binary it does ship cannot run there, so it offers no candidate.
|
|
86
|
+
if (preferred == null) {
|
|
87
|
+
return []
|
|
88
|
+
}
|
|
89
|
+
const alternate = platformEntry[detected === 'musl' ? 'glibc' : 'musl']
|
|
90
|
+
return alternate == null ? [preferred] : [preferred, alternate]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* How the host names itself in messages, spelled like the targets pnpm releases
|
|
95
|
+
* binaries for: `linux-x64-musl`, `darwin-arm64`.
|
|
96
|
+
*
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
export function hostTarget () {
|
|
100
|
+
return `${platform}-${arch}${detectLinuxLibc() === 'musl' ? '-musl' : ''}`
|
|
54
101
|
}
|
|
55
102
|
|
|
56
103
|
/**
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"./dist/node_modules/node-gyp/gyp/gyp"
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
|
-
"version": "12.
|
|
12
|
+
"version": "12.4.0",
|
|
13
13
|
"description": "Fast, disk space efficient package manager",
|
|
14
14
|
"keywords": [
|
|
15
15
|
"pnpm",
|
|
@@ -46,14 +46,20 @@
|
|
|
46
46
|
"THIRD-PARTY-NOTICES.md"
|
|
47
47
|
],
|
|
48
48
|
"optionalDependencies": {
|
|
49
|
-
"@pnpm/exe.win32-x64": "12.
|
|
50
|
-
"@pnpm/exe.win32-arm64": "12.
|
|
51
|
-
"@pnpm/exe.darwin-x64": "12.
|
|
52
|
-
"@pnpm/exe.darwin-arm64": "12.
|
|
53
|
-
"@pnpm/exe.linux-x64": "12.
|
|
54
|
-
"@pnpm/exe.linux-arm64": "12.
|
|
55
|
-
"@pnpm/exe.linux-
|
|
56
|
-
"@pnpm/exe.linux-
|
|
49
|
+
"@pnpm/exe.win32-x64": "12.4.0",
|
|
50
|
+
"@pnpm/exe.win32-arm64": "12.4.0",
|
|
51
|
+
"@pnpm/exe.darwin-x64": "12.4.0",
|
|
52
|
+
"@pnpm/exe.darwin-arm64": "12.4.0",
|
|
53
|
+
"@pnpm/exe.linux-x64": "12.4.0",
|
|
54
|
+
"@pnpm/exe.linux-arm64": "12.4.0",
|
|
55
|
+
"@pnpm/exe.linux-riscv64": "12.4.0",
|
|
56
|
+
"@pnpm/exe.linux-ppc64": "12.4.0",
|
|
57
|
+
"@pnpm/exe.linux-s390x": "12.4.0",
|
|
58
|
+
"@pnpm/exe.linux-x64-musl": "12.4.0",
|
|
59
|
+
"@pnpm/exe.linux-arm64-musl": "12.4.0",
|
|
60
|
+
"@pnpm/exe.freebsd-x64": "12.4.0",
|
|
61
|
+
"@pnpm/exe.android-arm64": "12.4.0",
|
|
62
|
+
"@pnpm/exe.android-x64": "12.4.0"
|
|
57
63
|
},
|
|
58
64
|
"scripts": {
|
|
59
65
|
"preinstall": "node install.js",
|