everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
310 lines (308 loc) • 10.5 kB
JavaScript
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
const require_theme = require('../utils/theme.cjs');
const require_linkify = require('../utils/linkify.cjs');
let ink = require("ink");
let react = require("react");
let react_jsx_runtime = require("react/jsx-runtime");
//#region src/components/dev-view.tsx
const PLUGIN_PREFIX = "plugin:";
function StatusIcon({ status }) {
switch (status) {
case "pending": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: "gray",
children: require_theme.icons.pending
});
case "starting": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: "#00ffff",
children: require_theme.icons.scan
});
case "ready": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: "#00ff41",
children: require_theme.icons.ok
});
case "error": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: "#ff3366",
children: require_theme.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__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: " " }),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status: proc.status }),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: " " }),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color,
bold: true,
children: getDisplayName(proc.name).padEnd(nameWidth)
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: "gray",
children: sourceLabel.padEnd(sourceWidth)
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: proc.status === "ready" ? "#00ff41" : "gray",
children: statusText
}),
showPort && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
color: "#00ffff",
children: [" ", portStr]
})
] });
}
function SectionHeader({ title }) {
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
marginBottom: 0,
marginTop: 1,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: "#00ffff",
bold: true,
children: title
})
});
}
function LogLine({ entry }) {
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
color: getServiceColor(entry.source),
children: [
"[",
entry.source,
"]"
]
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
color: entry.isError ? "#ff3366" : void 0,
children: [" ", require_linkify.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 } = (0, ink.useApp)();
const [isShuttingDown, setIsShuttingDown] = (0, react.useState)(false);
(0, ink.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__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
flexDirection: "column",
children: [
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
marginBottom: 0,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: "#00ffff",
children: require_theme.frames.top(52)
})
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, { children: [
" ",
require_theme.icons.run,
" ",
require_theme.gradients.cyber(description.toUpperCase())
] }) }),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
marginBottom: 1,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, {
color: "#00ffff",
children: require_theme.frames.bottom(52)
})
}),
allReady && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
marginBottom: 1,
flexDirection: "column",
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
color: "#00ff41",
children: [
" ",
require_theme.icons.app,
" APP READY"
]
}) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
color: "#00ff41",
bold: true,
children: [
" ",
require_theme.icons.arrow,
" http://localhost:",
hostPort
]
}) })]
}),
proxyTarget && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
marginBottom: 1,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
color: "#ffaa00",
children: [
" ",
require_theme.icons.arrow,
" API PROXY → ",
truncateUrl(proxyTarget, 38)
]
})
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
marginTop: 0,
marginBottom: 0,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: require_theme.colors.dim(require_theme.divider(52)) })
}),
sectionedProcesses.map((section) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
flexDirection: "column",
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionHeader, { title: section.title }), section.processes.map((proc) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProcessRow, {
proc,
nameWidth: columnWidths.name,
sourceWidth: columnWidths.source
}, proc.name))]
}, section.key)),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
marginTop: 1,
marginBottom: 0,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: require_theme.colors.dim(require_theme.divider(52)) })
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Box, {
marginTop: 0,
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
color: allReady ? "#00ff41" : "#00ffff",
children: [" ", allReady ? `${require_theme.icons.ok} All ${total} services running` : `${require_theme.icons.scan} ${readyCount}/${total} ready`]
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ink.Text, {
color: "gray",
children: [
" ",
require_theme.icons.dot,
" q quit ",
require_theme.icons.dot,
" l logs"
]
})]
}),
recentLogs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
marginTop: 1,
marginBottom: 0,
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Text, { children: require_theme.colors.dim(require_theme.divider(52)) })
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ink.Box, {
flexDirection: "column",
marginTop: 0,
children: recentLogs.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.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] = (0, react.useState)(0);
(0, react.useEffect)(() => {
rerender = () => forceUpdate((n) => n + 1);
return () => {
rerender = null;
};
}, []);
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevView, {
processes,
logs,
description,
proxyTarget,
onExit,
onExportLogs
});
}
const { unmount } = (0, ink.render)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DevViewWrapper, {}));
return {
updateProcess,
addLog,
unmount
};
}
//#endregion
exports.renderDevView = renderDevView;
//# sourceMappingURL=dev-view.cjs.map