/
githubmirror
/
babel
Обзор
Документация
Войти
/
githubmirror
/
babel
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
scripts/parallel-tsc/tsc.ts
262 строки
7 KB
liuxingbaoyu
chore: Migrate scripts to `.ts` (#17767)
13 фев 2026, 17:26
Не верифицирован
13 фев 2026, 17:26
931ddd6
Код
Авторство
О чём код?
import { createRequire } from "node:module"; const require = createRequire(import.meta.url); import { resolve as resolvePath, relative as relativePath, join as joinPath, dirname, } from "node:path"; import { readFileSync, copyFileSync, mkdirSync } from "node:fs"; import cp from "node:child_process"; import os from "node:os"; import JSON5 from "json5"; import { glob } from "glob"; import { setTimeout } from "node:timers/promises"; const tscPath = require.resolve("typescript/lib/tsc.js"); const entryProjectsPaths = new Set( process.argv.slice(2).map(project => resolvePath(process.cwd(), project)) ); const ready = new Set<string>(); const projectToDependencies = new Map<string, Set<string>>(); { const queue = Array.from(entryProjectsPaths); for (let i = 0; i < queue.length; i++) { const project = queue[i]; if (projectToDependencies.has(project) || ready.has(project)) continue; const tsconfig = JSON5.parse( readFileSync(resolvePath(project, "tsconfig.json"), "utf8") ); const references = tsconfig.references.map(({ path }: { path: string }) => resolvePath(project, path) ); if (references.length === 0) { ready.add(project); } else { projectToDependencies.set(project, new Set(references)); queue.push(...references); } } } const projectToDependents = new Map<string, Set<string>>(); { for (const [project, references] of projectToDependencies) { for (const reference of references) { let dependents = projectToDependents.get(reference); if (!dependents) { dependents = new Set(); projectToDependents.set(reference, dependents); } dependents.add(project); } } } class Pool { #filename; #concurrency; #onTaskStart; #onTaskDone; #onTaskError; #showOutput; #maxWeight; #getWeight; #running = new Map<cp.ChildProcess, number>(); #pending: string[] = []; #emptyListeners: (() => void)[] = []; #queued = false; constructor({ filename, concurrency, onTaskStart, onTaskDone, onTaskError, showOutput, maxWeight, taskWeight, }: { filename: string; concurrency: number; onTaskStart: (tasks: string[], weight: number) => void; onTaskDone: ( tasks: string[], code: number, duration: number ) => Promise<void>; onTaskError: (tasks: string[]) => void; showOutput: (tasks: string[]) => boolean; maxWeight: number; taskWeight: (task: string) => number; }) { this.#filename = filename; this.#concurrency = concurrency; this.#onTaskStart = onTaskStart; this.#onTaskDone = onTaskDone; this.#onTaskError = onTaskError; this.#showOutput = showOutput; this.#maxWeight = maxWeight; this.#getWeight = taskWeight; } run(task: string) { this.#pending.push(task); if (!this.#queued) { this.#queued = true; queueMicrotask(() => { this.#runTasks(); }); } } #runNextTask() { if (this.#pending.length === 0) return; const tasks = [this.#pending[0]]; let weight = this.#getWeight(tasks[0]); const toRemove = new Set([0]); for (let i = 1; i < this.#pending.length && weight < this.#maxWeight; i++) { const task = this.#pending[i]; const taskWeight = this.#getWeight(task); if (weight + taskWeight <= this.#maxWeight) { weight += taskWeight; tasks.push(task); toRemove.add(i); } } this.#pending = this.#pending.filter((_, i) => !toRemove.has(i)); const taskProcess = cp.fork(this.#filename, ["-b", ...tasks, "--pretty"], { stdio: this.#showOutput(tasks) ? "inherit" : "ignore", }); // eslint-disable-next-line @typescript-eslint/no-misused-promises taskProcess.once("exit", async code => { const startTime = this.#running.get(taskProcess)!; const duration = performance.now() - startTime; await this.#onTaskDone(tasks, code!, duration); this.#running.delete(taskProcess); // Spin the event loop so that #run can pick up the next task await setTimeout(0); this.#runTasks(); if (this.#running.size === 0 && this.#pending.length === 0) { this.#emptyListeners.forEach(f => f()); } }); taskProcess.once("error", err => { console.error("Error in task process:", err); this.#onTaskError(tasks); this.#running.delete(taskProcess); }); this.#onTaskStart(tasks, weight); this.#running.set(taskProcess, performance.now()); } #runTasks() { while (this.#running.size < this.#concurrency && this.#pending.length > 0) { this.#runNextTask(); } } get waitForEmpty() { return new Promise<void>(resolve => { this.#emptyListeners.push(resolve); }); } } const total = projectToDependencies.size + ready.size; let done = 0; const pool = new Pool({ filename: tscPath, concurrency: os.cpus().length, maxWeight: 20, taskWeight(project) { return 1 + (projectToDependents.get(project)?.size ?? 0); }, showOutput(projects) { return projects.some(task => entryProjectsPaths.has(task)); }, // eslint-disable-next-line @typescript-eslint/no-unused-vars onTaskStart(projects, weight) {}, async onTaskDone(projects, code, duration) { if (code !== 0) process.exitCode = code; await Promise.all( projects.map(async project => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const { include: projectFiles } = JSON5.parse( readFileSync(resolvePath(project, "tsconfig.json"), "utf8") ) as { include?: string[] }; if (!projectFiles) return; const globPatterns = projectFiles .filter(file => file.endsWith("*.ts")) .map( file => `${project}/${file.slice(0, -3)}*.d.ts`.replace(/\\/g, "/") // glob doesn't support \ on Windows ); const files = await glob(globPatterns); for (const file of files) { const out = joinPath( process.cwd(), "dts", relativePath(process.cwd(), file) ); mkdirSync(dirname(out), { recursive: true }); copyFileSync(file, out); } }) ); done += projects.length; if (!process.env.TSCHECK_SILENT) { console.log( `[${done}/${total}] ${projects.map(project => relativePath(process.cwd(), project)).join(" ")} took ${Math.round(duration) / 1e3}s` ); } for (const project of projects) { for (const dependent of projectToDependents.get(project) ?? []) { const dependencies = projectToDependencies.get(dependent); if (dependencies) { dependencies.delete(project); if (dependencies.size === 0) { projectToDependencies.delete(dependent); // @ts-expect-error run is defined on Pool if (!projects.includes(dependent)) this.run(dependent); } } } } }, onTaskError(projects, code = 1) { console.error("ERROR", projects); process.exitCode = code; }, }); for (const project of ready) pool.run(project); await pool.waitForEmpty; if (projectToDependencies.size > 0) { console.error("Did not typecheck the following projects:"); for (const project of projectToDependencies.keys()) { console.error(` - ${project}`); } process.exitCode = 1; }