2019-02-21 17:55:42 +00:00
|
|
|
import * as cp from "child_process";
|
2019-03-07 02:59:43 +00:00
|
|
|
import {field, Logger, logger, time} from "@coder/logger";
|
2019-02-21 17:55:42 +00:00
|
|
|
|
|
|
|
export interface CommandResult {
|
|
|
|
readonly exitCode: number;
|
|
|
|
readonly stdout: string;
|
|
|
|
readonly stderr: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const execute = (command: string, args: string[] = [], options: cp.SpawnOptions, logger: Logger): Promise<CommandResult> => {
|
|
|
|
let resolve: (result: CommandResult) => void;
|
2019-03-07 02:59:43 +00:00
|
|
|
const prom = new Promise<CommandResult>((res): void => {
|
|
|
|
resolve = res;
|
|
|
|
});
|
2019-02-21 17:55:42 +00:00
|
|
|
|
|
|
|
const stdout: string[] = [];
|
|
|
|
const stderr: string[] = [];
|
|
|
|
const complete = (exitCode: number): void => {
|
|
|
|
resolve({
|
|
|
|
stderr: stderr.join(""),
|
|
|
|
stdout: stdout.join(""),
|
|
|
|
exitCode,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
logger.info(`Executing '${command} ${JSON.stringify(args)}'`, field("options", options));
|
|
|
|
const proc = cp.spawn(command, args.length > 0 ? args : [], options);
|
|
|
|
proc.on("close", (code) => {
|
|
|
|
complete(code);
|
|
|
|
});
|
|
|
|
proc.on("exit", (code) => {
|
|
|
|
complete(code!);
|
|
|
|
});
|
|
|
|
proc.stdout.on("data", (d) => {
|
|
|
|
stdout.push(d.toString());
|
|
|
|
logger.debug("stdio", field("stdout", d.toString()));
|
|
|
|
});
|
|
|
|
proc.stderr.on("data", (d) => {
|
|
|
|
stderr.push(d.toString());
|
|
|
|
logger.debug("stdio", field("stderr", d.toString()));
|
|
|
|
});
|
|
|
|
|
|
|
|
return prom;
|
|
|
|
};
|
|
|
|
|
2019-03-07 00:15:52 +00:00
|
|
|
// tslint:disable-next-line no-any
|
|
|
|
export type TaskFunction = (runner: Runner, ...args: any[]) => void | Promise<void>;
|
2019-02-21 17:55:42 +00:00
|
|
|
|
|
|
|
export interface Runner {
|
|
|
|
cwd: string;
|
2019-03-07 02:59:43 +00:00
|
|
|
|
2019-02-21 17:55:42 +00:00
|
|
|
execute(command: string, args?: string[], env?: object): Promise<CommandResult>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Task {
|
|
|
|
readonly name: string;
|
|
|
|
readonly func: TaskFunction;
|
|
|
|
}
|
|
|
|
|
|
|
|
const tasks = new Map<string, Task>();
|
|
|
|
const activated = new Map<string, Promise<void>>();
|
|
|
|
|
|
|
|
export const register = (name: string, func: TaskFunction): () => void | Promise<void> => {
|
|
|
|
if (tasks.has(name)) {
|
|
|
|
throw new Error(`Task "${name}" already registered`);
|
|
|
|
}
|
|
|
|
|
|
|
|
tasks.set(name, {
|
|
|
|
name,
|
|
|
|
func,
|
|
|
|
});
|
|
|
|
|
|
|
|
return (): void | Promise<void> => {
|
|
|
|
return run(name);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export const run = (name: string = process.argv[2]): void | Promise<void> => {
|
|
|
|
const task = tasks.get(name);
|
|
|
|
if (!task) {
|
|
|
|
logger.error("Task not found.", field("name", name), field("available", Array.from(tasks.keys())));
|
|
|
|
|
|
|
|
return process.exit(1);
|
|
|
|
}
|
|
|
|
if (activated.has(name)) {
|
|
|
|
return activated.get(name);
|
|
|
|
}
|
|
|
|
let cwd: string = process.cwd();
|
|
|
|
const log = logger.named(name);
|
|
|
|
const timer = time(Number.MAX_SAFE_INTEGER);
|
2019-02-28 20:04:19 +00:00
|
|
|
let outputTimer: NodeJS.Timer | undefined;
|
2019-02-21 17:55:42 +00:00
|
|
|
log.info("Starting...");
|
|
|
|
const prom = task.func({
|
|
|
|
set cwd(path: string) {
|
|
|
|
cwd = path;
|
|
|
|
},
|
|
|
|
execute(command: string, args: string[] = [], env?: object): Promise<CommandResult> {
|
2019-03-07 02:59:43 +00:00
|
|
|
const prom = execute(command, args, {
|
2019-02-21 17:55:42 +00:00
|
|
|
cwd,
|
|
|
|
env: env as NodeJS.ProcessEnv,
|
|
|
|
}, log);
|
2019-03-07 02:59:43 +00:00
|
|
|
|
|
|
|
return prom.then((result: CommandResult) => {
|
|
|
|
if (result.exitCode != 0) {
|
|
|
|
log.error("failed",
|
|
|
|
field("exitCode", result.exitCode),
|
|
|
|
field("stdout", result.stdout),
|
|
|
|
field("stderr", result.stderr)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
});
|
2019-02-21 17:55:42 +00:00
|
|
|
},
|
2019-03-07 00:15:52 +00:00
|
|
|
}, ...process.argv.slice(3));
|
2019-02-21 17:55:42 +00:00
|
|
|
|
|
|
|
if (prom) {
|
|
|
|
activated.set(name, prom);
|
2019-02-28 20:04:19 +00:00
|
|
|
|
|
|
|
const doOutput = (): void => {
|
|
|
|
outputTimer = setTimeout(() => {
|
|
|
|
log.info("Still running...");
|
|
|
|
doOutput();
|
|
|
|
}, 60 * 1000 * 5);
|
|
|
|
};
|
|
|
|
doOutput();
|
|
|
|
|
2019-02-21 17:55:42 +00:00
|
|
|
prom.then(() => {
|
2019-02-28 20:04:19 +00:00
|
|
|
if (outputTimer) {
|
|
|
|
clearTimeout(outputTimer);
|
|
|
|
}
|
2019-02-21 17:55:42 +00:00
|
|
|
log.info("Completed!", field("time", timer));
|
|
|
|
}).catch((ex) => {
|
|
|
|
activated.delete(name);
|
|
|
|
log.error(`Failed: ${ex.message}`);
|
|
|
|
log.error(`Stack: ${ex.stack}`);
|
|
|
|
|
|
|
|
return process.exit(1);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return prom;
|
|
|
|
};
|