#!/usr/bin/env node // Run "tsc" with watch, upon successful compilation run mocha tests. const chalk = require("chalk"); const spawn = require("cross-spawn"); const readline = require("readline"); const yargs = require("yargs"); const fs = require("fs"); const argv = yargs .options({ p: { alias: "project", demand: false, default: ".", describe: "Path to tsconfig file or directory containing tsconfig, passed to `tsc -p `.", type: "string", }, t: { alias: "tsc", demand: false, default: "tsc", describe: "Path to executable tsc, by default points to typescript installed as dependency, then to global tsc installation.", type: "string", }, o: { alias: "opts", demand: false, default: "./test/mocha.opts", describe: "Path to mocha.opts file containing additional mocha configuration.", type: "string", }, m: { alias: "mocha", demand: false, default: "mocha", describe: "Path to executable mocha, by default points to mocha installed as dependency, then to global mocha installation.", type: "string", }, n: { alias: "times", demand: false, default: undefined, describe: "Number of iterations before the watcher process quits. For testing purposes only.", type: "number", }, g: { alias: "grep", demand: false, default: undefined, describe: "Passed down to mocha: only run tests matching ", type: "string", }, f: { alias: "fgrep", demand: false, default: undefined, describe: "Passed down to mocha: only run tests containing ", type: "string", }, }) .help("h") .alias("h", "help") .argv; const stdl = readline.createInterface({ input: process.stdin }); stdl.on("line", (line) => { // TODO: handle "g " or "f " to run mocha with pattern // Ctrl + R may restart mocha test run? }); let mochap = null; let errors = 0; function compilationStarted() { if (mochap) { mochap.kill("SIGINT"); } mochap = null; errors = 0; } function foundErrors() { errors ++; } function compilationComplete() { if (errors) { console.log(chalk.red("TS errors!")); return; } else { console.log(chalk.gray("Run mocha.")); } const mochaOptions = ["--colors"].concat(argv._); if (argv.opts && fs.existsSync(argv.opts)) { mochaOptions.push("--opts"); mochaOptions.push(argv.opts); } if (argv.g) { mochaOptions.push("-g"); mochaOptions.push(argv.g); } if (argv.f) { mochaOptions.push("-f"); mochaOptions.push(argv.f); } mochap = spawn(argv.mocha, mochaOptions); mochap.on("close", (code) => { if (mochap) { if (code) { console.log(chalk.red("Exited with " + code)); } else { } if (argv.times && (times >= argv.times)) { tscp.kill("SIGINT"); } mochap = null; } }); mochap.stdout.on("data", (chunk) => { // Ensure old processes won't interfere tsc, .pipe here may be good enough. if (mochap) { process.stdout.write(chunk); } }); mochap.stderr.on("data", (chunk) => { // Ensure old processes won't interfere tsc, .pipe here may be good enough. if (mochap) { process.stderr.write(chunk); } }); } const tscp = spawn(argv.tsc, ["-p", argv.project, "-w"]); const tscl = readline.createInterface({ input: tscp.stdout }); let times = 0; tscl.on("line", (line) => { if (line.indexOf("Compilation complete.") >= 0 || line.indexOf("Found ") >= 0) { console.log(line); times++; compilationComplete(); } else if (line.indexOf("File change detected.") >= 0) { compilationStarted(); console.log(line); } else if (line.indexOf(": error TS") >= 0) { console.log(line); foundErrors(); } }); tscl.on("close", () => { stdl.close(); tscl.close(); });