@openclaw/feishu 2026.9.8 → 2026.10.1-beta.2
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/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
- package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
- package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
- package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
- package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
- package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
- package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
- package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
- package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
- package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
- package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
- package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
- package/dist/api.js +234 -297
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +2 -2
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +1 -1
- package/dist/subagent-hooks-api.js +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
- package/node_modules/@openclaw/fs-safe/README.md +70 -379
- package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
- package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
- package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
- package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
- package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
- package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
- package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
- package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
- package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
- package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
- package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
- package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
- package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
- package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
- package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
- package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
- package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
- package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
- package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
- package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
- package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
- package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
- package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
- package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
- package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
- package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
- package/node_modules/@openclaw/fs-safe/package.json +10 -10
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/package.json +5 -5
- package/skills/feishu-wiki/SKILL.md +1 -1
- package/dist/.setup/accounts-wRqItHug.mjs +0 -206
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
- package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
- package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
- package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
|
@@ -3,81 +3,52 @@ import syncFs, {} from "node:fs";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
6
|
-
import { assertDestinationHardlinkPolicy,
|
|
7
|
-
import { applyDirectoryMode,
|
|
6
|
+
import { assertDestinationHardlinkPolicy, copyFallbackReplace } from "./replace-file-copy-fallback.js";
|
|
7
|
+
import { applyDirectoryMode, syncDirectoryBestEffort, writeTempFile } from "./replace-file-descriptor.js";
|
|
8
8
|
import { inheritedRegularFileMode } from "./replace-file-mode.js";
|
|
9
9
|
import { atomicExpectedContentHash, validateRenameIdentity, withAtomicRenameIdentityLock, withAtomicRenameIdentityLockSync, } from "./replace-file-rename-policy.js";
|
|
10
|
-
import {
|
|
10
|
+
import { AtomicTempOwner } from "./replace-file-temp-owner.js";
|
|
11
11
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
12
12
|
import { admitStandalonePublicationPath } from "./windows-path-alias.js";
|
|
13
|
-
import { sleep, sleepSync } from "./timing.js";
|
|
14
13
|
import { serializePathWrite } from "./write-queue.js";
|
|
15
14
|
import { hasErrorCode, readErrorCode } from "./file-cleanup.js";
|
|
16
15
|
import { AtomicMutation } from "./replace-file-mutation.js";
|
|
17
16
|
import { assertSynchronousCallbackResult } from "./mutation-authority.js";
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
import { AtomicIo, runAsync, runSync, wait } from "./atomic-io.js";
|
|
18
|
+
function* renameWithRetry(io, source, destination, options, mutation) {
|
|
19
|
+
const maxRetries = options.renameMaxRetries ?? 0;
|
|
20
|
+
const baseDelayMs = options.renameRetryBaseDelayMs ?? 50;
|
|
21
|
+
const copyFallbackOnPermissionError = options.copyFallbackOnPermissionError === true;
|
|
22
|
+
const restore = options.copyFallbackRestore ?? "none";
|
|
23
|
+
const maxRestoreBytes = options.maxRestoreBytes;
|
|
24
|
+
const destinationHardlinks = options.destinationHardlinks;
|
|
25
|
+
const sourceIdentity = source.identity;
|
|
26
|
+
const sync = options.syncTempFile === true;
|
|
27
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
20
28
|
if (attempt > 0)
|
|
21
|
-
|
|
29
|
+
yield* source.assertCurrent();
|
|
22
30
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
mutation.assert();
|
|
32
|
+
yield* io.rename(source.pathname, destination);
|
|
25
33
|
return { method: "rename" };
|
|
26
34
|
}
|
|
27
35
|
catch (error) {
|
|
28
|
-
|
|
36
|
+
mutation.rethrowRefusal();
|
|
29
37
|
const code = readErrorCode(error);
|
|
30
|
-
if (code === "EBUSY" && attempt <
|
|
31
|
-
|
|
38
|
+
if (code === "EBUSY" && attempt < maxRetries) {
|
|
39
|
+
yield* io.delay(baseDelayMs * 2 ** attempt);
|
|
32
40
|
continue;
|
|
33
41
|
}
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
mutation: params.mutation,
|
|
45
|
-
});
|
|
46
|
-
return { method: "copy-fallback" };
|
|
47
|
-
}
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
throw new Error("Atomic rename retry loop exhausted.");
|
|
52
|
-
}
|
|
53
|
-
function renameWithRetrySync(params) {
|
|
54
|
-
for (let attempt = 0; attempt <= params.maxRetries; attempt++) {
|
|
55
|
-
if (attempt > 0)
|
|
56
|
-
params.assertSourceCurrent();
|
|
57
|
-
try {
|
|
58
|
-
params.mutation.assert();
|
|
59
|
-
params.fsModule.renameSync(params.src, params.dest);
|
|
60
|
-
return { method: "rename" };
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
params.mutation.rethrowRefusal();
|
|
64
|
-
const code = readErrorCode(error);
|
|
65
|
-
if (code === "EBUSY" && attempt < params.maxRetries) {
|
|
66
|
-
sleepSync(params.baseDelayMs * 2 ** attempt);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (params.copyFallbackOnPermissionError && (code === "EPERM" || code === "EEXIST")) {
|
|
70
|
-
copyFallbackReplaceSync({
|
|
71
|
-
fsModule: params.fsModule,
|
|
72
|
-
src: params.src,
|
|
73
|
-
dest: params.dest,
|
|
74
|
-
destinationHardlinks: params.destinationHardlinks,
|
|
75
|
-
restore: params.copyFallbackRestore,
|
|
76
|
-
maxRestoreBytes: params.maxRestoreBytes,
|
|
77
|
-
expectedSourceIdentity: params.sourceIdentity,
|
|
78
|
-
fchmodSync: params.fchmodSync,
|
|
79
|
-
sync: params.syncFallback,
|
|
80
|
-
mutation: params.mutation,
|
|
42
|
+
if (copyFallbackOnPermissionError && (code === "EPERM" || code === "EEXIST")) {
|
|
43
|
+
yield* copyFallbackReplace(io, {
|
|
44
|
+
src: source.pathname,
|
|
45
|
+
dest: destination,
|
|
46
|
+
destinationHardlinks,
|
|
47
|
+
restore,
|
|
48
|
+
maxRestoreBytes,
|
|
49
|
+
expectedSourceIdentity: sourceIdentity,
|
|
50
|
+
sync,
|
|
51
|
+
mutation,
|
|
81
52
|
});
|
|
82
53
|
return { method: "copy-fallback" };
|
|
83
54
|
}
|
|
@@ -107,48 +78,24 @@ function buildReplaceTempPath(filePath, tempPrefix) {
|
|
|
107
78
|
const safePrefix = assertSafePathPrefix(tempPrefix ?? ".fs-safe-replace", { label: "atomic replace temp prefix" });
|
|
108
79
|
return path.join(dir, `${safePrefix}.${process.pid}.${randomUUID()}.tmp`);
|
|
109
80
|
}
|
|
110
|
-
|
|
81
|
+
function* resolveMode(io, options, filePath) {
|
|
111
82
|
const defaultMode = options.mode ?? 0o600;
|
|
112
|
-
if (!options.preserveExistingMode)
|
|
83
|
+
if (!options.preserveExistingMode)
|
|
113
84
|
return defaultMode;
|
|
114
|
-
}
|
|
115
|
-
const fsModule = options.fileSystem?.promises ?? fs;
|
|
116
85
|
let stat;
|
|
117
86
|
try {
|
|
118
|
-
stat =
|
|
87
|
+
stat = yield* io.lstat(filePath);
|
|
119
88
|
}
|
|
120
89
|
catch (error) {
|
|
121
|
-
if (hasErrorCode(error, "ENOENT"))
|
|
122
|
-
return defaultMode;
|
|
123
|
-
}
|
|
124
|
-
throw error;
|
|
125
|
-
}
|
|
126
|
-
return stat ? inheritedRegularFileMode(stat) : defaultMode;
|
|
127
|
-
}
|
|
128
|
-
function resolveModeSync(options, filePath) {
|
|
129
|
-
const defaultMode = options.mode ?? 0o600;
|
|
130
|
-
if (!options.preserveExistingMode) {
|
|
131
|
-
return defaultMode;
|
|
132
|
-
}
|
|
133
|
-
const fsModule = options.fileSystem ?? syncFs;
|
|
134
|
-
let stat;
|
|
135
|
-
try {
|
|
136
|
-
stat = fsModule.lstatSync(filePath);
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
if (!hasErrorCode(error, "ENOENT")) {
|
|
90
|
+
if (!hasErrorCode(error, "ENOENT"))
|
|
140
91
|
throw error;
|
|
141
|
-
}
|
|
142
92
|
}
|
|
143
93
|
return stat ? inheritedRegularFileMode(stat) : defaultMode;
|
|
144
94
|
}
|
|
145
|
-
function missingFchmodSyncError() {
|
|
146
|
-
return new TypeError("fileSystem.fchmodSync is required when mode, dirMode, or preserveExistingMode is specified");
|
|
147
|
-
}
|
|
148
95
|
export async function replaceFileAtomic(options) {
|
|
149
96
|
return await replaceFileAtomicWithDirectorySync(options);
|
|
150
97
|
}
|
|
151
|
-
//
|
|
98
|
+
// Directory durability remains inside the serialized publication and verification lifetime.
|
|
152
99
|
export async function replaceFileAtomicWithDirectorySync(options, syncParent) {
|
|
153
100
|
const mutation = new AtomicMutation(options);
|
|
154
101
|
const filePath = validateReplaceFilePath(options.filePath);
|
|
@@ -157,7 +104,7 @@ export async function replaceFileAtomicWithDirectorySync(options, syncParent) {
|
|
|
157
104
|
validateRenameIdentity(renameIdentity);
|
|
158
105
|
return await serializePathWrite(path.resolve(filePath), async () => {
|
|
159
106
|
if (renameIdentity !== "verify-content-with-lock") {
|
|
160
|
-
return await
|
|
107
|
+
return await replaceAsync(options, filePath, renameIdentity, mutation, syncParent);
|
|
161
108
|
}
|
|
162
109
|
const fsModule = options.fileSystem?.promises ?? fs;
|
|
163
110
|
const dir = path.dirname(filePath);
|
|
@@ -167,88 +114,11 @@ export async function replaceFileAtomicWithDirectorySync(options, syncParent) {
|
|
|
167
114
|
mode: options.dirMode ?? 0o700,
|
|
168
115
|
});
|
|
169
116
|
mutation.assert();
|
|
170
|
-
return await withAtomicRenameIdentityLock(filePath,
|
|
117
|
+
return await withAtomicRenameIdentityLock(filePath, () => replaceAsync(options, filePath, renameIdentity, mutation, syncParent));
|
|
171
118
|
});
|
|
172
119
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const dir = path.dirname(filePath);
|
|
176
|
-
const dirMode = options.dirMode ?? 0o700;
|
|
177
|
-
const mode = await resolveMode(options, filePath);
|
|
178
|
-
const expectedHash = atomicExpectedContentHash(renameIdentity, options.content);
|
|
179
|
-
const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
|
|
180
|
-
const tempOwner = new AsyncAtomicTempOwner(tempPath);
|
|
181
|
-
let originalFailure;
|
|
182
|
-
try {
|
|
183
|
-
mutation.assert();
|
|
184
|
-
await fsModule.mkdir(fsModule === fs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
|
|
185
|
-
await applyDirectoryMode({ fsModule, dirPath: dir, mode: dirMode, mutation });
|
|
186
|
-
tempOwner.start();
|
|
187
|
-
tempOwner.adopt(await writeTempFile({
|
|
188
|
-
fsModule,
|
|
189
|
-
tempPath,
|
|
190
|
-
content: options.content,
|
|
191
|
-
mode,
|
|
192
|
-
sync: options.syncTempFile === true,
|
|
193
|
-
onIdentity: tempOwner.onIdentity,
|
|
194
|
-
mutation,
|
|
195
|
-
}));
|
|
196
|
-
await tempOwner.assertCurrent(fsModule);
|
|
197
|
-
if (options.beforeRename) {
|
|
198
|
-
await options.beforeRename({ filePath, tempPath });
|
|
199
|
-
await tempOwner.assertCurrent(fsModule);
|
|
200
|
-
}
|
|
201
|
-
if (options.destinationHardlinks === "reject") {
|
|
202
|
-
await assertDestinationHardlinkPolicy(fsModule, filePath, options.destinationHardlinks);
|
|
203
|
-
await tempOwner.assertCurrent(fsModule);
|
|
204
|
-
}
|
|
205
|
-
const result = await renameWithRetry({
|
|
206
|
-
fsModule,
|
|
207
|
-
src: tempPath,
|
|
208
|
-
dest: filePath,
|
|
209
|
-
maxRetries: options.renameMaxRetries ?? 0,
|
|
210
|
-
baseDelayMs: options.renameRetryBaseDelayMs ?? 50,
|
|
211
|
-
copyFallbackOnPermissionError: options.copyFallbackOnPermissionError === true,
|
|
212
|
-
copyFallbackRestore: options.copyFallbackRestore ?? "none",
|
|
213
|
-
maxRestoreBytes: options.maxRestoreBytes,
|
|
214
|
-
destinationHardlinks: options.destinationHardlinks,
|
|
215
|
-
sourceIdentity: tempOwner.identity,
|
|
216
|
-
assertSourceCurrent: () => tempOwner.assertCurrent(fsModule),
|
|
217
|
-
syncFallback: options.syncTempFile === true,
|
|
218
|
-
mutation,
|
|
219
|
-
});
|
|
220
|
-
if (result.method === "rename") {
|
|
221
|
-
tempOwner.markRenamed();
|
|
222
|
-
await tempOwner.assertPublished(fsModule, filePath, expectedHash, identity => mutation.destination("published", filePath, identity));
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
await tempOwner.assertCurrent(fsModule);
|
|
226
|
-
}
|
|
227
|
-
let didSyncParent = false;
|
|
228
|
-
if (syncParent) {
|
|
229
|
-
await syncParent(dir);
|
|
230
|
-
didSyncParent = true;
|
|
231
|
-
}
|
|
232
|
-
else if (options.syncParentDir) {
|
|
233
|
-
await syncDirectoryBestEffort(fsModule, dir);
|
|
234
|
-
didSyncParent = true;
|
|
235
|
-
}
|
|
236
|
-
if (result.method === "rename" && didSyncParent) {
|
|
237
|
-
await tempOwner.assertPublished(fsModule, filePath, expectedHash);
|
|
238
|
-
}
|
|
239
|
-
return result;
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
originalFailure = { error };
|
|
243
|
-
throw error;
|
|
244
|
-
}
|
|
245
|
-
finally {
|
|
246
|
-
await tempOwner.finish({
|
|
247
|
-
fsModule,
|
|
248
|
-
originalFailure,
|
|
249
|
-
throwOnCleanupError: options.throwOnCleanupError === true,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
120
|
+
function replaceAsync(options, filePath, renameIdentity, mutation, syncParent) {
|
|
121
|
+
return runAsync(replace(AtomicIo.async(options.fileSystem?.promises ?? fs), options, filePath, renameIdentity, mutation, syncParent));
|
|
252
122
|
}
|
|
253
123
|
export function replaceFileAtomicSync(options) {
|
|
254
124
|
const mutation = new AtomicMutation(options);
|
|
@@ -257,7 +127,7 @@ export function replaceFileAtomicSync(options) {
|
|
|
257
127
|
const renameIdentity = options.renameIdentity;
|
|
258
128
|
validateRenameIdentity(renameIdentity);
|
|
259
129
|
if (renameIdentity !== "verify-content-with-lock") {
|
|
260
|
-
return
|
|
130
|
+
return replaceSync(options, filePath, renameIdentity, mutation);
|
|
261
131
|
}
|
|
262
132
|
const fsModule = options.fileSystem ?? syncFs;
|
|
263
133
|
const dir = path.dirname(filePath);
|
|
@@ -267,78 +137,72 @@ export function replaceFileAtomicSync(options) {
|
|
|
267
137
|
mode: options.dirMode ?? 0o700,
|
|
268
138
|
});
|
|
269
139
|
mutation.assert();
|
|
270
|
-
return withAtomicRenameIdentityLockSync(filePath, () =>
|
|
140
|
+
return withAtomicRenameIdentityLockSync(filePath, () => replaceSync(options, filePath, renameIdentity, mutation));
|
|
271
141
|
}
|
|
272
|
-
function
|
|
273
|
-
|
|
142
|
+
function replaceSync(options, filePath, renameIdentity, mutation) {
|
|
143
|
+
return runSync(replace(AtomicIo.sync(options.fileSystem ?? syncFs), options, filePath, renameIdentity, mutation));
|
|
144
|
+
}
|
|
145
|
+
function* replace(io, options, filePath, renameIdentity, mutation, syncParent) {
|
|
274
146
|
const dir = path.dirname(filePath);
|
|
275
147
|
const dirMode = options.dirMode ?? 0o700;
|
|
276
|
-
const mode =
|
|
148
|
+
const mode = yield* resolveMode(io, options, filePath);
|
|
277
149
|
const expectedHash = atomicExpectedContentHash(renameIdentity, options.content);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
(
|
|
281
|
-
|
|
150
|
+
if (!io.asynchronous) {
|
|
151
|
+
const syncOptions = options;
|
|
152
|
+
io.fchmodSync = syncOptions.fileSystem?.fchmodSync ?? (syncOptions.fileSystem === undefined ? syncFs.fchmodSync : undefined);
|
|
153
|
+
if (!io.fchmodSync && (options.mode !== undefined || options.preserveExistingMode === true ||
|
|
282
154
|
(process.platform !== "win32" && options.dirMode !== undefined))) {
|
|
283
|
-
|
|
155
|
+
throw new TypeError("fileSystem.fchmodSync is required when mode, dirMode, or preserveExistingMode is specified");
|
|
156
|
+
}
|
|
284
157
|
}
|
|
285
158
|
const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
|
|
286
|
-
const tempOwner = new
|
|
159
|
+
const tempOwner = new AtomicTempOwner(tempPath, io);
|
|
287
160
|
let originalFailure;
|
|
288
161
|
try {
|
|
289
162
|
mutation.assert();
|
|
290
|
-
|
|
291
|
-
|
|
163
|
+
yield* io.mkdir(dir, dirMode);
|
|
164
|
+
yield* applyDirectoryMode(io, { dirPath: dir, mode: dirMode, mutation });
|
|
292
165
|
tempOwner.start();
|
|
293
|
-
tempOwner.adopt(
|
|
294
|
-
fsModule,
|
|
166
|
+
tempOwner.adopt(yield* writeTempFile(io, {
|
|
295
167
|
tempPath,
|
|
296
168
|
content: options.content,
|
|
297
169
|
mode,
|
|
298
|
-
fchmodSync,
|
|
299
170
|
sync: options.syncTempFile === true,
|
|
300
171
|
onIdentity: tempOwner.onIdentity,
|
|
301
172
|
mutation,
|
|
302
173
|
}));
|
|
303
|
-
tempOwner.assertCurrent(
|
|
174
|
+
yield* tempOwner.assertCurrent();
|
|
304
175
|
if (options.beforeRename) {
|
|
305
|
-
|
|
306
|
-
|
|
176
|
+
const result = options.beforeRename({ filePath, tempPath });
|
|
177
|
+
if (io.asynchronous)
|
|
178
|
+
yield* wait(result);
|
|
179
|
+
else
|
|
180
|
+
assertSynchronousCallbackResult(result, "beforeRename");
|
|
181
|
+
yield* tempOwner.assertCurrent();
|
|
307
182
|
}
|
|
308
183
|
if (options.destinationHardlinks === "reject") {
|
|
309
|
-
|
|
310
|
-
tempOwner.assertCurrent(
|
|
184
|
+
yield* assertDestinationHardlinkPolicy(io, filePath, options.destinationHardlinks);
|
|
185
|
+
yield* tempOwner.assertCurrent();
|
|
311
186
|
}
|
|
312
|
-
const result =
|
|
313
|
-
fsModule,
|
|
314
|
-
src: tempPath,
|
|
315
|
-
dest: filePath,
|
|
316
|
-
maxRetries: options.renameMaxRetries ?? 0,
|
|
317
|
-
baseDelayMs: options.renameRetryBaseDelayMs ?? 50,
|
|
318
|
-
copyFallbackOnPermissionError: options.copyFallbackOnPermissionError === true,
|
|
319
|
-
copyFallbackRestore: options.copyFallbackRestore ?? "none",
|
|
320
|
-
maxRestoreBytes: options.maxRestoreBytes,
|
|
321
|
-
destinationHardlinks: options.destinationHardlinks,
|
|
322
|
-
sourceIdentity: tempOwner.identity,
|
|
323
|
-
assertSourceCurrent: () => tempOwner.assertCurrent(fsModule),
|
|
324
|
-
fchmodSync,
|
|
325
|
-
syncFallback: options.syncTempFile === true,
|
|
326
|
-
mutation,
|
|
327
|
-
});
|
|
187
|
+
const result = yield* renameWithRetry(io, tempOwner, filePath, options, mutation);
|
|
328
188
|
if (result.method === "rename") {
|
|
329
189
|
tempOwner.markRenamed();
|
|
330
|
-
tempOwner.assertPublished(
|
|
190
|
+
yield* tempOwner.assertPublished(filePath, expectedHash, identity => mutation.destination("published", filePath, identity));
|
|
331
191
|
}
|
|
332
192
|
else {
|
|
333
|
-
tempOwner.assertCurrent(
|
|
193
|
+
yield* tempOwner.assertCurrent();
|
|
334
194
|
}
|
|
335
195
|
let didSyncParent = false;
|
|
336
|
-
if (
|
|
337
|
-
|
|
196
|
+
if (syncParent) {
|
|
197
|
+
yield* wait(syncParent(dir));
|
|
198
|
+
didSyncParent = true;
|
|
199
|
+
}
|
|
200
|
+
else if (options.syncParentDir) {
|
|
201
|
+
yield* syncDirectoryBestEffort(io, dir);
|
|
338
202
|
didSyncParent = true;
|
|
339
203
|
}
|
|
340
204
|
if (result.method === "rename" && didSyncParent) {
|
|
341
|
-
tempOwner.assertPublished(
|
|
205
|
+
yield* tempOwner.assertPublished(filePath, expectedHash);
|
|
342
206
|
}
|
|
343
207
|
return result;
|
|
344
208
|
}
|
|
@@ -347,10 +211,6 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity, mu
|
|
|
347
211
|
throw error;
|
|
348
212
|
}
|
|
349
213
|
finally {
|
|
350
|
-
tempOwner.finish({
|
|
351
|
-
fsModule,
|
|
352
|
-
originalFailure,
|
|
353
|
-
throwOnCleanupError: options.throwOnCleanupError === true,
|
|
354
|
-
});
|
|
214
|
+
yield* tempOwner.finish({ originalFailure, throwOnCleanupError: options.throwOnCleanupError === true });
|
|
355
215
|
}
|
|
356
216
|
}
|
|
@@ -6,25 +6,14 @@ export function sameNormalizedPathSpelling(left, right) {
|
|
|
6
6
|
// Preserve case on Windows, where individual directories can be case-sensitive.
|
|
7
7
|
return path.resolve(left) === path.resolve(right);
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
const original = normalize
|
|
11
|
-
? path.win32.normalize(value.replaceAll("/", "\\"))
|
|
12
|
-
: value.replaceAll("/", "\\");
|
|
9
|
+
function windowsComparisonSpelling(original) {
|
|
13
10
|
if (/^\\\\\?\\UNC\\/i.test(original)) {
|
|
14
|
-
return {
|
|
15
|
-
original,
|
|
16
|
-
comparison: `\\\\${original.slice(8)}`,
|
|
17
|
-
originalIndex: (comparisonIndex) => comparisonIndex + 6,
|
|
18
|
-
};
|
|
11
|
+
return `\\\\${original.slice(8)}`;
|
|
19
12
|
}
|
|
20
13
|
if (original.startsWith("\\\\?\\")) {
|
|
21
|
-
return
|
|
22
|
-
original,
|
|
23
|
-
comparison: original.slice(4),
|
|
24
|
-
originalIndex: (comparisonIndex) => comparisonIndex + 4,
|
|
25
|
-
};
|
|
14
|
+
return original.slice(4);
|
|
26
15
|
}
|
|
27
|
-
return
|
|
16
|
+
return original;
|
|
28
17
|
}
|
|
29
18
|
function trimWindowsTrailingSeparators(value) {
|
|
30
19
|
const rootLength = path.win32.parse(value).root.length;
|
|
@@ -63,24 +52,25 @@ function windowsRootPrefix(rootPath, candidatePath) {
|
|
|
63
52
|
const exact = exactWindowsRootPrefix(rootPath, candidatePath);
|
|
64
53
|
if (exact)
|
|
65
54
|
return exact;
|
|
66
|
-
const trusted =
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
55
|
+
const trusted = path.win32.normalize(rootPath.replaceAll("/", "\\"));
|
|
56
|
+
const trustedComparison = windowsComparisonSpelling(trusted);
|
|
57
|
+
const supplied = candidatePath.replaceAll("/", "\\");
|
|
58
|
+
const candidate = windowsComparisonSpelling(supplied);
|
|
59
|
+
const root = trimWindowsTrailingSeparators(trusted);
|
|
60
|
+
const rootComparison = trimWindowsTrailingSeparators(trustedComparison);
|
|
61
|
+
if (!path.win32.isAbsolute(supplied))
|
|
72
62
|
return undefined;
|
|
73
63
|
let relativePath;
|
|
74
64
|
let candidateRootPath;
|
|
75
65
|
let admission;
|
|
76
66
|
if (candidate === rootComparison) {
|
|
77
67
|
relativePath = "";
|
|
78
|
-
candidateRootPath = supplied
|
|
68
|
+
candidateRootPath = supplied;
|
|
79
69
|
admission = "exact";
|
|
80
70
|
}
|
|
81
71
|
else if (candidate.toLowerCase() === rootComparison.toLowerCase()) {
|
|
82
72
|
relativePath = "";
|
|
83
|
-
candidateRootPath = supplied
|
|
73
|
+
candidateRootPath = supplied;
|
|
84
74
|
admission = "identity";
|
|
85
75
|
}
|
|
86
76
|
else {
|
|
@@ -96,7 +86,7 @@ function windowsRootPrefix(rootPath, candidatePath) {
|
|
|
96
86
|
return undefined;
|
|
97
87
|
}
|
|
98
88
|
relativePath = trimWindowsLeadingSeparators(candidate.slice(rootWithSep.length));
|
|
99
|
-
candidateRootPath = supplied.
|
|
89
|
+
candidateRootPath = supplied.slice(0, rootComparison.length + (supplied.length - candidate.length));
|
|
100
90
|
}
|
|
101
91
|
const rebasedPath = relativePath === ""
|
|
102
92
|
? root
|
|
@@ -4,9 +4,6 @@ export type RootContext = {
|
|
|
4
4
|
rootDir: string;
|
|
5
5
|
rootGuard?: AsyncDirectoryGuard<BigIntStats>;
|
|
6
6
|
rootIdentity: {
|
|
7
|
-
dev: number;
|
|
8
|
-
ino: number;
|
|
9
|
-
} | {
|
|
10
7
|
dev: bigint;
|
|
11
8
|
ino: bigint;
|
|
12
9
|
};
|
|
@@ -27,7 +24,7 @@ export declare function assertRootIdentityCurrent(root: RootContext, observe?: (
|
|
|
27
24
|
* This is deliberately separate from {@link assertRootIdentityCurrent}: callers
|
|
28
25
|
* must not retain the returned guard beyond the operation that requested it.
|
|
29
26
|
*/
|
|
30
|
-
export declare function createRootObservationGuard(root: RootContext): Promise<DirectoryObservationGuard
|
|
27
|
+
export declare function createRootObservationGuard(root: RootContext): Promise<DirectoryObservationGuard>;
|
|
31
28
|
export declare function resolvePathInRoot(root: RootContext, relativePath: string, options?: {
|
|
32
29
|
aliasErrorCode?: "outside-workspace" | "path-alias";
|
|
33
30
|
allowFinalSymlink?: boolean;
|
|
@@ -3,7 +3,6 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { extendDirectoryObservationGuard, inspectDirectoryIdentitySync, inspectDirectoryObservationSync, } from "./directory-guard.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
|
-
import { sameFileIdentity } from "./file-identity.js";
|
|
7
6
|
import { assertNoNulPathInput, assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isPathInside, } from "./path.js";
|
|
8
7
|
import { ROOT_PATH_ALIAS_POLICIES, resolveRootPath } from "./root-path.js";
|
|
9
8
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
@@ -12,9 +11,6 @@ import { isDriveRelativePath } from "./safe-path-segment.js";
|
|
|
12
11
|
import { realpathSync } from "./realpath.js";
|
|
13
12
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
14
13
|
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathFromBasePreservingWindowsRoot, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
15
|
-
function hasExactRootIdentity(identity) {
|
|
16
|
-
return typeof identity.dev === "bigint" && typeof identity.ino === "bigint";
|
|
17
|
-
}
|
|
18
14
|
export const ensureTrailingSep = (value) => value.endsWith(path.sep) ? value : value + path.sep;
|
|
19
15
|
export function assertValidRootRelativePath(relativePath) {
|
|
20
16
|
assertNoNulPathInput(relativePath, "relative path contains a NUL byte");
|
|
@@ -123,23 +119,13 @@ export function rootRelativeReadPath(root, filePath) {
|
|
|
123
119
|
return raw;
|
|
124
120
|
}
|
|
125
121
|
export function assertRootIdentityCurrentSync(root, observe) {
|
|
126
|
-
let current;
|
|
127
122
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
observe?.(stat);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
current = fs.lstatSync(root.rootReal);
|
|
123
|
+
const stat = inspectDirectoryIdentitySync(root.rootReal, { dev: root.rootIdentity.dev, ino: root.rootIdentity.ino });
|
|
124
|
+
observe?.(stat);
|
|
134
125
|
}
|
|
135
126
|
catch (error) {
|
|
136
127
|
throw rootPathChangedError(error instanceof Error ? error : undefined);
|
|
137
128
|
}
|
|
138
|
-
if (current.isSymbolicLink() ||
|
|
139
|
-
!current.isDirectory() ||
|
|
140
|
-
!sameFileIdentity(current, root.rootIdentity)) {
|
|
141
|
-
throw rootPathChangedError();
|
|
142
|
-
}
|
|
143
129
|
}
|
|
144
130
|
export async function assertRootIdentityCurrent(root, observe) {
|
|
145
131
|
assertRootIdentityCurrentSync(root, observe);
|
|
@@ -152,9 +138,8 @@ export async function assertRootIdentityCurrent(root, observe) {
|
|
|
152
138
|
*/
|
|
153
139
|
export async function createRootObservationGuard(root) {
|
|
154
140
|
const rootIdentity = root.rootIdentity;
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
return undefined;
|
|
141
|
+
if (typeof rootIdentity?.dev !== "bigint" || typeof rootIdentity.ino !== "bigint") {
|
|
142
|
+
throw rootPathChangedError();
|
|
158
143
|
}
|
|
159
144
|
try {
|
|
160
145
|
const observed = await inspectDirectoryObservationSync(root.rootReal, rootIdentity);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
|
+
import { type PinnedMutationPolicySnapshot } from "./pinned-mutation-admission.js";
|
|
5
|
+
import { type RootContext } from "./root-context.js";
|
|
6
|
+
import type { RootWritePathSelection, RetainedRootWriteSelection } from "./root-write-admission.js";
|
|
7
|
+
import type { WritableOpenResult } from "./root-impl.js";
|
|
8
|
+
import type { RootOpenWritableOptions } from "./root-options.js";
|
|
9
|
+
export type OpenedWritableFileInRoot = {
|
|
10
|
+
opened: WritableOpenResult;
|
|
11
|
+
identity: fs.BigIntStats;
|
|
12
|
+
writeSelection?: RetainedRootWriteSelection;
|
|
13
|
+
cleanupCreated?: () => Promise<void>;
|
|
14
|
+
releaseCreationParent?: () => void;
|
|
15
|
+
};
|
|
16
|
+
export type WritableFileInRootParams = Omit<RootOpenWritableOptions, "writeMode"> & {
|
|
17
|
+
relativePath: string;
|
|
18
|
+
truncateExisting?: boolean;
|
|
19
|
+
append?: boolean;
|
|
20
|
+
expectedWritePath?: string;
|
|
21
|
+
keepCreationParent?: boolean;
|
|
22
|
+
};
|
|
23
|
+
export declare function rootWriteQueueKey(root: RootContext, relativePath: string): string;
|
|
24
|
+
export declare function buildAtomicWriteTempPath(targetPath: string): string;
|
|
25
|
+
export type MissingWritableFileInRoot = {
|
|
26
|
+
missing: true;
|
|
27
|
+
targetPath: string;
|
|
28
|
+
parentGuard: AnyAsyncDirectoryGuard;
|
|
29
|
+
writeSelection?: RootWritePathSelection;
|
|
30
|
+
};
|
|
31
|
+
type Creation = {
|
|
32
|
+
root: RootContext;
|
|
33
|
+
directory: string;
|
|
34
|
+
target: string;
|
|
35
|
+
mkdir: boolean;
|
|
36
|
+
private?: boolean;
|
|
37
|
+
policy?: PinnedMutationPolicySnapshot;
|
|
38
|
+
resolveCurrent(): Promise<string>;
|
|
39
|
+
assertBeforeMutation?: () => void;
|
|
40
|
+
originalPath?: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function tryMkdirRootNative(params: Creation): Promise<boolean>;
|
|
43
|
+
export declare function tryOpenCreateRootNative(params: Creation & {
|
|
44
|
+
flags: number;
|
|
45
|
+
existingFlags: number;
|
|
46
|
+
mode: number;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
handle: FileHandle;
|
|
49
|
+
cleanupCreated(): Promise<void>;
|
|
50
|
+
releaseCreationParent(): void;
|
|
51
|
+
}>;
|
|
52
|
+
export {};
|