Skip to content

Commit

Permalink
Replace watch with FsWatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexagon committed Apr 29, 2024
1 parent af0cb90 commit 6887626
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 6 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Methods:

| Method | Deno | Node | Bun | Base implementation |
| --------- | ---- | ---- | --- | ------------------- |
| FsWatcher | X | X | X | custom |
| unlink | X | X | X | node:fs/promises |
| dirpath | X | X | X | @cross/dir |
| mkdir | X | X | X | node:fs/promises |
Expand All @@ -111,7 +112,6 @@ Methods:
| chmod | X | X | X | node:fs/promises |
| chown | X | X | X | node:fs/promises |
| rename | X | X | X | node:fs/promises |
| watch | X | X | X | node:fs/promises |
| truncate | X | X | X | node:fs/promises |
| open | X | X | X | node:fs/promises |
| access | X | X | X | node:fs/promises |
Expand Down
8 changes: 4 additions & 4 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cross/fs",
"version": "0.0.10",
"version": "0.1.11",
"exports": {
".": "./mod.ts",
"./stat": "./src/stat/mod.ts",
Expand All @@ -12,9 +12,9 @@
"@cross/env": "jsr:@cross/env@^1.0.0",
"@cross/runtime": "jsr:@cross/runtime@^1.0.0",
"@cross/test": "jsr:@cross/test@^0.0.9",
"@cross/utils": "jsr:@cross/utils@^0.11.0",
"@std/assert": "jsr:@std/assert@^0.223.0",
"@std/path": "jsr:@std/path@^0.223.0"
"@cross/utils": "jsr:@cross/utils@^0.12.0",
"@std/assert": "jsr:@std/assert@^0.224.0",
"@std/path": "jsr:@std/path@^0.224.0"
},
"publish": {
"exclude": [".github", "*.test.ts"]
Expand Down
2 changes: 1 addition & 1 deletion src/ops/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export {
rmdir,
truncate,
unlink,
watch,
} from "node:fs/promises";

export type { FSWatcher } from "node:fs";
Expand All @@ -20,3 +19,4 @@ export * from "./mktempdir.ts";
export * from "./tempfile.ts";
export * from "./chdir.ts";
export * from "./cwd.ts";
export * from "./watch.ts";
28 changes: 28 additions & 0 deletions src/ops/watch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { assertEquals } from "@std/assert";
import { test } from "@cross/test";
import { mktempdir, rm } from "./mod.ts";
import { join } from "@std/path";
import { type FileSystemEvent, FsWatcher } from "./watch.ts";
import { writeFile } from "../io/mod.ts";

test("FsWatcher watches for file changes", async () => {
const watcher = FsWatcher();
const tempdir = await mktempdir();
const filePath = join(tempdir, "test.txt");
const events: FileSystemEvent[] = [];
setTimeout(async () => {
await writeFile(filePath, "Hello");
}, 1000);
for await (const event of watcher.watch(tempdir)) {
if (event.kind === "modify" && filePath == event.paths[0]) {
events.push(event);
break; // Stop watching after the creation event
}
}
await new Promise((resolve) => setTimeout(resolve, 1000)); // Allow some time
watcher.close();
await rm(tempdir, { recursive: true });
assertEquals(events.length, 1);
assertEquals(events[0].kind, "modify");
assertEquals(events[0].paths[0], filePath);
});
91 changes: 91 additions & 0 deletions src/ops/watch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { CurrentRuntime, Runtime } from "@cross/runtime";
import { watch as nodeWatch } from "node:fs/promises";
import type { WatchOptions } from "node:fs";
import { join } from "@std/path";

export interface FileSystemWatcherOptions {
recursive: boolean;
signal?: AbortSignal;
}
export type FileSystemEventKind =
| "error"
| "any"
| "access"
| "create"
| "modify"
| "remove"
| "other";
export interface FileSystemEvent {
kind: FileSystemEventKind;
paths: (string | undefined)[];
}
export interface Watcher {
watch(
path: string,
options?: FileSystemWatcherOptions,
): AsyncIterable<FileSystemEvent>;
close(): void;
}
export function FsWatcher(): Watcher {
let denoWatcher: Deno.FsWatcher | undefined;
let nodeWatcher: AsyncIterable<unknown>;
const ac = new AbortController();
return {
async *watch(
path: string,
options?: FileSystemWatcherOptions,
): AsyncIterable<FileSystemEvent> {
try {
if (CurrentRuntime === Runtime.Deno) {
denoWatcher = Deno.watchFs(path, options);
for await (const event of denoWatcher) {
yield event;
}
} else if (
CurrentRuntime === Runtime.Node || CurrentRuntime === Runtime.Bun
) {
const usedOptions: FileSystemWatcherOptions = options
? options
: { recursive: true };
if (!options?.signal) usedOptions.signal = ac.signal;
nodeWatcher = await nodeWatch(path, usedOptions as WatchOptions);
for await (const event of nodeWatcher) {
//@ts-ignore cross-runtime
if (event.filename) {
const generatedEvent = {
//@ts-ignore cross-runtime
kind: (event.eventType === "change"
? "modify"
//@ts-ignore cross-runtime
: event.eventType) as FileSystemEventKind,
//@ts-ignore cross-runtime
paths: [join(path, event.filename?.toString())],
};
yield generatedEvent;
}
}
} else {
throw new Error("cross/watchFs: Runtime not supported.");
}
} catch (err) {
if (err.name === "AbortError") {
/* Ok! */
} else {
throw new Error(
"Cannot start asynchronous filesystem watcher using current runtime.",
);
}
}
},
close() {
if (denoWatcher) {
try {
denoWatcher.close();
} catch (_e) { /* Ignore */ }
}
if (nodeWatcher) {
ac?.abort();
}
},
};
}

0 comments on commit 6887626

Please sign in to comment.