diff --git a/lib/is.js b/lib/is.js index ebe0600..0aa34a8 100644 --- a/lib/is.js +++ b/lib/is.js @@ -1,4 +1,4 @@ -var fs = require('fs'); +var fs = require('fs-extra'); var path = require('path'); var os = require('os'); @@ -7,16 +7,13 @@ function matchObject(item, str) { === '[object ' + str + ']'; } -function checkStat(name, fn) { - try { - return fn(name); - } catch (err) { - if (/^(ENOENT|EPERM|EACCES)$/.test(err.code)) { - if (err.code !== 'ENOENT') { - console.warn('Warning: Cannot access %s', name); - } - return false; +function checkStat(err) { + if (/^(ENOENT|EPERM|EACCES)$/.test(err.code)) { + if (err.code !== 'ENOENT') { + console.warn('Warning: Cannot access %s', name); } + return false; + } else { throw err; } } @@ -49,26 +46,28 @@ var is = { number: function(item) { return matchObject(item, 'Number'); }, - exists: function(name) { - return fs.existsSync(name); + exists: async function(name) { + return fs.access(name, fs.constants.F_OK) + .then(() => true) + .catch(() => false) }, - file: function(name) { - return checkStat(name, function(n) { - return fs.statSync(n).isFile() - }); + file: async function(name) { + return fs.stat(name) + .then((stats) => stats.isFile()) + .catch(checkStat) }, samePath: function(a, b) { return path.resolve(a) === path.resolve(b); }, - directory: function(name) { - return checkStat(name, function(n) { - return fs.statSync(n).isDirectory() - }); + directory: async function(name) { + return fs.stat(name) + .then((stats) => stats.isDirectory()) + .catch(checkStat) }, symbolicLink: function(name) { - return checkStat(name, function(n) { - return fs.lstatSync(n).isSymbolicLink(); - }); + return fs.stat(name) + .then((stats) => stats.isSymbolicLink()) + .catch(checkStat) }, windows: function() { return os.platform() === 'win32'; diff --git a/lib/watch.d.ts b/lib/watch.d.ts index 9eca5d4..848e53a 100644 --- a/lib/watch.d.ts +++ b/lib/watch.d.ts @@ -21,7 +21,7 @@ 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; -type EventType = 'update' | 'remove'; +export type EventType = 'update' | 'remove'; type Callback = (eventType: EventType, filePath: string) => any; type PathName = string | Array; type FilterReturn = boolean | symbol; @@ -60,7 +60,7 @@ type Options = { delay ?: number; }; -declare interface Watcher extends FSWatcher { +export interface Watcher extends FSWatcher { /** * Returns `true` if the watcher has been closed. */ diff --git a/lib/watch.js b/lib/watch.js index 95bfeb5..66959ab 100644 --- a/lib/watch.js +++ b/lib/watch.js @@ -1,4 +1,4 @@ -var fs = require('fs'); +var fs = require('fs-extra'); var path = require('path'); var util = require('util'); var events = require('events'); @@ -52,15 +52,17 @@ function guard(fn) { } } -function composeMessage(names) { - return names.map(function(n) { - return is.exists(n) +async function composeMessage(names) { + const ps = names.map((n) => is.exists(n)) + const exists = await Promise.all(ps) + return names.map((n, index) => { + return exists[index] ? [EVENT_UPDATE, n] : [EVENT_REMOVE, n]; - }); + }) } -function getMessages(cache) { +async function getMessages(cache) { var filtered = unique(cache); // Saving file from an editor? If so, assuming the @@ -76,9 +78,13 @@ function getMessages(cache) { return c.replace(reg, ''); })); if (dup) { - filtered = filtered.filter(function(m) { + var existing = filtered.map(function(m) { return is.exists(m); }); + await Promise.all(existing); + filtered = filtered.filter((v, index) => { + return existing[index] + }) } } @@ -92,16 +98,18 @@ function debounce(info, fn) { if (!is.number(delay)) { delay = 200; } - function handle() { - getMessages(cache).forEach(function(msg) { + async function handle() { + const cacheClone = [...cache] + cache = []; + timer = null; + const messages = await getMessages(cacheClone) + messages.forEach(function(msg) { msg[1] = Buffer.from(msg[1]); if (encoding !== 'buffer') { msg[1] = msg[1].toString(encoding); } fn.apply(null, msg); }); - timer = null; - cache = []; } return function(rawEvt, name) { cache.push(name); @@ -126,27 +134,24 @@ function createDupsFilter() { } } -function getSubDirectories(dir, fn, done = function() {}) { - if (is.directory(dir)) { - fs.readdir(dir, function(err, all) { - if (err) { - // 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); - if (is.directory(sdir)) fn(sdir); - }); - done(); +async function getSubDirectories(dir, fn) { + if (!(await is.directory(dir))) return; + try { + const all = await fs.readdir(dir); + const subDirs = all.map(async function(f) { + var sdir = path.join(dir, f); + if (await (is.directory(sdir))) { + fn(sdir); } }); - } else { - done(); + return Promise.allSettled(subDirs); + } catch(err) { + // don't throw permission errors. + if (/^(EPERM|EACCES)$/.test(err.code)) { + console.warn('Warning: Cannot access %s.', dir); + } else { + throw err; + } } } @@ -218,7 +223,8 @@ Watcher.prototype.close = function(fullPath) { } getSubDirectories(fullPath, function(fpath) { self.close(fpath); - }); + }) + .catch((err) => self.emit('error', err)) } else { Object.keys(self.watchers).forEach(function(fpath) { @@ -291,20 +297,23 @@ Watcher.prototype.add = function(watcher, info) { 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); - } - } + is.exists(name) + .then(async exists => { + if (!exists) { + // remove watcher on removal + self.close(fullPath); + } else { + // watch new created directory + var shouldWatch = !self.watchers[fullPath] + && await is.directory(name) + && shouldNotSkip(name, info.options.filter); + + if (shouldWatch) { + self.watchDirectory(name, info.options); + } + } + }) + .catch((err) => self.emit('error', err)) } }); } @@ -413,7 +422,9 @@ Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCoun if (shouldNotSkip(d, options.filter)) { self.watchDirectory(d, options, null, counter); } - }, counter()); + }) + .then(() => counter()) + .catch((err) => self.emit('error', err)) } done(); @@ -470,12 +481,16 @@ function watch(fpath, options, fn) { fpath = fpath.toString(); } - if (!is.array(fpath) && !is.exists(fpath)) { - process.nextTick(function() { - watcher.emit('error', - new Error(fpath + ' does not exist.') - ); - }); + if (!is.array(fpath)) { + is.exists(fpath).then((result) => { + if (!result) { + process.nextTick(function() { + watcher.emit('error', + new Error(fpath + ' does not exist.') + ); + }); + } + }) } if (is.string(options)) { @@ -513,17 +528,22 @@ function watch(fpath, options, fn) { })); } - if (is.file(fpath)) { - watcher.watchFile(fpath, options, fn); - emitReady(watcher); - } - - else if (is.directory(fpath)) { - var counter = semaphore(function () { - emitReady(watcher); - }); - watcher.watchDirectory(fpath, options, fn, counter); - } + is.file(fpath) + .then((isFile) => { + if (isFile) { + watcher.watchFile(fpath, options, fn); + emitReady(watcher); + } else { + return is.directory(fpath).then((isDirectory) => { + if (!isDirectory) return + var counter = semaphore(function () { + emitReady(watcher); + }); + watcher.watchDirectory(fpath, options, fn, counter); + }) + } + }) + .catch((err) => self.emit('error', err)) return watcher.expose(); } diff --git a/package.json b/package.json index 241dc81..4363875 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,10 @@ "engines": { "node": ">=6" }, + "dependencies": { + "fs-extra": "^10.1.0" + }, "devDependencies": { - "fs-extra": "^7.0.1", "mocha": "^5.2.0" } }