Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
name: CI

on:
- push
- pull_request
push:
branches: [master, main]
pull_request:
branches: [master, main]

jobs:

test:
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
node-version: [14.x, 18.x, 20.x]
node-version: [18.x, 20.x, 24.x]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
cache: 'npm'
- run: npm ci
- run: npm test
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ node_modules
test/utils/__TREE__
.nyc_output/
coverage/
package-lock.json
.DS_Store
15 changes: 15 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
# Changelog

## 0.8.0

### Breaking Changes
* Minimum Node.js version is now 18.0.0 (previously 6.0.0).

### Improvements
* Modernized codebase - Converted all code to ES6+.
* Removed `fs-extra` - replaced with native Node.js `fs` module functions.
* Improved TypeScript definitions.
* Added `exports` field** in package.json for better ESM compatibility.

<br> <br>


## 0.7.4

* Fix: add export to interface #128 (by @multivoltage)
* Catch fs.watch exceptions #125 (by @campersau )
* Fix can't listener error event on incorrect file/directory #123 (by @leijuns)


<br> <br>


Expand Down
41 changes: 29 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ A wrapper and enhancements for [fs.watch](http://nodejs.org/api/fs.html#fs_fs_wa
npm install node-watch
```

## Requirements

- Node.js >= 18.0.0

## Example

```js
var watch = require('node-watch');
const watch = require('node-watch');

watch('file_or_dir', { recursive: true }, function(evt, name) {
watch('file_or_dir', { recursive: true }, (evt, name) => {
console.log('%s changed.', name);
});
```
Expand Down Expand Up @@ -91,7 +95,7 @@ The usage and options of `node-watch` are compatible with [fs.watch](https://nod

```js
const pm = require('picomatch');
let isMatch = pm('*.js');
const isMatch = pm('*.js');

watch('./', {
filter: f => isMatch(f)
Expand All @@ -112,13 +116,13 @@ The usage and options of `node-watch` are compatible with [fs.watch](https://nod
The events provided by the callback function is either `update` or `remove`, which is less confusing to `fs.watch`'s `rename` or `change`.

```js
watch('./', function(evt, name) {
watch('./', (evt, name) => {

if (evt == 'update') {
if (evt === 'update') {
// on create or modify
}

if (evt == 'remove') {
if (evt === 'remove') {
// on delete
}

Expand All @@ -133,17 +137,17 @@ The watch function returns a [fs.FSWatcher](https://nodejs.org/api/fs.html#fs_cl
#### Watcher events

```js
let watcher = watch('./', { recursive: true });
const watcher = watch('./', { recursive: true });

watcher.on('change', function(evt, name) {
watcher.on('change', (evt, name) => {
// callback
});

watcher.on('error', function(err) {
watcher.on('error', (err) => {
// handle error
});

watcher.on('ready', function() {
watcher.on('ready', () => {
// the watcher is ready to respond to changes
});
```
Expand Down Expand Up @@ -198,7 +202,7 @@ watch(['file1', 'file2'], console.log);
// https://github.com/nodejs/node-v0.x-archive/issues/3211
require('epipebomb')();

let watcher = require('node-watch')(
const watcher = require('node-watch')(
process.argv[2] || './', { recursive: true }, console.log
);

Expand All @@ -217,6 +221,19 @@ Follow this description to increase the limit:
[https://confluence.jetbrains.com/display/IDEADEV/Inotify+Watches+Limit](https://confluence.jetbrains.com/display/IDEADEV/Inotify+Watches+Limit)


## TypeScript

This package includes TypeScript type definitions:

```ts
import watch, { Watcher, EventType } from 'node-watch';

const watcher: Watcher = watch('./src', { recursive: true }, (evt: EventType, name: string) => {
console.log('%s changed.', name);
});
```


## Alternatives

* [chokidar](https://github.com/paulmillr/chokidar)
Expand All @@ -230,4 +247,4 @@ Thanks goes to [all wonderful people](https://github.com/yuanchuan/node-watch/gr
## License
MIT

Copyright (c) 2012-2021 [yuanchuan](https://github.com/yuanchuan)
Copyright (c) 2012-2026 [yuanchuan](https://github.com/yuanchuan)
150 changes: 88 additions & 62 deletions lib/has-native-recursive.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,101 @@
var fs = require('fs');
var os = require('os');
var path = require('path');
var is = require('./is');

var IS_SUPPORT;
var TEMP_DIR = os.tmpdir && os.tmpdir()
|| process.env.TMPDIR
|| process.env.TEMP
|| process.cwd();

function TempStack() {
this.stack = [];
}
'use strict';

const fs = require('fs');
const os = require('os');
const path = require('path');
const is = require('./is');

let IS_SUPPORT;
const TEMP_DIR = os.tmpdir?.() || process.env.TMPDIR || process.env.TEMP || process.cwd();

/**
* Manages temporary files/directories for testing native recursive support
*/
class TempStack {
constructor() {
this.stack = [];
}

TempStack.prototype = {
create: function(type, base) {
var name = path.join(base,
'node-watch-' + Math.random().toString(16).substr(2)
);
this.stack.push({ name: name, type: type });
/**
* Create a unique temporary path
* @param {'file'|'dir'} type - Type of path to create
* @param {string} base - Base directory
* @returns {string} - The created path name
*/
create(type, base) {
const name = path.join(base, `node-watch-${Math.random().toString(16).slice(2)}`);
this.stack.push({ name, type });
return name;
},
write: function(/* file */) {
for (var i = 0; i < arguments.length; ++i) {
fs.writeFileSync(arguments[i], ' ');
}

/**
* Write content to files
* @param {...string} files - File paths to write to
*/
write(...files) {
for (const file of files) {
fs.writeFileSync(file, ' ');
}
},
mkdir: function(/* dirs */) {
for (var i = 0; i < arguments.length; ++i) {
fs.mkdirSync(arguments[i]);
}

/**
* Create directories
* @param {...string} dirs - Directory paths to create
*/
mkdir(...dirs) {
for (const dir of dirs) {
fs.mkdirSync(dir);
}
},
cleanup: function(fn) {
}

/**
* Clean up all created files and directories
* @param {Function} [fn] - Optional callback after cleanup
*/
cleanup(fn) {
try {
var temp;
let temp;
while ((temp = this.stack.pop())) {
var type = temp.type;
var name = temp.name;
const { type, name } = temp;
if (type === 'file' && is.file(name)) {
fs.unlinkSync(name);
}
else if (type === 'dir' && is.directory(name)) {
} else if (type === 'dir' && is.directory(name)) {
fs.rmdirSync(name);
}
}
}
finally {
} finally {
if (is.func(fn)) fn();
}
}
};
}

var pending = false;
let pending = false;

module.exports = function hasNativeRecursive(fn) {
/**
* Detect if the platform supports native recursive watching
* @param {Function} fn - Callback with boolean result
* @returns {boolean|undefined}
*/
function hasNativeRecursive(fn) {
if (!is.func(fn)) {
return false;
}

if (IS_SUPPORT !== undefined) {
return fn(IS_SUPPORT);
}

if (!pending) {
pending = true;
}
// check again later
else {
return setTimeout(function() {
hasNativeRecursive(fn);
}, 300);
} else {
// Check again later if detection is already in progress
return setTimeout(() => hasNativeRecursive(fn), 300);
}

var stack = new TempStack();
var parent = stack.create('dir', TEMP_DIR);
var child = stack.create('dir', parent);
var file = stack.create('file', child);
const stack = new TempStack();
const parent = stack.create('dir', TEMP_DIR);
const child = stack.create('dir', parent);
const file = stack.create('file', child);

try {
stack.mkdir(parent, child);
Expand All @@ -88,38 +109,43 @@ module.exports = function hasNativeRecursive(fn) {
stack.mkdir(parent, child);
}

var options = { recursive: true };
var watcher;
const options = { recursive: true };
let watcher;

try {
watcher = fs.watch(parent, options);
} catch (e) {
if (e.code == 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM') {
return fn(IS_SUPPORT = false);
} else {
throw e;
if (e.code === 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM') {
IS_SUPPORT = false;
return fn(IS_SUPPORT);
}
throw e;
}

if (!watcher) {
return false;
}

var timer = setTimeout(function() {
const timer = setTimeout(() => {
watcher.close();
stack.cleanup(function() {
fn(IS_SUPPORT = false);
stack.cleanup(() => {
IS_SUPPORT = false;
fn(IS_SUPPORT);
});
}, 200);

watcher.on('change', function(evt, name) {
watcher.on('change', (evt, name) => {
if (path.basename(file) === path.basename(name)) {
watcher.close();
clearTimeout(timer);
stack.cleanup(function() {
fn(IS_SUPPORT = true);
stack.cleanup(() => {
IS_SUPPORT = true;
fn(IS_SUPPORT);
});
}
});

stack.write(file);
}

module.exports = hasNativeRecursive;
Loading