diff --git a/package.json b/package.json index ecff4b17684..6de5ae4e551 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "release:patch": "npm run release patch && npm run docs:deploy", "release:minor": "npm run release minor && npm run docs:deploy", "release:major": "npm run release major && npm run docs:deploy", - "test": "npm run prettier:test && npm run syncpack && npm run typecheck && npm run lint && npm run jest", + "test": "node scripts/tests/run-quality-checks.mjs && npm run jest", "test:exports": "tsx scripts/tests/exports", "test:conflicts": "tsx scripts/tests/conflicts", "test:validate-package-json": "tsx scripts/tests/validate-package-json", diff --git a/scripts/tests/run-quality-checks.mjs b/scripts/tests/run-quality-checks.mjs new file mode 100644 index 00000000000..8f286bc806e --- /dev/null +++ b/scripts/tests/run-quality-checks.mjs @@ -0,0 +1,83 @@ +import { spawn } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const repoRoot = path.resolve(__dirname, '..', '..'); +const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + +const tasks = [ + { name: 'prettier', script: 'prettier:test' }, + { name: 'syncpack', script: 'syncpack' }, + { name: 'typecheck', script: 'typecheck' }, + { name: 'lint', script: 'lint' }, +]; + +const running = new Map(); + +function log(message) { + // eslint-disable-next-line no-console + console.log(`[test:parallel] ${message}`); +} + +function terminateOthers(failedTask) { + running.forEach((child, name) => { + if (name === failedTask) { + return; + } + + if (!child.killed) { + child.kill('SIGTERM'); + } + }); +} + +function runTask(task) { + return new Promise((resolve, reject) => { + log(`Starting ${task.name}`); + + const child = spawn(npmCmd, ['run', task.script], { + cwd: repoRoot, + stdio: 'inherit', + env: process.env, + }); + + running.set(task.name, child); + + const handleExit = (code, signal) => { + running.delete(task.name); + + if (code === 0) { + log(`Finished ${task.name}`); + resolve(); + } else { + const reason = + code !== null ? `exit code ${code}` : signal ? `signal ${signal}` : 'unknown error'; + const error = new Error(`[${task.name}] failed with ${reason}`); + error.taskName = task.name; + reject(error); + } + }; + + child.on('exit', handleExit); + child.on('error', (error) => { + running.delete(task.name); + error.taskName = task.name; + reject(error); + }); + }); +} + +async function main() { + try { + await Promise.all(tasks.map((task) => runTask(task))); + log('All checks completed successfully'); + } catch (error) { + terminateOthers(error?.taskName); + log(error?.message ?? error); + process.exit(1); + } +} + +main();