import { readFile, rename, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; const root = process.cwd(); const sourcePath = resolve(root, "public/portfolio-data.json"); const outputPath = resolve(root, "data/project-health.json"); const timeoutMs = 10_000; const data = JSON.parse(await readFile(sourcePath, "utf8")); const projects = data.projects.filter((project) => project.demoUrl); const checkedAt = new Date().toISOString(); const results = await Promise.all( projects.map(async ({ id, demoUrl }) => { const startedAt = Date.now(); try { const response = await fetch(demoUrl, { redirect: "follow", signal: AbortSignal.timeout(timeoutMs), headers: { "user-agent": "evandro-project-health/1.0" }, }); const contentType = response.headers.get("content-type"); await response.body?.cancel(); return [ id, { status: response.status, contentType, checkedAt, finalUrl: response.url, latencyMs: Date.now() - startedAt, }, ]; } catch (error) { return [ id, { status: null, contentType: null, checkedAt, latencyMs: Date.now() - startedAt, error: error instanceof Error ? error.message : "Unknown health-check error", }, ]; } }), ); const snapshot = { checkedAt, projects: Object.fromEntries(results) }; const temporaryPath = `${outputPath}.${process.pid}.tmp`; await writeFile( temporaryPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8", ); await rename(temporaryPath, outputPath); const live = Object.values(snapshot.projects).filter( (check) => check.status === 200 && check.contentType?.toLowerCase().startsWith("text/html"), ).length; console.log(`Checked ${projects.length} project demos; ${live} are live.`);