import fs from "node:fs"; import path from "node:path"; import type { BuildContext } from "tsdown"; import { portableRelativePath } from "./build-artifact-cache.mts"; import { createDeclarationInputBoundary } from "./local-check-runtime.mts"; import { readDeclarationBuildInputs } from "./tsdown-declaration-boundary.mts"; const stagePrefix = (root: string) => path.join(fs.realpathSync.native(root), ".artifacts/plugin-sdk-staging-"); const receiptPath = (output: string, name: string) => path.join(output, "..", "compiler-inputs", `${name}.json`); export function createDeclarationStage(root: string) { return fs.mkdtempSync(stagePrefix(root)); } /** Requests and receipts belong only to the writer's fresh, joined private stage. */ export function requestDeclarationInputs(output: string, name: string, roots: string[]) { const file = receiptPath(output, name); fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, JSON.stringify({ roots }), { flag: "wx" }); } export function createDeclarationInputCapture(name: string) { return async ({ options }: BuildContext) => { const boundary = createDeclarationInputBoundary(options.cwd); const stage = path.dirname(boundary.resolve(options.outDir)); const prefix = stagePrefix(options.cwd); // Only the writer's private stage owns receipts. A direct build's sibling // compiler-inputs directory is ordinary checkout data, even if names match. if ( path.basename(options.outDir) !== "dist" || path.dirname(stage) !== path.dirname(prefix) || !stage.startsWith(prefix) ) { return; } const file = receiptPath(options.outDir, name); if (!fs.existsSync(file)) { return; } const request: { roots: string[] } = JSON.parse(fs.readFileSync(file, "utf8")); const compilation = readDeclarationBuildInputs(options); if (request.roots.some((root) => !compilation.roots.has(boundary.resolve(root)))) { throw new Error(`Incomplete compiler membership for ${name}`); } const inputs = [...compilation.inputs] .map((input) => portableRelativePath(boundary.root, boundary.assert(input))) .toSorted(); fs.writeFileSync(file, JSON.stringify({ ...request, inputs })); }; } export function readDeclarationInputs(output: string, name: string) { const receipt: { roots: string[]; inputs?: unknown } = JSON.parse( fs.readFileSync(receiptPath(output, name), "utf8"), ); if ( !Array.isArray(receipt.inputs) || // Bounded selections can leave empty partitions; only those requests // may finish successfully without creating a compiler Program. (!receipt.inputs.length && receipt.roots.length > 0) || !receipt.inputs.every((input): input is string => typeof input === "string") ) { throw new Error(`Missing successful compiler membership for ${name}`); } return receipt.inputs; }