import type { CliDeps } from "../cli/deps.js"; import { waitForever as defaultWaitForever } from "../cli/wait.js"; import { retryAsync } from "../infra/retry.js"; import type { RuntimeEnv } from "../runtime.js"; export async function upCommand( opts: { port: string; path: string; verbose?: boolean; yes?: boolean; dryRun?: boolean; }, deps: CliDeps, runtime: RuntimeEnv, waiter: typeof defaultWaitForever = defaultWaitForever, ) { const port = Number.parseInt(opts.port, 10); if (Number.isNaN(port) || port <= 0 || port >= 65536) { throw new Error("Port must be between 1 and 65535"); } await deps.ensurePortAvailable(port); const env = deps.readEnv(runtime); if (opts.dryRun) { runtime.log(`[dry-run] would enable funnel on port ${port}`); runtime.log(`[dry-run] would start webhook at path ${opts.path}`); runtime.log(`[dry-run] would update Twilio sender webhook`); const publicUrl = `https://dry-run${opts.path}`; return { server: undefined, publicUrl, senderSid: undefined, waiter }; } await deps.ensureBinary("tailscale", undefined, runtime); await retryAsync(() => deps.ensureFunnel(port, undefined, runtime), 3, 500); const host = await deps.getTailnetHostname(); const publicUrl = `https://${host}${opts.path}`; runtime.log(`🌐 Public webhook URL (via Funnel): ${publicUrl}`); const server = await retryAsync( () => deps.startWebhook( port, opts.path, undefined, Boolean(opts.verbose), runtime, ), 3, 300, ); if (!deps.createClient) { throw new Error("Twilio client dependency missing"); } const twilioClient = deps.createClient(env); const senderSid = await deps.findWhatsappSenderSid( twilioClient as unknown as import("../twilio/types.js").TwilioSenderListClient, env.whatsappFrom, env.whatsappSenderSid, runtime, ); await deps.updateWebhook(twilioClient, senderSid, publicUrl, "POST", runtime); runtime.log( "\nSetup complete. Leave this process running to keep the webhook online. Ctrl+C to stop.", ); return { server, publicUrl, senderSid, waiter }; }