You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
7.8 KiB
JavaScript

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const tree_kill_1 = __importDefault(require("tree-kill"));
const path_1 = require("path");
const cross_spawn_1 = require("cross-spawn");
const chokidar_1 = __importDefault(require("chokidar"));
const deque_1 = require("@blakeembrey/deque");
const template_1 = require("@blakeembrey/template");
const ECHO_JS_PATH = path_1.resolve(__dirname, "echo.js");
const ECHO_CMD = `${quote(process.execPath)} ${quote(ECHO_JS_PATH)}`;
/**
* Execute a "job" on each change event.
*/
class Job {
constructor(log, command, outpipe) {
this.log = log;
this.command = command;
this.outpipe = outpipe;
}
start(cwd, stdin, stdout, stderr, env, onexit) {
var _a, _b;
if (this.outpipe) {
const stdio = [null, stdout, stderr];
this.log(`executing outpipe "${this.outpipe}"`);
this.childOutpipe = cross_spawn_1.spawn(this.outpipe, { cwd, env, stdio, shell: true });
this.childOutpipe.on("exit", (code, signal) => {
this.log(`outpipe ${exitMessage(code, signal)}`);
this.childOutpipe = undefined;
if (!this.childCommand)
return onexit();
});
}
if (this.command.length) {
const stdio = [
stdin,
// Use `--outpipe` when specified, otherwise direct to `stdout`.
this.childOutpipe ? this.childOutpipe.stdin : stdout,
stderr,
];
this.log(`executing command "${this.command.join(" ")}"`);
this.childCommand = cross_spawn_1.spawn(this.command[0], this.command.slice(1), {
cwd,
env,
stdio,
});
this.childCommand.on("exit", (code, signal) => {
var _a, _b;
this.log(`command ${exitMessage(code, signal)}`);
this.childCommand = undefined;
if (!this.childOutpipe)
return onexit();
return (_b = (_a = this.childOutpipe) === null || _a === void 0 ? void 0 : _a.stdin) === null || _b === void 0 ? void 0 : _b.end();
});
}
else {
// No data to write to `outpipe`.
(_b = (_a = this.childOutpipe) === null || _a === void 0 ? void 0 : _a.stdin) === null || _b === void 0 ? void 0 : _b.end();
}
}
kill(killSignal) {
if (this.childOutpipe) {
this.log(`killing outpipe ${this.childOutpipe.pid}`);
tree_kill_1.default(this.childOutpipe.pid, killSignal);
}
if (this.childCommand) {
this.log(`killing command ${this.childCommand.pid}`);
tree_kill_1.default(this.childCommand.pid, killSignal);
}
}
}
/**
* Onchange manages
*/
function onchange(options) {
const { matches } = options;
const onReady = options.onReady || (() => undefined);
const initial = !!options.initial;
const kill = !!options.kill;
const cwd = options.cwd ? path_1.resolve(options.cwd) : process.cwd();
const stdin = options.stdin || process.stdin;
const stdout = options.stdout || process.stdout;
const stderr = options.stderr || process.stderr;
const env = options.env || process.env;
const delay = Math.max(options.delay || 0, 0);
const jobs = Math.max(options.jobs || 0, 1);
const killSignal = options.killSignal || "SIGTERM";
const command = options.command
? options.command.map((arg) => template_1.template(arg))
: [];
const outpipe = options.outpipe
? outpipeTemplate(options.outpipe)
: undefined;
const filter = options.filter || [];
const running = new Set();
const queue = new deque_1.Deque();
// Logging.
const log = options.verbose
? function log(message) {
stdout.write(`onchange: ${message}\n`);
}
: function () { };
// Invalid, nothing to run on change.
if (command.length === 0 && !outpipe) {
throw new TypeError('Expected "command" and/or "outpipe" to be specified');
}
const ignored = options.exclude || [];
const ignoreInitial = options.add !== true;
const usePolling = options.poll !== undefined;
const interval = options.poll !== undefined ? options.poll : undefined;
const awaitWriteFinish = options.awaitWriteFinish
? { stabilityThreshold: options.awaitWriteFinish }
: undefined;
// Add default excludes to the ignore list.
if (options.defaultExclude !== false) {
ignored.push("**/node_modules/**", "**/.git/**");
}
// Create the "watcher" instance for file system changes.
const watcher = chokidar_1.default.watch(matches, {
cwd,
ignored,
ignoreInitial,
usePolling,
interval,
awaitWriteFinish,
});
/**
* Try and dequeue the next job to run.
*/
function dequeue() {
// Nothing to process.
if (queue.size === 0)
return;
// Too many jobs running already.
if (running.size >= jobs)
return;
// Remove first job from queue (FIFO).
const job = queue.popLeft();
// Add job to running set.
running.add(job);
// Start the process and remove when finished.
job.start(cwd, stdin, stdout, stderr, env, () => {
running.delete(job);
if (delay > 0)
return setTimeout(dequeue, delay);
return dequeue();
});
}
/**
* Enqueue the next change event to run.
*/
function enqueue(event, file) {
const fileExt = path_1.extname(file);
const state = {
event,
changed: file,
file,
fileExt,
fileBase: path_1.basename(file),
fileBaseNoExt: path_1.basename(file, fileExt),
fileDir: path_1.dirname(file),
};
// Kill all existing tasks on `enqueue`.
if (kill) {
queue.clear(); // Remove pending ("killed") tasks.
running.forEach((child) => child.kill(killSignal)); // Kill running tasks.
}
// Log the event and the file affected.
log(`${file}: ${event}`);
// Add item to job queue.
queue.push(new Job(log, command.map((arg) => arg(state)), outpipe === null || outpipe === void 0 ? void 0 : outpipe(state)));
// Try to immediately run the enqueued job.
return dequeue();
}
// Execute initial event without any changes.
if (initial)
enqueue("", "");
// For any change, creation or deletion, try to run.
watcher.on("all", (event, file) => {
if (filter.length && filter.indexOf(event) === -1)
return;
return enqueue(event, file);
});
// On ready, prepare triggers.
watcher.on("ready", () => {
log(`watcher ready`);
// Notify external listener of "ready" event.
return onReady();
});
watcher.on("error", (error) => log(`watcher error: ${error}`));
// Return a close function.
return () => watcher.close();
}
exports.onchange = onchange;
// Template generator for `outpipe` option.
function outpipeTemplate(str) {
var value = str.trim();
if (value.charAt(0) === "|" || value.charAt(0) === ">") {
return template_1.template(`${ECHO_CMD} ${value}`);
}
return template_1.template(value);
}
// Simple exit message generator.
function exitMessage(code, signal) {
return code === null ? `exited with ${signal}` : `completed with ${code}`;
}
/**
* Quote value for `exec`.
*/
function quote(str) {
return `"${str.replace(/["\\$`!]/g, "\\$&")}"`;
}
//# sourceMappingURL=index.js.map