mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
- Compile production program loading across module graphs, packages, and configuration - Preserve optional defaults and support runtime URL bases and regex split captures - Isolate host execution services and initialize lowering tables outside module cycles
101 lines
4.2 KiB
TypeScript
101 lines
4.2 KiB
TypeScript
import { readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { loadProgram, checkPreflight } from "../../../packages/compiler/src/frontend/program.js";
|
|
import type { FrontendServices } from "../../../packages/compiler/src/frontend/services.js";
|
|
import type { ScrDiagnostic } from "../../../packages/compiler/src/diagnostics/diagnostic.js";
|
|
|
|
export interface FrontendProgramRequest {
|
|
root: string;
|
|
cases: {
|
|
name: string;
|
|
entry: string;
|
|
npmStatic: string[];
|
|
externalTypes: [string, string][];
|
|
edits: { path: string; source: string }[];
|
|
}[];
|
|
}
|
|
|
|
interface DiagnosticReport {
|
|
code: string;
|
|
message: string;
|
|
file: string;
|
|
start: number;
|
|
end: number;
|
|
}
|
|
|
|
interface ProgramReport {
|
|
name: string;
|
|
error: string;
|
|
diagnostics: DiagnosticReport[];
|
|
order: string[];
|
|
sources: { path: string; text: string }[];
|
|
external: { file: string; specifiers: string[] }[];
|
|
startup: { message: string; className: string; file: string; start: number; end: number } | null;
|
|
entryIdentity: boolean;
|
|
released: boolean;
|
|
}
|
|
|
|
function normalized(value: string, root: string): string {
|
|
return value.split("\\").join("/")
|
|
.split(realpathSync(root).split("\\").join("/") + "/").join("")
|
|
.split(root.split("\\").join("/") + "/").join("");
|
|
}
|
|
|
|
function diagnosticReport(diagnostic: ScrDiagnostic, root: string): DiagnosticReport {
|
|
return {
|
|
code: diagnostic.code,
|
|
message: normalized(diagnostic.message, root),
|
|
file: normalized(diagnostic.loc.file, root),
|
|
start: diagnostic.loc.start,
|
|
end: diagnostic.loc.end,
|
|
};
|
|
}
|
|
|
|
/** Run the production loader repeatedly through one service owner. Each load
|
|
* owns its program host; malformed inputs must not poison later projects. */
|
|
export function runFrontendPrograms(services: FrontendServices, input: string, output: string): void {
|
|
const request = JSON.parse(readFileSync(input, "utf8")) as FrontendProgramRequest;
|
|
const reports: ProgramReport[] = [];
|
|
for (const item of request.cases) {
|
|
for (const edit of item.edits) writeFileSync(join(request.root, edit.path), edit.source);
|
|
const report: ProgramReport = {
|
|
name: item.name, error: "", diagnostics: [], order: [], sources: [], external: [],
|
|
startup: null, entryIdentity: false, released: false,
|
|
};
|
|
try {
|
|
const entry = join(request.root, item.entry);
|
|
const load = loadProgram(entry, services, {
|
|
npmStatic: item.npmStatic,
|
|
externalTypes: item.externalTypes.map(([specifier, path]) => [specifier, join(request.root, path)]),
|
|
});
|
|
try {
|
|
report.entryIdentity = load.program.getSourceFile(entry) === load.entry;
|
|
report.diagnostics = checkPreflight(load).map((diagnostic) => diagnosticReport(diagnostic, request.root));
|
|
report.order = load.moduleOrder.map((source) => normalized(source.fileName, request.root));
|
|
report.sources = load.program.getSourceFiles()
|
|
.filter((source) => !source.isDeclarationFile)
|
|
.map((source) => ({ path: normalized(source.fileName, request.root), text: source.text }));
|
|
for (const [file, specifiers] of load.externalTypeSpecifiersByFile) {
|
|
report.external.push({ file: normalized(file, request.root), specifiers: [...specifiers] });
|
|
}
|
|
const crash = load.startupCrash;
|
|
if (crash !== undefined && crash !== null) report.startup = {
|
|
message: normalized(crash.message, request.root), className: crash.className,
|
|
file: normalized(crash.loc.file, request.root), start: crash.loc.start, end: crash.loc.end,
|
|
};
|
|
} finally { load.dispose(); }
|
|
report.released = load.program.analysis.nodeEsmFiles.size === 0 && load.program.analysis.createRequireReasons.size === 0;
|
|
} catch (error) {
|
|
report.error = normalized(error instanceof Error ? error.message : String(error), request.root);
|
|
}
|
|
reports.push(report);
|
|
// Retain completed cases when a later native run crashes or times out.
|
|
writeFileSync(output, JSON.stringify(reports));
|
|
}
|
|
services.close();
|
|
let refused = false;
|
|
try { services.createProgramHost(); } catch { refused = true; }
|
|
if (!refused) throw new Error("closed frontend services accepted a new program host");
|
|
services.close();
|
|
}
|