@earendil-works/pi-durable 1.0.2 → 1.0.3
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/CHANGELOG.md +30 -0
- package/README.md +15 -1
- package/dist/env/decode.d.ts +16 -0
- package/dist/env/decode.d.ts.map +1 -0
- package/dist/env/decode.js +28 -0
- package/dist/env/decode.js.map +1 -0
- package/dist/env/index.d.ts +155 -3
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js +2 -0
- package/dist/env/index.js.map +1 -1
- package/dist/env/line-scan.d.ts +13 -0
- package/dist/env/line-scan.d.ts.map +1 -0
- package/dist/env/line-scan.js +147 -0
- package/dist/env/line-scan.js.map +1 -0
- package/dist/env/node-watch.d.ts +29 -0
- package/dist/env/node-watch.d.ts.map +1 -0
- package/dist/env/node-watch.js +431 -0
- package/dist/env/node-watch.js.map +1 -0
- package/dist/env/node.d.ts +11 -2
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +284 -21
- package/dist/env/node.js.map +1 -1
- package/dist/harness/agent.d.ts +2 -1
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +5 -0
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +6 -5
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/output.d.ts +13 -6
- package/dist/harness/output.d.ts.map +1 -1
- package/dist/harness/output.js +61 -16
- package/dist/harness/output.js.map +1 -1
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +12 -4
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/types.d.ts +24 -3
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/testing/env-conformance.d.ts +7 -0
- package/dist/testing/env-conformance.d.ts.map +1 -0
- package/dist/testing/env-conformance.js +397 -0
- package/dist/testing/env-conformance.js.map +1 -0
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/runner.d.ts +4 -2
- package/dist/testing/runner.d.ts.map +1 -1
- package/dist/testing/runner.js +9 -0
- package/dist/testing/runner.js.map +1 -1
- package/dist/testing/types.d.ts +18 -0
- package/dist/testing/types.d.ts.map +1 -1
- package/dist/testing/types.js.map +1 -1
- package/dist/tools/bash.d.ts +2 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +5 -2
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/image.d.ts +10 -0
- package/dist/tools/image.d.ts.map +1 -1
- package/dist/tools/image.js +39 -0
- package/dist/tools/image.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +148 -75
- package/dist/tools/read.js.map +1 -1
- package/dist/truncate.d.ts +9 -0
- package/dist/truncate.d.ts.map +1 -1
- package/dist/truncate.js +12 -4
- package/dist/truncate.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import { type FileWatcher, type WatchChange, type WatchTarget } from "./index.ts";
|
|
3
|
+
/** How `NodeExecutionEnv` watches. */
|
|
4
|
+
export interface NodeWatchOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Force a mode. By default `polling` is chosen on Windows, where native watchers keep directories open and so block
|
|
7
|
+
* renaming their parents, and for file systems that do not report remote changes.
|
|
8
|
+
*/
|
|
9
|
+
mode?: "native" | "polling";
|
|
10
|
+
/** Interval between snapshots in `polling` mode; default 2000 ms. */
|
|
11
|
+
pollIntervalMs?: number;
|
|
12
|
+
/** Most directories one watcher covers; default 10,000. */
|
|
13
|
+
maxDirectories?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Watches by snapshots: native events only trigger a debounced rescan, and changes are the difference between
|
|
17
|
+
* snapshots plus the event paths. A replaced file, a renamed or recreated ancestor, or a directory created with its
|
|
18
|
+
* contents therefore never depends on which events an operating system sends. New directories get their watchers
|
|
19
|
+
* before they are scanned again, so nothing written into them before the watcher existed is missed.
|
|
20
|
+
*/
|
|
21
|
+
export declare class NodeFileWatcher implements FileWatcher {
|
|
22
|
+
#private;
|
|
23
|
+
private constructor();
|
|
24
|
+
/** Establish coverage: watchers first, then the snapshot later changes are compared with. */
|
|
25
|
+
static open(targets: readonly WatchTarget[], resolvePath: (path: string) => string, onChange: (change: WatchChange) => void, options: NodeWatchOptions): Promise<NodeFileWatcher>;
|
|
26
|
+
get mode(): "native" | "polling";
|
|
27
|
+
close(_context: Context): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=node-watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-watch.d.ts","sourceRoot":"","sources":["../../src/env/node-watch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAa,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7F,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAwGD;;;;;GAKG;AACH,qBAAa,eAAgB,YAAW,WAAW;;IAelD,OAAO,eAWN;IAED,6FAA6F;IAC7F,OAAa,IAAI,CAChB,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,EACvC,OAAO,EAAE,gBAAgB,GACvB,OAAO,CAAC,eAAe,CAAC,CAwB1B;IAED,IAAI,IAAI,IAAI,QAAQ,GAAG,SAAS,CAE/B;IAEK,KAAK,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5C;CA+PD"}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { watch as fsWatch } from "node:fs";
|
|
3
|
+
import { lstat, readdir, readFile, stat, statfs } from "node:fs/promises";
|
|
4
|
+
import { dirname, relative, sep } from "node:path";
|
|
5
|
+
import { FileError } from "./index.js";
|
|
6
|
+
const DEBOUNCE_MS = 50;
|
|
7
|
+
/**
|
|
8
|
+
* On macOS, `fs.watch` returns before libuv's FSEvents stream is live, and changes in between are never reported
|
|
9
|
+
* (about 2% of changes made right after the call, none after 100 ms). A rescan this long after installing watchers
|
|
10
|
+
* catches them.
|
|
11
|
+
*/
|
|
12
|
+
const FSEVENTS_SETTLE_MS = 500;
|
|
13
|
+
const DEFAULT_POLL_MS = 2000;
|
|
14
|
+
const DEFAULT_MAX_DIRECTORIES = 10_000;
|
|
15
|
+
/** In polling mode, recently modified small files are also compared by content: a second write within the file system's
|
|
16
|
+
* timestamp granularity can keep size and modification time. */
|
|
17
|
+
const HASH_MAX_BYTES = 256 * 1024;
|
|
18
|
+
const HASH_RECENT_MS = 5000;
|
|
19
|
+
/** Linux `statfs` magic numbers of file systems that accept watches but do not report changes made elsewhere. */
|
|
20
|
+
const UNRELIABLE_FILE_SYSTEMS = new Set([
|
|
21
|
+
0x6969, // NFS
|
|
22
|
+
0x517b, // SMB
|
|
23
|
+
0xff534d42, // CIFS
|
|
24
|
+
0xfe534d42, // SMB2
|
|
25
|
+
0x65735546, // FUSE (sshfs, Android shared storage)
|
|
26
|
+
0x01021997, // 9P (WSL2 Windows drives)
|
|
27
|
+
0x0bd00bd0, // Lustre
|
|
28
|
+
0x47504653, // GPFS
|
|
29
|
+
0x00c36400, // Ceph
|
|
30
|
+
0x5346414f, // OpenAFS
|
|
31
|
+
0x6b414653, // kAFS
|
|
32
|
+
0x5dca2df5, // sdcardfs
|
|
33
|
+
]);
|
|
34
|
+
class BudgetExceeded extends Error {
|
|
35
|
+
}
|
|
36
|
+
function isNodeError(error) {
|
|
37
|
+
return error instanceof Error && "code" in error;
|
|
38
|
+
}
|
|
39
|
+
function entryOf(stats, hash) {
|
|
40
|
+
const kind = stats.isFile()
|
|
41
|
+
? "file"
|
|
42
|
+
: stats.isDirectory()
|
|
43
|
+
? "directory"
|
|
44
|
+
: stats.isSymbolicLink()
|
|
45
|
+
? "symlink"
|
|
46
|
+
: "other";
|
|
47
|
+
return {
|
|
48
|
+
kind,
|
|
49
|
+
dev: stats.dev,
|
|
50
|
+
ino: stats.ino,
|
|
51
|
+
size: kind === "directory" ? 0 : stats.size,
|
|
52
|
+
mtimeMs: kind === "directory" ? 0 : stats.mtimeMs,
|
|
53
|
+
...(hash === undefined ? {} : { hash }),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function sameEntry(a, b) {
|
|
57
|
+
return (a.kind === b.kind &&
|
|
58
|
+
a.dev === b.dev &&
|
|
59
|
+
a.ino === b.ino &&
|
|
60
|
+
a.size === b.size &&
|
|
61
|
+
a.mtimeMs === b.mtimeMs &&
|
|
62
|
+
a.hash === b.hash);
|
|
63
|
+
}
|
|
64
|
+
/** Ancestors of `path`, nearest first, up to the root. */
|
|
65
|
+
function ancestorsOf(path) {
|
|
66
|
+
const result = [];
|
|
67
|
+
for (let current = dirname(path);; current = dirname(current)) {
|
|
68
|
+
result.push(current);
|
|
69
|
+
if (dirname(current) === current)
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Whether `path` is `ancestor` or below it. */
|
|
74
|
+
function isWithin(path, ancestor) {
|
|
75
|
+
return path === ancestor || path.startsWith(ancestor.endsWith(sep) ? ancestor : ancestor + sep);
|
|
76
|
+
}
|
|
77
|
+
function excluded(target, name) {
|
|
78
|
+
return (target.hidden && name.startsWith(".")) || target.names.has(name);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Watches by snapshots: native events only trigger a debounced rescan, and changes are the difference between
|
|
82
|
+
* snapshots plus the event paths. A replaced file, a renamed or recreated ancestor, or a directory created with its
|
|
83
|
+
* contents therefore never depends on which events an operating system sends. New directories get their watchers
|
|
84
|
+
* before they are scanned again, so nothing written into them before the watcher existed is missed.
|
|
85
|
+
*/
|
|
86
|
+
export class NodeFileWatcher {
|
|
87
|
+
#targets;
|
|
88
|
+
#onChange;
|
|
89
|
+
#pollIntervalMs;
|
|
90
|
+
#maxDirectories;
|
|
91
|
+
#watchers = new Map();
|
|
92
|
+
#events = new Set();
|
|
93
|
+
#mode;
|
|
94
|
+
#snapshot = new Map();
|
|
95
|
+
#timer;
|
|
96
|
+
#settleTimer;
|
|
97
|
+
#running;
|
|
98
|
+
#dirty = false;
|
|
99
|
+
#closed = false;
|
|
100
|
+
constructor(targets, onChange, mode, options) {
|
|
101
|
+
this.#targets = targets;
|
|
102
|
+
this.#onChange = onChange;
|
|
103
|
+
this.#mode = mode;
|
|
104
|
+
this.#pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
105
|
+
this.#maxDirectories = options.maxDirectories ?? DEFAULT_MAX_DIRECTORIES;
|
|
106
|
+
}
|
|
107
|
+
/** Establish coverage: watchers first, then the snapshot later changes are compared with. */
|
|
108
|
+
static async open(targets, resolvePath, onChange, options) {
|
|
109
|
+
const resolved = targets.map((target) => ({
|
|
110
|
+
path: resolvePath(target.path),
|
|
111
|
+
recursive: target.recursive === true,
|
|
112
|
+
hidden: target.exclude?.hidden === true,
|
|
113
|
+
names: new Set(target.exclude?.names ?? []),
|
|
114
|
+
}));
|
|
115
|
+
// Windows refuses to rename a directory while another directory below it is open, and a native watcher keeps
|
|
116
|
+
// every watched directory open: watching would break renames of their parents, so Windows polls.
|
|
117
|
+
const mode = options.mode ??
|
|
118
|
+
(process.platform === "win32" || (await anyUnreliable(resolved.map((target) => target.path)))
|
|
119
|
+
? "polling"
|
|
120
|
+
: "native");
|
|
121
|
+
const watcher = new NodeFileWatcher(resolved, onChange, mode, options);
|
|
122
|
+
try {
|
|
123
|
+
await watcher.#sync(false);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
watcher.#stop();
|
|
127
|
+
if (error instanceof BudgetExceeded)
|
|
128
|
+
throw new FileError("invalid", error.message);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
watcher.#schedulePoll();
|
|
132
|
+
return watcher;
|
|
133
|
+
}
|
|
134
|
+
get mode() {
|
|
135
|
+
return this.#mode;
|
|
136
|
+
}
|
|
137
|
+
async close(_context) {
|
|
138
|
+
if (this.#closed)
|
|
139
|
+
return;
|
|
140
|
+
this.#stop();
|
|
141
|
+
await this.#running;
|
|
142
|
+
}
|
|
143
|
+
#stop() {
|
|
144
|
+
this.#closed = true;
|
|
145
|
+
clearTimeout(this.#timer);
|
|
146
|
+
this.#timer = undefined;
|
|
147
|
+
clearTimeout(this.#settleTimer);
|
|
148
|
+
this.#settleTimer = undefined;
|
|
149
|
+
for (const watcher of this.#watchers.values())
|
|
150
|
+
watcher.close();
|
|
151
|
+
this.#watchers.clear();
|
|
152
|
+
}
|
|
153
|
+
#deliver(change) {
|
|
154
|
+
if (this.#closed)
|
|
155
|
+
return;
|
|
156
|
+
try {
|
|
157
|
+
this.#onChange(change);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// A throwing callback must not stop watching.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
#schedulePoll() {
|
|
164
|
+
if (this.#closed || this.#mode !== "polling")
|
|
165
|
+
return;
|
|
166
|
+
this.#timer = setTimeout(() => {
|
|
167
|
+
this.#timer = undefined;
|
|
168
|
+
void this.#flush().finally(() => this.#schedulePoll());
|
|
169
|
+
}, this.#pollIntervalMs);
|
|
170
|
+
}
|
|
171
|
+
#scheduleFlush() {
|
|
172
|
+
if (this.#closed || this.#timer !== undefined)
|
|
173
|
+
return;
|
|
174
|
+
this.#timer = setTimeout(() => {
|
|
175
|
+
this.#timer = undefined;
|
|
176
|
+
void this.#flush();
|
|
177
|
+
}, DEBOUNCE_MS);
|
|
178
|
+
}
|
|
179
|
+
#scheduleSettle() {
|
|
180
|
+
if (this.#closed)
|
|
181
|
+
return;
|
|
182
|
+
clearTimeout(this.#settleTimer);
|
|
183
|
+
this.#settleTimer = setTimeout(() => {
|
|
184
|
+
this.#settleTimer = undefined;
|
|
185
|
+
void this.#flush();
|
|
186
|
+
}, FSEVENTS_SETTLE_MS);
|
|
187
|
+
}
|
|
188
|
+
#flush() {
|
|
189
|
+
if (this.#running !== undefined) {
|
|
190
|
+
this.#dirty = true;
|
|
191
|
+
return this.#running;
|
|
192
|
+
}
|
|
193
|
+
this.#running = (async () => {
|
|
194
|
+
try {
|
|
195
|
+
do {
|
|
196
|
+
this.#dirty = false;
|
|
197
|
+
const events = [...this.#events];
|
|
198
|
+
this.#events.clear();
|
|
199
|
+
const changed = await this.#sync(true);
|
|
200
|
+
for (const path of events)
|
|
201
|
+
changed.add(path);
|
|
202
|
+
if (changed.size > 0)
|
|
203
|
+
this.#deliver({ paths: [...changed].sort() });
|
|
204
|
+
} while (this.#dirty && !this.#closed);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
const fileError = error instanceof FileError
|
|
208
|
+
? error
|
|
209
|
+
: new FileError("invalid", error instanceof Error ? error.message : String(error));
|
|
210
|
+
this.#deliver({ error: fileError });
|
|
211
|
+
this.#stop();
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
this.#running = undefined;
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
217
|
+
return this.#running;
|
|
218
|
+
}
|
|
219
|
+
/** Rescan, report differences, and install watchers for new directories, rescanning until none are new. */
|
|
220
|
+
async #sync(report) {
|
|
221
|
+
const changed = new Set();
|
|
222
|
+
for (let round = 0; round < 10 && !this.#closed; round++) {
|
|
223
|
+
const next = await this.#scan();
|
|
224
|
+
if (report)
|
|
225
|
+
for (const path of this.#diff(this.#snapshot, next))
|
|
226
|
+
changed.add(path);
|
|
227
|
+
this.#snapshot = next;
|
|
228
|
+
if (this.#mode === "polling" || !this.#reconcileWatchers(next))
|
|
229
|
+
break;
|
|
230
|
+
if (process.platform === "darwin")
|
|
231
|
+
this.#scheduleSettle();
|
|
232
|
+
// Something written into a new directory before its watcher existed shows up in the next round.
|
|
233
|
+
report = true;
|
|
234
|
+
}
|
|
235
|
+
return changed;
|
|
236
|
+
}
|
|
237
|
+
#diff(previous, next) {
|
|
238
|
+
const changed = [];
|
|
239
|
+
for (const [path, entry] of next) {
|
|
240
|
+
const before = previous.get(path);
|
|
241
|
+
if (before === undefined || !sameEntry(before, entry))
|
|
242
|
+
changed.push(this.#reported(path));
|
|
243
|
+
}
|
|
244
|
+
for (const path of previous.keys())
|
|
245
|
+
if (!next.has(path))
|
|
246
|
+
changed.push(this.#reported(path));
|
|
247
|
+
return changed;
|
|
248
|
+
}
|
|
249
|
+
/** An ancestor that changed identity moved every target below it; report those targets. */
|
|
250
|
+
#reported(path) {
|
|
251
|
+
if (this.#targets.some((target) => isWithin(path, target.path)))
|
|
252
|
+
return path;
|
|
253
|
+
return this.#targets.find((target) => isWithin(target.path, path))?.path ?? path;
|
|
254
|
+
}
|
|
255
|
+
async #scan() {
|
|
256
|
+
const snapshot = new Map();
|
|
257
|
+
let directories = 0;
|
|
258
|
+
const countDirectory = () => {
|
|
259
|
+
if (++directories > this.#maxDirectories) {
|
|
260
|
+
throw new BudgetExceeded(`Watched paths exceed ${this.#maxDirectories} directories`);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const record = async (path, stats) => {
|
|
264
|
+
let hash;
|
|
265
|
+
if (this.#mode === "polling" &&
|
|
266
|
+
stats.isFile() &&
|
|
267
|
+
stats.size <= HASH_MAX_BYTES &&
|
|
268
|
+
Date.now() - stats.mtimeMs < HASH_RECENT_MS) {
|
|
269
|
+
hash = await readFile(path).then((content) => createHash("sha256").update(content).digest("hex"), () => undefined);
|
|
270
|
+
}
|
|
271
|
+
snapshot.set(path, entryOf(stats, hash));
|
|
272
|
+
};
|
|
273
|
+
const scanDirectory = async (target, directory) => {
|
|
274
|
+
let names;
|
|
275
|
+
try {
|
|
276
|
+
names = await readdir(directory);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
if (isNodeError(error) && ["ENOENT", "EACCES", "EPERM", "ENOTDIR"].includes(error.code ?? ""))
|
|
280
|
+
return;
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
for (const name of names) {
|
|
284
|
+
if (excluded(target, name))
|
|
285
|
+
continue;
|
|
286
|
+
const path = `${directory.endsWith(sep) ? directory : directory + sep}${name}`;
|
|
287
|
+
if (snapshot.has(path))
|
|
288
|
+
continue;
|
|
289
|
+
const stats = await lstat(path).catch(() => undefined);
|
|
290
|
+
if (stats === undefined)
|
|
291
|
+
continue;
|
|
292
|
+
await record(path, stats);
|
|
293
|
+
if (target.recursive && stats.isDirectory()) {
|
|
294
|
+
countDirectory();
|
|
295
|
+
await scanDirectory(target, path);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
for (const target of this.#targets) {
|
|
300
|
+
for (const ancestor of ancestorsOf(target.path)) {
|
|
301
|
+
if (snapshot.has(ancestor))
|
|
302
|
+
continue;
|
|
303
|
+
const stats = await lstat(ancestor).catch(() => undefined);
|
|
304
|
+
// Identity only: an ancestor's own timestamps change with every unrelated sibling.
|
|
305
|
+
if (stats !== undefined)
|
|
306
|
+
snapshot.set(ancestor, { ...entryOf(stats), size: 0, mtimeMs: 0 });
|
|
307
|
+
}
|
|
308
|
+
// The target itself may be a symbolic link to what is watched; follow it.
|
|
309
|
+
const stats = await stat(target.path).catch(() => undefined);
|
|
310
|
+
if (stats === undefined)
|
|
311
|
+
continue;
|
|
312
|
+
await record(target.path, stats);
|
|
313
|
+
if (stats.isDirectory()) {
|
|
314
|
+
countDirectory();
|
|
315
|
+
await scanDirectory(target, target.path);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return snapshot;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Watch every existing ancestor of each target, each target directory, and on Linux each directory below a recursive
|
|
322
|
+
* target (elsewhere one recursive watcher per target). Returns whether a watcher was added.
|
|
323
|
+
*/
|
|
324
|
+
#reconcileWatchers(snapshot) {
|
|
325
|
+
const wanted = new Map();
|
|
326
|
+
const perDirectory = process.platform === "linux" || process.platform === "android";
|
|
327
|
+
for (const target of this.#targets) {
|
|
328
|
+
for (const ancestor of ancestorsOf(target.path)) {
|
|
329
|
+
if (snapshot.get(ancestor)?.kind === "directory" && !wanted.has(ancestor))
|
|
330
|
+
wanted.set(ancestor, false);
|
|
331
|
+
}
|
|
332
|
+
if (snapshot.get(target.path)?.kind !== "directory")
|
|
333
|
+
continue;
|
|
334
|
+
const recursiveRoot = target.recursive && !perDirectory;
|
|
335
|
+
wanted.set(target.path, recursiveRoot);
|
|
336
|
+
if (target.recursive && perDirectory) {
|
|
337
|
+
for (const [path, entry] of snapshot) {
|
|
338
|
+
if (entry.kind === "directory" && path !== target.path && isWithin(path, target.path)) {
|
|
339
|
+
wanted.set(path, false);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const [path, watcher] of this.#watchers) {
|
|
345
|
+
if (!wanted.has(path)) {
|
|
346
|
+
watcher.close();
|
|
347
|
+
this.#watchers.delete(path);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
let added = false;
|
|
351
|
+
for (const [path, recursive] of wanted) {
|
|
352
|
+
if (this.#watchers.has(path))
|
|
353
|
+
continue;
|
|
354
|
+
try {
|
|
355
|
+
const watcher = fsWatch(path, { recursive, persistent: false }, (_event, filename) => this.#onEvent(path, filename === null ? undefined : String(filename)));
|
|
356
|
+
// A watched directory that disappears or fails: rescan, which also replaces the watcher.
|
|
357
|
+
watcher.on("error", () => {
|
|
358
|
+
watcher.close();
|
|
359
|
+
if (this.#watchers.get(path) === watcher)
|
|
360
|
+
this.#watchers.delete(path);
|
|
361
|
+
this.#scheduleFlush();
|
|
362
|
+
});
|
|
363
|
+
this.#watchers.set(path, watcher);
|
|
364
|
+
added = true;
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
// Out of watches or unsupported: compare snapshots from now on, and say coverage was uncertain.
|
|
368
|
+
if (isNodeError(error) && error.code !== "ENOENT" && error.code !== "EACCES" && error.code !== "EPERM") {
|
|
369
|
+
this.#switchToPolling();
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return added;
|
|
375
|
+
}
|
|
376
|
+
#switchToPolling() {
|
|
377
|
+
if (this.#mode === "polling")
|
|
378
|
+
return;
|
|
379
|
+
this.#mode = "polling";
|
|
380
|
+
for (const watcher of this.#watchers.values())
|
|
381
|
+
watcher.close();
|
|
382
|
+
this.#watchers.clear();
|
|
383
|
+
this.#deliver({ overflow: true });
|
|
384
|
+
clearTimeout(this.#timer);
|
|
385
|
+
this.#timer = undefined;
|
|
386
|
+
this.#schedulePoll();
|
|
387
|
+
}
|
|
388
|
+
#onEvent(directory, filename) {
|
|
389
|
+
if (this.#closed)
|
|
390
|
+
return;
|
|
391
|
+
const path = filename === undefined ? directory : `${directory.endsWith(sep) ? directory : directory + sep}${filename}`;
|
|
392
|
+
// Events about unrelated siblings of an ancestor, or about excluded entries, are ignored.
|
|
393
|
+
const relevant = this.#inScope(path);
|
|
394
|
+
if (relevant)
|
|
395
|
+
this.#events.add(this.#reported(path));
|
|
396
|
+
if (relevant || filename === undefined)
|
|
397
|
+
this.#scheduleFlush();
|
|
398
|
+
}
|
|
399
|
+
#inScope(path) {
|
|
400
|
+
for (const target of this.#targets) {
|
|
401
|
+
if (isWithin(target.path, path))
|
|
402
|
+
return true;
|
|
403
|
+
if (!isWithin(path, target.path) || path === target.path)
|
|
404
|
+
continue;
|
|
405
|
+
const components = relative(target.path, path).split(sep);
|
|
406
|
+
if (!target.recursive && components.length > 1)
|
|
407
|
+
continue;
|
|
408
|
+
if (components.some((name) => excluded(target, name)))
|
|
409
|
+
continue;
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/** Whether any path, or its nearest existing ancestor, is on a file system that does not report remote changes. */
|
|
416
|
+
async function anyUnreliable(paths) {
|
|
417
|
+
if (process.platform !== "linux" && process.platform !== "android")
|
|
418
|
+
return false;
|
|
419
|
+
for (const path of paths) {
|
|
420
|
+
for (const candidate of [path, ...ancestorsOf(path)]) {
|
|
421
|
+
const info = await statfs(candidate).catch(() => undefined);
|
|
422
|
+
if (info === undefined)
|
|
423
|
+
continue;
|
|
424
|
+
if (UNRELIABLE_FILE_SYSTEMS.has(info.type))
|
|
425
|
+
return true;
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
//# sourceMappingURL=node-watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-watch.js","sourceRoot":"","sources":["../../src/env/node-watch.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { type FSWatcher, watch as fsWatch, type Stats } from \"node:fs\";\nimport { lstat, readdir, readFile, stat, statfs } from \"node:fs/promises\";\nimport { dirname, relative, sep } from \"node:path\";\nimport type { Context } from \"@earendil-works/chord\";\nimport { FileError, type FileWatcher, type WatchChange, type WatchTarget } from \"./index.ts\";\n\n/** How `NodeExecutionEnv` watches. */\nexport interface NodeWatchOptions {\n\t/**\n\t * Force a mode. By default `polling` is chosen on Windows, where native watchers keep directories open and so block\n\t * renaming their parents, and for file systems that do not report remote changes.\n\t */\n\tmode?: \"native\" | \"polling\";\n\t/** Interval between snapshots in `polling` mode; default 2000 ms. */\n\tpollIntervalMs?: number;\n\t/** Most directories one watcher covers; default 10,000. */\n\tmaxDirectories?: number;\n}\n\ntype ResolvedTarget = {\n\treadonly path: string;\n\treadonly recursive: boolean;\n\treadonly hidden: boolean;\n\treadonly names: ReadonlySet<string>;\n};\n\n/** What a snapshot remembers of one path. Directories and ancestors are compared by identity only. */\ntype Entry = {\n\treadonly kind: \"file\" | \"directory\" | \"symlink\" | \"other\";\n\treadonly dev: number;\n\treadonly ino: number;\n\treadonly size: number;\n\treadonly mtimeMs: number;\n\treadonly hash?: string;\n};\n\ntype Snapshot = Map<string, Entry>;\n\nconst DEBOUNCE_MS = 50;\n/**\n * On macOS, `fs.watch` returns before libuv's FSEvents stream is live, and changes in between are never reported\n * (about 2% of changes made right after the call, none after 100 ms). A rescan this long after installing watchers\n * catches them.\n */\nconst FSEVENTS_SETTLE_MS = 500;\nconst DEFAULT_POLL_MS = 2000;\nconst DEFAULT_MAX_DIRECTORIES = 10_000;\n/** In polling mode, recently modified small files are also compared by content: a second write within the file system's\n * timestamp granularity can keep size and modification time. */\nconst HASH_MAX_BYTES = 256 * 1024;\nconst HASH_RECENT_MS = 5000;\n\n/** Linux `statfs` magic numbers of file systems that accept watches but do not report changes made elsewhere. */\nconst UNRELIABLE_FILE_SYSTEMS = new Set([\n\t0x6969, // NFS\n\t0x517b, // SMB\n\t0xff534d42, // CIFS\n\t0xfe534d42, // SMB2\n\t0x65735546, // FUSE (sshfs, Android shared storage)\n\t0x01021997, // 9P (WSL2 Windows drives)\n\t0x0bd00bd0, // Lustre\n\t0x47504653, // GPFS\n\t0x00c36400, // Ceph\n\t0x5346414f, // OpenAFS\n\t0x6b414653, // kAFS\n\t0x5dca2df5, // sdcardfs\n]);\n\nclass BudgetExceeded extends Error {}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n\treturn error instanceof Error && \"code\" in error;\n}\n\nfunction entryOf(stats: Stats, hash?: string): Entry {\n\tconst kind = stats.isFile()\n\t\t? \"file\"\n\t\t: stats.isDirectory()\n\t\t\t? \"directory\"\n\t\t\t: stats.isSymbolicLink()\n\t\t\t\t? \"symlink\"\n\t\t\t\t: \"other\";\n\treturn {\n\t\tkind,\n\t\tdev: stats.dev,\n\t\tino: stats.ino,\n\t\tsize: kind === \"directory\" ? 0 : stats.size,\n\t\tmtimeMs: kind === \"directory\" ? 0 : stats.mtimeMs,\n\t\t...(hash === undefined ? {} : { hash }),\n\t};\n}\n\nfunction sameEntry(a: Entry, b: Entry): boolean {\n\treturn (\n\t\ta.kind === b.kind &&\n\t\ta.dev === b.dev &&\n\t\ta.ino === b.ino &&\n\t\ta.size === b.size &&\n\t\ta.mtimeMs === b.mtimeMs &&\n\t\ta.hash === b.hash\n\t);\n}\n\n/** Ancestors of `path`, nearest first, up to the root. */\nfunction ancestorsOf(path: string): string[] {\n\tconst result: string[] = [];\n\tfor (let current = dirname(path); ; current = dirname(current)) {\n\t\tresult.push(current);\n\t\tif (dirname(current) === current) return result;\n\t}\n}\n\n/** Whether `path` is `ancestor` or below it. */\nfunction isWithin(path: string, ancestor: string): boolean {\n\treturn path === ancestor || path.startsWith(ancestor.endsWith(sep) ? ancestor : ancestor + sep);\n}\n\nfunction excluded(target: ResolvedTarget, name: string): boolean {\n\treturn (target.hidden && name.startsWith(\".\")) || target.names.has(name);\n}\n\n/**\n * Watches by snapshots: native events only trigger a debounced rescan, and changes are the difference between\n * snapshots plus the event paths. A replaced file, a renamed or recreated ancestor, or a directory created with its\n * contents therefore never depends on which events an operating system sends. New directories get their watchers\n * before they are scanned again, so nothing written into them before the watcher existed is missed.\n */\nexport class NodeFileWatcher implements FileWatcher {\n\treadonly #targets: readonly ResolvedTarget[];\n\treadonly #onChange: (change: WatchChange) => void;\n\treadonly #pollIntervalMs: number;\n\treadonly #maxDirectories: number;\n\treadonly #watchers = new Map<string, FSWatcher>();\n\treadonly #events = new Set<string>();\n\t#mode: \"native\" | \"polling\";\n\t#snapshot: Snapshot = new Map();\n\t#timer: ReturnType<typeof setTimeout> | undefined;\n\t#settleTimer: ReturnType<typeof setTimeout> | undefined;\n\t#running: Promise<void> | undefined;\n\t#dirty = false;\n\t#closed = false;\n\n\tprivate constructor(\n\t\ttargets: readonly ResolvedTarget[],\n\t\tonChange: (change: WatchChange) => void,\n\t\tmode: \"native\" | \"polling\",\n\t\toptions: NodeWatchOptions,\n\t) {\n\t\tthis.#targets = targets;\n\t\tthis.#onChange = onChange;\n\t\tthis.#mode = mode;\n\t\tthis.#pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;\n\t\tthis.#maxDirectories = options.maxDirectories ?? DEFAULT_MAX_DIRECTORIES;\n\t}\n\n\t/** Establish coverage: watchers first, then the snapshot later changes are compared with. */\n\tstatic async open(\n\t\ttargets: readonly WatchTarget[],\n\t\tresolvePath: (path: string) => string,\n\t\tonChange: (change: WatchChange) => void,\n\t\toptions: NodeWatchOptions,\n\t): Promise<NodeFileWatcher> {\n\t\tconst resolved = targets.map((target) => ({\n\t\t\tpath: resolvePath(target.path),\n\t\t\trecursive: target.recursive === true,\n\t\t\thidden: target.exclude?.hidden === true,\n\t\t\tnames: new Set(target.exclude?.names ?? []),\n\t\t}));\n\t\t// Windows refuses to rename a directory while another directory below it is open, and a native watcher keeps\n\t\t// every watched directory open: watching would break renames of their parents, so Windows polls.\n\t\tconst mode =\n\t\t\toptions.mode ??\n\t\t\t(process.platform === \"win32\" || (await anyUnreliable(resolved.map((target) => target.path)))\n\t\t\t\t? \"polling\"\n\t\t\t\t: \"native\");\n\t\tconst watcher = new NodeFileWatcher(resolved, onChange, mode, options);\n\t\ttry {\n\t\t\tawait watcher.#sync(false);\n\t\t} catch (error) {\n\t\t\twatcher.#stop();\n\t\t\tif (error instanceof BudgetExceeded) throw new FileError(\"invalid\", error.message);\n\t\t\tthrow error;\n\t\t}\n\t\twatcher.#schedulePoll();\n\t\treturn watcher;\n\t}\n\n\tget mode(): \"native\" | \"polling\" {\n\t\treturn this.#mode;\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#stop();\n\t\tawait this.#running;\n\t}\n\n\t#stop(): void {\n\t\tthis.#closed = true;\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tclearTimeout(this.#settleTimer);\n\t\tthis.#settleTimer = undefined;\n\t\tfor (const watcher of this.#watchers.values()) watcher.close();\n\t\tthis.#watchers.clear();\n\t}\n\n\t#deliver(change: WatchChange): void {\n\t\tif (this.#closed) return;\n\t\ttry {\n\t\t\tthis.#onChange(change);\n\t\t} catch {\n\t\t\t// A throwing callback must not stop watching.\n\t\t}\n\t}\n\n\t#schedulePoll(): void {\n\t\tif (this.#closed || this.#mode !== \"polling\") return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#flush().finally(() => this.#schedulePoll());\n\t\t}, this.#pollIntervalMs);\n\t}\n\n\t#scheduleFlush(): void {\n\t\tif (this.#closed || this.#timer !== undefined) return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#flush();\n\t\t}, DEBOUNCE_MS);\n\t}\n\n\t#scheduleSettle(): void {\n\t\tif (this.#closed) return;\n\t\tclearTimeout(this.#settleTimer);\n\t\tthis.#settleTimer = setTimeout(() => {\n\t\t\tthis.#settleTimer = undefined;\n\t\t\tvoid this.#flush();\n\t\t}, FSEVENTS_SETTLE_MS);\n\t}\n\n\t#flush(): Promise<void> {\n\t\tif (this.#running !== undefined) {\n\t\t\tthis.#dirty = true;\n\t\t\treturn this.#running;\n\t\t}\n\t\tthis.#running = (async () => {\n\t\t\ttry {\n\t\t\t\tdo {\n\t\t\t\t\tthis.#dirty = false;\n\t\t\t\t\tconst events = [...this.#events];\n\t\t\t\t\tthis.#events.clear();\n\t\t\t\t\tconst changed = await this.#sync(true);\n\t\t\t\t\tfor (const path of events) changed.add(path);\n\t\t\t\t\tif (changed.size > 0) this.#deliver({ paths: [...changed].sort() });\n\t\t\t\t} while (this.#dirty && !this.#closed);\n\t\t\t} catch (error) {\n\t\t\t\tconst fileError =\n\t\t\t\t\terror instanceof FileError\n\t\t\t\t\t\t? error\n\t\t\t\t\t\t: new FileError(\"invalid\", error instanceof Error ? error.message : String(error));\n\t\t\t\tthis.#deliver({ error: fileError });\n\t\t\t\tthis.#stop();\n\t\t\t} finally {\n\t\t\t\tthis.#running = undefined;\n\t\t\t}\n\t\t})();\n\t\treturn this.#running;\n\t}\n\n\t/** Rescan, report differences, and install watchers for new directories, rescanning until none are new. */\n\tasync #sync(report: boolean): Promise<Set<string>> {\n\t\tconst changed = new Set<string>();\n\t\tfor (let round = 0; round < 10 && !this.#closed; round++) {\n\t\t\tconst next = await this.#scan();\n\t\t\tif (report) for (const path of this.#diff(this.#snapshot, next)) changed.add(path);\n\t\t\tthis.#snapshot = next;\n\t\t\tif (this.#mode === \"polling\" || !this.#reconcileWatchers(next)) break;\n\t\t\tif (process.platform === \"darwin\") this.#scheduleSettle();\n\t\t\t// Something written into a new directory before its watcher existed shows up in the next round.\n\t\t\treport = true;\n\t\t}\n\t\treturn changed;\n\t}\n\n\t#diff(previous: Snapshot, next: Snapshot): string[] {\n\t\tconst changed: string[] = [];\n\t\tfor (const [path, entry] of next) {\n\t\t\tconst before = previous.get(path);\n\t\t\tif (before === undefined || !sameEntry(before, entry)) changed.push(this.#reported(path));\n\t\t}\n\t\tfor (const path of previous.keys()) if (!next.has(path)) changed.push(this.#reported(path));\n\t\treturn changed;\n\t}\n\n\t/** An ancestor that changed identity moved every target below it; report those targets. */\n\t#reported(path: string): string {\n\t\tif (this.#targets.some((target) => isWithin(path, target.path))) return path;\n\t\treturn this.#targets.find((target) => isWithin(target.path, path))?.path ?? path;\n\t}\n\n\tasync #scan(): Promise<Snapshot> {\n\t\tconst snapshot: Snapshot = new Map();\n\t\tlet directories = 0;\n\t\tconst countDirectory = (): void => {\n\t\t\tif (++directories > this.#maxDirectories) {\n\t\t\t\tthrow new BudgetExceeded(`Watched paths exceed ${this.#maxDirectories} directories`);\n\t\t\t}\n\t\t};\n\t\tconst record = async (path: string, stats: Stats): Promise<void> => {\n\t\t\tlet hash: string | undefined;\n\t\t\tif (\n\t\t\t\tthis.#mode === \"polling\" &&\n\t\t\t\tstats.isFile() &&\n\t\t\t\tstats.size <= HASH_MAX_BYTES &&\n\t\t\t\tDate.now() - stats.mtimeMs < HASH_RECENT_MS\n\t\t\t) {\n\t\t\t\thash = await readFile(path).then(\n\t\t\t\t\t(content) => createHash(\"sha256\").update(content).digest(\"hex\"),\n\t\t\t\t\t() => undefined,\n\t\t\t\t);\n\t\t\t}\n\t\t\tsnapshot.set(path, entryOf(stats, hash));\n\t\t};\n\t\tconst scanDirectory = async (target: ResolvedTarget, directory: string): Promise<void> => {\n\t\t\tlet names: string[];\n\t\t\ttry {\n\t\t\t\tnames = await readdir(directory);\n\t\t\t} catch (error) {\n\t\t\t\tif (isNodeError(error) && [\"ENOENT\", \"EACCES\", \"EPERM\", \"ENOTDIR\"].includes(error.code ?? \"\")) return;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tfor (const name of names) {\n\t\t\t\tif (excluded(target, name)) continue;\n\t\t\t\tconst path = `${directory.endsWith(sep) ? directory : directory + sep}${name}`;\n\t\t\t\tif (snapshot.has(path)) continue;\n\t\t\t\tconst stats = await lstat(path).catch(() => undefined);\n\t\t\t\tif (stats === undefined) continue;\n\t\t\t\tawait record(path, stats);\n\t\t\t\tif (target.recursive && stats.isDirectory()) {\n\t\t\t\t\tcountDirectory();\n\t\t\t\t\tawait scanDirectory(target, path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tfor (const target of this.#targets) {\n\t\t\tfor (const ancestor of ancestorsOf(target.path)) {\n\t\t\t\tif (snapshot.has(ancestor)) continue;\n\t\t\t\tconst stats = await lstat(ancestor).catch(() => undefined);\n\t\t\t\t// Identity only: an ancestor's own timestamps change with every unrelated sibling.\n\t\t\t\tif (stats !== undefined) snapshot.set(ancestor, { ...entryOf(stats), size: 0, mtimeMs: 0 });\n\t\t\t}\n\t\t\t// The target itself may be a symbolic link to what is watched; follow it.\n\t\t\tconst stats = await stat(target.path).catch(() => undefined);\n\t\t\tif (stats === undefined) continue;\n\t\t\tawait record(target.path, stats);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tcountDirectory();\n\t\t\t\tawait scanDirectory(target, target.path);\n\t\t\t}\n\t\t}\n\t\treturn snapshot;\n\t}\n\n\t/**\n\t * Watch every existing ancestor of each target, each target directory, and on Linux each directory below a recursive\n\t * target (elsewhere one recursive watcher per target). Returns whether a watcher was added.\n\t */\n\t#reconcileWatchers(snapshot: Snapshot): boolean {\n\t\tconst wanted = new Map<string, boolean>();\n\t\tconst perDirectory = process.platform === \"linux\" || process.platform === \"android\";\n\t\tfor (const target of this.#targets) {\n\t\t\tfor (const ancestor of ancestorsOf(target.path)) {\n\t\t\t\tif (snapshot.get(ancestor)?.kind === \"directory\" && !wanted.has(ancestor)) wanted.set(ancestor, false);\n\t\t\t}\n\t\t\tif (snapshot.get(target.path)?.kind !== \"directory\") continue;\n\t\t\tconst recursiveRoot = target.recursive && !perDirectory;\n\t\t\twanted.set(target.path, recursiveRoot);\n\t\t\tif (target.recursive && perDirectory) {\n\t\t\t\tfor (const [path, entry] of snapshot) {\n\t\t\t\t\tif (entry.kind === \"directory\" && path !== target.path && isWithin(path, target.path)) {\n\t\t\t\t\t\twanted.set(path, false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const [path, watcher] of this.#watchers) {\n\t\t\tif (!wanted.has(path)) {\n\t\t\t\twatcher.close();\n\t\t\t\tthis.#watchers.delete(path);\n\t\t\t}\n\t\t}\n\t\tlet added = false;\n\t\tfor (const [path, recursive] of wanted) {\n\t\t\tif (this.#watchers.has(path)) continue;\n\t\t\ttry {\n\t\t\t\tconst watcher = fsWatch(path, { recursive, persistent: false }, (_event, filename) =>\n\t\t\t\t\tthis.#onEvent(path, filename === null ? undefined : String(filename)),\n\t\t\t\t);\n\t\t\t\t// A watched directory that disappears or fails: rescan, which also replaces the watcher.\n\t\t\t\twatcher.on(\"error\", () => {\n\t\t\t\t\twatcher.close();\n\t\t\t\t\tif (this.#watchers.get(path) === watcher) this.#watchers.delete(path);\n\t\t\t\t\tthis.#scheduleFlush();\n\t\t\t\t});\n\t\t\t\tthis.#watchers.set(path, watcher);\n\t\t\t\tadded = true;\n\t\t\t} catch (error) {\n\t\t\t\t// Out of watches or unsupported: compare snapshots from now on, and say coverage was uncertain.\n\t\t\t\tif (isNodeError(error) && error.code !== \"ENOENT\" && error.code !== \"EACCES\" && error.code !== \"EPERM\") {\n\t\t\t\t\tthis.#switchToPolling();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn added;\n\t}\n\n\t#switchToPolling(): void {\n\t\tif (this.#mode === \"polling\") return;\n\t\tthis.#mode = \"polling\";\n\t\tfor (const watcher of this.#watchers.values()) watcher.close();\n\t\tthis.#watchers.clear();\n\t\tthis.#deliver({ overflow: true });\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#schedulePoll();\n\t}\n\n\t#onEvent(directory: string, filename: string | undefined): void {\n\t\tif (this.#closed) return;\n\t\tconst path =\n\t\t\tfilename === undefined ? directory : `${directory.endsWith(sep) ? directory : directory + sep}${filename}`;\n\t\t// Events about unrelated siblings of an ancestor, or about excluded entries, are ignored.\n\t\tconst relevant = this.#inScope(path);\n\t\tif (relevant) this.#events.add(this.#reported(path));\n\t\tif (relevant || filename === undefined) this.#scheduleFlush();\n\t}\n\n\t#inScope(path: string): boolean {\n\t\tfor (const target of this.#targets) {\n\t\t\tif (isWithin(target.path, path)) return true;\n\t\t\tif (!isWithin(path, target.path) || path === target.path) continue;\n\t\t\tconst components = relative(target.path, path).split(sep);\n\t\t\tif (!target.recursive && components.length > 1) continue;\n\t\t\tif (components.some((name) => excluded(target, name))) continue;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/** Whether any path, or its nearest existing ancestor, is on a file system that does not report remote changes. */\nasync function anyUnreliable(paths: readonly string[]): Promise<boolean> {\n\tif (process.platform !== \"linux\" && process.platform !== \"android\") return false;\n\tfor (const path of paths) {\n\t\tfor (const candidate of [path, ...ancestorsOf(path)]) {\n\t\t\tconst info = await statfs(candidate).catch(() => undefined);\n\t\t\tif (info === undefined) continue;\n\t\t\tif (UNRELIABLE_FILE_SYSTEMS.has(info.type)) return true;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn false;\n}\n"]}
|
package/dist/env/node.d.ts
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
import type { Context } from "@earendil-works/chord";
|
|
2
|
-
import { type ExecutionEnv, ExecutionError, FileError, type FileInfo, type Result, type ShellExecOptions, type ShellExecResult, type TextLineReader } from "./index.ts";
|
|
2
|
+
import { type BinaryReader, type DirReader, type ExecutionEnv, ExecutionError, FileError, type FileInfo, type FileWatcher, type Result, type ShellExecOptions, type ShellExecResult, type TextLineReader, type WatchChange, type WatchTarget } from "./index.ts";
|
|
3
|
+
import { type NodeWatchOptions } from "./node-watch.ts";
|
|
3
4
|
export declare class NodeExecutionEnv implements ExecutionEnv {
|
|
4
5
|
/** Every local environment sees the same files. */
|
|
5
6
|
readonly id: string;
|
|
6
7
|
cwd: string;
|
|
7
8
|
private shellPath?;
|
|
8
9
|
private shellEnv?;
|
|
10
|
+
private watchOptions;
|
|
9
11
|
private activeChildPids;
|
|
10
12
|
constructor(options: {
|
|
11
13
|
cwd: string;
|
|
12
14
|
shellPath?: string;
|
|
13
15
|
shellEnv?: NodeJS.ProcessEnv;
|
|
16
|
+
watch?: NodeWatchOptions;
|
|
14
17
|
});
|
|
18
|
+
watch(targets: readonly WatchTarget[], onChange: (change: WatchChange) => void, context: Context): Promise<Result<FileWatcher, FileError>>;
|
|
15
19
|
absolutePath(path: string, _context: Context): Promise<Result<string, FileError>>;
|
|
16
20
|
joinPath(parts: string[], _context: Context): Promise<Result<string, FileError>>;
|
|
17
|
-
exec(command: string, options: ShellExecOptions | undefined, context: Context): Promise<Result<ShellExecResult, ExecutionError>>;
|
|
21
|
+
exec(command: string | readonly string[], options: ShellExecOptions | undefined, context: Context): Promise<Result<ShellExecResult, ExecutionError>>;
|
|
18
22
|
openTextLineReader(path: string, context: Context): Promise<Result<TextLineReader, FileError>>;
|
|
19
23
|
readTextFile(path: string, context: Context): Promise<Result<string, FileError>>;
|
|
20
24
|
readTextLines(path: string, options: {
|
|
21
25
|
maxLines?: number;
|
|
22
26
|
} | undefined, context: Context): Promise<Result<string[], FileError>>;
|
|
23
27
|
readBinaryFile(path: string, context: Context): Promise<Result<Uint8Array, FileError>>;
|
|
28
|
+
openBinaryReader(path: string, options: {
|
|
29
|
+
noFollow?: boolean;
|
|
30
|
+
} | undefined, context: Context): Promise<Result<BinaryReader, FileError>>;
|
|
24
31
|
writeFile(path: string, content: string | Uint8Array, context: Context): Promise<Result<void, FileError>>;
|
|
25
32
|
appendFile(path: string, content: string | Uint8Array, context: Context): Promise<Result<void, FileError>>;
|
|
26
33
|
truncateFile(path: string, size: number, context: Context): Promise<Result<void, FileError>>;
|
|
@@ -28,6 +35,7 @@ export declare class NodeExecutionEnv implements ExecutionEnv {
|
|
|
28
35
|
renameFile(sourcePath: string, destinationPath: string, context: Context): Promise<Result<void, FileError>>;
|
|
29
36
|
fileInfo(path: string, context: Context): Promise<Result<FileInfo, FileError>>;
|
|
30
37
|
listDir(path: string, context: Context): Promise<Result<FileInfo[], FileError>>;
|
|
38
|
+
openDirReader(path: string, context: Context): Promise<Result<DirReader, FileError>>;
|
|
31
39
|
canonicalPath(path: string, context: Context): Promise<Result<string, FileError>>;
|
|
32
40
|
exists(path: string, context: Context): Promise<Result<boolean, FileError>>;
|
|
33
41
|
createDir(path: string, options: {
|
|
@@ -44,4 +52,5 @@ export declare class NodeExecutionEnv implements ExecutionEnv {
|
|
|
44
52
|
} | undefined, context: Context): Promise<Result<string, FileError>>;
|
|
45
53
|
cleanup(_context: Context): Promise<void>;
|
|
46
54
|
}
|
|
55
|
+
export type { NodeWatchOptions } from "./node-watch.ts";
|
|
47
56
|
//# sourceMappingURL=node.d.ts.map
|
package/dist/env/node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/env/node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/env/node.ts"],"names":[],"mappings":"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"}
|