CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-watch

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

Pending
Overview
Eval results
Files

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

Install with Tessl CLI

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