@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands
132 lines (129 loc) • 5.74 kB
JavaScript
import { r as resolveAdminBaseUrl } from '../packem_shared/admin-url-4UzT-CI4.mjs';
import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
const GET_FUNCTION_STATS_OP = "__lunora_admin__:getFunctionStats";
const DEFAULT_LIMIT = 10;
const TRAILING_SLASH = /\/$/u;
const toInsightRow = (stat, rate) => {
return {
calls: stat.calls,
conflicts: stat.conflicts ?? 0,
errors: stat.errors,
lastErrorMessage: stat.lastErrorMessage,
maxDurationMs: stat.maxDurationMs,
meanDurationMs: stat.calls === 0 ? 0 : stat.totalDurationMs / stat.calls,
path: stat.path,
rate
};
};
const buildInsightsReport = (functions, limit) => {
const writeContention = functions.filter((stat) => (stat.conflicts ?? 0) > 0).map((stat) => toInsightRow(stat, stat.calls === 0 ? 0 : (stat.conflicts ?? 0) / stat.calls)).toSorted((a, b) => b.rate - a.rate || b.conflicts - a.conflicts).slice(0, limit);
const errorHotspots = functions.filter((stat) => stat.errors > 0).map((stat) => toInsightRow(stat, stat.calls === 0 ? 0 : stat.errors / stat.calls)).toSorted((a, b) => b.rate - a.rate || b.errors - a.errors).slice(0, limit);
const latencyOutliers = functions.map((stat) => toInsightRow(stat, 0)).toSorted((a, b) => b.maxDurationMs - a.maxDurationMs).slice(0, limit);
return { errorHotspots, latencyOutliers, totalFunctions: functions.length, writeContention };
};
const percent = (rate) => `${(rate * 100).toFixed(1)}%`;
const formatMs = (ms) => ms < 1e3 ? `${Math.round(ms).toString()}ms` : `${(ms / 1e3).toFixed(2)}s`;
const formatSection = (heading, rows, emptyNote, renderRow) => [
heading,
...rows.length === 0 ? [` ${emptyNote}`] : rows.map((row) => ` ${renderRow(row)}`)
];
const formatInsightsReport = (report) => {
const errorTail = (row) => row.lastErrorMessage ? ` — ${row.lastErrorMessage}` : "";
return [
`Insights over ${report.totalFunctions.toString()} function${report.totalFunctions === 1 ? "" : "s"}`,
"",
...formatSection(
"Write-conflict hot-spots (OCC contention — candidates for sharding):",
report.writeContention,
"none — no write conflicts observed",
(row) => `${row.path} ${row.conflicts.toString()}/${row.calls.toString()} calls (${percent(row.rate)})`
),
"",
...formatSection(
"Error hot-spots:",
report.errorHotspots,
"none — no errors observed",
(row) => `${row.path} ${row.errors.toString()}/${row.calls.toString()} calls (${percent(row.rate)})${errorTail(row)}`
),
"",
...formatSection(
"Latency outliers (slowest single call):",
report.latencyOutliers,
"none — no functions have run",
(row) => `${row.path} max ${formatMs(row.maxDurationMs)}, mean ${formatMs(row.meanDurationMs)} over ${row.calls.toString()} calls`
)
].join("\n");
};
const resolveLimit = (raw) => {
if (raw === void 0 || !Number.isFinite(raw) || raw <= 0) {
return DEFAULT_LIMIT;
}
return Math.floor(raw);
};
const runInsightsCommand = async (options) => {
if (options.prod && options.url === void 0) {
options.logger.error("--prod requires an explicit --url (refusing to report from the implicit localhost worker)");
return { code: 1 };
}
const token = options.token ?? process.env.LUNORA_ADMIN_TOKEN;
if (!token) {
options.logger.error("admin token required — pass --token or set LUNORA_ADMIN_TOKEN");
return { code: 1 };
}
const baseUrl = resolveAdminBaseUrl(options.url, options.logger);
if (baseUrl === void 0) {
return { code: 1 };
}
const requestUrl = `${baseUrl.replace(TRAILING_SLASH, "")}/_lunora/rpc`;
const fetchImpl = globalThis.fetch;
if (typeof fetchImpl !== "function") {
throw new TypeError("no fetch implementation available — pass fetchImpl or run on Node >= 18");
}
const payload = { args: {}, functionPath: GET_FUNCTION_STATS_OP };
if (options.shard !== void 0) {
payload.shardKey = options.shard;
}
options.logger.info(`POST ${requestUrl} -> insights`);
const response = await fetchImpl(requestUrl, {
body: JSON.stringify(payload),
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
method: "POST"
});
const text = await response.text();
if (!response.ok) {
options.logger.error(`insights failed: HTTP ${String(response.status)}: ${text}`);
return { code: 1 };
}
let parsed;
try {
parsed = JSON.parse(text);
} catch {
options.logger.error(`insights failed: worker returned non-JSON: ${text}`);
return { code: 1 };
}
const result = parsed.result ?? parsed;
const { functions } = result;
if (!Array.isArray(functions)) {
options.logger.error("insights failed: response carried no `functions` array");
return { code: 1 };
}
const report = buildInsightsReport(functions, resolveLimit(options.limit));
options.logger.info(options.json ? JSON.stringify(report, void 0, 2) : formatInsightsReport(report));
return { code: 0, report };
};
const execute = defineHandler(({ cwd, logger, options }) => {
const limit = options.limit === void 0 ? void 0 : Number.parseInt(options.limit, 10);
return runInsightsCommand({
json: options.json,
limit,
logger,
prod: options.prod,
shard: options.shard,
token: options.token,
// Fall back to the `.lunora/project.json` link when `--prod` is set, so a
// linked checkout doesn't need --url repeated for prod insights.
url: resolveProductionWorkerUrl({ cwd, prod: options.prod === true, url: options.url })
});
});
export { buildInsightsReport, execute, formatInsightsReport, runInsightsCommand };