@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
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import fs from "node:fs";
|
|
54
|
+
import { randomUUID } from "node:crypto";
|
|
55
|
+
import fsAsync from "node:fs/promises";
|
|
56
|
+
import path from "node:path";
|
|
57
|
+
import { assertSyncDirectoryGuard } from "./directory-guard.js";
|
|
58
|
+
import { nodeDirectorySearchOnlyFlags } from "./directory-mode-node.js";
|
|
59
|
+
import { assertDarwinCreationAcl, assertPrivateDirectory } from "./creation-boundary.js";
|
|
60
|
+
import { assertMutationNotDenied } from "./deny-mutations.js";
|
|
61
|
+
import { FsSafeError } from "./errors.js";
|
|
62
|
+
import { getNativeBinding } from "./native.js";
|
|
63
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
64
|
+
import { openNativeRootAdmission } from "./native-parent-admission.js";
|
|
65
|
+
import { capturePolicyAwareNativeParent } from "./native-policy-parent.js";
|
|
66
|
+
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
67
|
+
import { preparePinnedWriteMutationAdmission } from "./pinned-mutation-admission.js";
|
|
68
|
+
import { resolvePathViaExistingAncestor } from "./root-path-existing.js";
|
|
69
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
70
|
+
import { assertRootIdentityCurrentSync } from "./root-context.js";
|
|
71
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
72
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
73
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
74
|
+
export function rootWriteQueueKey(root, relativePath) {
|
|
75
|
+
return `${root.rootReal}\0${relativePath}`;
|
|
76
|
+
}
|
|
77
|
+
export function buildAtomicWriteTempPath(targetPath) {
|
|
78
|
+
return path.join(path.dirname(targetPath), `.fs-safe-${randomUUID()}.tmp`);
|
|
79
|
+
}
|
|
80
|
+
function unavailable() {
|
|
81
|
+
throw new FsSafeError("helper-unavailable", "native confined creation is unavailable");
|
|
82
|
+
}
|
|
83
|
+
async function withNativeDirectory(params, operation, discard) {
|
|
84
|
+
const binding = getNativeBinding();
|
|
85
|
+
if (!binding?.openBeneath || !binding.mkdirChildBeneath || (params.private && process.platform === "win32"))
|
|
86
|
+
return unavailable();
|
|
87
|
+
const directoryTarget = params.directory === params.target;
|
|
88
|
+
const canonicalTarget = async (target) => directoryTarget
|
|
89
|
+
? await resolvePathViaExistingAncestor(target)
|
|
90
|
+
: path.join(await resolvePathViaExistingAncestor(path.dirname(target)), path.basename(target));
|
|
91
|
+
const resolveCurrent = params.resolveCurrent;
|
|
92
|
+
params = { ...params,
|
|
93
|
+
directory: await resolvePathViaExistingAncestor(params.directory),
|
|
94
|
+
target: await canonicalTarget(params.target),
|
|
95
|
+
resolveCurrent: async () => await canonicalTarget(await resolveCurrent()),
|
|
96
|
+
};
|
|
97
|
+
if (!isPathInside(params.root.rootReal, params.directory)) {
|
|
98
|
+
throw new FsSafeError("outside-workspace", "creation parent is outside root");
|
|
99
|
+
}
|
|
100
|
+
const close = captureNativeFdClose(binding);
|
|
101
|
+
let pending;
|
|
102
|
+
try {
|
|
103
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
104
|
+
try {
|
|
105
|
+
const admitted = await openNativeRootAdmission(binding, {
|
|
106
|
+
rootPath: params.root.rootReal, rootIdentity: params.root.rootIdentity, operation: "create", reportCloseErrors: true, searchOnly: true,
|
|
107
|
+
});
|
|
108
|
+
const rootOwner = __addDisposableResource(env_1, admitted.root, true);
|
|
109
|
+
const descriptors = [];
|
|
110
|
+
const directoryOwner = __addDisposableResource(env_1, { [Symbol.dispose]() {
|
|
111
|
+
const errors = [];
|
|
112
|
+
for (const fd of descriptors.reverse())
|
|
113
|
+
try {
|
|
114
|
+
close(fd);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
errors.push(error);
|
|
118
|
+
}
|
|
119
|
+
if (errors.length)
|
|
120
|
+
throw new AggregateError(errors, "creation directory close failed");
|
|
121
|
+
} }, false);
|
|
122
|
+
const guards = [];
|
|
123
|
+
function assertCurrent() {
|
|
124
|
+
assertRootIdentityCurrentSync(params.root);
|
|
125
|
+
for (const guard of guards)
|
|
126
|
+
assertSyncDirectoryGuard(guard);
|
|
127
|
+
}
|
|
128
|
+
async function authorize(mutation) {
|
|
129
|
+
if (path.relative(params.target, await params.resolveCurrent()) !== "") {
|
|
130
|
+
throw new FsSafeError("path-mismatch", "creation route changed during admission");
|
|
131
|
+
}
|
|
132
|
+
await assertMutationNotDenied(params.target, params.policy?.denyMutations);
|
|
133
|
+
await assertMutationNotDenied(mutation, params.policy?.denyMutations);
|
|
134
|
+
assertCurrent();
|
|
135
|
+
}
|
|
136
|
+
let current = rootOwner.fd;
|
|
137
|
+
let currentPath = params.root.rootReal;
|
|
138
|
+
const flags = (nodeDirectorySearchOnlyFlags()?.flags ?? fs.constants.O_RDONLY) |
|
|
139
|
+
(fs.constants.O_DIRECTORY ?? 0) | (fs.constants.O_NOFOLLOW ?? 0);
|
|
140
|
+
if (!params.private && !(directoryTarget && params.directory === params.root.rootReal)) {
|
|
141
|
+
if (!admitPathInsideRoot({ rootPath: params.root.rootReal, candidatePath: params.target, rootIdentity: params.root.rootIdentity })) {
|
|
142
|
+
throw new FsSafeError("outside-workspace", "creation target is outside root");
|
|
143
|
+
}
|
|
144
|
+
const relativeParentPath = path.relative(params.root.rootReal, params.directory).split(path.sep).join("/");
|
|
145
|
+
const prepared = await preparePinnedWriteMutationAdmission({
|
|
146
|
+
rootReal: params.root.rootReal, rootIdentity: params.root.rootIdentity,
|
|
147
|
+
resolvedTargetPath: params.target, defaultRelativeParentPath: relativeParentPath,
|
|
148
|
+
originalPath: params.originalPath, policy: params.policy ?? {},
|
|
149
|
+
resolveCurrent: async () => ({ resolved: await params.resolveCurrent() }),
|
|
150
|
+
});
|
|
151
|
+
const parent = await capturePolicyAwareNativeParent(binding, {
|
|
152
|
+
rootPath: params.root.rootReal, rootIdentity: params.root.rootIdentity,
|
|
153
|
+
relativeParentPath, basename: directoryTarget ? "" : path.basename(params.target),
|
|
154
|
+
mkdir: params.mkdir, mode: 0o600, input: { kind: "buffer", data: "" },
|
|
155
|
+
mutationAdmission: prepared.mutationAdmission, assertBeforeMutation: params.assertBeforeMutation,
|
|
156
|
+
}, admitted, process.platform === "win32", flags, true);
|
|
157
|
+
descriptors.push(parent.fd);
|
|
158
|
+
guards.push(parent.guard);
|
|
159
|
+
assertCurrent();
|
|
160
|
+
pending = { value: await operation(binding, parent.fd, assertCurrent) };
|
|
161
|
+
return pending.value;
|
|
162
|
+
}
|
|
163
|
+
for (const segment of path.relative(currentPath, params.directory).split(path.sep).filter(Boolean)) {
|
|
164
|
+
const childPath = path.join(currentPath, segment);
|
|
165
|
+
let child;
|
|
166
|
+
let created = false;
|
|
167
|
+
try {
|
|
168
|
+
child = binding.openBeneath(current, segment, flags).fd;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (!params.mkdir || !isNotFoundPathError(error))
|
|
172
|
+
throw error;
|
|
173
|
+
await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", childPath);
|
|
174
|
+
await authorize(childPath);
|
|
175
|
+
if (params.private)
|
|
176
|
+
assertDarwinCreationAcl(current, "directory");
|
|
177
|
+
params.assertBeforeMutation?.();
|
|
178
|
+
assertCurrent();
|
|
179
|
+
if (binding.mkdirOpenChildBeneath) {
|
|
180
|
+
const opened = binding.mkdirOpenChildBeneath(current, segment, params.private ? 0o700 : 0o777, flags);
|
|
181
|
+
created = opened.created;
|
|
182
|
+
child = opened.fd;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
created = binding.mkdirChildBeneath(current, segment, params.private ? 0o700 : 0o777);
|
|
186
|
+
child = binding.openBeneath(current, segment, flags).fd;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
descriptors.push(child);
|
|
190
|
+
const stat = inspectFileIdentitySync(() => fs.fstatSync(child, { bigint: true }));
|
|
191
|
+
if (!stat.isDirectory())
|
|
192
|
+
throw new FsSafeError("not-file", "creation parent is not a directory");
|
|
193
|
+
guards.push({ dir: childPath, realPath: childPath, stat });
|
|
194
|
+
assertCurrent();
|
|
195
|
+
if (created && params.private)
|
|
196
|
+
await assertPrivateDirectory(childPath);
|
|
197
|
+
current = child;
|
|
198
|
+
currentPath = childPath;
|
|
199
|
+
}
|
|
200
|
+
await authorize(params.target);
|
|
201
|
+
if (params.private) {
|
|
202
|
+
await assertPrivateDirectory(currentPath);
|
|
203
|
+
assertCurrent();
|
|
204
|
+
}
|
|
205
|
+
pending = { value: await operation(binding, current, assertCurrent) };
|
|
206
|
+
return pending.value;
|
|
207
|
+
}
|
|
208
|
+
catch (e_1) {
|
|
209
|
+
env_1.error = e_1;
|
|
210
|
+
env_1.hasError = true;
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
const result_1 = __disposeResources(env_1);
|
|
214
|
+
if (result_1)
|
|
215
|
+
await result_1;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (pending && discard) {
|
|
220
|
+
try {
|
|
221
|
+
await discard(pending.value);
|
|
222
|
+
}
|
|
223
|
+
catch (cleanupError) {
|
|
224
|
+
throw createSuppressedError(cleanupError, error, "creation handoff and cleanup failed");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
export async function tryMkdirRootNative(params) {
|
|
231
|
+
// The protected Windows creator already verifies the admitted parent identity
|
|
232
|
+
// and uses handle-relative NtCreateFile with its protected security descriptor.
|
|
233
|
+
if (params.private && process.platform === "win32")
|
|
234
|
+
return false;
|
|
235
|
+
return await withNativeDirectory(params, async (_binding, _fd, assertCurrent) => { assertCurrent(); return true; });
|
|
236
|
+
}
|
|
237
|
+
export async function tryOpenCreateRootNative(params) {
|
|
238
|
+
const binding = getNativeBinding();
|
|
239
|
+
if (!binding?.removeStagedFile || !binding.openCreateBeneath)
|
|
240
|
+
return unavailable();
|
|
241
|
+
const access = (params.existingFlags & fs.constants.O_RDWR) ? 0o600 : 0o200;
|
|
242
|
+
if (typeof params.mode !== "number")
|
|
243
|
+
throw Object.assign(new TypeError("mode must be a number"), { code: "ERR_INVALID_ARG_TYPE" });
|
|
244
|
+
if (!Number.isInteger(params.mode) || params.mode < 0 || params.mode > 0xffffffff) {
|
|
245
|
+
throw Object.assign(new RangeError("mode must be an unsigned 32-bit integer"), { code: "ERR_OUT_OF_RANGE" });
|
|
246
|
+
}
|
|
247
|
+
const creationMode = () => {
|
|
248
|
+
const mask = process.umask();
|
|
249
|
+
const desired = params.mode & 0o7777 & ~mask;
|
|
250
|
+
return (desired & access) === access ? desired : undefined;
|
|
251
|
+
};
|
|
252
|
+
// A public FileHandle reopen must not require widening creation permissions.
|
|
253
|
+
if (creationMode() === undefined)
|
|
254
|
+
return unavailable();
|
|
255
|
+
return await withNativeDirectory(params, async (binding, parent, assertCurrent) => {
|
|
256
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
257
|
+
try {
|
|
258
|
+
params.assertBeforeMutation?.();
|
|
259
|
+
assertCurrent();
|
|
260
|
+
const mode = creationMode();
|
|
261
|
+
if (mode === undefined)
|
|
262
|
+
throw new FsSafeError("helper-unavailable", "native creation cannot preserve the requested permissions during handoff");
|
|
263
|
+
// Native creation owns the only O_CREAT dispatch. The subsequent Node open
|
|
264
|
+
// carries neither O_CREAT nor O_TRUNC and must match this retained inode.
|
|
265
|
+
const close = captureNativeFdClose(binding);
|
|
266
|
+
const fd = binding.openCreateBeneath(parent, path.basename(params.target), params.flags, params.mode & 0o7777);
|
|
267
|
+
let rawOpen = true;
|
|
268
|
+
const closeRaw = () => { if (rawOpen) {
|
|
269
|
+
rawOpen = false;
|
|
270
|
+
close(fd);
|
|
271
|
+
} };
|
|
272
|
+
const owner = __addDisposableResource(env_2, { [Symbol.dispose]: closeRaw }, false);
|
|
273
|
+
try {
|
|
274
|
+
const identity = inspectFileIdentitySync(() => fs.fstatSync(fd, { bigint: true }));
|
|
275
|
+
if (!identity.isFile() || identity.nlink !== 1n)
|
|
276
|
+
throw new FsSafeError("path-mismatch", "created file changed");
|
|
277
|
+
// Keep the kernel's creation-time ACL and umask decisions. chmod here
|
|
278
|
+
// could widen inherited ACL restrictions, even for ordinary mode bits.
|
|
279
|
+
if (process.platform !== "win32" && (Number(identity.mode) & access) !== access) {
|
|
280
|
+
throw new FsSafeError("helper-unavailable", "created permissions do not permit a FileHandle handoff");
|
|
281
|
+
}
|
|
282
|
+
assertCurrent();
|
|
283
|
+
const handle = await fsAsync.open(params.target, params.existingFlags);
|
|
284
|
+
let validatedHandle = false;
|
|
285
|
+
try {
|
|
286
|
+
inspectFileIdentitySync(() => fs.fstatSync(handle.fd, { bigint: true }), identity);
|
|
287
|
+
validatedHandle = true;
|
|
288
|
+
assertCurrent();
|
|
289
|
+
// Close failures still belong to this scope, before transferring either owner.
|
|
290
|
+
closeRaw();
|
|
291
|
+
// Empty relative paths duplicate the admitted descriptor without reopening its name.
|
|
292
|
+
const retainedParent = binding.openBeneath(parent, "", (nodeDirectorySearchOnlyFlags()?.flags ?? fs.constants.O_RDONLY) | (fs.constants.O_DIRECTORY ?? 0)).fd;
|
|
293
|
+
let held = true;
|
|
294
|
+
return { handle, async cleanupCreated() {
|
|
295
|
+
if (!held)
|
|
296
|
+
return;
|
|
297
|
+
inspectFileIdentitySync(() => fs.fstatSync(handle.fd, { bigint: true }), identity);
|
|
298
|
+
binding.removeStagedFile(retainedParent, path.basename(params.target), handle.fd);
|
|
299
|
+
}, releaseCreationParent() {
|
|
300
|
+
if (!held)
|
|
301
|
+
return;
|
|
302
|
+
held = false;
|
|
303
|
+
close(retainedParent);
|
|
304
|
+
} };
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
if (!rawOpen && validatedHandle) {
|
|
308
|
+
try {
|
|
309
|
+
binding.removeStagedFile(parent, path.basename(params.target), handle.fd);
|
|
310
|
+
}
|
|
311
|
+
catch { /* Preserve handoff failure. */ }
|
|
312
|
+
}
|
|
313
|
+
await handle.close();
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
if (rawOpen)
|
|
319
|
+
try {
|
|
320
|
+
binding.removeStagedFile(parent, path.basename(params.target), fd);
|
|
321
|
+
}
|
|
322
|
+
catch { /* Preserve the admission failure. */ }
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (e_2) {
|
|
327
|
+
env_2.error = e_2;
|
|
328
|
+
env_2.hasError = true;
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
__disposeResources(env_2);
|
|
332
|
+
}
|
|
333
|
+
}, async (created) => {
|
|
334
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
335
|
+
try {
|
|
336
|
+
const handle = __addDisposableResource(env_3, created.handle, true);
|
|
337
|
+
const parent = __addDisposableResource(env_3, { [Symbol.dispose]: created.releaseCreationParent }, false);
|
|
338
|
+
await created.cleanupCreated();
|
|
339
|
+
}
|
|
340
|
+
catch (e_3) {
|
|
341
|
+
env_3.error = e_3;
|
|
342
|
+
env_3.hasError = true;
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
const result_2 = __disposeResources(env_3);
|
|
346
|
+
if (result_2)
|
|
347
|
+
await result_2;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ExactStatIdentity } from "./stat-observation.js";
|
|
2
|
+
import type { DirEntry } from "./types.js";
|
|
3
|
+
/** Strings only: cached spellings never replace an observation or its identity fences. */
|
|
4
|
+
export type RootDirectoryListingPaths = {
|
|
5
|
+
directory: string;
|
|
6
|
+
names: Map<string, string>;
|
|
7
|
+
};
|
|
8
|
+
export type RootDirectoryListing = {
|
|
9
|
+
paths: RootDirectoryListingPaths;
|
|
10
|
+
assertCurrent(): Promise<void>;
|
|
11
|
+
next(): Promise<{
|
|
12
|
+
kind: "entry";
|
|
13
|
+
entry: DirEntry;
|
|
14
|
+
identity?: ExactStatIdentity;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "limit";
|
|
17
|
+
name: string;
|
|
18
|
+
} | undefined>;
|
|
19
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
export type RootDirectoryListingOptions = {
|
|
22
|
+
order: "sorted" | "filesystem";
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
snapshot: boolean;
|
|
25
|
+
maxNames?: number;
|
|
26
|
+
metadataBatchSize?: number;
|
|
27
|
+
/** Internal watch lane: exact identities and bounded synchronous name reads on Node. */
|
|
28
|
+
exactIdentity?: boolean;
|
|
29
|
+
/** Advisory scans may omit vanished leaves after revalidating their parent. */
|
|
30
|
+
skipVanished?: boolean;
|
|
31
|
+
previousPaths?: RootDirectoryListingPaths;
|
|
32
|
+
/** Internal owner receives cleanup failures, including acquisition rollback. */
|
|
33
|
+
onCleanupFailure?: (error: unknown) => void;
|
|
34
|
+
admitEntry(): boolean;
|
|
35
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -2,9 +2,10 @@ import type { BigIntStats, Stats } from "node:fs";
|
|
|
2
2
|
import { type AsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
3
|
import { type RootContext } from "./root-context.js";
|
|
4
4
|
import type { RootPathDirectoryObservationGuard, RootPathObservationReceipt, RootPathParentObservationReceipt } from "./root-path.js";
|
|
5
|
-
import { type ExactStatIdentity } from "./stat-observation.js";
|
|
6
5
|
import type { DirEntry, PathStat } from "./types.js";
|
|
6
|
+
import type { RootDirectoryListing, RootDirectoryListingOptions } from "./root-directory-list-types.js";
|
|
7
7
|
export declare function pathStatFromStats(stat: Stats | BigIntStats): PathStat;
|
|
8
|
+
export declare function pathStatFromStats(stat: Stats | BigIntStats, name: string): DirEntry;
|
|
8
9
|
export type RootDirectoryObservationGuard = AsyncDirectoryGuard<BigIntStats>;
|
|
9
10
|
export declare function createRootDirectoryObservationGuard(root: RootContext, directory: string): Promise<RootDirectoryObservationGuard>;
|
|
10
11
|
export declare function assertRootDirectoryObservationGuard(root: RootContext, guard: RootDirectoryObservationGuard | RootPathDirectoryObservationGuard): Promise<void>;
|
|
@@ -17,30 +18,4 @@ export declare function assertRootDirectoryObservationGuard(root: RootContext, g
|
|
|
17
18
|
export declare function assertRootPathObservationReceiptCurrent(root: RootContext, receipt: RootPathObservationReceipt | RootPathParentObservationReceipt, finalTarget?: Stats | BigIntStats): void;
|
|
18
19
|
export declare function listDirectoryPath(root: RootContext, directory: string, withFileTypes: true, receipt?: RootPathObservationReceipt): Promise<DirEntry[]>;
|
|
19
20
|
export declare function listDirectoryPath(root: RootContext, directory: string, withFileTypes: boolean, receipt?: RootPathObservationReceipt): Promise<string[] | DirEntry[]>;
|
|
20
|
-
export type RootDirectoryListing = {
|
|
21
|
-
assertCurrent(): Promise<void>;
|
|
22
|
-
next(): Promise<{
|
|
23
|
-
kind: "entry";
|
|
24
|
-
entry: DirEntry;
|
|
25
|
-
identity?: ExactStatIdentity;
|
|
26
|
-
} | {
|
|
27
|
-
kind: "limit";
|
|
28
|
-
name: string;
|
|
29
|
-
} | undefined>;
|
|
30
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
31
|
-
};
|
|
32
|
-
export type RootDirectoryListingOptions = {
|
|
33
|
-
order: "sorted" | "filesystem";
|
|
34
|
-
signal?: AbortSignal;
|
|
35
|
-
snapshot: boolean;
|
|
36
|
-
maxNames?: number;
|
|
37
|
-
metadataBatchSize?: number;
|
|
38
|
-
/** Internal exact metadata lane, supported by streaming filesystem order. */
|
|
39
|
-
exactIdentity?: boolean;
|
|
40
|
-
/** Advisory scans may omit vanished leaves after revalidating their parent. */
|
|
41
|
-
skipVanished?: boolean;
|
|
42
|
-
/** Internal owner receives cleanup failures, including acquisition rollback. */
|
|
43
|
-
onCleanupFailure?: (error: unknown) => void;
|
|
44
|
-
admitEntry(): boolean;
|
|
45
|
-
};
|
|
46
21
|
export declare function openRootDirectoryListing(root: RootContext, directory: string, options: RootDirectoryListingOptions, receipt?: RootPathObservationReceipt): Promise<RootDirectoryListing>;
|
|
@@ -21,8 +21,8 @@ function directoryEntryName(bytes) {
|
|
|
21
21
|
}
|
|
22
22
|
return bytes.toString("utf8");
|
|
23
23
|
}
|
|
24
|
-
async function readDirectoryEntryName(handle) {
|
|
25
|
-
const entry = await handle.read();
|
|
24
|
+
async function readDirectoryEntryName(handle, synchronous = false) {
|
|
25
|
+
const entry = synchronous ? handle.readSync() : await handle.read();
|
|
26
26
|
// Bun returns the raw Buffer directly; Node returns a Dirent whose name is a Buffer.
|
|
27
27
|
// Node's Dir types still declare only string names.
|
|
28
28
|
return entry === null ? undefined
|
|
@@ -31,25 +31,19 @@ async function readDirectoryEntryName(handle) {
|
|
|
31
31
|
function openDirectoryNames(directory) {
|
|
32
32
|
return fs.opendir(directory, { bufferSize: 1, encoding: "buffer" });
|
|
33
33
|
}
|
|
34
|
-
export function pathStatFromStats(stat) {
|
|
34
|
+
export function pathStatFromStats(stat, name) {
|
|
35
35
|
const mtimeMs = typeof stat.mtimeMs === "bigint"
|
|
36
36
|
? "mtimeNs" in stat && typeof stat.mtimeNs === "bigint"
|
|
37
37
|
? Number(stat.mtimeNs) / 1_000_000
|
|
38
38
|
: stat.mtime.getTime()
|
|
39
39
|
: stat.mtimeMs;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
mode: Number(stat.mode),
|
|
48
|
-
mtimeMs,
|
|
49
|
-
nlink: Number(stat.nlink),
|
|
50
|
-
size: Number(stat.size),
|
|
51
|
-
uid: Number(stat.uid),
|
|
52
|
-
};
|
|
40
|
+
const dev = Number(stat.dev), gid = Number(stat.gid), ino = Number(stat.ino);
|
|
41
|
+
const isDirectory = stat.isDirectory(), isFile = stat.isFile(), isSymbolicLink = stat.isSymbolicLink();
|
|
42
|
+
const mode = Number(stat.mode), nlink = Number(stat.nlink), size = Number(stat.size), uid = Number(stat.uid);
|
|
43
|
+
// Construct named metadata directly, preserving property order without a spread copy.
|
|
44
|
+
return name === undefined
|
|
45
|
+
? { dev, gid, ino, isDirectory, isFile, isSymbolicLink, mode, mtimeMs, nlink, size, uid }
|
|
46
|
+
: { name, dev, gid, ino, isDirectory, isFile, isSymbolicLink, mode, mtimeMs, nlink, size, uid };
|
|
53
47
|
}
|
|
54
48
|
export async function createRootDirectoryObservationGuard(root, directory) {
|
|
55
49
|
const guard = await createAsyncDirectoryGuard(directory, { bigint: true });
|
|
@@ -220,12 +214,14 @@ async function listGuardedDirectoryPath(root, guard, withFileTypes, receipt) {
|
|
|
220
214
|
if (beforeObservation)
|
|
221
215
|
await beforeObservation(guard.realPath, withFileTypes);
|
|
222
216
|
const names = (await fs.readdir(guard.realPath, { encoding: "buffer" })).map(directoryEntryName).sort();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
217
|
+
if (withFileTypes) {
|
|
218
|
+
// readdir supplies literal child names beneath the already-canonical directory.
|
|
219
|
+
const prefix = guard.realPath.endsWith(path.sep) ? guard.realPath : `${guard.realPath}${path.sep}`;
|
|
220
|
+
entries = names.map(name => pathStatFromStats(fsSync.lstatSync(`${prefix}${name}`), name));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
entries = names;
|
|
224
|
+
}
|
|
229
225
|
}
|
|
230
226
|
catch (error) {
|
|
231
227
|
// Preserve ordinary observation errors only while the admitted directory is
|
|
@@ -268,12 +264,14 @@ export async function openRootDirectoryListing(root, directory, options, receipt
|
|
|
268
264
|
await assertRootDirectoryObservationGuard(root, guard);
|
|
269
265
|
options.signal?.throwIfAborted();
|
|
270
266
|
};
|
|
267
|
+
const paths = { directory: guard.realPath, names: new Map() };
|
|
268
|
+
const previousPaths = options.previousPaths?.directory === guard.realPath ? options.previousPaths.names : undefined;
|
|
271
269
|
let handle;
|
|
272
270
|
let names;
|
|
273
271
|
let snapshot;
|
|
274
|
-
let index = 0;
|
|
272
|
+
let index = 0, preparedIndex = 0;
|
|
275
273
|
let prepared = [];
|
|
276
|
-
|
|
274
|
+
const synchronousNames = options.exactIdentity && !process.versions.bun && !process.versions.deno;
|
|
277
275
|
let pendingFailure;
|
|
278
276
|
let pendingLimit;
|
|
279
277
|
let preparedBatch = false;
|
|
@@ -347,7 +345,7 @@ export async function openRootDirectoryListing(root, directory, options, receipt
|
|
|
347
345
|
preparedIndex = 0;
|
|
348
346
|
while (true) {
|
|
349
347
|
try {
|
|
350
|
-
const entry =
|
|
348
|
+
const entry = pathStatFromStats(fsSync.lstatSync(path.join(guard.realPath, name)), name);
|
|
351
349
|
prepared.push(entry);
|
|
352
350
|
// No later sibling can be observed before a possible recursive descent.
|
|
353
351
|
if (entry.isDirectory || entry.isSymbolicLink || prepared.length >= (options.metadataBatchSize ?? METADATA_BATCH_SIZE))
|
|
@@ -376,6 +374,7 @@ export async function openRootDirectoryListing(root, directory, options, receipt
|
|
|
376
374
|
}
|
|
377
375
|
};
|
|
378
376
|
return {
|
|
377
|
+
paths,
|
|
379
378
|
assertCurrent,
|
|
380
379
|
async next() {
|
|
381
380
|
try {
|
|
@@ -400,15 +399,19 @@ export async function openRootDirectoryListing(root, directory, options, receipt
|
|
|
400
399
|
return;
|
|
401
400
|
}
|
|
402
401
|
for (;;) {
|
|
402
|
+
if (synchronousNames && index++ % METADATA_BATCH_SIZE === METADATA_BATCH_SIZE - 1)
|
|
403
|
+
await yieldToEventLoop();
|
|
403
404
|
await assertCurrent();
|
|
404
|
-
const name = await readDirectoryEntryName(handle);
|
|
405
|
+
const name = await readDirectoryEntryName(handle, synchronousNames);
|
|
405
406
|
await assertCurrent();
|
|
406
407
|
if (name === undefined)
|
|
407
408
|
return;
|
|
408
409
|
if (!options.admitEntry())
|
|
409
410
|
return { kind: "limit", name };
|
|
410
411
|
// The stream's post-read fence is also the pre-stat fence in this owned operation.
|
|
411
|
-
const pathname = path.join(guard.realPath, name);
|
|
412
|
+
const pathname = previousPaths?.get(name) ?? path.join(guard.realPath, name);
|
|
413
|
+
if (options.exactIdentity)
|
|
414
|
+
paths.names.set(name, pathname);
|
|
412
415
|
let observed;
|
|
413
416
|
try {
|
|
414
417
|
observed = options.exactIdentity ? inspectStatObservationSync(bigint => bigint
|
|
@@ -421,7 +424,7 @@ export async function openRootDirectoryListing(root, directory, options, receipt
|
|
|
421
424
|
throw error;
|
|
422
425
|
}
|
|
423
426
|
await assertCurrent();
|
|
424
|
-
return { kind: "entry", entry:
|
|
427
|
+
return { kind: "entry", entry: pathStatFromStats(observed.stat, name), identity: observed.identity };
|
|
425
428
|
}
|
|
426
429
|
}
|
|
427
430
|
catch (error) {
|
|
@@ -5,11 +5,8 @@ import { formatErrorDetail } from "./error-detail.js";
|
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { assertNoNulPathInput, hasNodeErrorCode, isNodeError, isNotFoundPathError, isPathInside, isPathRelativeEscape, } from "./path.js";
|
|
7
7
|
import { realpathSync } from "./realpath.js";
|
|
8
|
-
import { resolvePathWithinRoot } from "./root-paths-lexical.js";
|
|
8
|
+
import { invalidPath, resolvePathWithinRoot } from "./root-paths-lexical.js";
|
|
9
9
|
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
10
|
-
function invalidPath(scopeLabel) {
|
|
11
|
-
return { ok: false, error: `Invalid path: must stay within ${scopeLabel}` };
|
|
12
|
-
}
|
|
13
10
|
export function resolveNearestExistingPath(targetPath) {
|
|
14
11
|
assertNoWindowsPathAlias(targetPath);
|
|
15
12
|
let current = resolvePathPreservingWindowsRoot(targetPath);
|
|
@@ -29,7 +29,41 @@ export function hardlinkedPathNotAllowedError() {
|
|
|
29
29
|
export function isAlreadyExistsError(error) {
|
|
30
30
|
return hasNodeErrorCode(error, "EEXIST") || /File exists|EEXIST/i.test(String(error));
|
|
31
31
|
}
|
|
32
|
+
function descriptorExhaustion(error) {
|
|
33
|
+
const pending = [error];
|
|
34
|
+
let details;
|
|
35
|
+
let code;
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
while (pending.length) {
|
|
38
|
+
const current = pending.pop();
|
|
39
|
+
if (!(current instanceof Error) || seen.has(current))
|
|
40
|
+
continue;
|
|
41
|
+
seen.add(current);
|
|
42
|
+
// Keep boundary/policy failures primary, including combined failures.
|
|
43
|
+
if (current instanceof FsSafeError && current.code !== "helper-failed" && current.code !== "not-removable")
|
|
44
|
+
return;
|
|
45
|
+
const diagnostic = current;
|
|
46
|
+
if (current instanceof FsSafeError && (!details || current.details?.cleanup))
|
|
47
|
+
details = current.details ?? details;
|
|
48
|
+
if (diagnostic.code === "EMFILE" || diagnostic.code === "ENFILE")
|
|
49
|
+
code ??= diagnostic.code;
|
|
50
|
+
if (diagnostic.name === "SuppressedError")
|
|
51
|
+
pending.push(diagnostic.suppressed, diagnostic.error);
|
|
52
|
+
if (current instanceof AggregateError)
|
|
53
|
+
pending.push(...current.errors);
|
|
54
|
+
pending.push(current.cause);
|
|
55
|
+
}
|
|
56
|
+
if (!code)
|
|
57
|
+
return;
|
|
58
|
+
const cleanup = details?.cleanup;
|
|
59
|
+
const publication = details?.publication;
|
|
60
|
+
const preserved = cleanup?.status === "preserved" && publication?.status === "indeterminate";
|
|
61
|
+
return new FsSafeError("helper-failed", `filesystem write failed: too many open files (${code})${preserved ? "; publication outcome is indeterminate; staged file preserved" : ""}`, { cause: error, details });
|
|
62
|
+
}
|
|
32
63
|
export function normalizePinnedWriteError(error) {
|
|
64
|
+
const exhausted = descriptorExhaustion(error);
|
|
65
|
+
if (exhausted)
|
|
66
|
+
return exhausted;
|
|
33
67
|
if (error instanceof FsSafeError) {
|
|
34
68
|
return error;
|
|
35
69
|
}
|