Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

defiler

Package Overview
Dependencies
Maintainers
1
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

defiler - npm Package Compare versions

Comparing version 0.17.3 to 0.17.4

4

CHANGELOG.md

@@ -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 @@

1080

dist/index.cjs.js

@@ -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;

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc