fix: remove nested .git folders, re-add as normal directories
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
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<string, unknown> | unknown[];
|
||||
type EventSummary = ReturnType<typeof buildSummary>;
|
||||
|
||||
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<string, unknown> | null => {
|
||||
if (!value) return null;
|
||||
if (Array.isArray(value)) return null;
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const parseJsonStringFields = (
|
||||
value: Record<string, unknown> | null,
|
||||
): Record<string, unknown> | null => {
|
||||
if (!value) return null;
|
||||
|
||||
return Object.entries(value).reduce<Record<string, unknown>>(
|
||||
(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<Record<string, string>>((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<string, unknown> | null,
|
||||
parsedEntity: Record<string, unknown> | 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<string, string>; 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<string, unknown>) => {
|
||||
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}`);
|
||||
Reference in New Issue
Block a user