@openclaw/signal 2026.11.3 → 2026.11.5-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/{accounts-B0Pba1-q.mjs → accounts-enJKL3ci.mjs} +4 -8
- package/dist/.setup/api-CrasQEKI.mjs +14 -0
- package/dist/.setup/{api-CbN3mye4.mjs → api-DljZsfUn.mjs} +9 -11
- package/dist/.setup/{approval-auth-DhUKkSkA.mjs → approval-auth-B_5Ytx8K.mjs} +7 -11
- package/dist/.setup/{approval-handler.runtime-CsEcBZoc.mjs → approval-handler.runtime-0RbVmTxx.mjs} +7 -8
- package/dist/.setup/{approval-reactions-JsqCEOHK.mjs → approval-reactions-CvqVSSx8.mjs} +16 -24
- package/dist/.setup/{channel-CM-rLLPN.mjs → channel-ByJj8fSh.mjs} +417 -372
- package/dist/.setup/{client-Duwqbgqi.mjs → client-CesmEjRc.mjs} +170 -186
- package/dist/.setup/{config-schema-DvxZ18SD.mjs → config-schema-BCmW0BLB.mjs} +2 -5
- package/dist/.setup/{doctor-contract-api-BIdVW0PF.mjs → doctor-contract-api-CcLwIhsD.mjs} +88 -95
- package/dist/.setup/{identity-B3JyPqGO.mjs → identity-vKtR3DrM.mjs} +39 -2
- package/dist/.setup/{install-signal-cli-hYVLYGXe.mjs → install-signal-cli-DvLDZfpP.mjs} +95 -109
- package/dist/.setup/{transport-url-CAC0JcuI.mjs → legacy-transport-D_cZC3bS.mjs} +18 -1
- package/dist/.setup/{monitor-C6u1JV2C.mjs → monitor-Cf5sihwl.mjs} +166 -239
- package/dist/.setup/{probe-D_93QCe0.mjs → probe-Caq5Y4kA.mjs} +3 -3
- package/dist/.setup/{send-CM7-016e.mjs → send-BJuOWLky.mjs} +34 -66
- package/dist/.setup/{setup-surface-DvKLwYrl.mjs → setup-surface-DQ1yFZmY.mjs} +3 -3
- package/dist/.setup/{transport-detection-CmlgOybe.mjs → transport-detection-DvR6X_UQ.mjs} +3 -3
- package/dist/.setup/{transport-policy-B6C_LogW.mjs → transport-policy-zIR3lRUj.mjs} +1 -1
- package/dist/.setup/transport-probes.runtime-CoH6UFtc.mjs +1 -0
- package/dist/.source-module-urls/b02c526df8b90083105b63e5b51d5c8bbfe02676df72f6be8dd8f825817b32b9.json +1 -0
- package/dist/api.js +9 -11
- package/dist/channel-config-api.js +1 -2
- package/dist/channel-plugin-api.js +1 -2
- package/dist/contract-api.js +3 -4
- package/dist/doctor-contract-api.js +1 -2
- package/dist/runtime-api.js +1 -2
- package/dist/setup-entry.js +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +94 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- 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 +3 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/file-open-error.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/file-open-error.js +14 -0
- package/node_modules/@openclaw/fs-safe/dist/file-sync.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/file-sync.js +6 -1
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +14 -3
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +9 -2
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +40 -32
- package/node_modules/@openclaw/fs-safe/dist/output.js +6 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +25 -2
- package/node_modules/@openclaw/fs-safe/dist/publication-handle.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/publication-handle.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +19 -9
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +23 -24
- package/node_modules/@openclaw/fs-safe/dist/publish-rename-fallback.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-rename-fallback.js +102 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +8 -5
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +19 -9
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +27 -13
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +18 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +68 -21
- package/node_modules/@openclaw/fs-safe/dist/watch-io-error.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io-error.js +27 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io-worker.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io-worker.js +93 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io.d.ts +47 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-io.js +236 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-probe.d.ts +16 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-probe.js +135 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +86 -24
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +78 -16
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +23 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/watch.js +60 -13
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +5 -2
- package/node_modules/@openclaw/fs-safe/docs/compiler-qualification.md +392 -0
- package/node_modules/@openclaw/fs-safe/docs/config.md +7 -1
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +25 -5
- package/node_modules/@openclaw/fs-safe/docs/copy.md +10 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +9 -3
- package/node_modules/@openclaw/fs-safe/docs/errors.md +13 -0
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -4
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +6 -4
- package/node_modules/@openclaw/fs-safe/docs/output.md +4 -2
- package/node_modules/@openclaw/fs-safe/docs/path.md +5 -2
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +15 -0
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +9 -0
- package/node_modules/@openclaw/fs-safe/docs/staged-file.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/testing.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +62 -5
- package/node_modules/@openclaw/fs-safe/docs/writing.md +6 -1
- package/node_modules/@openclaw/fs-safe/package.json +18 -16
- 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-freebsd-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-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-arm64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/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/node_modules/zod/package.json +1 -1
- package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/zod/v4/core/versions.js +1 -1
- package/openclaw.plugin.json +0 -30
- package/package.json +6 -8
- package/dist/.setup/api-CJBtD3lT.mjs +0 -16
- package/dist/.setup/client-adapter-Chnbch_z.mjs +0 -30
- package/dist/.setup/legacy-transport-QIpaMNST.mjs +0 -18
- package/dist/.setup/normalize-D7kSomXt.mjs +0 -39
- package/dist/.setup/reaction-runtime-api-mdO4k0yv.mjs +0 -65
- package/dist/.setup/reaction-targets-BgPLDHBf.mjs +0 -72
- package/dist/.setup/rpc-context-BAXcX7dI.mjs +0 -13
- package/dist/.setup/transport-probes.runtime-D-xIMRcn.mjs +0 -2
- package/dist/reaction-runtime-api.js +0 -2
- package/node_modules/ws/LICENSE +0 -20
- package/node_modules/ws/README.md +0 -548
- package/node_modules/ws/browser.js +0 -8
- package/node_modules/ws/index.js +0 -22
- package/node_modules/ws/lib/buffer-util.js +0 -131
- package/node_modules/ws/lib/constants.js +0 -19
- package/node_modules/ws/lib/event-target.js +0 -292
- package/node_modules/ws/lib/extension.js +0 -203
- package/node_modules/ws/lib/limiter.js +0 -55
- package/node_modules/ws/lib/permessage-deflate.js +0 -530
- package/node_modules/ws/lib/receiver.js +0 -743
- package/node_modules/ws/lib/sender.js +0 -607
- package/node_modules/ws/lib/stream.js +0 -161
- package/node_modules/ws/lib/subprotocol.js +0 -62
- package/node_modules/ws/lib/validation.js +0 -152
- package/node_modules/ws/lib/websocket-server.js +0 -562
- package/node_modules/ws/lib/websocket.js +0 -1421
- package/node_modules/ws/package.json +0 -74
- package/node_modules/ws/wrapper.mjs +0 -21
|
@@ -51,6 +51,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
53
|
import fs from "node:fs";
|
|
54
|
+
import { annotateReadOnlyOpenError } from "./file-open-error.js";
|
|
54
55
|
import { randomUUID } from "node:crypto";
|
|
55
56
|
import fsAsync from "node:fs/promises";
|
|
56
57
|
import path from "node:path";
|
|
@@ -487,6 +488,7 @@ export async function tryOpenCreateRootNative(params) {
|
|
|
487
488
|
catch (error) {
|
|
488
489
|
if (hasNodeErrorCode(error, "EEXIST") && typeof error === "object" && error !== null)
|
|
489
490
|
nativeCreateCollisions.add(error);
|
|
491
|
+
annotateReadOnlyOpenError(error, params.target);
|
|
490
492
|
throw error;
|
|
491
493
|
}
|
|
492
494
|
let rawOpen = true;
|
|
@@ -2,8 +2,10 @@ import { type RootContext } from "./root-context.js";
|
|
|
2
2
|
import { type RootDirectoryObservationGuard } from "./root-directory-list.js";
|
|
3
3
|
import { type ExactStatIdentity } from "./stat-observation.js";
|
|
4
4
|
import type { DirEntry } from "./types.js";
|
|
5
|
+
import type { RootEntryObservation } from "./root-directory-list-types.js";
|
|
5
6
|
/** One literal entry lookup under an operation-local admitted parent; never follow the leaf. */
|
|
6
|
-
export declare function lookupRootDirectoryEntry(root: RootContext, guard: RootDirectoryObservationGuard, name: string): Promise<{
|
|
7
|
+
export declare function lookupRootDirectoryEntry(root: RootContext, guard: RootDirectoryObservationGuard, name: string, exactMetadata?: boolean): Promise<{
|
|
7
8
|
entry: DirEntry;
|
|
8
9
|
identity: ExactStatIdentity;
|
|
10
|
+
observation?: RootEntryObservation;
|
|
9
11
|
} | undefined>;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { isNotFoundPathError } from "./path.js";
|
|
5
5
|
import { assertValidRootRelativePath } from "./root-context.js";
|
|
6
6
|
import { assertRootDirectoryObservationGuard, pathStatFromStats } from "./root-directory-list.js";
|
|
7
7
|
import { inspectStatObservationSync } from "./stat-observation.js";
|
|
8
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
8
9
|
/** One literal entry lookup under an operation-local admitted parent; never follow the leaf. */
|
|
9
|
-
export async function lookupRootDirectoryEntry(root, guard, name) {
|
|
10
|
+
export async function lookupRootDirectoryEntry(root, guard, name, exactMetadata = false) {
|
|
10
11
|
if (!name || name === "." || name === ".." || path.basename(name) !== name || path.isAbsolute(name)) {
|
|
11
12
|
throw new FsSafeError("invalid-path", "directory lookup requires one literal name");
|
|
12
13
|
}
|
|
@@ -14,10 +15,12 @@ export async function lookupRootDirectoryEntry(root, guard, name) {
|
|
|
14
15
|
await assertRootDirectoryObservationGuard(root, guard);
|
|
15
16
|
try {
|
|
16
17
|
const pathname = path.join(guard.realPath, name);
|
|
17
|
-
const
|
|
18
|
-
|
|
18
|
+
const exact = exactMetadata ? inspectFileIdentitySync(() => fs.lstatSync(pathname, { bigint: true })) : undefined;
|
|
19
|
+
const observed = exact ? { stat: exact, identity: { dev: exact.dev, ino: exact.ino } }
|
|
20
|
+
: inspectStatObservationSync(bigint => bigint ? fs.lstatSync(pathname, { bigint: true }) : fs.lstatSync(pathname));
|
|
19
21
|
await assertRootDirectoryObservationGuard(root, guard);
|
|
20
|
-
return { entry: pathStatFromStats(observed.stat, name), identity: observed.identity
|
|
22
|
+
return { entry: pathStatFromStats(observed.stat, name), identity: observed.identity,
|
|
23
|
+
...(exactMetadata ? { observation: observed.stat } : {}) };
|
|
21
24
|
}
|
|
22
25
|
catch (error) {
|
|
23
26
|
await assertRootDirectoryObservationGuard(root, guard);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { ExactStatIdentity } from "./stat-observation.js";
|
|
2
|
+
import type { BigIntStats } from "node:fs";
|
|
2
3
|
import type { DirEntry } from "./types.js";
|
|
3
4
|
/** Strings only: cached spellings never replace an observation or its identity fences. */
|
|
4
5
|
export type RootDirectoryListingPaths = {
|
|
5
6
|
directory: string;
|
|
6
7
|
names: Map<string, string>;
|
|
7
8
|
};
|
|
9
|
+
export type RootEntryObservation = Pick<BigIntStats, "dev" | "ino" | "mode" | "nlink" | "size" | "mtimeNs" | "ctimeNs">;
|
|
8
10
|
export type RootDirectoryListingResult = {
|
|
9
11
|
kind: "entry";
|
|
10
12
|
entry: DirEntry;
|
|
11
13
|
identity?: ExactStatIdentity;
|
|
14
|
+
observation?: RootEntryObservation;
|
|
12
15
|
} | {
|
|
13
16
|
kind: "limit";
|
|
14
17
|
name: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BigIntStats, Stats } from "node:fs";
|
|
2
2
|
import { type AsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
|
+
import { type NativeDirectoryObservationBackend } from "./native-directory-observation.js";
|
|
3
4
|
import { type RootContext } from "./root-context.js";
|
|
4
5
|
import type { RootPathDirectoryObservationGuard, RootPathObservationReceipt, RootPathParentObservationReceipt } from "./root-path.js";
|
|
5
6
|
import type { DirEntry, PathStat } from "./types.js";
|
|
@@ -8,7 +9,7 @@ export declare function pathStatFromStats(stat: Stats | BigIntStats): PathStat;
|
|
|
8
9
|
export declare function pathStatFromStats(stat: Stats | BigIntStats, name: string): DirEntry;
|
|
9
10
|
export type RootDirectoryObservationGuard = AsyncDirectoryGuard<BigIntStats>;
|
|
10
11
|
export declare function createRootDirectoryObservationGuard(root: RootContext, directory: string): Promise<RootDirectoryObservationGuard>;
|
|
11
|
-
export declare function assertRootDirectoryObservationGuard(root: RootContext, guard: RootDirectoryObservationGuard | RootPathDirectoryObservationGuard): Promise<void>;
|
|
12
|
+
export declare function assertRootDirectoryObservationGuard(root: RootContext, guard: RootDirectoryObservationGuard | RootPathDirectoryObservationGuard, backend?: NativeDirectoryObservationBackend): Promise<void>;
|
|
12
13
|
/**
|
|
13
14
|
* Close an operation-local traversal receipt with fresh exact observations.
|
|
14
15
|
* Descendant canonical paths are admitted while the receipt is created. The
|
|
@@ -6,14 +6,14 @@ import path from "node:path";
|
|
|
6
6
|
import { setImmediate as yieldToEventLoop } from "node:timers/promises";
|
|
7
7
|
import { assertAsyncDirectoryGuard, assertDirectoryObservationGuardSync, assertDirectoryObservationSync, createAsyncDirectoryGuard, extendDirectoryObservationGuard, } from "./directory-guard.js";
|
|
8
8
|
import { FsSafeError } from "./errors.js";
|
|
9
|
-
import { inspectNativeDirectoryObservation, isNativeDirectoryObservationGuard, } from "./native-directory-observation.js";
|
|
9
|
+
import { inspectNativeDirectoryObservation, getNativeDirectoryObservationBackend, isNativeDirectoryObservationGuard, } from "./native-directory-observation.js";
|
|
10
10
|
import { isNotFoundPathError } from "./path.js";
|
|
11
11
|
import { assertRootIdentityCurrent } from "./root-context.js";
|
|
12
12
|
import { errorCauseOptions, rootPathChangedError } from "./root-errors.js";
|
|
13
13
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
14
14
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
15
|
import { realpathSync } from "./realpath.js";
|
|
16
|
-
import {
|
|
16
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
17
17
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
18
18
|
const METADATA_BATCH_SIZE = 32;
|
|
19
19
|
function directoryEntryName(bytes) {
|
|
@@ -77,7 +77,7 @@ function directoryChangedError(error) {
|
|
|
77
77
|
return error;
|
|
78
78
|
return new FsSafeError("path-mismatch", "directory changed during operation", errorCauseOptions(error));
|
|
79
79
|
}
|
|
80
|
-
export async function assertRootDirectoryObservationGuard(root, guard) {
|
|
80
|
+
export async function assertRootDirectoryObservationGuard(root, guard, backend) {
|
|
81
81
|
if (isNativeDirectoryObservationGuard(guard)) {
|
|
82
82
|
throw new FsSafeError("path-mismatch", "native directory observation cannot be revalidated");
|
|
83
83
|
}
|
|
@@ -86,7 +86,9 @@ export async function assertRootDirectoryObservationGuard(root, guard) {
|
|
|
86
86
|
identity.dev === root.rootIdentity.dev && identity.ino === root.rootIdentity.ino;
|
|
87
87
|
if (guardPinsRoot) {
|
|
88
88
|
try {
|
|
89
|
-
if ("identity" in guard)
|
|
89
|
+
if (backend && "identity" in guard)
|
|
90
|
+
assertReceiptDirectoryGuardSync(root, guard, backend);
|
|
91
|
+
else if ("identity" in guard)
|
|
90
92
|
assertDirectoryObservationGuardSync(guard);
|
|
91
93
|
else
|
|
92
94
|
await assertAsyncDirectoryGuard(guard);
|
|
@@ -109,7 +111,9 @@ export async function assertRootDirectoryObservationGuard(root, guard) {
|
|
|
109
111
|
await assertRootIdentityCurrent(root);
|
|
110
112
|
}
|
|
111
113
|
try {
|
|
112
|
-
if ("identity" in guard)
|
|
114
|
+
if (backend && "identity" in guard)
|
|
115
|
+
assertReceiptDirectoryGuardSync(root, guard, backend);
|
|
116
|
+
else if ("identity" in guard)
|
|
113
117
|
assertDirectoryObservationGuardSync(guard);
|
|
114
118
|
else
|
|
115
119
|
await assertAsyncDirectoryGuard(guard);
|
|
@@ -263,12 +267,15 @@ export async function openRootDirectoryListing(root, directory, options, receipt
|
|
|
263
267
|
identity: { dev: admitted.stat.dev, ino: admitted.stat.ino },
|
|
264
268
|
}, admitted.dir, admitted.realPath);
|
|
265
269
|
}
|
|
270
|
+
// Windows watch listings keep every fence while sharing one handle for its
|
|
271
|
+
// identity/path queries. POSIX pathname checks are cheaper than this helper.
|
|
272
|
+
const backend = getNativeDirectoryObservationBackend(options.exactIdentity === true && process.platform === "win32");
|
|
266
273
|
const assertCurrent = async () => {
|
|
267
274
|
options.signal?.throwIfAborted();
|
|
268
275
|
if (receipt)
|
|
269
276
|
assertRootPathObservationReceiptCurrent(root, receipt);
|
|
270
277
|
else
|
|
271
|
-
await assertRootDirectoryObservationGuard(root, guard);
|
|
278
|
+
await assertRootDirectoryObservationGuard(root, guard, backend);
|
|
272
279
|
options.signal?.throwIfAborted();
|
|
273
280
|
};
|
|
274
281
|
const paths = { directory: guard.realPath, names: new Map() };
|
|
@@ -430,8 +437,10 @@ export async function openRootDirectoryListing(root, directory, options, receipt
|
|
|
430
437
|
paths.names.set(name, pathname);
|
|
431
438
|
let observed;
|
|
432
439
|
try {
|
|
433
|
-
|
|
434
|
-
?
|
|
440
|
+
const stat = options.exactIdentity
|
|
441
|
+
? inspectFileIdentitySync(() => fsSync.lstatSync(pathname, { bigint: true }))
|
|
442
|
+
: fsSync.lstatSync(pathname, { bigint: true });
|
|
443
|
+
observed = { stat, ...(options.exactIdentity ? { identity: { dev: stat.dev, ino: stat.ino } } : {}) };
|
|
435
444
|
}
|
|
436
445
|
catch (error) {
|
|
437
446
|
await assertCurrent();
|
|
@@ -440,7 +449,8 @@ export async function openRootDirectoryListing(root, directory, options, receipt
|
|
|
440
449
|
throw error;
|
|
441
450
|
}
|
|
442
451
|
await assertCurrent();
|
|
443
|
-
return { kind: "entry", entry: pathStatFromStats(observed.stat, name), identity: observed.identity
|
|
452
|
+
return { kind: "entry", entry: pathStatFromStats(observed.stat, name), identity: observed.identity,
|
|
453
|
+
...(options.exactIdentity ? { observation: observed.stat } : {}) };
|
|
444
454
|
}
|
|
445
455
|
}
|
|
446
456
|
catch (error) {
|
|
@@ -31,9 +31,9 @@ export function observeCanonicalRoot(ioFs, rootPath) {
|
|
|
31
31
|
const stat = inspectCanonicalRoot(ioFs, rootPath);
|
|
32
32
|
const identity = Object.freeze({ dev: stat.dev, ino: stat.ino });
|
|
33
33
|
let observedPath = rootPath;
|
|
34
|
-
if (
|
|
35
|
-
//
|
|
36
|
-
//
|
|
34
|
+
if (ioFs === fs) {
|
|
35
|
+
// Match native leaf resolution (including case and Windows short names)
|
|
36
|
+
// without replacing the already observed root identity.
|
|
37
37
|
observedPath = realpathSync.native(pathForWindowsFilesystem(rootPath));
|
|
38
38
|
if (observedPath !== rootPath)
|
|
39
39
|
inspectCanonicalRoot(ioFs, observedPath, identity);
|
|
@@ -8,5 +8,5 @@ export declare function rawPathRelativeToCanonicalRoot(candidate: string, rootCa
|
|
|
8
8
|
export declare function resolvePathViaExistingAncestor(targetPath: string): Promise<string>;
|
|
9
9
|
export declare function resolvePathViaExistingAncestorSync(targetPath: string): string;
|
|
10
10
|
export type RootPathMetadataMode = "native" | "ordinary";
|
|
11
|
-
export declare function resolveExistingAncestor(targetPath: string, mode: RootPathMetadataMode): string;
|
|
12
|
-
export declare function resolveSymlinkHopPath(symlinkPath: string, mode: RootPathMetadataMode, rejectUnresolved?: boolean): string;
|
|
11
|
+
export declare function resolveExistingAncestor(targetPath: string, mode: RootPathMetadataMode, preserveRawSegments?: boolean, remainingHops?: number): string;
|
|
12
|
+
export declare function resolveSymlinkHopPath(symlinkPath: string, mode: RootPathMetadataMode, rejectUnresolved?: boolean, remainingHops?: number): string;
|
|
@@ -104,14 +104,15 @@ function pathExistsSync(targetPath) {
|
|
|
104
104
|
export function resolvePathViaExistingAncestorSync(targetPath) {
|
|
105
105
|
return resolveExistingAncestor(targetPath, "ordinary");
|
|
106
106
|
}
|
|
107
|
-
export function resolveExistingAncestor(targetPath, mode) {
|
|
108
|
-
const exists = mode === "native" ? pathExists : pathExistsSync;
|
|
109
|
-
const canonicalize =
|
|
107
|
+
export function resolveExistingAncestor(targetPath, mode, preserveRawSegments = false, remainingHops = 64) {
|
|
108
|
+
const exists = mode === "native" || preserveRawSegments ? pathExists : pathExistsSync;
|
|
109
|
+
const canonicalize = realpathSync.native;
|
|
110
110
|
assertNoWindowsPathAlias(targetPath);
|
|
111
111
|
const normalized = resolvePathPreservingWindowsRoot(targetPath);
|
|
112
112
|
assertNoWindowsPathAlias(normalized);
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
const candidate = preserveRawSegments ? absolutePathWithRawSegments(targetPath) : normalized;
|
|
114
|
+
let cursor = candidate;
|
|
115
|
+
const filesystemRoot = path.parse(candidate).root;
|
|
115
116
|
while (cursor !== filesystemRoot &&
|
|
116
117
|
!exists(cursor)) {
|
|
117
118
|
cursor = path.dirname(cursor);
|
|
@@ -123,22 +124,30 @@ export function resolveExistingAncestor(targetPath, mode) {
|
|
|
123
124
|
try {
|
|
124
125
|
rawResolvedAncestor = canonicalize(pathForWindowsFilesystem(cursor));
|
|
125
126
|
}
|
|
126
|
-
catch {
|
|
127
|
-
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (!preserveRawSegments)
|
|
129
|
+
return normalized;
|
|
130
|
+
if (!isNotFoundPathError(error))
|
|
131
|
+
throw error;
|
|
132
|
+
rawResolvedAncestor = resolveSymlinkHopPath(cursor, mode, false, remainingHops);
|
|
128
133
|
}
|
|
129
134
|
assertNoWindowsPathAlias(rawResolvedAncestor);
|
|
130
135
|
const resolvedAncestor = resolvePathPreservingWindowsRoot(rawResolvedAncestor);
|
|
131
136
|
assertNoWindowsPathAlias(resolvedAncestor);
|
|
132
137
|
// Keep a leading suffix separator relative to the canonical ancestor.
|
|
133
|
-
const
|
|
138
|
+
const suffix = candidate.slice(cursor.length);
|
|
139
|
+
if (preserveRawSegments && suffix.split(path.sep).includes("..")) {
|
|
140
|
+
throw new FsSafeError("symlink", "symlink target has an unresolved parent component");
|
|
141
|
+
}
|
|
142
|
+
const resolved = cursor === candidate
|
|
134
143
|
? resolvedAncestor
|
|
135
|
-
: path.resolve(resolvedAncestor, `.${path.sep}${
|
|
144
|
+
: path.resolve(resolvedAncestor, `.${path.sep}${suffix}`);
|
|
136
145
|
assertNoWindowsPathAlias(resolved);
|
|
137
146
|
return resolved;
|
|
138
147
|
}
|
|
139
|
-
export function resolveSymlinkHopPath(symlinkPath, mode, rejectUnresolved) {
|
|
148
|
+
export function resolveSymlinkHopPath(symlinkPath, mode, rejectUnresolved, remainingHops = 64) {
|
|
140
149
|
try {
|
|
141
|
-
const canonicalize =
|
|
150
|
+
const canonicalize = realpathSync.native;
|
|
142
151
|
const rawRealPath = canonicalize(symlinkPath);
|
|
143
152
|
assertNoWindowsPathAlias(rawRealPath, "filesystem", "resolved symlink path uses a Windows filesystem namespace alias");
|
|
144
153
|
const realPath = resolvePathPreservingWindowsRoot(rawRealPath);
|
|
@@ -151,11 +160,16 @@ export function resolveSymlinkHopPath(symlinkPath, mode, rejectUnresolved) {
|
|
|
151
160
|
}
|
|
152
161
|
if (!isNotFoundPathError(error))
|
|
153
162
|
throw error;
|
|
163
|
+
if (remainingHops <= 0)
|
|
164
|
+
throw new FsSafeError("symlink", "too many unresolved symlink hops");
|
|
154
165
|
const linkTarget = fs.readlinkSync(symlinkPath);
|
|
155
166
|
assertNoWindowsPathAlias(linkTarget, "filesystem", "symlink target uses a Windows filesystem namespace alias");
|
|
156
167
|
const linkDir = resolvePathPreservingWindowsRoot(path.dirname(symlinkPath));
|
|
157
|
-
|
|
158
|
-
const
|
|
168
|
+
// Keep parent segments until the kernel has resolved the existing prefix.
|
|
169
|
+
const targetPath = path.isAbsolute(linkTarget) ? linkTarget
|
|
170
|
+
: path.parse(linkTarget).root ? resolvePathFromBasePreservingWindowsRoot(linkDir, linkTarget)
|
|
171
|
+
: `${linkDir}${path.sep}${linkTarget}`;
|
|
172
|
+
const resolved = resolveExistingAncestor(targetPath, mode, true, remainingHops - 1);
|
|
159
173
|
assertNoWindowsPathAlias(resolved, "filesystem", "resolved symlink path uses a Windows filesystem namespace alias");
|
|
160
174
|
return resolved;
|
|
161
175
|
}
|
|
@@ -255,8 +255,8 @@ function assertResolvedLinkDirectory(pathname, isLast) {
|
|
|
255
255
|
const stat = fs.statSync(pathForWindowsFilesystem(pathname));
|
|
256
256
|
assertDirectoryBeforeMoreSegments(stat, pathname, isLast);
|
|
257
257
|
}
|
|
258
|
-
// Both public APIs use synchronous metadata; only
|
|
259
|
-
// existence
|
|
258
|
+
// Both public APIs use kernel canonicalization and synchronous metadata; only
|
|
259
|
+
// ancestor existence differs. Promise wrappers preserve asynchronous rejection.
|
|
260
260
|
function traverseRootPath(params, { mode = "native", observation: observationOutput, observeRoot, removalReceipts }) {
|
|
261
261
|
const input = captureValidRootPathInputs(params);
|
|
262
262
|
const rawAbsolutePath = absolutePathWithRawSegments(input.absolutePath);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
import { type AsyncDirectoryGuard } from "./directory-guard.js";
|
|
1
3
|
export declare function resolveCallbackTempPath(workspaceDir: string, component: string): string;
|
|
2
4
|
export declare function writeCallbackSibling<T>(params: {
|
|
3
5
|
tempDir: string;
|
|
4
6
|
tempName: string;
|
|
7
|
+
rootBoundary?: AsyncDirectoryGuard<BigIntStats>;
|
|
5
8
|
write: (tempPath: string) => Promise<T>;
|
|
6
9
|
producerIsolation?: "private-directory";
|
|
7
10
|
resolveFinalPath: (result: T) => string;
|
|
@@ -13,6 +13,7 @@ import { cachedNoReplaceDevice, isNoReplaceUnavailable } from "./native-noreplac
|
|
|
13
13
|
import { handoffPrivateProducerFile, } from "./private-producer-handoff.js";
|
|
14
14
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
15
15
|
import { rootFromDirectoryGuard } from "./root-impl.js";
|
|
16
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
16
17
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
17
18
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
18
19
|
import { createOwnedTempFile } from "./temp-target.js";
|
|
@@ -160,10 +161,22 @@ export async function writeCallbackSibling(params) {
|
|
|
160
161
|
const maxBytes = params.maxBytes;
|
|
161
162
|
const syncTempFile = params.syncTempFile;
|
|
162
163
|
const syncParentDir = params.syncParentDir;
|
|
164
|
+
const rootBoundary = params.rootBoundary;
|
|
163
165
|
const guard = await createAsyncDirectoryGuard(parent, { bigint: true });
|
|
164
|
-
const assertParent = () =>
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
const assertParent = () => {
|
|
167
|
+
assertDirectoryIdentitySync(parent, {
|
|
168
|
+
dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath,
|
|
169
|
+
});
|
|
170
|
+
if (rootBoundary) {
|
|
171
|
+
assertDirectoryIdentitySync(rootBoundary.dir, {
|
|
172
|
+
dev: rootBoundary.stat.dev, ino: rootBoundary.stat.ino, realPath: rootBoundary.realPath,
|
|
173
|
+
});
|
|
174
|
+
if (!admitPathInsideRoot({
|
|
175
|
+
rootPath: rootBoundary.realPath, candidatePath: guard.realPath, rootIdentity: rootBoundary.stat,
|
|
176
|
+
}))
|
|
177
|
+
throw new FsSafeError("outside-workspace", "sibling parent resolves outside the root");
|
|
178
|
+
}
|
|
179
|
+
};
|
|
167
180
|
let handle;
|
|
168
181
|
let identity;
|
|
169
182
|
let unregister;
|
|
@@ -181,6 +194,8 @@ export async function writeCallbackSibling(params) {
|
|
|
181
194
|
}
|
|
182
195
|
};
|
|
183
196
|
try {
|
|
197
|
+
if (rootBoundary)
|
|
198
|
+
assertParent();
|
|
184
199
|
let result;
|
|
185
200
|
if (producerIsolation === "private-directory") {
|
|
186
201
|
const isolated = await writeIsolatedProducer({
|
|
@@ -9,7 +9,7 @@ export declare function selectedWatchChildren(scopes: readonly WatchScope[], dir
|
|
|
9
9
|
/** A folded directory can contain a missing scope component, unlike a nameless child. */
|
|
10
10
|
export declare function selectedWatchSubtree(scopes: readonly WatchScope[], directory: string): boolean;
|
|
11
11
|
export declare function nativeChanges(scopes: readonly WatchScope[], snapshot: WatchSnapshot | undefined, batch: NativeWatchBatch, limit?: number, after?: WatchSnapshot): WatchChange[] | undefined;
|
|
12
|
-
export declare function changedEntries(before: WatchSnapshot | undefined, after: WatchSnapshot, limit: number): WatchChange[] | undefined
|
|
12
|
+
export declare function changedEntries(before: WatchSnapshot | undefined, after: WatchSnapshot, limit: number): Promise<WatchChange[] | undefined>;
|
|
13
13
|
/** Backend names never establish authority to publish a pathname. */
|
|
14
14
|
export declare function guardedHintChanges(scopes: readonly WatchScope[], before: WatchSnapshot | undefined, after: WatchSnapshot, hints: readonly WatchChange[] | undefined, observed: readonly WatchChange[] | undefined, limit: number): WatchChange[] | undefined;
|
|
15
15
|
/** Resolve native spelling aliases without treating case folding as identity. */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
3
|
import { isNotFoundPathError } from "./path.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { lookupRootDirectoryEntry } from "./
|
|
4
|
+
import {} from "./root-context.js";
|
|
5
|
+
import {} from "./root-directory-list.js";
|
|
6
|
+
import { watchRootCurrent as assertRootIdentityCurrent, watchResolve as resolvePathInRoot, watchGuard as createRootDirectoryObservationGuard, watchAssert as assertRootDirectoryObservationGuard, watchLookup as lookupRootDirectoryEntry, watchYield } from "./watch-io.js";
|
|
7
7
|
function below(parent, child) {
|
|
8
8
|
return parent === "" ? child !== "" : child.startsWith(parent + path.sep);
|
|
9
9
|
}
|
|
@@ -86,7 +86,7 @@ export function nativeChanges(scopes, snapshot, batch, limit = 256, after) {
|
|
|
86
86
|
}
|
|
87
87
|
return [...result.values()];
|
|
88
88
|
}
|
|
89
|
-
export function changedEntries(before, after, limit) {
|
|
89
|
+
export async function changedEntries(before, after, limit) {
|
|
90
90
|
if (!before)
|
|
91
91
|
return undefined;
|
|
92
92
|
const changes = [];
|
|
@@ -102,12 +102,17 @@ export function changedEntries(before, after, limit) {
|
|
|
102
102
|
changes.push(Object.freeze({ path: name, type: sameFile ? "content" : "structural" }));
|
|
103
103
|
return true;
|
|
104
104
|
};
|
|
105
|
-
|
|
105
|
+
const checkpoint = watchYield();
|
|
106
|
+
for (const name of before.entries.keys()) {
|
|
107
|
+
await checkpoint();
|
|
106
108
|
if (!compare(name))
|
|
107
109
|
return undefined;
|
|
108
|
-
|
|
110
|
+
}
|
|
111
|
+
for (const name of after.entries.keys()) {
|
|
112
|
+
await checkpoint();
|
|
109
113
|
if (!before.entries.has(name) && !compare(name))
|
|
110
114
|
return undefined;
|
|
115
|
+
}
|
|
111
116
|
return changes;
|
|
112
117
|
}
|
|
113
118
|
/** Backend names never establish authority to publish a pathname. */
|
|
@@ -116,14 +121,19 @@ export function guardedHintChanges(scopes, before, after, hints, observed, limit
|
|
|
116
121
|
return undefined;
|
|
117
122
|
const result = new Map(observed.map(change => [change.path, change]));
|
|
118
123
|
for (const hint of hints) {
|
|
119
|
-
//
|
|
120
|
-
//
|
|
124
|
+
// A consumer can observe a transient child between our scans. Invalidate
|
|
125
|
+
// its admitted parent without publishing an unobserved backend spelling.
|
|
121
126
|
if (!before?.entries.has(hint.path) && !after.entries.has(hint.path) && !scopes.some(scope => scope.path === hint.path)) {
|
|
122
127
|
const parent = path.dirname(hint.path);
|
|
123
128
|
const directory = parent === "." ? "" : parent;
|
|
124
129
|
const left = before?.directories.get(directory), right = after.directories.get(directory);
|
|
125
|
-
if (left && right && left.dev === right.dev && left.ino === right.ino)
|
|
130
|
+
if (left && right && left.dev === right.dev && left.ino === right.ino) {
|
|
131
|
+
for (const change of scopedChanges(scopes, { path: directory, type: "structural" })) {
|
|
132
|
+
if (!addChange(result, change, limit))
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
126
135
|
continue;
|
|
136
|
+
}
|
|
127
137
|
return undefined;
|
|
128
138
|
}
|
|
129
139
|
// Equal snapshots cannot exclude an intermediate change and restoration (ABA).
|
|
@@ -140,11 +150,18 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
|
|
|
140
150
|
return undefined;
|
|
141
151
|
if (scheduling && !batch.hints.length)
|
|
142
152
|
return undefined;
|
|
153
|
+
const checkpoint = watchYield();
|
|
143
154
|
const result = new Map();
|
|
144
|
-
const candidates = new Map(
|
|
155
|
+
const candidates = new Map();
|
|
156
|
+
for (const entries of [before?.targets, after.targets, after.directories])
|
|
157
|
+
for (const [name, identity] of entries ?? []) {
|
|
158
|
+
candidates.set(name, identity);
|
|
159
|
+
await checkpoint();
|
|
160
|
+
}
|
|
145
161
|
if (scheduling) {
|
|
146
162
|
const identities = new Map();
|
|
147
163
|
for (const { dev, ino } of candidates.values()) {
|
|
164
|
+
await checkpoint();
|
|
148
165
|
let inodes = identities.get(dev);
|
|
149
166
|
if (!inodes)
|
|
150
167
|
identities.set(dev, inodes = new Set());
|
|
@@ -173,9 +190,11 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
|
|
|
173
190
|
};
|
|
174
191
|
if (scheduling) {
|
|
175
192
|
// A sibling hint cannot hide a replaced scope anchor or its identity chain.
|
|
176
|
-
for (const name of after.directories.keys())
|
|
193
|
+
for (const name of after.directories.keys()) {
|
|
194
|
+
await checkpoint();
|
|
177
195
|
if (scopes.some(scope => !name || scope.path === name || scope.path.startsWith(name + path.sep)))
|
|
178
196
|
await admittedParent(name);
|
|
197
|
+
}
|
|
179
198
|
for (const scope of scopes) {
|
|
180
199
|
const anchor = after.scopeAnchors?.get(scope.path);
|
|
181
200
|
if (!anchor)
|
|
@@ -206,6 +225,7 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
|
|
|
206
225
|
return undefined;
|
|
207
226
|
const guard = await admittedParent(hint.directory);
|
|
208
227
|
for (const [name, identity] of candidates) {
|
|
228
|
+
await checkpoint();
|
|
209
229
|
if (identity.dev === guard.stat.dev && identity.ino === guard.stat.ino && selectedWatchSubtree(scopes, name))
|
|
210
230
|
return undefined;
|
|
211
231
|
}
|
|
@@ -225,22 +245,48 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
|
|
|
225
245
|
// Native recursion observes one Root handle and may report descendants of
|
|
226
246
|
// unselected/entry-only directories. Admit a parent alias by exact identity,
|
|
227
247
|
// not by lowercasing, and do not turn unselected descendants into events.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
248
|
+
for (let attempt = 0;; attempt++) {
|
|
249
|
+
const retained = guards.has(parent);
|
|
250
|
+
try {
|
|
251
|
+
guard = await admittedParent(parent);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
await assertRootIdentityCurrent(root);
|
|
256
|
+
// An earlier hint may already depend on this selected-alias guard.
|
|
257
|
+
if (scheduling || retained)
|
|
258
|
+
throw error;
|
|
259
|
+
guards.delete(parent);
|
|
260
|
+
if (isNotFoundPathError(error) || (error instanceof FsSafeError && ["not-found", "path-alias", "outside-workspace", "symlink", "not-file"].includes(error.code)))
|
|
261
|
+
break;
|
|
262
|
+
// A yielded admission may race an ordinary sibling deletion. Fresh
|
|
263
|
+
// admission can establish absence; persistent ambiguity still fails.
|
|
264
|
+
// Windows also reports EPERM while an unrelated directory is delete-pending.
|
|
265
|
+
const failure = error;
|
|
266
|
+
const failedRealpath = failure?.syscall === "realpath" &&
|
|
267
|
+
(failure.code === "EBADF" || failure.code === "EPERM");
|
|
268
|
+
if (attempt < 2 && (failedRealpath || error instanceof FsSafeError && error.code === "path-mismatch"))
|
|
269
|
+
continue;
|
|
234
270
|
throw error;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!guard)
|
|
274
|
+
continue;
|
|
275
|
+
let admitted;
|
|
276
|
+
for (const entry of after.directories) {
|
|
277
|
+
await checkpoint();
|
|
278
|
+
if (entry[1].dev === guard.stat.dev && entry[1].ino === guard.stat.ino) {
|
|
279
|
+
admitted = entry;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
238
282
|
}
|
|
239
|
-
const admitted = [...after.directories].find(([, identity]) => identity.dev === guard.stat.dev && identity.ino === guard.stat.ino);
|
|
240
283
|
if (!admitted) {
|
|
241
284
|
// A newly created directory may contain selected entries absent from the baseline.
|
|
242
285
|
if (scheduling)
|
|
243
286
|
return undefined;
|
|
287
|
+
// The guarded observation proved this parent is not a selected alias.
|
|
288
|
+
// No result depends on it; do not extend its lifetime through sibling churn.
|
|
289
|
+
guards.delete(hint.directory);
|
|
244
290
|
continue;
|
|
245
291
|
}
|
|
246
292
|
[parent, expected] = admitted;
|
|
@@ -288,6 +334,7 @@ export async function admittedNativeChanges(root, scopes, before, after, batch,
|
|
|
288
334
|
if (scheduling && found.entry.isSymbolicLink)
|
|
289
335
|
return undefined;
|
|
290
336
|
for (const [relative, identity] of candidates) {
|
|
337
|
+
await checkpoint();
|
|
291
338
|
if (!relative || identity.dev !== found.identity.dev || identity.ino !== found.identity.ino)
|
|
292
339
|
continue;
|
|
293
340
|
if ((path.dirname(relative) === "." ? "" : path.dirname(relative)) !== parent) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
3
|
+
export function encodeWatchError(error) {
|
|
4
|
+
if (!(error instanceof Error))
|
|
5
|
+
return error;
|
|
6
|
+
const combined = error;
|
|
7
|
+
const syscall = error.syscall;
|
|
8
|
+
return {
|
|
9
|
+
name: error.name, message: error.message, code: error.code,
|
|
10
|
+
...(typeof syscall === "string" ? { syscall } : {}),
|
|
11
|
+
...(error instanceof FsSafeError ? { details: error.details } : {}),
|
|
12
|
+
cause: error.cause === undefined ? undefined : encodeWatchError(error.cause),
|
|
13
|
+
...(error.name === "SuppressedError" ? { error: encodeWatchError(combined.error), suppressed: encodeWatchError(combined.suppressed) } : {}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function decodeWatchError(value) {
|
|
17
|
+
if (!value || typeof value !== "object" || !("message" in value) || typeof value.message !== "string")
|
|
18
|
+
return value;
|
|
19
|
+
const error = value;
|
|
20
|
+
if (error.name === "SuppressedError")
|
|
21
|
+
return createSuppressedError(decodeWatchError(error.error), decodeWatchError(error.suppressed), error.message);
|
|
22
|
+
const cause = error.cause === undefined ? undefined : decodeWatchError(error.cause);
|
|
23
|
+
if (error.name === "FsSafeError")
|
|
24
|
+
return new FsSafeError(error.code, error.message, { cause, details: error.details });
|
|
25
|
+
return Object.assign(new Error(error.message, { cause }), { name: error.name, code: error.code,
|
|
26
|
+
...(typeof error.syscall === "string" ? { syscall: error.syscall } : {}) });
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|