diff --git a/src/emulation/async.ts b/src/emulation/async.ts index 91d4e167..57dbd3d3 100644 --- a/src/emulation/async.ts +++ b/src/emulation/async.ts @@ -588,7 +588,7 @@ export function watchFile( return; } - const watcher = new StatWatcher(normalizedPath, opts); + const watcher = new StatWatcher(this, normalizedPath, opts); watcher.on('change', (curr: Stats, prev: Stats) => { const entry = statWatchers.get(normalizedPath); if (!entry) { @@ -639,7 +639,7 @@ export function watch( options?: fs.WatchOptions | ((event: string, filename: string) => any), listener?: (event: string, filename: string) => any ): FSWatcher { - const watcher = new FSWatcher(normalizePath(path), typeof options == 'object' ? options : {}); + const watcher = new FSWatcher(this, normalizePath(path), typeof options == 'object' ? options : {}); listener = typeof options == 'function' ? options : listener; watcher.on('change', listener || nop); return watcher; diff --git a/src/emulation/promises.ts b/src/emulation/promises.ts index b0b8c9b0..6a6be1a3 100644 --- a/src/emulation/promises.ts +++ b/src/emulation/promises.ts @@ -943,13 +943,17 @@ export async function realpath(this: V_Context, path: fs.PathLike, options?: fs. } realpath satisfies typeof promises.realpath; -export function watch(filename: fs.PathLike, options?: fs.WatchOptions | BufferEncoding): AsyncIterable>; -export function watch(filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable>; -export function watch(filename: fs.PathLike, options?: fs.WatchOptions | string): AsyncIterable> | AsyncIterable>; -export function watch(filename: fs.PathLike, options: fs.WatchOptions | string = {}): AsyncIterable> { +export function watch(this: V_Context, filename: fs.PathLike, options?: fs.WatchOptions | BufferEncoding): AsyncIterable>; +export function watch(this: V_Context, filename: fs.PathLike, options: fs.WatchOptions | fs.BufferEncodingOption): AsyncIterable>; +export function watch( + this: V_Context, + filename: fs.PathLike, + options?: fs.WatchOptions | string +): AsyncIterable> | AsyncIterable>; +export function watch(this: V_Context, filename: fs.PathLike, options: fs.WatchOptions | string = {}): AsyncIterable> { return { [Symbol.asyncIterator](): AsyncIterator> { - const watcher = new FSWatcher(filename.toString(), typeof options !== 'string' ? options : { encoding: options as BufferEncoding | 'buffer' }); + const watcher = new FSWatcher(this, filename.toString(), typeof options !== 'string' ? options : { encoding: options as BufferEncoding | 'buffer' }); // A queue to hold change events, since we need to resolve them in the async iterator const eventQueue: ((value: IteratorResult>) => void)[] = []; diff --git a/src/emulation/watchers.ts b/src/emulation/watchers.ts index 9577081a..51aa4467 100644 --- a/src/emulation/watchers.ts +++ b/src/emulation/watchers.ts @@ -1,6 +1,7 @@ import { EventEmitter } from 'eventemitter3'; import type { EventEmitter as NodeEventEmitter } from 'node:events'; import type * as fs from 'node:fs'; +import type { V_Context } from '../context.js'; import { ErrnoError } from '../error.js'; import { isStatsEqual, type Stats } from '../stats.js'; import { normalizePath } from '../utils.js'; @@ -24,7 +25,13 @@ class Watcher = Record implements fs.FSWatcher { public constructor( + context: V_Context, path: string, public readonly options: fs.WatchOptions ) { - super(path); + super(context, path); addWatcher(path.toString(), this); } @@ -105,10 +113,11 @@ export class StatWatcher private previous?: Stats; public constructor( + context: V_Context, path: string, private options: { persistent?: boolean; interval?: number } ) { - super(path); + super(context, path); this.start(); } @@ -185,10 +194,16 @@ export function emitChange(eventType: fs.WatchEventType, filename: string) { while (parent !== normalizedFilename) { normalizedFilename = parent; parent = dirname(parent); - if (watchers.has(parent)) { - for (const watcher of watchers.get(parent)!) { - watcher.emit('change', eventType, filename.slice(parent.length + (parent == '/' ? 0 : 1))); - } + if (!watchers.has(parent)) continue; + + for (const watcher of watchers.get(parent)!) { + // Strip the context root from the path if the watcher has a context + + const root = watcher._context?.root; + const contextPath = root && filename.startsWith(root) ? filename.slice(root.length) : filename; + const relativePath = contextPath.slice(parent.length + (parent == '/' ? 0 : 1)); + + watcher.emit('change', eventType, relativePath); } } }