UNPKG

7.96 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const tree_kill_1 = __importDefault(require("tree-kill"));
7const path_1 = require("path");
8const cross_spawn_1 = require("cross-spawn");
9const chokidar_1 = __importDefault(require("chokidar"));
10const deque_1 = require("@blakeembrey/deque");
11const template_1 = require("@blakeembrey/template");
12const ECHO_JS_PATH = path_1.resolve(__dirname, "echo.js");
13const ECHO_CMD = `${quote(process.execPath)} ${quote(ECHO_JS_PATH)}`;
14/**
15 * Execute a "job" on each change event.
16 */
17class Job {
18 constructor(log, command, outpipe) {
19 this.log = log;
20 this.command = command;
21 this.outpipe = outpipe;
22 }
23 start(cwd, stdin, stdout, stderr, env, onexit) {
24 var _a, _b;
25 if (this.outpipe) {
26 const stdio = [null, stdout, stderr];
27 this.log(`executing outpipe "${this.outpipe}"`);
28 this.childOutpipe = cross_spawn_1.spawn(this.outpipe, { cwd, env, stdio, shell: true });
29 this.childOutpipe.on("exit", (code, signal) => {
30 this.log(`outpipe ${exitMessage(code, signal)}`);
31 this.childOutpipe = undefined;
32 if (!this.childCommand)
33 return onexit();
34 });
35 }
36 if (this.command.length) {
37 const stdio = [
38 stdin,
39 // Use `--outpipe` when specified, otherwise direct to `stdout`.
40 this.childOutpipe ? this.childOutpipe.stdin : stdout,
41 stderr,
42 ];
43 this.log(`executing command "${this.command.join(" ")}"`);
44 this.childCommand = cross_spawn_1.spawn(this.command[0], this.command.slice(1), {
45 cwd,
46 env,
47 stdio,
48 });
49 this.childCommand.on("exit", (code, signal) => {
50 var _a, _b;
51 this.log(`command ${exitMessage(code, signal)}`);
52 this.childCommand = undefined;
53 if (!this.childOutpipe)
54 return onexit();
55 return (_b = (_a = this.childOutpipe) === null || _a === void 0 ? void 0 : _a.stdin) === null || _b === void 0 ? void 0 : _b.end();
56 });
57 }
58 else {
59 // No data to write to `outpipe`.
60 (_b = (_a = this.childOutpipe) === null || _a === void 0 ? void 0 : _a.stdin) === null || _b === void 0 ? void 0 : _b.end();
61 }
62 }
63 kill(killSignal) {
64 if (this.childOutpipe) {
65 this.log(`killing outpipe ${this.childOutpipe.pid}`);
66 tree_kill_1.default(this.childOutpipe.pid, killSignal);
67 }
68 if (this.childCommand) {
69 this.log(`killing command ${this.childCommand.pid}`);
70 tree_kill_1.default(this.childCommand.pid, killSignal);
71 }
72 }
73}
74/**
75 * Onchange manages
76 */
77function onchange(options) {
78 const { matches } = options;
79 const onReady = options.onReady || (() => undefined);
80 const initial = !!options.initial;
81 const kill = !!options.kill;
82 const cwd = options.cwd ? path_1.resolve(options.cwd) : process.cwd();
83 const stdin = options.stdin || process.stdin;
84 const stdout = options.stdout || process.stdout;
85 const stderr = options.stderr || process.stderr;
86 const env = options.env || process.env;
87 const delay = Math.max(options.delay || 0, 0);
88 const jobs = Math.max(options.jobs || 0, 1);
89 const killSignal = options.killSignal || "SIGTERM";
90 const command = options.command
91 ? options.command.map((arg) => template_1.template(arg))
92 : [];
93 const outpipe = options.outpipe
94 ? outpipeTemplate(options.outpipe)
95 : undefined;
96 const filter = options.filter || [];
97 const running = new Set();
98 const queue = new deque_1.Deque();
99 // Logging.
100 const log = options.verbose
101 ? function log(message) {
102 stdout.write(`onchange: ${message}\n`);
103 }
104 : function () { };
105 // Invalid, nothing to run on change.
106 if (command.length === 0 && !outpipe) {
107 throw new TypeError('Expected "command" and/or "outpipe" to be specified');
108 }
109 const ignored = options.exclude || [];
110 const ignoreInitial = options.add !== true;
111 const usePolling = options.poll !== undefined;
112 const interval = options.poll !== undefined ? options.poll : undefined;
113 const awaitWriteFinish = options.awaitWriteFinish
114 ? { stabilityThreshold: options.awaitWriteFinish }
115 : undefined;
116 // Add default excludes to the ignore list.
117 if (options.defaultExclude !== false) {
118 ignored.push("**/node_modules/**", "**/.git/**");
119 }
120 // Create the "watcher" instance for file system changes.
121 const watcher = chokidar_1.default.watch(matches, {
122 cwd,
123 ignored,
124 ignoreInitial,
125 usePolling,
126 interval,
127 awaitWriteFinish,
128 });
129 /**
130 * Try and dequeue the next job to run.
131 */
132 function dequeue() {
133 // Nothing to process.
134 if (queue.size === 0)
135 return;
136 // Too many jobs running already.
137 if (running.size >= jobs)
138 return;
139 // Remove first job from queue (FIFO).
140 const job = queue.popLeft();
141 // Add job to running set.
142 running.add(job);
143 // Start the process and remove when finished.
144 job.start(cwd, stdin, stdout, stderr, env, () => {
145 running.delete(job);
146 if (delay > 0)
147 return setTimeout(dequeue, delay);
148 return dequeue();
149 });
150 }
151 /**
152 * Enqueue the next change event to run.
153 */
154 function enqueue(event, file) {
155 const fileExt = path_1.extname(file);
156 const state = {
157 event,
158 changed: file,
159 file,
160 fileExt,
161 fileBase: path_1.basename(file),
162 fileBaseNoExt: path_1.basename(file, fileExt),
163 fileDir: path_1.dirname(file),
164 };
165 // Kill all existing tasks on `enqueue`.
166 if (kill) {
167 queue.clear(); // Remove pending ("killed") tasks.
168 running.forEach((child) => child.kill(killSignal)); // Kill running tasks.
169 }
170 // Log the event and the file affected.
171 log(`${file}: ${event}`);
172 // Add item to job queue.
173 queue.push(new Job(log, command.map((arg) => arg(state)), outpipe === null || outpipe === void 0 ? void 0 : outpipe(state)));
174 // Try to immediately run the enqueued job.
175 return dequeue();
176 }
177 // Execute initial event without any changes.
178 if (initial)
179 enqueue("", "");
180 // For any change, creation or deletion, try to run.
181 watcher.on("all", (event, file) => {
182 if (filter.length && filter.indexOf(event) === -1)
183 return;
184 return enqueue(event, file);
185 });
186 // On ready, prepare triggers.
187 watcher.on("ready", () => {
188 log(`watcher ready`);
189 // Notify external listener of "ready" event.
190 return onReady();
191 });
192 watcher.on("error", (error) => log(`watcher error: ${error}`));
193 // Return a close function.
194 return () => watcher.close();
195}
196exports.onchange = onchange;
197// Template generator for `outpipe` option.
198function outpipeTemplate(str) {
199 var value = str.trim();
200 if (value.charAt(0) === "|" || value.charAt(0) === ">") {
201 return template_1.template(`${ECHO_CMD} ${value}`);
202 }
203 return template_1.template(value);
204}
205// Simple exit message generator.
206function exitMessage(code, signal) {
207 return code === null ? `exited with ${signal}` : `completed with ${code}`;
208}
209/**
210 * Quote value for `exec`.
211 */
212function quote(str) {
213 return `"${str.replace(/["\\$`!]/g, "\\$&")}"`;
214}
215//# sourceMappingURL=index.js.map
\No newline at end of file