UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

309 lines (307 loc) 8.85 kB
import { colors, divider, frames, gradients, icons } from "../utils/theme.mjs"; import { linkify } from "../utils/linkify.mjs"; import { Box, Text, render, useApp, useInput } from "ink"; import { useEffect, useState } from "react"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; //#region src/components/dev-view.tsx const PLUGIN_PREFIX = "plugin:"; function StatusIcon({ status }) { switch (status) { case "pending": return /* @__PURE__ */ jsx(Text, { color: "gray", children: icons.pending }); case "starting": return /* @__PURE__ */ jsx(Text, { color: "#00ffff", children: icons.scan }); case "ready": return /* @__PURE__ */ jsx(Text, { color: "#00ff41", children: icons.ok }); case "error": return /* @__PURE__ */ jsx(Text, { color: "#ff3366", children: icons.err }); } } function getServiceColor(name) { if (name.startsWith(PLUGIN_PREFIX)) return "#ffaa00"; return name === "host" ? "#00ffff" : name === "ui" ? "#ff00ff" : "#0080ff"; } function getDisplayName(name) { return name.startsWith(PLUGIN_PREFIX) ? name.slice(7).toUpperCase() : name.toUpperCase(); } function isPlugin(name) { return name.startsWith(PLUGIN_PREFIX); } function getSectionedProcesses(processes) { const plugins = processes.filter((p) => isPlugin(p.name)); const services = processes.filter((p) => !isPlugin(p.name)); const sections = []; if (plugins.length > 0) sections.push({ key: "plugins", title: "PLUGINS", processes: plugins }); if (services.length > 0) sections.push({ key: "services", title: "SERVICES", processes: services }); return sections; } function getColumnWidths(processes) { return { name: Math.max(6, ...processes.map((p) => getDisplayName(p.name).length)), source: Math.max(10, ...processes.map((p) => p.source ? `(${p.source})`.length : 0)) }; } function ProcessRow({ proc, nameWidth, sourceWidth }) { const color = getServiceColor(proc.name); const isRemote = proc.source === "remote"; const isHost = proc.name === "host"; const showPort = proc.port > 0 && (isHost || !isRemote); const portStr = showPort ? `:${proc.port}` : ""; const sourceLabel = proc.source ? ` (${proc.source})` : ""; const statusText = proc.status === "pending" ? "waiting" : proc.status === "starting" ? "starting" : proc.status === "ready" ? isRemote && !isHost ? "loaded" : "running" : "failed"; return /* @__PURE__ */ jsxs(Box, { children: [ /* @__PURE__ */ jsx(Text, { children: " " }), /* @__PURE__ */ jsx(StatusIcon, { status: proc.status }), /* @__PURE__ */ jsx(Text, { children: " " }), /* @__PURE__ */ jsx(Text, { color, bold: true, children: getDisplayName(proc.name).padEnd(nameWidth) }), /* @__PURE__ */ jsx(Text, { color: "gray", children: sourceLabel.padEnd(sourceWidth) }), /* @__PURE__ */ jsx(Text, { color: proc.status === "ready" ? "#00ff41" : "gray", children: statusText }), showPort && /* @__PURE__ */ jsxs(Text, { color: "#00ffff", children: [" ", portStr] }) ] }); } function SectionHeader({ title }) { return /* @__PURE__ */ jsx(Box, { marginBottom: 0, marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "#00ffff", bold: true, children: title }) }); } function LogLine({ entry }) { return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Text, { color: getServiceColor(entry.source), children: [ "[", entry.source, "]" ] }), /* @__PURE__ */ jsxs(Text, { color: entry.isError ? "#ff3366" : void 0, children: [" ", linkify(entry.line)] })] }); } function truncateUrl(url, maxLen) { if (url.length <= maxLen) return url; try { const host = new URL(url).host; if (host.length > maxLen - 10) return `${host.slice(0, maxLen - 13)}...`; return host; } catch { return `${url.slice(0, maxLen - 3)}...`; } } function DevView({ processes, logs, description, proxyTarget, onExit, onExportLogs }) { const { exit } = useApp(); const [isShuttingDown, setIsShuttingDown] = useState(false); useInput((input, key) => { if (isShuttingDown) return; if (input === "q" || key.ctrl && input === "c") { setIsShuttingDown(true); Promise.resolve(onExit?.()).then(() => { exit(); }); } if (input === "l") { setIsShuttingDown(true); Promise.resolve(onExportLogs?.()).then(() => { exit(); }); } }); const readyCount = processes.filter((p) => p.status === "ready").length; const total = processes.length; const allReady = readyCount === total; const hostPort = processes.find((p) => p.name === "host")?.port || 3e3; const recentLogs = logs.slice(-12); const sectionedProcesses = getSectionedProcesses(processes); const columnWidths = getColumnWidths(processes); return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [ /* @__PURE__ */ jsx(Box, { marginBottom: 0, children: /* @__PURE__ */ jsx(Text, { color: "#00ffff", children: frames.top(52) }) }), /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [ " ", icons.run, " ", gradients.cyber(description.toUpperCase()) ] }) }), /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "#00ffff", children: frames.bottom(52) }) }), allReady && /* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "#00ff41", children: [ " ", icons.app, " APP READY" ] }) }), /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "#00ff41", bold: true, children: [ " ", icons.arrow, " http://localhost:", hostPort ] }) })] }), proxyTarget && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { color: "#ffaa00", children: [ " ", icons.arrow, " API PROXY → ", truncateUrl(proxyTarget, 38) ] }) }), /* @__PURE__ */ jsx(Box, { marginTop: 0, marginBottom: 0, children: /* @__PURE__ */ jsx(Text, { children: colors.dim(divider(52)) }) }), sectionedProcesses.map((section) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [/* @__PURE__ */ jsx(SectionHeader, { title: section.title }), section.processes.map((proc) => /* @__PURE__ */ jsx(ProcessRow, { proc, nameWidth: columnWidths.name, sourceWidth: columnWidths.source }, proc.name))] }, section.key)), /* @__PURE__ */ jsx(Box, { marginTop: 1, marginBottom: 0, children: /* @__PURE__ */ jsx(Text, { children: colors.dim(divider(52)) }) }), /* @__PURE__ */ jsxs(Box, { marginTop: 0, children: [/* @__PURE__ */ jsxs(Text, { color: allReady ? "#00ff41" : "#00ffff", children: [" ", allReady ? `${icons.ok} All ${total} services running` : `${icons.scan} ${readyCount}/${total} ready`] }), /* @__PURE__ */ jsxs(Text, { color: "gray", children: [ " ", icons.dot, " q quit ", icons.dot, " l logs" ] })] }), recentLogs.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Box, { marginTop: 1, marginBottom: 0, children: /* @__PURE__ */ jsx(Text, { children: colors.dim(divider(52)) }) }), /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 0, children: recentLogs.map((entry) => /* @__PURE__ */ jsx(LogLine, { entry }, entry.id)) })] }) ] }); } function renderDevView(initialProcesses, description, env, onExit, onExportLogs) { let processes = [...initialProcesses]; let logs = []; let rerender = null; const proxyTarget = env.API_PROXY; let logSeq = 0; let lastLogKey = null; const updateProcess = (name, status, message) => { processes = processes.map((p) => p.name === name ? { ...p, status, message } : p); rerender?.(); }; const addLog = (source, line, isError = false) => { const nextKey = `${source}:${isError ? "1" : "0"}:${line}`; if (nextKey === lastLogKey) return; lastLogKey = nextKey; logs = [...logs, { id: `${Date.now()}-${++logSeq}`, source, line, timestamp: Date.now(), isError }]; if (logs.length > 100) logs = logs.slice(-100); rerender?.(); }; function DevViewWrapper() { const [, forceUpdate] = useState(0); useEffect(() => { rerender = () => forceUpdate((n) => n + 1); return () => { rerender = null; }; }, []); return /* @__PURE__ */ jsx(DevView, { processes, logs, description, proxyTarget, onExit, onExportLogs }); } const { unmount } = render(/* @__PURE__ */ jsx(DevViewWrapper, {})); return { updateProcess, addLog, unmount }; } //#endregion export { renderDevView }; //# sourceMappingURL=dev-view.mjs.map