@pnpm/fs.graceful-fs 1100.2.3 → 1100.2.5
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 +18 -0
- package/lib/index.d.ts +7 -1
- package/lib/index.js +42 -24
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @pnpm/fs.graceful-fs
|
|
2
2
|
|
|
3
|
+
## 1100.2.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies:
|
|
8
|
+
- @pnpm/error@1100.2.2
|
|
9
|
+
|
|
10
|
+
## 1100.2.4
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Fixed concurrent installs failing when replacing the same stale hoisted dependency link.
|
|
15
|
+
|
|
16
|
+
- `pnpm install` now works in StackBlitz WebContainers on projects without a lockfile. It used to fail there with `ENOENT ... pnpm-lock.yaml`, because pnpm did not recognize the errors that WebContainers return from asynchronous file system calls [#15649](https://github.com/pnpm/pnpm/issues/15649).
|
|
17
|
+
|
|
18
|
+
- Updated dependencies:
|
|
19
|
+
- @pnpm/error@1100.2.1
|
|
20
|
+
|
|
3
21
|
## 1100.2.3
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/lib/index.d.ts
CHANGED
|
@@ -55,4 +55,10 @@ export declare function unlinkWithRetry(target: string): void;
|
|
|
55
55
|
* Runs a filesystem operation with the retry policy of
|
|
56
56
|
* {@link renameFileWithRetry}.
|
|
57
57
|
*/
|
|
58
|
-
export declare function withFileLockRetry<
|
|
58
|
+
export declare function withFileLockRetry<Result>(operation: () => Result): Result;
|
|
59
|
+
/**
|
|
60
|
+
* Asynchronous {@link withFileLockRetry}, which waits between attempts
|
|
61
|
+
* without blocking the event loop.
|
|
62
|
+
*/
|
|
63
|
+
export declare function withFileLockRetryAsync<Result>(operation: () => Promise<Result>): Promise<Result>;
|
|
64
|
+
export declare function isTransientFileLockError(err: unknown): err is NodeJS.ErrnoException;
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
-
import
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { isError } from '@pnpm/error';
|
|
4
5
|
import gfs from 'graceful-fs';
|
|
5
6
|
const FILE_LOCK_RETRY_BUDGET_MS = 60_000;
|
|
6
7
|
const PERMISSION_DENIED_RETRY_BUDGET_MS = 1_000;
|
|
@@ -30,23 +31,33 @@ function withEagainRetry(fn, maxRetries = 15) {
|
|
|
30
31
|
return (...args) => {
|
|
31
32
|
let attempts = 0;
|
|
32
33
|
while (attempts <= maxRetries) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (util.types.isNativeError(err) && 'code' in err && err.code === 'EAGAIN' && attempts < maxRetries) {
|
|
38
|
-
attempts++;
|
|
39
|
-
// Exponential backoff: wait 2^attempts milliseconds, max 300ms
|
|
40
|
-
const delay = Math.min(Math.pow(2, attempts), 300);
|
|
41
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
throw err;
|
|
45
|
-
}
|
|
34
|
+
const outcome = tryCallWithEagain(fn, args, attempts, maxRetries);
|
|
35
|
+
if (outcome.success)
|
|
36
|
+
return outcome.value;
|
|
37
|
+
attempts = outcome.attempts;
|
|
46
38
|
}
|
|
47
39
|
throw new Error('Unreachable');
|
|
48
40
|
};
|
|
49
41
|
}
|
|
42
|
+
function tryCallWithEagain(fn, args, attempts, maxRetries) {
|
|
43
|
+
try {
|
|
44
|
+
return { success: true, value: fn(...args) };
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (!isEagainError(err) || attempts >= maxRetries)
|
|
48
|
+
throw err;
|
|
49
|
+
const nextAttempts = attempts + 1;
|
|
50
|
+
waitEagainBackoff(nextAttempts);
|
|
51
|
+
return { success: false, attempts: nextAttempts };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function isEagainError(err) {
|
|
55
|
+
return isError(err) && 'code' in err && err.code === 'EAGAIN';
|
|
56
|
+
}
|
|
57
|
+
function waitEagainBackoff(attempts) {
|
|
58
|
+
const delay = Math.min(Math.pow(2, attempts), 300);
|
|
59
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
|
|
60
|
+
}
|
|
50
61
|
/**
|
|
51
62
|
* Renames `src` over `dest`, retrying Windows EBUSY errors for up to a minute.
|
|
52
63
|
* Windows drives mounted into WSL get the same retries.
|
|
@@ -108,23 +119,30 @@ export function withFileLockRetry(operation) {
|
|
|
108
119
|
}
|
|
109
120
|
}
|
|
110
121
|
}
|
|
111
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Asynchronous {@link withFileLockRetry}, which waits between attempts
|
|
124
|
+
* without blocking the event loop.
|
|
125
|
+
*/
|
|
126
|
+
export async function withFileLockRetryAsync(operation) {
|
|
112
127
|
const retry = createFileLockRetry();
|
|
113
128
|
for (;;) {
|
|
114
129
|
try {
|
|
115
|
-
// eslint-disable-next-line no-await-in-loop
|
|
130
|
+
// eslint-disable-next-line no-await-in-loop -- the next attempt runs only after this one fails
|
|
116
131
|
return await operation();
|
|
117
132
|
}
|
|
118
133
|
catch (err) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// eslint-disable-next-line no-await-in-loop
|
|
122
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
123
|
-
}
|
|
124
|
-
retry.checkBudgetAfterDelay(err);
|
|
134
|
+
// eslint-disable-next-line no-await-in-loop -- backs off before the next attempt
|
|
135
|
+
await backOffWithFileLockRetryAsync(retry, err);
|
|
125
136
|
}
|
|
126
137
|
}
|
|
127
138
|
}
|
|
139
|
+
async function backOffWithFileLockRetryAsync(retry, err) {
|
|
140
|
+
const delayMs = retry.delayBeforeNextAttempt(err);
|
|
141
|
+
if (delayMs > 0) {
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
143
|
+
}
|
|
144
|
+
retry.checkBudgetAfterDelay(err);
|
|
145
|
+
}
|
|
128
146
|
function createFileLockRetry() {
|
|
129
147
|
const startedAt = Date.now();
|
|
130
148
|
let backoffMs = 0;
|
|
@@ -147,9 +165,9 @@ function createFileLockRetry() {
|
|
|
147
165
|
},
|
|
148
166
|
};
|
|
149
167
|
}
|
|
150
|
-
function isTransientFileLockError(err) {
|
|
168
|
+
export function isTransientFileLockError(err) {
|
|
151
169
|
return (process.platform === 'win32' || isWsl()) &&
|
|
152
|
-
|
|
170
|
+
isError(err) &&
|
|
153
171
|
'code' in err &&
|
|
154
172
|
(err.code === 'EPERM' || err.code === 'EACCES' || err.code === 'EBUSY');
|
|
155
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/fs.graceful-fs",
|
|
3
|
-
"version": "1100.2.
|
|
3
|
+
"version": "1100.2.5",
|
|
4
4
|
"description": "Promisified graceful-fs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -27,11 +27,12 @@
|
|
|
27
27
|
"!*.map"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@pnpm/error": "1100.2.2",
|
|
30
31
|
"graceful-fs": "^4.2.11"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@jest/globals": "30.4.1",
|
|
34
|
-
"@pnpm/fs.graceful-fs": "1100.2.
|
|
35
|
+
"@pnpm/fs.graceful-fs": "1100.2.5",
|
|
35
36
|
"@types/graceful-fs": "^4.1.9"
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|