@openclaw/fs-safe 0.6.0 → 0.7.1
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 +79 -0
- package/README.md +7 -0
- package/dist/archive-deadline.d.ts +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +44 -8
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +1 -0
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +26 -16
- package/dist/archive-limits.d.ts +11 -3
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +24 -0
- package/dist/archive-native.d.ts +3 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +24 -9
- package/dist/archive-policy.d.ts +2 -0
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +9 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +69 -41
- package/dist/archive-staging.d.ts +3 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +91 -43
- package/dist/archive-tar-admission.d.ts +7 -0
- package/dist/archive-tar-admission.d.ts.map +1 -0
- package/dist/archive-tar-admission.js +43 -0
- package/dist/archive-tar-gnu.d.ts +2 -0
- package/dist/archive-tar-gnu.d.ts.map +1 -0
- package/dist/archive-tar-gnu.js +20 -0
- package/dist/archive-tar-header.d.ts +8 -0
- package/dist/archive-tar-header.d.ts.map +1 -0
- package/dist/archive-tar-header.js +47 -0
- package/dist/archive-tar-meta.d.ts +29 -1
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +104 -35
- package/dist/archive-tar-runtime.d.ts +3 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +3 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -2
- package/dist/archive-zip-names.js +2 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +38 -27
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +2 -4
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +3 -10
- package/dist/byte-budget.d.ts +5 -0
- package/dist/byte-budget.d.ts.map +1 -0
- package/dist/byte-budget.js +9 -0
- package/dist/directory-guard.d.ts +2 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +10 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +81 -30
- package/dist/file-observation.d.ts +9 -0
- package/dist/file-observation.d.ts.map +1 -0
- package/dist/file-observation.js +22 -0
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +9 -5
- package/dist/file-store-limit.d.ts +2 -0
- package/dist/file-store-limit.d.ts.map +1 -0
- package/dist/file-store-limit.js +8 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +38 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +26 -26
- package/dist/json-durable-queue-directory.d.ts +2 -0
- package/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/dist/json-durable-queue-directory.js +20 -0
- package/dist/json-durable-queue-ownership.d.ts +14 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/dist/json-durable-queue-ownership.js +168 -0
- package/dist/json-durable-queue-retirement.d.ts +9 -0
- package/dist/json-durable-queue-retirement.d.ts.map +1 -0
- package/dist/json-durable-queue-retirement.js +126 -0
- package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
- package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
- package/dist/json-durable-queue-transfer-lock.js +19 -0
- package/dist/json-durable-queue.d.ts +1 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +90 -57
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +32 -13
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +12 -3
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +1 -1
- package/dist/opened-file-failure.d.ts +7 -0
- package/dist/opened-file-failure.d.ts.map +1 -0
- package/dist/opened-file-failure.js +41 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +8 -2
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +11 -110
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +10 -10
- package/dist/private-temp-workspace.d.ts +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +81 -56
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -4
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +6 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +136 -82
- package/dist/replace-file-copy-fallback.d.ts +3 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +21 -36
- package/dist/replace-file-copy-source.d.ts +21 -0
- package/dist/replace-file-copy-source.d.ts.map +1 -0
- package/dist/replace-file-copy-source.js +112 -0
- package/dist/replace-file-descriptor.d.ts +13 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +32 -7
- package/dist/replace-file-rename-policy.d.ts +7 -0
- package/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/dist/replace-file-rename-policy.js +30 -0
- package/dist/replace-file-temp-owner.d.ts +46 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/dist/replace-file-temp-owner.js +346 -0
- package/dist/replace-file.d.ts +6 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +72 -58
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +113 -82
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +6 -3
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +6 -5
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +17 -13
- package/dist/sibling-staged-file.d.ts +15 -0
- package/dist/sibling-staged-file.d.ts.map +1 -0
- package/dist/sibling-staged-file.js +148 -0
- package/dist/sibling-temp.d.ts +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +30 -74
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +91 -37
- package/dist/sidecar-lock-handle.d.ts +6 -2
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +17 -3
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts +16 -5
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +60 -28
- package/dist/sidecar-lock-root.d.ts +3 -0
- package/dist/sidecar-lock-root.d.ts.map +1 -0
- package/dist/sidecar-lock-root.js +76 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +4 -1
- package/dist/suppressed-error.d.ts +6 -0
- package/dist/suppressed-error.d.ts.map +1 -0
- package/dist/suppressed-error.js +15 -0
- package/dist/temp-cleanup.d.ts +2 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +25 -10
- package/dist/temp-workspace-owner.d.ts +23 -0
- package/dist/temp-workspace-owner.d.ts.map +1 -0
- package/dist/temp-workspace-owner.js +320 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +5 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/docs/advanced.md +8 -1
- package/docs/archive.md +202 -26
- package/docs/atomic.md +11 -2
- package/docs/durability.md +1 -1
- package/docs/file-store.md +2 -0
- package/docs/install.md +8 -4
- package/docs/json.md +11 -4
- package/docs/native-helper.md +15 -3
- package/docs/native.md +33 -6
- package/docs/output.md +16 -3
- package/docs/public-api.md +6 -3
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +1 -1
- package/docs/regular-file.md +9 -2
- package/docs/root.md +2 -0
- package/docs/secret-file.md +5 -1
- package/docs/secure-file.md +4 -2
- package/docs/security-model.md +18 -0
- package/docs/sidecar-lock.md +60 -4
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- package/docs/writing.md +6 -0
- package/package.json +10 -10
|
@@ -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":"AAUA,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;AAsLjC,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;iBAqBW,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
|
@@ -3,6 +3,7 @@ import { sameFileIdentity } from "./file-identity.js";
|
|
|
3
3
|
import { removeSidecarLockIfUnchanged, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
4
4
|
import { acquireSidecarLock } from "./sidecar-lock-acquire.js";
|
|
5
5
|
import { createHeldSidecarLockHandle } from "./sidecar-lock-handle.js";
|
|
6
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
6
7
|
const GLOBAL_STATE_KEY = Symbol.for("fsSafe.sidecarLockManagers");
|
|
7
8
|
const GLOBAL_CLEANUP_KEY = Symbol.for("fsSafe.sidecarLockCleanupRegistered");
|
|
8
9
|
const GLOBAL_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.sidecarLockCleanupHandler");
|
|
@@ -121,23 +122,18 @@ async function releaseHeldLock(state, normalizedTargetPath, held, options = {})
|
|
|
121
122
|
if (current !== held) {
|
|
122
123
|
return false;
|
|
123
124
|
}
|
|
125
|
+
if (held.releasePromise) {
|
|
126
|
+
await held.releasePromise;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
124
129
|
if (options.force) {
|
|
125
130
|
held.refCount = 0;
|
|
126
131
|
}
|
|
127
|
-
else {
|
|
132
|
+
else if (!options.retry && held.refCount > 0) {
|
|
128
133
|
held.refCount -= 1;
|
|
129
|
-
if (held.refCount > 0) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (held.releasePromise) {
|
|
134
|
-
await held.releasePromise.catch(() => undefined);
|
|
135
|
-
return true;
|
|
136
134
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
clearInterval(held.compromiseTimer);
|
|
140
|
-
held.compromiseTimer = undefined;
|
|
135
|
+
if (held.refCount > 0) {
|
|
136
|
+
return false;
|
|
141
137
|
}
|
|
142
138
|
held.releasePromise = (async () => {
|
|
143
139
|
await held.handle.close().catch(() => undefined);
|
|
@@ -145,6 +141,13 @@ async function releaseHeldLock(state, normalizedTargetPath, held, options = {})
|
|
|
145
141
|
lockRoot: held.lockRoot,
|
|
146
142
|
parsePayload: held.parsePayload,
|
|
147
143
|
});
|
|
144
|
+
if (state.held.get(normalizedTargetPath) === held) {
|
|
145
|
+
state.held.delete(normalizedTargetPath);
|
|
146
|
+
}
|
|
147
|
+
if (held.compromiseTimer) {
|
|
148
|
+
clearInterval(held.compromiseTimer);
|
|
149
|
+
held.compromiseTimer = undefined;
|
|
150
|
+
}
|
|
148
151
|
})();
|
|
149
152
|
try {
|
|
150
153
|
await held.releasePromise;
|
|
@@ -158,7 +161,7 @@ function handleForHeldLock(state, normalizedTargetPath, held) {
|
|
|
158
161
|
return createHeldSidecarLockHandle({
|
|
159
162
|
normalizedTargetPath,
|
|
160
163
|
held,
|
|
161
|
-
release: async () => await releaseHeldLock(state, normalizedTargetPath, held),
|
|
164
|
+
release: async (options) => await releaseHeldLock(state, normalizedTargetPath, held, { retry: options?.retry }),
|
|
162
165
|
});
|
|
163
166
|
}
|
|
164
167
|
export function createSidecarLockManager(key) {
|
|
@@ -179,12 +182,21 @@ export function createSidecarLockManager(key) {
|
|
|
179
182
|
}
|
|
180
183
|
async function withLock(options, fn) {
|
|
181
184
|
const lock = await acquire(options);
|
|
185
|
+
let result;
|
|
182
186
|
try {
|
|
183
|
-
|
|
187
|
+
result = await fn();
|
|
184
188
|
}
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
catch (bodyError) {
|
|
190
|
+
try {
|
|
191
|
+
await lock.release();
|
|
192
|
+
}
|
|
193
|
+
catch (releaseError) {
|
|
194
|
+
throw createSuppressedError(releaseError, bodyError, "file lock callback and release both failed");
|
|
195
|
+
}
|
|
196
|
+
throw bodyError;
|
|
187
197
|
}
|
|
198
|
+
await lock.release();
|
|
199
|
+
return result;
|
|
188
200
|
}
|
|
189
201
|
async function drain() {
|
|
190
202
|
for (const [normalizedTargetPath, held] of Array.from(state.held.entries())) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AA+B1D,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EACnE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,iBAAiB,EACjE,OAAO,EAAE,MAAM,CAAC,EAChB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,CAAC,CAOH"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { recordFileObservationFailure } from "./file-observation.js";
|
|
2
3
|
function identityMismatch() {
|
|
3
|
-
|
|
4
|
+
const error = new FsSafeError("path-mismatch", "file identity changed or could not be verified");
|
|
5
|
+
recordFileObservationFailure(error, "identity");
|
|
6
|
+
return error;
|
|
4
7
|
}
|
|
5
8
|
function identityCheck(expected, platform) {
|
|
6
9
|
const known = {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suppressed-error.d.ts","sourceRoot":"","sources":["../src/suppressed-error.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAQF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,MAAM,GACd,mBAAmB,CAgBrB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function createSuppressedError(error, suppressed, message) {
|
|
2
|
+
const constructor = globalThis.SuppressedError;
|
|
3
|
+
if (constructor) {
|
|
4
|
+
return new constructor(error, suppressed, message);
|
|
5
|
+
}
|
|
6
|
+
const combined = new Error(message, {
|
|
7
|
+
cause: error instanceof Error ? error : undefined,
|
|
8
|
+
});
|
|
9
|
+
combined.name = "SuppressedError";
|
|
10
|
+
Object.defineProperties(combined, {
|
|
11
|
+
error: { configurable: true, value: error },
|
|
12
|
+
suppressed: { configurable: true, value: suppressed },
|
|
13
|
+
});
|
|
14
|
+
return combined;
|
|
15
|
+
}
|
package/dist/temp-cleanup.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type TempPathRegistration = (() => void) & {
|
|
|
6
6
|
export declare function registerTempPathForExit(tempPath: string, options?: {
|
|
7
7
|
recursive?: boolean;
|
|
8
8
|
identity?: FileIdentityStat;
|
|
9
|
+
singleLinkFile?: boolean;
|
|
10
|
+
cleanupSync?: () => unknown;
|
|
9
11
|
}): TempPathRegistration;
|
|
10
12
|
export declare function __cleanupRegisteredTempPathsForTest(): void;
|
|
11
13
|
export declare function __cleanupRegisteredTempPathForTest(tempPath: string): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temp-cleanup.d.ts","sourceRoot":"","sources":["../src/temp-cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAChD,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/C,CAAC;
|
|
1
|
+
{"version":3,"file":"temp-cleanup.d.ts","sourceRoot":"","sources":["../src/temp-cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAChD,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/C,CAAC;AAkDF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B,GACA,oBAAoB,CA2BtB;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUzE"}
|
package/dist/temp-cleanup.js
CHANGED
|
@@ -7,18 +7,32 @@ function pathStillMatchesReceipt(entry) {
|
|
|
7
7
|
return false;
|
|
8
8
|
}
|
|
9
9
|
try {
|
|
10
|
-
|
|
10
|
+
const current = fsSync.lstatSync(entry.path, { bigint: true });
|
|
11
|
+
return (!entry.singleLinkFile || (current.isFile() && current.nlink === 1n)) &&
|
|
12
|
+
sameFileIdentityForCleanup(current, entry.identity);
|
|
11
13
|
}
|
|
12
14
|
catch (error) {
|
|
13
|
-
return error.code === "ENOENT";
|
|
15
|
+
return !entry.singleLinkFile && error.code === "ENOENT";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function removeRegisteredPathSync(entry) {
|
|
19
|
+
if (entry.singleLinkFile)
|
|
20
|
+
fsSync.unlinkSync(entry.path);
|
|
21
|
+
else
|
|
22
|
+
fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
23
|
+
}
|
|
24
|
+
function cleanupEntrySync(entry) {
|
|
25
|
+
if (entry.cleanupSync) {
|
|
26
|
+
entry.cleanupSync();
|
|
27
|
+
}
|
|
28
|
+
else if (pathStillMatchesReceipt(entry)) {
|
|
29
|
+
removeRegisteredPathSync(entry);
|
|
14
30
|
}
|
|
15
31
|
}
|
|
16
32
|
function cleanupRegisteredTempPathsSync() {
|
|
17
33
|
for (const entry of tempCleanupEntries.values()) {
|
|
18
34
|
try {
|
|
19
|
-
|
|
20
|
-
fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
21
|
-
}
|
|
35
|
+
cleanupEntrySync(entry);
|
|
22
36
|
}
|
|
23
37
|
catch {
|
|
24
38
|
// Process-exit cleanup is best-effort.
|
|
@@ -35,8 +49,10 @@ export function registerTempPathForExit(tempPath, options) {
|
|
|
35
49
|
path: tempPath,
|
|
36
50
|
recursive: options?.recursive === true,
|
|
37
51
|
identity: options?.identity,
|
|
52
|
+
singleLinkFile: options?.singleLinkFile,
|
|
53
|
+
cleanupSync: options?.cleanupSync,
|
|
38
54
|
};
|
|
39
|
-
if (!entry.identity) {
|
|
55
|
+
if (!entry.identity && !entry.cleanupSync) {
|
|
40
56
|
try {
|
|
41
57
|
entry.identity = fsSync.lstatSync(tempPath, { bigint: true });
|
|
42
58
|
}
|
|
@@ -46,7 +62,8 @@ export function registerTempPathForExit(tempPath, options) {
|
|
|
46
62
|
}
|
|
47
63
|
tempCleanupEntries.set(tempPath, entry);
|
|
48
64
|
const unregister = (() => {
|
|
49
|
-
tempCleanupEntries.
|
|
65
|
+
if (tempCleanupEntries.get(tempPath) === entry)
|
|
66
|
+
tempCleanupEntries.delete(tempPath);
|
|
50
67
|
});
|
|
51
68
|
unregister.setIdentity = (identity) => {
|
|
52
69
|
entry.identity = identity;
|
|
@@ -62,9 +79,7 @@ export function __cleanupRegisteredTempPathForTest(tempPath) {
|
|
|
62
79
|
return;
|
|
63
80
|
}
|
|
64
81
|
try {
|
|
65
|
-
|
|
66
|
-
fsSync.rmSync(entry.path, { force: true, recursive: entry.recursive });
|
|
67
|
-
}
|
|
82
|
+
cleanupEntrySync(entry);
|
|
68
83
|
}
|
|
69
84
|
finally {
|
|
70
85
|
tempCleanupEntries.delete(tempPath);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type FileIdentityStat } from "./file-identity.js";
|
|
2
|
+
import { type NativeBinding } from "./native.js";
|
|
3
|
+
import { openStagedDirectory } from "./staged-directory.js";
|
|
4
|
+
export type TempWorkspaceCleanupResult = "removed" | "missing" | "identity-mismatch" | "indeterminate";
|
|
5
|
+
export type TempWorkspaceCleanupSafety = "compatible" | "require-bounded";
|
|
6
|
+
type RetainedDirectory = ReturnType<typeof openStagedDirectory>;
|
|
7
|
+
export declare class TempWorkspaceCleanupCapability {
|
|
8
|
+
#private;
|
|
9
|
+
readonly binding: NativeBinding | undefined;
|
|
10
|
+
readonly parent: RetainedDirectory | undefined;
|
|
11
|
+
constructor(root: string, safety: TempWorkspaceCleanupSafety);
|
|
12
|
+
get canRemoveOwnedTree(): boolean;
|
|
13
|
+
assertCurrent(): void;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare class TempWorkspaceCleanupOwner {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(dir: string, identity: FileIdentityStat, capability: TempWorkspaceCleanupCapability);
|
|
19
|
+
cleanup(): Promise<TempWorkspaceCleanupResult>;
|
|
20
|
+
cleanupSync(): TempWorkspaceCleanupResult;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=temp-workspace-owner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temp-workspace-owner.d.ts","sourceRoot":"","sources":["../src/temp-workspace-owner.ts"],"names":[],"mappings":"AAMA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAgC,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG1F,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,GAAG,eAAe,CAAC;AACvG,MAAM,MAAM,0BAA0B,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAE1E,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAoBhE,qBAAa,8BAA8B;;IACzC,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAI/C,YAAY,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,0BAA0B,EAiC3D;IAED,IAAI,kBAAkB,IAAI,OAAO,CAEhC;IAED,aAAa,IAAI,IAAI,CASpB;IAED,KAAK,IAAI,IAAI,CAIZ;CACF;AAED,qBAAa,yBAAyB;;IAWpC,YAAY,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,EAAE,8BAA8B,EAe9F;IAuLD,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAM7C;IAED,WAAW,IAAI,0BAA0B,CAexC;CACF"}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fsSync from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createAsyncDirectoryGuard, createSyncDirectoryGuard } from "./directory-guard.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
8
|
+
import { withAsyncDirectoryGuards, withSyncDirectoryGuards } from "./guarded-mutation.js";
|
|
9
|
+
import { getNativeBinding } from "./native.js";
|
|
10
|
+
import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
|
|
11
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
12
|
+
function isNativeCleanupBinding(binding) {
|
|
13
|
+
return typeof binding?.renameNoReplace === "function" &&
|
|
14
|
+
typeof binding.removeOwnedTree === "function" &&
|
|
15
|
+
typeof binding.removeOwnedTreeSync === "function" &&
|
|
16
|
+
typeof binding.ownedTreeRemovalAvailable === "function";
|
|
17
|
+
}
|
|
18
|
+
function nativeRemovalError(result) {
|
|
19
|
+
if (!result.errorCode)
|
|
20
|
+
return undefined;
|
|
21
|
+
return Object.assign(new Error(result.errorMessage ?? "native owned-tree cleanup failed"), {
|
|
22
|
+
code: result.errorCode,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export class TempWorkspaceCleanupCapability {
|
|
26
|
+
binding;
|
|
27
|
+
parent;
|
|
28
|
+
#ownedTreeRemovalAvailable;
|
|
29
|
+
#closed = false;
|
|
30
|
+
constructor(root, safety) {
|
|
31
|
+
let binding;
|
|
32
|
+
try {
|
|
33
|
+
binding = getNativeBinding();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (safety === "require-bounded")
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
this.binding = binding;
|
|
40
|
+
let parent;
|
|
41
|
+
try {
|
|
42
|
+
parent = openStagedDirectory(root);
|
|
43
|
+
assertStagedDirectoryCurrent(parent.receipt);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
if (parent)
|
|
47
|
+
fsSync.closeSync(parent.fd);
|
|
48
|
+
parent = undefined;
|
|
49
|
+
}
|
|
50
|
+
this.parent = parent;
|
|
51
|
+
let available = false;
|
|
52
|
+
if (parent && isNativeCleanupBinding(binding)) {
|
|
53
|
+
try {
|
|
54
|
+
available = binding.ownedTreeRemovalAvailable(parent.fd) === true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Runtime denial must select fallback or reject before child creation.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this.#ownedTreeRemovalAvailable = available;
|
|
61
|
+
if (safety === "require-bounded" && !this.canRemoveOwnedTree) {
|
|
62
|
+
this.close();
|
|
63
|
+
throw new FsSafeError("helper-unavailable", "temp workspace owned-tree cleanup is unavailable");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
get canRemoveOwnedTree() {
|
|
67
|
+
return !this.#closed && this.#ownedTreeRemovalAvailable;
|
|
68
|
+
}
|
|
69
|
+
assertCurrent() {
|
|
70
|
+
if (this.#closed || !this.parent) {
|
|
71
|
+
throw new FsSafeError("path-mismatch", "temp workspace cleanup parent is unavailable");
|
|
72
|
+
}
|
|
73
|
+
assertStagedDirectoryCurrent(this.parent.receipt);
|
|
74
|
+
const current = fsSync.fstatSync(this.parent.fd, { bigint: true });
|
|
75
|
+
if (!sameFileIdentityForCleanup(current, this.parent.receipt.identity)) {
|
|
76
|
+
throw new FsSafeError("path-mismatch", "temp workspace cleanup parent changed");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
close() {
|
|
80
|
+
if (this.#closed)
|
|
81
|
+
return;
|
|
82
|
+
this.#closed = true;
|
|
83
|
+
if (this.parent)
|
|
84
|
+
fsSync.closeSync(this.parent.fd);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export class TempWorkspaceCleanupOwner {
|
|
88
|
+
#dir;
|
|
89
|
+
#identity;
|
|
90
|
+
#capability;
|
|
91
|
+
#directory;
|
|
92
|
+
#closed = false;
|
|
93
|
+
#running = false;
|
|
94
|
+
#exitInterrupted = false;
|
|
95
|
+
#result;
|
|
96
|
+
#pending;
|
|
97
|
+
constructor(dir, identity, capability) {
|
|
98
|
+
this.#dir = dir;
|
|
99
|
+
this.#identity = { dev: identity.dev, ino: identity.ino };
|
|
100
|
+
this.#capability = capability;
|
|
101
|
+
let directory;
|
|
102
|
+
if (capability.canRemoveOwnedTree) {
|
|
103
|
+
directory = openStagedDirectory(dir);
|
|
104
|
+
const current = fsSync.fstatSync(directory.fd, { bigint: true });
|
|
105
|
+
if (!sameFileIdentityForCleanup(current, this.#identity)) {
|
|
106
|
+
fsSync.closeSync(directory.fd);
|
|
107
|
+
capability.close();
|
|
108
|
+
throw new FsSafeError("path-mismatch", "temp workspace changed while retaining cleanup authority");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.#directory = directory;
|
|
112
|
+
}
|
|
113
|
+
#repeat() {
|
|
114
|
+
return this.#result === "removed" ? "missing" : this.#result;
|
|
115
|
+
}
|
|
116
|
+
#close() {
|
|
117
|
+
if (this.#closed)
|
|
118
|
+
return;
|
|
119
|
+
this.#closed = true;
|
|
120
|
+
const errors = [];
|
|
121
|
+
if (this.#directory) {
|
|
122
|
+
try {
|
|
123
|
+
fsSync.closeSync(this.#directory.fd);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
errors.push(error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
this.#capability.close();
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
errors.push(error);
|
|
134
|
+
}
|
|
135
|
+
if (errors.length === 1)
|
|
136
|
+
throw errors[0];
|
|
137
|
+
if (errors.length > 1)
|
|
138
|
+
throw new AggregateError(errors, "temp workspace cleanup descriptor close failed");
|
|
139
|
+
}
|
|
140
|
+
#finish(result) {
|
|
141
|
+
this.#result ??= this.#exitInterrupted ? "indeterminate" : result;
|
|
142
|
+
try {
|
|
143
|
+
this.#close();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.#result = "indeterminate";
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
return this.#result;
|
|
150
|
+
}
|
|
151
|
+
#fallbackResult() {
|
|
152
|
+
try {
|
|
153
|
+
const current = fsSync.lstatSync(this.#dir, { bigint: true });
|
|
154
|
+
return current.isDirectory() && !current.isSymbolicLink() &&
|
|
155
|
+
sameFileIdentityForCleanup(current, this.#identity)
|
|
156
|
+
? "indeterminate"
|
|
157
|
+
: "identity-mismatch";
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return error.code === "ENOENT" ? "missing" : "indeterminate";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
#prepare() {
|
|
164
|
+
const parent = this.#capability.parent;
|
|
165
|
+
if (!parent)
|
|
166
|
+
return this.#fallbackResult();
|
|
167
|
+
try {
|
|
168
|
+
this.#capability.assertCurrent();
|
|
169
|
+
let current;
|
|
170
|
+
try {
|
|
171
|
+
current = fsSync.lstatSync(this.#dir, { bigint: true });
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
this.#capability.assertCurrent();
|
|
175
|
+
return error.code === "ENOENT" ? "missing" : "indeterminate";
|
|
176
|
+
}
|
|
177
|
+
this.#capability.assertCurrent();
|
|
178
|
+
if (!current.isDirectory() || current.isSymbolicLink() ||
|
|
179
|
+
!sameFileIdentityForCleanup(current, this.#identity)) {
|
|
180
|
+
return "identity-mismatch";
|
|
181
|
+
}
|
|
182
|
+
const name = `.fs-safe-workspace-cleanup-${randomUUID()}`;
|
|
183
|
+
const quarantinePath = path.join(parent.receipt.path, name);
|
|
184
|
+
const nativeRemoval = this.#capability.canRemoveOwnedTree && this.#directory !== undefined;
|
|
185
|
+
if (nativeRemoval) {
|
|
186
|
+
this.#capability.binding.renameNoReplace(parent.fd, path.basename(this.#dir), parent.fd, name);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const guard = createSyncDirectoryGuard(parent.receipt.path);
|
|
190
|
+
withSyncDirectoryGuards([guard], () => {
|
|
191
|
+
this.#capability.assertCurrent();
|
|
192
|
+
fsSync.renameSync(this.#dir, quarantinePath);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
this.#capability.assertCurrent();
|
|
196
|
+
const quarantined = fsSync.lstatSync(quarantinePath, { bigint: true });
|
|
197
|
+
this.#capability.assertCurrent();
|
|
198
|
+
if (!quarantined.isDirectory() || quarantined.isSymbolicLink() ||
|
|
199
|
+
!sameFileIdentityForCleanup(quarantined, this.#identity)) {
|
|
200
|
+
return "indeterminate";
|
|
201
|
+
}
|
|
202
|
+
return { name, path: quarantinePath, nativeRemoval };
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// A failed rename can still have committed on a remote filesystem.
|
|
206
|
+
return "indeterminate";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
#assertQuarantine(quarantine) {
|
|
210
|
+
const current = fsSync.lstatSync(quarantine.path, { bigint: true });
|
|
211
|
+
if (!current.isDirectory() || current.isSymbolicLink() ||
|
|
212
|
+
!sameFileIdentityForCleanup(current, this.#identity)) {
|
|
213
|
+
throw new FsSafeError("path-mismatch", "temp workspace quarantine changed");
|
|
214
|
+
}
|
|
215
|
+
this.#capability.assertCurrent();
|
|
216
|
+
}
|
|
217
|
+
#mapRemoval(result) {
|
|
218
|
+
const error = nativeRemovalError(result);
|
|
219
|
+
if (error) {
|
|
220
|
+
if (error.code === "path-mismatch")
|
|
221
|
+
return "indeterminate";
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
return result.outcome === "removed" ? "removed" : "indeterminate";
|
|
225
|
+
}
|
|
226
|
+
async #remove(quarantine) {
|
|
227
|
+
if (quarantine.nativeRemoval) {
|
|
228
|
+
const beforeNativeRemoval = getFsSafeTestHooks()?.beforeTempWorkspaceNativeRemoval;
|
|
229
|
+
if (beforeNativeRemoval)
|
|
230
|
+
await beforeNativeRemoval(quarantine.path);
|
|
231
|
+
return this.#mapRemoval(await this.#capability.binding.removeOwnedTree(this.#capability.parent.fd, quarantine.name, this.#directory.fd));
|
|
232
|
+
}
|
|
233
|
+
let removalError;
|
|
234
|
+
try {
|
|
235
|
+
const guard = await createAsyncDirectoryGuard(this.#capability.parent.receipt.path);
|
|
236
|
+
await withAsyncDirectoryGuards([guard], async () => {
|
|
237
|
+
this.#assertQuarantine(quarantine);
|
|
238
|
+
try {
|
|
239
|
+
await fs.rm(quarantine.path, { recursive: true, force: true });
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
removalError = error;
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return "removed";
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error === removalError)
|
|
250
|
+
throw error;
|
|
251
|
+
return "indeterminate";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
#removeSync(quarantine) {
|
|
255
|
+
if (quarantine.nativeRemoval) {
|
|
256
|
+
getFsSafeTestHooks()?.beforeTempWorkspaceNativeRemovalSync?.(quarantine.path);
|
|
257
|
+
return this.#mapRemoval(this.#capability.binding.removeOwnedTreeSync(this.#capability.parent.fd, quarantine.name, this.#directory.fd));
|
|
258
|
+
}
|
|
259
|
+
let removalError;
|
|
260
|
+
try {
|
|
261
|
+
const guard = createSyncDirectoryGuard(this.#capability.parent.receipt.path);
|
|
262
|
+
withSyncDirectoryGuards([guard], () => {
|
|
263
|
+
this.#assertQuarantine(quarantine);
|
|
264
|
+
try {
|
|
265
|
+
fsSync.rmSync(quarantine.path, { recursive: true, force: true });
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
removalError = error;
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
return "removed";
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
if (error === removalError)
|
|
276
|
+
throw error;
|
|
277
|
+
return "indeterminate";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async #run() {
|
|
281
|
+
let result = "indeterminate";
|
|
282
|
+
try {
|
|
283
|
+
const prepared = this.#prepare();
|
|
284
|
+
result = typeof prepared === "string" ? prepared : await this.#remove(prepared);
|
|
285
|
+
return this.#finish(result);
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
if (!this.#closed)
|
|
289
|
+
this.#finish(result);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
cleanup() {
|
|
293
|
+
if (this.#result)
|
|
294
|
+
return Promise.resolve(this.#repeat());
|
|
295
|
+
if (this.#pending)
|
|
296
|
+
return this.#pending.then(() => this.#repeat());
|
|
297
|
+
this.#running = true;
|
|
298
|
+
this.#pending = this.#run();
|
|
299
|
+
return this.#pending;
|
|
300
|
+
}
|
|
301
|
+
cleanupSync() {
|
|
302
|
+
if (this.#result)
|
|
303
|
+
return this.#repeat();
|
|
304
|
+
if (this.#running) {
|
|
305
|
+
this.#exitInterrupted = true;
|
|
306
|
+
return "indeterminate";
|
|
307
|
+
}
|
|
308
|
+
this.#running = true;
|
|
309
|
+
let result = "indeterminate";
|
|
310
|
+
try {
|
|
311
|
+
const prepared = this.#prepare();
|
|
312
|
+
result = typeof prepared === "string" ? prepared : this.#removeSync(prepared);
|
|
313
|
+
return this.#finish(result);
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
if (!this.#closed)
|
|
317
|
+
this.#finish(result);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
package/dist/temp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { tempWorkspace, type TempWorkspace, type TempWorkspaceOptions, type TempWorkspaceCleanupResult, tempWorkspaceSync, type TempWorkspaceSync, withTempWorkspace, withTempWorkspaceSync, } from "./private-temp-workspace.js";
|
|
1
|
+
export { tempWorkspace, type TempWorkspace, type TempWorkspaceOptions, type TempWorkspaceCleanupResult, type TempWorkspaceCleanupSafety, tempWorkspaceSync, type TempWorkspaceSync, withTempWorkspace, withTempWorkspaceSync, } from "./private-temp-workspace.js";
|
|
2
2
|
export type { TempPathIdentityReceipt } from "./temp-cleanup.js";
|
|
3
3
|
export { resolveSecureTempRoot, type ResolveSecureTempRootOptions } from "./secure-temp-dir.js";
|
|
4
4
|
//# sourceMappingURL=temp.d.ts.map
|
package/dist/temp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"temp.d.ts","sourceRoot":"","sources":["../src/temp.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,KAAK,4BAA4B,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"temp.d.ts","sourceRoot":"","sources":["../src/temp.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,KAAK,4BAA4B,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/test-hooks.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type FsSafeTestHooks = {
|
|
|
4
4
|
afterPreOpenLstat?: (filePath: string) => Promise<void> | void;
|
|
5
5
|
beforeOpen?: (filePath: string, flags: number) => Promise<void> | void;
|
|
6
6
|
afterOpen?: (filePath: string, handle: FileHandle) => Promise<void> | void;
|
|
7
|
+
afterOpenedPathIdentityCheck?: (filePath: string, handle: FileHandle) => Promise<void> | void;
|
|
7
8
|
beforeArchiveOutputMutation?: (operation: "mkdir" | "chmod", targetPath: string) => Promise<void> | void;
|
|
8
9
|
beforeFileStorePruneDescend?: (dirPath: string) => Promise<void> | void;
|
|
9
10
|
beforeFileStoreSyncPrivateWrite?: (filePath: string) => void;
|
|
@@ -11,6 +12,10 @@ export type FsSafeTestHooks = {
|
|
|
11
12
|
afterPinnedWriteFallbackRename?: (targetPath: string) => Promise<void> | void;
|
|
12
13
|
beforeSiblingTempWrite?: (tempPath: string) => Promise<void> | void;
|
|
13
14
|
beforeSidecarLockSnapshotOpen?: (lockPath: string) => Promise<void> | void;
|
|
15
|
+
beforeRegularFileAppendOpen?: (filePath: string) => Promise<void> | void;
|
|
16
|
+
beforeRegularFileAppendOpenSync?: (filePath: string) => void;
|
|
17
|
+
beforeTempWorkspaceNativeRemoval?: (quarantinePath: string) => Promise<void> | void;
|
|
18
|
+
beforeTempWorkspaceNativeRemovalSync?: (quarantinePath: string) => void;
|
|
14
19
|
beforeTrashMove?: (targetPath: string, destPath: string) => void;
|
|
15
20
|
afterPublishTargetCreated?: (method: "hardlink" | "exclusive-copy" | "rename-noreplace", targetPath: string, identity: FileIdentityStat) => Promise<void> | void;
|
|
16
21
|
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,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"}
|
|
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,4BAA4B,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9F,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,2BAA2B,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzE,+BAA+B,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,gCAAgC,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpF,oCAAoC,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,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/docs/advanced.md
CHANGED
|
@@ -72,6 +72,13 @@ Operational filesystem failures such as permissions or I/O errors are rethrown.
|
|
|
72
72
|
| `assertNoSymlinkParents`, `assertNoSymlinkParentsSync`, `AssertNoSymlinkParentsOptions` | – | Reject paths whose ancestor chain contains symlinks. |
|
|
73
73
|
| `assertNoHardlinkedFinalPath`, `assertNoPathAliasEscape`, `PATH_ALIAS_POLICIES`, `PathAliasPolicy` | – | Hardlink/alias defense building blocks. |
|
|
74
74
|
|
|
75
|
+
`openRootFile()` and `openRootFileSync()` compare exact bigint identities before
|
|
76
|
+
open, on the retained descriptor, and on the current resolved path. Their `stat`
|
|
77
|
+
receipts remain numeric. Custom `ioFs` adapters must honor `{ bigint: true }` for
|
|
78
|
+
`lstatSync` and `fstatSync`; numeric identity responses fail validation. Unknown
|
|
79
|
+
Windows identities receive one re-inspection without reopening, then fail
|
|
80
|
+
validation if still unknown.
|
|
81
|
+
|
|
75
82
|
The bounded descriptor helpers start at the descriptor's current offset and
|
|
76
83
|
leave ownership with the caller. They are intended for the second half of a
|
|
77
84
|
safe read: first open and validate the path using the boundary appropriate to
|
|
@@ -118,7 +125,7 @@ component is followed by another segment, both helpers throw
|
|
|
118
125
|
|---|---|---|
|
|
119
126
|
| `stageFileInDirectory`, `StagedFile`, `StagedFileReceipt`, `PublishedFileReceipt`, `StagedFilePublication`, `StagedFileCleanupReceipt`, `StagedFileFailureDetails` | [staged-file.md](staged-file.md) | Native-required Linux/macOS lifecycle retaining the original directory for abort cleanup. |
|
|
120
127
|
| `tempFile`, `withTempFile`, `TempFile`, `buildRandomTempFilePath`, `sanitizeTempFileName` | [temp.md](temp.md) | One-file temp primitive; prefer `tempWorkspace` from `@openclaw/fs-safe/temp` for the stable surface. |
|
|
121
|
-
| `writeSiblingTempFile`, `writeViaSiblingTempPath`, `WriteSiblingTempFileOptions`, `WriteSiblingTempFileResult` | – |
|
|
128
|
+
| `writeSiblingTempFile`, `writeViaSiblingTempPath`, `WriteSiblingTempFileOptions`, `WriteSiblingTempFileResult` | – | Callback-produced file staging: verified sibling publication or private-workspace copy through a root. |
|
|
122
129
|
|
|
123
130
|
### Permissions
|
|
124
131
|
|