From 1ff58049c0a49f05af92bc89baf110cec2c995c7 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Thu, 19 Feb 2026 16:13:09 -0800 Subject: [PATCH] feat: pass GITEA_TOKEN to IDE containers + prewarm on project list load Co-authored-by: Cursor --- app/[workspace]/projects/page.tsx | 16 +++++++++++++++- app/api/projects/prewarm/route.ts | 29 +++++++++++++++++++++++++++++ lib/cloud-run-workspace.ts | 2 ++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 app/api/projects/prewarm/route.ts diff --git a/app/[workspace]/projects/page.tsx b/app/[workspace]/projects/page.tsx index f478497..1e8a55c 100644 --- a/app/[workspace]/projects/page.tsx +++ b/app/[workspace]/projects/page.tsx @@ -126,8 +126,22 @@ export default function ProjectsPage() { throw new Error(err.error || "Failed to fetch projects"); } const data = await res.json(); - setProjects(data.projects || []); + const loaded: ProjectWithStats[] = data.projects || []; + setProjects(loaded); setError(null); + + // Fire-and-forget: prewarm all provisioned IDE workspaces so containers + // are already running by the time the user clicks "Open IDE" + const warmUrls = loaded + .map((p) => p.theiaWorkspaceUrl) + .filter((u): u is string => Boolean(u)); + if (warmUrls.length > 0) { + fetch("/api/projects/prewarm", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ urls: warmUrls }), + }).catch(() => {}); // ignore errors — this is best-effort + } } catch (err: unknown) { setError(err instanceof Error ? err.message : "Unknown error"); } finally { diff --git a/app/api/projects/prewarm/route.ts b/app/api/projects/prewarm/route.ts new file mode 100644 index 0000000..8f02456 --- /dev/null +++ b/app/api/projects/prewarm/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/lib/auth'; +import { prewarmWorkspace } from '@/lib/cloud-run-workspace'; + +/** + * POST /api/projects/prewarm + * Body: { urls: string[] } + * + * Fires warm-up requests to Cloud Run workspace URLs so containers + * are running by the time the user clicks "Open IDE". Server-side + * to avoid CORS issues with run.app domains. + */ +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session?.user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { urls } = await req.json() as { urls: string[] }; + if (!Array.isArray(urls) || urls.length === 0) { + return NextResponse.json({ warmed: 0 }); + } + + // Fire all prewarm pings in parallel — intentionally not awaited + Promise.allSettled(urls.map(url => prewarmWorkspace(url))).catch(() => {}); + + return NextResponse.json({ warmed: urls.length }); +} diff --git a/lib/cloud-run-workspace.ts b/lib/cloud-run-workspace.ts index 221e7fe..71f622d 100644 --- a/lib/cloud-run-workspace.ts +++ b/lib/cloud-run-workspace.ts @@ -85,6 +85,8 @@ export async function provisionTheiaWorkspace( { name: 'VIBN_API_URL', value: VIBN_URL }, { name: 'GITEA_REPO', value: giteaRepo ?? '' }, { name: 'GITEA_API_URL', value: process.env.GITEA_API_URL ?? 'https://git.vibnai.com' }, + // Token lets the startup script clone and push to the project's repo + { name: 'GITEA_TOKEN', value: process.env.GITEA_API_TOKEN ?? '' }, ], // 5 minute startup timeout — Theia needs time to initialise startupProbe: {