feat: add provider-backed microservices
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { runCommand } from "./command";
|
||||
import { type UniDeskConfig, repoRoot } from "./config";
|
||||
import { jsonByteLength, previewJson } from "./preview";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<string, unknown>;
|
||||
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=<path>:<limit> in the proxied path.",
|
||||
};
|
||||
}
|
||||
|
||||
export async function runMicroserviceCommand(_config: UniDeskConfig, args: string[]): Promise<unknown> {
|
||||
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}`), args);
|
||||
}
|
||||
throw new Error("microservice command must be one of: list, status, health, proxy");
|
||||
}
|
||||
Reference in New Issue
Block a user