or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-watch

Utilities for watching file trees in Node.js applications with EventEmitter patterns and CLI support

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/watch@1.0.x

To install, run

npx @tessl/cli install tessl/npm-watch@1.0.0

index.mddocs/

Watch

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.

Package Information

  • Package Name: watch
  • Package Type: npm
  • Language: JavaScript
  • Installation: npm install watch

Core Imports

const watch = require("watch");

For individual imports:

const { watchTree, unwatchTree, createMonitor, walk } = require("watch");

Basic Usage

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();
});

Capabilities

Directory Tree Watching

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:

  • Setup completion: f is an object (file hash), curr and prev are null
  • New file: prev is null, curr contains file stats
  • Removed file: curr.nlink is 0 (file's link count is zero)
  • Changed file: Both curr and prev contain stats, and modification times differ

Important: The function uses fs.watchFile internally and only triggers callbacks for actual content changes (based on mtime comparison), not just access times.

Event-Driven Monitoring

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.

Directory Walking

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;

Command Line Interface

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 directories

The 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

Types

/**
 * 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;

Usage Examples

Advanced Filtering

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);
  }
});

Monitor with Cleanup

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();
});

One-time Directory Scan

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);
    }
  });
});