(server: RuntimeServer)
| 58 | } |
| 59 | |
| 60 | export function registerRuntimeNodeFsWatchModule(server: RuntimeServer): () => void { |
| 61 | const fsWatches = new Map<string, FsWatchState>() |
| 62 | |
| 63 | server.registerNotification("fs/watch/event") |
| 64 | server.registerNotification("fs/watch/stopped") |
| 65 | |
| 66 | server.register( |
| 67 | "fs/watch/start", |
| 68 | (params) => { |
| 69 | const record = asRecord(params) |
| 70 | const dir = requiredString(record, "dir") |
| 71 | if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) throw new Error("dir must be an existing directory") |
| 72 | |
| 73 | const watchId = typeof record.watchId === "string" && record.watchId.length > 0 ? record.watchId : `watch-${randomUUID()}` |
| 74 | if (fsWatches.has(watchId)) return { watchId, runtimeId: fsWatchRuntimeId(watchId), reused: true } |
| 75 | |
| 76 | const watcher = fs.watch(dir, { persistent: false }, (eventType: string, filename: string | Buffer | null) => { |
| 77 | emitWatchEvent(server, watchId, dir, eventType, filename?.toString()) |
| 78 | }) |
| 79 | watcher.on("error", (error: Error) => { |
| 80 | const runtime = server.supervisor.update(fsWatchRuntimeId(watchId), { |
| 81 | status: "failed", |
| 82 | error: error instanceof Error ? error.message : "File watcher failed", |
| 83 | }) |
| 84 | server.notify("runtime/failed", runtime) |
| 85 | }) |
| 86 | |
| 87 | const state: FsWatchState = { |
| 88 | watcher, |
| 89 | dir, |
| 90 | snapshot: readDirectorySnapshot(dir), |
| 91 | poller: setInterval(() => { |
| 92 | const current = readDirectorySnapshot(dir) |
| 93 | for (const [filename, mtimeMs] of current) { |
| 94 | if (state.snapshot.get(filename) !== mtimeMs) { |
| 95 | emitWatchEvent(server, watchId, dir, "change", filename) |
| 96 | } |
| 97 | } |
| 98 | for (const filename of state.snapshot.keys()) { |
| 99 | if (!current.has(filename)) { |
| 100 | emitWatchEvent(server, watchId, dir, "rename", filename) |
| 101 | } |
| 102 | } |
| 103 | state.snapshot = current |
| 104 | }, 250), |
| 105 | } |
| 106 | state.poller.unref?.() |
| 107 | fsWatches.set(watchId, state) |
| 108 | const runtime = server.supervisor.create({ |
| 109 | runtimeId: fsWatchRuntimeId(watchId), |
| 110 | kind: "fsWatch", |
| 111 | status: "running", |
| 112 | scope: { |
| 113 | ownerType: "fs-watch", |
| 114 | ownerId: watchId, |
| 115 | rootPath: dir, |
| 116 | }, |
| 117 | nativeId: watchId, |
no test coverage detected