import { Hono } from "hono"; import { rolledUpStats } from "./migrations.js"; import type { Migration } from "./types.js"; export function buildApp(migrations: Migration[]): Hono { const app = new Hono(); app.get("/", (c) => c.html(renderDashboardHtml(migrations))); app.get("/dashboard", (c) => c.html(renderDashboardHtml(migrations))); app.get("/migrations", (c) => c.json({ stats: rolledUpStats(migrations), migrations: migrations.map((m) => ({ id: m.id, repo: m.repo, state: m.state, sourceRuntime: m.sourceRuntime, targetRuntime: m.targetRuntime, turns: m.turns, spentUsd: m.spentUsd, })), }), ); app.get("/migrations/:id", (c) => { const id = c.req.param("id"); const m = migrations.find((x) => x.id === id); if (!m) return c.json({ error: "not_found", id }, 404); return c.json(m); }); return app; } export function renderDashboardHtml(migrations: Migration[]): string { const stats = rolledUpStats(migrations); const rows = migrations .map((m) => { const passedFiles = m.files.filter((f) => f.status === "passed").length; const pct = m.files.length === 0 ? 0 : Math.round((passedFiles / m.files.length) * 100); return [ "", `${m.repo}`, `${m.sourceRuntime} to ${m.targetRuntime}`, `${m.state}`, `${pct}%`, `${m.turns}/${m.maxTurns}`, `$${m.spentUsd.toFixed(2)}/$${m.budgetUsd}`, "", ].join(""); }) .join("\n"); return [ "", "Code migration dashboard", "", "

Code migration dashboard

", "
", `
${stats.total} migrations
`, `
${stats.running} running
`, `
${stats.passed} passed
`, `
${stats.failed} failed
`, `
$${stats.spentUsd.toFixed(2)} spent
`, "
", "", "", "", rows, "
repomigrationstateprogressturnscost
", "

Auto-refreshes every 2s. Endpoints: /migrations, /migrations/:id.

", "", "", ].join("\n"); }