UNPKG

typescript-assistant

Version:

Combines and integrates professional Typescript tools into your project

176 lines (156 loc) 5.1 kB
import * as fs from "fs"; import { parallelLimit } from "async"; import * as glob from "glob"; import { Bus } from "./bus"; import { Logger } from "./logger"; import { Task, TaskRunner } from "./taskrunner"; import { absolutePath } from "./util"; export interface Compiler { start(configs?: string[]): void; stop(): void; runOnce(tscArgs: string[], disabledProjects?: string[]): Promise<boolean>; } type TaskFunctionCallback = () => void; type TaskFunction = (callback: TaskFunctionCallback) => void; let runningTasks: Task[] = []; export function createCompiler(dependencies: { taskRunner: TaskRunner; logger: Logger; bus: Bus; }): Compiler { const { taskRunner, logger, bus } = dependencies; let busyCompilers = 0; let errors: string[] = []; function handleOutput(line: string) { if (/Starting incremental compilation...$/.test(line)) { if (busyCompilers === 0) { bus.report({ tool: "compiler", status: "busy" }); errors = []; } busyCompilers++; logger.log("compiler", "compiling..."); bus.signal("compile-started"); } else if (/Watching for file changes.$/.test(line)) { busyCompilers--; logger.log("compiler", `ready, found ${errors.length} errors`); bus.signal(errors.length === 0 ? "compile-compiled" : "compile-errored"); if (busyCompilers === 0) { bus.report({ tool: "compiler", status: "ready", errors: errors.length, }); } } else { let matches = /([^(]+)\((\d+),(\d+)\): (error TS\d+: )?(.*)$/.exec(line); if (matches) { errors.push(matches[0]); logger.log( "compiler", `${absolutePath(matches[1])}:${matches[2]}:${matches[3]} ${ matches[5] }` ); } else { matches = /error TS\d+: (.+)$/.exec(line); if (matches) { errors.push(matches[1]); logger.log("compiler", `${matches[1]}`); } else { // just echo the output logger.log("compiler", line); } } } return true; } let taskFunctions: TaskFunction[] = []; return { runOnce(tscArgs, disabledProjects = []) { return new Promise((resolve, reject) => { glob( "**/tsconfig.json", { ignore: "**/node_modules/**" }, (error: Error | null, tsConfigFiles: string[]) => { if (error) { reject(error); } tsConfigFiles = tsConfigFiles .filter((file) => { return !disabledProjects.includes(file.split("/")[0]); }) .sort((a, b) => { if (a.includes("test") && !b.includes("test")) { return 1; } if (!a.includes("test") && b.includes("test")) { return -1; } return 0; }); let groupedConfigs = tsConfigFiles.reduce((result, file) => { let key = file.replace("/test", ""); result[key] ??= []; result[key].push(file); return result; }, <Record<string, string[]>>{}); Object.values(groupedConfigs).forEach((files) => { let args = ["--build", ...files]; let taskFunction = (callback: TaskFunctionCallback) => { let task = taskRunner.runTask("./node_modules/.bin/tsc", args, { name: `tsc --build ${files.join(" ")}`, logger, handleOutput, }); runningTasks.push(task); task.result .then(() => { runningTasks.splice(runningTasks.indexOf(task), 1); }) .then(callback) .catch(reject); }; taskFunctions.push(taskFunction); }); let limit = 2; parallelLimit(taskFunctions, limit, resolve); } ); }); }, /** * Watching all tsconfig.json files has proven to cost too much CPU. */ start(tsConfigFiles = ["./tsconfig.json", "./src/tsconfig.json"]) { tsConfigFiles = tsConfigFiles.map((config) => config.replace(/\\\\/g, "/") ); tsConfigFiles.forEach((tsconfigFile) => { if (!fs.existsSync(tsconfigFile)) { throw new Error(`File does not exist: ${tsconfigFile}`); } }); let task = taskRunner.runTask( "./node_modules/.bin/tsc", ["-b", ...tsConfigFiles, "--watch", "--preserveWatchOutput"], { name: `tsc --build ${tsConfigFiles.join(" ")} --watch`, logger, handleOutput, } ); runningTasks.push(task); busyCompilers++; task.result.catch((err) => { logger.error("compiler", err.message); process.exit(1); }); }, stop() { runningTasks.forEach((task) => { task.kill(); }); runningTasks = []; }, }; }