import { mkdirSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' import type { Context } from 'hono' /** * Forward unmatched requests to a real upstream (PROXY_TARGET). * With record=true, persist the upstream JSON response as a mock file so the * next run serves it locally โ€” record-once, replay-forever. */ export async function proxyPass( c: Context, opts: { target: string; record: boolean; routesDir: string }, ): Promise { const reqUrl = new URL(c.req.url) const upstream = new URL(reqUrl.pathname + reqUrl.search, opts.target) const init: RequestInit = { method: c.req.method, headers: stripHopHeaders(c.req.raw.headers), } if (!['GET', 'HEAD'].includes(c.req.method)) init.body = await c.req.raw.arrayBuffer() const res = await fetch(upstream, init) const buf = await res.arrayBuffer() if (opts.record && res.ok && (res.headers.get('content-type') ?? '').includes('json')) { try { recordMock(opts.routesDir, reqUrl.pathname, c.req.method, JSON.parse(Buffer.from(buf).toString('utf8'))) } catch { /* non-json body โ€” skip recording */ } } const headers = new Headers(res.headers) headers.set('X-Mock-Proxy', upstream.href) if (opts.record) headers.set('X-Mock-Recorded', 'true') return new Response(buf, { status: res.status, headers }) } function recordMock(routesDir: string, pathname: string, method: string, body: unknown) { const segs = pathname.split('/').filter(Boolean) const base = segs.length ? segs.join('/') : 'index' const suffix = method.toLowerCase() === 'get' ? '' : `.${method.toLowerCase()}` const file = join(routesDir, `${base}${suffix}.json`) mkdirSync(dirname(file), { recursive: true }) writeFileSync(file, JSON.stringify(body, null, 2) + '\n') console.log(`โค“ recorded ${method} ${pathname} -> ${file}`) } function stripHopHeaders(h: Headers): Headers { const out = new Headers(h) for (const k of ['host', 'connection', 'content-length', 'accept-encoding']) out.delete(k) return out }