Comparing version 0.17.3 to 0.17.4
@@ -0,1 +1,5 @@ | ||
# v0.17.4 | ||
- New `defiler.get` feature: pass a filter function and retrieve all physical files whose paths match the filter (and make the transform/generator also re-run when physical files are created that match the filter) | ||
# v0.17.3 | ||
@@ -2,0 +6,0 @@ |
@@ -11,131 +11,101 @@ 'use strict'; | ||
const _path = Symbol(); | ||
const _dir = Symbol(); | ||
const _filename = Symbol(); | ||
const _ext = Symbol(); | ||
const _enc = Symbol(); | ||
const _bytes = Symbol(); | ||
const _text = Symbol(); | ||
class File { | ||
constructor() { | ||
// path of file | ||
this[_path] = null; | ||
// cached dir | ||
this[_dir] = null; | ||
// cached filename | ||
this[_filename] = null; | ||
// cached ext | ||
this[_ext] = null; | ||
// stats of file | ||
this.stats = null; | ||
// encoding | ||
this[_enc] = 'utf8'; | ||
// Buffer of file contents | ||
this[_bytes] = null; | ||
// string of file contents | ||
this[_text] = null; | ||
} | ||
get path() { | ||
return this[_path]; | ||
} | ||
set path(path$$1) { | ||
if (typeof path$$1 !== 'string') { | ||
throw new TypeError('file.path must be a string'); | ||
} | ||
if (this[_path] !== path$$1) { | ||
this[_path] = path$$1; | ||
this[_dir] = this[_filename] = this[_ext] = null; | ||
} | ||
} | ||
get dir() { | ||
if (this[_dir] == null) { | ||
const p = this[_path].lastIndexOf('/'); | ||
this[_dir] = p > -1 ? this[_path].slice(0, p) : ''; | ||
} | ||
return this[_dir]; | ||
} | ||
set dir(dir) { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('file.dir must be a string'); | ||
} | ||
this.path = (dir ? dir + '/' : '') + this.filename; | ||
} | ||
get filename() { | ||
if (this[_filename] == null) { | ||
const p = this[_path].lastIndexOf('/'); | ||
this[_filename] = p > -1 ? this[_path].slice(p + 1) : this[_path]; | ||
} | ||
return this[_filename]; | ||
} | ||
set filename(filename) { | ||
if (typeof filename !== 'string') { | ||
throw new TypeError('file.filename must be a string'); | ||
} | ||
const old = this.filename; | ||
this.path = | ||
(old ? this[_path].slice(0, -old.length) : this[_path]) + filename; | ||
} | ||
get ext() { | ||
if (this[_ext] == null) { | ||
const p1 = this[_path].lastIndexOf('.'); | ||
const p2 = this[_path].lastIndexOf('/'); | ||
this[_ext] = p1 > -1 && p1 > p2 ? this[_path].slice(p1) : ''; | ||
} | ||
return this[_ext]; | ||
} | ||
set ext(ext) { | ||
if (typeof ext !== 'string') { | ||
throw new TypeError('file.ext must be a string'); | ||
} | ||
const old = this.ext; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + ext; | ||
} | ||
get enc() { | ||
return this[_enc]; | ||
} | ||
set enc(enc) { | ||
if (!Buffer.isEncoding(enc)) { | ||
throw new TypeError('file.enc must be a supported encoding'); | ||
} | ||
this[_enc] = enc; | ||
} | ||
get bytes() { | ||
return this[_bytes] == null && this[_text] != null | ||
? (this[_bytes] = Buffer.from(this[_text], this[_enc])) | ||
: this[_bytes]; | ||
} | ||
set bytes(bytes) { | ||
if (bytes != null && !Buffer.isBuffer(bytes)) { | ||
throw new TypeError('file.bytes must be a Buffer or null'); | ||
} | ||
this[_bytes] = bytes; | ||
this[_text] = null; | ||
} | ||
get text() { | ||
return this[_text] == null && this[_bytes] != null | ||
? (this[_text] = this[_bytes].toString(this[_enc])) | ||
: this[_text]; | ||
} | ||
set text(text) { | ||
if (text != null && typeof text !== 'string') { | ||
throw new TypeError('file.text must be a string or null'); | ||
} | ||
this[_text] = text; | ||
this[_bytes] = null; | ||
} | ||
constructor() { | ||
this._path = null; | ||
this._dir = null; | ||
this._filename = null; | ||
this._ext = null; | ||
this.stats = null; | ||
this._enc = 'utf8'; | ||
this._bytes = null; | ||
this._text = null; | ||
} | ||
get path() { | ||
return this._path; | ||
} | ||
set path(path$$1) { | ||
if (typeof path$$1 !== 'string') { | ||
throw new TypeError('file.path must be a string'); | ||
} | ||
if (this._path !== path$$1) { | ||
this._path = path$$1; | ||
this._dir = this._filename = this._ext = null; | ||
} | ||
} | ||
get dir() { | ||
if (this._dir == null) { | ||
const p = this._path.lastIndexOf('/'); | ||
this._dir = p > -1 ? this._path.slice(0, p) : ''; | ||
} | ||
return this._dir; | ||
} | ||
set dir(dir) { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('file.dir must be a string'); | ||
} | ||
this.path = (dir ? dir + '/' : '') + this.filename; | ||
} | ||
get filename() { | ||
if (this._filename == null) { | ||
const p = this._path.lastIndexOf('/'); | ||
this._filename = p > -1 ? this._path.slice(p + 1) : this._path; | ||
} | ||
return this._filename; | ||
} | ||
set filename(filename) { | ||
if (typeof filename !== 'string') { | ||
throw new TypeError('file.filename must be a string'); | ||
} | ||
const old = this.filename; | ||
this.path = | ||
(old ? this._path.slice(0, -old.length) : this._path) + filename; | ||
} | ||
get ext() { | ||
if (this._ext == null) { | ||
const p1 = this._path.lastIndexOf('.'); | ||
const p2 = this._path.lastIndexOf('/'); | ||
this._ext = p1 > -1 && p1 > p2 ? this._path.slice(p1) : ''; | ||
} | ||
return this._ext; | ||
} | ||
set ext(ext) { | ||
if (typeof ext !== 'string') { | ||
throw new TypeError('file.ext must be a string'); | ||
} | ||
const old = this.ext; | ||
this.path = (old ? this._path.slice(0, -old.length) : this._path) + ext; | ||
} | ||
get enc() { | ||
return this._enc; | ||
} | ||
set enc(enc) { | ||
if (!Buffer.isEncoding(enc)) { | ||
throw new TypeError('file.enc must be a supported encoding'); | ||
} | ||
this._enc = enc; | ||
} | ||
get bytes() { | ||
return this._bytes == null && this._text != null | ||
? (this._bytes = Buffer.from(this._text, this._enc)) | ||
: this._bytes; | ||
} | ||
set bytes(bytes) { | ||
if (bytes != null && !Buffer.isBuffer(bytes)) { | ||
throw new TypeError('file.bytes must be a Buffer or null'); | ||
} | ||
this._bytes = bytes; | ||
this._text = null; | ||
} | ||
get text() { | ||
return this._text == null && this._bytes != null | ||
? (this._text = this._bytes.toString(this._enc)) | ||
: this._text; | ||
} | ||
set text(text) { | ||
if (text != null && typeof text !== 'string') { | ||
throw new TypeError('file.text must be a string or null'); | ||
} | ||
this._text = text; | ||
this._bytes = null; | ||
} | ||
} | ||
@@ -147,493 +117,385 @@ | ||
const _watchers = Symbol(); | ||
const _stats = Symbol(); | ||
const _timeouts = Symbol(); | ||
const _queue = Symbol(); | ||
const _isProcessing = Symbol(); | ||
const _recurse = Symbol(); | ||
const _handle = Symbol(); | ||
const _enqueue = Symbol(); | ||
class Watcher extends EventEmitter { | ||
constructor(data /* = { dir, filter, watch, debounce } */) { | ||
super(); | ||
Object.assign(this, data); | ||
// paths of all directories -> FSWatcher instances | ||
this[_watchers] = new Map(); | ||
// paths of all files -> file stats | ||
this[_stats] = new Map(); | ||
// paths of files with pending debounced events -> setTimeout timer ids | ||
this[_timeouts] = new Map(); | ||
// queue of pending FSWatcher events to handle | ||
this[_queue] = []; | ||
// whether some FSWatcher event is currently already in the process of being handled | ||
this[_isProcessing] = false; | ||
} | ||
// recurse directory, get stats, set up FSWatcher instances | ||
// returns array of { path, stats } | ||
async init() { | ||
await this[_recurse](this.dir); | ||
return [...this[_stats].entries()].map(([path$$1, stats]) => ({ | ||
path: path$$1, | ||
stats, | ||
})); | ||
} | ||
// recurse a given directory | ||
async [_recurse](full) { | ||
const path$$1 = full.slice(this.dir.length + 1); | ||
const stats = await stat(full); | ||
if (this.filter && !await this.filter({ path: path$$1, stats })) { | ||
return; | ||
} | ||
if (stats.isFile()) { | ||
this[_stats].set(path$$1, stats); | ||
} else if (stats.isDirectory()) { | ||
if (this.watch) { | ||
this[_watchers].set(path$$1, fs.watch(full, this[_handle].bind(this, full))); | ||
} | ||
await Promise.all( | ||
(await readdir(full)).map(sub => this[_recurse](full + '/' + sub)), | ||
); | ||
} | ||
} | ||
// handle FSWatcher event for given directory | ||
[_handle](dir, event, file) { | ||
const full = dir + '/' + file; | ||
if (this[_timeouts].has(full)) clearTimeout(this[_timeouts].get(full)); | ||
this[_timeouts].set( | ||
full, | ||
setTimeout(() => { | ||
this[_timeouts].delete(full); | ||
this[_enqueue](full); | ||
}, this.debounce), | ||
); | ||
} | ||
// add an FSWatcher event to the queue, and handle queued events | ||
async [_enqueue](full) { | ||
this[_queue].push(full); | ||
if (this[_isProcessing]) { | ||
return; | ||
} | ||
this[_isProcessing] = true; | ||
while (this[_queue].length) { | ||
const full = this[_queue].shift(); | ||
const path$$1 = full.slice(this.dir.length + 1); | ||
try { | ||
const stats = await stat(full); | ||
if (this.filter && !await this.filter({ path: path$$1, stats })) { | ||
continue; | ||
} | ||
if (stats.isFile()) { | ||
// note the new/changed file | ||
this[_stats].set(path$$1, stats); | ||
this.emit('', { event: '+', path: path$$1, stats }); | ||
} else if (stats.isDirectory() && !this[_watchers].has(path$$1)) { | ||
// note the new directory: start watching it, and report any files in it | ||
await this[_recurse](full); | ||
for (const [newPath, stats] of this[_stats].entries()) { | ||
if (newPath.startsWith(path$$1 + '/')) { | ||
this.emit('', { event: '+', path: newPath, stats }); | ||
} | ||
} | ||
} | ||
} catch (e) { | ||
// probably this was a deleted file/directory | ||
if (this[_stats].has(path$$1)) { | ||
// note the deleted file | ||
this[_stats].delete(path$$1); | ||
this.emit('', { event: '-', path: path$$1 }); | ||
} else if (this[_watchers].has(path$$1)) { | ||
// note the deleted directory: stop watching it, and report any files that were in it | ||
for (const old of this[_watchers].keys()) { | ||
if (old === path$$1 || old.startsWith(path$$1 + '/')) { | ||
this[_watchers].get(old).close(); | ||
this[_watchers].delete(old); | ||
} | ||
} | ||
for (const old of this[_stats].keys()) { | ||
if (old.startsWith(path$$1 + '/')) { | ||
this[_stats].delete(old); | ||
this.emit('', { event: '-', path: old }); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
this[_isProcessing] = false; | ||
} | ||
constructor(data) { | ||
super(); | ||
this._watchers = new Map(); | ||
this._stats = new Map(); | ||
this._timeouts = new Map(); | ||
this._queue = new Array(); | ||
this._isProcessing = false; | ||
Object.assign(this, data); | ||
} | ||
async init() { | ||
await this._recurse(this.dir); | ||
return [...this._stats.entries()].map(([path$$1, stats]) => ({ | ||
path: path$$1, | ||
stats, | ||
})); | ||
} | ||
async _recurse(full) { | ||
const path$$1 = full.slice(this.dir.length + 1); | ||
const stats = await stat(full); | ||
if (this.filter && !(await this.filter({ path: path$$1, stats }))) { | ||
return; | ||
} | ||
if (stats.isFile()) { | ||
this._stats.set(path$$1, stats); | ||
} | ||
else if (stats.isDirectory()) { | ||
if (this.watch) { | ||
this._watchers.set(path$$1, fs.watch(full, this._handle.bind(this, full))); | ||
} | ||
await Promise.all((await readdir(full)).map(sub => this._recurse(full + '/' + sub))); | ||
} | ||
} | ||
_handle(dir, event, file) { | ||
const full = dir + '/' + file; | ||
if (this._timeouts.has(full)) { | ||
clearTimeout(this._timeouts.get(full)); | ||
} | ||
this._timeouts.set(full, setTimeout(() => { | ||
this._timeouts.delete(full); | ||
this._enqueue(full); | ||
}, this.debounce)); | ||
} | ||
async _enqueue(full) { | ||
this._queue.push(full); | ||
if (this._isProcessing) { | ||
return; | ||
} | ||
this._isProcessing = true; | ||
while (this._queue.length) { | ||
const full = this._queue.shift(); | ||
const path$$1 = full.slice(this.dir.length + 1); | ||
try { | ||
const stats = await stat(full); | ||
if (this.filter && !(await this.filter({ path: path$$1, stats }))) { | ||
continue; | ||
} | ||
if (stats.isFile()) { | ||
this._stats.set(path$$1, stats); | ||
this.emit('', { event: '+', path: path$$1, stats }); | ||
} | ||
else if (stats.isDirectory() && !this._watchers.has(path$$1)) { | ||
await this._recurse(full); | ||
for (const [newPath, stats] of this._stats.entries()) { | ||
if (newPath.startsWith(path$$1 + '/')) { | ||
this.emit('', { event: '+', path: newPath, stats }); | ||
} | ||
} | ||
} | ||
} | ||
catch (e) { | ||
if (this._stats.has(path$$1)) { | ||
this._stats.delete(path$$1); | ||
this.emit('', { event: '-', path: path$$1 }); | ||
} | ||
else if (this._watchers.has(path$$1)) { | ||
for (const old of this._watchers.keys()) { | ||
if (old === path$$1 || old.startsWith(path$$1 + '/')) { | ||
this._watchers.get(old).close(); | ||
this._watchers.delete(old); | ||
} | ||
} | ||
for (const old of this._stats.keys()) { | ||
if (old.startsWith(path$$1 + '/')) { | ||
this._stats.delete(old); | ||
this.emit('', { event: '-', path: old }); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
this._isProcessing = false; | ||
} | ||
} | ||
const contexts = new Map(); | ||
async_hooks.createHook({ | ||
init: (id, _, trigger) => contexts.set(id, contexts.get(trigger)), | ||
destroy: id => contexts.delete(id), | ||
init: (id, _, trigger) => contexts.set(id, contexts.get(trigger)), | ||
destroy: id => contexts.delete(id), | ||
}).enable(); | ||
const create = data => contexts.set(async_hooks.executionAsyncId(), data); | ||
const create = (data) => { | ||
contexts.set(async_hooks.executionAsyncId(), data); | ||
}; | ||
const current = () => contexts.get(async_hooks.executionAsyncId()); | ||
const _origData = Symbol(); | ||
const _status = Symbol(); | ||
const _before = Symbol(); | ||
const _during = Symbol(); | ||
const _after = Symbol(); | ||
const _watchers$1 = Symbol(); | ||
const _transform = Symbol(); | ||
const _generators = Symbol(); | ||
const _resolver = Symbol(); | ||
const _onerror = Symbol(); | ||
const _active = Symbol(); | ||
const _waitingFor = Symbol(); | ||
const _whenFound = Symbol(); | ||
const _deps = Symbol(); | ||
const _queue$1 = Symbol(); | ||
const _isProcessing$1 = Symbol(); | ||
const _startWave = Symbol(); | ||
const _endWave = Symbol(); | ||
const _enqueue$1 = Symbol(); | ||
const _processPhysicalFile = Symbol(); | ||
const _processFile = Symbol(); | ||
const _callTransform = Symbol(); | ||
const _processGenerator = Symbol(); | ||
const _checkWave = Symbol(); | ||
const _processDependents = Symbol(); | ||
const _markFound = Symbol(); | ||
class Defiler { | ||
constructor(...args) { | ||
const { transform, generators = [], resolver, onerror } = args.pop(); | ||
if (typeof transform !== 'function') { | ||
throw new TypeError('defiler: transform must be a function'); | ||
} | ||
if ( | ||
!Array.isArray(generators) || | ||
generators.some(generator => typeof generator !== 'function') | ||
) { | ||
throw new TypeError('defiler: generators must be an array of functions'); | ||
} | ||
if (resolver && typeof resolver !== 'function') { | ||
throw new TypeError('defiler: resolver must be a function'); | ||
} | ||
if (onerror && typeof onerror !== 'function') { | ||
throw new TypeError('defiler: onerror must be a function'); | ||
} | ||
// set of original paths for all physical files | ||
this.paths = new Set(); | ||
// original paths -> original file data for all physical files ({ path, stats, bytes, enc }) | ||
this[_origData] = new Map(); | ||
// original paths -> transformed files for all physical and virtual files | ||
this.files = new Map(); | ||
// _before, _during, or _after exec has been called | ||
this[_status] = _before; | ||
// Watcher instances | ||
this[_watchers$1] = args.map( | ||
({ | ||
dir, | ||
filter, | ||
read = true, | ||
enc = 'utf8', | ||
pre, | ||
watch = true, | ||
debounce = 10, | ||
}) => { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('defiler: dir must be a string'); | ||
} | ||
if (filter && typeof filter !== 'function') { | ||
throw new TypeError('defiler: filter must be a function'); | ||
} | ||
if (typeof read !== 'boolean' && typeof read !== 'function') { | ||
throw new TypeError('defiler: read must be a boolean or a function'); | ||
} | ||
if (!Buffer.isEncoding(enc) && typeof enc !== 'function') { | ||
throw new TypeError( | ||
'defiler: enc must be a supported encoding or a function', | ||
); | ||
} | ||
if (pre && typeof pre !== 'function') { | ||
throw new TypeError('defiler: pre must be a function'); | ||
} | ||
if (typeof watch !== 'boolean') { | ||
throw new TypeError('defiler: watch must be a boolean'); | ||
} | ||
if (typeof debounce !== 'number') { | ||
throw new TypeError('defiler: debounce must be a number'); | ||
} | ||
dir = path.resolve(dir); | ||
return new Watcher({ dir, filter, read, enc, pre, watch, debounce }); | ||
}, | ||
); | ||
// the transform to run on all files | ||
this[_transform] = transform; | ||
// unique symbols -> registered generators | ||
this[_generators] = new Map( | ||
generators.map(generator => [Symbol(), generator]), | ||
); | ||
// (base, path) => path resolver function, used in defiler.get and defiler.add from transform | ||
this[_resolver] = resolver; | ||
// handler to call when errors occur | ||
this[_onerror] = onerror; | ||
// original paths of all files currently undergoing transformation and symbols of all generators currently running | ||
this[_active] = new Set(); | ||
// original paths -> number of other files they're currently waiting on to exist | ||
this[_waitingFor] = new Map(); | ||
// original paths -> { promise, resolve, paths } objects for when awaited files become available | ||
this[_whenFound] = new Map(); | ||
// array of [dependent, dependency] pairs, specifying changes to which files should trigger re-processing which other files | ||
this[_deps] = []; | ||
// queue of pending Watcher events to handle | ||
this[_queue$1] = []; | ||
// whether some Watcher event is currently already in the process of being handled | ||
this[_isProcessing$1] = false; | ||
} | ||
// execute everything, and return a promise that resolves when the first wave of processing is complete | ||
async exec() { | ||
if (this[_status] !== _before) { | ||
throw new Error('defiler.exec: cannot call more than once'); | ||
} | ||
this[_status] = _during; | ||
this[_isProcessing$1] = true; | ||
const done = this[_startWave](); | ||
// init the Watcher instances | ||
const files = []; | ||
await Promise.all( | ||
this[_watchers$1].map(async watcher => { | ||
watcher.on('', event => this[_enqueue$1](watcher, event)); | ||
// note that all files are pending transformation | ||
await Promise.all( | ||
(await watcher.init()).map(async file => { | ||
const { path: path$$1 } = file; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
this.paths.add(file.path); | ||
this[_active].add(file.path); | ||
files.push([watcher, path$$1, file]); | ||
}), | ||
); | ||
}), | ||
); | ||
for (const symbol of this[_generators].keys()) { | ||
this[_active].add(symbol); | ||
} | ||
// process each physical file | ||
for (const [watcher, path$$1, file] of files) { | ||
this[_processPhysicalFile](watcher, path$$1, file); | ||
} | ||
// process each generator | ||
for (const symbol of this[_generators].keys()) { | ||
this[_processGenerator](symbol); | ||
} | ||
// wait and finish up | ||
await done; | ||
this[_status] = _after; | ||
this[_isProcessing$1] = false; | ||
this[_enqueue$1](); | ||
} | ||
// wait for a file to be available and retrieve it, marking dependencies as appropriate | ||
async get(path$$1) { | ||
if (Array.isArray(path$$1)) { | ||
return Promise.all(path$$1.map(path$$1 => this.get(path$$1))); | ||
} | ||
const current$$1 = current(); | ||
path$$1 = this.resolve(path$$1); | ||
if (typeof path$$1 !== 'string') { | ||
throw new TypeError('defiler.get: path must be a string'); | ||
} | ||
if (current$$1) { | ||
this[_deps].push([current$$1, path$$1]); | ||
} | ||
if (this[_status] === _during && !this.files.has(path$$1) && current$$1) { | ||
this[_waitingFor].set(current$$1, (this[_waitingFor].get(current$$1) || 0) + 1); | ||
if (this[_whenFound].has(path$$1)) { | ||
const { promise, paths } = this[_whenFound].get(path$$1); | ||
paths.push(current$$1); | ||
await promise; | ||
} else { | ||
let resolve; | ||
const promise = new Promise(res => (resolve = res)); | ||
this[_whenFound].set(path$$1, { promise, resolve, paths: [current$$1] }); | ||
await promise; | ||
} | ||
} | ||
return this.files.get(path$$1); | ||
} | ||
// add a new virtual file | ||
add(file) { | ||
if (this[_status] === _before) { | ||
throw new Error('defiler.add: cannot call before calling exec'); | ||
} | ||
if (typeof file !== 'object') { | ||
throw new TypeError('defiler.add: file must be an object'); | ||
} | ||
file.path = this.resolve(file.path); | ||
this[_origData].set(file.path, file); | ||
this[_processFile](file, 'add'); | ||
} | ||
// resolve a given path from the file currently being transformed | ||
resolve(path$$1) { | ||
return this[_resolver] && typeof current() === 'string' | ||
? this[_resolver](current(), path$$1) | ||
: path$$1; | ||
} | ||
// private methods | ||
// return a Promise that we will resolve at the end of this wave, and save its resolver | ||
[_startWave]() { | ||
return new Promise(res => (this[_endWave] = res)); | ||
} | ||
// add a Watcher event to the queue, and handle queued events | ||
async [_enqueue$1](watcher, event) { | ||
if (event) { | ||
this[_queue$1].push([watcher, event]); | ||
} | ||
if (this[_isProcessing$1]) { | ||
return; | ||
} | ||
this[_isProcessing$1] = true; | ||
while (this[_queue$1].length) { | ||
const done = this[_startWave](); | ||
const [watcher, { event, path: path$$1, stats }] = this[_queue$1].shift(); | ||
const file = { path: path$$1, stats }; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
if (event === '+') { | ||
this[_processPhysicalFile](watcher, path$$1, file); | ||
} else if (event === '-') { | ||
const { path: path$$1 } = file; | ||
const oldFile = this.files.get(path$$1); | ||
this.paths.delete(path$$1); | ||
this[_origData].delete(path$$1); | ||
this.files.delete(path$$1); | ||
await this[_callTransform](oldFile, 'delete'); | ||
this[_processDependents](path$$1); | ||
} | ||
await done; | ||
} | ||
this[_isProcessing$1] = false; | ||
} | ||
// create a file object for a physical file and process it | ||
async [_processPhysicalFile]({ dir, read, enc }, path$$1, file) { | ||
if (typeof read === 'function') { | ||
read = await read({ path: path$$1, stats: file.stats }); | ||
} | ||
if (read) { | ||
file.bytes = await readFile(dir + '/' + path$$1); | ||
} | ||
if (typeof enc === 'function') { | ||
enc = await enc({ path: path$$1, stats: file.stats, bytes: file.bytes }); | ||
} | ||
file.enc = enc; | ||
this.paths.add(file.path); | ||
this[_origData].set(file.path, file); | ||
await this[_processFile](file, 'read'); | ||
} | ||
// transform a file, store it, and process dependents | ||
async [_processFile](data, event) { | ||
const file = Object.assign(new File(), data); | ||
const { path: path$$1 } = file; | ||
this[_active].add(path$$1); | ||
await this[_callTransform](file, event); | ||
this.files.set(path$$1, file); | ||
this[this[_status] === _during ? _markFound : _processDependents](path$$1); | ||
this[_active].delete(path$$1); | ||
this[_checkWave](); | ||
} | ||
// call the transform on a file with the given event string, and handle errors | ||
async [_callTransform](file, event) { | ||
await null; | ||
create(file.path); | ||
try { | ||
await this[_transform]({ file, event }); | ||
} catch (error) { | ||
if (this[_onerror]) { | ||
this[_onerror]({ file, event, error }); | ||
} | ||
} | ||
} | ||
// run the generator given by the symbol | ||
async [_processGenerator](symbol) { | ||
this[_active].add(symbol); | ||
const generator = this[_generators].get(symbol); | ||
await null; | ||
create(symbol); | ||
try { | ||
await generator(); | ||
} catch (error) { | ||
if (this[_onerror]) { | ||
this[_onerror]({ generator, error }); | ||
} | ||
} | ||
this[_active].delete(symbol); | ||
this[_checkWave](); | ||
} | ||
// re-process all files that depend on a particular path | ||
[_processDependents](path$$1) { | ||
const dependents = new Set(); | ||
for (const [dependent, dependency] of this[_deps]) { | ||
if (dependency === path$$1) { | ||
dependents.add(dependent); | ||
} | ||
} | ||
this[_deps] = this[_deps].filter( | ||
([dependent]) => !dependents.has(dependent), | ||
); | ||
for (const dependent of dependents) { | ||
if (this[_origData].has(dependent)) { | ||
this[_processFile](this[_origData].get(dependent), 'retransform'); | ||
} else if (this[_generators].has(dependent)) { | ||
this[_processGenerator](dependent); | ||
} | ||
} | ||
this[_checkWave](); | ||
} | ||
// check whether this wave is complete, and, if not, whether we need to break a deadlock | ||
[_checkWave]() { | ||
if (!this[_active].size) { | ||
this[_endWave](); | ||
} else if ( | ||
this[_status] === _during && | ||
[...this[_active]].every(path$$1 => this[_waitingFor].get(path$$1)) | ||
) { | ||
// all pending files are currently waiting for one or more other files to exist | ||
// break deadlock: assume all files that have not appeared yet will never do so | ||
for (const path$$1 of this[_whenFound].keys()) { | ||
if (!this[_active].has(path$$1)) { | ||
this[_markFound](path$$1); | ||
} | ||
} | ||
} | ||
} | ||
// mark a given awaited file as being found | ||
[_markFound](path$$1) { | ||
if (this[_whenFound].has(path$$1)) { | ||
const { resolve, paths } = this[_whenFound].get(path$$1); | ||
for (const path$$1 of paths) { | ||
this[_waitingFor].set(path$$1, this[_waitingFor].get(path$$1) - 1); | ||
} | ||
resolve(); | ||
this[_whenFound].delete(path$$1); | ||
} | ||
} | ||
constructor(...args) { | ||
this.paths = new Set(); | ||
this._origData = new Map(); | ||
this.files = new Map(); | ||
this._status = Status.Before; | ||
this._active = new Set(); | ||
this._waitingFor = new Map(); | ||
this._whenFound = new Map(); | ||
this._deps = new Array(); | ||
this._queue = new Array(); | ||
this._isProcessing = false; | ||
this._endWave = null; | ||
const { transform, generators = [], resolver, onerror } = (args.pop()); | ||
if (typeof transform !== 'function') { | ||
throw new TypeError('defiler: transform must be a function'); | ||
} | ||
if (!Array.isArray(generators) || | ||
generators.some(generator => typeof generator !== 'function')) { | ||
throw new TypeError('defiler: generators must be an array of functions'); | ||
} | ||
if (resolver && typeof resolver !== 'function') { | ||
throw new TypeError('defiler: resolver must be a function'); | ||
} | ||
if (onerror && typeof onerror !== 'function') { | ||
throw new TypeError('defiler: onerror must be a function'); | ||
} | ||
this._watchers = args.map(({ dir, filter, read = true, enc = 'utf8', pre, watch = true, debounce = 10, }) => { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('defiler: dir must be a string'); | ||
} | ||
if (filter && typeof filter !== 'function') { | ||
throw new TypeError('defiler: filter must be a function'); | ||
} | ||
if (typeof read !== 'boolean' && typeof read !== 'function') { | ||
throw new TypeError('defiler: read must be a boolean or a function'); | ||
} | ||
if (!Buffer.isEncoding(enc) && typeof enc !== 'function') { | ||
throw new TypeError('defiler: enc must be a supported encoding or a function'); | ||
} | ||
if (pre && typeof pre !== 'function') { | ||
throw new TypeError('defiler: pre must be a function'); | ||
} | ||
if (typeof watch !== 'boolean') { | ||
throw new TypeError('defiler: watch must be a boolean'); | ||
} | ||
if (typeof debounce !== 'number') { | ||
throw new TypeError('defiler: debounce must be a number'); | ||
} | ||
dir = path.resolve(dir); | ||
return (new Watcher({ dir, filter, read, enc, pre, watch, debounce })); | ||
}); | ||
this._transform = transform; | ||
this._generators = new Map(generators.map(generator => [Symbol(), generator])); | ||
this._resolver = resolver; | ||
this._onerror = onerror; | ||
} | ||
async exec() { | ||
if (this._status !== Status.Before) { | ||
throw new Error('defiler.exec: cannot call more than once'); | ||
} | ||
this._status = Status.During; | ||
this._isProcessing = true; | ||
const done = this._startWave(); | ||
const files = new Array(); | ||
await Promise.all(this._watchers.map(async (watcher) => { | ||
watcher.on('', event => this._enqueue(watcher, event)); | ||
await Promise.all((await watcher.init()).map(async (file) => { | ||
const { path: path$$1 } = file; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
this.paths.add(file.path); | ||
this._active.add(file.path); | ||
files.push([watcher, path$$1, file]); | ||
})); | ||
})); | ||
for (const symbol of this._generators.keys()) { | ||
this._active.add(symbol); | ||
} | ||
for (const [watcher, path$$1, file] of files) { | ||
this._processPhysicalFile(watcher, path$$1, file); | ||
} | ||
for (const symbol of this._generators.keys()) { | ||
this._processGenerator(symbol); | ||
} | ||
await done; | ||
this._status = Status.After; | ||
this._isProcessing = false; | ||
this._enqueue(); | ||
} | ||
async get(_) { | ||
if (Array.isArray(_)) { | ||
return Promise.all(_.map(path$$1 => this.get(path$$1))); | ||
} | ||
if (typeof _ !== 'string' && typeof _ !== 'function') { | ||
throw new TypeError('defiler.get: argument must be a string, an array, or a function'); | ||
} | ||
const current$$1 = current(); | ||
if (current$$1) { | ||
this._deps.push([current$$1, _]); | ||
} | ||
if (typeof _ === 'function') { | ||
return this.get([...this.paths].filter(_).sort()); | ||
} | ||
const path$$1 = this.resolve(_); | ||
if (this._status === Status.During && !this.files.has(path$$1) && current$$1) { | ||
this._waitingFor.set(current$$1, (this._waitingFor.get(current$$1) || 0) + 1); | ||
if (this._whenFound.has(path$$1)) { | ||
const { promise, paths } = this._whenFound.get(path$$1); | ||
paths.push(current$$1); | ||
await promise; | ||
} | ||
else { | ||
let resolve; | ||
const promise = new Promise(res => (resolve = res)); | ||
this._whenFound.set(path$$1, { promise, resolve, paths: [current$$1] }); | ||
await promise; | ||
} | ||
} | ||
return this.files.get(path$$1); | ||
} | ||
add(file) { | ||
if (this._status === Status.Before) { | ||
throw new Error('defiler.add: cannot call before calling exec'); | ||
} | ||
if (typeof file !== 'object') { | ||
throw new TypeError('defiler.add: file must be an object'); | ||
} | ||
file.path = this.resolve(file.path); | ||
this._origData.set(file.path, file); | ||
this._processFile(file, 'add'); | ||
} | ||
resolve(path$$1) { | ||
return this._resolver && typeof current() === 'string' | ||
? this._resolver(current(), path$$1) | ||
: path$$1; | ||
} | ||
_startWave() { | ||
return new Promise(res => (this._endWave = res)); | ||
} | ||
async _enqueue(watcher, event) { | ||
if (event) { | ||
this._queue.push([watcher, event]); | ||
} | ||
if (this._isProcessing) { | ||
return; | ||
} | ||
this._isProcessing = true; | ||
while (this._queue.length) { | ||
const done = this._startWave(); | ||
const [watcher, { event, path: path$$1, stats }] = this._queue.shift(); | ||
const file = { path: path$$1, stats }; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
if (event === '+') { | ||
this._processPhysicalFile(watcher, path$$1, file); | ||
} | ||
else if (event === '-') { | ||
const { path: path$$1 } = file; | ||
const oldFile = this.files.get(path$$1); | ||
this.paths.delete(path$$1); | ||
this._origData.delete(path$$1); | ||
this.files.delete(path$$1); | ||
await this._callTransform(oldFile, 'delete'); | ||
this._processDependents(path$$1); | ||
} | ||
await done; | ||
} | ||
this._isProcessing = false; | ||
} | ||
async _processPhysicalFile({ dir, read, enc }, path$$1, file) { | ||
if (typeof read === 'function') { | ||
read = await read({ path: path$$1, stats: file.stats }); | ||
} | ||
if (read) { | ||
file.bytes = await readFile(dir + '/' + path$$1); | ||
} | ||
if (typeof enc === 'function') { | ||
enc = await enc({ path: path$$1, stats: file.stats, bytes: file.bytes }); | ||
} | ||
file.enc = enc; | ||
this.paths.add(file.path); | ||
this._origData.set(file.path, file); | ||
await this._processFile(file, 'read'); | ||
} | ||
async _processFile(data, event) { | ||
const file = Object.assign(new File(), data); | ||
const { path: path$$1 } = file; | ||
this._active.add(path$$1); | ||
await this._callTransform(file, event); | ||
this.files.set(path$$1, file); | ||
if (this._status === Status.During) { | ||
this._markFound(path$$1); | ||
} | ||
else { | ||
this._processDependents(path$$1); | ||
} | ||
this._active.delete(path$$1); | ||
this._checkWave(); | ||
} | ||
async _callTransform(file, event) { | ||
await null; | ||
create(file.path); | ||
try { | ||
await this._transform({ file, event }); | ||
} | ||
catch (error) { | ||
if (this._onerror) { | ||
this._onerror({ file, event, error }); | ||
} | ||
} | ||
} | ||
async _processGenerator(symbol) { | ||
this._active.add(symbol); | ||
const generator = this._generators.get(symbol); | ||
await null; | ||
create(symbol); | ||
try { | ||
await generator(); | ||
} | ||
catch (error) { | ||
if (this._onerror) { | ||
this._onerror({ generator, error }); | ||
} | ||
} | ||
this._active.delete(symbol); | ||
this._checkWave(); | ||
} | ||
_processDependents(path$$1) { | ||
const dependents = new Set(); | ||
for (const [dependent, dependency] of this._deps) { | ||
if (typeof dependency === 'string' ? dependency === path$$1 : dependency(path$$1)) { | ||
dependents.add(dependent); | ||
} | ||
} | ||
this._deps = this._deps.filter(([dependent]) => !dependents.has(dependent)); | ||
for (const dependent of dependents) { | ||
if (this._origData.has(dependent)) { | ||
this._processFile(this._origData.get(dependent), 'retransform'); | ||
} | ||
else if (this._generators.has(dependent)) { | ||
this._processGenerator(dependent); | ||
} | ||
} | ||
this._checkWave(); | ||
} | ||
_checkWave() { | ||
if (!this._active.size) { | ||
this._endWave(); | ||
} | ||
else if (this._status === Status.During && | ||
[...this._active].every(path$$1 => !!this._waitingFor.get(path$$1))) { | ||
for (const path$$1 of this._whenFound.keys()) { | ||
if (!this._active.has(path$$1)) { | ||
this._markFound(path$$1); | ||
} | ||
} | ||
} | ||
} | ||
_markFound(path$$1) { | ||
if (this._whenFound.has(path$$1)) { | ||
const { resolve, paths } = this._whenFound.get(path$$1); | ||
for (const path$$1 of paths) { | ||
this._waitingFor.set(path$$1, this._waitingFor.get(path$$1) - 1); | ||
} | ||
resolve(); | ||
this._whenFound.delete(path$$1); | ||
} | ||
} | ||
} | ||
var Status; | ||
(function (Status) { | ||
Status[Status["Before"] = 0] = "Before"; | ||
Status[Status["During"] = 1] = "During"; | ||
Status[Status["After"] = 2] = "After"; | ||
})(Status || (Status = {})); | ||
@@ -640,0 +502,0 @@ exports.File = File; |
1082
dist/index.es.js
import { readdir, readFile, stat, watch } from 'fs'; | ||
import { promisify } from 'util'; | ||
import EventEmitter from 'events'; | ||
import * as EventEmitter from 'events'; | ||
import { createHook, executionAsyncId } from 'async_hooks'; | ||
import { resolve } from 'path'; | ||
const _path = Symbol(); | ||
const _dir = Symbol(); | ||
const _filename = Symbol(); | ||
const _ext = Symbol(); | ||
const _enc = Symbol(); | ||
const _bytes = Symbol(); | ||
const _text = Symbol(); | ||
class File { | ||
constructor() { | ||
// path of file | ||
this[_path] = null; | ||
// cached dir | ||
this[_dir] = null; | ||
// cached filename | ||
this[_filename] = null; | ||
// cached ext | ||
this[_ext] = null; | ||
// stats of file | ||
this.stats = null; | ||
// encoding | ||
this[_enc] = 'utf8'; | ||
// Buffer of file contents | ||
this[_bytes] = null; | ||
// string of file contents | ||
this[_text] = null; | ||
} | ||
get path() { | ||
return this[_path]; | ||
} | ||
set path(path) { | ||
if (typeof path !== 'string') { | ||
throw new TypeError('file.path must be a string'); | ||
} | ||
if (this[_path] !== path) { | ||
this[_path] = path; | ||
this[_dir] = this[_filename] = this[_ext] = null; | ||
} | ||
} | ||
get dir() { | ||
if (this[_dir] == null) { | ||
const p = this[_path].lastIndexOf('/'); | ||
this[_dir] = p > -1 ? this[_path].slice(0, p) : ''; | ||
} | ||
return this[_dir]; | ||
} | ||
set dir(dir) { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('file.dir must be a string'); | ||
} | ||
this.path = (dir ? dir + '/' : '') + this.filename; | ||
} | ||
get filename() { | ||
if (this[_filename] == null) { | ||
const p = this[_path].lastIndexOf('/'); | ||
this[_filename] = p > -1 ? this[_path].slice(p + 1) : this[_path]; | ||
} | ||
return this[_filename]; | ||
} | ||
set filename(filename) { | ||
if (typeof filename !== 'string') { | ||
throw new TypeError('file.filename must be a string'); | ||
} | ||
const old = this.filename; | ||
this.path = | ||
(old ? this[_path].slice(0, -old.length) : this[_path]) + filename; | ||
} | ||
get ext() { | ||
if (this[_ext] == null) { | ||
const p1 = this[_path].lastIndexOf('.'); | ||
const p2 = this[_path].lastIndexOf('/'); | ||
this[_ext] = p1 > -1 && p1 > p2 ? this[_path].slice(p1) : ''; | ||
} | ||
return this[_ext]; | ||
} | ||
set ext(ext) { | ||
if (typeof ext !== 'string') { | ||
throw new TypeError('file.ext must be a string'); | ||
} | ||
const old = this.ext; | ||
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + ext; | ||
} | ||
get enc() { | ||
return this[_enc]; | ||
} | ||
set enc(enc) { | ||
if (!Buffer.isEncoding(enc)) { | ||
throw new TypeError('file.enc must be a supported encoding'); | ||
} | ||
this[_enc] = enc; | ||
} | ||
get bytes() { | ||
return this[_bytes] == null && this[_text] != null | ||
? (this[_bytes] = Buffer.from(this[_text], this[_enc])) | ||
: this[_bytes]; | ||
} | ||
set bytes(bytes) { | ||
if (bytes != null && !Buffer.isBuffer(bytes)) { | ||
throw new TypeError('file.bytes must be a Buffer or null'); | ||
} | ||
this[_bytes] = bytes; | ||
this[_text] = null; | ||
} | ||
get text() { | ||
return this[_text] == null && this[_bytes] != null | ||
? (this[_text] = this[_bytes].toString(this[_enc])) | ||
: this[_text]; | ||
} | ||
set text(text) { | ||
if (text != null && typeof text !== 'string') { | ||
throw new TypeError('file.text must be a string or null'); | ||
} | ||
this[_text] = text; | ||
this[_bytes] = null; | ||
} | ||
constructor() { | ||
this._path = null; | ||
this._dir = null; | ||
this._filename = null; | ||
this._ext = null; | ||
this.stats = null; | ||
this._enc = 'utf8'; | ||
this._bytes = null; | ||
this._text = null; | ||
} | ||
get path() { | ||
return this._path; | ||
} | ||
set path(path) { | ||
if (typeof path !== 'string') { | ||
throw new TypeError('file.path must be a string'); | ||
} | ||
if (this._path !== path) { | ||
this._path = path; | ||
this._dir = this._filename = this._ext = null; | ||
} | ||
} | ||
get dir() { | ||
if (this._dir == null) { | ||
const p = this._path.lastIndexOf('/'); | ||
this._dir = p > -1 ? this._path.slice(0, p) : ''; | ||
} | ||
return this._dir; | ||
} | ||
set dir(dir) { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('file.dir must be a string'); | ||
} | ||
this.path = (dir ? dir + '/' : '') + this.filename; | ||
} | ||
get filename() { | ||
if (this._filename == null) { | ||
const p = this._path.lastIndexOf('/'); | ||
this._filename = p > -1 ? this._path.slice(p + 1) : this._path; | ||
} | ||
return this._filename; | ||
} | ||
set filename(filename) { | ||
if (typeof filename !== 'string') { | ||
throw new TypeError('file.filename must be a string'); | ||
} | ||
const old = this.filename; | ||
this.path = | ||
(old ? this._path.slice(0, -old.length) : this._path) + filename; | ||
} | ||
get ext() { | ||
if (this._ext == null) { | ||
const p1 = this._path.lastIndexOf('.'); | ||
const p2 = this._path.lastIndexOf('/'); | ||
this._ext = p1 > -1 && p1 > p2 ? this._path.slice(p1) : ''; | ||
} | ||
return this._ext; | ||
} | ||
set ext(ext) { | ||
if (typeof ext !== 'string') { | ||
throw new TypeError('file.ext must be a string'); | ||
} | ||
const old = this.ext; | ||
this.path = (old ? this._path.slice(0, -old.length) : this._path) + ext; | ||
} | ||
get enc() { | ||
return this._enc; | ||
} | ||
set enc(enc) { | ||
if (!Buffer.isEncoding(enc)) { | ||
throw new TypeError('file.enc must be a supported encoding'); | ||
} | ||
this._enc = enc; | ||
} | ||
get bytes() { | ||
return this._bytes == null && this._text != null | ||
? (this._bytes = Buffer.from(this._text, this._enc)) | ||
: this._bytes; | ||
} | ||
set bytes(bytes) { | ||
if (bytes != null && !Buffer.isBuffer(bytes)) { | ||
throw new TypeError('file.bytes must be a Buffer or null'); | ||
} | ||
this._bytes = bytes; | ||
this._text = null; | ||
} | ||
get text() { | ||
return this._text == null && this._bytes != null | ||
? (this._text = this._bytes.toString(this._enc)) | ||
: this._text; | ||
} | ||
set text(text) { | ||
if (text != null && typeof text !== 'string') { | ||
throw new TypeError('file.text must be a string or null'); | ||
} | ||
this._text = text; | ||
this._bytes = null; | ||
} | ||
} | ||
@@ -142,495 +112,387 @@ | ||
const _watchers = Symbol(); | ||
const _stats = Symbol(); | ||
const _timeouts = Symbol(); | ||
const _queue = Symbol(); | ||
const _isProcessing = Symbol(); | ||
const _recurse = Symbol(); | ||
const _handle = Symbol(); | ||
const _enqueue = Symbol(); | ||
class Watcher extends EventEmitter { | ||
constructor(data /* = { dir, filter, watch, debounce } */) { | ||
super(); | ||
Object.assign(this, data); | ||
// paths of all directories -> FSWatcher instances | ||
this[_watchers] = new Map(); | ||
// paths of all files -> file stats | ||
this[_stats] = new Map(); | ||
// paths of files with pending debounced events -> setTimeout timer ids | ||
this[_timeouts] = new Map(); | ||
// queue of pending FSWatcher events to handle | ||
this[_queue] = []; | ||
// whether some FSWatcher event is currently already in the process of being handled | ||
this[_isProcessing] = false; | ||
} | ||
// recurse directory, get stats, set up FSWatcher instances | ||
// returns array of { path, stats } | ||
async init() { | ||
await this[_recurse](this.dir); | ||
return [...this[_stats].entries()].map(([path, stats]) => ({ | ||
path, | ||
stats, | ||
})); | ||
} | ||
// recurse a given directory | ||
async [_recurse](full) { | ||
const path = full.slice(this.dir.length + 1); | ||
const stats = await stat$1(full); | ||
if (this.filter && !await this.filter({ path, stats })) { | ||
return; | ||
} | ||
if (stats.isFile()) { | ||
this[_stats].set(path, stats); | ||
} else if (stats.isDirectory()) { | ||
if (this.watch) { | ||
this[_watchers].set(path, watch(full, this[_handle].bind(this, full))); | ||
} | ||
await Promise.all( | ||
(await readdir$1(full)).map(sub => this[_recurse](full + '/' + sub)), | ||
); | ||
} | ||
} | ||
// handle FSWatcher event for given directory | ||
[_handle](dir, event, file) { | ||
const full = dir + '/' + file; | ||
if (this[_timeouts].has(full)) clearTimeout(this[_timeouts].get(full)); | ||
this[_timeouts].set( | ||
full, | ||
setTimeout(() => { | ||
this[_timeouts].delete(full); | ||
this[_enqueue](full); | ||
}, this.debounce), | ||
); | ||
} | ||
// add an FSWatcher event to the queue, and handle queued events | ||
async [_enqueue](full) { | ||
this[_queue].push(full); | ||
if (this[_isProcessing]) { | ||
return; | ||
} | ||
this[_isProcessing] = true; | ||
while (this[_queue].length) { | ||
const full = this[_queue].shift(); | ||
const path = full.slice(this.dir.length + 1); | ||
try { | ||
const stats = await stat$1(full); | ||
if (this.filter && !await this.filter({ path, stats })) { | ||
continue; | ||
} | ||
if (stats.isFile()) { | ||
// note the new/changed file | ||
this[_stats].set(path, stats); | ||
this.emit('', { event: '+', path, stats }); | ||
} else if (stats.isDirectory() && !this[_watchers].has(path)) { | ||
// note the new directory: start watching it, and report any files in it | ||
await this[_recurse](full); | ||
for (const [newPath, stats] of this[_stats].entries()) { | ||
if (newPath.startsWith(path + '/')) { | ||
this.emit('', { event: '+', path: newPath, stats }); | ||
} | ||
} | ||
} | ||
} catch (e) { | ||
// probably this was a deleted file/directory | ||
if (this[_stats].has(path)) { | ||
// note the deleted file | ||
this[_stats].delete(path); | ||
this.emit('', { event: '-', path }); | ||
} else if (this[_watchers].has(path)) { | ||
// note the deleted directory: stop watching it, and report any files that were in it | ||
for (const old of this[_watchers].keys()) { | ||
if (old === path || old.startsWith(path + '/')) { | ||
this[_watchers].get(old).close(); | ||
this[_watchers].delete(old); | ||
} | ||
} | ||
for (const old of this[_stats].keys()) { | ||
if (old.startsWith(path + '/')) { | ||
this[_stats].delete(old); | ||
this.emit('', { event: '-', path: old }); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
this[_isProcessing] = false; | ||
} | ||
constructor(data) { | ||
super(); | ||
this._watchers = new Map(); | ||
this._stats = new Map(); | ||
this._timeouts = new Map(); | ||
this._queue = new Array(); | ||
this._isProcessing = false; | ||
Object.assign(this, data); | ||
} | ||
async init() { | ||
await this._recurse(this.dir); | ||
return [...this._stats.entries()].map(([path, stats]) => ({ | ||
path, | ||
stats, | ||
})); | ||
} | ||
async _recurse(full) { | ||
const path = full.slice(this.dir.length + 1); | ||
const stats = await stat$1(full); | ||
if (this.filter && !(await this.filter({ path, stats }))) { | ||
return; | ||
} | ||
if (stats.isFile()) { | ||
this._stats.set(path, stats); | ||
} | ||
else if (stats.isDirectory()) { | ||
if (this.watch) { | ||
this._watchers.set(path, watch(full, this._handle.bind(this, full))); | ||
} | ||
await Promise.all((await readdir$1(full)).map(sub => this._recurse(full + '/' + sub))); | ||
} | ||
} | ||
_handle(dir, event, file) { | ||
const full = dir + '/' + file; | ||
if (this._timeouts.has(full)) { | ||
clearTimeout(this._timeouts.get(full)); | ||
} | ||
this._timeouts.set(full, setTimeout(() => { | ||
this._timeouts.delete(full); | ||
this._enqueue(full); | ||
}, this.debounce)); | ||
} | ||
async _enqueue(full) { | ||
this._queue.push(full); | ||
if (this._isProcessing) { | ||
return; | ||
} | ||
this._isProcessing = true; | ||
while (this._queue.length) { | ||
const full = this._queue.shift(); | ||
const path = full.slice(this.dir.length + 1); | ||
try { | ||
const stats = await stat$1(full); | ||
if (this.filter && !(await this.filter({ path, stats }))) { | ||
continue; | ||
} | ||
if (stats.isFile()) { | ||
this._stats.set(path, stats); | ||
this.emit('', { event: '+', path, stats }); | ||
} | ||
else if (stats.isDirectory() && !this._watchers.has(path)) { | ||
await this._recurse(full); | ||
for (const [newPath, stats] of this._stats.entries()) { | ||
if (newPath.startsWith(path + '/')) { | ||
this.emit('', { event: '+', path: newPath, stats }); | ||
} | ||
} | ||
} | ||
} | ||
catch (e) { | ||
if (this._stats.has(path)) { | ||
this._stats.delete(path); | ||
this.emit('', { event: '-', path }); | ||
} | ||
else if (this._watchers.has(path)) { | ||
for (const old of this._watchers.keys()) { | ||
if (old === path || old.startsWith(path + '/')) { | ||
this._watchers.get(old).close(); | ||
this._watchers.delete(old); | ||
} | ||
} | ||
for (const old of this._stats.keys()) { | ||
if (old.startsWith(path + '/')) { | ||
this._stats.delete(old); | ||
this.emit('', { event: '-', path: old }); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
this._isProcessing = false; | ||
} | ||
} | ||
const contexts = new Map(); | ||
createHook({ | ||
init: (id, _, trigger) => contexts.set(id, contexts.get(trigger)), | ||
destroy: id => contexts.delete(id), | ||
init: (id, _, trigger) => contexts.set(id, contexts.get(trigger)), | ||
destroy: id => contexts.delete(id), | ||
}).enable(); | ||
const create = data => contexts.set(executionAsyncId(), data); | ||
const create = (data) => { | ||
contexts.set(executionAsyncId(), data); | ||
}; | ||
const current = () => contexts.get(executionAsyncId()); | ||
const _origData = Symbol(); | ||
const _status = Symbol(); | ||
const _before = Symbol(); | ||
const _during = Symbol(); | ||
const _after = Symbol(); | ||
const _watchers$1 = Symbol(); | ||
const _transform = Symbol(); | ||
const _generators = Symbol(); | ||
const _resolver = Symbol(); | ||
const _onerror = Symbol(); | ||
const _active = Symbol(); | ||
const _waitingFor = Symbol(); | ||
const _whenFound = Symbol(); | ||
const _deps = Symbol(); | ||
const _queue$1 = Symbol(); | ||
const _isProcessing$1 = Symbol(); | ||
const _startWave = Symbol(); | ||
const _endWave = Symbol(); | ||
const _enqueue$1 = Symbol(); | ||
const _processPhysicalFile = Symbol(); | ||
const _processFile = Symbol(); | ||
const _callTransform = Symbol(); | ||
const _processGenerator = Symbol(); | ||
const _checkWave = Symbol(); | ||
const _processDependents = Symbol(); | ||
const _markFound = Symbol(); | ||
class Defiler { | ||
constructor(...args) { | ||
const { transform, generators = [], resolver, onerror } = args.pop(); | ||
if (typeof transform !== 'function') { | ||
throw new TypeError('defiler: transform must be a function'); | ||
} | ||
if ( | ||
!Array.isArray(generators) || | ||
generators.some(generator => typeof generator !== 'function') | ||
) { | ||
throw new TypeError('defiler: generators must be an array of functions'); | ||
} | ||
if (resolver && typeof resolver !== 'function') { | ||
throw new TypeError('defiler: resolver must be a function'); | ||
} | ||
if (onerror && typeof onerror !== 'function') { | ||
throw new TypeError('defiler: onerror must be a function'); | ||
} | ||
// set of original paths for all physical files | ||
this.paths = new Set(); | ||
// original paths -> original file data for all physical files ({ path, stats, bytes, enc }) | ||
this[_origData] = new Map(); | ||
// original paths -> transformed files for all physical and virtual files | ||
this.files = new Map(); | ||
// _before, _during, or _after exec has been called | ||
this[_status] = _before; | ||
// Watcher instances | ||
this[_watchers$1] = args.map( | ||
({ | ||
dir, | ||
filter, | ||
read = true, | ||
enc = 'utf8', | ||
pre, | ||
watch$$1 = true, | ||
debounce = 10, | ||
}) => { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('defiler: dir must be a string'); | ||
} | ||
if (filter && typeof filter !== 'function') { | ||
throw new TypeError('defiler: filter must be a function'); | ||
} | ||
if (typeof read !== 'boolean' && typeof read !== 'function') { | ||
throw new TypeError('defiler: read must be a boolean or a function'); | ||
} | ||
if (!Buffer.isEncoding(enc) && typeof enc !== 'function') { | ||
throw new TypeError( | ||
'defiler: enc must be a supported encoding or a function', | ||
); | ||
} | ||
if (pre && typeof pre !== 'function') { | ||
throw new TypeError('defiler: pre must be a function'); | ||
} | ||
if (typeof watch$$1 !== 'boolean') { | ||
throw new TypeError('defiler: watch must be a boolean'); | ||
} | ||
if (typeof debounce !== 'number') { | ||
throw new TypeError('defiler: debounce must be a number'); | ||
} | ||
dir = resolve(dir); | ||
return new Watcher({ dir, filter, read, enc, pre, watch: watch$$1, debounce }); | ||
}, | ||
); | ||
// the transform to run on all files | ||
this[_transform] = transform; | ||
// unique symbols -> registered generators | ||
this[_generators] = new Map( | ||
generators.map(generator => [Symbol(), generator]), | ||
); | ||
// (base, path) => path resolver function, used in defiler.get and defiler.add from transform | ||
this[_resolver] = resolver; | ||
// handler to call when errors occur | ||
this[_onerror] = onerror; | ||
// original paths of all files currently undergoing transformation and symbols of all generators currently running | ||
this[_active] = new Set(); | ||
// original paths -> number of other files they're currently waiting on to exist | ||
this[_waitingFor] = new Map(); | ||
// original paths -> { promise, resolve, paths } objects for when awaited files become available | ||
this[_whenFound] = new Map(); | ||
// array of [dependent, dependency] pairs, specifying changes to which files should trigger re-processing which other files | ||
this[_deps] = []; | ||
// queue of pending Watcher events to handle | ||
this[_queue$1] = []; | ||
// whether some Watcher event is currently already in the process of being handled | ||
this[_isProcessing$1] = false; | ||
} | ||
// execute everything, and return a promise that resolves when the first wave of processing is complete | ||
async exec() { | ||
if (this[_status] !== _before) { | ||
throw new Error('defiler.exec: cannot call more than once'); | ||
} | ||
this[_status] = _during; | ||
this[_isProcessing$1] = true; | ||
const done = this[_startWave](); | ||
// init the Watcher instances | ||
const files = []; | ||
await Promise.all( | ||
this[_watchers$1].map(async watcher => { | ||
watcher.on('', event => this[_enqueue$1](watcher, event)); | ||
// note that all files are pending transformation | ||
await Promise.all( | ||
(await watcher.init()).map(async file => { | ||
const { path } = file; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
this.paths.add(file.path); | ||
this[_active].add(file.path); | ||
files.push([watcher, path, file]); | ||
}), | ||
); | ||
}), | ||
); | ||
for (const symbol of this[_generators].keys()) { | ||
this[_active].add(symbol); | ||
} | ||
// process each physical file | ||
for (const [watcher, path, file] of files) { | ||
this[_processPhysicalFile](watcher, path, file); | ||
} | ||
// process each generator | ||
for (const symbol of this[_generators].keys()) { | ||
this[_processGenerator](symbol); | ||
} | ||
// wait and finish up | ||
await done; | ||
this[_status] = _after; | ||
this[_isProcessing$1] = false; | ||
this[_enqueue$1](); | ||
} | ||
// wait for a file to be available and retrieve it, marking dependencies as appropriate | ||
async get(path) { | ||
if (Array.isArray(path)) { | ||
return Promise.all(path.map(path => this.get(path))); | ||
} | ||
const current$$1 = current(); | ||
path = this.resolve(path); | ||
if (typeof path !== 'string') { | ||
throw new TypeError('defiler.get: path must be a string'); | ||
} | ||
if (current$$1) { | ||
this[_deps].push([current$$1, path]); | ||
} | ||
if (this[_status] === _during && !this.files.has(path) && current$$1) { | ||
this[_waitingFor].set(current$$1, (this[_waitingFor].get(current$$1) || 0) + 1); | ||
if (this[_whenFound].has(path)) { | ||
const { promise, paths } = this[_whenFound].get(path); | ||
paths.push(current$$1); | ||
await promise; | ||
} else { | ||
let resolve$$1; | ||
const promise = new Promise(res => (resolve$$1 = res)); | ||
this[_whenFound].set(path, { promise, resolve: resolve$$1, paths: [current$$1] }); | ||
await promise; | ||
} | ||
} | ||
return this.files.get(path); | ||
} | ||
// add a new virtual file | ||
add(file) { | ||
if (this[_status] === _before) { | ||
throw new Error('defiler.add: cannot call before calling exec'); | ||
} | ||
if (typeof file !== 'object') { | ||
throw new TypeError('defiler.add: file must be an object'); | ||
} | ||
file.path = this.resolve(file.path); | ||
this[_origData].set(file.path, file); | ||
this[_processFile](file, 'add'); | ||
} | ||
// resolve a given path from the file currently being transformed | ||
resolve(path) { | ||
return this[_resolver] && typeof current() === 'string' | ||
? this[_resolver](current(), path) | ||
: path; | ||
} | ||
// private methods | ||
// return a Promise that we will resolve at the end of this wave, and save its resolver | ||
[_startWave]() { | ||
return new Promise(res => (this[_endWave] = res)); | ||
} | ||
// add a Watcher event to the queue, and handle queued events | ||
async [_enqueue$1](watcher, event) { | ||
if (event) { | ||
this[_queue$1].push([watcher, event]); | ||
} | ||
if (this[_isProcessing$1]) { | ||
return; | ||
} | ||
this[_isProcessing$1] = true; | ||
while (this[_queue$1].length) { | ||
const done = this[_startWave](); | ||
const [watcher, { event, path, stats }] = this[_queue$1].shift(); | ||
const file = { path, stats }; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
if (event === '+') { | ||
this[_processPhysicalFile](watcher, path, file); | ||
} else if (event === '-') { | ||
const { path } = file; | ||
const oldFile = this.files.get(path); | ||
this.paths.delete(path); | ||
this[_origData].delete(path); | ||
this.files.delete(path); | ||
await this[_callTransform](oldFile, 'delete'); | ||
this[_processDependents](path); | ||
} | ||
await done; | ||
} | ||
this[_isProcessing$1] = false; | ||
} | ||
// create a file object for a physical file and process it | ||
async [_processPhysicalFile]({ dir, read, enc }, path, file) { | ||
if (typeof read === 'function') { | ||
read = await read({ path, stats: file.stats }); | ||
} | ||
if (read) { | ||
file.bytes = await readFile$1(dir + '/' + path); | ||
} | ||
if (typeof enc === 'function') { | ||
enc = await enc({ path, stats: file.stats, bytes: file.bytes }); | ||
} | ||
file.enc = enc; | ||
this.paths.add(file.path); | ||
this[_origData].set(file.path, file); | ||
await this[_processFile](file, 'read'); | ||
} | ||
// transform a file, store it, and process dependents | ||
async [_processFile](data, event) { | ||
const file = Object.assign(new File(), data); | ||
const { path } = file; | ||
this[_active].add(path); | ||
await this[_callTransform](file, event); | ||
this.files.set(path, file); | ||
this[this[_status] === _during ? _markFound : _processDependents](path); | ||
this[_active].delete(path); | ||
this[_checkWave](); | ||
} | ||
// call the transform on a file with the given event string, and handle errors | ||
async [_callTransform](file, event) { | ||
await null; | ||
create(file.path); | ||
try { | ||
await this[_transform]({ file, event }); | ||
} catch (error) { | ||
if (this[_onerror]) { | ||
this[_onerror]({ file, event, error }); | ||
} | ||
} | ||
} | ||
// run the generator given by the symbol | ||
async [_processGenerator](symbol) { | ||
this[_active].add(symbol); | ||
const generator = this[_generators].get(symbol); | ||
await null; | ||
create(symbol); | ||
try { | ||
await generator(); | ||
} catch (error) { | ||
if (this[_onerror]) { | ||
this[_onerror]({ generator, error }); | ||
} | ||
} | ||
this[_active].delete(symbol); | ||
this[_checkWave](); | ||
} | ||
// re-process all files that depend on a particular path | ||
[_processDependents](path) { | ||
const dependents = new Set(); | ||
for (const [dependent, dependency] of this[_deps]) { | ||
if (dependency === path) { | ||
dependents.add(dependent); | ||
} | ||
} | ||
this[_deps] = this[_deps].filter( | ||
([dependent]) => !dependents.has(dependent), | ||
); | ||
for (const dependent of dependents) { | ||
if (this[_origData].has(dependent)) { | ||
this[_processFile](this[_origData].get(dependent), 'retransform'); | ||
} else if (this[_generators].has(dependent)) { | ||
this[_processGenerator](dependent); | ||
} | ||
} | ||
this[_checkWave](); | ||
} | ||
// check whether this wave is complete, and, if not, whether we need to break a deadlock | ||
[_checkWave]() { | ||
if (!this[_active].size) { | ||
this[_endWave](); | ||
} else if ( | ||
this[_status] === _during && | ||
[...this[_active]].every(path => this[_waitingFor].get(path)) | ||
) { | ||
// all pending files are currently waiting for one or more other files to exist | ||
// break deadlock: assume all files that have not appeared yet will never do so | ||
for (const path of this[_whenFound].keys()) { | ||
if (!this[_active].has(path)) { | ||
this[_markFound](path); | ||
} | ||
} | ||
} | ||
} | ||
// mark a given awaited file as being found | ||
[_markFound](path) { | ||
if (this[_whenFound].has(path)) { | ||
const { resolve: resolve$$1, paths } = this[_whenFound].get(path); | ||
for (const path of paths) { | ||
this[_waitingFor].set(path, this[_waitingFor].get(path) - 1); | ||
} | ||
resolve$$1(); | ||
this[_whenFound].delete(path); | ||
} | ||
} | ||
constructor(...args) { | ||
this.paths = new Set(); | ||
this._origData = new Map(); | ||
this.files = new Map(); | ||
this._status = Status.Before; | ||
this._active = new Set(); | ||
this._waitingFor = new Map(); | ||
this._whenFound = new Map(); | ||
this._deps = new Array(); | ||
this._queue = new Array(); | ||
this._isProcessing = false; | ||
this._endWave = null; | ||
const { transform, generators = [], resolver, onerror } = (args.pop()); | ||
if (typeof transform !== 'function') { | ||
throw new TypeError('defiler: transform must be a function'); | ||
} | ||
if (!Array.isArray(generators) || | ||
generators.some(generator => typeof generator !== 'function')) { | ||
throw new TypeError('defiler: generators must be an array of functions'); | ||
} | ||
if (resolver && typeof resolver !== 'function') { | ||
throw new TypeError('defiler: resolver must be a function'); | ||
} | ||
if (onerror && typeof onerror !== 'function') { | ||
throw new TypeError('defiler: onerror must be a function'); | ||
} | ||
this._watchers = args.map(({ dir, filter, read = true, enc = 'utf8', pre, watch$$1 = true, debounce = 10, }) => { | ||
if (typeof dir !== 'string') { | ||
throw new TypeError('defiler: dir must be a string'); | ||
} | ||
if (filter && typeof filter !== 'function') { | ||
throw new TypeError('defiler: filter must be a function'); | ||
} | ||
if (typeof read !== 'boolean' && typeof read !== 'function') { | ||
throw new TypeError('defiler: read must be a boolean or a function'); | ||
} | ||
if (!Buffer.isEncoding(enc) && typeof enc !== 'function') { | ||
throw new TypeError('defiler: enc must be a supported encoding or a function'); | ||
} | ||
if (pre && typeof pre !== 'function') { | ||
throw new TypeError('defiler: pre must be a function'); | ||
} | ||
if (typeof watch$$1 !== 'boolean') { | ||
throw new TypeError('defiler: watch must be a boolean'); | ||
} | ||
if (typeof debounce !== 'number') { | ||
throw new TypeError('defiler: debounce must be a number'); | ||
} | ||
dir = resolve(dir); | ||
return (new Watcher({ dir, filter, read, enc, pre, watch: watch$$1, debounce })); | ||
}); | ||
this._transform = transform; | ||
this._generators = new Map(generators.map(generator => [Symbol(), generator])); | ||
this._resolver = resolver; | ||
this._onerror = onerror; | ||
} | ||
async exec() { | ||
if (this._status !== Status.Before) { | ||
throw new Error('defiler.exec: cannot call more than once'); | ||
} | ||
this._status = Status.During; | ||
this._isProcessing = true; | ||
const done = this._startWave(); | ||
const files = new Array(); | ||
await Promise.all(this._watchers.map(async (watcher) => { | ||
watcher.on('', event => this._enqueue(watcher, event)); | ||
await Promise.all((await watcher.init()).map(async (file) => { | ||
const { path } = file; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
this.paths.add(file.path); | ||
this._active.add(file.path); | ||
files.push([watcher, path, file]); | ||
})); | ||
})); | ||
for (const symbol of this._generators.keys()) { | ||
this._active.add(symbol); | ||
} | ||
for (const [watcher, path, file] of files) { | ||
this._processPhysicalFile(watcher, path, file); | ||
} | ||
for (const symbol of this._generators.keys()) { | ||
this._processGenerator(symbol); | ||
} | ||
await done; | ||
this._status = Status.After; | ||
this._isProcessing = false; | ||
this._enqueue(); | ||
} | ||
async get(_) { | ||
if (Array.isArray(_)) { | ||
return Promise.all(_.map(path => this.get(path))); | ||
} | ||
if (typeof _ !== 'string' && typeof _ !== 'function') { | ||
throw new TypeError('defiler.get: argument must be a string, an array, or a function'); | ||
} | ||
const current$$1 = current(); | ||
if (current$$1) { | ||
this._deps.push([current$$1, _]); | ||
} | ||
if (typeof _ === 'function') { | ||
return this.get([...this.paths].filter(_).sort()); | ||
} | ||
const path = this.resolve(_); | ||
if (this._status === Status.During && !this.files.has(path) && current$$1) { | ||
this._waitingFor.set(current$$1, (this._waitingFor.get(current$$1) || 0) + 1); | ||
if (this._whenFound.has(path)) { | ||
const { promise, paths } = this._whenFound.get(path); | ||
paths.push(current$$1); | ||
await promise; | ||
} | ||
else { | ||
let resolve$$1; | ||
const promise = new Promise(res => (resolve$$1 = res)); | ||
this._whenFound.set(path, { promise, resolve: resolve$$1, paths: [current$$1] }); | ||
await promise; | ||
} | ||
} | ||
return this.files.get(path); | ||
} | ||
add(file) { | ||
if (this._status === Status.Before) { | ||
throw new Error('defiler.add: cannot call before calling exec'); | ||
} | ||
if (typeof file !== 'object') { | ||
throw new TypeError('defiler.add: file must be an object'); | ||
} | ||
file.path = this.resolve(file.path); | ||
this._origData.set(file.path, file); | ||
this._processFile(file, 'add'); | ||
} | ||
resolve(path) { | ||
return this._resolver && typeof current() === 'string' | ||
? this._resolver(current(), path) | ||
: path; | ||
} | ||
_startWave() { | ||
return new Promise(res => (this._endWave = res)); | ||
} | ||
async _enqueue(watcher, event) { | ||
if (event) { | ||
this._queue.push([watcher, event]); | ||
} | ||
if (this._isProcessing) { | ||
return; | ||
} | ||
this._isProcessing = true; | ||
while (this._queue.length) { | ||
const done = this._startWave(); | ||
const [watcher, { event, path, stats }] = this._queue.shift(); | ||
const file = { path, stats }; | ||
if (watcher.pre) { | ||
await watcher.pre(file); | ||
} | ||
if (event === '+') { | ||
this._processPhysicalFile(watcher, path, file); | ||
} | ||
else if (event === '-') { | ||
const { path } = file; | ||
const oldFile = this.files.get(path); | ||
this.paths.delete(path); | ||
this._origData.delete(path); | ||
this.files.delete(path); | ||
await this._callTransform(oldFile, 'delete'); | ||
this._processDependents(path); | ||
} | ||
await done; | ||
} | ||
this._isProcessing = false; | ||
} | ||
async _processPhysicalFile({ dir, read, enc }, path, file) { | ||
if (typeof read === 'function') { | ||
read = await read({ path, stats: file.stats }); | ||
} | ||
if (read) { | ||
file.bytes = await readFile$1(dir + '/' + path); | ||
} | ||
if (typeof enc === 'function') { | ||
enc = await enc({ path, stats: file.stats, bytes: file.bytes }); | ||
} | ||
file.enc = enc; | ||
this.paths.add(file.path); | ||
this._origData.set(file.path, file); | ||
await this._processFile(file, 'read'); | ||
} | ||
async _processFile(data, event) { | ||
const file = Object.assign(new File(), data); | ||
const { path } = file; | ||
this._active.add(path); | ||
await this._callTransform(file, event); | ||
this.files.set(path, file); | ||
if (this._status === Status.During) { | ||
this._markFound(path); | ||
} | ||
else { | ||
this._processDependents(path); | ||
} | ||
this._active.delete(path); | ||
this._checkWave(); | ||
} | ||
async _callTransform(file, event) { | ||
await null; | ||
create(file.path); | ||
try { | ||
await this._transform({ file, event }); | ||
} | ||
catch (error) { | ||
if (this._onerror) { | ||
this._onerror({ file, event, error }); | ||
} | ||
} | ||
} | ||
async _processGenerator(symbol) { | ||
this._active.add(symbol); | ||
const generator = this._generators.get(symbol); | ||
await null; | ||
create(symbol); | ||
try { | ||
await generator(); | ||
} | ||
catch (error) { | ||
if (this._onerror) { | ||
this._onerror({ generator, error }); | ||
} | ||
} | ||
this._active.delete(symbol); | ||
this._checkWave(); | ||
} | ||
_processDependents(path) { | ||
const dependents = new Set(); | ||
for (const [dependent, dependency] of this._deps) { | ||
if (typeof dependency === 'string' ? dependency === path : dependency(path)) { | ||
dependents.add(dependent); | ||
} | ||
} | ||
this._deps = this._deps.filter(([dependent]) => !dependents.has(dependent)); | ||
for (const dependent of dependents) { | ||
if (this._origData.has(dependent)) { | ||
this._processFile(this._origData.get(dependent), 'retransform'); | ||
} | ||
else if (this._generators.has(dependent)) { | ||
this._processGenerator(dependent); | ||
} | ||
} | ||
this._checkWave(); | ||
} | ||
_checkWave() { | ||
if (!this._active.size) { | ||
this._endWave(); | ||
} | ||
else if (this._status === Status.During && | ||
[...this._active].every(path => !!this._waitingFor.get(path))) { | ||
for (const path of this._whenFound.keys()) { | ||
if (!this._active.has(path)) { | ||
this._markFound(path); | ||
} | ||
} | ||
} | ||
} | ||
_markFound(path) { | ||
if (this._whenFound.has(path)) { | ||
const { resolve: resolve$$1, paths } = this._whenFound.get(path); | ||
for (const path of paths) { | ||
this._waitingFor.set(path, this._waitingFor.get(path) - 1); | ||
} | ||
resolve$$1(); | ||
this._whenFound.delete(path); | ||
} | ||
} | ||
} | ||
var Status; | ||
(function (Status) { | ||
Status[Status["Before"] = 0] = "Before"; | ||
Status[Status["During"] = 1] = "During"; | ||
Status[Status["After"] = 2] = "After"; | ||
})(Status || (Status = {})); | ||
export { File, Defiler }; | ||
//# sourceMappingURL=index.es.js.map |
{ | ||
"name": "defiler", | ||
"version": "0.17.3", | ||
"version": "0.17.4", | ||
"description": "A small, strange building block", | ||
@@ -30,3 +30,5 @@ "keywords": [ | ||
"devDependencies": { | ||
"rollup": "*" | ||
"rollup": "*", | ||
"typescript": "*", | ||
"@types/node": "*" | ||
}, | ||
@@ -33,0 +35,0 @@ "scripts": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
125228
3
985
1