import { runCommand } from "./command"; import { type UniDeskConfig, repoRoot } from "./config"; import { jsonByteLength, previewJson } from "./preview"; export function coreInternalFetch(path: string, init?: { method?: string; body?: unknown }): unknown { if (!path.startsWith("/")) throw new Error("core internal path must start with /"); const code = ` const res = await fetch(${JSON.stringify(`http://127.0.0.1:8080${path}`)}, ${JSON.stringify({ method: init?.method ?? "GET", headers: init?.body === undefined ? undefined : { "content-type": "application/json" }, body: init?.body === undefined ? undefined : JSON.stringify(init.body), })}); const text = await res.text(); let body = null; try { body = text ? JSON.parse(text) : null; } catch { body = { text }; } console.log(JSON.stringify({ ok: res.ok, status: res.status, body })); `; const result = runCommand(["docker", "exec", "unidesk-backend-core", "bun", "-e", code], repoRoot); if (result.exitCode !== 0) { return { ok: false, exitCode: result.exitCode, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } try { return JSON.parse(result.stdout.trim()) as unknown; } catch { return { ok: true, stdoutTail: result.stdout.slice(-1200), stderrTail: result.stderr.slice(-1200) }; } } function requireId(value: string | undefined, command: string): string { if (value === undefined || value.length === 0) throw new Error(`${command} requires microservice id`); return value; } function requireProxyPath(value: string | undefined): string { if (value === undefined || value.length === 0) throw new Error("microservice proxy requires upstream path, for example /api/summary"); if (!value.startsWith("/")) throw new Error("microservice proxy upstream path must start with /"); return value; } function encodeId(value: string): string { return encodeURIComponent(value); } function numberOption(args: string[], name: string, defaultValue: number): number { const index = args.indexOf(name); if (index === -1) return defaultValue; const raw = args[index + 1]; const value = Number(raw); if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`); return value; } function stringOption(args: string[], name: string): string | undefined { const index = args.indexOf(name); if (index === -1) return undefined; const raw = args[index + 1]; if (raw === undefined || raw.length === 0) throw new Error(`${name} requires a non-empty value`); return raw; } function methodOption(args: string[]): string { const method = (stringOption(args, "--method") ?? "GET").toUpperCase(); if (!["GET", "HEAD", "POST", "DELETE", "PUT", "PATCH"].includes(method)) throw new Error(`unsupported --method ${method}`); return method; } export function summarizeMicroserviceProxyResponse(response: unknown, args: string[]): unknown { if (args.includes("--raw")) return response; const maxBodyBytes = numberOption(args, "--max-body-bytes", 60_000); if (typeof response !== "object" || response === null || Array.isArray(response)) return response; const record = response as Record; if (!("body" in record)) return response; const bodyBytes = jsonByteLength(record.body); if (bodyBytes <= maxBodyBytes) return response; const rest = { ...record }; delete rest.body; return { ...rest, bodyOmitted: true, bodyBytes, bodyMaxBytes: maxBodyBytes, bodyPreview: previewJson(record.body, { maxDepth: 3, maxArrayItems: 3, maxObjectKeys: 16, maxStringLength: 320 }), rawHint: "Re-run with --raw for the full body, or add/tighten __unideskArrayLimit=: in the proxied path.", }; } export async function runMicroserviceCommand(_config: UniDeskConfig, args: string[]): Promise { const [action = "list", idArg, pathArg] = args; if (action === "list") return coreInternalFetch("/api/microservices"); if (action === "status") { const id = requireId(idArg, "microservice status"); return coreInternalFetch(`/api/microservices/${encodeId(id)}/status`); } if (action === "health") { const id = requireId(idArg, "microservice health"); return coreInternalFetch(`/api/microservices/${encodeId(id)}/health`); } if (action === "proxy") { const id = requireId(idArg, "microservice proxy"); const path = requireProxyPath(pathArg); return summarizeMicroserviceProxyResponse(coreInternalFetch(`/api/microservices/${encodeId(id)}/proxy${path}`, { method: methodOption(args) }), args); } throw new Error("microservice command must be one of: list, status, health, proxy"); }