import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' /** * Dev runner for /api/* requests in `npm run dev`. * * The files in /api are Vercel serverless functions — on Vercel they run as * real functions; locally Vite doesn't execute them. To test the real APIs * locally (e.g. the Tomorrow.io forecast) this middleware loads .env into * process.env and invokes the ACTUAL handler for KV-free routes via a small * req/res shim that matches the Vercel handler contract. * * Routes that use server-only deps (`@vercel/kv`: hail-status, hail-webhook) * stay stubbed with a 503 — every frontend hook that hits these already * degrades gracefully on a failed fetch. */ // Only KV-free handlers are safe to import into the dev pipeline. const DEV_API_HANDLERS = { 'storm-forecast': () => import('./api/storm-forecast.js'), 'storm-history': () => import('./api/storm-history.js'), 'hail-proxy': () => import('./api/hail-proxy.js'), 'storm-events': () => import('./api/storm-events.js'), 'storm-polygons': () => import('./api/storm-polygons.js'), // Payments module (needs real Stripe test keys in .env to function locally). // The webhook is intentionally excluded — it needs the raw body + Stripe CLI. 'payments/create-checkout-session': () => import('./api/payments/create-checkout-session.js'), 'payments/session-status': () => import('./api/payments/session-status.js'), 'payments/transactions': () => import('./api/payments/transactions.js'), } // Server-side env vars the handlers read (mirrors what you'd set in Vercel). const SERVER_ENV_KEYS = [ 'TOMORROW_API_KEY', 'HAIL_RECON_ACCESS_KEY', 'HAIL_RECON_ACCESS_SECRET', 'HAIL_RECON_WEBHOOK_SECRET', 'STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET', ] function devApi(env) { // .env is authoritative in dev: always apply it so edits are picked up on a // server restart. (A `!process.env[k]` guard here would keep a stale value // from a previous load — e.g. the placeholder secret key — and silently // ignore the real key you just added.) for (const k of SERVER_ENV_KEYS) { if (env[k]) process.env[k] = env[k] } return { name: 'dev-api', apply: 'serve', configureServer(server) { server.middlewares.use(async (req, res, next) => { if (!req.url || !req.url.startsWith('/api/')) return next() const url = new URL(req.url, 'http://localhost') const name = url.pathname.replace(/^\/api\//, '').replace(/\/+$/, '') const load = DEV_API_HANDLERS[name] if (!load) { res.statusCode = 503 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ error: 'API route not available in local dev (needs Vercel).' })) return } // Shim the Node req/res to the Vercel handler contract. req.query = Object.fromEntries(url.searchParams.entries()) res.status = (code) => { res.statusCode = code; return res } res.json = (obj) => { if (!res.getHeader('Content-Type')) res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify(obj)) return res } res.send = (body) => { res.end(typeof body === 'string' ? body : JSON.stringify(body)); return res } // Parse a JSON body for POST/PUT/PATCH (Vercel does this automatically). if (['POST', 'PUT', 'PATCH'].includes(req.method)) { req.body = await new Promise((resolve) => { let data = '' req.on('data', (c) => { data += c }) req.on('end', () => { try { resolve(data ? JSON.parse(data) : {}) } catch { resolve({}) } }) req.on('error', () => resolve({})) }) } try { const mod = await load() await mod.default(req, res) } catch (e) { res.statusCode = 500 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ error: 'dev api handler failed', detail: e.message })) } }) }, } } export default defineConfig(({ mode }) => { // Empty prefix → load ALL vars from .env (including non-VITE_ server keys). const env = loadEnv(mode, process.cwd(), '') return { plugins: [react(), devApi(env)], server: { // Don't watch/serve /api as client files — they're invoked server-side above. watch: { ignored: ['**/api/**'] }, fs: { deny: ['api'] }, }, optimizeDeps: { exclude: ['api'], }, } })