UNPKG

prex

Version:

Async coordination primitives and extensions on top of ES6 Promises

134 lines (122 loc) 4.02 kB
const { spawn } = require("child_process"); const { default: chalk } = require("chalk"); const log = require("fancy-log"); const isWindows = /^win/.test(process.platform); /** * @param {number} timeout * @param {() => Promise} action */ function debounce(timeout, action) { /** @type {{ promise: Promise, resolve: (value: any) => void, reject: (value: any) => void }} */ let deferred; let timer; function enqueue() { if (timer) { clearTimeout(timer); timer = undefined; } if (!deferred) { deferred = {}; deferred.promise = new Promise((resolve, reject) => { deferred.resolve = resolve; deferred.reject = reject; }); } timer = setTimeout(run, timeout); return deferred.promise; } function run() { if (timer) { clearTimeout(timer); timer = undefined; } const currentDeferred = deferred; deferred = undefined; try { currentDeferred.resolve(action()); } catch (e) { currentDeferred.reject(e); } } return enqueue; } /** * @param {(projects: readonly string[]) => Promise} action */ function createProjectQueue(action) { /** @type {string[]} */ const projects = []; const debouncer = debounce(100, async () => { const currentProjects = projects.slice(); projects.length = 0; return action(currentProjects); }); /** * @param {string} project */ function enqueue(project) { projects.push(project); return debouncer(); } return enqueue; } /** * @param {string} cmd * @param {string[]} args * @param {object} options * @param {boolean} [options.ignoreExitCode] * @param {boolean} [options.verbose] * @returns {Promise<{exitCode: number}>} */ function exec(cmd, args, { ignoreExitCode, verbose } = {}) { return new Promise((resolve, reject) => { const shell = isWindows ? "cmd" : "/bin/sh"; const shellArgs = isWindows ? ["/c", cmd.includes(" ") ? `"${cmd}"` : cmd, ...args] : ["-c", `${cmd} ${args.join(" ")}`]; if (verbose) log(`> ${chalk.green(cmd)} ${args.join(" ")}`); const child = spawn(shell, shellArgs, { stdio: "inherit", windowsVerbatimArguments: true }); child.on("exit", (exitCode) => { child.removeAllListeners(); if (exitCode === 0 || ignoreExitCode) { resolve({ exitCode }); } else { reject(new Error(`Process exited with code: ${exitCode}`)); } }); child.on("error", error => { child.removeAllListeners(); reject(error); }); }); } exports.exec = exec; const buildProject = createProjectQueue(async projects => { await exec(process.execPath, [require.resolve("typescript/lib/tsc.js"), "-b", ...projects]); }); const forceBuildProject = createProjectQueue(async projects => { await exec(process.execPath, [require.resolve("typescript/lib/tsc.js"), "-b", "--force", ...projects]); }); /** * Build a project. * @param {string} project * @param {object} options * @param {boolean} [options.force] */ exports.buildProject = (project, {force} = {}) => force ? forceBuildProject(project) : buildProject(project); /** * Clean a project's outputs. * @param {string} project */ exports.cleanProject = createProjectQueue(async projects => { await exec(process.execPath, [require.resolve("typescript/lib/tsc.js"), "-b", "--clean", ...projects]); }); /** * Watch a project for changes. * @param {string} project */ exports.watchProject = createProjectQueue(async projects => { await exec(process.execPath, [require.resolve("typescript/lib/tsc.js"), "-b", "--watch", ...projects]); });