import { stat } from "node:fs/promises"; import path from "node:path"; import { runningJob, jobView, startJob } from "@/lib/jobs"; import { projectRef } from "@/lib/projects"; import { MANIFEST_NAME } from "@/lib/projects/report.mjs"; import { checkSourcesSteps } from "@/lib/report/driver.mjs"; export const dynamic = "force-dynamic"; // Re-check sources across projects: one check-availability step per report // project, through the same job runner as a build -- so it serialises with // builds, is cancellable, and writes each project's out/availability.json with // the same script the preflight uses. // // The body names PROJECT IDS. Never a path, never an argv. export async function POST(request: Request) { const url = new URL(request.url); const dry = url.searchParams.get("dry") === "1"; const body = (await request.json().catch(() => ({}))) as { projects?: unknown }; const ids = Array.isArray(body.projects) ? body.projects.map(String) : []; if (!ids.length) return Response.json({ error: "projects is required" }, { status: 400 }); const refs = []; for (const id of ids) { const p = await projectRef(id); if (!p) return Response.json({ error: `no such project: ${id}` }, { status: 404 }); // Only a project WITH A MANIFEST has sources to preflight. Asking the disk // rather than the registry keeps this file from naming a kind. if (!(await stat(path.join(p.dir, MANIFEST_NAME)).then((s) => s.isFile(), () => false))) { return Response.json({ error: `${id} has no ${MANIFEST_NAME} to check` }, { status: 400 }); } refs.push(p); } const steps = checkSourcesSteps(refs); const view = { projects: refs.map((p) => p.id), steps: steps.map((s) => ({ label: s.label, argv: s.argv, cwd: s.cwd, timeoutMs: s.timeoutMs ?? null })) }; if (dry) return Response.json({ dry: true, ...view }, { headers: { "cache-control": "no-store" } }); const running = runningJob(); if (running) { return Response.json({ error: `a job is already running (${running.kind})`, running: jobView(running) }, { status: 409 }); } try { const job = startJob(`check-sources (${refs.length} project${refs.length === 1 ? "" : "s"})`, steps, { project: refs.length === 1 ? refs[0].id : null, }); return Response.json({ ok: true, ...view, job: jobView(job) }, { headers: { "cache-control": "no-store" } }); } catch (e) { return Response.json({ error: e instanceof Error ? e.message : String(e) }, { status: 409 }); } }