Utilities for watching file trees in Node.js applications with EventEmitter patterns and CLI support
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Watch provides utilities for watching file trees in Node.js applications. It offers two primary interfaces: watchTree for simple callback-based file monitoring, and createMonitor for event-driven monitoring with EventEmitter patterns. The library supports comprehensive filtering options and includes a command-line interface for automated command execution on file changes.
npm install watchconst watch = require("watch");For individual imports:
const { watchTree, unwatchTree, createMonitor, walk } = require("watch");const watch = require("watch");
// Simple file watching with callback
watch.watchTree("/path/to/watch", function (f, curr, prev) {
if (typeof f == "object" && prev === null && curr === null) {
// Finished walking the tree
console.log("Watching", Object.keys(f).length, "files");
} else if (prev === null) {
// New file
console.log("New file:", f);
} else if (curr.nlink === 0) {
// File removed
console.log("Removed:", f);
} else {
// File changed
console.log("Changed:", f);
}
});
// Event-driven monitoring
watch.createMonitor("/path/to/watch", function (monitor) {
monitor.on("created", function (f, stat) {
console.log("Created:", f);
});
monitor.on("changed", function (f, curr, prev) {
console.log("Changed:", f);
});
monitor.on("removed", function (f, stat) {
console.log("Removed:", f);
});
// Stop monitoring
// monitor.stop();
});Monitor file and directory changes with customizable filtering and polling options.
/**
* Watch a directory tree for file changes
* @param {string} root - Directory path to watch
* @param {WatchOptions|WatchCallback} [options] - Watch configuration options (optional)
* @param {WatchCallback} callback - Called on file changes and setup completion
* Note: If options is not provided, the second parameter becomes the callback
*/
function watchTree(root, options, callback);
/**
* Stop watching a directory tree
* @param {string} root - Directory path to stop watching
*/
function unwatchTree(root);
interface WatchOptions {
/** Skip files starting with "." */
ignoreDotFiles?: boolean;
/** Function to filter files/directories (return true to include) */
filter?: (filepath: string, stat: Stats) => boolean;
/** Polling interval in seconds */
interval?: number;
/** Skip unreadable directories silently (handles EACCES errors) */
ignoreUnreadableDir?: boolean;
/** Skip files with permission issues silently (handles EPERM errors) */
ignoreNotPermitted?: boolean;
/** Skip directories matching this pattern */
ignoreDirectoryPattern?: RegExp;
}
/**
* Callback for watchTree file changes
* @param {string|object} f - File path or file hash object (on completion)
* @param {Stats|null} curr - Current file stats
* @param {Stats|null} prev - Previous file stats (null for new files)
*/
type WatchCallback = (f: string | object, curr: Stats | null, prev: Stats | null) => void;Callback Behavior:
f is an object (file hash), curr and prev are nullprev is null, curr contains file statscurr.nlink is 0 (file's link count is zero)curr and prev contain stats, and modification times differImportant: The function uses fs.watchFile internally and only triggers callbacks for actual content changes (based on mtime comparison), not just access times.
Create an EventEmitter-based monitor for advanced file system event handling.
/**
* Create an EventEmitter-based file monitor
* @param {string} root - Directory path to monitor
* @param {WatchOptions|MonitorCallback} [options] - Monitor configuration options (optional)
* @param {MonitorCallback} callback - Called with monitor object when ready
* Note: If options is not provided, the second parameter becomes the callback
*/
function createMonitor(root, options, callback);
/**
* Callback for createMonitor setup completion
* @param {Monitor} monitor - The configured monitor instance
*/
type MonitorCallback = (monitor: Monitor) => void;
interface Monitor extends EventEmitter {
/** Hash of file paths to current stat objects */
files: { [filepath: string]: Stats };
/** Stop monitoring (calls unwatchTree) */
stop(): void;
}Monitor Events:
'created' - New file created: (filename: string, stat: Stats)'removed' - File removed: (filename: string, stat: Stats)'changed' - File modified: (filename: string, curr: Stats, prev: Stats)Note: The monitor includes deduplication logic to prevent duplicate events for the same file and action type within rapid succession.
Recursively walk a directory tree and build a file hash without watching.
/**
* Recursively walk a directory tree
* @param {string} dir - Directory to walk
* @param {WalkOptions|WalkCallback} [options] - Walk configuration options (optional)
* @param {WalkCallback} callback - Called with results when complete
* Note: If options is not provided, the second parameter becomes the callback
*/
function walk(dir, options, callback);
interface WalkOptions {
/** Skip files starting with "." */
ignoreDotFiles?: boolean;
/** Function to filter files/directories (return true to include) */
filter?: (filepath: string, stat: Stats) => boolean;
/** Skip unreadable directories silently (handles EACCES errors) */
ignoreUnreadableDir?: boolean;
/** Skip files with permission issues silently (handles EPERM errors) */
ignoreNotPermitted?: boolean;
/** Skip directories matching this pattern */
ignoreDirectoryPattern?: RegExp;
}
/**
* Callback for walk completion
* @param {Error|null} err - Error if walk failed
* @param {object} files - Hash of file paths to stat objects
*/
type WalkCallback = (err: Error | null, files: { [filepath: string]: Stats }) => void;Execute commands automatically when files change, with throttling and filtering support. Install globally with npm install watch -g to use the watch command anywhere.
# Basic usage
watch <command> [...directory]
# With options
watch "npm test" ./src --wait=2 --ignoreDotFiles
# Available options:
--wait=<seconds>, -w # Throttle command execution (seconds)
--filter=<file>, -f # Path to filter function module (exports filter function)
--interval=<seconds>, -i # Polling interval (default: 0.2)
--ignoreDotFiles, -d # Ignore dot files
--ignoreUnreadable, -u # Ignore unreadable directories
--ignoreDirectoryPattern=<regexp>, -p # Ignore matching directoriesThe CLI tool watches specified directories (defaults to current working directory) and executes the given command whenever files change. The --wait option prevents rapid repeated executions.
Filter Module Example:
The --filter option accepts a path to a JavaScript file that exports a filter function:
// myfilter.js
module.exports = function (filepath, stat) {
// Return true to include file in watching, false to exclude
return stat.isDirectory() || filepath.match(/\.(js|ts|json)$/);
};Then use: watch "npm test" ./src --filter=./myfilter.js
/**
* Node.js fs.Stats object containing file metadata
*/
interface Stats {
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
size: number;
mtime: Date;
nlink: number;
// ... other fs.Stats properties
}
/**
* Filter function type for determining which files to include
* @param {string} filepath - Full path to file or directory
* @param {Stats} stat - File system stats object
* @returns {boolean} True to include file in watching/walking
*/
type FilterFunction = (filepath: string, stat: Stats) => boolean;const watch = require("watch");
// Custom filter function
function sourceFilter(f, stat) {
return stat.isDirectory() || f.match(/\.(js|ts|json)$/);
}
watch.watchTree("./src", {
filter: sourceFilter,
ignoreDotFiles: true,
ignoreDirectoryPattern: /node_modules/
}, function (f, curr, prev) {
if (typeof f !== "object") {
console.log("Source file changed:", f);
}
});const watch = require("watch");
let monitor;
watch.createMonitor("./project", {
interval: 1,
ignoreDotFiles: true
}, function (m) {
monitor = m;
monitor.on("created", (f) => console.log("+ " + f));
monitor.on("removed", (f) => console.log("- " + f));
monitor.on("changed", (f) => console.log("~ " + f));
});
// Cleanup on exit
process.on("SIGINT", function() {
if (monitor) {
monitor.stop();
}
process.exit();
});const watch = require("watch");
watch.walk("./docs", {
ignoreDotFiles: true,
filter: (f, stat) => stat.isDirectory() || f.endsWith('.md')
}, function (err, files) {
if (err) throw err;
console.log("Found", Object.keys(files).length, "markdown files:");
Object.keys(files).forEach(file => {
if (files[file].isFile()) {
console.log("-", file);
}
});
});