Files
talk/scripts/watcher/LongRunningExecutor.ts
Vinh 3bfcc509d2 [CORL-678] Transition to eslint (#2634)
* chore: setup eslint

* chore: tslint checks with types & check for import order

* chore: complete eslint transition

* fix: tests

* fix: linting after rebase, faster lint for lint-staged

* chore: remove line

* fix: lint rules

* feat: add a11y linter and fix errors

* fix: tests
2019-10-15 22:56:38 +00:00

117 lines
2.7 KiB
TypeScript

import chalk from "chalk";
import { ChildProcess } from "child_process";
import spawn from "cross-spawn";
import { Cancelable, debounce } from "lodash";
import psTree from "pstree.remy";
import { Executor } from "./types";
interface LongRunningExecutorOptions {
args?: ReadonlyArray<string>;
/** Specify the period in which the process is restarted at max once. */
debounce?: number;
}
export default class LongRunningExecutor implements Executor {
private cmd: string;
private args?: ReadonlyArray<string>;
private process: ChildProcess | null = null;
private isRunning = false;
private shouldRestart = false;
private restartDebounced: (() => void) & Cancelable;
constructor(cmd: string, opts: LongRunningExecutorOptions = {}) {
this.cmd = cmd;
this.args = opts.args;
this.restartDebounced = debounce(
() => this.restart(),
opts.debounce || 500
);
}
private spawnProcess() {
this.isRunning = true;
this.process = spawn(this.cmd, this.args as string[], {
stdio: "inherit",
shell: !this.args,
});
this.process.on("exit", (code: number) => {
this.isRunning = false;
if (code !== 0 && code !== null) {
// eslint-disable-next-line no-console
console.log(chalk.red(`Command exited with ${code}`));
return;
}
if (this.shouldRestart) {
this.shouldRestart = false;
this.spawnProcess();
}
});
}
private restart() {
this.shouldRestart = true;
return this.internalKill();
}
private kill() {
this.shouldRestart = false;
return this.internalKill();
}
private async internalKill(): Promise<void> {
return new Promise<void>((resolve, reject) => {
const signal = "SIGTERM";
if (process.platform === "win32") {
// Force kill (/F) the whole child tree (/T) by PID
spawn.sync("taskkill", [
"/pid",
this.process!.pid.toString(),
"/T",
"/F",
]);
resolve();
return;
}
psTree(this.process!.pid, (err, kids) => {
if (err) {
reject(err);
}
if (kids) {
spawn.sync("kill", [
`-${signal}`,
this.process!.pid.toString(),
...kids,
]);
}
resolve();
});
});
}
// This is called before watching starts.
public onInit(): void {
this.spawnProcess();
}
// This is called before exiting.
public async onCleanup() {
this.restartDebounced.cancel();
if (this.isRunning) {
await this.kill();
}
}
public execute(filePath: string) {
if (this.isRunning) {
this.restartDebounced();
return;
}
this.spawnProcess();
}
}