import { appendFile, mkdir } from "node:fs/promises"; const port = 3001; const logDir = "cw-api-logs"; const logFilePath = `${logDir}/test-webserver-${new Date().toISOString().replace(/[:.]/g, "-")}.jsonl`; const jsonBodyMethods = ["POST", "PUT", "PATCH", "DELETE"]; type ParsedJson = Record | unknown[]; type EventSummary = ReturnType; const color = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", blue: "\x1b[34m", yellow: "\x1b[33m", magenta: "\x1b[35m", green: "\x1b[32m", gray: "\x1b[90m", }; const paint = (value: string, tone: keyof typeof color) => `${color[tone]}${value}${color.reset}`; const safeParseJson = (value: string): ParsedJson | null => { try { const parsed = JSON.parse(value); const isObject = typeof parsed === "object" && parsed !== null; return isObject ? (parsed as ParsedJson) : null; } catch { return null; } }; const parseEntity = (value: unknown): ParsedJson | null => { if (typeof value === "string") return safeParseJson(value); if (typeof value !== "object" || value === null) return null; return value as ParsedJson; }; const asObject = (value: ParsedJson | null): Record | null => { if (!value) return null; if (Array.isArray(value)) return null; return value; }; const parseJsonStringFields = ( value: Record | null, ): Record | null => { if (!value) return null; return Object.entries(value).reduce>( (acc, [key, current]) => { if (typeof current !== "string") { acc[key] = current; return acc; } const looksLikeJson = current.startsWith("{") || current.startsWith("["); if (!looksLikeJson) { acc[key] = current; return acc; } const parsed = safeParseJson(current); acc[key] = parsed ?? current; return acc; }, {}, ); }; const parseQuery = (url: URL) => { const entries = [...url.searchParams.entries()]; const params = entries.reduce>((acc, [key, value]) => { acc[key] = value; return acc; }, {}); const rawQuery = url.search.startsWith("?") ? url.search.slice(1) : url.search; const firstSegment = rawQuery.split("&")[0] ?? ""; const hasEquals = firstSegment.includes("="); const inferredId = !hasEquals && firstSegment ? firstSegment : null; return { params, inferredId, }; }; const buildSummary = ( parsedBody: Record | null, parsedEntity: Record | null, ) => { if (!parsedBody) return null; return { messageId: parsedBody.MessageId ?? null, action: parsedBody.Action ?? null, type: parsedBody.Type ?? null, id: parsedBody.ID ?? null, memberId: parsedBody.MemberId ?? null, entityStatus: parsedEntity?.StatusName ?? parsedEntity?.TicketStatus ?? parsedEntity?.Status ?? null, entitySummary: parsedEntity?.Summary ?? parsedEntity?.CompanyName ?? null, entityUpdatedBy: parsedEntity?.UpdatedBy ?? null, entityLastUpdated: parsedEntity?.LastUpdatedUTC ?? parsedEntity?.LastUpdated ?? null, }; }; const displayTerminalEvent = ( method: string, routePath: string, query: { params: Record; inferredId: string | null }, summary: EventSummary, timestamp: string, ) => { const id = String(summary?.id ?? query.inferredId ?? "-"); const action = String(summary?.action ?? query.params.action ?? "-"); const eventType = String(summary?.type ?? routePath.split("/")[1] ?? "-"); const actor = String( summary?.entityUpdatedBy ?? query.params.memberId ?? summary?.memberId ?? "-", ); const status = String(summary?.entityStatus ?? "-"); const title = String(summary?.entitySummary ?? "-"); const methodTone = method === "GET" ? "green" : "yellow"; console.log(); console.log( `${paint("●", "cyan")} ${paint(method, methodTone)} ${paint(routePath, "blue")} ${paint(timestamp, "gray")}`, ); console.log( `${paint("type", "magenta")}: ${paint(eventType, "bold")} ${paint("action", "magenta")}: ${action} ${paint("id", "magenta")}: ${id}`, ); console.log( `${paint("actor", "magenta")}: ${paint(actor, "cyan")} ${paint("status", "magenta")}: ${status}`, ); console.log(`${paint("title", "magenta")}: ${title}`); }; const writeLogRecord = async (record: Record) => { await appendFile(logFilePath, `${JSON.stringify(record)}\n`, "utf8"); }; await mkdir(logDir, { recursive: true }); Bun.serve({ port, async fetch(request) { const url = new URL(request.url); const routePath = `${url.pathname}${url.search}`; const method = request.method; const query = parseQuery(url); const startedAt = new Date().toISOString(); const rawBody = jsonBodyMethods.includes(method) ? await request.text() : ""; const parsedJson = safeParseJson(rawBody); const parsedBody = asObject(parsedJson); const parsedBodyExpanded = parseJsonStringFields(parsedBody); const parsedEntity = asObject(parseEntity(parsedBodyExpanded?.Entity)); const summary = buildSummary(parsedBodyExpanded, parsedEntity); const responseBody = { success: true, method, path: routePath, timestamp: startedAt, summary, }; const responseStatus = 200; displayTerminalEvent(method, routePath, query, summary, startedAt); await writeLogRecord({ timestamp: startedAt, request: { method, path: routePath, query, headers: Object.fromEntries(request.headers.entries()), bodyRaw: rawBody || null, bodyParsed: parsedBodyExpanded, entityParsed: parsedEntity, summary, }, response: { status: responseStatus, body: responseBody, }, }); return Response.json(responseBody, { status: responseStatus }); }, }); console.log(`Test webserver listening on http://localhost:${port}`); console.log(`Response/request log file: ${logFilePath}`);