diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5dc578c..70548b7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,11 +1,12 @@
name: CI
on:
- - push
- - pull_request
+ push:
+ branches: [master, main]
+ pull_request:
+ branches: [master, main]
jobs:
-
test:
runs-on: ${{ matrix.os }}
@@ -13,13 +14,14 @@ jobs:
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
diff --git a/.gitignore b/.gitignore
index 4bf3a39..727441d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,4 @@ node_modules
test/utils/__TREE__
.nyc_output/
coverage/
-package-lock.json
.DS_Store
diff --git a/Changelog.md b/Changelog.md
index 8c5da97..6304943 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -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.
+
+
+
+
## 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)
+
diff --git a/README.md b/README.md
index c0f8bd2..a7576ec 100644
--- a/README.md
+++ b/README.md
@@ -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);
});
```
@@ -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)
@@ -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
}
@@ -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
});
```
@@ -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
);
@@ -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)
@@ -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)
diff --git a/lib/has-native-recursive.js b/lib/has-native-recursive.js
index 49092be..252d792 100644
--- a/lib/has-native-recursive.js
+++ b/lib/has-native-recursive.js
@@ -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);
@@ -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;
diff --git a/lib/is.js b/lib/is.js
index ebe0600..1d3bcc7 100644
--- a/lib/is.js
+++ b/lib/is.js
@@ -1,12 +1,25 @@
-var fs = require('fs');
-var path = require('path');
-var os = require('os');
+'use strict';
-function matchObject(item, str) {
- return Object.prototype.toString.call(item)
- === '[object ' + str + ']';
+const fs = require('fs');
+const path = require('path');
+const os = require('os');
+
+/**
+ * Check if an object matches a specific type string
+ * @param {*} item - The item to check
+ * @param {string} type - The type name to match
+ * @returns {boolean}
+ */
+function matchObject(item, type) {
+ return Object.prototype.toString.call(item) === `[object ${type}]`;
}
+/**
+ * Safely check file stats, handling permission errors gracefully
+ * @param {string} name - File path to check
+ * @param {Function} fn - Stat check function
+ * @returns {boolean}
+ */
function checkStat(name, fn) {
try {
return fn(name);
@@ -21,56 +34,133 @@ function checkStat(name, fn) {
}
}
-var is = {
- nil: function(item) {
+const is = {
+ /**
+ * Check if value is null or undefined
+ * @param {*} item
+ * @returns {boolean}
+ */
+ nil(item) {
return item == null;
},
- array: function(item) {
+
+ /**
+ * Check if value is an array
+ * @param {*} item
+ * @returns {boolean}
+ */
+ array(item) {
return Array.isArray(item);
},
- emptyObject: function(item) {
- for (var key in item) {
+
+ /**
+ * Check if object is empty (has no own enumerable properties)
+ * @param {Object} item
+ * @returns {boolean}
+ */
+ emptyObject(item) {
+ for (const key in item) {
return false;
}
return true;
},
- buffer: function(item) {
+
+ /**
+ * Check if value is a Buffer
+ * @param {*} item
+ * @returns {boolean}
+ */
+ buffer(item) {
return Buffer.isBuffer(item);
},
- regExp: function(item) {
+
+ /**
+ * Check if value is a RegExp
+ * @param {*} item
+ * @returns {boolean}
+ */
+ regExp(item) {
return matchObject(item, 'RegExp');
},
- string: function(item) {
+
+ /**
+ * Check if value is a string
+ * @param {*} item
+ * @returns {boolean}
+ */
+ string(item) {
return matchObject(item, 'String');
},
- func: function(item) {
+
+ /**
+ * Check if value is a function
+ * @param {*} item
+ * @returns {boolean}
+ */
+ func(item) {
return typeof item === 'function';
},
- number: function(item) {
+
+ /**
+ * Check if value is a number
+ * @param {*} item
+ * @returns {boolean}
+ */
+ number(item) {
return matchObject(item, 'Number');
},
- exists: function(name) {
+
+ /**
+ * Check if a file or directory exists
+ * @param {string} name - Path to check
+ * @returns {boolean}
+ */
+ exists(name) {
return fs.existsSync(name);
},
- file: function(name) {
- return checkStat(name, function(n) {
- return fs.statSync(n).isFile()
- });
+
+ /**
+ * Check if path is a file
+ * @param {string} name - Path to check
+ * @returns {boolean}
+ */
+ file(name) {
+ return checkStat(name, (n) => fs.statSync(n).isFile());
},
- samePath: function(a, b) {
+
+ /**
+ * Check if two paths resolve to the same location
+ * @param {string} a - First path
+ * @param {string} b - Second path
+ * @returns {boolean}
+ */
+ samePath(a, b) {
return path.resolve(a) === path.resolve(b);
},
- directory: function(name) {
- return checkStat(name, function(n) {
- return fs.statSync(n).isDirectory()
- });
+
+ /**
+ * Check if path is a directory
+ * @param {string} name - Path to check
+ * @returns {boolean}
+ */
+ directory(name) {
+ return checkStat(name, (n) => fs.statSync(n).isDirectory());
},
- symbolicLink: function(name) {
- return checkStat(name, function(n) {
- return fs.lstatSync(n).isSymbolicLink();
- });
+
+ /**
+ * Check if path is a symbolic link
+ * @param {string} name - Path to check
+ * @returns {boolean}
+ */
+ symbolicLink(name) {
+ return checkStat(name, (n) => fs.lstatSync(n).isSymbolicLink());
},
- windows: function() {
+
+ /**
+ * Check if running on Windows
+ * @returns {boolean}
+ */
+ windows() {
return os.platform() === 'win32';
}
};
diff --git a/lib/watch.d.ts b/lib/watch.d.ts
index f59f17d..ef7a22b 100644
--- a/lib/watch.d.ts
+++ b/lib/watch.d.ts
@@ -1,75 +1,179 @@
-import { FSWatcher } from 'fs';
+import {FSWatcher} from 'fs';
+import {EventEmitter} from 'events';
/**
- * Watch for changes on `filename`, where filename is either a file or a directory.
- * The second argument is optional.
+ * Watch for changes on files or directories.
*
- * If `options` is provided as a string, it specifies the encoding.
- * Otherwise `options` should be passed as an object.
+ * @param pathName - File or directory to watch. Can be a single path or array of paths.
+ * @param options - Watch options or encoding string.
+ * @param callback - Callback function invoked on changes.
+ * @returns A Watcher object that can be used to manage the watch.
*
- * The listener callback gets two arguments, `(eventType, filePath)`,
- * which is the same with `fs.watch`.
- * `eventType` is either `update` or `remove`,
- * `filePath` is the name of the file which triggered the event.
+ * @example
+ * ```js
+ * import watch from 'node-watch';
*
- * @param {Filename} filename File or directory to watch.
- * @param {Options|string} options
- * @param {Function} callback
+ * // Watch a directory recursively
+ * const watcher = watch('./src', { recursive: true }, (evt, name) => {
+ * console.log('%s changed.', name);
+ * });
+ *
+ * // Close the watcher when done
+ * watcher.close();
+ * ```
*/
declare function watch(pathName: PathName): Watcher;
-declare function watch(pathName: PathName, options: Options) : Watcher;
-declare function watch(pathName: PathName, callback: Callback): Watcher;
-declare function watch(pathName: PathName, options: Options, callback: Callback): Watcher;
+declare function watch(pathName: PathName,options: Options): Watcher;
+declare function watch(pathName: PathName,callback: Callback): Watcher;
+declare function watch(pathName: PathName,options: Options,callback: Callback): Watcher;
+
+/**
+ * Event type emitted by the watcher.
+ * - `update`: File or directory was created or modified.
+ * - `remove`: File or directory was deleted.
+ */
+export type EventType='update'|'remove';
+
+/**
+ * Callback function invoked when a file system event occurs.
+ * @param eventType - The type of event (update or remove).
+ * @param filePath - The path of the file or directory that changed.
+ */
+export type Callback=(eventType: EventType,filePath: string) => void;
+
+/**
+ * Path name to watch. Can be a single path or an array of paths.
+ */
+export type PathName=string|readonly string[];
+
+/**
+ * Return value from a filter function.
+ * - `true`: Include the file/directory.
+ * - `false`: Exclude the file/directory.
+ * - `skip` symbol: Exclude and don't recurse into subdirectories.
+ */
+export type FilterReturn=boolean|symbol;
-type EventType = 'update' | 'remove';
-type Callback = (eventType: EventType, filePath: string) => any;
-type PathName = string | Array;
-type FilterReturn = boolean | symbol;
+/**
+ * Filter function for selectively watching files and directories.
+ * @param file - The file or directory path being considered.
+ * @param skip - A symbol that can be returned to skip a directory and its subdirectories.
+ * @returns Whether to include the file/directory, or the skip symbol.
+ */
+export type FilterFunction=(file: string,skip: symbol) => FilterReturn;
-type Options = {
+/**
+ * Options for configuring the watcher.
+ */
+export interface Options {
/**
* Indicates whether the process should continue to run
* as long as files are being watched.
* @default true
*/
- persistent ?: boolean;
+ persistent?: boolean;
/**
* Indicates whether all subdirectories should be watched.
* @default false
*/
- recursive ?: boolean;
+ recursive?: boolean;
/**
* Specifies the character encoding to be used for the filename
- * passed to the listener.
+ * passed to the listener. Use 'buffer' to receive Buffer objects.
* @default 'utf8'
*/
- encoding ?: string;
+ encoding?: BufferEncoding|'buffer';
/**
- * Only files which pass this filter (when it returns `true`)
- * will be sent to the listener.
+ * Only files which pass this filter will trigger events.
+ * Can be a RegExp or a function.
+ *
+ * @example
+ * ```js
+ * // Filter with RegExp
+ * watch('./', { filter: /\.js$/ });
+ *
+ * // Filter with function
+ * watch('./', {
+ * filter: (f, skip) => {
+ * if (/node_modules/.test(f)) return skip;
+ * return /\.js$/.test(f);
+ * }
+ * });
+ * ```
*/
- filter ?: RegExp | ((file: string, skip: symbol) => FilterReturn);
+ filter?: RegExp|FilterFunction;
/**
- * Delay time of the callback function.
+ * Delay in milliseconds before triggering the callback.
+ * Events that occur within this window are deduplicated.
* @default 200
*/
- delay ?: number;
-};
+ delay?: number;
+}
-export declare interface Watcher extends FSWatcher {
+/**
+ * Extended FSWatcher interface with additional methods.
+ */
+export interface Watcher extends Pick {
/**
* Returns `true` if the watcher has been closed.
*/
isClosed(): boolean;
/**
- * Returns all watched paths.
+ * Close the watcher and stop watching for changes.
+ */
+ close(): void;
+
+ /**
+ * Get all watched paths asynchronously.
+ * @param callback - Function called with array of watched paths.
+ */
+ getWatchedPaths(callback: (paths: string[]) => void): void;
+
+ /**
+ * Listen for change events.
+ */
+ on(event: 'change',listener: Callback): this;
+
+ /**
+ * Listen for error events.
+ */
+ on(event: 'error',listener: (error: Error) => void): this;
+
+ /**
+ * Listen for when the watcher is ready.
+ */
+ on(event: 'ready',listener: () => void): this;
+
+ /**
+ * Listen for when the watcher is closed.
+ */
+ on(event: 'close',listener: () => void): this;
+
+ /**
+ * Listen once for change events.
+ */
+ once(event: 'change',listener: Callback): this;
+
+ /**
+ * Listen once for error events.
+ */
+ once(event: 'error',listener: (error: Error) => void): this;
+
+ /**
+ * Listen once for when the watcher is ready.
+ */
+ once(event: 'ready',listener: () => void): this;
+
+ /**
+ * Listen once for when the watcher is closed.
*/
- getWatchedPaths(): Array;
+ once(event: 'close',listener: () => void): this;
}
+export {watch};
export default watch;
diff --git a/lib/watch.js b/lib/watch.js
index 73f1c38..1be7733 100644
--- a/lib/watch.js
+++ b/lib/watch.js
@@ -1,155 +1,184 @@
-var fs = require('fs');
-var path = require('path');
-var util = require('util');
-var events = require('events');
+'use strict';
-var hasNativeRecursive = require('./has-native-recursive');
-var is = require('./is');
+const fs = require('fs');
+const path = require('path');
+const { EventEmitter } = require('events');
-var EVENT_UPDATE = 'update';
-var EVENT_REMOVE = 'remove';
+const hasNativeRecursive = require('./has-native-recursive');
+const is = require('./is');
-var SKIP_FLAG = Symbol('skip');
+// Event type constants
+const EVENT_UPDATE = 'update';
+const EVENT_REMOVE = 'remove';
+// Symbol for skip flag in filter function
+const SKIP_FLAG = Symbol('skip');
+
+/**
+ * Check if array contains duplicates
+ * @param {Array} arr
+ * @returns {boolean}
+ */
function hasDup(arr) {
- return arr.some(function(v, i, self) {
- return self.indexOf(v) !== i;
- });
+ return arr.some((v, i, self) => self.indexOf(v) !== i);
}
+/**
+ * Remove duplicates from array
+ * @param {Array} arr
+ * @returns {Array}
+ */
function unique(arr) {
- return arr.filter(function(v, i, self) {
- return self.indexOf(v) === i;
- });
-}
-
-// One level flat
-function flat1(arr) {
- return arr.reduce(function(acc, v) {
- return acc.concat(v);
- }, []);
+ return arr.filter((v, i, self) => self.indexOf(v) === i);
}
+/**
+ * Validate encoding option
+ * @param {string} encoding
+ * @throws {Error} If encoding is unknown
+ */
function assertEncoding(encoding) {
if (encoding && encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {
- throw new Error('Unknown encoding: ' + encoding);
+ throw new Error(`Unknown encoding: ${encoding}`);
}
}
+/**
+ * Create a filter guard function from filter option
+ * @param {Function|RegExp} fn - Filter function or RegExp
+ * @returns {Function} Guard function that calls action if filter passes
+ */
function guard(fn) {
if (is.func(fn)) {
- return function(arg, action) {
+ return (arg, action) => {
if (fn(arg, false)) action();
- }
+ };
}
if (is.regExp(fn)) {
- return function(arg, action) {
+ return (arg, action) => {
if (fn.test(arg)) action();
- }
- }
- return function(arg, action) {
- action();
+ };
}
+ return (arg, action) => action();
}
+/**
+ * Convert file names to event messages
+ * @param {string[]} names - File names
+ * @returns {Array<[string, string]>} Array of [eventType, fileName] tuples
+ */
function composeMessage(names) {
- return names.map(function(n) {
- return is.exists(n)
- ? [EVENT_UPDATE, n]
- : [EVENT_REMOVE, n];
- });
+ return names.map((n) => (is.exists(n) ? [EVENT_UPDATE, n] : [EVENT_REMOVE, n]));
}
+/**
+ * Process cached file names and generate event messages
+ * Filters out temporary editor files when saving
+ * @param {string[]} cache - Cached file names
+ * @returns {Array<[string, string]>} Array of [eventType, fileName] tuples
+ */
function getMessages(cache) {
- var filtered = unique(cache);
+ let filtered = unique(cache);
// Saving file from an editor? If so, assuming the
// non-existed files in the cache are temporary files
// generated by an editor and thus be filtered.
- var reg = /~$|^\.#|^##$/g;
- var hasSpecialChar = cache.some(function(c) {
- return reg.test(c);
- });
+ const reg = /~$|^\.#|^##$/g;
+ const hasSpecialChar = cache.some((c) => reg.test(c));
if (hasSpecialChar) {
- var dup = hasDup(cache.map(function(c) {
- return c.replace(reg, '');
- }));
+ const dup = hasDup(cache.map((c) => c.replace(reg, '')));
if (dup) {
- filtered = filtered.filter(function(m) {
- return is.exists(m);
- });
+ filtered = filtered.filter((m) => is.exists(m));
}
}
return composeMessage(filtered);
}
+/**
+ * Create a debounced event handler
+ * @param {Object} info - Watch info object
+ * @param {Function} fn - Callback function
+ * @returns {Function} Debounced handler
+ */
function debounce(info, fn) {
- var timer, cache = [];
- var encoding = info.options.encoding;
- var delay = info.options.delay;
- if (!is.number(delay)) {
- delay = 200;
- }
+ let timer = null;
+ let cache = [];
+ const { encoding } = info.options;
+ const delay = is.number(info.options.delay) ? info.options.delay : 200;
+
function handle() {
- getMessages(cache).forEach(function(msg) {
+ getMessages(cache).forEach((msg) => {
msg[1] = Buffer.from(msg[1]);
if (encoding !== 'buffer') {
msg[1] = msg[1].toString(encoding);
}
- fn.apply(null, msg);
+ fn(...msg);
});
timer = null;
cache = [];
}
- return function(rawEvt, name) {
+
+ return (rawEvt, name) => {
cache.push(name);
if (!timer) {
timer = setTimeout(handle, delay);
}
- }
+ };
}
+/**
+ * Create a duplicate event filter for composed watchers
+ * @returns {Function} Filter function
+ */
function createDupsFilter() {
- var memo = {};
- return function(fn) {
- return function(evt, name) {
- memo[evt + name] = [evt, name];
- setTimeout(function() {
- Object.keys(memo).forEach(function(n) {
- fn.apply(null, memo[n]);
- });
- memo = {};
+ let memo = {};
+ return (fn) => (evt, name) => {
+ memo[evt + name] = [evt, name];
+ setTimeout(() => {
+ Object.keys(memo).forEach((n) => {
+ fn(...memo[n]);
});
- }
- }
+ memo = {};
+ });
+ };
}
+/**
+ * Safely start watching a path
+ * @param {Watcher} watcher - Watcher instance
+ * @param {string} dir - Directory to watch
+ * @param {Object} opts - Watch options
+ * @returns {fs.FSWatcher|undefined}
+ */
function tryWatch(watcher, dir, opts) {
try {
return fs.watch(dir, opts);
} catch (e) {
- process.nextTick(function() {
- watcher.emit('error', e);
- });
+ process.nextTick(() => watcher.emit('error', e));
}
}
-function getSubDirectories(dir, fn, done = function() {}) {
+/**
+ * Get subdirectories of a directory
+ * @param {string} dir - Directory path
+ * @param {Function} fn - Callback for each subdirectory
+ * @param {Function} [done] - Callback when complete
+ */
+function getSubDirectories(dir, fn, done = () => {}) {
if (is.directory(dir)) {
- fs.readdir(dir, function(err, all) {
+ fs.readdir(dir, (err, all) => {
if (err) {
- // don't throw permission errors.
+ // Don't throw permission errors
if (/^(EPERM|EACCES)$/.test(err.code)) {
console.warn('Warning: Cannot access %s.', dir);
} else {
throw err;
}
- }
- else {
- all.forEach(function(f) {
- var sdir = path.join(dir, f);
+ } else {
+ all.forEach((f) => {
+ const sdir = path.join(dir, f);
if (is.directory(sdir)) fn(sdir);
});
done();
@@ -160,8 +189,13 @@ function getSubDirectories(dir, fn, done = function() {}) {
}
}
+/**
+ * Create a semaphore for tracking async operations
+ * @param {Function} final - Callback when all operations complete
+ * @returns {Function} Start function
+ */
function semaphore(final) {
- var counter = 0;
+ let counter = 0;
return function start() {
counter++;
return function stop() {
@@ -171,375 +205,419 @@ function semaphore(final) {
};
}
+/**
+ * Null counter for single directory watching
+ * @returns {Function}
+ */
function nullCounter() {
- return function nullStop() {};
+ return () => {};
}
+/**
+ * Check if a path should be watched based on filter
+ * @param {string} filePath - Path to check
+ * @param {Function} filter - Filter function
+ * @returns {boolean}
+ */
function shouldNotSkip(filePath, filter) {
- // watch it only if the filter is not function
- // or not being skipped explicitly.
return !is.func(filter) || filter(filePath, SKIP_FLAG) !== SKIP_FLAG;
}
-var deprecationWarning = util.deprecate(
- function() {},
- '(node-watch) First param in callback function\
- is replaced with event name since 0.5.0, use\
- `(evt, filename) => {}` if you want to get the filename'
-);
-
-function Watcher() {
- events.EventEmitter.call(this);
- this.watchers = {};
- this._isReady = false;
- this._isClosed = false;
-}
-
-util.inherits(Watcher, events.EventEmitter);
-
-Watcher.prototype.expose = function() {
- var expose = {};
- var self = this;
- var methods = [
- 'on', 'emit', 'once',
- 'close', 'isClosed',
- 'listeners', 'setMaxListeners', 'getMaxListeners',
- 'getWatchedPaths'
- ];
- methods.forEach(function(name) {
- expose[name] = function() {
- return self[name].apply(self, arguments);
- }
- });
- return expose;
-}
-
-Watcher.prototype.isClosed = function() {
- return this._isClosed;
-}
-
-Watcher.prototype.close = function(fullPath) {
- var self = this;
- if (fullPath) {
- var watcher = this.watchers[fullPath];
- if (watcher && watcher.close) {
- watcher.close();
- delete self.watchers[fullPath];
- }
- getSubDirectories(fullPath, function(fpath) {
- self.close(fpath);
- });
- }
- else {
- Object.keys(self.watchers).forEach(function(fpath) {
- var watcher = self.watchers[fpath];
- if (watcher && watcher.close) {
- watcher.close();
- }
- });
- this.watchers = {};
- }
- // Do not close the Watcher unless all child watchers are closed.
- // https://github.com/yuanchuan/node-watch/issues/75
- if (is.emptyObject(self.watchers)) {
- // should emit once
- if (!this._isClosed) {
- this._isClosed = true;
- process.nextTick(emitClose, this);
- }
- }
-}
-
-Watcher.prototype.getWatchedPaths = function(fn) {
- if (is.func(fn)) {
- var self = this;
- if (self._isReady) {
- fn(Object.keys(self.watchers));
- } else {
- self.on('ready', function() {
- fn(Object.keys(self.watchers));
- });
- }
- }
-}
-
+/**
+ * Emit ready event on watcher
+ * @param {Watcher} self - Watcher instance
+ */
function emitReady(self) {
if (!self._isReady) {
self._isReady = true;
- // do not call emit for 'ready' until after watch() has returned,
- // so that consumer can call on().
- process.nextTick(function () {
- self.emit('ready');
- });
+ process.nextTick(() => self.emit('ready'));
}
}
+/**
+ * Emit close event on watcher
+ * @param {Watcher} self - Watcher instance
+ */
function emitClose(self) {
self.emit('close');
}
-Watcher.prototype.add = function(watcher, info) {
- var self = this;
- info = info || { fpath: '' };
- var watcherPath = path.resolve(info.fpath);
- this.watchers[watcherPath] = watcher;
+/**
+ * Watcher class extending EventEmitter
+ * Manages file system watchers for files and directories
+ */
+class Watcher extends EventEmitter {
+ constructor() {
+ super();
+ this.watchers = {};
+ this._isReady = false;
+ this._isClosed = false;
+ this.flag = '';
+ }
- // Internal callback for handling fs.FSWatcher 'change' events
- var internalOnChange = function(rawEvt, rawName) {
- if (self.isClosed()) {
- return;
- }
+ /**
+ * Create an exposed interface for external use
+ * @returns {Object} Exposed methods
+ */
+ expose() {
+ const methods = [
+ 'on',
+ 'emit',
+ 'once',
+ 'close',
+ 'isClosed',
+ 'listeners',
+ 'setMaxListeners',
+ 'getMaxListeners',
+ 'getWatchedPaths'
+ ];
+
+ const expose = {};
+ methods.forEach((name) => {
+ expose[name] = (...args) => this[name](...args);
+ });
+ return expose;
+ }
- // normalise lack of name and convert to full path
- var name = rawName;
- if (is.nil(name)) {
- name = '';
- }
- name = path.join(info.fpath, name);
-
- if (info.options.recursive) {
- hasNativeRecursive(function(has) {
- if (!has) {
- var fullPath = path.resolve(name);
- // remove watcher on removal
- if (!is.exists(name)) {
- self.close(fullPath);
- }
- // watch new created directory
- else {
- var shouldWatch = is.directory(name)
- && !self.watchers[fullPath]
- && shouldNotSkip(name, info.options.filter);
-
- if (shouldWatch) {
- self.watchDirectory(name, info.options);
- }
- }
+ /**
+ * Check if watcher is closed
+ * @returns {boolean}
+ */
+ isClosed() {
+ return this._isClosed;
+ }
+
+ /**
+ * Close watcher for a specific path or all paths
+ * @param {string} [fullPath] - Specific path to close, or undefined for all
+ */
+ close(fullPath) {
+ if (fullPath) {
+ const watcher = this.watchers[fullPath];
+ if (watcher?.close) {
+ watcher.close();
+ delete this.watchers[fullPath];
+ }
+ getSubDirectories(fullPath, (fpath) => this.close(fpath));
+ } else {
+ Object.keys(this.watchers).forEach((fpath) => {
+ const watcher = this.watchers[fpath];
+ if (watcher?.close) {
+ watcher.close();
}
});
+ this.watchers = {};
}
- handlePublicEvents(rawEvt, name);
- };
-
- // Debounced based on the 'delay' option
- var handlePublicEvents = debounce(info, function (evt, name) {
- // watch single file
- if (info.compareName) {
- if (info.compareName(name)) {
- self.emit('change', evt, name);
+ // Do not close the Watcher unless all child watchers are closed
+ // https://github.com/yuanchuan/node-watch/issues/75
+ if (is.emptyObject(this.watchers)) {
+ if (!this._isClosed) {
+ this._isClosed = true;
+ process.nextTick(emitClose, this);
}
}
- // watch directory
- else {
- var filterGuard = guard(info.options.filter);
- filterGuard(name, function() {
- if (self.flag) self.flag = '';
- else self.emit('change', evt, name);
- });
- }
- });
+ }
- watcher.on('error', function(err) {
- if (self.isClosed()) {
- return;
- }
- if (is.windows() && err.code === 'EPERM') {
- watcher.emit('change', EVENT_REMOVE, info.fpath && '');
- self.flag = 'windows-error';
- self.close(watcherPath);
- } else {
- self.emit('error', err);
+ /**
+ * Get all watched paths
+ * @param {Function} fn - Callback with array of paths
+ */
+ getWatchedPaths(fn) {
+ if (is.func(fn)) {
+ if (this._isReady) {
+ fn(Object.keys(this.watchers));
+ } else {
+ this.on('ready', () => fn(Object.keys(this.watchers)));
+ }
}
- });
+ }
- watcher.on('change', internalOnChange);
-}
+ /**
+ * Add a native FSWatcher to this watcher
+ * @param {fs.FSWatcher} watcher - Native watcher
+ * @param {Object} info - Watch info
+ */
+ add(watcher, info = { fpath: '' }) {
+ const watcherPath = path.resolve(info.fpath);
+ this.watchers[watcherPath] = watcher;
+
+ // Debounced handler for public events
+ const handlePublicEvents = debounce(info, (evt, name) => {
+ // Watch single file
+ if (info.compareName) {
+ if (info.compareName(name)) {
+ this.emit('change', evt, name);
+ }
+ } else {
+ // Watch directory
+ const filterGuard = guard(info.options.filter);
+ filterGuard(name, () => {
+ if (this.flag) {
+ this.flag = '';
+ } else {
+ this.emit('change', evt, name);
+ }
+ });
+ }
+ });
-Watcher.prototype.watchFile = function(file, options, fn) {
- var parent = path.join(file, '../');
- var opts = Object.assign({}, options, {
- // no filter for single file
- filter: null,
- encoding: 'utf8'
- });
+ // Internal callback for handling fs.FSWatcher 'change' events
+ const internalOnChange = (rawEvt, rawName) => {
+ if (this.isClosed()) {
+ return;
+ }
- // no need to watch recursively
- delete opts.recursive;
+ // Normalize lack of name and convert to full path
+ let name = rawName ?? '';
+ name = path.join(info.fpath, name);
+
+ if (info.options.recursive) {
+ hasNativeRecursive((has) => {
+ if (!has) {
+ const fullPath = path.resolve(name);
+ // Remove watcher on removal
+ if (!is.exists(name)) {
+ this.close(fullPath);
+ } else {
+ // Watch new created directory
+ const shouldWatch =
+ is.directory(name) &&
+ !this.watchers[fullPath] &&
+ shouldNotSkip(name, info.options.filter);
+
+ if (shouldWatch) {
+ this.watchDirectory(name, info.options);
+ }
+ }
+ }
+ });
+ }
- var watcher = tryWatch(this, parent, opts);
- if (!watcher) {
- return;
- }
+ handlePublicEvents(rawEvt, name);
+ };
- this.add(watcher, {
- type: 'file',
- fpath: parent,
- options: Object.assign({}, opts, {
- encoding: options.encoding
- }),
- compareName: function(n) {
- return is.samePath(n, file);
- }
- });
+ watcher.on('error', (err) => {
+ if (this.isClosed()) {
+ return;
+ }
+ if (is.windows() && err.code === 'EPERM') {
+ watcher.emit('change', EVENT_REMOVE, info.fpath ?? '');
+ this.flag = 'windows-error';
+ this.close(watcherPath);
+ } else {
+ this.emit('error', err);
+ }
+ });
- if (is.func(fn)) {
- if (fn.length === 1) deprecationWarning();
- this.on('change', fn);
+ watcher.on('change', internalOnChange);
}
-}
-Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCounter) {
- var self = this;
- var done = counter();
- hasNativeRecursive(function(has) {
- // always specify recursive
- options.recursive = !!options.recursive;
- // using utf8 internally
- var opts = Object.assign({}, options, {
+ /**
+ * Watch a single file
+ * @param {string} file - File path
+ * @param {Object} options - Watch options
+ * @param {Function} [fn] - Change callback
+ */
+ watchFile(file, options, fn) {
+ const parent = path.join(file, '../');
+ const opts = {
+ ...options,
+ filter: null, // No filter for single file
encoding: 'utf8'
- });
- if (!has) {
- delete opts.recursive;
- }
+ };
- // check if it's closed before calling watch.
- if (self._isClosed) {
- done();
- return self.close();
- }
+ // No need to watch recursively for single file
+ delete opts.recursive;
- var watcher = tryWatch(self, dir, opts);
+ const watcher = tryWatch(this, parent, opts);
if (!watcher) {
- done();
return;
}
- self.add(watcher, {
- type: 'dir',
- fpath: dir,
- options: options
+ this.add(watcher, {
+ type: 'file',
+ fpath: parent,
+ options: { ...opts, encoding: options.encoding },
+ compareName: (n) => is.samePath(n, file)
});
if (is.func(fn)) {
- if (fn.length === 1) deprecationWarning();
- self.on('change', fn);
+ this.on('change', fn);
}
+ }
- if (options.recursive && !has) {
- getSubDirectories(dir, function(d) {
- if (shouldNotSkip(d, options.filter)) {
- self.watchDirectory(d, options, null, counter);
- }
- }, counter());
- }
+ /**
+ * Watch a directory
+ * @param {string} dir - Directory path
+ * @param {Object} options - Watch options
+ * @param {Function} [fn] - Change callback
+ * @param {Function} [counter] - Semaphore counter
+ */
+ watchDirectory(dir, options, fn, counter = nullCounter) {
+ const done = counter();
+
+ hasNativeRecursive((has) => {
+ // Always specify recursive
+ options.recursive = !!options.recursive;
+
+ // Using utf8 internally
+ const opts = { ...options, encoding: 'utf8' };
+ if (!has) {
+ delete opts.recursive;
+ }
- done();
- });
+ // Check if closed before calling watch
+ if (this._isClosed) {
+ done();
+ return this.close();
+ }
+
+ const watcher = tryWatch(this, dir, opts);
+ if (!watcher) {
+ done();
+ return;
+ }
+
+ this.add(watcher, {
+ type: 'dir',
+ fpath: dir,
+ options
+ });
+
+ if (is.func(fn)) {
+ this.on('change', fn);
+ }
+
+ if (options.recursive && !has) {
+ getSubDirectories(
+ dir,
+ (d) => {
+ if (shouldNotSkip(d, options.filter)) {
+ this.watchDirectory(d, options, null, counter);
+ }
+ },
+ counter()
+ );
+ }
+
+ done();
+ });
+ }
}
+/**
+ * Compose multiple watchers into one
+ * @param {Watcher[]} watchers - Array of watchers
+ * @returns {Object} Composed watcher interface
+ */
function composeWatcher(watchers) {
- var watcher = new Watcher();
- var filterDups = createDupsFilter();
- var counter = watchers.length;
-
- watchers.forEach(function(w) {
- w.on('change', filterDups(function(evt, name) {
- watcher.emit('change', evt, name);
- }));
- w.on('error', function(err) {
- watcher.emit('error', err);
- });
- w.on('ready', function() {
- if (!(--counter)) {
+ const watcher = new Watcher();
+ const filterDups = createDupsFilter();
+ let counter = watchers.length;
+
+ watchers.forEach((w) => {
+ w.on(
+ 'change',
+ filterDups((evt, name) => watcher.emit('change', evt, name))
+ );
+ w.on('error', (err) => watcher.emit('error', err));
+ w.on('ready', () => {
+ if (!--counter) {
emitReady(watcher);
}
});
});
- watcher.close = function() {
- watchers.forEach(function(w) {
- w.close();
- });
+ watcher.close = () => {
+ watchers.forEach((w) => w.close());
process.nextTick(emitClose, watcher);
- }
+ };
- watcher.getWatchedPaths = function(fn) {
+ watcher.getWatchedPaths = (fn) => {
if (is.func(fn)) {
- var promises = watchers.map(function(w) {
- return new Promise(function(resolve) {
- w.getWatchedPaths(resolve);
- });
- });
- Promise.all(promises).then(function(result) {
- var ret = unique(flat1(result));
- fn(ret);
+ const promises = watchers.map(
+ (w) => new Promise((resolve) => w.getWatchedPaths(resolve))
+ );
+ Promise.all(promises).then((result) => {
+ fn(unique(result.flat(1)));
});
}
- }
+ };
return watcher.expose();
}
+/**
+ * Watch files or directories for changes
+ * @param {string|Buffer|string[]} fpath - Path(s) to watch
+ * @param {Object|string|Function} [options] - Watch options or encoding string
+ * @param {Function} [fn] - Change callback
+ * @returns {Object} Watcher interface
+ */
function watch(fpath, options, fn) {
- var watcher = new Watcher();
+ const watcher = new Watcher();
+ // Handle Buffer input
if (is.buffer(fpath)) {
fpath = fpath.toString();
}
+ // Validate path exists
if (!is.array(fpath) && !is.exists(fpath)) {
- process.nextTick(function() {
- watcher.emit('error',
- new Error(fpath + ' does not exist.')
- );
+ process.nextTick(() => {
+ watcher.emit('error', new Error(`${fpath} does not exist.`));
});
}
+ // Handle encoding string as options
if (is.string(options)) {
- options = {
- encoding: options
- }
+ options = { encoding: options };
}
+ // Handle callback as second argument
if (is.func(options)) {
fn = options;
options = {};
}
+ // Default options
if (arguments.length < 2) {
options = {};
}
+ // Validate and set encoding
if (options.encoding) {
assertEncoding(options.encoding);
} else {
options.encoding = 'utf8';
}
+ // Handle array of paths
if (is.array(fpath)) {
if (fpath.length === 1) {
return watch(fpath[0], options, fn);
}
- var filterDups = createDupsFilter();
- return composeWatcher(unique(fpath).map(function(f) {
- var w = watch(f, options);
- if (is.func(fn)) {
- w.on('change', filterDups(fn));
- }
- return w;
- }));
+ const filterDups = createDupsFilter();
+ return composeWatcher(
+ unique(fpath).map((f) => {
+ const w = watch(f, options);
+ if (is.func(fn)) {
+ w.on('change', filterDups(fn));
+ }
+ return w;
+ })
+ );
}
+ // Watch file
if (is.file(fpath)) {
watcher.watchFile(fpath, options, fn);
emitReady(watcher);
}
-
+ // Watch directory
else if (is.directory(fpath)) {
- var counter = semaphore(function () {
- emitReady(watcher);
- });
+ const counter = semaphore(() => emitReady(watcher));
watcher.watchDirectory(fpath, options, fn, counter);
}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..8207e6b
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1190 @@
+{
+ "name": "node-watch",
+ "version": "0.8.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "node-watch",
+ "version": "0.8.0",
+ "license": "MIT",
+ "devDependencies": {
+ "mocha": "^11.7.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/diff": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
+ "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mocha": {
+ "version": "11.7.5",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz",
+ "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browser-stdout": "^1.3.1",
+ "chokidar": "^4.0.1",
+ "debug": "^4.3.5",
+ "diff": "^7.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-up": "^5.0.0",
+ "glob": "^10.4.5",
+ "he": "^1.2.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "log-symbols": "^4.1.0",
+ "minimatch": "^9.0.5",
+ "ms": "^2.1.3",
+ "picocolors": "^1.1.1",
+ "serialize-javascript": "^6.0.2",
+ "strip-json-comments": "^3.1.1",
+ "supports-color": "^8.1.1",
+ "workerpool": "^9.2.0",
+ "yargs": "^17.7.2",
+ "yargs-parser": "^21.1.1",
+ "yargs-unparser": "^2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/workerpool": {
+ "version": "9.3.4",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz",
+ "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 48f9afb..28a678c 100644
--- a/package.json
+++ b/package.json
@@ -1,36 +1,43 @@
{
+ "name": "node-watch",
+ "version": "0.8.0",
"description": "A wrapper and enhancements for fs.watch",
"license": "MIT",
- "name": "node-watch",
+ "author": "yuanchuan (https://yuanchuan.dev)",
"repository": {
- "url": "git://github.com/yuanchuan/node-watch.git",
- "type": "git"
+ "type": "git",
+ "url": "git://github.com/yuanchuan/node-watch.git"
+ },
+ "homepage": "https://github.com/yuanchuan/node-watch#readme",
+ "bugs": {
+ "url": "https://github.com/yuanchuan/node-watch/issues"
},
"keywords": [
"fs.watch",
"watch",
- "watchfile"
+ "watchfile",
+ "file watcher",
+ "directory watcher"
],
- "version": "0.7.4",
- "bugs": {
- "url": "https://github.com/yuanchuan/node-watch/issues"
- },
- "url": "https://github.com/yuanchuan/node-watch",
- "author": "yuanchuan (http://yuanchuan.name)",
- "main": "./lib/watch",
+ "main": "./lib/watch.js",
"types": "./lib/watch.d.ts",
+ "exports": {
+ ".": {
+ "types": "./lib/watch.d.ts",
+ "require": "./lib/watch.js",
+ "default": "./lib/watch.js"
+ }
+ },
"files": [
"lib/"
],
- "homepage": "https://github.com/yuanchuan/node-watch#readme",
"scripts": {
- "test": "mocha test/test.js --exit --slow 500"
+ "test": "mocha test/test.js --exit --slow 500 --timeout 5000 --retries 2"
},
"engines": {
- "node": ">=6"
+ "node": ">=18"
},
"devDependencies": {
- "fs-extra": "^7.0.1",
- "mocha": "^10.2.0"
+ "mocha": "^11.7.5"
}
}
diff --git a/test/test.js b/test/test.js
index 3c1473d..8c58574 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,17 +1,19 @@
-var assert = require('assert');
-var Tree = require('./utils/builder');
-var watch = require('../lib/watch');
-var is = require('../lib/is');
-var hasNativeRecursive = require('../lib/has-native-recursive');
+'use strict';
-var tree = Tree();
-var watcher;
+const assert = require('assert');
+const Tree = require('./utils/builder');
+const watch = require('../lib/watch');
+const is = require('../lib/is');
+const hasNativeRecursive = require('../lib/has-native-recursive');
-beforeEach(function() {
+let tree = Tree();
+let watcher;
+
+beforeEach(() => {
tree = Tree();
});
-afterEach(function(done) {
+afterEach((done) => {
if (watcher && !watcher.isClosed()) {
watcher.on('close', done);
watcher.close();
@@ -20,19 +22,24 @@ afterEach(function(done) {
}
});
-after(function() {
+after(() => {
if (tree) {
tree.cleanup();
}
});
-function wait(fn, timeout) {
+/**
+ * Retry assertion until it passes or timeout
+ * @param {Function} fn - Assertion function
+ * @param {number} timeout - Timeout in milliseconds
+ */
+function wait(fn, timeout = 450) {
try {
fn();
} catch (error) {
timeout -= 30;
if (timeout >= 0) {
- setTimeout(function() {
+ setTimeout(() => {
wait(fn, timeout);
}, 30);
} else {
@@ -41,115 +48,115 @@ function wait(fn, timeout) {
}
}
-describe('process events', function() {
- it('should emit `close` event', function(done) {
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
- watcher = watch(fpath, function() {});
- watcher.on('close', function() {
+describe('process events', () => {
+ it('should emit `close` event', (done) => {
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
+ watcher = watch(fpath, () => {});
+ watcher.on('close', () => {
done();
});
watcher.close();
});
- it('should emit `ready` event when watching a file', function(done) {
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
+ it('should emit `ready` event when watching a file', (done) => {
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
watcher = watch(fpath);
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
done();
});
});
- it('should emit `ready` event when watching a directory recursively', function(done) {
- var dir = tree.getPath('home');
+ it('should emit `ready` event when watching a directory recursively', (done) => {
+ const dir = tree.getPath('home');
watcher = watch(dir, { recursive: true });
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
done();
});
});
- it('should emit `ready` properly in a composed watcher', function(done) {
- var dir1 = tree.getPath('home/a');
- var dir2 = tree.getPath('home/b');
- var file = tree.getPath('home/b/file1');
+ it('should emit `ready` properly in a composed watcher', (done) => {
+ const dir1 = tree.getPath('home/a');
+ const dir2 = tree.getPath('home/b');
+ const file = tree.getPath('home/b/file1');
watcher = watch([dir1, dir2, file], { recursive: true });
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
done();
});
});
});
-describe('watch for files', function() {
- it('should watch a single file and keep watching', function(done) {
- var times = 1;
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
- watcher = watch(fpath, { delay: 0 }, function(evt, name) {
- assert.equal(fpath, name)
+describe('watch for files', () => {
+ it('should watch a single file and keep watching', (done) => {
+ let times = 1;
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
+ watcher = watch(fpath, { delay: 0 }, (evt, name) => {
+ assert.strictEqual(fpath, name);
if (times++ >= 3) {
done();
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file);
tree.modify(file, 100);
tree.modify(file, 200);
});
});
- it('should watch files inside a directory', function(done) {
- var fpath = tree.getPath('home/a');
- var stack = [
+ it('should watch files inside a directory', (done) => {
+ const fpath = tree.getPath('home/a');
+ const stack = [
tree.getPath('home/a/file1'),
tree.getPath('home/a/file2')
];
- watcher = watch(fpath, { delay: 0 }, function(evt, name) {
+ watcher = watch(fpath, { delay: 0 }, (evt, name) => {
stack.splice(stack.indexOf(name), 1);
if (!stack.length) done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify('home/a/file1');
tree.modify('home/a/file2', 100);
});
});
- it('should ignore duplicate changes', function(done) {
- var file = 'home/a/file2';
- var fpath = tree.getPath(file);
- var times = 0;
- watcher = watch(fpath, { delay: 200 }, function(evt, name) {
+ it('should ignore duplicate changes', (done) => {
+ const file = 'home/a/file2';
+ const fpath = tree.getPath(file);
+ let times = 0;
+ watcher = watch(fpath, { delay: 200 }, (evt, name) => {
if (fpath === name) times++;
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file);
tree.modify(file, 100);
tree.modify(file, 150);
- wait(function() {
- assert.equal(times, 1)
+ wait(() => {
+ assert.strictEqual(times, 1);
done();
- }, 250);
+ });
});
});
- it('should listen to new created files', function(done) {
- var home = tree.getPath('home');
- var newfile1 = 'home/a/newfile' + Math.random();
- var newfile2 = 'home/a/newfile' + Math.random();
- var changes = [];
- watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) {
+ it('should listen to new created files', (done) => {
+ const home = tree.getPath('home');
+ const newfile1 = 'home/a/newfile' + Math.random();
+ const newfile2 = 'home/a/newfile' + Math.random();
+ const changes = [];
+ watcher = watch(home, { delay: 0, recursive: true }, (evt, name) => {
changes.push(name);
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.newFile(newfile1);
tree.newFile(newfile2);
- wait(function() {
+ wait(() => {
// On windows it will report its parent directory along with the filename
// https://github.com/yuanchuan/node-watch/issues/79
if (is.windows()) {
- // Make sure new files are deteced
+ // Make sure new files are detected
assert.ok(
changes.includes(tree.getPath(newfile1)) &&
changes.includes(tree.getPath(newfile2))
@@ -157,73 +164,70 @@ describe('watch for files', function() {
// It should only include new files and its parent directory
// if there are more than 2 events
if (changes.length > 2) {
- let accepts = [
+ const accepts = [
tree.getPath(newfile1),
tree.getPath(newfile2),
tree.getPath('home/a')
];
- changes.forEach(function(name) {
- assert.ok(accepts.includes(name), name + " should not be included");
+ changes.forEach((name) => {
+ assert.ok(accepts.includes(name), name + ' should not be included');
});
}
} else {
assert.deepStrictEqual(
- changes,
- [tree.getPath(newfile1), tree.getPath(newfile2)]
+ changes.sort(),
+ [tree.getPath(newfile1), tree.getPath(newfile2)].sort()
);
}
done();
- }, 100);
+ });
});
});
- it('should error when parent gets deleted before calling fs.watch', function(done) {
- var fpath = tree.getPath('home/a/file1');
+ it('should error when parent gets deleted before calling fs.watch', (done) => {
+ const fpath = tree.getPath('home/a/file1');
watcher = watch(fpath, Object.defineProperty({}, 'test', {
enumerable: true,
- get: function() {
+ get() {
tree.remove('home/a');
return 'test';
}
}));
- watcher.on('error', function() {
+ watcher.on('error', () => {
done();
});
});
});
-describe('watch for directories', function() {
- it('should watch directories inside a directory', function(done) {
- var home = tree.getPath('home');
- var dir = tree.getPath('home/c');
- var events = [];
+describe('watch for directories', () => {
+ it('should watch directories inside a directory', (done) => {
+ const home = tree.getPath('home');
+ const dir = tree.getPath('home/c');
+ const events = [];
- watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) {
+ watcher = watch(home, { delay: 0, recursive: true }, (evt, name) => {
if (name === dir) {
events.push(evt);
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.remove('home/c');
- wait(function () {
- assert.deepStrictEqual(
- events,
- [ 'remove' ]
- );
+ wait(() => {
+ assert.deepStrictEqual(events, ['remove']);
done();
- }, 400);
+ });
});
});
- it('should watch new created directories', function(done) {
- var home = tree.getPath('home');
- watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) {
+ it('should watch new created directories', (done) => {
+ const home = tree.getPath('home');
+ watcher = watch(home, { delay: 0, recursive: true }, (evt, name) => {
if (name === tree.getPath('home/new/file1')) {
done();
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
// newFile() will create the 'new/' directory and the 'new/file1' file,
// but, only the creation of the directory is observed.
// Because of that, there will only be one event for file1, when it
@@ -233,169 +237,169 @@ describe('watch for directories', function() {
});
});
- it('should not watch new created directories which are being skipped in the filter', function(done) {
- var home = tree.getPath('home');
- var options = {
+ it('should not watch new created directories which are being skipped in the filter', (done) => {
+ const home = tree.getPath('home');
+ const options = {
delay: 0,
recursive: true,
- filter: function(filePath, skip) {
+ filter(filePath, skip) {
if (/ignored/.test(filePath)) return skip;
return true;
}
- }
+ };
- watcher = watch(home, options, function(evt, name) {
- assert.fail("event detect", name);
+ watcher = watch(home, options, (evt, name) => {
+ assert.fail('event detect', name);
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.newFile('home/ignored/file');
tree.modify('home/ignored/file', 100);
- wait(done, 150);
+ wait(done);
});
});
- it('should keep watching after removal of sub directory', function(done) {
- var home = tree.getPath('home');
- var file1 = tree.getPath('home/e/file1');
- var file2 = tree.getPath('home/e/file2');
- var dir = tree.getPath('home/e/sub');
- var events = [];
- watcher = watch(home, { delay: 0, recursive: true }, function(evt, name) {
+ it('should keep watching after removal of sub directory', (done) => {
+ const home = tree.getPath('home');
+ const file1 = tree.getPath('home/e/file1');
+ const file2 = tree.getPath('home/e/file2');
+ const dir = tree.getPath('home/e/sub');
+ const events = [];
+ watcher = watch(home, { delay: 0, recursive: true }, (evt, name) => {
if (name === dir || name === file1 || name === file2) {
events.push(name);
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.remove('home/e/sub', 50);
tree.modify('home/e/file1', 100);
tree.modify('home/e/file2', 200);
- wait(function() {
+ wait(() => {
assert.deepStrictEqual(events, [dir, file1, file2]);
done();
- }, 300);
+ });
});
});
- it('should watch new directories without delay', function(done) {
- var home = tree.getPath('home');
- var events = [];
- watcher = watch(home, { delay: 200, recursive: true }, function(evt, name) {
+ it('should watch new directories without delay', (done) => {
+ const home = tree.getPath('home');
+ const events = [];
+ watcher = watch(home, { delay: 200, recursive: true }, (evt, name) => {
if (name === tree.getPath('home/new/file1')) {
events.push(evt);
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.newFile('home/new/file1');
tree.modify('home/new/file1', 50);
tree.modify('home/new/file1', 100);
- wait(function() {
+ wait(() => {
assert.deepStrictEqual(events, ['update']);
done();
- }, 350);
+ });
});
});
- it('should error when directory gets deleted before calling fs.watch', function(done) {
- var dir = 'home/c';
- var fpath = tree.getPath(dir);
+ it('should error when directory gets deleted before calling fs.watch', (done) => {
+ const dir = 'home/c';
+ const fpath = tree.getPath(dir);
watcher = watch(fpath, Object.defineProperty({}, 'test', {
enumerable: true,
- get: function() {
+ get() {
tree.remove(dir);
return 'test';
}
}));
- watcher.on('error', function() {
+ watcher.on('error', () => {
done();
});
});
});
-describe('file events', function() {
- it('should identify `remove` event', function(done) {
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
- watcher = watch(fpath, function(evt, name) {
+describe('file events', () => {
+ it('should identify `remove` event', (done) => {
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
+ watcher = watch(fpath, (evt, name) => {
if (evt === 'remove' && name === fpath) done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.remove(file);
});
});
- it('should identify `remove` event on directory', function(done) {
- var dir = 'home/a';
- var home = tree.getPath('home');
- var fpath = tree.getPath(dir);
- watcher = watch(home, function(evt, name) {
+ it('should identify `remove` event on directory', (done) => {
+ const dir = 'home/a';
+ const home = tree.getPath('home');
+ const fpath = tree.getPath(dir);
+ watcher = watch(home, (evt, name) => {
if (evt === 'remove' && name === fpath) done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.remove(dir);
});
});
- it('should be able to handle many events on deleting', function(done) {
- var dir = 'home/a';
- var fpath = tree.getPath(dir);
- var names = tree.newRandomFiles(dir, 300);
+ it('should be able to handle many events on deleting', (done) => {
+ const dir = 'home/a';
+ const fpath = tree.getPath(dir);
+ const names = tree.newRandomFiles(dir, 300);
- var count = 0;
- watcher = watch(fpath, function(evt, name) {
+ let count = 0;
+ watcher = watch(fpath, () => {
count += 1;
- if (count == names.length) done();
+ if (count === names.length) done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
names.forEach(tree.remove.bind(tree));
});
});
- it('should identify `update` event', function(done) {
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
- watcher = watch(fpath, function(evt, name) {
+ it('should identify `update` event', (done) => {
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
+ watcher = watch(fpath, (evt, name) => {
if (evt === 'update' && name === fpath) done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file);
});
});
- it('should report `update` on new files', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/newfile' + Date.now();
- var fpath = tree.getPath(file);
- watcher = watch(dir, function(evt, name) {
+ it('should report `update` on new files', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/newfile' + Date.now();
+ const fpath = tree.getPath(file);
+ watcher = watch(dir, (evt, name) => {
if (evt === 'update' && name === fpath) done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.newFile(file);
});
});
});
-describe('options', function() {
- describe('recursive', function() {
- it('should watch recursively with `recursive: true` option', function(done) {
- var dir = tree.getPath('home');
- var file = tree.getPath('home/bb/file1');
- watcher = watch(dir, { recursive: true }, function(evt, name) {
+describe('options', () => {
+ describe('recursive', () => {
+ it('should watch recursively with `recursive: true` option', (done) => {
+ const dir = tree.getPath('home');
+ const file = tree.getPath('home/bb/file1');
+ watcher = watch(dir, { recursive: true }, (evt, name) => {
if (file === name) {
done();
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify('home/bb/file1');
});
});
});
- describe('encoding', function() {
- it('should throw on invalid encoding', function(done) {
- var dir = tree.getPath('home/a');
+ describe('encoding', () => {
+ it('should throw on invalid encoding', (done) => {
+ const dir = tree.getPath('home/a');
try {
watcher = watch(dir, 'unknown');
} catch (e) {
@@ -403,207 +407,204 @@ describe('options', function() {
}
});
- it('should accept options as an encoding string', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
- watcher = watch(dir, 'utf8', function(evt, name) {
- assert.equal(name.toString(), fpath);
+ it('should accept options as an encoding string', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
+ watcher = watch(dir, 'utf8', (evt, name) => {
+ assert.strictEqual(name.toString(), fpath);
done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file);
});
});
- it('should support buffer encoding', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
- watcher = watch(dir, 'buffer', function(evt, name) {
- assert(Buffer.isBuffer(name), 'not a Buffer')
- assert.equal(name.toString(), fpath);
+ it('should support buffer encoding', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
+ watcher = watch(dir, 'buffer', (evt, name) => {
+ assert(Buffer.isBuffer(name), 'not a Buffer');
+ assert.strictEqual(name.toString(), fpath);
done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file);
});
});
- it('should support base64 encoding', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
- watcher = watch(dir, 'base64', function(evt, name) {
- assert.equal(
+ it('should support base64 encoding', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
+ watcher = watch(dir, 'base64', (evt, name) => {
+ assert.strictEqual(
name,
Buffer.from(fpath).toString('base64'),
'wrong base64 encoding'
);
done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file);
});
});
- it('should support hex encoding', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
- watcher = watch(dir, 'hex', function(evt, name) {
- assert.equal(
+ it('should support hex encoding', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
+ watcher = watch(dir, 'hex', (evt, name) => {
+ assert.strictEqual(
name,
Buffer.from(fpath).toString('hex'),
'wrong hex encoding'
);
done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file);
});
});
});
- describe('filter', function() {
- it('should only watch filtered directories', function(done) {
- var matchRegularDir = false;
- var matchIgnoredDir = false;
+ describe('filter', () => {
+ it('should only watch filtered directories', (done) => {
+ let matchRegularDir = false;
+ let matchIgnoredDir = false;
- var options = {
+ const options = {
delay: 0,
recursive: true,
- filter: function(name) {
+ filter(name) {
return !/deep_node_modules/.test(name);
}
};
- watcher = watch(tree.getPath('home'), options, function(evt, name) {
+ watcher = watch(tree.getPath('home'), options, (evt, name) => {
if (/deep_node_modules/.test(name)) {
matchIgnoredDir = true;
} else {
matchRegularDir = true;
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify('home/b/file1');
tree.modify('home/deep_node_modules/ma/file1');
- wait(function() {
+ wait(() => {
assert(matchRegularDir, 'watch failed to detect regular file');
assert(!matchIgnoredDir, 'fail to ignore path `deep_node_modules`');
done();
- }, 100);
+ });
});
});
- it('should only report filtered files', function(done) {
- var dir = tree.getPath('home');
- var file1 = 'home/bb/file1';
- var file2 = 'home/bb/file2';
+ it('should only report filtered files', (done) => {
+ const dir = tree.getPath('home');
+ const file1 = 'home/bb/file1';
+ const file2 = 'home/bb/file2';
- var options = {
+ const options = {
delay: 0,
recursive: true,
- filter: function(name) {
+ filter(name) {
return /file2/.test(name);
}
- }
+ };
- var times = 0;
- var matchIgnoredFile = false;
- watcher = watch(dir, options, function(evt, name) {
+ let times = 0;
+ let matchIgnoredFile = false;
+ watcher = watch(dir, options, (evt, name) => {
times++;
if (name === tree.getPath(file1)) {
matchIgnoredFile = true;
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file1);
tree.modify(file2, 50);
- wait(function() {
- assert.equal(times, 1, 'should only report /home/bb/file2 once');
- assert.equal(matchIgnoredFile, false, 'home/bb/file1 should be ignored');
+ wait(() => {
+ assert.strictEqual(times, 1, 'should only report /home/bb/file2 once');
+ assert.strictEqual(matchIgnoredFile, false, 'home/bb/file1 should be ignored');
done();
- }, 100);
+ });
});
});
- it('should be able to filter with regexp', function(done) {
- var dir = tree.getPath('home');
- var file1 = 'home/bb/file1';
- var file2 = 'home/bb/file2';
+ it('should be able to filter with regexp', (done) => {
+ const dir = tree.getPath('home');
+ const file1 = 'home/bb/file1';
+ const file2 = 'home/bb/file2';
- var options = {
+ const options = {
delay: 0,
recursive: true,
- filter: /file2/
- }
+ filter: /file2/
+ };
- var times = 0;
- var matchIgnoredFile = false;
- watcher = watch(dir, options, function(evt, name) {
+ let times = 0;
+ let matchIgnoredFile = false;
+ watcher = watch(dir, options, (evt, name) => {
times++;
if (name === tree.getPath(file1)) {
matchIgnoredFile = true;
}
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file1);
tree.modify(file2, 50);
- wait(function() {
+ wait(() => {
assert(times, 1, 'report file2');
assert(!matchIgnoredFile, 'home/bb/file1 should be ignored');
done();
- }, 100);
+ });
});
});
- it('should be able to skip subdirectories with `skip` flag', function(done) {
- var home = tree.getPath('home');
- var options = {
+ it('should be able to skip subdirectories with `skip` flag', (done) => {
+ const home = tree.getPath('home');
+ const options = {
delay: 0,
recursive: true,
- filter: function(name, skip) {
+ filter(name, skip) {
if (/\/deep_node_modules/.test(name)) return skip;
}
};
watcher = watch(home, options);
- watcher.getWatchedPaths(function(paths) {
- hasNativeRecursive(function(supportRecursive) {
- var watched = supportRecursive
- // The skip flag has no effect to the platforms which support recursive option,
- // so the home directory is the only one that's in the watching list.
+ watcher.getWatchedPaths((paths) => {
+ hasNativeRecursive((supportRecursive) => {
+ const watched = supportRecursive
+ // The skip flag has no effect to the platforms which support recursive option,
+ // so the home directory is the only one that's in the watching list.
? [home]
- // The deep_node_modules and all its subdirectories should not be watched
- // with skip flag specified in the filter.
- : tree.getAllDirectories().filter(function(name) {
+ // The deep_node_modules and all its subdirectories should not be watched
+ // with skip flag specified in the filter.
+ : tree.getAllDirectories().filter((name) => {
return !/\/deep_node_modules/.test(name);
});
- assert.deepStrictEqual(
- watched.sort(), paths.sort()
- );
-
+ assert.deepStrictEqual(watched.sort(), paths.sort());
done();
});
});
});
});
- describe('delay', function() {
- it('should have delayed response', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/file1';
- var start;
- watcher = watch(dir, { delay: 300 }, function(evt, name) {
+ describe('delay', () => {
+ it('should have delayed response', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/file1';
+ let start;
+ watcher = watch(dir, { delay: 300 }, () => {
assert(Date.now() - start >= 300, 'delay not working');
done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
start = Date.now();
tree.modify(file);
});
@@ -611,223 +612,214 @@ describe('options', function() {
});
});
-describe('parameters', function() {
- it('should throw error on non-existed file', function(done) {
- var somedir = tree.getPath('home/somedir');
+describe('parameters', () => {
+ it('should throw error on non-existed file', (done) => {
+ const somedir = tree.getPath('home/somedir');
watcher = watch(somedir);
- watcher.on('error', function(err) {
+ watcher.on('error', (err) => {
if (err.message.includes('does not exist')) {
- done()
+ done();
}
- })
+ });
});
- it('should accept filename as Buffer', function(done) {
- var fpath = tree.getPath('home/a/file1');
- watcher = watch(Buffer.from(fpath), { delay: 0 }, function(evt, name) {
- assert.equal(name, fpath);
+ it('should accept filename as Buffer', (done) => {
+ const fpath = tree.getPath('home/a/file1');
+ watcher = watch(Buffer.from(fpath), { delay: 0 }, (evt, name) => {
+ assert.strictEqual(name, fpath);
done();
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify('home/a/file1');
});
});
- it('should compose array of files or directories', function(done) {
- var file1 = 'home/a/file1';
- var file2 = 'home/a/file2';
- var fpaths = [
+ it('should compose array of files or directories', (done) => {
+ const file1 = 'home/a/file1';
+ const file2 = 'home/a/file2';
+ const fpaths = [
tree.getPath(file1),
tree.getPath(file2)
];
- var times = 0;
- watcher = watch(fpaths, { delay: 0 }, function(evt, name) {
+ let times = 0;
+ watcher = watch(fpaths, { delay: 0 }, (evt, name) => {
if (fpaths.indexOf(name) !== -1) times++;
- if (times === 2) done(); // calling done more than twice causes mocha test to fail
+ if (times === 2) done(); // calling done more than twice causes mocha test to fail
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file1);
tree.modify(file2, 50);
});
});
- it('should filter duplicate events for composed watcher', function(done) {
- var home = 'home';
- var dir = 'home/a';
- var file1 = 'home/a/file1';
- var file2 = 'home/a/file2';
- var fpaths = [
+ it('should filter duplicate events for composed watcher', (done) => {
+ const home = 'home';
+ const dir = 'home/a';
+ const file1 = 'home/a/file1';
+ const file2 = 'home/a/file2';
+ const fpaths = [
tree.getPath(home),
tree.getPath(dir),
tree.getPath(file1),
tree.getPath(file2)
];
- var changes = [];
- watcher = watch(fpaths, { delay: 100, recursive: true }, function(evt, name) {
+ const changes = [];
+ watcher = watch(fpaths, { delay: 100, recursive: true }, (evt, name) => {
changes.push(name);
});
- watcher.on('ready', function() {
+ watcher.on('ready', () => {
tree.modify(file1);
tree.modify(file2, 50);
- wait(function() {
+ wait(() => {
assert.deepStrictEqual(
- changes,
- [tree.getPath(file1), tree.getPath(file2)]
+ changes.sort(),
+ [tree.getPath(file1), tree.getPath(file2)].sort()
);
done();
- }, 200);
+ });
});
});
});
-describe('watcher object', function() {
- it('should using watcher object to watch', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/file1';
- var fpath = tree.getPath(file);
+describe('watcher object', () => {
+ it('should using watcher object to watch', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/file1';
+ const fpath = tree.getPath(file);
watcher = watch(dir, { delay: 0 });
- watcher.on('ready', function() {
- watcher.on('change', function(evt, name) {
- assert.equal(evt, 'update');
- assert.equal(name, fpath);
+ watcher.on('ready', () => {
+ watcher.on('change', (evt, name) => {
+ assert.strictEqual(evt, 'update');
+ assert.strictEqual(name, fpath);
done();
});
tree.modify(file);
});
});
- describe('close()', function() {
- it('should close a watcher using .close()', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/file1';
- var times = 0;
+ describe('close()', () => {
+ it('should close a watcher using .close()', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/file1';
+ let times = 0;
watcher = watch(dir, { delay: 0 });
- watcher.on('change', function(evt, name) {
+ watcher.on('change', () => {
times++;
});
- watcher.on('ready', function() {
-
+ watcher.on('ready', () => {
watcher.close();
tree.modify(file);
tree.modify(file, 100);
- wait(function() {
+ wait(() => {
assert(watcher.isClosed(), 'watcher should be closed');
- assert.equal(times, 0, 'failed to close the watcher');
+ assert.strictEqual(times, 0, 'failed to close the watcher');
done();
- }, 150);
+ });
});
});
- it('should not watch after .close() is called', function(done) {
- var dir = tree.getPath('home');
+ it('should not watch after .close() is called', (done) => {
+ const dir = tree.getPath('home');
watcher = watch(dir, { delay: 0, recursive: true });
watcher.close();
- watcher.getWatchedPaths(function(dirs) {
+ watcher.getWatchedPaths((dirs) => {
assert(dirs.length === 0);
done();
});
});
- it('Do not emit after close', function(done) {
- var dir = tree.getPath('home/a');
- var file = 'home/a/file1';
- var times = 0;
+ it('Do not emit after close', (done) => {
+ const dir = tree.getPath('home/a');
+ const file = 'home/a/file1';
+ let times = 0;
watcher = watch(dir, { delay: 0 });
- watcher.on('change', function(evt, name) {
+ watcher.on('change', () => {
times++;
});
- watcher.on('ready', function() {
-
+ watcher.on('ready', () => {
watcher.close();
- var timer = setInterval(function() {
+ const timer = setInterval(() => {
tree.modify(file);
});
- wait(function() {
+ wait(() => {
clearInterval(timer);
assert(watcher.isClosed(), 'watcher should be closed');
- assert.equal(times, 0, 'failed to close the watcher');
+ assert.strictEqual(times, 0, 'failed to close the watcher');
done();
- }, 100);
+ });
});
});
-
});
- describe('getWatchedPaths()', function() {
- it('should get all the watched paths', function(done) {
- var home = tree.getPath('home');
+ describe('getWatchedPaths()', () => {
+ it('should get all the watched paths', (done) => {
+ const home = tree.getPath('home');
watcher = watch(home, {
delay: 0,
recursive: true
});
- watcher.getWatchedPaths(function(paths) {
- hasNativeRecursive(function(supportRecursive) {
- var watched = supportRecursive
- // The home directory is the only one that's being watched
- // if the recursive option is natively supported.
+ watcher.getWatchedPaths((paths) => {
+ hasNativeRecursive((supportRecursive) => {
+ const watched = supportRecursive
+ // The home directory is the only one that's being watched
+ // if the recursive option is natively supported.
? [home]
- // Otherwise it should include all its subdirectories.
+ // Otherwise it should include all its subdirectories.
: tree.getAllDirectories();
- assert.deepStrictEqual(
- watched.sort(), paths.sort()
- );
-
+ assert.deepStrictEqual(watched.sort(), paths.sort());
done();
});
});
});
- it('should get its parent path instead of the file itself', function(done) {
- var file = tree.getPath('home/a/file1');
+ it('should get its parent path instead of the file itself', (done) => {
+ const file = tree.getPath('home/a/file1');
// The parent path is actually being watched instead.
- var parent = tree.getPath('home/a');
+ const parent = tree.getPath('home/a');
watcher = watch(file, { delay: 0 });
- watcher.getWatchedPaths(function(paths) {
+ watcher.getWatchedPaths((paths) => {
assert.deepStrictEqual([parent], paths);
done();
});
});
- it('should work correctly with composed watcher', function(done) {
- var a = tree.getPath('home/a');
+ it('should work correctly with composed watcher', (done) => {
+ const a = tree.getPath('home/a');
- var b = tree.getPath('home/b');
- var file = tree.getPath('home/b/file1');
+ const b = tree.getPath('home/b');
+ const file = tree.getPath('home/b/file1');
- var nested = tree.getPath('home/deep_node_modules');
- var ma = tree.getPath('home/deep_node_modules/ma');
- var mb = tree.getPath('home/deep_node_modules/mb');
- var mc = tree.getPath('home/deep_node_modules/mc');
+ const nested = tree.getPath('home/deep_node_modules');
+ const ma = tree.getPath('home/deep_node_modules/ma');
+ const mb = tree.getPath('home/deep_node_modules/mb');
+ const mc = tree.getPath('home/deep_node_modules/mc');
watcher = watch([a, file, nested], {
delay: 0,
recursive: true
});
- watcher.getWatchedPaths(function(paths) {
- hasNativeRecursive(function(supportRecursive) {
- var watched = supportRecursive
+ watcher.getWatchedPaths((paths) => {
+ hasNativeRecursive((supportRecursive) => {
+ const watched = supportRecursive
? [a, b, nested]
: [a, b, nested, ma, mb, mc];
- assert.deepStrictEqual(
- watched.sort(), paths.sort()
- );
-
+ assert.deepStrictEqual(watched.sort(), paths.sort());
done();
});
});
diff --git a/test/utils/builder.js b/test/utils/builder.js
index d23c040..36acb9b 100644
--- a/test/utils/builder.js
+++ b/test/utils/builder.js
@@ -1,64 +1,112 @@
-var fs = require('fs-extra');
-var path = require('path');
-
-var structure = fs.readFileSync(
- path.join(__dirname, './structure'),
- 'utf-8'
-);
-
-var code = structure
- .split('\n')
- .map(function(line) {
- return {
- indent: line.length - line.replace(/^\s+/,'').length,
- type: /\/$/.test(line) ? 'dir': 'file',
- text: line.replace(/^\s+|\s*\/\s*|\s+$/g, '')
- }
- })
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+
+const structure = fs.readFileSync(path.join(__dirname, './structure'), 'utf-8');
+const code = structure.split('\n').map((line) => ({
+ indent: line.length - line.replace(/^\s+/, '').length,
+ type: /\/$/.test(line) ? 'dir' : 'file',
+ text: line.replace(/^\s+|\s*\/\s*|\s+$/g, '')
+}));
+
+/**
+ * Join path segments
+ * @param {string[]} arr - Path segments
+ * @returns {string}
+ */
function join(arr) {
return arr.join('/');
}
+/**
+ * Transform parsed structure into path list
+ * @param {Array} arr - Parsed structure
+ * @returns {Array}
+ */
function transform(arr) {
- var result = [];
- var temp = [];
- var indent = 0;
- arr.forEach(function(line) {
+ const result = [];
+ const temp = [];
+ let indent = 0;
+
+ arr.forEach((line) => {
if (!line.text) {
return;
- }
- else if (!line.indent) {
+ } else if (!line.indent) {
temp.push(line.text);
- result.push({type: line.type, text: join(temp) });
- }
- else if (indent < line.indent) {
+ result.push({ type: line.type, text: join(temp) });
+ } else if (indent < line.indent) {
temp.push(line.text);
result[result.length - 1].type = 'dir';
- result.push({type: line.type, text: join(temp) });
- }
- else if (indent === line.indent) {
+ result.push({ type: line.type, text: join(temp) });
+ } else if (indent === line.indent) {
temp.pop();
temp.push(line.text);
- result.push({type: line.type, text: join(temp) });
- }
- else if(indent > line.indent) {
+ result.push({ type: line.type, text: join(temp) });
+ } else if (indent > line.indent) {
temp.pop();
temp.pop();
- temp.push(line.text)
- result.push({type: line.type, text: join(temp) });
+ temp.push(line.text);
+ result.push({ type: line.type, text: join(temp) });
}
indent = line.indent;
});
+
return result;
}
-var transformed= transform(code);
-var defaultTestPath= path.join(__dirname, '__TREE__');
+/**
+ * Ensure directory exists (like fs-extra's ensureDirSync)
+ * @param {string} dirPath - Directory path to ensure
+ */
+function ensureDirSync(dirPath) {
+ fs.mkdirSync(dirPath, { recursive: true });
+}
+
+/**
+ * Ensure file exists, creating parent directories if needed (like fs-extra's ensureFileSync)
+ * @param {string} filePath - File path to ensure
+ */
+function ensureFileSync(filePath) {
+ const dir = path.dirname(filePath);
+ ensureDirSync(dir);
+ // Only create if it doesn't exist
+ if (!fs.existsSync(filePath)) {
+ fs.writeFileSync(filePath, '');
+ }
+}
-var delayTimers = [];
+/**
+ * Remove file or directory recursively (like fs-extra's removeSync)
+ * @param {string} targetPath - Path to remove
+ */
+function removeSync(targetPath) {
+ fs.rmSync(targetPath, { recursive: true, force: true });
+}
+/**
+ * Ensure symlink exists, creating parent directories if needed
+ * @param {string} srcPath - Source path
+ * @param {string} destPath - Destination path
+ */
+function ensureSymlinkSync(srcPath, destPath) {
+ const dir = path.dirname(destPath);
+ ensureDirSync(dir);
+ fs.symlinkSync(srcPath, destPath);
+}
+
+const transformed = transform(code);
+const defaultTestPath = path.join(__dirname, '__TREE__');
+
+const delayTimers = [];
+
+/**
+ * Execute function with optional delay
+ * @param {Function} fn - Function to execute
+ * @param {number} [delay] - Delay in milliseconds
+ */
function maybeDelay(fn, delay) {
if (delay) {
delayTimers.push(setTimeout(fn, delay));
@@ -67,80 +115,137 @@ function maybeDelay(fn, delay) {
}
}
+/**
+ * Clear all pending delay timers
+ */
function clearDelayTimers() {
delayTimers.forEach(clearTimeout);
delayTimers.length = 0;
}
+/**
+ * Create a test file tree builder
+ * @returns {Object} Tree builder interface
+ */
module.exports = function builder() {
clearDelayTimers();
- var root = defaultTestPath;
- transformed.forEach(function(line) {
- var target = path.join(root, line.text)
+ const root = defaultTestPath;
+
+ transformed.forEach((line) => {
+ const target = path.join(root, line.text);
if (line.type === 'dir') {
- fs.ensureDirSync(target);
- }
- else {
- fs.ensureFileSync(target);
+ ensureDirSync(target);
+ } else {
+ ensureFileSync(target);
}
});
+
return {
- getPath: function(fpath, sub) {
+ /**
+ * Get full path for a relative path
+ * @param {string} fpath - Relative path
+ * @param {string} [sub] - Sub path
+ * @returns {string}
+ */
+ getPath(fpath, sub) {
return path.join(root, fpath, sub || '');
},
- modify: function(fpath, delay) {
- var filePath = this.getPath(fpath);
- maybeDelay(function() {
+
+ /**
+ * Modify a file (append content)
+ * @param {string} fpath - File path
+ * @param {number} [delay] - Delay in milliseconds
+ */
+ modify(fpath, delay) {
+ const filePath = this.getPath(fpath);
+ maybeDelay(() => {
fs.appendFileSync(filePath, 'hello');
}, delay);
},
- remove: function(fpath, delay) {
- var filePath = this.getPath(fpath);
- maybeDelay(function() {
- fs.removeSync(filePath);
+
+ /**
+ * Remove a file or directory
+ * @param {string} fpath - Path to remove
+ * @param {number} [delay] - Delay in milliseconds
+ */
+ remove(fpath, delay) {
+ const filePath = this.getPath(fpath);
+ maybeDelay(() => {
+ removeSync(filePath);
}, delay);
},
- newFile: function(fpath, delay) {
- var filePath = this.getPath(fpath);
- maybeDelay(function() {
- fs.ensureFileSync(filePath);
+
+ /**
+ * Create a new file
+ * @param {string} fpath - File path
+ * @param {number} [delay] - Delay in milliseconds
+ */
+ newFile(fpath, delay) {
+ const filePath = this.getPath(fpath);
+ maybeDelay(() => {
+ ensureFileSync(filePath);
}, delay);
},
- newRandomFiles: function(fpath, count) {
- var names = [];
- for (var i = 0; i < count; ++i) {
- var name = Math.random().toString().substr(2);
- var filePath = this.getPath(fpath, name);
- fs.ensureFileSync(filePath);
+
+ /**
+ * Create multiple random files
+ * @param {string} fpath - Directory path
+ * @param {number} count - Number of files to create
+ * @returns {string[]} Array of created file paths
+ */
+ newRandomFiles(fpath, count) {
+ const names = [];
+ for (let i = 0; i < count; ++i) {
+ const name = Math.random().toString().slice(2);
+ const filePath = this.getPath(fpath, name);
+ ensureFileSync(filePath);
names.push(path.join(fpath, name));
}
return names;
},
- newSymLink: function(src, dist) {
- fs.ensureSymlinkSync(
- this.getPath(src),
- this.getPath(dist)
- );
+
+ /**
+ * Create a symbolic link
+ * @param {string} src - Source path
+ * @param {string} dist - Destination path
+ */
+ newSymLink(src, dist) {
+ ensureSymlinkSync(this.getPath(src), this.getPath(dist));
},
- newDir: function(fpath, delay) {
- var filePath = this.getPath(fpath);
- maybeDelay(function() {
- fs.ensureDirSync(filePath);
+
+ /**
+ * Create a new directory
+ * @param {string} fpath - Directory path
+ * @param {number} [delay] - Delay in milliseconds
+ */
+ newDir(fpath, delay) {
+ const filePath = this.getPath(fpath);
+ maybeDelay(() => {
+ ensureDirSync(filePath);
}, delay);
},
- cleanup: function() {
+
+ /**
+ * Clean up the test tree
+ */
+ cleanup() {
try {
- fs.removeSync(root);
+ removeSync(root);
} catch (e) {
console.warn('cleanup failed.');
}
},
- getAllDirectories: function() {
+
+ /**
+ * Get all directories in the tree
+ * @returns {string[]}
+ */
+ getAllDirectories() {
function walk(dir) {
- var ret = [];
- fs.readdirSync(dir).forEach(function(d) {
- var fpath = path.join(dir, d);
+ let ret = [];
+ fs.readdirSync(dir).forEach((d) => {
+ const fpath = path.join(dir, d);
if (fs.statSync(fpath).isDirectory()) {
ret.push(fpath);
ret = ret.concat(walk(fpath));
@@ -150,5 +255,5 @@ module.exports = function builder() {
}
return walk(root);
}
- }
-}
+ };
+};