0
# Watch
1
2
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.
3
4
## Package Information
5
6
- **Package Name**: watch
7
- **Package Type**: npm
8
- **Language**: JavaScript
9
- **Installation**: `npm install watch`
10
11
## Core Imports
12
13
```javascript
14
const watch = require("watch");
15
```
16
17
For individual imports:
18
19
```javascript
20
const { watchTree, unwatchTree, createMonitor, walk } = require("watch");
21
```
22
23
## Basic Usage
24
25
```javascript
26
const watch = require("watch");
27
28
// Simple file watching with callback
29
watch.watchTree("/path/to/watch", function (f, curr, prev) {
30
if (typeof f == "object" && prev === null && curr === null) {
31
// Finished walking the tree
32
console.log("Watching", Object.keys(f).length, "files");
33
} else if (prev === null) {
34
// New file
35
console.log("New file:", f);
36
} else if (curr.nlink === 0) {
37
// File removed
38
console.log("Removed:", f);
39
} else {
40
// File changed
41
console.log("Changed:", f);
42
}
43
});
44
45
// Event-driven monitoring
46
watch.createMonitor("/path/to/watch", function (monitor) {
47
monitor.on("created", function (f, stat) {
48
console.log("Created:", f);
49
});
50
monitor.on("changed", function (f, curr, prev) {
51
console.log("Changed:", f);
52
});
53
monitor.on("removed", function (f, stat) {
54
console.log("Removed:", f);
55
});
56
57
// Stop monitoring
58
// monitor.stop();
59
});
60
```
61
62
## Capabilities
63
64
### Directory Tree Watching
65
66
Monitor file and directory changes with customizable filtering and polling options.
67
68
```javascript { .api }
69
/**
70
* Watch a directory tree for file changes
71
* @param {string} root - Directory path to watch
72
* @param {WatchOptions|WatchCallback} [options] - Watch configuration options (optional)
73
* @param {WatchCallback} callback - Called on file changes and setup completion
74
* Note: If options is not provided, the second parameter becomes the callback
75
*/
76
function watchTree(root, options, callback);
77
78
/**
79
* Stop watching a directory tree
80
* @param {string} root - Directory path to stop watching
81
*/
82
function unwatchTree(root);
83
84
interface WatchOptions {
85
/** Skip files starting with "." */
86
ignoreDotFiles?: boolean;
87
/** Function to filter files/directories (return true to include) */
88
filter?: (filepath: string, stat: Stats) => boolean;
89
/** Polling interval in seconds */
90
interval?: number;
91
/** Skip unreadable directories silently (handles EACCES errors) */
92
ignoreUnreadableDir?: boolean;
93
/** Skip files with permission issues silently (handles EPERM errors) */
94
ignoreNotPermitted?: boolean;
95
/** Skip directories matching this pattern */
96
ignoreDirectoryPattern?: RegExp;
97
}
98
99
/**
100
* Callback for watchTree file changes
101
* @param {string|object} f - File path or file hash object (on completion)
102
* @param {Stats|null} curr - Current file stats
103
* @param {Stats|null} prev - Previous file stats (null for new files)
104
*/
105
type WatchCallback = (f: string | object, curr: Stats | null, prev: Stats | null) => void;
106
```
107
108
**Callback Behavior:**
109
- **Setup completion**: `f` is an object (file hash), `curr` and `prev` are `null`
110
- **New file**: `prev` is `null`, `curr` contains file stats
111
- **Removed file**: `curr.nlink` is `0` (file's link count is zero)
112
- **Changed file**: Both `curr` and `prev` contain stats, and modification times differ
113
114
**Important:** The function uses `fs.watchFile` internally and only triggers callbacks for actual content changes (based on mtime comparison), not just access times.
115
116
### Event-Driven Monitoring
117
118
Create an EventEmitter-based monitor for advanced file system event handling.
119
120
```javascript { .api }
121
/**
122
* Create an EventEmitter-based file monitor
123
* @param {string} root - Directory path to monitor
124
* @param {WatchOptions|MonitorCallback} [options] - Monitor configuration options (optional)
125
* @param {MonitorCallback} callback - Called with monitor object when ready
126
* Note: If options is not provided, the second parameter becomes the callback
127
*/
128
function createMonitor(root, options, callback);
129
130
/**
131
* Callback for createMonitor setup completion
132
* @param {Monitor} monitor - The configured monitor instance
133
*/
134
type MonitorCallback = (monitor: Monitor) => void;
135
136
interface Monitor extends EventEmitter {
137
/** Hash of file paths to current stat objects */
138
files: { [filepath: string]: Stats };
139
/** Stop monitoring (calls unwatchTree) */
140
stop(): void;
141
}
142
```
143
144
**Monitor Events:**
145
- `'created'` - New file created: `(filename: string, stat: Stats)`
146
- `'removed'` - File removed: `(filename: string, stat: Stats)`
147
- `'changed'` - File modified: `(filename: string, curr: Stats, prev: Stats)`
148
149
**Note:** The monitor includes deduplication logic to prevent duplicate events for the same file and action type within rapid succession.
150
151
### Directory Walking
152
153
Recursively walk a directory tree and build a file hash without watching.
154
155
```javascript { .api }
156
/**
157
* Recursively walk a directory tree
158
* @param {string} dir - Directory to walk
159
* @param {WalkOptions|WalkCallback} [options] - Walk configuration options (optional)
160
* @param {WalkCallback} callback - Called with results when complete
161
* Note: If options is not provided, the second parameter becomes the callback
162
*/
163
function walk(dir, options, callback);
164
165
interface WalkOptions {
166
/** Skip files starting with "." */
167
ignoreDotFiles?: boolean;
168
/** Function to filter files/directories (return true to include) */
169
filter?: (filepath: string, stat: Stats) => boolean;
170
/** Skip unreadable directories silently (handles EACCES errors) */
171
ignoreUnreadableDir?: boolean;
172
/** Skip files with permission issues silently (handles EPERM errors) */
173
ignoreNotPermitted?: boolean;
174
/** Skip directories matching this pattern */
175
ignoreDirectoryPattern?: RegExp;
176
}
177
178
/**
179
* Callback for walk completion
180
* @param {Error|null} err - Error if walk failed
181
* @param {object} files - Hash of file paths to stat objects
182
*/
183
type WalkCallback = (err: Error | null, files: { [filepath: string]: Stats }) => void;
184
```
185
186
### Command Line Interface
187
188
Execute commands automatically when files change, with throttling and filtering support. Install globally with `npm install watch -g` to use the `watch` command anywhere.
189
190
```bash { .api }
191
# Basic usage
192
watch <command> [...directory]
193
194
# With options
195
watch "npm test" ./src --wait=2 --ignoreDotFiles
196
197
# Available options:
198
--wait=<seconds>, -w # Throttle command execution (seconds)
199
--filter=<file>, -f # Path to filter function module (exports filter function)
200
--interval=<seconds>, -i # Polling interval (default: 0.2)
201
--ignoreDotFiles, -d # Ignore dot files
202
--ignoreUnreadable, -u # Ignore unreadable directories
203
--ignoreDirectoryPattern=<regexp>, -p # Ignore matching directories
204
```
205
206
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.
207
208
**Filter Module Example:**
209
The `--filter` option accepts a path to a JavaScript file that exports a filter function:
210
211
```javascript
212
// myfilter.js
213
module.exports = function (filepath, stat) {
214
// Return true to include file in watching, false to exclude
215
return stat.isDirectory() || filepath.match(/\.(js|ts|json)$/);
216
};
217
```
218
219
Then use: `watch "npm test" ./src --filter=./myfilter.js`
220
221
## Types
222
223
```javascript { .api }
224
/**
225
* Node.js fs.Stats object containing file metadata
226
*/
227
interface Stats {
228
isFile(): boolean;
229
isDirectory(): boolean;
230
isSymbolicLink(): boolean;
231
size: number;
232
mtime: Date;
233
nlink: number;
234
// ... other fs.Stats properties
235
}
236
237
/**
238
* Filter function type for determining which files to include
239
* @param {string} filepath - Full path to file or directory
240
* @param {Stats} stat - File system stats object
241
* @returns {boolean} True to include file in watching/walking
242
*/
243
type FilterFunction = (filepath: string, stat: Stats) => boolean;
244
```
245
246
## Usage Examples
247
248
### Advanced Filtering
249
250
```javascript
251
const watch = require("watch");
252
253
// Custom filter function
254
function sourceFilter(f, stat) {
255
return stat.isDirectory() || f.match(/\.(js|ts|json)$/);
256
}
257
258
watch.watchTree("./src", {
259
filter: sourceFilter,
260
ignoreDotFiles: true,
261
ignoreDirectoryPattern: /node_modules/
262
}, function (f, curr, prev) {
263
if (typeof f !== "object") {
264
console.log("Source file changed:", f);
265
}
266
});
267
```
268
269
### Monitor with Cleanup
270
271
```javascript
272
const watch = require("watch");
273
274
let monitor;
275
276
watch.createMonitor("./project", {
277
interval: 1,
278
ignoreDotFiles: true
279
}, function (m) {
280
monitor = m;
281
282
monitor.on("created", (f) => console.log("+ " + f));
283
monitor.on("removed", (f) => console.log("- " + f));
284
monitor.on("changed", (f) => console.log("~ " + f));
285
});
286
287
// Cleanup on exit
288
process.on("SIGINT", function() {
289
if (monitor) {
290
monitor.stop();
291
}
292
process.exit();
293
});
294
```
295
296
### One-time Directory Scan
297
298
```javascript
299
const watch = require("watch");
300
301
watch.walk("./docs", {
302
ignoreDotFiles: true,
303
filter: (f, stat) => stat.isDirectory() || f.endsWith('.md')
304
}, function (err, files) {
305
if (err) throw err;
306
307
console.log("Found", Object.keys(files).length, "markdown files:");
308
Object.keys(files).forEach(file => {
309
if (files[file].isFile()) {
310
console.log("-", file);
311
}
312
});
313
});
314
```