@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands
129 lines (126 loc) • 5.38 kB
JavaScript
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 };