diff --git a/package-lock.json b/package-lock.json index 9350e46..2a0a7b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typescript-assistant", - "version": "0.52.1", + "version": "0.52.2-tsc-build-attempt-2.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "typescript-assistant", - "version": "0.52.1", + "version": "0.52.2-tsc-build-attempt-2.3", "license": "MIT", "dependencies": { "@types/chai": "4.2.21", @@ -14,6 +14,7 @@ "@types/prettier": "2.3.2", "@typescript-eslint/eslint-plugin": "4.30.0", "@typescript-eslint/parser": "4.30.0", + "async": "3.2.1", "chai": "4.3.4", "chokidar": "3.5.2", "eslint": "7.32.0", @@ -993,6 +994,11 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -5265,6 +5271,11 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, + "async": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", diff --git a/package.json b/package.json index 462d76a..9bdf07e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typescript-assistant", - "version": "0.52.1", + "version": "0.52.2-tsc-build-attempt-2.3", "description": "Combines and integrates professional Typescript tools into your project", "main": "dist/index.js", "bin": { @@ -79,6 +79,7 @@ "@types/prettier": "2.3.2", "@typescript-eslint/eslint-plugin": "4.30.0", "@typescript-eslint/parser": "4.30.0", + "async": "3.2.1", "chai": "4.3.4", "chokidar": "3.5.2", "eslint": "7.32.0", diff --git a/src/commands/assist.ts b/src/commands/assist.ts index 33249a7..1047d4b 100644 --- a/src/commands/assist.ts +++ b/src/commands/assist.ts @@ -5,6 +5,9 @@ export interface AssistOptions { statusServerPort?: number; format?: boolean; coverage?: boolean; + projects?: string[]; + testConfig?: string; + testsGlob?: string; } export function createAssistCommand( @@ -14,7 +17,13 @@ export function createAssistCommand( return { execute(options: AssistOptions = {}) { - const { format = true, coverage = true } = options; + const { + format = true, + coverage = true, + projects, + testConfig, + testsGlob, + } = options; watcher.watchSourceFileChanged(); if (options.statusServerPort) { @@ -26,8 +35,8 @@ export function createAssistCommand( } else { linter.start("source-files-changed", true); } - nyc.start(["source-files-changed"], coverage); - compiler.start(); + nyc.start(["source-files-changed"], coverage, testConfig, testsGlob); + compiler.start(projects); return Promise.resolve(true); }, diff --git a/src/commands/pre-push.ts b/src/commands/pre-push.ts index bcfadbc..417a4e6 100644 --- a/src/commands/pre-push.ts +++ b/src/commands/pre-push.ts @@ -1,7 +1,9 @@ import { Dependencies } from "../dependencies"; +import { AssistOptions } from "./assist"; import { Command } from "./command"; -export interface PrePushCommandOptions { +export interface PrePushCommandOptions + extends Pick { disabledProjects?: string[]; } @@ -12,6 +14,8 @@ export function createPrePushCommand( return { async execute(options: PrePushCommandOptions = {}): Promise { + const { disabledProjects, testConfig, testsGlob } = options; + let timestamp = new Date().getTime(); let pristine = await git.isPristine(); if (!pristine) { @@ -27,8 +31,8 @@ export function createPrePushCommand( return false; } let results = await Promise.all([ - compiler.runOnce([], options.disabledProjects), - nyc.run(), + compiler.runOnce([], disabledProjects), + nyc.run(true, testConfig, testsGlob), ]); let toolErrors = results.filter((result) => result === false).length; logger.log( diff --git a/src/compiler.ts b/src/compiler.ts index 049df52..3d29f19 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1,5 +1,6 @@ import * as fs from "fs"; +import { parallelLimit } from "async"; import * as glob from "glob"; import { Bus } from "./bus"; @@ -8,11 +9,14 @@ import { Task, TaskRunner } from "./taskrunner"; import { absolutePath } from "./util"; export interface Compiler { - start(): void; + start(configs?: string[]): void; stop(): void; runOnce(tscArgs: string[], disabledProjects?: string[]): Promise; } +type TaskFunctionCallback = () => void; +type TaskFunction = (callback: TaskFunctionCallback) => void; + let runningTasks: Task[] = []; export function createCompiler(dependencies: { @@ -69,6 +73,8 @@ export function createCompiler(dependencies: { return true; } + let taskFunctions: TaskFunction[] = []; + return { runOnce(tscArgs, disabledProjects = []) { return new Promise((resolve, reject) => { @@ -79,43 +85,58 @@ export function createCompiler(dependencies: { if (error) { reject(error); } - let files = tsConfigFiles.filter( - (file) => !disabledProjects.includes(file.split("/")[0]) - ); - let task = taskRunner.runTask( - "./node_modules/.bin/tsc", - ["--build", ...files], - { - name: `tsc --build ${files.join(" ")}`, - logger, - handleOutput, - } - ); - runningTasks.push(task); - busyCompilers++; - task.result - .then(() => { - busyCompilers--; - runningTasks.splice(runningTasks.indexOf(task), 1); + tsConfigFiles + .filter((file) => { + return !disabledProjects.includes(file.split("/")[0]); }) - .catch((err) => { - logger.error("compiler", err.message); - process.exit(1); + .forEach((file) => { + let args = ["--build", file]; + let taskFunction = (callback: TaskFunctionCallback) => { + let task = taskRunner.runTask( + "./node_modules/.bin/tsc", + args, + { + name: `tsc --build ${file}`, + 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); } ); }); }, - start() { - let tsConfigFiles = ["./tsconfig.json", "./src/tsconfig.json"]; // Watching all **/tsconfig.json files has proven to cost too much CPU - tsConfigFiles = tsConfigFiles.filter((tsconfigFile) => - fs.existsSync(tsconfigFile) + /** + * 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", - ["--build", ...tsConfigFiles, "--watch", "--preserveWatchOutput"], + ["-b", ...tsConfigFiles, "--watch", "--preserveWatchOutput"], { name: `tsc --build ${tsConfigFiles.join(" ")} --watch`, logger, @@ -124,15 +145,10 @@ export function createCompiler(dependencies: { ); runningTasks.push(task); busyCompilers++; - task.result - .then(() => { - busyCompilers--; - runningTasks.splice(runningTasks.indexOf(task), 1); - }) - .catch((err) => { - logger.error("compiler", err.message); - process.exit(1); - }); + task.result.catch((err) => { + logger.error("compiler", err.message); + process.exit(1); + }); }, stop() { runningTasks.forEach((task) => { diff --git a/src/index.ts b/src/index.ts index efeb56b..99e2105 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,6 +58,19 @@ yargsModule.command( boolean: true, default: true, }, + project: { + describe: "provide tsconfigs to watch", + string: true, + type: "array", + }, + testConfig: { + describe: "path to nyc config file", + string: true, + }, + testsGlob: { + describe: "glob for test files", + string: true, + }, }, (yargs) => { if ( @@ -68,6 +81,9 @@ yargsModule.command( statusServerPort: parseInt(yargs.port as string, 10) || 0, format: yargs.format, coverage: yargs.coverage, + projects: yargs.project, + testConfig: yargs.testConfig, + testsGlob: yargs.testsGlob, }); } else { console.error("Unknown command"); @@ -212,11 +228,26 @@ yargsModule.command( yargsModule.command( "pre-push", "Pre-push git hook for husky", - (yargs) => yargs.array("disable"), + { + disable: { + string: true, + type: "array", + }, + testConfig: { + describe: "path to nyc config file", + string: true, + }, + testsGlob: { + describe: "glob for test files", + string: true, + }, + }, (yargs) => { inject(createPrePushCommand) .execute({ - disabledProjects: yargs.disable as string[], + disabledProjects: yargs.disable, + testConfig: yargs.testConfig, + testsGlob: yargs.testsGlob, }) .then(failIfUnsuccessful, onFailure); } diff --git a/src/taskrunner.ts b/src/taskrunner.ts index f6c6958..82d7b04 100644 --- a/src/taskrunner.ts +++ b/src/taskrunner.ts @@ -33,10 +33,9 @@ export let createDefaultTaskRunner = (): TaskRunner => { let loggerCategory = config.name; let logger = config.logger; - let readableCommand = command.replace( - `.${path.sep}node_modules${path.sep}.bin${path.sep}`, - "" - ); + let readableCommand = command + .replace(`.${path.sep}node_modules${path.sep}.bin${path.sep}`, "") + .replace(".cmd", ""); logger.log( loggerCategory, diff --git a/src/testing/nyc.ts b/src/testing/nyc.ts index ad14250..319d869 100644 --- a/src/testing/nyc.ts +++ b/src/testing/nyc.ts @@ -4,9 +4,18 @@ import { Logger } from "../logger"; import { Task, TaskRunner } from "../taskrunner"; export interface NYC { - start(triggers: EventType[], withCoverage: boolean): void; + start( + triggers: EventType[], + withCoverage: boolean, + config?: string, + testsGlob?: string + ): void; stop(): void; - run(withCoverage?: boolean): Promise; + run( + withCoverage?: boolean, + config?: string, + testsGlob?: string + ): Promise; } function delay(ms: number): Promise { @@ -19,11 +28,15 @@ export function createNyc(dependencies: { bus: Bus; git: Git; }): NYC { - let { taskRunner, logger, bus, git } = dependencies; + const { taskRunner, logger, bus, git } = dependencies; let runningTask: Task | undefined; let coolingDown: Promise | undefined; - let startNyc = async (withCoverage?: boolean): Promise => { + async function startNyc( + withCoverage = true, + config?: string, + testsGlob = "test/**/*-tests.ts*" + ): Promise { let hasFailingTest = false; let myCoolingDown = delay(100); coolingDown = myCoolingDown; @@ -41,7 +54,8 @@ export function createNyc(dependencies: { bus.report({ tool: "coverage", status: "busy" }); } let lastLineWasNotOk = false; - let handleOutput = (line: string) => { + + function handleOutput(line: string) { if (task === runningTask) { let notOk = /^not ok \d+ (.*)/.exec(line); let ok = /^ok \d+ (.*)/.exec(line); @@ -56,20 +70,27 @@ export function createNyc(dependencies: { } } return true; - }; - let handleError = (line: string) => { + } + + function handleError(line: string) { if (task === runningTask && !line.startsWith("ERROR: Coverage for")) { logger.error("nyc", line); } return true; - }; - if (withCoverage === false) { - logger.log("nyc", "running tests without coverage"); + } + + if (withCoverage) { runningTask = taskRunner.runTask( - "./node_modules/.bin/mocha", - "--require ts-node/register/transpile-only --exit --reporter tap test/**/*-tests.ts*".split( - " " - ), + "./node_modules/.bin/nyc", + [ + config ? `--nycrc-path ${config}` : "", + "--check-coverage", + "-- mocha --require ts-node/register/transpile-only --exit --reporter tap", + `"${testsGlob}"`, + ] + .join(" ") + .trim() + .split(" "), { name: "nyc", logger, @@ -78,12 +99,15 @@ export function createNyc(dependencies: { } ); } else { + logger.log("nyc", "running tests without coverage"); runningTask = taskRunner.runTask( - "./node_modules/.bin/nyc", - ( - "--check-coverage -- " + - "./node_modules/.bin/mocha --require ts-node/register/transpile-only --exit --reporter tap test/**/*-tests.ts*" - ).split(" "), + "./node_modules/.bin/mocha", + [ + "--require ts-node/register/transpile-only --exit --reporter tap", + `"${testsGlob}"`, + ] + .join(" ") + .split(" "), { name: "nyc", logger, @@ -117,16 +141,21 @@ export function createNyc(dependencies: { let isOnBranch = await git.isOnBranch(); return isOnBranch && !hasFailingTest; }); - }; + } let callback: (() => Promise) | undefined; return { - run(withCoverage?: boolean) { - return startNyc(withCoverage).catch(() => false); + run(withCoverage?: boolean, config?: string, testsGlob?: string) { + return startNyc(withCoverage, config, testsGlob).catch(() => false); }, - start(triggers: EventType[], withCoverage) { - callback = () => startNyc(withCoverage); + start( + triggers: EventType[], + withCoverage, + config?: string, + testsGlob?: string + ) { + callback = () => startNyc(withCoverage, config, testsGlob); bus.registerAll(triggers, callback as () => void); callback().catch(() => false); },