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.11.1 to 0.11.2

11

CHANGELOG.md

@@ -0,4 +1,9 @@

# v0.11.2
- Add `file.dir` and `file.filename`, which also are kept in sync with `file.path`
- Add support for other encodings, via `file.enc` and the `enc` option when specifying an input directory
# v0.11.1
- Close FSWatcher instances when their directories are deleted
- Close `FSWatcher` instances when their directories are deleted

@@ -51,7 +56,7 @@ # v0.11.0

- Do not include directories in `defiler.origFiles`/`defiler.files`/`defiler.origPaths`.
- Do not include directories in `defiler.origFiles`/`defiler.files`/`defiler.origPaths`
# v0.6.1
- Add second argument to transform and generator callbacks which is a convenience function calling `defiler.get` with the appropriate `dependent`.
- Add second argument to transform and generator callbacks which is a convenience function calling `defiler.get` with the appropriate `dependent`

@@ -58,0 +63,0 @@ # v0.6.0

@@ -12,6 +12,13 @@ 'use strict';

var symbols = new Proxy({}, { get: (_, key) => Symbol(key) })
let { _path, _dir, _filename, _ext, _enc, _bytes, _text } = symbols;
class File {
constructor(path$$1) {
constructor(path$$1 = '') {
if (typeof path$$1 !== 'string') throw new TypeError('file.path must be a string')
// path of file
this._path = path$$1;
this[_path] = path$$1;
// cached dir/filename/ext values
this[_dir] = this[_filename] = this[_ext] = null;
// all historical paths of file

@@ -21,15 +28,19 @@ this.paths = path$$1 ? [path$$1] : [];

this.stat = null;
// encoding
this[_enc] = 'utf8';
// Buffer of file contents
this._bytes = null;
this[_bytes] = null;
// string of file contents
this._text = null;
this[_text] = null;
}
get path() {
return this._path
return this[_path]
}
set path(path$$1) {
if (this._path !== path$$1) {
this._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;
this.paths.push(path$$1);

@@ -39,37 +50,75 @@ }

get dir() {
if (this[_dir] == null) {
let 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) {
let p = this[_path].lastIndexOf('/');
this[_filename] = p > -1 ? this[_path].slice(p + 1) : '';
}
return this[_filename]
}
set filename(filename) {
if (typeof filename !== 'string') throw new TypeError('file.filename must be a string')
let old = this.filename;
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + filename;
}
get ext() {
if (this.path && this.path.match) {
let match = this.path.match(/\.[^./\\]+$/);
if (match) return match[0]
if (this[_ext] == null) {
let p1 = this[_path].lastIndexOf('.');
let p2 = this[_path].lastIndexOf('/');
this[_ext] = p1 > -1 && p1 > p2 ? this[_path].slice(p1) : '';
}
return ''
return this[_ext]
}
set ext(ext) {
let oldExt = this.ext;
if (oldExt) {
this.path = this.path.slice(0, -oldExt.length) + ext;
} else {
this.path += ext;
}
if (typeof ext !== 'string') throw new TypeError('file.ext must be a string')
let 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() {
if (this._bytes == null && this._text != null) this._bytes = Buffer.from(this._text);
return this._bytes
return this[_bytes] == null && this[_text] != null
? (this[_bytes] = Buffer.from(this[_text], this[_enc]))
: this[_bytes]
}
set bytes(bytes) {
this._bytes = bytes;
this._text = null;
if (!Buffer.isBuffer(bytes)) throw new TypeError('file.bytes must be a Buffer')
this[_bytes] = bytes;
this[_text] = null;
}
get text() {
if (this._text == null && this._bytes != null) this._text = this._bytes.toString();
return this._text
return this[_text] == null && this[_bytes] != null
? (this[_text] = this[_bytes].toString(this[_enc]))
: this[_text]
}
set text(text) {
this._text = text;
this._bytes = null;
if (typeof text !== 'string') throw new TypeError('file.text must be a string')
this[_text] = text;
this[_bytes] = null;
}

@@ -82,2 +131,16 @@ }

let {
_dir: _dir$1,
_watch,
_debounce,
_dirs,
_files,
_timeouts,
_queue,
_processing,
_recurse,
_handle,
_enqueue,
} = symbols;
class Watcher extends EventEmitter {

@@ -87,17 +150,17 @@ constructor(dir, watch, debounce) {

// directory to recursively watch the contents of
this._dir = dir;
this[_dir$1] = dir;
// whether to actually watch for changes (or just walk and retrieve contents and file stats)
this._watch = watch;
this[_watch] = watch;
// fs.watch event debounce, in milliseconds
this._debounce = debounce;
this[_debounce] = debounce;
// paths of all (recursive) directories -> FSWatcher instances
this._dirs = new Map();
this[_dirs] = new Map();
// paths of all (recursive) files -> file stats
this._files = new Map();
this[_files] = new Map();
// paths of (recursive) files with pending debounced events -> setTimeout timer ids
this._timeouts = new Map();
this[_timeouts] = new Map();
// queue of pending FSWatcher events to handle
this._queue = [];
this[_queue] = [];
// whether some FSWatcher event is currently already in the process of being handled
this._processing = false;
this[_processing] = false;
}

@@ -108,15 +171,15 @@

async init() {
await this._recurse(this._dir);
return [...this._files.entries()].map(([path$$1, stat$$1]) => ({ path: path$$1, stat: stat$$1 }))
await this[_recurse](this[_dir$1]);
return [...this[_files].entries()].map(([path$$1, stat$$1]) => ({ path: path$$1, stat: stat$$1 }))
}
// recurse a given directory
async _recurse(full) {
let path$$1 = full.slice(this._dir.length + 1);
async [_recurse](full) {
let path$$1 = full.slice(this[_dir$1].length + 1);
let fileStat = await stat(full);
if (fileStat.isFile()) {
this._files.set(path$$1, fileStat);
this[_files].set(path$$1, fileStat);
} else if (fileStat.isDirectory()) {
if (this._watch) this._dirs.set(path$$1, fs.watch(full, this._handle.bind(this, full)));
await Promise.all((await readdir(full)).map(sub => this._recurse(full + '/' + sub)));
if (this[_watch]) this[_dirs].set(path$$1, fs.watch(full, this[_handle].bind(this, full)));
await Promise.all((await readdir(full)).map(sub => this[_recurse](full + '/' + sub)));
}

@@ -126,11 +189,11 @@ }

// handle FSWatcher event for given directory
_handle(dir, event, file) {
[_handle](dir, event, file) {
let full = dir + '/' + file;
if (this._timeouts.has(full)) clearTimeout(this._timeouts.get(full));
this._timeouts.set(
if (this[_timeouts].has(full)) clearTimeout(this[_timeouts].get(full));
this[_timeouts].set(
full,
setTimeout(() => {
this._timeouts.delete(full);
this._enqueue(full);
}, this._debounce),
this[_timeouts].delete(full);
this[_enqueue](full);
}, this[_debounce]),
);

@@ -140,9 +203,9 @@ }

// add an FSWatcher event to the queue, and handle queued events
async _enqueue(full) {
this._queue.push(full);
if (this._processing) return
this._processing = true;
while (this._queue.length) {
let full = this._queue.shift();
let path$$1 = full.slice(this._dir.length + 1);
async [_enqueue](full) {
this[_queue].push(full);
if (this[_processing]) return
this[_processing] = true;
while (this[_queue].length) {
let full = this[_queue].shift();
let path$$1 = full.slice(this[_dir$1].length + 1);
try {

@@ -152,8 +215,8 @@ let fileStat = await stat(full);

// note the new/changed file
this._files.set(path$$1, fileStat);
this[_files].set(path$$1, fileStat);
this.emit('', { event: '+', path: path$$1, stat: fileStat });
} else if (fileStat.isDirectory() && !this._dirs.has(path$$1)) {
} else if (fileStat.isDirectory() && !this[_dirs].has(path$$1)) {
// note the new directory: start watching it, and report any files in it
await this._recurse(full);
for (let [newPath, fileStat] of this._files.entries()) {
await this[_recurse](full);
for (let [newPath, fileStat] of this[_files].entries()) {
if (newPath.startsWith(path$$1 + '/')) {

@@ -166,17 +229,17 @@ this.emit('', { event: '+', path: newPath, stat: fileStat });

// probably this was a deleted file/directory
if (this._files.has(path$$1)) {
if (this[_files].has(path$$1)) {
// note the deleted file
this._files.delete(path$$1);
this[_files].delete(path$$1);
this.emit('', { event: '-', path: path$$1 });
} else if (this._dirs.has(path$$1)) {
} else if (this[_dirs].has(path$$1)) {
// note the deleted directory: stop watching it, and report any files that were in it
for (let old of this._dirs.keys()) {
for (let old of this[_dirs].keys()) {
if (old === path$$1 || old.startsWith(path$$1 + '/')) {
this._dirs.get(old).close();
this._dirs.delete(old);
this[_dirs].get(old).close();
this[_dirs].delete(old);
}
}
for (let old of this._files.keys()) {
for (let old of this[_files].keys()) {
if (old.startsWith(path$$1 + '/')) {
this._files.delete(old);
this[_files].delete(old);
this.emit('', { event: '-', path: old });

@@ -188,6 +251,33 @@ }

}
this._processing = false;
this[_processing] = false;
}
}
let {
_origFiles,
_files: _files$1,
_status,
_watchers,
_transforms,
_generators,
_done,
_pending,
_waiting,
_available,
_dependents,
_queue: _queue$1,
_processing: _processing$1,
_checkBeforeExec,
_checkAfterExec,
_wait,
_enqueue: _enqueue$1,
_processPhysicalFile,
_processFile,
_transformFile,
_handleGeneratedFile,
_get,
_processDependents,
_found,
} = symbols;
class Defiler extends EventEmitter {

@@ -197,27 +287,27 @@ constructor() {

// original paths -> original files for all physical files
this._origFiles = new Map();
this[_origFiles] = new Map();
// original paths -> transformed files for all physical/generated/etc. files
this._files = new Map();
this[_files$1] = new Map();
// null = exec not called; false = exec pending; true = exec finished
this._status = null;
this[_status] = null;
// all registered Watcher instances (one per directory)
this._watchers = [];
this[_watchers] = [];
// all registered transforms
this._transforms = [];
this[_transforms] = [];
// paths -> registered generators
this._generators = new Map();
this[_generators] = new Map();
// { count, resolve, reject } object for main promise for the first exec wave
this._done = { count: 0, resolve: null, reject: null };
this[_done] = { count: 0, resolve: null, reject: null };
// original paths of all files currently undergoing some transform/generator
this._pending = new Set();
this[_pending] = new Set();
// original paths -> number of other files they're currently waiting on to exist
this._waiting = new Map();
this[_waiting] = new Map();
// original paths -> { promise, resolve } objects for when awaited files become available
this._available = new Map();
this[_available] = new Map();
// original paths of dependents -> set of original paths of dependencies, specifying changes to which files should trigger re-processing which other files
this._dependents = new Map();
this[_dependents] = new Map();
// queue of pending Watcher events to handle
this._queue = [];
this[_queue$1] = [];
// whether some Watcher event is currently already in the process of being handled
this._processing = false;
this[_processing$1] = false;
}

@@ -228,15 +318,15 @@

get status() {
return this._status
return this[_status]
}
get origFiles() {
return this._origFiles
return this[_origFiles]
}
get files() {
return this._files
return this[_files$1]
}
get origPaths() {
return [...this._origFiles.keys()].sort()
return [...this[_origFiles].keys()].sort()
}

@@ -248,7 +338,8 @@

dir(...dirs) {
this._checkBeforeExec('dir');
for (let { dir, read = true, watch = true, debounce = 10 } of dirs.filter(Boolean)) {
dir = path.resolve(dir).replace(/\\/g, '/');
this[_checkBeforeExec]('dir');
for (let config of dirs.filter(Boolean)) {
let { dir, read = true, enc = 'utf8', watch = true, debounce = 10 } = config;
dir = path.resolve(dir);
let watcher = new Watcher(dir, watch, debounce);
this._watchers.push({ watcher, dir, read, watch });
this[_watchers].push({ watcher, dir, read, enc, watch });
}

@@ -260,4 +351,4 @@ return this

transform(...transforms) {
this._checkBeforeExec('transform');
this._transforms.push(...transforms.filter(Boolean));
this[_checkBeforeExec]('transform');
this[_transforms].push(...transforms.filter(Boolean));
return this

@@ -268,5 +359,5 @@ }

generator(generators) {
this._checkBeforeExec('generator');
this[_checkBeforeExec]('generator');
for (let [path$$1, generator] of Object.entries(generators)) {
if (path$$1 && generator) this._generators.set(path$$1, generator);
if (path$$1 && generator) this[_generators].set(path$$1, generator);
}

@@ -279,8 +370,8 @@ return this

async exec() {
this._checkBeforeExec('exec');
this._status = false;
this._processing = true;
this[_checkBeforeExec]('exec');
this[_status] = false;
this[_processing$1] = true;
let promise = new Promise((res, rej) => {
this._done.resolve = res;
this._done.reject = rej;
this[_done].resolve = res;
this[_done].reject = rej;
});

@@ -290,24 +381,26 @@ // init all Watcher instances; note that all files have pending transforms

await Promise.all(
this._watchers.map(async ({ watcher, dir, read, watch }) => {
this[_watchers].map(async ({ watcher, dir, read, enc, watch }) => {
if (watch) {
watcher.on('', ({ event, path: path$$1, stat: stat$$1 }) => this._enqueue({ event, dir, path: path$$1, stat: stat$$1, read }));
watcher.on('', ({ event, path: path$$1, stat: stat$$1 }) => {
this[_enqueue$1]({ event, dir, path: path$$1, stat: stat$$1, read, enc });
});
}
for (let { path: path$$1, stat: stat$$1 } of await watcher.init()) {
files.push({ dir, path: path$$1, stat: stat$$1, read });
this._origFiles.set(path$$1, null);
this._pending.add(path$$1);
files.push({ dir, path: path$$1, stat: stat$$1, read, enc });
this[_origFiles].set(path$$1, null);
this[_pending].add(path$$1);
}
}),
);
for (let path$$1 of this._generators.keys()) this._pending.add(path$$1);
for (let path$$1 of this[_generators].keys()) this[_pending].add(path$$1);
// process each physical file
for (let { dir, path: path$$1, stat: stat$$1, read } of files) {
this._wait(this._processPhysicalFile(dir, path$$1, stat$$1, read));
for (let { dir, path: path$$1, stat: stat$$1, read, enc } of files) {
this[_wait](this[_processPhysicalFile](dir, path$$1, stat$$1, read, enc));
}
// process each generated file
for (let path$$1 of this._generators.keys()) this._wait(this._handleGeneratedFile(path$$1));
for (let path$$1 of this[_generators].keys()) this[_wait](this[_handleGeneratedFile](path$$1));
// wait and finish up
await promise;
this._status = true;
this._processing = false;
this[_status] = true;
this[_processing$1] = false;
}

@@ -319,10 +412,10 @@

async file(file) {
this._checkAfterExec('file');
this[_checkAfterExec]('file');
let { path: path$$1 } = file;
if (!(file instanceof File)) file = Object.assign(new File(), file);
await this._wait(this._transformFile(file));
this._files.set(path$$1, file);
await this[_wait](this[_transformFile](file));
this[_files$1].set(path$$1, file);
this.emit('file', { defiler: this, path: path$$1, file });
this._found(path$$1);
this._processDependents(path$$1);
this[_found](path$$1);
this[_processDependents](path$$1);
}

@@ -332,7 +425,7 @@

depend(dependent, path$$1) {
this._checkAfterExec('depend');
if (this._dependents.has(dependent)) {
this._dependents.get(dependent).add(path$$1);
this[_checkAfterExec]('depend');
if (this[_dependents].has(dependent)) {
this[_dependents].get(dependent).add(path$$1);
} else {
this._dependents.set(dependent, new Set([path$$1]));
this[_dependents].set(dependent, new Set([path$$1]));
}

@@ -343,15 +436,15 @@ }

_checkBeforeExec(methodName) {
if (this._status !== null) throw new Error(`Cannot call ${methodName} after calling exec`)
[_checkBeforeExec](methodName) {
if (this[_status] !== null) throw new Error(`Cannot call ${methodName} after calling exec`)
}
_checkAfterExec(methodName) {
if (this._status === null) throw new Error(`Cannot call ${methodName} before calling exec`)
[_checkAfterExec](methodName) {
if (this[_status] === null) throw new Error(`Cannot call ${methodName} before calling exec`)
}
// add another promise that must resolve before the initial exec wave can finish
_wait(promise) {
if (!this._status) {
this._done.count++;
promise.then(() => --this._done.count || this._done.resolve(), this._done.reject);
[_wait](promise) {
if (!this[_status]) {
this[_done].count++;
promise.then(() => --this[_done].count || this[_done].resolve(), this[_done].reject);
}

@@ -362,46 +455,45 @@ return promise

// add a Watcher event to the queue, and handle queued events
async _enqueue(event) {
this._queue.push(event);
if (this._processing) return
this._processing = true;
while (this._queue.length) {
let { event, dir, path: path$$1, stat: stat$$1, read } = this._queue.shift();
async [_enqueue$1](event) {
this[_queue$1].push(event);
if (this[_processing$1]) return
this[_processing$1] = true;
while (this[_queue$1].length) {
let { event, dir, path: path$$1, stat: stat$$1, read, enc } = this[_queue$1].shift();
if (event === '+') {
await this._processPhysicalFile(dir, path$$1, stat$$1, read);
await this[_processPhysicalFile](dir, path$$1, stat$$1, read, enc);
} else if (event === '-') {
this._origFiles.delete(path$$1);
this._files.delete(path$$1);
this[_origFiles].delete(path$$1);
this[_files$1].delete(path$$1);
this.emit('deleted', { defiler: this, path: path$$1 });
this._processDependents(path$$1);
this[_processDependents](path$$1);
}
}
this._processing = false;
this[_processing$1] = false;
}
// create a file object for a physical file and process it
async _processPhysicalFile(dir, path$$1, stat$$1, read) {
let origFile = new File(path$$1);
origFile.stat = stat$$1;
async [_processPhysicalFile](dir, path$$1, stat$$1, read, enc) {
let origFile = Object.assign(new File(), { path: path$$1, stat: stat$$1, enc });
if (read) origFile.bytes = await readFile(dir + '/' + path$$1);
this._origFiles.set(path$$1, origFile);
this[_origFiles].set(path$$1, origFile);
this.emit('origFile', { defiler: this, file: origFile });
await this._processFile(origFile);
await this[_processFile](origFile);
}
// transform a file, store it, and process dependents
async _processFile(origFile) {
async [_processFile](origFile) {
let file = Object.assign(new File(), origFile);
await this._transformFile(file);
this._files.set(origFile.path, file);
await this[_transformFile](file);
this[_files$1].set(origFile.path, file);
this.emit('file', { defiler: this, path: origFile.path, file });
this._found(origFile.path);
this._processDependents(origFile.path);
this[_found](origFile.path);
this[_processDependents](origFile.path);
}
// perform all transforms on a file
async _transformFile(file) {
async [_transformFile](file) {
let { path: path$$1 } = file;
this._pending.add(path$$1);
this[_pending].add(path$$1);
try {
for (let transform of this._transforms) {
for (let transform of this[_transforms]) {
await transform({

@@ -411,3 +503,3 @@ defiler: this,

file,
get: dependency => this._get(path$$1, dependency),
get: dependency => this[_get](path$$1, dependency),
});

@@ -418,7 +510,7 @@ }

}
this._pending.delete(path$$1);
if (!this._status && [...this._pending].every(path$$1 => this._waiting.get(path$$1))) {
this[_pending].delete(path$$1);
if (!this[_status] && [...this[_pending]].every(path$$1 => this[_waiting].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 (let path$$1 of this._available.keys()) if (!this._pending.has(path$$1)) this._found(path$$1);
for (let path$$1 of this[_available].keys()) if (!this[_pending].has(path$$1)) this[_found](path$$1);
}

@@ -428,10 +520,10 @@ }

// run a generator and transform and add the file
async _handleGeneratedFile(path$$1) {
async [_handleGeneratedFile](path$$1) {
let file;
try {
file = new File(path$$1);
await this._generators.get(path$$1)({
await this[_generators].get(path$$1)({
defiler: this,
file,
get: dependency => this._get(path$$1, dependency),
get: dependency => this[_get](path$$1, dependency),
});

@@ -445,35 +537,35 @@ await this.file(file);

// wait for a file to be available and mark another file as depending on it
async _get(dependent, path$$1) {
if (Array.isArray(path$$1)) return Promise.all(path$$1.map(path$$1 => this._get(dependent, path$$1)))
async [_get](dependent, path$$1) {
if (Array.isArray(path$$1)) return Promise.all(path$$1.map(path$$1 => this[_get](dependent, path$$1)))
this.depend(dependent, path$$1);
if (!this._status && !this._files.has(path$$1)) {
this._waiting.set(dependent, (this._waiting.get(dependent) || 0) + 1);
if (this._available.has(path$$1)) {
await this._available.get(path$$1).promise;
if (!this[_status] && !this[_files$1].has(path$$1)) {
this[_waiting].set(dependent, (this[_waiting].get(dependent) || 0) + 1);
if (this[_available].has(path$$1)) {
await this[_available].get(path$$1).promise;
} else {
let resolve;
let promise = new Promise(res => (resolve = res));
this._available.set(path$$1, { promise, resolve });
this[_available].set(path$$1, { promise, resolve });
await promise;
}
this._waiting.set(dependent, this._waiting.get(dependent) - 1);
this[_waiting].set(dependent, this[_waiting].get(dependent) - 1);
}
return this._files.get(path$$1)
return this[_files$1].get(path$$1)
}
// re-process all files that depend on a particular path
_processDependents(path$$1) {
if (!this._status) return
[_processDependents](path$$1) {
if (!this[_status]) return
let dependents = new Set();
for (let [dependent, dependencies] of this._dependents.entries()) {
for (let [dependent, dependencies] of this[_dependents].entries()) {
if (dependencies.has(path$$1)) {
dependents.add(dependent);
this._dependents.delete(dependent);
this[_dependents].delete(dependent);
}
}
for (let dependent of dependents) {
if (this._generators.has(dependent)) {
this._handleGeneratedFile(dependent);
} else if (this._origFiles.has(dependent)) {
this._processFile(this._origFiles.get(dependent));
if (this[_generators].has(dependent)) {
this[_handleGeneratedFile](dependent);
} else if (this[_origFiles].has(dependent)) {
this[_processFile](this[_origFiles].get(dependent));
}

@@ -484,6 +576,6 @@ }

// mark a given awaited file as being found
_found(path$$1) {
if (!this._status && this._available.has(path$$1)) {
this._available.get(path$$1).resolve();
this._available.delete(path$$1);
[_found](path$$1) {
if (!this[_status] && this[_available].has(path$$1)) {
this[_available].get(path$$1).resolve();
this[_available].delete(path$$1);
}

@@ -490,0 +582,0 @@ }

@@ -6,6 +6,13 @@ import { readdir, readFile, stat, watch } from 'fs';

var symbols = new Proxy({}, { get: (_, key) => Symbol(key) })
let { _path, _dir, _filename, _ext, _enc, _bytes, _text } = symbols;
class File {
constructor(path) {
constructor(path = '') {
if (typeof path !== 'string') throw new TypeError('file.path must be a string')
// path of file
this._path = path;
this[_path] = path;
// cached dir/filename/ext values
this[_dir] = this[_filename] = this[_ext] = null;
// all historical paths of file

@@ -15,15 +22,19 @@ this.paths = path ? [path] : [];

this.stat = null;
// encoding
this[_enc] = 'utf8';
// Buffer of file contents
this._bytes = null;
this[_bytes] = null;
// string of file contents
this._text = null;
this[_text] = null;
}
get path() {
return this._path
return this[_path]
}
set path(path) {
if (this._path !== path) {
this._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;
this.paths.push(path);

@@ -33,37 +44,75 @@ }

get dir() {
if (this[_dir] == null) {
let 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) {
let p = this[_path].lastIndexOf('/');
this[_filename] = p > -1 ? this[_path].slice(p + 1) : '';
}
return this[_filename]
}
set filename(filename) {
if (typeof filename !== 'string') throw new TypeError('file.filename must be a string')
let old = this.filename;
this.path = (old ? this[_path].slice(0, -old.length) : this[_path]) + filename;
}
get ext() {
if (this.path && this.path.match) {
let match = this.path.match(/\.[^./\\]+$/);
if (match) return match[0]
if (this[_ext] == null) {
let p1 = this[_path].lastIndexOf('.');
let p2 = this[_path].lastIndexOf('/');
this[_ext] = p1 > -1 && p1 > p2 ? this[_path].slice(p1) : '';
}
return ''
return this[_ext]
}
set ext(ext) {
let oldExt = this.ext;
if (oldExt) {
this.path = this.path.slice(0, -oldExt.length) + ext;
} else {
this.path += ext;
}
if (typeof ext !== 'string') throw new TypeError('file.ext must be a string')
let 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() {
if (this._bytes == null && this._text != null) this._bytes = Buffer.from(this._text);
return this._bytes
return this[_bytes] == null && this[_text] != null
? (this[_bytes] = Buffer.from(this[_text], this[_enc]))
: this[_bytes]
}
set bytes(bytes) {
this._bytes = bytes;
this._text = null;
if (!Buffer.isBuffer(bytes)) throw new TypeError('file.bytes must be a Buffer')
this[_bytes] = bytes;
this[_text] = null;
}
get text() {
if (this._text == null && this._bytes != null) this._text = this._bytes.toString();
return this._text
return this[_text] == null && this[_bytes] != null
? (this[_text] = this[_bytes].toString(this[_enc]))
: this[_text]
}
set text(text) {
this._text = text;
this._bytes = null;
if (typeof text !== 'string') throw new TypeError('file.text must be a string')
this[_text] = text;
this[_bytes] = null;
}

@@ -76,2 +125,16 @@ }

let {
_dir: _dir$1,
_watch,
_debounce,
_dirs,
_files,
_timeouts,
_queue,
_processing,
_recurse,
_handle,
_enqueue,
} = symbols;
class Watcher extends EventEmitter {

@@ -81,17 +144,17 @@ constructor(dir, watch$$1, debounce) {

// directory to recursively watch the contents of
this._dir = dir;
this[_dir$1] = dir;
// whether to actually watch for changes (or just walk and retrieve contents and file stats)
this._watch = watch$$1;
this[_watch] = watch$$1;
// fs.watch event debounce, in milliseconds
this._debounce = debounce;
this[_debounce] = debounce;
// paths of all (recursive) directories -> FSWatcher instances
this._dirs = new Map();
this[_dirs] = new Map();
// paths of all (recursive) files -> file stats
this._files = new Map();
this[_files] = new Map();
// paths of (recursive) files with pending debounced events -> setTimeout timer ids
this._timeouts = new Map();
this[_timeouts] = new Map();
// queue of pending FSWatcher events to handle
this._queue = [];
this[_queue] = [];
// whether some FSWatcher event is currently already in the process of being handled
this._processing = false;
this[_processing] = false;
}

@@ -102,15 +165,15 @@

async init() {
await this._recurse(this._dir);
return [...this._files.entries()].map(([path, stat$$1]) => ({ path, stat: stat$$1 }))
await this[_recurse](this[_dir$1]);
return [...this[_files].entries()].map(([path, stat$$1]) => ({ path, stat: stat$$1 }))
}
// recurse a given directory
async _recurse(full) {
let path = full.slice(this._dir.length + 1);
async [_recurse](full) {
let path = full.slice(this[_dir$1].length + 1);
let fileStat = await stat$1(full);
if (fileStat.isFile()) {
this._files.set(path, fileStat);
this[_files].set(path, fileStat);
} else if (fileStat.isDirectory()) {
if (this._watch) this._dirs.set(path, watch(full, this._handle.bind(this, full)));
await Promise.all((await readdir$1(full)).map(sub => this._recurse(full + '/' + sub)));
if (this[_watch]) this[_dirs].set(path, watch(full, this[_handle].bind(this, full)));
await Promise.all((await readdir$1(full)).map(sub => this[_recurse](full + '/' + sub)));
}

@@ -120,11 +183,11 @@ }

// handle FSWatcher event for given directory
_handle(dir, event, file) {
[_handle](dir, event, file) {
let full = dir + '/' + file;
if (this._timeouts.has(full)) clearTimeout(this._timeouts.get(full));
this._timeouts.set(
if (this[_timeouts].has(full)) clearTimeout(this[_timeouts].get(full));
this[_timeouts].set(
full,
setTimeout(() => {
this._timeouts.delete(full);
this._enqueue(full);
}, this._debounce),
this[_timeouts].delete(full);
this[_enqueue](full);
}, this[_debounce]),
);

@@ -134,9 +197,9 @@ }

// add an FSWatcher event to the queue, and handle queued events
async _enqueue(full) {
this._queue.push(full);
if (this._processing) return
this._processing = true;
while (this._queue.length) {
let full = this._queue.shift();
let path = full.slice(this._dir.length + 1);
async [_enqueue](full) {
this[_queue].push(full);
if (this[_processing]) return
this[_processing] = true;
while (this[_queue].length) {
let full = this[_queue].shift();
let path = full.slice(this[_dir$1].length + 1);
try {

@@ -146,8 +209,8 @@ let fileStat = await stat$1(full);

// note the new/changed file
this._files.set(path, fileStat);
this[_files].set(path, fileStat);
this.emit('', { event: '+', path, stat: fileStat });
} else if (fileStat.isDirectory() && !this._dirs.has(path)) {
} else if (fileStat.isDirectory() && !this[_dirs].has(path)) {
// note the new directory: start watching it, and report any files in it
await this._recurse(full);
for (let [newPath, fileStat] of this._files.entries()) {
await this[_recurse](full);
for (let [newPath, fileStat] of this[_files].entries()) {
if (newPath.startsWith(path + '/')) {

@@ -160,17 +223,17 @@ this.emit('', { event: '+', path: newPath, stat: fileStat });

// probably this was a deleted file/directory
if (this._files.has(path)) {
if (this[_files].has(path)) {
// note the deleted file
this._files.delete(path);
this[_files].delete(path);
this.emit('', { event: '-', path });
} else if (this._dirs.has(path)) {
} else if (this[_dirs].has(path)) {
// note the deleted directory: stop watching it, and report any files that were in it
for (let old of this._dirs.keys()) {
for (let old of this[_dirs].keys()) {
if (old === path || old.startsWith(path + '/')) {
this._dirs.get(old).close();
this._dirs.delete(old);
this[_dirs].get(old).close();
this[_dirs].delete(old);
}
}
for (let old of this._files.keys()) {
for (let old of this[_files].keys()) {
if (old.startsWith(path + '/')) {
this._files.delete(old);
this[_files].delete(old);
this.emit('', { event: '-', path: old });

@@ -182,6 +245,33 @@ }

}
this._processing = false;
this[_processing] = false;
}
}
let {
_origFiles,
_files: _files$1,
_status,
_watchers,
_transforms,
_generators,
_done,
_pending,
_waiting,
_available,
_dependents,
_queue: _queue$1,
_processing: _processing$1,
_checkBeforeExec,
_checkAfterExec,
_wait,
_enqueue: _enqueue$1,
_processPhysicalFile,
_processFile,
_transformFile,
_handleGeneratedFile,
_get,
_processDependents,
_found,
} = symbols;
class Defiler extends EventEmitter {

@@ -191,27 +281,27 @@ constructor() {

// original paths -> original files for all physical files
this._origFiles = new Map();
this[_origFiles] = new Map();
// original paths -> transformed files for all physical/generated/etc. files
this._files = new Map();
this[_files$1] = new Map();
// null = exec not called; false = exec pending; true = exec finished
this._status = null;
this[_status] = null;
// all registered Watcher instances (one per directory)
this._watchers = [];
this[_watchers] = [];
// all registered transforms
this._transforms = [];
this[_transforms] = [];
// paths -> registered generators
this._generators = new Map();
this[_generators] = new Map();
// { count, resolve, reject } object for main promise for the first exec wave
this._done = { count: 0, resolve: null, reject: null };
this[_done] = { count: 0, resolve: null, reject: null };
// original paths of all files currently undergoing some transform/generator
this._pending = new Set();
this[_pending] = new Set();
// original paths -> number of other files they're currently waiting on to exist
this._waiting = new Map();
this[_waiting] = new Map();
// original paths -> { promise, resolve } objects for when awaited files become available
this._available = new Map();
this[_available] = new Map();
// original paths of dependents -> set of original paths of dependencies, specifying changes to which files should trigger re-processing which other files
this._dependents = new Map();
this[_dependents] = new Map();
// queue of pending Watcher events to handle
this._queue = [];
this[_queue$1] = [];
// whether some Watcher event is currently already in the process of being handled
this._processing = false;
this[_processing$1] = false;
}

@@ -222,15 +312,15 @@

get status() {
return this._status
return this[_status]
}
get origFiles() {
return this._origFiles
return this[_origFiles]
}
get files() {
return this._files
return this[_files$1]
}
get origPaths() {
return [...this._origFiles.keys()].sort()
return [...this[_origFiles].keys()].sort()
}

@@ -242,7 +332,8 @@

dir(...dirs) {
this._checkBeforeExec('dir');
for (let { dir, read = true, watch: watch$$1 = true, debounce = 10 } of dirs.filter(Boolean)) {
dir = resolve(dir).replace(/\\/g, '/');
this[_checkBeforeExec]('dir');
for (let config of dirs.filter(Boolean)) {
let { dir, read = true, enc = 'utf8', watch: watch$$1 = true, debounce = 10 } = config;
dir = resolve(dir);
let watcher = new Watcher(dir, watch$$1, debounce);
this._watchers.push({ watcher, dir, read, watch: watch$$1 });
this[_watchers].push({ watcher, dir, read, enc, watch: watch$$1 });
}

@@ -254,4 +345,4 @@ return this

transform(...transforms) {
this._checkBeforeExec('transform');
this._transforms.push(...transforms.filter(Boolean));
this[_checkBeforeExec]('transform');
this[_transforms].push(...transforms.filter(Boolean));
return this

@@ -262,5 +353,5 @@ }

generator(generators) {
this._checkBeforeExec('generator');
this[_checkBeforeExec]('generator');
for (let [path, generator] of Object.entries(generators)) {
if (path && generator) this._generators.set(path, generator);
if (path && generator) this[_generators].set(path, generator);
}

@@ -273,8 +364,8 @@ return this

async exec() {
this._checkBeforeExec('exec');
this._status = false;
this._processing = true;
this[_checkBeforeExec]('exec');
this[_status] = false;
this[_processing$1] = true;
let promise = new Promise((res, rej) => {
this._done.resolve = res;
this._done.reject = rej;
this[_done].resolve = res;
this[_done].reject = rej;
});

@@ -284,24 +375,26 @@ // init all Watcher instances; note that all files have pending transforms

await Promise.all(
this._watchers.map(async ({ watcher, dir, read, watch: watch$$1 }) => {
this[_watchers].map(async ({ watcher, dir, read, enc, watch: watch$$1 }) => {
if (watch$$1) {
watcher.on('', ({ event, path, stat: stat$$1 }) => this._enqueue({ event, dir, path, stat: stat$$1, read }));
watcher.on('', ({ event, path, stat: stat$$1 }) => {
this[_enqueue$1]({ event, dir, path, stat: stat$$1, read, enc });
});
}
for (let { path, stat: stat$$1 } of await watcher.init()) {
files.push({ dir, path, stat: stat$$1, read });
this._origFiles.set(path, null);
this._pending.add(path);
files.push({ dir, path, stat: stat$$1, read, enc });
this[_origFiles].set(path, null);
this[_pending].add(path);
}
}),
);
for (let path of this._generators.keys()) this._pending.add(path);
for (let path of this[_generators].keys()) this[_pending].add(path);
// process each physical file
for (let { dir, path, stat: stat$$1, read } of files) {
this._wait(this._processPhysicalFile(dir, path, stat$$1, read));
for (let { dir, path, stat: stat$$1, read, enc } of files) {
this[_wait](this[_processPhysicalFile](dir, path, stat$$1, read, enc));
}
// process each generated file
for (let path of this._generators.keys()) this._wait(this._handleGeneratedFile(path));
for (let path of this[_generators].keys()) this[_wait](this[_handleGeneratedFile](path));
// wait and finish up
await promise;
this._status = true;
this._processing = false;
this[_status] = true;
this[_processing$1] = false;
}

@@ -313,10 +406,10 @@

async file(file) {
this._checkAfterExec('file');
this[_checkAfterExec]('file');
let { path } = file;
if (!(file instanceof File)) file = Object.assign(new File(), file);
await this._wait(this._transformFile(file));
this._files.set(path, file);
await this[_wait](this[_transformFile](file));
this[_files$1].set(path, file);
this.emit('file', { defiler: this, path, file });
this._found(path);
this._processDependents(path);
this[_found](path);
this[_processDependents](path);
}

@@ -326,7 +419,7 @@

depend(dependent, path) {
this._checkAfterExec('depend');
if (this._dependents.has(dependent)) {
this._dependents.get(dependent).add(path);
this[_checkAfterExec]('depend');
if (this[_dependents].has(dependent)) {
this[_dependents].get(dependent).add(path);
} else {
this._dependents.set(dependent, new Set([path]));
this[_dependents].set(dependent, new Set([path]));
}

@@ -337,15 +430,15 @@ }

_checkBeforeExec(methodName) {
if (this._status !== null) throw new Error(`Cannot call ${methodName} after calling exec`)
[_checkBeforeExec](methodName) {
if (this[_status] !== null) throw new Error(`Cannot call ${methodName} after calling exec`)
}
_checkAfterExec(methodName) {
if (this._status === null) throw new Error(`Cannot call ${methodName} before calling exec`)
[_checkAfterExec](methodName) {
if (this[_status] === null) throw new Error(`Cannot call ${methodName} before calling exec`)
}
// add another promise that must resolve before the initial exec wave can finish
_wait(promise) {
if (!this._status) {
this._done.count++;
promise.then(() => --this._done.count || this._done.resolve(), this._done.reject);
[_wait](promise) {
if (!this[_status]) {
this[_done].count++;
promise.then(() => --this[_done].count || this[_done].resolve(), this[_done].reject);
}

@@ -356,46 +449,45 @@ return promise

// add a Watcher event to the queue, and handle queued events
async _enqueue(event) {
this._queue.push(event);
if (this._processing) return
this._processing = true;
while (this._queue.length) {
let { event, dir, path, stat: stat$$1, read } = this._queue.shift();
async [_enqueue$1](event) {
this[_queue$1].push(event);
if (this[_processing$1]) return
this[_processing$1] = true;
while (this[_queue$1].length) {
let { event, dir, path, stat: stat$$1, read, enc } = this[_queue$1].shift();
if (event === '+') {
await this._processPhysicalFile(dir, path, stat$$1, read);
await this[_processPhysicalFile](dir, path, stat$$1, read, enc);
} else if (event === '-') {
this._origFiles.delete(path);
this._files.delete(path);
this[_origFiles].delete(path);
this[_files$1].delete(path);
this.emit('deleted', { defiler: this, path });
this._processDependents(path);
this[_processDependents](path);
}
}
this._processing = false;
this[_processing$1] = false;
}
// create a file object for a physical file and process it
async _processPhysicalFile(dir, path, stat$$1, read) {
let origFile = new File(path);
origFile.stat = stat$$1;
async [_processPhysicalFile](dir, path, stat$$1, read, enc) {
let origFile = Object.assign(new File(), { path, stat: stat$$1, enc });
if (read) origFile.bytes = await readFile$1(dir + '/' + path);
this._origFiles.set(path, origFile);
this[_origFiles].set(path, origFile);
this.emit('origFile', { defiler: this, file: origFile });
await this._processFile(origFile);
await this[_processFile](origFile);
}
// transform a file, store it, and process dependents
async _processFile(origFile) {
async [_processFile](origFile) {
let file = Object.assign(new File(), origFile);
await this._transformFile(file);
this._files.set(origFile.path, file);
await this[_transformFile](file);
this[_files$1].set(origFile.path, file);
this.emit('file', { defiler: this, path: origFile.path, file });
this._found(origFile.path);
this._processDependents(origFile.path);
this[_found](origFile.path);
this[_processDependents](origFile.path);
}
// perform all transforms on a file
async _transformFile(file) {
async [_transformFile](file) {
let { path } = file;
this._pending.add(path);
this[_pending].add(path);
try {
for (let transform of this._transforms) {
for (let transform of this[_transforms]) {
await transform({

@@ -405,3 +497,3 @@ defiler: this,

file,
get: dependency => this._get(path, dependency),
get: dependency => this[_get](path, dependency),
});

@@ -412,7 +504,7 @@ }

}
this._pending.delete(path);
if (!this._status && [...this._pending].every(path => this._waiting.get(path))) {
this[_pending].delete(path);
if (!this[_status] && [...this[_pending]].every(path => this[_waiting].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 (let path of this._available.keys()) if (!this._pending.has(path)) this._found(path);
for (let path of this[_available].keys()) if (!this[_pending].has(path)) this[_found](path);
}

@@ -422,10 +514,10 @@ }

// run a generator and transform and add the file
async _handleGeneratedFile(path) {
async [_handleGeneratedFile](path) {
let file;
try {
file = new File(path);
await this._generators.get(path)({
await this[_generators].get(path)({
defiler: this,
file,
get: dependency => this._get(path, dependency),
get: dependency => this[_get](path, dependency),
});

@@ -439,35 +531,35 @@ await this.file(file);

// wait for a file to be available and mark another file as depending on it
async _get(dependent, path) {
if (Array.isArray(path)) return Promise.all(path.map(path => this._get(dependent, path)))
async [_get](dependent, path) {
if (Array.isArray(path)) return Promise.all(path.map(path => this[_get](dependent, path)))
this.depend(dependent, path);
if (!this._status && !this._files.has(path)) {
this._waiting.set(dependent, (this._waiting.get(dependent) || 0) + 1);
if (this._available.has(path)) {
await this._available.get(path).promise;
if (!this[_status] && !this[_files$1].has(path)) {
this[_waiting].set(dependent, (this[_waiting].get(dependent) || 0) + 1);
if (this[_available].has(path)) {
await this[_available].get(path).promise;
} else {
let resolve$$1;
let promise = new Promise(res => (resolve$$1 = res));
this._available.set(path, { promise, resolve: resolve$$1 });
this[_available].set(path, { promise, resolve: resolve$$1 });
await promise;
}
this._waiting.set(dependent, this._waiting.get(dependent) - 1);
this[_waiting].set(dependent, this[_waiting].get(dependent) - 1);
}
return this._files.get(path)
return this[_files$1].get(path)
}
// re-process all files that depend on a particular path
_processDependents(path) {
if (!this._status) return
[_processDependents](path) {
if (!this[_status]) return
let dependents = new Set();
for (let [dependent, dependencies] of this._dependents.entries()) {
for (let [dependent, dependencies] of this[_dependents].entries()) {
if (dependencies.has(path)) {
dependents.add(dependent);
this._dependents.delete(dependent);
this[_dependents].delete(dependent);
}
}
for (let dependent of dependents) {
if (this._generators.has(dependent)) {
this._handleGeneratedFile(dependent);
} else if (this._origFiles.has(dependent)) {
this._processFile(this._origFiles.get(dependent));
if (this[_generators].has(dependent)) {
this[_handleGeneratedFile](dependent);
} else if (this[_origFiles].has(dependent)) {
this[_processFile](this[_origFiles].get(dependent));
}

@@ -478,6 +570,6 @@ }

// mark a given awaited file as being found
_found(path) {
if (!this._status && this._available.has(path)) {
this._available.get(path).resolve();
this._available.delete(path);
[_found](path) {
if (!this[_status] && this[_available].has(path)) {
this[_available].get(path).resolve();
this[_available].delete(path);
}

@@ -484,0 +576,0 @@ }

{
"name": "defiler",
"version": "0.11.1",
"version": "0.11.2",
"description": "A small, strange building block",

@@ -5,0 +5,0 @@ "keywords": ["build", "framework", "async", "watch"],

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