@openclaw/fs-safe 0.5.1 → 0.5.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 +86 -0
- package/README.md +19 -6
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +20 -12
- package/dist/archive-entry.d.ts +1 -0
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +37 -6
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +4 -0
- package/dist/archive-input.d.ts +7 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +11 -1
- package/dist/archive-limits.js +1 -1
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +12 -6
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +2 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +62 -37
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +3 -2
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +15 -1
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +11 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -4
- package/dist/archive-zip-entry.d.ts +7 -1
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +11 -1
- package/dist/archive-zip-integrity.d.ts +5 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -0
- package/dist/archive-zip-integrity.js +54 -0
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +12 -1
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -5
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +13 -2
- package/dist/deny-mutations.d.ts +1 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +6 -39
- package/dist/device-path.d.ts +1 -0
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +6 -6
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +5 -4
- package/dist/error-detail.d.ts +2 -0
- package/dist/error-detail.d.ts.map +1 -0
- package/dist/error-detail.js +4 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +4 -1
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +88 -35
- package/dist/file-store-boundary.d.ts +6 -0
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -5
- package/dist/file-store-sync-write.d.ts +9 -0
- package/dist/file-store-sync-write.d.ts.map +1 -0
- package/dist/file-store-sync-write.js +97 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +62 -139
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +27 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +5 -6
- package/dist/install-path.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +19 -13
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +1 -3
- package/dist/local-file-access.d.ts +3 -2
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +12 -6
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -30
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +51 -12
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
- package/dist/native-binding.d.ts +1 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +14 -1
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +44 -17
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +6 -5
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +1 -7
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +4 -0
- package/dist/permission-exec.d.ts +6 -0
- package/dist/permission-exec.d.ts.map +1 -0
- package/dist/permission-exec.js +28 -0
- package/dist/permissions-windows.d.ts +56 -0
- package/dist/permissions-windows.d.ts.map +1 -0
- package/dist/permissions-windows.js +285 -0
- package/dist/permissions.d.ts +3 -46
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +17 -328
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +21 -4
- package/dist/pinned-write.js +2 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +45 -4
- package/dist/read-error.d.ts +2 -0
- package/dist/read-error.d.ts.map +1 -0
- package/dist/read-error.js +11 -0
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +21 -2
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +10 -1
- package/dist/replace-file-copy-fallback.d.ts +3 -0
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +18 -11
- package/dist/replace-file-descriptor.d.ts +33 -0
- package/dist/replace-file-descriptor.d.ts.map +1 -0
- package/dist/replace-file-descriptor.js +96 -0
- package/dist/replace-file.d.ts +9 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +64 -72
- package/dist/root-context.d.ts +8 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +43 -2
- package/dist/root-errors.d.ts +7 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +46 -1
- package/dist/root-file.d.ts +1 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +2 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +216 -169
- package/dist/root-path-symlink.d.ts +3 -0
- package/dist/root-path-symlink.d.ts.map +1 -0
- package/dist/root-path-symlink.js +35 -0
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +129 -190
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +15 -0
- package/dist/safe-path-segment.d.ts +2 -0
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +12 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +29 -6
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +5 -1
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +6 -1
- package/dist/short-path.d.ts +2 -0
- package/dist/short-path.d.ts.map +1 -0
- package/dist/short-path.js +7 -0
- package/dist/sidecar-lock-acquire.d.ts +30 -0
- package/dist/sidecar-lock-acquire.d.ts.map +1 -0
- package/dist/sidecar-lock-acquire.js +260 -0
- package/dist/sidecar-lock-policy.d.ts +3 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +20 -3
- package/dist/sidecar-lock-reclaim.d.ts +5 -1
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +59 -9
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +27 -226
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +9 -8
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +15 -4
- package/dist/test-hooks.d.ts +1 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/timing.d.ts +2 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +9 -0
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +10 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +15 -0
- package/docs/advanced.md +7 -1
- package/docs/archive.md +27 -20
- package/docs/assets/readme-banner.jpg +0 -0
- package/docs/atomic.md +48 -19
- package/docs/errors.md +34 -8
- package/docs/file-store.md +14 -1
- package/docs/filename.md +17 -16
- package/docs/index.md +1 -0
- package/docs/install-path.md +9 -6
- package/docs/json.md +8 -2
- package/docs/local-roots.md +79 -103
- package/docs/native-helper.md +11 -5
- package/docs/native.md +2 -2
- package/docs/output.md +8 -3
- package/docs/path-scope.md +4 -1
- package/docs/path.md +7 -3
- package/docs/permissions.md +8 -0
- package/docs/public-api.md +109 -0
- package/docs/quickstart.md +6 -2
- package/docs/reading.md +13 -10
- package/docs/root.md +26 -4
- package/docs/secret-file.md +21 -4
- package/docs/security-model.md +21 -4
- package/docs/sidecar-lock.md +15 -5
- package/docs/temp.md +42 -5
- package/docs/test-hooks.md +2 -0
- package/docs/types.md +32 -15
- package/docs/writing.md +25 -12
- package/package.json +15 -4
- package/dist/archive-file-io.d.ts +0 -9
- package/dist/archive-file-io.d.ts.map +0 -1
- package/dist/archive-file-io.js +0 -11
- package/dist/file-store-source.d.ts +0 -5
- package/dist/file-store-source.d.ts.map +0 -1
- package/dist/file-store-source.js +0 -30
- package/dist/file-url.d.ts +0 -2
- package/dist/file-url.d.ts.map +0 -1
- package/dist/file-url.js +0 -1
- package/dist/path-stat.d.ts +0 -4
- package/dist/path-stat.d.ts.map +0 -1
- package/dist/path-stat.js +0 -15
- package/dist/windows-permissions-native.d.ts +0 -9
- package/dist/windows-permissions-native.d.ts.map +0 -1
- package/dist/windows-permissions-native.js +0 -31
|
@@ -2,9 +2,10 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import fsSync, {} from "node:fs";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { readFileHandleBounded } from "./bounded-read.js";
|
|
5
|
+
import { readFileDescriptorBoundedSync, readFileHandleBounded } from "./bounded-read.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { sameFileIdentity } from "./file-identity.js";
|
|
8
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
8
9
|
const MAX_LOCK_PAYLOAD_BYTES = 1024 * 1024;
|
|
9
10
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES = 16;
|
|
10
11
|
const SIDECAR_LOCK_OWNERSHIP_TOKEN_BITS = SIDECAR_LOCK_OWNERSHIP_TOKEN_BYTES * 8;
|
|
@@ -55,6 +56,7 @@ export function parseSidecarLockPayload(raw, parser) {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
export async function readSidecarLockSnapshot(lockPath, options = {}) {
|
|
59
|
+
let handle;
|
|
58
60
|
try {
|
|
59
61
|
if (options.lockRoot) {
|
|
60
62
|
const opened = await options.lockRoot.open(relativeSidecarLockPath(options.lockRoot, lockPath));
|
|
@@ -70,9 +72,44 @@ export async function readSidecarLockSnapshot(lockPath, options = {}) {
|
|
|
70
72
|
await opened.handle.close().catch(() => undefined);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const before = await fs.lstat(lockPath);
|
|
76
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
77
|
+
if (options.rejectNonFile) {
|
|
78
|
+
throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
await getFsSafeTestHooks()?.beforeSidecarLockSnapshotOpen?.(lockPath);
|
|
83
|
+
const noFollow = process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
|
|
84
|
+
? fsSync.constants.O_NOFOLLOW
|
|
85
|
+
: 0;
|
|
86
|
+
try {
|
|
87
|
+
handle = await fs.open(lockPath, fsSync.constants.O_RDONLY |
|
|
88
|
+
noFollow |
|
|
89
|
+
(typeof fsSync.constants.O_NONBLOCK === "number" ? fsSync.constants.O_NONBLOCK : 0));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (options.rejectNonFile && error.code === "ELOOP") {
|
|
93
|
+
throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`, {
|
|
94
|
+
cause: error,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
const opened = await handle.stat();
|
|
100
|
+
if (!opened.isFile()) {
|
|
101
|
+
if (options.rejectNonFile) {
|
|
102
|
+
throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (!options.allowDescriptorIdentityDrift && !sameFileIdentity(before, opened))
|
|
107
|
+
return null;
|
|
108
|
+
const raw = (await readFileHandleBounded(handle, MAX_LOCK_PAYLOAD_BYTES)).toString("utf8");
|
|
109
|
+
const after = await fs.lstat(lockPath);
|
|
110
|
+
if (!after.isFile() || !sameFileIdentity(before, after))
|
|
111
|
+
return null;
|
|
112
|
+
return { raw, payload: parseSidecarLockPayload(raw, options.parsePayload), stat: after };
|
|
76
113
|
}
|
|
77
114
|
catch (err) {
|
|
78
115
|
if (err.code === "ENOENT" ||
|
|
@@ -81,19 +118,26 @@ export async function readSidecarLockSnapshot(lockPath, options = {}) {
|
|
|
81
118
|
}
|
|
82
119
|
throw err;
|
|
83
120
|
}
|
|
121
|
+
finally {
|
|
122
|
+
await handle?.close().catch(() => undefined);
|
|
123
|
+
}
|
|
84
124
|
}
|
|
85
|
-
export function readSidecarLockSnapshotSync(lockPath, parsePayload) {
|
|
125
|
+
export function readSidecarLockSnapshotSync(lockPath, parsePayload, options = {}) {
|
|
86
126
|
let fd;
|
|
87
127
|
try {
|
|
88
128
|
const before = fsSync.lstatSync(lockPath);
|
|
89
|
-
if (!before.isFile() || before.isSymbolicLink())
|
|
129
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
130
|
+
if (options.rejectNonFile) {
|
|
131
|
+
throw new FsSafeError("not-file", `sidecar lock is not a regular file: ${lockPath}`);
|
|
132
|
+
}
|
|
90
133
|
return null;
|
|
134
|
+
}
|
|
91
135
|
const noFollow = process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
|
|
92
136
|
? fsSync.constants.O_NOFOLLOW
|
|
93
137
|
: 0;
|
|
94
138
|
fd = fsSync.openSync(lockPath, fsSync.constants.O_RDONLY | noFollow);
|
|
95
139
|
const opened = fsSync.fstatSync(fd);
|
|
96
|
-
const raw =
|
|
140
|
+
const raw = readFileDescriptorBoundedSync(fd, MAX_LOCK_PAYLOAD_BYTES).toString("utf8");
|
|
97
141
|
const after = fsSync.lstatSync(lockPath);
|
|
98
142
|
if (!sameFileIdentity(before, opened) || !sameFileIdentity(opened, after))
|
|
99
143
|
return null;
|
|
@@ -139,7 +183,10 @@ export function sidecarLockSnapshotMatches(current, observed) {
|
|
|
139
183
|
return observed.stat !== undefined && current.stat !== undefined;
|
|
140
184
|
}
|
|
141
185
|
export async function removeSidecarLockIfUnchanged(lockPath, observed, options = {}) {
|
|
142
|
-
const current = await readSidecarLockSnapshot(lockPath,
|
|
186
|
+
const current = await readSidecarLockSnapshot(lockPath, {
|
|
187
|
+
...options,
|
|
188
|
+
allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
|
|
189
|
+
});
|
|
143
190
|
if (!current || !observed || !sidecarLockSnapshotMatches(current, observed)) {
|
|
144
191
|
return false;
|
|
145
192
|
}
|
|
@@ -152,7 +199,10 @@ export async function removeSidecarLockIfUnchanged(lockPath, observed, options =
|
|
|
152
199
|
return true;
|
|
153
200
|
}
|
|
154
201
|
export async function sidecarLockSnapshotStillPresent(lockPath, observed, options = {}) {
|
|
155
|
-
const current = await readSidecarLockSnapshot(lockPath,
|
|
202
|
+
const current = await readSidecarLockSnapshot(lockPath, {
|
|
203
|
+
...options,
|
|
204
|
+
allowDescriptorIdentityDrift: observed?.ownershipToken !== undefined,
|
|
205
|
+
});
|
|
156
206
|
return !!current && !!observed && sidecarLockSnapshotMatches(current, observed);
|
|
157
207
|
}
|
|
158
208
|
export async function sidecarReclaimGuardExists(pathname) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAmLjC,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAS3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAYL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBASW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
|
package/dist/sidecar-lock.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import { sameFileIdentity } from "./file-identity.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { createNativeExclusiveFile } from "./native-operations.js";
|
|
3
|
+
import { removeSidecarLockIfUnchanged, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
4
|
+
import { acquireSidecarLock } from "./sidecar-lock-acquire.js";
|
|
8
5
|
import { createHeldSidecarLockHandle } from "./sidecar-lock-handle.js";
|
|
9
|
-
import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, } from "./sidecar-lock-policy.js";
|
|
10
6
|
const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
7
|
+
const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
|
|
8
|
+
const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
|
|
11
9
|
function getGlobalManagers() {
|
|
12
10
|
const globalWithState = globalThis;
|
|
13
11
|
if (!globalWithState[GLOBAL_STATE_KEY]) {
|
|
@@ -79,17 +77,6 @@ function snapshotMatchesSync(lockPath, observed) {
|
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
}
|
|
82
|
-
async function resolveNormalizedTargetPath(targetPath) {
|
|
83
|
-
const resolved = path.resolve(targetPath);
|
|
84
|
-
const dir = path.dirname(resolved);
|
|
85
|
-
await fs.mkdir(dir, { recursive: true });
|
|
86
|
-
try {
|
|
87
|
-
return path.join(await fs.realpath(dir), path.basename(resolved));
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
return resolved;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
80
|
function releaseAllReclaimGuardsSync(state) {
|
|
94
81
|
for (const reclaimGuardPath of state.reclaimGuards) {
|
|
95
82
|
try {
|
|
@@ -116,6 +103,19 @@ function releaseAllLocksSync(state) {
|
|
|
116
103
|
}
|
|
117
104
|
releaseAllReclaimGuardsSync(state);
|
|
118
105
|
}
|
|
106
|
+
function ensureGlobalExitCleanupRegistered() {
|
|
107
|
+
const globalWithCleanup = globalThis;
|
|
108
|
+
if (globalWithCleanup[GLOBAL_CLEANUP_KEY])
|
|
109
|
+
return;
|
|
110
|
+
globalWithCleanup[GLOBAL_CLEANUP_KEY] = true;
|
|
111
|
+
const cleanup = () => {
|
|
112
|
+
for (const state of getGlobalManagers().values()) {
|
|
113
|
+
releaseAllLocksSync(state);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
globalWithCleanup[GLOBAL_CLEANUP_HANDLER_KEY] = cleanup;
|
|
117
|
+
process.on("exit", cleanup);
|
|
118
|
+
}
|
|
119
119
|
async function releaseHeldLock(state, normalizedTargetPath, held, options = {}) {
|
|
120
120
|
const current = state.held.get(normalizedTargetPath);
|
|
121
121
|
if (current !== held) {
|
|
@@ -164,217 +164,18 @@ function handleForHeldLock(state, normalizedTargetPath, held) {
|
|
|
164
164
|
export function createSidecarLockManager(key) {
|
|
165
165
|
const state = resolveManagerState(key);
|
|
166
166
|
function ensureExitCleanupRegistered() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
process.on("exit", () => releaseAllLocksSync(state));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (!state.reclaimCleanupRegistered) {
|
|
174
|
-
state.reclaimCleanupRegistered = true;
|
|
175
|
-
process.on("exit", () => releaseAllReclaimGuardsSync(state));
|
|
176
|
-
}
|
|
167
|
+
state.cleanupRegistered = true;
|
|
168
|
+
state.reclaimCleanupRegistered = true;
|
|
169
|
+
ensureGlobalExitCleanupRegistered();
|
|
177
170
|
}
|
|
178
171
|
async function acquire(options) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
options.reentrantOwner === held.reentrantOwner) {
|
|
187
|
-
held.refCount += 1;
|
|
188
|
-
return handleForHeldLock(state, normalizedTargetPath, held);
|
|
189
|
-
}
|
|
190
|
-
const startedAt = Date.now();
|
|
191
|
-
const retry = options.retry ?? {};
|
|
192
|
-
const maxRetries = options.timeoutMs === Number.POSITIVE_INFINITY ? undefined : retry.retries;
|
|
193
|
-
const reclaimGuardPath = `${lockPath}.reclaim`;
|
|
194
|
-
let ownsReclaimGuard = false;
|
|
195
|
-
let attempt = 0;
|
|
196
|
-
const waitForRetry = async () => {
|
|
197
|
-
const elapsed = Date.now() - startedAt;
|
|
198
|
-
if ((options.timeoutMs !== undefined &&
|
|
199
|
-
options.timeoutMs !== Number.POSITIVE_INFINITY &&
|
|
200
|
-
elapsed >= options.timeoutMs) ||
|
|
201
|
-
(maxRetries !== undefined && attempt >= maxRetries)) {
|
|
202
|
-
throw Object.assign(new Error(`file lock timeout for ${normalizedTargetPath}`), {
|
|
203
|
-
code: "file_lock_timeout",
|
|
204
|
-
lockPath,
|
|
205
|
-
normalizedTargetPath,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
const remaining = options.timeoutMs === undefined || options.timeoutMs === Number.POSITIVE_INFINITY
|
|
209
|
-
? Number.POSITIVE_INFINITY
|
|
210
|
-
: Math.max(0, options.timeoutMs - elapsed);
|
|
211
|
-
const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
|
|
212
|
-
attempt += 1;
|
|
213
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
214
|
-
};
|
|
215
|
-
try {
|
|
216
|
-
while (true) {
|
|
217
|
-
if (!ownsReclaimGuard && (await sidecarReclaimGuardExists(reclaimGuardPath))) {
|
|
218
|
-
await waitForRetry();
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
let handle = null;
|
|
222
|
-
try {
|
|
223
|
-
const payload = await options.payload();
|
|
224
|
-
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
225
|
-
if (options.lockRoot) {
|
|
226
|
-
const relativeLockPath = relativeSidecarLockPath(options.lockRoot, lockPath);
|
|
227
|
-
try {
|
|
228
|
-
await options.lockRoot.create(relativeLockPath, raw, { mkdir: true, mode: 0o600 });
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
if (error instanceof FsSafeError && error.code === "already-exists") {
|
|
232
|
-
throw Object.assign(new Error("sidecar lock exists"), { code: "EEXIST" });
|
|
233
|
-
}
|
|
234
|
-
throw error;
|
|
235
|
-
}
|
|
236
|
-
handle = (await options.lockRoot.open(relativeLockPath)).handle;
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
handle = (await createNativeExclusiveFile(lockPath, 0o600)) ?? await fs.open(lockPath, "wx");
|
|
240
|
-
await handle.writeFile(raw, "utf8");
|
|
241
|
-
}
|
|
242
|
-
const snapshot = { raw, payload, stat: await handle.stat(), ownershipToken };
|
|
243
|
-
const createdHeld = {
|
|
244
|
-
refCount: 1,
|
|
245
|
-
reentrantOwner: options.reentrantOwner,
|
|
246
|
-
handle,
|
|
247
|
-
lockPath,
|
|
248
|
-
snapshot,
|
|
249
|
-
acquiredAt: Date.now(),
|
|
250
|
-
metadata: options.metadata ?? {},
|
|
251
|
-
lockRoot: options.lockRoot,
|
|
252
|
-
parsePayload: options.parsePayload,
|
|
253
|
-
};
|
|
254
|
-
state.held.set(normalizedTargetPath, createdHeld);
|
|
255
|
-
if (ownsReclaimGuard) {
|
|
256
|
-
try {
|
|
257
|
-
await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
|
|
258
|
-
ownsReclaimGuard = false;
|
|
259
|
-
}
|
|
260
|
-
catch (err) {
|
|
261
|
-
await releaseHeldLock(state, normalizedTargetPath, createdHeld, { force: true });
|
|
262
|
-
throw err;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
const returnedHandle = handleForHeldLock(state, normalizedTargetPath, createdHeld);
|
|
266
|
-
const interval = options.compromiseCheckIntervalMs;
|
|
267
|
-
if (options.onCompromised && interval !== undefined && interval > 0) {
|
|
268
|
-
createdHeld.compromiseTimer = setInterval(() => {
|
|
269
|
-
void returnedHandle.verifyStillHeld().then((stillHeld) => {
|
|
270
|
-
if (!stillHeld && createdHeld.compromiseTimer) {
|
|
271
|
-
clearInterval(createdHeld.compromiseTimer);
|
|
272
|
-
createdHeld.compromiseTimer = undefined;
|
|
273
|
-
options.onCompromised?.({ lockPath, normalizedTargetPath });
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
}, interval);
|
|
277
|
-
createdHeld.compromiseTimer.unref();
|
|
278
|
-
}
|
|
279
|
-
return returnedHandle;
|
|
280
|
-
}
|
|
281
|
-
catch (err) {
|
|
282
|
-
if (handle) {
|
|
283
|
-
const failedSnapshot = { payload: null };
|
|
284
|
-
try {
|
|
285
|
-
failedSnapshot.stat = await handle.stat();
|
|
286
|
-
}
|
|
287
|
-
catch {
|
|
288
|
-
// Best-effort cleanup of a failed exclusive create.
|
|
289
|
-
}
|
|
290
|
-
const current = state.held.get(normalizedTargetPath);
|
|
291
|
-
if (current?.handle === handle) {
|
|
292
|
-
state.held.delete(normalizedTargetPath);
|
|
293
|
-
}
|
|
294
|
-
// If payload serialization/write fails, the file may be empty or
|
|
295
|
-
// partial JSON, so remove while our exclusive handle is still open.
|
|
296
|
-
if (!options.lockRoot) {
|
|
297
|
-
await fs.rm(lockPath, { force: true }).catch(() => undefined);
|
|
298
|
-
}
|
|
299
|
-
await handle.close().catch(() => undefined);
|
|
300
|
-
// Windows can refuse removing an open file; retry after close but
|
|
301
|
-
// only if the path still points at the file identity we created.
|
|
302
|
-
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
303
|
-
lockRoot: options.lockRoot,
|
|
304
|
-
parsePayload: options.parsePayload,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
if (err.code !== "EEXIST") {
|
|
308
|
-
throw err;
|
|
309
|
-
}
|
|
310
|
-
if (ownsReclaimGuard) {
|
|
311
|
-
await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
|
|
312
|
-
ownsReclaimGuard = false;
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
const nowMs = Date.now();
|
|
316
|
-
const snapshot = await readSidecarLockSnapshot(lockPath, {
|
|
317
|
-
lockRoot: options.lockRoot,
|
|
318
|
-
parsePayload: options.parsePayload,
|
|
319
|
-
});
|
|
320
|
-
if (!snapshot) {
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
if (state.held.has(normalizedTargetPath)) {
|
|
324
|
-
await waitForRetry();
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
const shouldReclaim = options.shouldReclaim ?? defaultSidecarLockShouldReclaim;
|
|
328
|
-
if (await shouldReclaim({
|
|
329
|
-
lockPath,
|
|
330
|
-
normalizedTargetPath,
|
|
331
|
-
payload: snapshot?.payload ?? null,
|
|
332
|
-
staleMs: options.staleMs,
|
|
333
|
-
nowMs,
|
|
334
|
-
heldByThisProcess: state.held.has(normalizedTargetPath),
|
|
335
|
-
})) {
|
|
336
|
-
if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
|
|
337
|
-
lockRoot: options.lockRoot,
|
|
338
|
-
parsePayload: options.parsePayload,
|
|
339
|
-
}))) {
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
const staleRecovery = options.staleRecovery ?? "fail-closed";
|
|
343
|
-
if (staleRecovery === "remove-if-unchanged") {
|
|
344
|
-
if (!(await tryAcquireSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath))) {
|
|
345
|
-
await waitForRetry();
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
ownsReclaimGuard = true;
|
|
349
|
-
const removal = await removeStaleSidecarLockIfAllowed({
|
|
350
|
-
lockPath,
|
|
351
|
-
normalizedTargetPath,
|
|
352
|
-
snapshot,
|
|
353
|
-
shouldRemoveStaleLock: options.shouldRemoveStaleLock,
|
|
354
|
-
lockRoot: options.lockRoot,
|
|
355
|
-
parsePayload: options.parsePayload,
|
|
356
|
-
});
|
|
357
|
-
if (removal === "removed" || removal === "changed") {
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath);
|
|
361
|
-
ownsReclaimGuard = false;
|
|
362
|
-
}
|
|
363
|
-
throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
|
|
364
|
-
code: "file_lock_stale",
|
|
365
|
-
lockPath,
|
|
366
|
-
normalizedTargetPath,
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
await waitForRetry();
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
finally {
|
|
374
|
-
if (ownsReclaimGuard) {
|
|
375
|
-
await releaseSidecarReclaimGuard(state.reclaimGuards, reclaimGuardPath).catch(() => undefined);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
172
|
+
return await acquireSidecarLock(options, {
|
|
173
|
+
held: state.held,
|
|
174
|
+
reclaimGuards: state.reclaimGuards,
|
|
175
|
+
ensureExitCleanupRegistered,
|
|
176
|
+
handleForHeldLock: (normalizedTargetPath, held) => handleForHeldLock(state, normalizedTargetPath, held),
|
|
177
|
+
releaseHeldLock: async (normalizedTargetPath, held, releaseOptions) => await releaseHeldLock(state, normalizedTargetPath, held, releaseOptions),
|
|
178
|
+
});
|
|
378
179
|
}
|
|
379
180
|
async function withLock(options, fn) {
|
|
380
181
|
const lock = await acquire(options);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symlink-parents.d.ts","sourceRoot":"","sources":["../src/symlink-parents.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"symlink-parents.d.ts","sourceRoot":"","sources":["../src/symlink-parents.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAyBF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,6BAA6B,GACpC,IAAI,CA6BN"}
|
package/dist/symlink-parents.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
5
|
+
import { hasNodeErrorCode, isPathRelativeEscape } from "./path.js";
|
|
5
6
|
function resolvePathWalk(params) {
|
|
6
7
|
const root = path.resolve(params.rootDir);
|
|
7
8
|
const target = path.resolve(params.targetPath);
|
|
@@ -26,7 +27,7 @@ export async function assertNoSymlinkParents(params) {
|
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
let current = walk.root;
|
|
29
|
-
for (const segment of walk.segments) {
|
|
30
|
+
for (const [index, segment] of walk.segments.entries()) {
|
|
30
31
|
current = path.join(current, segment);
|
|
31
32
|
try {
|
|
32
33
|
const stat = await fs.lstat(current);
|
|
@@ -36,12 +37,12 @@ export async function assertNoSymlinkParents(params) {
|
|
|
36
37
|
}
|
|
37
38
|
throw new Error(formatUnsafePath(params, current));
|
|
38
39
|
}
|
|
39
|
-
if (params.requireDirectories && !stat.isDirectory()) {
|
|
40
|
-
throw new
|
|
40
|
+
if ((params.requireDirectories || index < walk.segments.length - 1) && !stat.isDirectory()) {
|
|
41
|
+
throw new FsSafeError("not-file", `${params.messagePrefix ?? "Path"} must traverse directories: ${current}`);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
catch (err) {
|
|
44
|
-
if (
|
|
45
|
+
if (hasNodeErrorCode(err, "ENOENT") && params.allowMissing !== false) {
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
throw err;
|
|
@@ -54,7 +55,7 @@ export function assertNoSymlinkParentsSync(params) {
|
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
let current = walk.root;
|
|
57
|
-
for (const segment of walk.segments) {
|
|
58
|
+
for (const [index, segment] of walk.segments.entries()) {
|
|
58
59
|
current = path.join(current, segment);
|
|
59
60
|
try {
|
|
60
61
|
const stat = fsSync.lstatSync(current);
|
|
@@ -64,8 +65,8 @@ export function assertNoSymlinkParentsSync(params) {
|
|
|
64
65
|
}
|
|
65
66
|
throw new Error(formatUnsafePath(params, current));
|
|
66
67
|
}
|
|
67
|
-
if (params.requireDirectories && !stat.isDirectory()) {
|
|
68
|
-
throw new
|
|
68
|
+
if ((params.requireDirectories || index < walk.segments.length - 1) && !stat.isDirectory()) {
|
|
69
|
+
throw new FsSafeError("not-file", `${params.messagePrefix ?? "Path"} must traverse directories: ${current}`);
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"temp-target.d.ts","sourceRoot":"","sources":["../src/temp-target.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AA0EF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAaT;AAsCD,wBAAsB,QAAQ,CAAC,MAAM,EAAE;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBpB;AAED,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE;IACN,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC3C,EACD,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
|
package/dist/temp-target.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { lstat, mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { sameFileIdentity } from "./file-identity.js";
|
|
4
5
|
import { assertSafePathSegment, sanitizeSafePathSegment } from "./safe-path-segment.js";
|
|
5
6
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
6
7
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
@@ -89,8 +90,17 @@ function isNodeErrorWithCode(err, code) {
|
|
|
89
90
|
"code" in err &&
|
|
90
91
|
err.code === code);
|
|
91
92
|
}
|
|
92
|
-
async function cleanupTempDir(dir, onCleanupError) {
|
|
93
|
+
async function cleanupTempDir(dir, identity, onCleanupError) {
|
|
93
94
|
try {
|
|
95
|
+
const current = await lstat(dir).catch((error) => {
|
|
96
|
+
if (isNodeErrorWithCode(error, "ENOENT")) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
});
|
|
101
|
+
if (!current || !sameFileIdentity(current, identity)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
94
104
|
await rm(dir, { recursive: true, force: true });
|
|
95
105
|
}
|
|
96
106
|
catch (err) {
|
|
@@ -106,11 +116,12 @@ export async function tempFile(params) {
|
|
|
106
116
|
const rootDir = resolveTempRoot(params.rootDir);
|
|
107
117
|
const prefix = `${sanitizePrefix(params.prefix)}-`;
|
|
108
118
|
const dir = await mkdtemp(path.join(rootDir, prefix));
|
|
109
|
-
const
|
|
119
|
+
const identity = await lstat(dir);
|
|
120
|
+
const unregisterTempDir = registerTempPathForExit(dir, { recursive: true, identity });
|
|
110
121
|
const file = (fileName) => path.join(dir, sanitizeTempFileName(fileName ?? params.fileName ?? "download.bin"));
|
|
111
122
|
const cleanup = async () => {
|
|
112
123
|
try {
|
|
113
|
-
await cleanupTempDir(dir, params.onCleanupError);
|
|
124
|
+
await cleanupTempDir(dir, identity, params.onCleanupError);
|
|
114
125
|
}
|
|
115
126
|
finally {
|
|
116
127
|
unregisterTempDir();
|
package/dist/test-hooks.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type FsSafeTestHooks = {
|
|
|
10
10
|
beforeRootFallbackMutation?: (operation: "mkdir" | "move" | "remove", targetPath: string) => Promise<void> | void;
|
|
11
11
|
afterPinnedWriteFallbackRename?: (targetPath: string) => Promise<void> | void;
|
|
12
12
|
beforeSiblingTempWrite?: (tempPath: string) => Promise<void> | void;
|
|
13
|
+
beforeSidecarLockSnapshotOpen?: (lockPath: string) => Promise<void> | void;
|
|
13
14
|
beforeTrashMove?: (targetPath: string, destPath: string) => void;
|
|
14
15
|
afterPublishTargetCreated?: (method: "hardlink" | "exclusive-copy" | "rename-noreplace", targetPath: string, identity: FileIdentityStat) => Promise<void> | void;
|
|
15
16
|
beforePublishDirectorySync?: (method: "hardlink" | "exclusive-copy" | "rename-noreplace", targetPath: string, identity: FileIdentityStat) => Promise<void> | void;
|
package/dist/test-hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-hooks.d.ts","sourceRoot":"","sources":["../src/test-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,MAAM,eAAe,GAAG;IAC5B,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3E,2BAA2B,CAAC,EAAE,CAC5B,SAAS,EAAE,OAAO,GAAG,OAAO,EAC5B,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,2BAA2B,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxE,+BAA+B,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,0BAA0B,CAAC,EAAE,CAC3B,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,EACtC,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,8BAA8B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9E,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpE,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,yBAAyB,CAAC,EAAE,CAC1B,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,EAC1D,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,gBAAgB,KACvB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,0BAA0B,CAAC,EAAE,CAC3B,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,EAC1D,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,gBAAgB,KACvB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3B,CAAC;AAQF,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI,CAKzE"}
|
|
1
|
+
{"version":3,"file":"test-hooks.d.ts","sourceRoot":"","sources":["../src/test-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,MAAM,eAAe,GAAG;IAC5B,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3E,2BAA2B,CAAC,EAAE,CAC5B,SAAS,EAAE,OAAO,GAAG,OAAO,EAC5B,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,2BAA2B,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxE,+BAA+B,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,0BAA0B,CAAC,EAAE,CAC3B,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,EACtC,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,8BAA8B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9E,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpE,6BAA6B,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3E,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,yBAAyB,CAAC,EAAE,CAC1B,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,EAC1D,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,gBAAgB,KACvB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,0BAA0B,CAAC,EAAE,CAC3B,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,EAC1D,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,gBAAgB,KACvB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3B,CAAC;AAQF,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI,CAKzE"}
|
package/dist/timing.d.ts
CHANGED
package/dist/timing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAA;CAErF,GACA,OAAO,CAAC,CAAC,CAAC,CA6BZ"}
|
|
1
|
+
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAK1C;AAED,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAA;CAErF,GACA,OAAO,CAAC,CAAC,CAAC,CA6BZ"}
|
package/dist/timing.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export function sleep(ms) {
|
|
2
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3
|
+
}
|
|
4
|
+
export function sleepSync(ms) {
|
|
5
|
+
if (ms <= 0) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
9
|
+
}
|
|
1
10
|
export async function withTimeout(promise, timeoutMs, labelOrOptions = {
|
|
2
11
|
message: "timeout",
|
|
3
12
|
}) {
|
package/dist/trash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../src/trash.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;
|
|
1
|
+
{"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../src/trash.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAkLF,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,CAcjB"}
|
package/dist/trash.js
CHANGED
|
@@ -117,6 +117,15 @@ function reserveTrashDestination(trashDir, base, timestamp) {
|
|
|
117
117
|
}
|
|
118
118
|
return resolveContainedPath(container, base);
|
|
119
119
|
}
|
|
120
|
+
function copyTrashTargetSync(target, dest) {
|
|
121
|
+
if (target.stat.isSymbolicLink()) {
|
|
122
|
+
const linkTarget = fs.readlinkSync(target.path);
|
|
123
|
+
assertTrashTargetGuard(target);
|
|
124
|
+
fs.symlinkSync(linkTarget, dest);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
fs.cpSync(target.path, dest, { recursive: true, force: false, errorOnExist: true });
|
|
128
|
+
}
|
|
120
129
|
function movePathToDestination(target, dest) {
|
|
121
130
|
getFsSafeTestHooks()?.beforeTrashMove?.(target.path, dest);
|
|
122
131
|
assertTrashTargetGuard(target);
|
|
@@ -134,7 +143,7 @@ function movePathToDestination(target, dest) {
|
|
|
134
143
|
}
|
|
135
144
|
try {
|
|
136
145
|
assertTrashTargetGuard(target);
|
|
137
|
-
|
|
146
|
+
copyTrashTargetSync(target, dest);
|
|
138
147
|
assertTrashTargetGuard(target);
|
|
139
148
|
guardedRmSync({ target: target.path, recursive: true, force: false, verifyAfter: false });
|
|
140
149
|
return true;
|
package/dist/walk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AA+FF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CAyDjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,+BAA+B,CAAC,CAyD1C"}
|
package/dist/walk.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
function validateWalkBudget(name, value) {
|
|
5
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
6
|
+
throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function validateWalkOptions(options) {
|
|
10
|
+
validateWalkBudget("maxDepth", options.maxDepth);
|
|
11
|
+
validateWalkBudget("maxEntries", options.maxEntries);
|
|
12
|
+
if (options.symlinks !== undefined &&
|
|
13
|
+
!["skip", "follow", "include"].includes(options.symlinks)) {
|
|
14
|
+
throw new TypeError(`invalid walk symlink policy: ${String(options.symlinks)}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
function kindForDirent(dirent) {
|
|
5
18
|
if (dirent.isDirectory())
|
|
6
19
|
return "directory";
|
|
@@ -75,6 +88,7 @@ async function resolveAsyncKind(fullPath, dirent, symlinks) {
|
|
|
75
88
|
return "other";
|
|
76
89
|
}
|
|
77
90
|
export function walkDirectorySync(rootDir, options = {}) {
|
|
91
|
+
validateWalkOptions(options);
|
|
78
92
|
const root = path.resolve(rootDir);
|
|
79
93
|
const symlinks = options.symlinks ?? "skip";
|
|
80
94
|
const result = {
|
|
@@ -133,6 +147,7 @@ export function walkDirectorySync(rootDir, options = {}) {
|
|
|
133
147
|
return result;
|
|
134
148
|
}
|
|
135
149
|
export async function walkDirectory(rootDir, options = {}) {
|
|
150
|
+
validateWalkOptions(options);
|
|
136
151
|
const root = path.resolve(rootDir);
|
|
137
152
|
const symlinks = options.symlinks ?? "skip";
|
|
138
153
|
const result = {
|