import { query, one } from '../db.js'; import { env } from '../env.js'; interface TriggerArgs { buildId: string; websiteUrl: string; appName: string; platform: string; packageName: string; config: Record; } /** * Trigger a cloud build. * * If CODEMAGIC_API_TOKEN + CODEMAGIC_APP_ID are configured, this calls the * Codemagic API for real. Otherwise it runs a local SIMULATION so the build * flow is demonstrable in development (progress + placeholder artifact). */ export async function triggerCloudBuild(args: TriggerArgs): Promise<{ cloudBuildId?: string; message: string }> { if (env.CODEMAGIC_API_TOKEN && env.CODEMAGIC_APP_ID) { const res = await fetch('https://api.codemagic.io/builds', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-auth-token': env.CODEMAGIC_API_TOKEN }, body: JSON.stringify({ appId: env.CODEMAGIC_APP_ID, workflowId: args.platform === 'ios' ? 'ios-workflow' : 'android-workflow', branch: 'main', environment: { variables: { APP_NAME: args.appName, WEBSITE_URL: args.websiteUrl, PACKAGE_NAME: args.packageName, }, }, }), }); const json: any = await res.json().catch(() => ({})); if (!res.ok) throw new Error(json?.error || json?.message || 'Codemagic build trigger failed'); const cloudBuildId = json?.buildId || json?._id; await query(`UPDATE public.app_builds SET status = 'building', progress = 5 WHERE id = $1`, [args.buildId]); return { cloudBuildId, message: 'Cloud build started (Codemagic)' }; } // ---- Simulation (no Codemagic configured) ---- await query(`UPDATE public.app_builds SET status = 'building', progress = 10 WHERE id = $1`, [args.buildId]); simulateBuild(args.buildId).catch((e) => console.error('[cloud-build sim]', e.message)); return { message: 'Cloud build simulated (Codemagic not configured)' }; } async function simulateBuild(buildId: string) { const steps = [25, 45, 65, 85]; for (const p of steps) { await delay(2000); await query(`UPDATE public.app_builds SET progress = $2 WHERE id = $1 AND status = 'building'`, [buildId, p]); } await delay(2000); const build = await one(`SELECT package_name FROM public.app_builds WHERE id = $1`, [buildId]); const fileName = `${(build?.package_name || 'app').replace(/\./g, '-')}.apk`; await query( `UPDATE public.app_builds SET status = 'complete', progress = 100, download_url = $2, file_size_bytes = $3 WHERE id = $1 AND status = 'building'`, [buildId, `/storage/apk-builds/simulated/${fileName}`, 12 * 1024 * 1024] ); } const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); /** Returns the current build row (used by the status endpoint). */ export async function getCloudBuildStatus(buildId: string) { return one(`SELECT * FROM public.app_builds WHERE id = $1`, [buildId]); }