UNPKG

@lunora/cli

Version:

The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands

132 lines (129 loc) 5.74 kB
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 };