@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
|
@@ -1,7 +1,7 @@
|
|
|
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 { assertDirectoryGuard, captureDirectoryGuard, } from "./directory-guard.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { pathExists } from "./fs.js";
|
|
7
7
|
import { realpathSync } from "./realpath.js";
|
|
@@ -22,27 +22,35 @@ function ensureDirectoryFailure(code, message, cause) {
|
|
|
22
22
|
error: new FsSafeError(code, message, { cause }),
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
function directoryGuardResult(target, scopeLabel) {
|
|
26
26
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const guard = typeof target === "string" ? captureDirectoryGuard(target, "native", { bigint: true }) : target;
|
|
28
|
+
if (typeof target !== "string")
|
|
29
|
+
assertDirectoryGuard(guard, "native");
|
|
30
|
+
return { ok: true, guard };
|
|
29
31
|
}
|
|
30
32
|
catch (err) {
|
|
31
|
-
if (err instanceof FsSafeError)
|
|
32
|
-
|
|
33
|
+
if (!(err instanceof FsSafeError))
|
|
34
|
+
throw err;
|
|
35
|
+
const dir = typeof target === "string" ? target : target.dir;
|
|
36
|
+
if (err.code !== "not-file") {
|
|
37
|
+
return { ok: false, code: err.code, error: err };
|
|
33
38
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
if (err instanceof FsSafeError) {
|
|
43
|
-
return await directoryGuardFailure(err, dir, scopeLabel);
|
|
39
|
+
try {
|
|
40
|
+
const stat = fsSync.lstatSync(pathForWindowsFilesystem(dir));
|
|
41
|
+
const failure = classifyExistingDirectorySegment(stat, scopeLabel);
|
|
42
|
+
if (failure) {
|
|
43
|
+
return failure;
|
|
44
|
+
}
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
+
catch (lookupErr) {
|
|
47
|
+
const failure = classifyDirectoryLookupError(lookupErr, scopeLabel);
|
|
48
|
+
if (failure) {
|
|
49
|
+
return failure;
|
|
50
|
+
}
|
|
51
|
+
throw lookupErr;
|
|
52
|
+
}
|
|
53
|
+
return { ok: false, code: err.code, error: err };
|
|
46
54
|
}
|
|
47
55
|
}
|
|
48
56
|
function classifyDirectoryLookupError(err, scopeLabel) {
|
|
@@ -64,27 +72,7 @@ function classifyExistingDirectorySegment(stat, scopeLabel) {
|
|
|
64
72
|
}
|
|
65
73
|
return null;
|
|
66
74
|
}
|
|
67
|
-
|
|
68
|
-
if (err.code !== "not-file") {
|
|
69
|
-
return { ok: false, code: err.code, error: err };
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
const stat = fsSync.lstatSync(pathForWindowsFilesystem(dir));
|
|
73
|
-
const failure = classifyExistingDirectorySegment(stat, scopeLabel);
|
|
74
|
-
if (failure) {
|
|
75
|
-
return failure;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch (lookupErr) {
|
|
79
|
-
const failure = classifyDirectoryLookupError(lookupErr, scopeLabel);
|
|
80
|
-
if (failure) {
|
|
81
|
-
return failure;
|
|
82
|
-
}
|
|
83
|
-
throw lookupErr;
|
|
84
|
-
}
|
|
85
|
-
return { ok: false, code: err.code, error: err };
|
|
86
|
-
}
|
|
87
|
-
async function resolveTrustedDirectoryPrefix(targetPath, scopeLabel) {
|
|
75
|
+
function resolveTrustedDirectoryPrefix(targetPath, scopeLabel) {
|
|
88
76
|
const root = path.parse(targetPath).root;
|
|
89
77
|
let current = root;
|
|
90
78
|
let currentStat;
|
|
@@ -119,7 +107,6 @@ async function resolveTrustedDirectoryPrefix(targetPath, scopeLabel) {
|
|
|
119
107
|
return segmentFailure;
|
|
120
108
|
}
|
|
121
109
|
current = next;
|
|
122
|
-
currentStat = nextStat;
|
|
123
110
|
}
|
|
124
111
|
catch (err) {
|
|
125
112
|
const code = err.code;
|
|
@@ -192,7 +179,7 @@ export async function ensureAbsoluteDirectory(dirPath, options = {}) {
|
|
|
192
179
|
return prefix;
|
|
193
180
|
}
|
|
194
181
|
let current = prefix.ancestorPath;
|
|
195
|
-
const initialGuard = await
|
|
182
|
+
const initialGuard = await directoryGuardResult(prefix.ancestorPath, scopeLabel);
|
|
196
183
|
if (!initialGuard.ok) {
|
|
197
184
|
return initialGuard;
|
|
198
185
|
}
|
|
@@ -200,25 +187,22 @@ export async function ensureAbsoluteDirectory(dirPath, options = {}) {
|
|
|
200
187
|
for (const segment of prefix.missingSegments) {
|
|
201
188
|
current = path.join(current, segment);
|
|
202
189
|
while (true) {
|
|
203
|
-
const guardResult = await
|
|
190
|
+
const guardResult = await directoryGuardResult(currentGuard, scopeLabel);
|
|
204
191
|
if (!guardResult.ok) {
|
|
205
192
|
return guardResult;
|
|
206
193
|
}
|
|
207
194
|
try {
|
|
208
195
|
const stat = fsSync.lstatSync(current);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (!stat.isDirectory()) {
|
|
213
|
-
return ensureDirectoryFailure("not-file", `path must be a real directory within ${scopeLabel}`);
|
|
214
|
-
}
|
|
196
|
+
const failure = classifyExistingDirectorySegment(stat, scopeLabel);
|
|
197
|
+
if (failure)
|
|
198
|
+
return failure;
|
|
215
199
|
break;
|
|
216
200
|
}
|
|
217
201
|
catch (err) {
|
|
218
202
|
if (err.code !== "ENOENT") {
|
|
219
203
|
throw err;
|
|
220
204
|
}
|
|
221
|
-
const parentStillValid = await
|
|
205
|
+
const parentStillValid = await directoryGuardResult(currentGuard, scopeLabel);
|
|
222
206
|
if (!parentStillValid.ok) {
|
|
223
207
|
return parentStillValid;
|
|
224
208
|
}
|
|
@@ -233,17 +217,17 @@ export async function ensureAbsoluteDirectory(dirPath, options = {}) {
|
|
|
233
217
|
}
|
|
234
218
|
}
|
|
235
219
|
}
|
|
236
|
-
const nextGuard = await
|
|
220
|
+
const nextGuard = await directoryGuardResult(current, scopeLabel);
|
|
237
221
|
if (!nextGuard.ok) {
|
|
238
222
|
return nextGuard;
|
|
239
223
|
}
|
|
240
|
-
const previousGuardStillValid = await
|
|
224
|
+
const previousGuardStillValid = await directoryGuardResult(currentGuard, scopeLabel);
|
|
241
225
|
if (!previousGuardStillValid.ok) {
|
|
242
226
|
return previousGuardStillValid;
|
|
243
227
|
}
|
|
244
228
|
currentGuard = nextGuard.guard;
|
|
245
229
|
}
|
|
246
|
-
const finalGuardResult = await
|
|
230
|
+
const finalGuardResult = await directoryGuardResult(currentGuard, scopeLabel);
|
|
247
231
|
if (!finalGuardResult.ok) {
|
|
248
232
|
return finalGuardResult;
|
|
249
233
|
}
|
|
@@ -39,3 +39,5 @@ export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSum
|
|
|
39
39
|
export type { PermissionCommandFailure } from "./permission-exec.js";
|
|
40
40
|
export { retainFileInDirectory } from "./retained-file.js";
|
|
41
41
|
export type { RetainedFile, RetainedFileAdmission, RetainedFileExpected, RetainedFileIssue, RetainedFileReceipt, RetainedFileResult, RetainFileInDirectoryOptions, } from "./retained-file-types.js";
|
|
42
|
+
export { retainEntryForPublication } from "./entry-publication.js";
|
|
43
|
+
export type { PublicationIdentity, PublicationParent, RetainEntryForPublicationOptions, EntryPublicationReceipt, EntryPublicationIssue, EntryPublicationResult, RetainedEntryPublication, } from "./entry-publication-types.js";
|
|
@@ -38,3 +38,4 @@ export { buildRandomTempFilePath, sanitizeTempFileName, tempFile, withTempFile,
|
|
|
38
38
|
export { writeSiblingTempFile, writeViaSiblingTempPath, } from "./sibling-temp.js";
|
|
39
39
|
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
40
40
|
export { retainFileInDirectory } from "./retained-file.js";
|
|
41
|
+
export { retainEntryForPublication } from "./entry-publication.js";
|
|
@@ -6,6 +6,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
6
6
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
7
7
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
8
|
import { tempFile } from "./temp-target.js";
|
|
9
|
+
import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
|
|
9
10
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
10
11
|
async function closeFileHandle(handle) {
|
|
11
12
|
if (handle)
|
|
@@ -64,6 +65,7 @@ export async function stageArchiveFileForExtraction(params) {
|
|
|
64
65
|
(process.platform !== "win32" && "O_NOFOLLOW" in fsConstants
|
|
65
66
|
? fsConstants.O_NOFOLLOW
|
|
66
67
|
: 0);
|
|
68
|
+
assertExclusiveCreateLeaf(staged.path);
|
|
67
69
|
output = await fs.open(staged.path, flags, 0o600);
|
|
68
70
|
const buffer = Buffer.allocUnsafe(Math.min(512 * 1024, Math.max(64 * 1024, Number(opened.size)), params.limits.maxArchiveBytes + 1));
|
|
69
71
|
let written = 0;
|
|
Binary file
|
|
@@ -3,6 +3,7 @@ import { type AsyncDirectoryGuard } from "./directory-guard.js";
|
|
|
3
3
|
import { type ExtractionDeadline } from "./archive-deadline.js";
|
|
4
4
|
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
5
5
|
export type ArchiveDirectoryGuard = AsyncDirectoryGuard<BigIntStats>;
|
|
6
|
+
export declare function createArchiveSymlinkTraversalError(originalPath: string): ArchiveSecurityError;
|
|
6
7
|
export declare function createDirectoryIdentityGuard(dir: string): Promise<ArchiveDirectoryGuard>;
|
|
7
8
|
export declare function assertDirectoryIdentityGuard(guard: ArchiveDirectoryGuard): Promise<void>;
|
|
8
9
|
export declare function prepareArchiveDestinationGuard(destDir: string): Promise<ArchiveDirectoryGuard>;
|
|
@@ -28,5 +29,4 @@ export declare function withStagedArchiveDestination<T>(params: {
|
|
|
28
29
|
stagingDirPrefix?: string;
|
|
29
30
|
run: (stagingDir: string) => Promise<T>;
|
|
30
31
|
}): Promise<T>;
|
|
31
|
-
export declare function createArchiveSymlinkTraversalError(originalPath: string): ArchiveSecurityError;
|
|
32
32
|
export {};
|
|
@@ -14,14 +14,14 @@ import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
|
|
|
14
14
|
import { expandRelativePathWithHome } from "./root-context.js";
|
|
15
15
|
import { resolveRootPath } from "./root-path.js";
|
|
16
16
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
17
|
-
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
17
|
+
import { assertNoWindowsPathAlias, isForeignWindowsShareOrDevicePath } from "./windows-path-alias.js";
|
|
18
18
|
import { realpathSync } from "./realpath.js";
|
|
19
19
|
const ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK = "archive entry traverses symlink in destination";
|
|
20
20
|
const ARCHIVE_STAGING_MODE = 0o700;
|
|
21
21
|
function checkExtractionDeadline(deadline) {
|
|
22
22
|
deadline?.check();
|
|
23
23
|
}
|
|
24
|
-
function
|
|
24
|
+
export function createArchiveSymlinkTraversalError(originalPath) {
|
|
25
25
|
return new ArchiveSecurityError("destination-symlink-traversal", `${ERROR_ARCHIVE_ENTRY_TRAVERSES_SYMLINK}: ${formatErrorDetail(originalPath)}`);
|
|
26
26
|
}
|
|
27
27
|
export async function createDirectoryIdentityGuard(dir) {
|
|
@@ -106,7 +106,7 @@ async function assertNoSymlinkTraversal(params) {
|
|
|
106
106
|
throw err;
|
|
107
107
|
}
|
|
108
108
|
if (stat.isSymbolicLink()) {
|
|
109
|
-
throw
|
|
109
|
+
throw createArchiveSymlinkTraversalError(params.originalPath);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -128,7 +128,7 @@ export async function assertResolvedInsideDestination(params) {
|
|
|
128
128
|
}
|
|
129
129
|
assertNoWindowsPathAlias(resolved);
|
|
130
130
|
if (!isPathInside(destinationRealDir, resolved)) {
|
|
131
|
-
throw
|
|
131
|
+
throw createArchiveSymlinkTraversalError(originalPath);
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
async function mkdirArchiveOutput(params) {
|
|
@@ -139,7 +139,7 @@ async function mkdirArchiveOutput(params) {
|
|
|
139
139
|
}
|
|
140
140
|
catch (error) {
|
|
141
141
|
if (error instanceof FsSafeError) {
|
|
142
|
-
throw
|
|
142
|
+
throw createArchiveSymlinkTraversalError(params.originalPath);
|
|
143
143
|
}
|
|
144
144
|
throw error;
|
|
145
145
|
}
|
|
@@ -166,6 +166,9 @@ export async function preparePrivateArchiveOutputPath(params, assertGuards, dest
|
|
|
166
166
|
await prepareOutputPath(ownArchiveOutputPathParams(params), assertGuards, true, destinationGuard);
|
|
167
167
|
}
|
|
168
168
|
async function prepareOutputPath(params, assertGuards, privateWorkingMode = false, existingDestinationGuard) {
|
|
169
|
+
if (isForeignWindowsShareOrDevicePath(params.outPath, [params.destinationDir, params.destinationRealDir])) {
|
|
170
|
+
throw createArchiveSymlinkTraversalError(params.originalPath);
|
|
171
|
+
}
|
|
169
172
|
const targetRoot = privateWorkingMode ? {
|
|
170
173
|
async mkdir(relativePath) {
|
|
171
174
|
// Retain Root.mkdir's strict alias admission before the shared traversal,
|
|
@@ -298,11 +301,7 @@ export async function withStagedArchiveDestination(params) {
|
|
|
298
301
|
await fs.rm(stagingDir, { recursive: true, force: true }).catch(() => undefined);
|
|
299
302
|
}
|
|
300
303
|
catch {
|
|
301
|
-
//
|
|
302
|
-
// outside the private temp tree, so fail closed and leave it for OS cleanup.
|
|
304
|
+
// Preserve staging when its identity cannot be verified; pathname cleanup is unsafe.
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
|
-
export function createArchiveSymlinkTraversalError(originalPath) {
|
|
307
|
-
return symlinkTraversalError(originalPath);
|
|
308
|
-
}
|
|
@@ -17,6 +17,7 @@ import { ArchiveSecurityError, ArchiveFormatError, isArchiveFormatErrorMessage,
|
|
|
17
17
|
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
18
18
|
import { getNativeBinding, } from "./native.js";
|
|
19
19
|
import { writeSiblingTempFile } from "./sibling-temp.js";
|
|
20
|
+
import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
|
|
20
21
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
21
22
|
import { classifyArchiveParserError } from "./archive-parser-errors.js";
|
|
22
23
|
import { validateArchiveEntryPath } from "./archive-entry.js";
|
|
@@ -61,6 +62,7 @@ async function writeZipFileEntry(params) {
|
|
|
61
62
|
syncTempFile: false,
|
|
62
63
|
syncParentDir: false,
|
|
63
64
|
writeTemp: async (tempPath) => {
|
|
65
|
+
assertExclusiveCreateLeaf(tempPath);
|
|
64
66
|
tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o600);
|
|
65
67
|
const writable = tempHandle.createWriteStream();
|
|
66
68
|
writable.once("close", () => {
|
|
@@ -83,11 +85,6 @@ async function writeZipFileEntry(params) {
|
|
|
83
85
|
resolveFinalPath: (filePath) => filePath,
|
|
84
86
|
});
|
|
85
87
|
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
// Failures here happen before the temp has been committed. The destination
|
|
88
|
-
// parent may already be untrusted, so cleanup must stay limited to temp state.
|
|
89
|
-
throw err;
|
|
90
|
-
}
|
|
91
88
|
finally {
|
|
92
89
|
const openTempHandle = tempHandle;
|
|
93
90
|
if (openTempHandle && !handleClosedByStream) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import syncFs, { type BigIntStats, type Stats } from "node:fs";
|
|
2
|
+
import fs, { type FileHandle } from "node:fs/promises";
|
|
3
|
+
export type Procedure<T> = Generator<unknown, T, unknown>;
|
|
4
|
+
type SyncFchmod = (fd: number, mode: number) => void;
|
|
5
|
+
type AsyncAtomicFileSystem = Partial<Pick<typeof fs, "open" | "lstat" | "mkdir" | "rename" | "rm" | "unlink" | "writeFile">>;
|
|
6
|
+
type SyncAtomicFileSystem = Partial<Pick<typeof syncFs, "openSync" | "lstatSync" | "mkdirSync" | "renameSync" | "rmSync" | "unlinkSync" | "fstatSync" | "closeSync" | "fsyncSync" | "writeFileSync" | "readFileSync" | "readSync" | "writeSync" | "ftruncateSync">>;
|
|
7
|
+
export declare function wait<T>(value: T | PromiseLike<T>): Procedure<T>;
|
|
8
|
+
export declare function runAsync<T>(procedure: Procedure<T>): Promise<T>;
|
|
9
|
+
export declare function runSync<T>(procedure: Procedure<T>): T;
|
|
10
|
+
export declare function inspectAtomicIdentity<T extends Pick<BigIntStats, "dev" | "ino">>(io: AtomicIo, read: () => T | Promise<T>, expected?: Pick<BigIntStats, "dev" | "ino">, synchronous?: boolean, admit?: (stat: T) => void): T | Promise<T>;
|
|
11
|
+
/** One adapter per operation; each admitted descriptor receives one AtomicFile. */
|
|
12
|
+
export declare class AtomicIo {
|
|
13
|
+
readonly asyncFs: AsyncAtomicFileSystem | undefined;
|
|
14
|
+
readonly syncFs: SyncAtomicFileSystem | undefined;
|
|
15
|
+
fchmodSync?: SyncFchmod | undefined;
|
|
16
|
+
readonly asynchronous: boolean;
|
|
17
|
+
private constructor();
|
|
18
|
+
static async(fsModule: AsyncAtomicFileSystem): AtomicIo;
|
|
19
|
+
static sync(fsModule: SyncAtomicFileSystem, fchmodSync?: SyncFchmod): AtomicIo;
|
|
20
|
+
wrap(resource: FileHandle | number): AtomicFile;
|
|
21
|
+
open(pathname: string, flags: string | number, mode?: number): Procedure<AtomicFile>;
|
|
22
|
+
lstat(pathname: string): Procedure<Stats>;
|
|
23
|
+
lstatExact(pathname: string, synchronousBuiltin?: boolean): BigIntStats | Promise<BigIntStats>;
|
|
24
|
+
mkdir(directory: string, mode: number): Procedure<void>;
|
|
25
|
+
rename(source: string, destination: string): Procedure<void>;
|
|
26
|
+
remove(pathname: string): Procedure<void>;
|
|
27
|
+
unlink(pathname: string): Procedure<void>;
|
|
28
|
+
delay(milliseconds: number): Procedure<void>;
|
|
29
|
+
}
|
|
30
|
+
export declare class AtomicFile {
|
|
31
|
+
readonly io: AtomicIo;
|
|
32
|
+
readonly resource: FileHandle | number;
|
|
33
|
+
constructor(io: AtomicIo, resource: FileHandle | number);
|
|
34
|
+
get fd(): number;
|
|
35
|
+
stat(): Procedure<Stats>;
|
|
36
|
+
statExact(): BigIntStats | Promise<BigIntStats>;
|
|
37
|
+
close(): void | Promise<void>;
|
|
38
|
+
sync(): Procedure<void>;
|
|
39
|
+
syncBestEffort(): Procedure<void>;
|
|
40
|
+
chmod(mode: number): void | Promise<void>;
|
|
41
|
+
writeFile(data: string | Uint8Array, throughModule?: boolean): void | Promise<void>;
|
|
42
|
+
readFile(sizeHint?: number): Procedure<Buffer>;
|
|
43
|
+
read(buffer: Buffer, offset: number, length: number, position: number | null): number | Promise<{
|
|
44
|
+
bytesRead: number;
|
|
45
|
+
}>;
|
|
46
|
+
write(buffer: Buffer, offset: number, length: number, position: number | null): number | Promise<{
|
|
47
|
+
bytesWritten: number;
|
|
48
|
+
}>;
|
|
49
|
+
truncate(length: number): Procedure<void>;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import syncFs, {} from "node:fs";
|
|
2
|
+
import fs, {} from "node:fs/promises";
|
|
3
|
+
import { readBoundedSync } from "./bounded-read.js";
|
|
4
|
+
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
5
|
+
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
6
|
+
import { sleep, sleepSync } from "./timing.js";
|
|
7
|
+
import { assertExclusiveCreateStat } from "./exclusive-create.js";
|
|
8
|
+
import { hasNodeErrorCode } from "./path.js";
|
|
9
|
+
export function* wait(value) {
|
|
10
|
+
return (yield value);
|
|
11
|
+
}
|
|
12
|
+
export async function runAsync(procedure) {
|
|
13
|
+
let step = procedure.next();
|
|
14
|
+
while (!step.done) {
|
|
15
|
+
let value;
|
|
16
|
+
try {
|
|
17
|
+
value = await step.value;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
step = procedure.throw(error);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
step = procedure.next(value);
|
|
24
|
+
}
|
|
25
|
+
return step.value;
|
|
26
|
+
}
|
|
27
|
+
export function runSync(procedure) {
|
|
28
|
+
let step = procedure.next();
|
|
29
|
+
// Sync adapters retain their ordinary return values; this driver never reads then.
|
|
30
|
+
while (!step.done)
|
|
31
|
+
step = procedure.next(step.value);
|
|
32
|
+
return step.value;
|
|
33
|
+
}
|
|
34
|
+
export function inspectAtomicIdentity(io, read, expected, synchronous = false, admit) {
|
|
35
|
+
if (io.asynchronous && !synchronous) {
|
|
36
|
+
const inspect = admit ? async () => {
|
|
37
|
+
const stat = await read();
|
|
38
|
+
admit(stat);
|
|
39
|
+
return stat;
|
|
40
|
+
} : read;
|
|
41
|
+
return inspectFileIdentity(inspect, expected);
|
|
42
|
+
}
|
|
43
|
+
// Sync metadata is an ordinary value, including objects with a then getter.
|
|
44
|
+
const inspect = admit ? () => {
|
|
45
|
+
const stat = read();
|
|
46
|
+
admit(stat);
|
|
47
|
+
return stat;
|
|
48
|
+
} : read;
|
|
49
|
+
return inspectFileIdentitySync(inspect, expected);
|
|
50
|
+
}
|
|
51
|
+
/** One adapter per operation; each admitted descriptor receives one AtomicFile. */
|
|
52
|
+
export class AtomicIo {
|
|
53
|
+
asyncFs;
|
|
54
|
+
syncFs;
|
|
55
|
+
fchmodSync;
|
|
56
|
+
asynchronous;
|
|
57
|
+
constructor(asyncFs, syncFs, fchmodSync) {
|
|
58
|
+
this.asyncFs = asyncFs;
|
|
59
|
+
this.syncFs = syncFs;
|
|
60
|
+
this.fchmodSync = fchmodSync;
|
|
61
|
+
this.asynchronous = asyncFs !== undefined;
|
|
62
|
+
}
|
|
63
|
+
static async(fsModule) {
|
|
64
|
+
return new AtomicIo(fsModule, undefined);
|
|
65
|
+
}
|
|
66
|
+
static sync(fsModule, fchmodSync) {
|
|
67
|
+
return new AtomicIo(undefined, fsModule, fchmodSync);
|
|
68
|
+
}
|
|
69
|
+
wrap(resource) {
|
|
70
|
+
return new AtomicFile(this, resource);
|
|
71
|
+
}
|
|
72
|
+
*open(pathname, flags, mode) {
|
|
73
|
+
const withMode = arguments.length > 2;
|
|
74
|
+
const exclusive = syncFs.constants.O_CREAT | syncFs.constants.O_EXCL;
|
|
75
|
+
if (process.platform === "win32" && (typeof flags === "string"
|
|
76
|
+
? flags.includes("x") : (flags & exclusive) === exclusive)) {
|
|
77
|
+
try {
|
|
78
|
+
assertExclusiveCreateStat(pathname, yield* this.lstat(pathname));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (!hasNodeErrorCode(error, "ENOENT"))
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const resource = this.asyncFs
|
|
86
|
+
? yield* wait(withMode
|
|
87
|
+
? this.asyncFs.open(pathname, flags, mode)
|
|
88
|
+
: this.asyncFs.open(pathname, flags))
|
|
89
|
+
: withMode
|
|
90
|
+
? this.syncFs.openSync(pathname, flags, mode)
|
|
91
|
+
: this.syncFs.openSync(pathname, flags);
|
|
92
|
+
return this.wrap(resource);
|
|
93
|
+
}
|
|
94
|
+
*lstat(pathname) {
|
|
95
|
+
if (!this.asyncFs)
|
|
96
|
+
return this.syncFs.lstatSync(pathname);
|
|
97
|
+
if (this.asyncFs === fs)
|
|
98
|
+
return syncFs.lstatSync(pathname);
|
|
99
|
+
return yield* wait(this.asyncFs.lstat(pathname));
|
|
100
|
+
}
|
|
101
|
+
lstatExact(pathname, synchronousBuiltin = true) {
|
|
102
|
+
if (!this.asyncFs)
|
|
103
|
+
return this.syncFs.lstatSync(pathname, { bigint: true });
|
|
104
|
+
if (synchronousBuiltin && this.asyncFs === fs)
|
|
105
|
+
return syncFs.lstatSync(pathname, { bigint: true });
|
|
106
|
+
return this.asyncFs.lstat(pathname, { bigint: true });
|
|
107
|
+
}
|
|
108
|
+
*mkdir(directory, mode) {
|
|
109
|
+
if (this.asyncFs) {
|
|
110
|
+
const pathname = this.asyncFs === fs ? recursiveMkdirPath(directory) : directory;
|
|
111
|
+
yield* wait(this.asyncFs.mkdir(pathname, { recursive: true, mode }));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const pathname = this.syncFs === syncFs ? recursiveMkdirPath(directory) : directory;
|
|
115
|
+
this.syncFs.mkdirSync(pathname, { recursive: true, mode });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
*rename(source, destination) {
|
|
119
|
+
if (this.asyncFs)
|
|
120
|
+
yield* wait(this.asyncFs.rename(source, destination));
|
|
121
|
+
else
|
|
122
|
+
this.syncFs.renameSync(source, destination);
|
|
123
|
+
}
|
|
124
|
+
*remove(pathname) {
|
|
125
|
+
if (this.asyncFs)
|
|
126
|
+
yield* wait(this.asyncFs.rm(pathname, { force: true }));
|
|
127
|
+
else
|
|
128
|
+
this.syncFs.rmSync(pathname, { force: true });
|
|
129
|
+
}
|
|
130
|
+
*unlink(pathname) {
|
|
131
|
+
if (this.asyncFs)
|
|
132
|
+
yield* wait(this.asyncFs.unlink(pathname));
|
|
133
|
+
else
|
|
134
|
+
this.syncFs.unlinkSync(pathname);
|
|
135
|
+
}
|
|
136
|
+
*delay(milliseconds) {
|
|
137
|
+
if (this.asynchronous)
|
|
138
|
+
yield* wait(sleep(milliseconds));
|
|
139
|
+
else
|
|
140
|
+
sleepSync(milliseconds);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export class AtomicFile {
|
|
144
|
+
io;
|
|
145
|
+
resource;
|
|
146
|
+
constructor(io, resource) {
|
|
147
|
+
this.io = io;
|
|
148
|
+
this.resource = resource;
|
|
149
|
+
}
|
|
150
|
+
get fd() {
|
|
151
|
+
return typeof this.resource === "number" ? this.resource : this.resource.fd;
|
|
152
|
+
}
|
|
153
|
+
*stat() {
|
|
154
|
+
if (typeof this.resource === "number")
|
|
155
|
+
return this.io.syncFs.fstatSync(this.resource);
|
|
156
|
+
if (this.io.asyncFs === fs)
|
|
157
|
+
return syncFs.fstatSync(this.resource.fd);
|
|
158
|
+
return yield* wait(this.resource.stat());
|
|
159
|
+
}
|
|
160
|
+
statExact() {
|
|
161
|
+
if (typeof this.resource === "number")
|
|
162
|
+
return this.io.syncFs.fstatSync(this.resource, { bigint: true });
|
|
163
|
+
if (this.io.asyncFs === fs)
|
|
164
|
+
return syncFs.fstatSync(this.resource.fd, { bigint: true });
|
|
165
|
+
return this.resource.stat({ bigint: true });
|
|
166
|
+
}
|
|
167
|
+
close() {
|
|
168
|
+
if (typeof this.resource === "number")
|
|
169
|
+
this.io.syncFs.closeSync(this.resource);
|
|
170
|
+
else
|
|
171
|
+
return this.resource.close();
|
|
172
|
+
}
|
|
173
|
+
*sync() {
|
|
174
|
+
if (typeof this.resource === "number")
|
|
175
|
+
this.io.syncFs.fsyncSync(this.resource);
|
|
176
|
+
else
|
|
177
|
+
yield* wait(this.resource.sync());
|
|
178
|
+
}
|
|
179
|
+
*syncBestEffort() {
|
|
180
|
+
try {
|
|
181
|
+
yield* this.sync();
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (error?.code !== "EPERM")
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
chmod(mode) {
|
|
189
|
+
if (typeof this.resource === "number") {
|
|
190
|
+
const chmod = this.io.fchmodSync;
|
|
191
|
+
chmod?.(this.resource, mode);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
return this.resource.chmod(mode);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
writeFile(data, throughModule = false) {
|
|
198
|
+
if (typeof this.resource === "number") {
|
|
199
|
+
this.io.syncFs.writeFileSync(this.resource, data);
|
|
200
|
+
}
|
|
201
|
+
else if (throughModule) {
|
|
202
|
+
return this.io.asyncFs.writeFile(this.resource, data);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
return this.resource.writeFile(data);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
*readFile(sizeHint) {
|
|
209
|
+
if (typeof this.resource !== "number")
|
|
210
|
+
return yield* wait(this.resource.readFile());
|
|
211
|
+
if (sizeHint === undefined)
|
|
212
|
+
return this.io.syncFs.readFileSync(this.resource);
|
|
213
|
+
const fd = this.resource;
|
|
214
|
+
let position = 0;
|
|
215
|
+
return readBoundedSync(Infinity, (buffer, length) => {
|
|
216
|
+
const bytesRead = this.io.syncFs.readSync(fd, buffer, 0, length, position);
|
|
217
|
+
position += bytesRead;
|
|
218
|
+
return bytesRead;
|
|
219
|
+
}, { initialSize: Number.isSafeInteger(sizeHint) && sizeHint >= 0 ? sizeHint : undefined });
|
|
220
|
+
}
|
|
221
|
+
read(buffer, offset, length, position) {
|
|
222
|
+
if (typeof this.resource === "number") {
|
|
223
|
+
return this.io.syncFs.readSync(this.resource, buffer, offset, length, position);
|
|
224
|
+
}
|
|
225
|
+
return this.resource.read(buffer, offset, length, position);
|
|
226
|
+
}
|
|
227
|
+
write(buffer, offset, length, position) {
|
|
228
|
+
if (typeof this.resource === "number") {
|
|
229
|
+
const written = this.io.syncFs.writeSync(this.resource, buffer, offset, length, position);
|
|
230
|
+
if (written === 0)
|
|
231
|
+
throw new Error("Copy fallback write made no progress");
|
|
232
|
+
return written;
|
|
233
|
+
}
|
|
234
|
+
return this.resource.write(buffer, offset, length, position);
|
|
235
|
+
}
|
|
236
|
+
*truncate(length) {
|
|
237
|
+
if (typeof this.resource === "number")
|
|
238
|
+
this.io.syncFs.ftruncateSync(this.resource, length);
|
|
239
|
+
else
|
|
240
|
+
yield* wait(this.resource.truncate(length));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { configureFsSafeNative, getFsSafeNativeConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
|
|
2
2
|
export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
|
|
2
2
|
export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
|
|
@@ -17,5 +17,5 @@ export declare function assertCopySourceCurrent(source: {
|
|
|
17
17
|
realPath: string;
|
|
18
18
|
}, identity: BigIntStats): Promise<void>;
|
|
19
19
|
export declare function writeCopyFileToFd(fd: number, input: CopyFileInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
|
|
20
|
-
export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined
|
|
20
|
+
export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined): Promise<NativeFileCopyResult | undefined>;
|
|
21
21
|
export declare function assertNativeCopyCompleted(input: CopyFileInput, copied?: NativeFileCopyResult): void;
|
|
@@ -27,7 +27,7 @@ export async function writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutatio
|
|
|
27
27
|
maxBytes, sizeHint: input.size, signal: input.signal, assertBeforeMutation,
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
-
export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes
|
|
30
|
+
export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes) {
|
|
31
31
|
input.signal?.throwIfAborted();
|
|
32
32
|
if (!native.copyFileExclusive) {
|
|
33
33
|
if (input.clone === "always") {
|
|
@@ -39,7 +39,7 @@ export async function createNativeCopyFile(native, input, parentFd, basename, ma
|
|
|
39
39
|
const nativeSignal = input.signal ? AbortSignal.any([input.signal]) : undefined;
|
|
40
40
|
try {
|
|
41
41
|
// The caller adopts this descriptor before observing a later cancellation.
|
|
42
|
-
return await native.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal
|
|
42
|
+
return await native.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
|
|
43
43
|
}
|
|
44
44
|
catch (error) {
|
|
45
45
|
const code = error?.code;
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import fsp, {} from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
|
+
import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
|
|
6
7
|
import { assertStagedDirectoryCurrent, exactIdentityMatches, openStagedDirectory, } from "./staged-directory.js";
|
|
7
8
|
function timestampSeconds(nanoseconds) {
|
|
8
9
|
// Dates discard sub-millisecond precision; negative numbers mean "now" in
|
|
@@ -49,6 +50,7 @@ export async function copyOwnedTree(source, destination, options) {
|
|
|
49
50
|
if (!opened.isFile() || !exactIdentityMatches(stat, opened)) {
|
|
50
51
|
throw new FsSafeError("path-mismatch", "copy source changed while opening");
|
|
51
52
|
}
|
|
53
|
+
assertExclusiveCreateLeaf(to);
|
|
52
54
|
output = await fsp.open(to, "wx", 0o600);
|
|
53
55
|
if (options.copyFileContents) {
|
|
54
56
|
// napi-rs owns onabort. Each admitted file gets a separate signal; the
|