|
| 1 | +import {exec} from "node:child_process"; |
| 2 | +import {readFileSync, readdirSync, existsSync} from "node:fs"; |
| 3 | +import {join, resolve} from "node:path"; |
| 4 | + |
| 5 | +const rootDir = resolve(import.meta.dirname, ".."); |
| 6 | +const rootPkg = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8")); |
| 7 | + |
| 8 | +const workspaceDirs = rootPkg.workspaces.flatMap((pattern) => { |
| 9 | + const base = pattern.replace("/*", ""); |
| 10 | + const dir = join(rootDir, base); |
| 11 | + if (!existsSync(dir)) return []; |
| 12 | + return readdirSync(dir, {withFileTypes: true}) |
| 13 | + .filter((d) => d.isDirectory()) |
| 14 | + .map((d) => join(dir, d.name)); |
| 15 | +}); |
| 16 | + |
| 17 | +const workspaces = workspaceDirs |
| 18 | + .map((dir) => { |
| 19 | + const pkgPath = join(dir, "package.json"); |
| 20 | + if (!existsSync(pkgPath)) return null; |
| 21 | + const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); |
| 22 | + if (!pkg.scripts?.unit) return null; |
| 23 | + return {name: pkg.name, dir}; |
| 24 | + }) |
| 25 | + .filter(Boolean); |
| 26 | + |
| 27 | +function runWorkspace(ws) { |
| 28 | + return new Promise((resolve) => { |
| 29 | + exec(`npm run unit --workspace=${ws.name}`, { |
| 30 | + cwd: rootDir, |
| 31 | + encoding: "utf8", |
| 32 | + maxBuffer: 50 * 1024 * 1024, |
| 33 | + }, (err, stdout, stderr) => { |
| 34 | + if (err) { |
| 35 | + resolve({name: ws.name, passed: false, output: stdout}); |
| 36 | + } else { |
| 37 | + resolve({name: ws.name, passed: true}); |
| 38 | + } |
| 39 | + }); |
| 40 | + }); |
| 41 | +} |
| 42 | + |
| 43 | +console.log(`Running unit tests across ${workspaces.length} workspaces in parallel...\n`); |
| 44 | + |
| 45 | +const results = await Promise.all(workspaces.map(runWorkspace)); |
| 46 | + |
| 47 | +for (const r of results) { |
| 48 | + if (r.passed) { |
| 49 | + console.log(` ${r.name} \x1b[32m✓\x1b[0m`); |
| 50 | + } else { |
| 51 | + console.log(` ${r.name} \x1b[31m✗\x1b[0m`); |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +const failures = results.filter((r) => !r.passed); |
| 56 | +const passes = results.filter((r) => r.passed); |
| 57 | + |
| 58 | +if (failures.length > 0) { |
| 59 | + for (const f of failures) { |
| 60 | + console.log(`\n\x1b[31m${"━".repeat(60)}\x1b[0m`); |
| 61 | + console.log(`\x1b[31m Failures: ${f.name}\x1b[0m`); |
| 62 | + console.log(`\x1b[31m${"━".repeat(60)}\x1b[0m\n`); |
| 63 | + console.log(f.output); |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +const failedInfo = failures.length > 0 ? ` (${failures.map((f) => f.name).join(", ")})` : ""; |
| 68 | +const summaryLine = ` Unit Test Summary: ${passes.length} passed, ${failures.length} failed${failedInfo}`; |
| 69 | +const frameWidth = Math.max(60, summaryLine.length + 2); |
| 70 | +console.log(`\n${"═".repeat(frameWidth)}`); |
| 71 | +console.log(` Unit Test Summary: \x1b[32m${passes.length} passed` + |
| 72 | + `\x1b[0m, \x1b[31m${failures.length} failed\x1b[0m${failedInfo}`); |
| 73 | +console.log(`${"═".repeat(frameWidth)}`); |
| 74 | + |
| 75 | +process.exitCode = failures.length > 0 ? 1 : 0; |
0 commit comments