UNPKG

@lunora/cli

Version:

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

129 lines (126 loc) 5.38 kB
import { existsSync } from 'node:fs'; import { join } from 'node:path'; import { runCodegen } from '@lunora/codegen'; import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs'; import { a as renderCodegenHint } from '../packem_shared/codegen-error-DJG-ghs_.mjs'; import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs'; import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-v4hHpQd0.mjs'; import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-B4642rjE.mjs'; import { r as runSchemaDriftGate } from '../packem_shared/schema-drift-gate-BtBt0as0.mjs'; import { defaultSpawner } from '../packem_shared/createRecordingSpawner-WuSn20kb.mjs'; import { validateWranglerProject } from '@lunora/config'; const runTypecheckStep = async (cwd, spawner) => { if (!existsSync(join(cwd, "tsconfig.json"))) { return { warning: "no tsconfig.json found — skipping TypeScript type-check" }; } const exec = execArgsFor(detectPackageManager(cwd), "tsc", ["--noEmit", "-p", "tsconfig.json"]); const result = await spawner({ args: exec.args, command: exec.command, cwd }); return result.code === 0 ? {} : { error: `type errors: tsc --noEmit exited ${String(result.code)}` }; }; const HEALTH_PATH = "/_lunora/health"; const joinHealthUrl = (base) => (base.endsWith("/") ? base.slice(0, -1) : base) + HEALTH_PATH; const runHealthProbeStep = async (healthUrl, healthFetch) => { const url = joinHealthUrl(healthUrl); let response; try { response = await healthFetch(url); } catch (error) { const message = error instanceof Error ? error.message : String(error); return { error: `health probe failed: could not reach ${url} (${message})` }; } if (response.ok) { return {}; } return { error: `health probe failed: ${url} returned HTTP ${String(response.status)}` }; }; const probeHealthIfRequested = async (options, logger) => { if (options.healthUrl === void 0 || options.healthUrl === "") { return void 0; } const probe = await runHealthProbeStep(options.healthUrl, options.healthFetch ?? ((url) => fetch(url))); if (probe.error === void 0) { logger.success(`verify: health probe ok (${joinHealthUrl(options.healthUrl)})`); return void 0; } return probe.error; }; const reportVerifyResult = (logger, errors, warnings, wranglerPath) => { if (errors.length === 0 && warnings.length === 0) { logger.success("verify: project is valid"); return { code: 0, errors: [], warnings: [], wranglerPath }; } if (warnings.length > 0) { logger.warn("verify: warnings:"); for (const warning of warnings) { logger.warn(` - ${warning}`); } } if (errors.length > 0) { logger.error("verify: errors:"); for (const error of errors) { logger.error(` - ${error}`); const hint = renderCodegenHint(error); if (hint !== void 0) { logger.error(hint); } } return { code: 1, errors, warnings, wranglerPath }; } logger.success("verify: project is valid (with warnings)"); return { code: 0, errors: [], warnings, wranglerPath }; }; const runVerifyCommand = async (options) => { const cwd = options.cwd ?? process.cwd(); const logger = loggerForFormat(options.format, options.logger); const formatError = validateOutputFormat("verify", options.format); if (formatError !== void 0) { options.logger.error(formatError); return { code: 1, error: formatError, errors: [], warnings: [], wranglerPath: void 0 }; } const validation = validateWranglerProject({ projectRoot: cwd }); const errors = [...validation.report.errors]; const warnings = [...validation.report.warnings]; try { const codegen = runCodegen({ apiSpec: options.apiSpec, dryRun: true, projectRoot: cwd }); const gate = runSchemaDriftGate({ allowDrift: options.allowSchemaDrift === true, codegen, logger, readOnly: true }); if (gate.blocked) { errors.push(gate.reason); } } catch (error) { const message = error instanceof Error ? error.message : String(error); errors.push(`codegen failed: ${message}`); } if (options.typecheck !== false) { const typecheck = await runTypecheckStep(cwd, options.spawner ?? defaultSpawner); if (typecheck.error !== void 0) { errors.push(typecheck.error); } if (typecheck.warning !== void 0) { warnings.push(typecheck.warning); } } const healthError = await probeHealthIfRequested(options, logger); if (healthError !== void 0) { errors.push(healthError); } const result = reportVerifyResult(logger, errors, warnings, validation.wranglerPath); if (isJsonFormat(options.format)) { printJson(result); } return result; }; const execute = defineHandler(async ({ cwd, logger, options }) => { const result = await runVerifyCommand({ allowSchemaDrift: options.allowSchemaDrift === true, apiSpec: parseApiSpec(options.apiSpec), cwd, format: options.format, healthUrl: options.healthUrl, logger, // `--no-typecheck` is declared as a `no-*` option but cerebro exposes it // under the negated `typecheck` key (false when passed, true when absent). typecheck: options.typecheck === false ? false : void 0 }); return { code: result.code }; }); export { execute, runVerifyCommand };