UNPKG

@lunora/cli

Version:

The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands

2 lines (1 loc) 10.1 kB
import{existsSync as p,readFileSync as w,realpathSync as h}from"node:fs";import{join as u}from"node:path";import{isPlaceholderValue as b,DEV_VARS_FILE as g,parseDevVariableEntries as k,discoverSchemaInfo as x,inferLunoraBindings as y}from"@lunora/config";import{findWranglerFile as _,readWranglerJsonc as $,validateWranglerProject as R,UNEXPORTED_CLASS_MARKER as v,collectExportGaps as S}from"@lunora/config/cloudflare";import{i as L,m as N,c as A}from"../packem_shared/vectorize-metadata-dXpl2_Ar.mjs";import{r as j,d as I}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{d as E}from"../packem_shared/command-Mmxzxyr5.mjs";import{i as O}from"../packem_shared/path-containment-CgxYZggb.mjs";const D=a=>{const e=a.trim();if(e==="")return!0;const r=e.toLowerCase();return r.includes("replace")||r.startsWith("<")&&r.endsWith(">")},C=a=>{const e=_(a);if(e===void 0)return{parsed:void 0,path:void 0};const{parsed:r}=$(e);return{parsed:r,path:e}},P=(a,e,r,n)=>{if(r===void 0){n.push({code:"wrangler-missing",fix:"Run `lunora init` (or `lunora dev`) to scaffold and reconcile wrangler.jsonc.",level:"fail",message:"wrangler.jsonc not found."});return}if(e===void 0){n.push({code:"wrangler-unparseable",fix:`Check ${r} is valid JSONC.`,level:"fail",message:`Could not parse ${r}.`});return}const{report:t}=R({projectRoot:a});for(const i of t.errors.filter(l=>l.includes(v)))n.push({code:"wrangler-class-unexported",level:"fail",message:i});for(const i of t.warnings.filter(l=>l.includes("LUNORA_ORIGIN_URL")))n.push({code:"scheduler-origin-missing",fix:"Set LUNORA_ORIGIN_URL in wrangler vars to the worker's public URL (or `wrangler secret put LUNORA_ORIGIN_URL`).",level:"warn",message:i});const o=i=>i.includes(v)||i.includes("SHARD");for(const i of t.errors.filter(l=>!o(l)))n.push({code:"wrangler-invalid",fix:"Fix the reported wrangler.jsonc problem; `lunora verify` reports the same set.",level:"fail",message:i});for(const i of t.warnings.filter(l=>!l.includes("LUNORA_ORIGIN_URL")))n.push({code:"wrangler-advisory",level:"warn",message:i});const c=t.errors.find(i=>i.includes("SHARD"));c===void 0?n.push({code:"wrangler-shard-binding-ok",level:"pass",message:"wrangler.jsonc present with a SHARD durable-object binding."}):n.push({code:"wrangler-shard-binding-missing",fix:"Run `lunora dev` to auto-reconcile, or add the binding manually.",level:"fail",message:c})},U=(a,e)=>{p(u(a,"lunora.json"))&&e.push({code:"stale-lunora-json",fix:'Move `target` / `remote` into `lunora.config.ts` (`export default { target: "…" }`), then delete lunora.json.',level:"warn",message:"lunora.json is present but no longer read — lunora.config.* replaced it, so any `target` or `remote` in it is being ignored."})},V=(a,e)=>{a===void 0||a.limits?.cpu_ms!==void 0||e.push({code:"cpu-limit-missing",fix:'Add `"limits": { "cpu_ms": 30000 }` to wrangler.jsonc, tuned to your slowest legitimate handler.',level:"info",message:"no limits.cpu_ms is set — a runaway handler can burn CPU up to the platform default before anything stops it."})},M=(a,e)=>{const r=(a?.r2_buckets??[]).map(n=>n?.bucket_name).filter(n=>typeof n=="string"&&n.length>0);r.length!==0&&e.push({code:"r2-lifecycle-unset",fix:"wrangler r2 bucket lifecycle list <bucket> — add an abort-incomplete-multipart rule if none exists.",level:"info",message:`R2 bucket(s) ${r.join(", ")}: verify a lifecycle rule aborts incomplete multipart uploads — orphaned parts are billed but invisible in a listing.`})},F=(a,e)=>{const{error:r,info:n}=x(a,"lunora");if(r!==void 0){e.push({code:"schema-unreadable",fix:"Fix the parse error, then re-run `lunora doctor`. `lunora codegen` reports the same failure with the offending source.",level:"fail",message:`lunora/schema.ts could not be parsed, so every schema-derived check was skipped: ${r}`});return}for(const t of n?.vectorMetadata??[]){const o=N(t.kind);if(o===void 0){e.push({code:"vector-metadata-unfilterable",fix:"Filter on a string, number or boolean column, or drop it from `metadata`.",level:"warn",message:`vector index "${t.index}" declares metadata "${t.property}", whose type Vectorize cannot filter on.`});continue}e.push({code:"vector-metadata-index-required",fix:`wrangler ${A({index:t.index,property:t.property,type:o}).join(" ")}`,level:"info",message:`vector index "${t.index}" filters on metadata "${t.property}" — that needs a Vectorize metadata index (\`lunora deploy\` creates it).`})}},T=(a,e)=>{if(a===void 0)return;const r=(a.d1_databases??[]).filter(Boolean);for(const n of r){const t=typeof n.database_id=="string"?n.database_id:"";if(D(t)){const o=typeof n.binding=="string"&&n.binding.length>0?n.binding:"<unnamed>";e.push({code:"d1-placeholder-id",fix:"Run `wrangler d1 create <name>` and paste the returned database_id into wrangler.jsonc.",level:"fail",message:`D1 binding "${o}" has a placeholder database_id ("${t||"<empty>"}").`})}}},W=(a,e)=>{if(a===void 0)return;const r=(a.send_email??[]).filter(Boolean);for(const n of r){const t=typeof n.destination_address=="string"?n.destination_address:"";if(t!==""&&b(t)){const o=typeof n.name=="string"&&n.name.length>0?n.name:"send_email";e.push({code:"email-destination-placeholder",fix:"Set destination_address to a verified Cloudflare Email Routing address.",level:"warn",message:`send_email binding "${o}" has a placeholder destination_address ("${t}").`})}}},G=(a,e)=>{const r=u(a,g);if(!p(r))return;let n;try{n=w(r,"utf8")}catch{return}const t=k(n).filter(o=>L(o.key)&&b(o.value)).map(o=>o.key);t.length>0&&e.push({code:"dev-vars-missing-secret",fix:"Run `lunora dev` to auto-generate secrets, or fill them in by hand.",level:"warn",message:`${g} has unfilled secret value(s): ${t.join(", ")}.`})},z=(a,e)=>{const{source:r}=j({cwd:a});r===void 0?e.push({code:"admin-token-missing",fix:"Set LUNORA_ADMIN_TOKEN (env or `.dev.vars`) to enable admin RPCs / studio.",level:"info",message:"LUNORA_ADMIN_TOKEN is not set."}):e.push({code:"admin-token-set",level:"pass",message:`LUNORA_ADMIN_TOKEN is set (${I(r)}).`})},K=async(a,e)=>{let r;try{r=await y({projectRoot:a})}catch(o){e.push({code:"declared-export-unchecked",fix:"Make sure the worker entry resolves (see `lunora codegen`), then re-run.",level:"warn",message:`could not check whether declared containers/workflows/agents are re-exported by the worker entry: ${o instanceof Error?o.message:String(o)}`});return}const n=S(r),t=[...r.containers.map(o=>({...o,kind:"container"})),...r.workflows.map(o=>({...o,kind:"workflow"})),...r.agents.map(o=>({...o,kind:"agent"}))];for(const o of t.filter(c=>c.exported))e.push({code:"declared-export-ok",level:"pass",message:`${o.kind} "${o.exportName}" is exported by the worker entry.`});for(const o of n)e.push({code:"declared-export-missing",fix:`Add \`export * from "./lunora/_generated/${o.module}"\` to your worker entry (or re-run \`vis generate lunora-${o.kind}\`).`,level:"fail",message:`${o.kind} "${o.exportName}" is declared but ${o.className} is not exported by the worker entry.`})},B=/^[\^~><= ]+/u,H=/^(?<core>\d+\.\d+\.\d+)(?:-(?<channel>[a-z]+)\.(?<counter>\d+))?$/u,J=a=>{const e=a.replace(B,"").trim(),r=H.exec(e);if(!r?.groups)return;const{channel:n,core:t,counter:o}=r.groups;return{channel:n??"",core:t??"",counter:o===void 0?void 0:Number.parseInt(o,10),raw:e}},q=(a,e)=>{const r=u(a,"package.json");if(!p(r))return;let n;try{n=JSON.parse(w(r,"utf8"))}catch{return}const t={...n.devDependencies,...n.dependencies},o=[];for(const[s,d]of Object.entries(t)){if(s!=="lunorash"&&!s.startsWith("@lunora/"))continue;const m=J(d);m&&o.push({name:s,version:m})}if(o.length<2)return;const c=s=>s.map(d=>`${d.name}@${d.version.raw}`).join(", "),i=new Set(o.map(s=>s.version.core)),l=new Set(o.map(s=>s.version.channel));if(i.size>1){e.push({code:"version-skew-cores",fix:'Run `pnpm update "@lunora/*" lunorash` (or `npm`/`yarn` equivalent) so every Lunora package moves to the same release.',level:"warn",message:`Lunora packages span ${String(i.size)} different versions: ${c(o)}.`});return}if(l.size>1){e.push({code:"version-skew-channels",fix:"Pick one channel (all stable, or all alpha/beta) and update the odd package out.",level:"warn",message:`Lunora packages mix release channels (${[...l].map(s=>s===""?"stable":s).join(" + ")}): ${c(o)}.`});return}const f=o.map(s=>s.version.counter).filter(s=>s!==void 0);if(f.length>1){const s=Math.min(...f),d=Math.max(...f);e.push({code:"version-counter-spread",level:"info",message:f.length===o.length&&s===d?`Lunora packages are all at ${[...l][0]??""}.${String(d)}.`:`Lunora pre-release counters span ${String(s)}${String(d)}: ${c(o)}.`})}},X=[u("@lunora","cli"),"lunorash"],Q=(a,e,r)=>{if(e===void 0)return;const n=[];for(const o of X){const c=u(a,"node_modules",o);if(p(c))try{n.push(h(c))}catch{}}if(n.length===0)return;let t;try{t=h(e)}catch{return}n.some(o=>O(o,t))||r.push({code:"cli-shadowed",fix:"Run the project's own CLI: `pnpm exec lunora …` (or `npx lunora …`).",level:"warn",message:`the running lunora (${t}) is not the project's own install (${n.join(", ")}).`})},Y=async a=>{const e=a.cwd??process.cwd(),r=[],{parsed:n,path:t}=C(e);P(e,n,t,r),T(n,r),W(n,r),V(n,r),M(n,r),G(e,r),z(e,r),q(e,r),F(e,r),U(e,r),Q(e,a.executablePath??process.argv[1],r),await K(e,r);const o={fail:0,info:0,pass:0,warn:0};for(const i of r)o[i.level]+=1;const c=o.fail>0?1:0;return{code:c,findings:r,ok:c===0,summary:o}},Z={fail:"FAIL",info:"INFO",pass:"PASS",warn:"WARN"},ee=(a,e)=>{e.info("lunora doctor — project preflight");for(const t of a.findings){const o=`[${Z[t.level]}] ${t.message}`;t.level==="fail"?e.error(o):t.level==="warn"?e.warn(o):e.info(o),t.fix!==void 0&&t.level!=="pass"&&e.info(` fix: ${t.fix}`)}const{fail:r,warn:n}=a.summary;r>0?e.error(`${String(r)} failure(s), ${String(n)} warning(s).`):n>0?e.warn(`0 failures, ${String(n)} warning(s).`):e.success("all checks passed.")},re=async a=>{const{logger:e}=a,r=await Y({...a});return ee(r,e),r},de=E(async({cwd:a,format:e,logger:r})=>{const n=await re({cwd:a,format:e,logger:r});return{code:n.code,data:{findings:n.findings,summary:n.summary}}});export{de as execute,Y as runDoctor,re as runDoctorCommand};