@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
|
@@ -39,7 +39,7 @@ function buildEntry(params) {
|
|
|
39
39
|
path: fullPath,
|
|
40
40
|
relativePath: params.relativePath,
|
|
41
41
|
depth: params.depth,
|
|
42
|
-
kind: params.kind
|
|
42
|
+
kind: params.kind,
|
|
43
43
|
dirent: params.dirent,
|
|
44
44
|
};
|
|
45
45
|
}
|
|
@@ -101,35 +101,62 @@ export function walkDirectorySync(rootDir, options = {}) {
|
|
|
101
101
|
return;
|
|
102
102
|
visitedDirs.add(realDir);
|
|
103
103
|
let entries;
|
|
104
|
+
let handle;
|
|
104
105
|
try {
|
|
105
|
-
|
|
106
|
+
if (options.maxEntries === undefined) {
|
|
107
|
+
entries = fsSync.readdirSync(operationPath, { withFileTypes: true });
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
handle = fsSync.opendirSync(operationPath);
|
|
111
|
+
}
|
|
106
112
|
}
|
|
107
113
|
catch (error) {
|
|
108
114
|
recordFailedDir(result, root, dir, depth, error);
|
|
109
115
|
return;
|
|
110
116
|
}
|
|
111
117
|
const childPrefix = dir.endsWith(path.sep) ? dir : `${dir}${path.sep}`;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
let index = 0;
|
|
119
|
+
try {
|
|
120
|
+
while (true) {
|
|
121
|
+
let dirent;
|
|
122
|
+
try {
|
|
123
|
+
dirent = handle ? handle.readSync() : entries[index++];
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
recordFailedDir(result, root, dir, depth, error);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!dirent)
|
|
130
|
+
return;
|
|
131
|
+
if (shouldStop(result, options)) {
|
|
132
|
+
result.truncated = true;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
result.scannedEntryCount += 1;
|
|
136
|
+
const fullPath = childPrefix + dirent.name;
|
|
137
|
+
const kind = resolveKind(fullPath, dirent, symlinks);
|
|
138
|
+
if (!kind)
|
|
139
|
+
continue;
|
|
140
|
+
const relativePath = relativeDir ? `${relativeDir}${path.sep}${dirent.name}` : dirent.name;
|
|
141
|
+
const entry = buildEntry({ relativePath, fullPath, dirent, depth, kind });
|
|
142
|
+
if (options.include?.(entry) ?? true) {
|
|
143
|
+
result.entries.push(entry);
|
|
144
|
+
}
|
|
145
|
+
if (kind === "directory" &&
|
|
146
|
+
(options.maxDepth === undefined || depth < options.maxDepth) &&
|
|
147
|
+
(options.descend?.(entry) ?? true)) {
|
|
148
|
+
visit(fullPath, relativePath, depth + 1);
|
|
149
|
+
if (result.truncated)
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
116
152
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
continue;
|
|
122
|
-
const relativePath = relativeDir ? `${relativeDir}${path.sep}${dirent.name}` : dirent.name;
|
|
123
|
-
const entry = buildEntry({ relativePath, fullPath, dirent, depth, kind });
|
|
124
|
-
if (options.include?.(entry) ?? true) {
|
|
125
|
-
result.entries.push(entry);
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
try {
|
|
156
|
+
handle?.closeSync();
|
|
126
157
|
}
|
|
127
|
-
|
|
128
|
-
(
|
|
129
|
-
(options.descend?.(entry) ?? true)) {
|
|
130
|
-
visit(fullPath, relativePath, depth + 1);
|
|
131
|
-
if (result.truncated)
|
|
132
|
-
return;
|
|
158
|
+
catch (error) {
|
|
159
|
+
recordFailedDir(result, root, dir, depth, error);
|
|
133
160
|
}
|
|
134
161
|
}
|
|
135
162
|
}
|
|
@@ -165,40 +192,67 @@ export async function walkDirectory(rootDir, options = {}) {
|
|
|
165
192
|
return;
|
|
166
193
|
visitedDirs.add(realDir);
|
|
167
194
|
let entries;
|
|
195
|
+
let handle;
|
|
168
196
|
try {
|
|
169
|
-
|
|
197
|
+
if (options.maxEntries === undefined) {
|
|
198
|
+
entries = await fs.readdir(operationPath, { withFileTypes: true });
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
handle = await fs.opendir(operationPath);
|
|
202
|
+
}
|
|
170
203
|
}
|
|
171
204
|
catch (error) {
|
|
172
205
|
recordFailedDir(result, root, dir, depth, error);
|
|
173
206
|
return;
|
|
174
207
|
}
|
|
175
208
|
const childPrefix = dir.endsWith(path.sep) ? dir : `${dir}${path.sep}`;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
209
|
+
let index = 0;
|
|
210
|
+
try {
|
|
211
|
+
while (true) {
|
|
212
|
+
let dirent;
|
|
213
|
+
try {
|
|
214
|
+
dirent = handle ? await handle.read() : entries[index++];
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
recordFailedDir(result, root, dir, depth, error);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!dirent)
|
|
221
|
+
return;
|
|
222
|
+
if (shouldStop(result, options)) {
|
|
223
|
+
result.truncated = true;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
result.scannedEntryCount += 1;
|
|
227
|
+
const fullPath = childPrefix + dirent.name;
|
|
228
|
+
const kind = resolveKind(fullPath, dirent, symlinks);
|
|
229
|
+
if (!kind)
|
|
230
|
+
continue;
|
|
231
|
+
const relativePath = relativeDir ? `${relativeDir}${path.sep}${dirent.name}` : dirent.name;
|
|
232
|
+
const entry = buildEntry({ relativePath, fullPath, dirent, depth, kind });
|
|
233
|
+
const include = options.include;
|
|
234
|
+
const included = include == null ? true : Reflect.apply(include, options, [entry]);
|
|
235
|
+
if ((isObjectResult(included) ? await included : included) ?? true) {
|
|
236
|
+
result.entries.push(entry);
|
|
237
|
+
}
|
|
238
|
+
if (kind === "directory" &&
|
|
239
|
+
(options.maxDepth === undefined || depth < options.maxDepth)) {
|
|
240
|
+
const descend = options.descend;
|
|
241
|
+
const descended = descend == null ? true : Reflect.apply(descend, options, [entry]);
|
|
242
|
+
if ((isObjectResult(descended) ? await descended : descended) ?? true) {
|
|
243
|
+
await visit(fullPath, relativePath, depth + 1);
|
|
244
|
+
if (result.truncated)
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
180
248
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
continue;
|
|
186
|
-
const relativePath = relativeDir ? `${relativeDir}${path.sep}${dirent.name}` : dirent.name;
|
|
187
|
-
const entry = buildEntry({ relativePath, fullPath, dirent, depth, kind });
|
|
188
|
-
const include = options.include;
|
|
189
|
-
const included = include == null ? true : Reflect.apply(include, options, [entry]);
|
|
190
|
-
if ((isObjectResult(included) ? await included : included) ?? true) {
|
|
191
|
-
result.entries.push(entry);
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
try {
|
|
252
|
+
await handle?.close();
|
|
192
253
|
}
|
|
193
|
-
|
|
194
|
-
(
|
|
195
|
-
const descend = options.descend;
|
|
196
|
-
const descended = descend == null ? true : Reflect.apply(descend, options, [entry]);
|
|
197
|
-
if ((isObjectResult(descended) ? await descended : descended) ?? true) {
|
|
198
|
-
await visit(fullPath, relativePath, depth + 1);
|
|
199
|
-
if (result.truncated)
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
recordFailedDir(result, root, dir, depth, error);
|
|
202
256
|
}
|
|
203
257
|
}
|
|
204
258
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import { type RootContext } from "./root-context.js";
|
|
1
2
|
import type { NativeWatchBatch } from "./watch-native.js";
|
|
2
3
|
import type { WatchSnapshot } from "./watch-scan.js";
|
|
3
4
|
import type { WatchChange, WatchScope } from "./watch-types.js";
|
|
4
5
|
export declare function excludedWatchPath(snapshot: WatchSnapshot | undefined, name: string): boolean;
|
|
5
6
|
export declare function scopedChanges(scopes: readonly WatchScope[], change: WatchChange): WatchChange[];
|
|
7
|
+
/** An undecodable child cannot equal a validated literal scope component. */
|
|
8
|
+
export declare function selectedWatchChildren(scopes: readonly WatchScope[], directory: string): boolean;
|
|
9
|
+
/** A folded directory can contain a missing scope component, unlike a nameless child. */
|
|
10
|
+
export declare function selectedWatchSubtree(scopes: readonly WatchScope[], directory: string): boolean;
|
|
6
11
|
export declare function nativeChanges(scopes: readonly WatchScope[], snapshot: WatchSnapshot | undefined, batch: NativeWatchBatch, limit?: number, after?: WatchSnapshot): WatchChange[] | undefined;
|
|
7
12
|
export declare function changedEntries(before: WatchSnapshot | undefined, after: WatchSnapshot, limit: number): WatchChange[] | undefined;
|
|
8
13
|
/** Backend names never establish authority to publish a pathname. */
|
|
9
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
|
+
/** Resolve native spelling aliases without treating case folding as identity. */
|
|
16
|
+
export declare function admittedNativeChanges(root: RootContext, scopes: readonly WatchScope[], before: WatchSnapshot | undefined, after: WatchSnapshot, batch: NativeWatchBatch, signal: AbortSignal, limit: number, scheduling?: boolean): Promise<WatchChange[] | undefined>;
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { isNotFoundPathError } from "./path.js";
|
|
4
|
+
import { assertRootIdentityCurrent, resolvePathInRoot } from "./root-context.js";
|
|
5
|
+
import { createRootDirectoryObservationGuard, assertRootDirectoryObservationGuard } from "./root-directory-list.js";
|
|
6
|
+
import { lookupRootDirectoryEntry } from "./root-directory-entry.js";
|
|
2
7
|
function below(parent, child) {
|
|
3
8
|
return parent === "" ? child !== "" : child.startsWith(parent + path.sep);
|
|
4
9
|
}
|
|
5
|
-
function
|
|
6
|
-
return
|
|
10
|
+
function selectedByTree(scope, name, childDepth = 0) {
|
|
11
|
+
return scope.kind === "tree" && (scope.path === name ? childDepth <= scope.depth : below(scope.path, name) &&
|
|
12
|
+
(scope.path === "" ? name : name.slice(scope.path.length + 1)).split(path.sep).length + childDepth <= scope.depth);
|
|
13
|
+
}
|
|
14
|
+
function addChange(result, change, limit) {
|
|
15
|
+
if (!result.has(change.path) && result.size >= limit)
|
|
16
|
+
return false;
|
|
17
|
+
const prior = result.get(change.path);
|
|
18
|
+
result.set(change.path, prior?.type === "structural" ? prior : change);
|
|
19
|
+
return true;
|
|
7
20
|
}
|
|
8
21
|
export function excludedWatchPath(snapshot, name) {
|
|
9
22
|
const exclusions = snapshot?.excluded;
|
|
@@ -20,7 +33,7 @@ export function excludedWatchPath(snapshot, name) {
|
|
|
20
33
|
export function scopedChanges(scopes, change) {
|
|
21
34
|
const result = new Map();
|
|
22
35
|
for (const scope of scopes) {
|
|
23
|
-
if (scope.path === change.path || (scope
|
|
36
|
+
if (scope.path === change.path || selectedByTree(scope, change.path)) {
|
|
24
37
|
result.set(change.path, change);
|
|
25
38
|
}
|
|
26
39
|
else if (below(change.path, scope.path)) {
|
|
@@ -30,6 +43,19 @@ export function scopedChanges(scopes, change) {
|
|
|
30
43
|
}
|
|
31
44
|
return [...result.values()];
|
|
32
45
|
}
|
|
46
|
+
/** An undecodable child cannot equal a validated literal scope component. */
|
|
47
|
+
export function selectedWatchChildren(scopes, directory) {
|
|
48
|
+
return scopes.some(scope => selectedByTree(scope, directory, 1));
|
|
49
|
+
}
|
|
50
|
+
/** A folded directory can contain a missing scope component, unlike a nameless child. */
|
|
51
|
+
export function selectedWatchSubtree(scopes, directory) {
|
|
52
|
+
return scopes.some(scope => scope.path === directory || below(directory, scope.path) ||
|
|
53
|
+
selectedByTree(scope, directory));
|
|
54
|
+
}
|
|
55
|
+
function literalName(name) {
|
|
56
|
+
return typeof name === "string" && !!name && name !== "." && name !== ".." && !name.includes("\0") && !name.includes("/") &&
|
|
57
|
+
(process.platform !== "win32" || !/[\\:]/.test(name));
|
|
58
|
+
}
|
|
33
59
|
export function nativeChanges(scopes, snapshot, batch, limit = 256, after) {
|
|
34
60
|
if (batch.overflow)
|
|
35
61
|
return undefined;
|
|
@@ -37,19 +63,25 @@ export function nativeChanges(scopes, snapshot, batch, limit = 256, after) {
|
|
|
37
63
|
for (const hint of batch.hints) {
|
|
38
64
|
const name = hint.name;
|
|
39
65
|
// Backend filenames are untrusted hints. Never resolve or perform I/O on them.
|
|
40
|
-
if (typeof
|
|
66
|
+
if (typeof hint.directory !== "string" || (hint.directory && !hint.directory.split(path.sep).every(literalName)))
|
|
41
67
|
return undefined;
|
|
42
|
-
const
|
|
68
|
+
const children = hint.event === "children";
|
|
69
|
+
const subtree = hint.event === "subtree";
|
|
70
|
+
if (children || subtree ? name !== "" : !literalName(name))
|
|
71
|
+
return undefined;
|
|
72
|
+
if (subtree)
|
|
73
|
+
continue; // Folded hints only schedule guarded diff passes; never publish their spelling.
|
|
74
|
+
if (children && !selectedWatchChildren(scopes, hint.directory))
|
|
75
|
+
continue;
|
|
76
|
+
const relative = children ? hint.directory : hint.directory ? path.join(hint.directory, name) : name;
|
|
43
77
|
if (excludedWatchPath(snapshot, relative) || excludedWatchPath(after, relative))
|
|
44
78
|
continue;
|
|
45
79
|
for (const change of scopedChanges(scopes, {
|
|
46
80
|
path: relative,
|
|
47
81
|
type: hint.event === "change" && snapshot?.entries.get(relative)?.startsWith("file:") ? "content" : "structural",
|
|
48
82
|
})) {
|
|
49
|
-
if (!
|
|
83
|
+
if (!addChange(result, change, limit))
|
|
50
84
|
return undefined;
|
|
51
|
-
const prior = result.get(change.path);
|
|
52
|
-
result.set(change.path, prior?.type === "structural" ? prior : change);
|
|
53
85
|
}
|
|
54
86
|
}
|
|
55
87
|
return [...result.values()];
|
|
@@ -58,17 +90,24 @@ export function changedEntries(before, after, limit) {
|
|
|
58
90
|
if (!before)
|
|
59
91
|
return undefined;
|
|
60
92
|
const changes = [];
|
|
61
|
-
|
|
93
|
+
const compare = (name) => {
|
|
62
94
|
const left = before.entries.get(name);
|
|
63
95
|
const right = after.entries.get(name);
|
|
64
96
|
if (left === right)
|
|
65
|
-
|
|
97
|
+
return true;
|
|
66
98
|
if (changes.length >= limit)
|
|
67
|
-
return
|
|
99
|
+
return false;
|
|
68
100
|
const sameFile = left?.startsWith("file:") && right?.startsWith("file:") &&
|
|
69
101
|
left.split(":").slice(0, 3).join(":") === right.split(":").slice(0, 3).join(":");
|
|
70
102
|
changes.push(Object.freeze({ path: name, type: sameFile ? "content" : "structural" }));
|
|
71
|
-
|
|
103
|
+
return true;
|
|
104
|
+
};
|
|
105
|
+
for (const name of before.entries.keys())
|
|
106
|
+
if (!compare(name))
|
|
107
|
+
return undefined;
|
|
108
|
+
for (const name of after.entries.keys())
|
|
109
|
+
if (!before.entries.has(name) && !compare(name))
|
|
110
|
+
return undefined;
|
|
72
111
|
return changes;
|
|
73
112
|
}
|
|
74
113
|
/** Backend names never establish authority to publish a pathname. */
|
|
@@ -77,17 +116,194 @@ export function guardedHintChanges(scopes, before, after, hints, observed, limit
|
|
|
77
116
|
return undefined;
|
|
78
117
|
const result = new Map(observed.map(change => [change.path, change]));
|
|
79
118
|
for (const hint of hints) {
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
119
|
+
// Drop unobserved non-target names only under a parent with the same guarded
|
|
120
|
+
// identity in both passes; otherwise a stale/misdirected watch erases detail.
|
|
121
|
+
if (!before?.entries.has(hint.path) && !after.entries.has(hint.path) && !scopes.some(scope => scope.path === hint.path)) {
|
|
122
|
+
const parent = path.dirname(hint.path);
|
|
123
|
+
const directory = parent === "." ? "" : parent;
|
|
124
|
+
const left = before?.directories.get(directory), right = after.directories.get(directory);
|
|
125
|
+
if (left && right && left.dev === right.dev && left.ino === right.ino)
|
|
126
|
+
continue;
|
|
84
127
|
return undefined;
|
|
128
|
+
}
|
|
85
129
|
// Equal snapshots cannot exclude an intermediate change and restoration (ABA).
|
|
86
130
|
// Preserve admitted hints, including setup activity delivered after ready.
|
|
87
|
-
if (!
|
|
131
|
+
if (!addChange(result, hint, limit))
|
|
88
132
|
return undefined;
|
|
89
|
-
|
|
90
|
-
|
|
133
|
+
Object.freeze(result.get(hint.path));
|
|
134
|
+
}
|
|
135
|
+
return [...result.values()];
|
|
136
|
+
}
|
|
137
|
+
/** Resolve native spelling aliases without treating case folding as identity. */
|
|
138
|
+
export async function admittedNativeChanges(root, scopes, before, after, batch, signal, limit, scheduling = false) {
|
|
139
|
+
if (!nativeChanges(scopes, before, batch, limit, after))
|
|
140
|
+
return undefined;
|
|
141
|
+
if (scheduling && !batch.hints.length)
|
|
142
|
+
return undefined;
|
|
143
|
+
const result = new Map();
|
|
144
|
+
const candidates = new Map([...before?.targets ?? [], ...after.targets, ...after.directories]);
|
|
145
|
+
if (scheduling) {
|
|
146
|
+
const identities = new Map();
|
|
147
|
+
for (const { dev, ino } of candidates.values()) {
|
|
148
|
+
let inodes = identities.get(dev);
|
|
149
|
+
if (!inodes)
|
|
150
|
+
identities.set(dev, inodes = new Set());
|
|
151
|
+
// One spelling cannot discard activity selected through another spelling,
|
|
152
|
+
// including entry targets that alias a tree's directory without registering it.
|
|
153
|
+
if (inodes.has(ino))
|
|
154
|
+
return undefined;
|
|
155
|
+
inodes.add(ino);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const guards = new Map();
|
|
159
|
+
const admittedParent = async (parent) => {
|
|
160
|
+
let guard = guards.get(parent);
|
|
161
|
+
if (!guard) {
|
|
162
|
+
const resolved = await resolvePathInRoot(root, parent ? "./" + parent : ".", { rejectSymlinks: true });
|
|
163
|
+
guard = await createRootDirectoryObservationGuard(root, resolved.resolved);
|
|
164
|
+
const expected = after.directories.get(parent);
|
|
165
|
+
if (expected && (guard.stat.dev !== expected.dev || guard.stat.ino !== expected.ino)) {
|
|
166
|
+
throw new FsSafeError("path-mismatch", "watch hint parent changed during reconciliation");
|
|
167
|
+
}
|
|
168
|
+
guards.set(parent, guard);
|
|
169
|
+
}
|
|
170
|
+
await assertRootDirectoryObservationGuard(root, guard);
|
|
171
|
+
signal.throwIfAborted();
|
|
172
|
+
return guard;
|
|
173
|
+
};
|
|
174
|
+
if (scheduling) {
|
|
175
|
+
// A sibling hint cannot hide a replaced scope anchor or its identity chain.
|
|
176
|
+
for (const name of after.directories.keys())
|
|
177
|
+
if (scopes.some(scope => !name || scope.path === name || scope.path.startsWith(name + path.sep)))
|
|
178
|
+
await admittedParent(name);
|
|
179
|
+
for (const scope of scopes) {
|
|
180
|
+
const anchor = after.scopeAnchors?.get(scope.path);
|
|
181
|
+
if (!anchor)
|
|
182
|
+
return undefined;
|
|
183
|
+
if (!anchor.name)
|
|
184
|
+
continue;
|
|
185
|
+
const guard = await admittedParent(anchor.directory);
|
|
186
|
+
const found = await lookupRootDirectoryEntry(root, guard, anchor.name);
|
|
187
|
+
signal.throwIfAborted();
|
|
188
|
+
const expected = anchor.target;
|
|
189
|
+
// Missing targets and vanished/replaced spelling aliases have no hint inode
|
|
190
|
+
// to match. Recheck the selected spelling before declaring a batch unrelated.
|
|
191
|
+
if (!found) {
|
|
192
|
+
if (expected)
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
else if (!expected || found.identity.dev !== expected.dev || found.identity.ino !== expected.ino ||
|
|
196
|
+
(found.entry.isSymbolicLink ? "symlink" : found.entry.isDirectory ? "directory" : found.entry.isFile ? "file" : "other") !== expected.kind)
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
for (const hint of batch.hints) {
|
|
201
|
+
signal.throwIfAborted();
|
|
202
|
+
if (hint.event === "subtree") {
|
|
203
|
+
if (!scheduling)
|
|
204
|
+
continue; // Only the full guarded snapshot supplies detail.
|
|
205
|
+
if (selectedWatchSubtree(scopes, hint.directory))
|
|
206
|
+
return undefined;
|
|
207
|
+
const guard = await admittedParent(hint.directory);
|
|
208
|
+
for (const [name, identity] of candidates) {
|
|
209
|
+
if (identity.dev === guard.stat.dev && identity.ino === guard.stat.ino && selectedWatchSubtree(scopes, name))
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const name = hint.name; // nativeChanges rejected unknown or non-literal names.
|
|
215
|
+
let parent = hint.directory;
|
|
216
|
+
const relative = parent ? path.join(parent, name) : name;
|
|
217
|
+
if (excludedWatchPath(before, relative) || excludedWatchPath(after, relative)) {
|
|
218
|
+
if (scheduling)
|
|
219
|
+
return undefined;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
let guard;
|
|
223
|
+
let expected = after.directories.get(parent);
|
|
224
|
+
if (!expected) {
|
|
225
|
+
// Native recursion observes one Root handle and may report descendants of
|
|
226
|
+
// unselected/entry-only directories. Admit a parent alias by exact identity,
|
|
227
|
+
// not by lowercasing, and do not turn unselected descendants into events.
|
|
228
|
+
try {
|
|
229
|
+
guard = await admittedParent(parent);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
await assertRootIdentityCurrent(root);
|
|
233
|
+
if (scheduling)
|
|
234
|
+
throw error;
|
|
235
|
+
if (isNotFoundPathError(error) || (error instanceof FsSafeError && ["not-found", "path-alias", "outside-workspace", "symlink", "not-file"].includes(error.code)))
|
|
236
|
+
continue;
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
const admitted = [...after.directories].find(([, identity]) => identity.dev === guard.stat.dev && identity.ino === guard.stat.ino);
|
|
240
|
+
if (!admitted) {
|
|
241
|
+
// A newly created directory may contain selected entries absent from the baseline.
|
|
242
|
+
if (scheduling)
|
|
243
|
+
return undefined;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
[parent, expected] = admitted;
|
|
247
|
+
}
|
|
248
|
+
if (scheduling)
|
|
249
|
+
guard = await admittedParent(parent);
|
|
250
|
+
if (hint.event === "children") {
|
|
251
|
+
// There is no child spelling to look up. Only guarded scans may discover it.
|
|
252
|
+
if (selectedWatchChildren(scopes, parent) && !excludedWatchPath(before, parent) && !excludedWatchPath(after, parent) &&
|
|
253
|
+
!addChange(result, { path: parent, type: "structural" }, limit))
|
|
254
|
+
return undefined;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const candidate = parent ? path.join(parent, name) : name;
|
|
258
|
+
if (excludedWatchPath(before, candidate) || excludedWatchPath(after, candidate)) {
|
|
259
|
+
if (scheduling)
|
|
260
|
+
return undefined;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const inspected = scheduling ? await lookupRootDirectoryEntry(root, guard, name) : undefined;
|
|
264
|
+
signal.throwIfAborted();
|
|
265
|
+
// A vanished or multiply linked leaf may have changed selected entries in
|
|
266
|
+
// another directory. Its current spelling cannot prove unrelatedness.
|
|
267
|
+
if (scheduling && (!inspected || (!inspected.entry.isDirectory && inspected.entry.nlink !== 1)))
|
|
268
|
+
return undefined;
|
|
269
|
+
const selected = scopedChanges(scopes, { path: candidate,
|
|
270
|
+
type: hint.event === "change" && before?.entries.get(candidate)?.startsWith("file:") ? "content" : "structural" });
|
|
271
|
+
if (selected.length) {
|
|
272
|
+
for (const change of selected)
|
|
273
|
+
if (!addChange(result, change, limit))
|
|
274
|
+
return undefined;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
guard ??= await admittedParent(parent);
|
|
278
|
+
if (guard.stat.dev !== expected.dev || guard.stat.ino !== expected.ino) {
|
|
279
|
+
throw new FsSafeError("path-mismatch", "watch hint parent changed during reconciliation");
|
|
280
|
+
}
|
|
281
|
+
const found = scheduling ? inspected : await lookupRootDirectoryEntry(root, guard, name);
|
|
282
|
+
signal.throwIfAborted();
|
|
283
|
+
// A missing unselected sibling is not evidence of lost selected detail.
|
|
284
|
+
// Previously observed selected deletions remain visible in the snapshot diff;
|
|
285
|
+
// an unseen, already-gone name has no identity proving a selected alias.
|
|
286
|
+
if (!found)
|
|
287
|
+
continue;
|
|
288
|
+
if (scheduling && found.entry.isSymbolicLink)
|
|
289
|
+
return undefined;
|
|
290
|
+
for (const [relative, identity] of candidates) {
|
|
291
|
+
if (!relative || identity.dev !== found.identity.dev || identity.ino !== found.identity.ino)
|
|
292
|
+
continue;
|
|
293
|
+
if ((path.dirname(relative) === "." ? "" : path.dirname(relative)) !== parent) {
|
|
294
|
+
if (scheduling)
|
|
295
|
+
return undefined;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
for (const change of scopedChanges(scopes, { path: relative, type: "structural" }))
|
|
299
|
+
if (!addChange(result, change, limit))
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
await assertRootDirectoryObservationGuard(root, guard);
|
|
91
303
|
}
|
|
304
|
+
for (const guard of guards.values())
|
|
305
|
+
await assertRootDirectoryObservationGuard(root, guard);
|
|
306
|
+
await assertRootIdentityCurrent(root);
|
|
307
|
+
signal.throwIfAborted();
|
|
92
308
|
return [...result.values()];
|
|
93
309
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type NativeBinding } from "./native.js";
|
|
2
2
|
import type { RootContext } from "./root-context.js";
|
|
3
|
-
import type { DirectoryIdentity } from "./watch-scan.js";
|
|
3
|
+
import type { DirectoryIdentity, WatchSnapshot } from "./watch-scan.js";
|
|
4
4
|
import type { WatchStreamPaths } from "./watch-stream.js";
|
|
5
5
|
export type NativeWatchHint = {
|
|
6
6
|
directory: string;
|
|
7
7
|
name: string;
|
|
8
|
-
event: "rename" | "change";
|
|
8
|
+
event: "rename" | "change" | "children" | "subtree";
|
|
9
9
|
};
|
|
10
10
|
export type NativeWatchBatch = {
|
|
11
11
|
hints: NativeWatchHint[];
|
|
@@ -18,19 +18,38 @@ export type NativeWatchWireBatch = {
|
|
|
18
18
|
name: string;
|
|
19
19
|
structural: boolean;
|
|
20
20
|
flags?: number;
|
|
21
|
+
namelessChild?: boolean;
|
|
22
|
+
subtree?: boolean;
|
|
21
23
|
}[];
|
|
22
24
|
overflow: boolean;
|
|
23
25
|
error?: string;
|
|
24
26
|
};
|
|
27
|
+
export type NativeWatchEntry = {
|
|
28
|
+
scope: string;
|
|
29
|
+
directory: {
|
|
30
|
+
root: string;
|
|
31
|
+
relative: string;
|
|
32
|
+
rootDev: bigint;
|
|
33
|
+
rootIno: bigint;
|
|
34
|
+
dev: bigint;
|
|
35
|
+
ino: bigint;
|
|
36
|
+
};
|
|
37
|
+
name: string;
|
|
38
|
+
target?: DirectoryIdentity & {
|
|
39
|
+
kind: string;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
25
42
|
export declare function watchBinding(mode: "auto" | "events" | "poll"): NativeBinding | undefined;
|
|
26
43
|
export declare class NativeWatchBackend {
|
|
27
44
|
private binding;
|
|
28
45
|
private root;
|
|
29
46
|
private id;
|
|
30
47
|
private streamPaths;
|
|
48
|
+
directories: number | undefined;
|
|
31
49
|
constructor(binding: NativeBinding, root: RootContext, callback: (batch: NativeWatchBatch) => void, limit: number, persistent: boolean);
|
|
32
50
|
add(name: string, identity: DirectoryIdentity): void;
|
|
33
51
|
testEvent(path: string, flags: number): void;
|
|
52
|
+
entries(snapshot: WatchSnapshot): boolean;
|
|
34
53
|
configure(paths: WatchStreamPaths): boolean;
|
|
35
54
|
close(): void;
|
|
36
55
|
}
|
|
@@ -6,7 +6,7 @@ export function watchBinding(mode) {
|
|
|
6
6
|
return;
|
|
7
7
|
const binding = getNativeBinding(); // Preserves require + missing-addon failure.
|
|
8
8
|
// Bun TSFN teardown remains unqualified; guarded native scans remain usable.
|
|
9
|
-
if (binding?.watchRegister && (process.platform !== "darwin" || binding.watchConfigure) && !process.versions.bun && !process.versions.deno && ["linux", "darwin", "win32"].includes(process.platform))
|
|
9
|
+
if (binding?.watchRegister && (process.platform !== "darwin" || (binding.watchConfigure && binding.watchEntries)) && !process.versions.bun && !process.versions.deno && ["linux", "darwin", "win32"].includes(process.platform))
|
|
10
10
|
return binding;
|
|
11
11
|
if (mode === "events" || getFsSafeNativeConfig().mode === "require") {
|
|
12
12
|
throw new FsSafeError("helper-unavailable", "native watch events are unavailable", { details: { operation: "watch" } });
|
|
@@ -17,15 +17,16 @@ export class NativeWatchBackend {
|
|
|
17
17
|
root;
|
|
18
18
|
id;
|
|
19
19
|
streamPaths;
|
|
20
|
+
directories;
|
|
20
21
|
constructor(binding, root, callback, limit, persistent) {
|
|
21
22
|
this.binding = binding;
|
|
22
23
|
this.root = root;
|
|
23
|
-
this.streamPaths = JSON.stringify({ anchors: [
|
|
24
|
+
this.streamPaths = JSON.stringify({ anchors: [], exclusions: [] });
|
|
24
25
|
try {
|
|
25
26
|
this.id = binding.watchRegister(root.rootReal, limit, batch => {
|
|
26
27
|
if (this.id !== undefined)
|
|
27
28
|
callback({ overflow: batch.overflow, error: batch.error, hints: batch.hints.map(hint => ({
|
|
28
|
-
directory: hint.directory, name: hint.name, event: hint.structural ? "rename" : "change",
|
|
29
|
+
directory: hint.directory, name: hint.name, event: hint.subtree ? "subtree" : hint.namelessChild ? "children" : hint.structural ? "rename" : "change",
|
|
29
30
|
})) });
|
|
30
31
|
}, persistent);
|
|
31
32
|
}
|
|
@@ -43,6 +44,23 @@ export class NativeWatchBackend {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
testEvent(path, flags) { this.binding.watchTestEvent(this.id, path, flags); }
|
|
47
|
+
entries(snapshot) {
|
|
48
|
+
if (process.platform !== "darwin")
|
|
49
|
+
return false;
|
|
50
|
+
const entries = [...snapshot.entryAnchors ?? []].map(([scope, anchor]) => ({
|
|
51
|
+
scope, name: anchor.name, target: anchor.target,
|
|
52
|
+
directory: { root: this.root.rootReal, relative: anchor.directory,
|
|
53
|
+
rootDev: BigInt(this.root.rootIdentity.dev), rootIno: BigInt(this.root.rootIdentity.ino), ...snapshot.directories.get(anchor.directory) },
|
|
54
|
+
}));
|
|
55
|
+
try {
|
|
56
|
+
const result = this.binding.watchEntries(this.id, entries);
|
|
57
|
+
this.directories = result.directories;
|
|
58
|
+
return result.changed;
|
|
59
|
+
}
|
|
60
|
+
catch (cause) {
|
|
61
|
+
throw watchError(cause);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
46
64
|
configure(paths) {
|
|
47
65
|
if (process.platform !== "darwin")
|
|
48
66
|
return false;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WatchChange, WatchScope } from "./watch-types.js";
|
|
2
|
+
import type { WatchSnapshot } from "./watch-scan.js";
|
|
3
|
+
/** Only previously enumerated directories can be reconciled without discovering topology. */
|
|
4
|
+
export declare function watchRescanScopes(scopes: readonly WatchScope[], snapshot: WatchSnapshot, changes: readonly WatchChange[]): readonly WatchScope[] | undefined;
|
|
5
|
+
/** Merge only freshly observed slices. Any topology/anchor ambiguity requires a full pass. */
|
|
6
|
+
export declare function mergeWatchRescan(scopes: readonly WatchScope[], before: WatchSnapshot, slice: WatchSnapshot, requested: readonly WatchScope[]): WatchSnapshot | undefined;
|