@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) • 7.81 kB
JavaScript
import{existsSync as p,readFileSync as v,realpathSync as g}from"node:fs";import{join as u}from"node:path";import{isPlaceholderValue as w,DEV_VARS_FILE as h,parseDevVariableEntries as x,discoverSchemaInfo as y,inferLunoraBindings as $}from"@lunora/config";import{findWranglerFile as b,readWranglerJsonc as k,validateWranglerConfig as S,collectExportGaps as _}from"@lunora/config/cloudflare";import{i as N}from"../packem_shared/command-0l-ZPhIX.mjs";import{i as R,t as j,d as A,s as L}from"../packem_shared/output-format-Cv3aLqhP.mjs";import{e as O}from"../packem_shared/path-containment-CcTCKfzV.mjs";import{o as D,p as E}from"../packem_shared/vectorize-metadata-BvZbbqUD.mjs";const C=/(?:KEY|PASSWORD|SECRET|TOKEN)$/u,I=a=>{const e=a.trim();if(e==="")return!0;const n=e.toLowerCase();return n.includes("replace")||n.startsWith("<")&&n.endsWith(">")},W=a=>{const e=b(a);if(e===void 0)return{parsed:void 0,path:void 0};const{parsed:n}=k(e);return{parsed:n,path:e}},M=(a,e,n)=>{if(e===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(a===void 0){n.push({code:"wrangler-unparseable",fix:`Check ${e} is valid JSONC.`,level:"fail",message:`Could not parse ${e}.`});return}const r=S(a).errors.find(s=>s.includes("SHARD"));r===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:r})},z=(a,e)=>{const{info:n}=y(a,"lunora");for(const r of n?.vectorMetadata??[]){const s=E(r.kind);if(s===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 "${r.index}" declares metadata "${r.property}", whose type Vectorize cannot filter on.`});continue}e.push({code:"vector-metadata-index-required",fix:`wrangler ${D({index:r.index,property:r.property,type:s}).join(" ")}`,level:"info",message:`vector index "${r.index}" filters on metadata "${r.property}" — that needs a Vectorize metadata index (\`lunora deploy\` creates it).`})}},K=(a,e)=>{if(a===void 0)return;const n=(a.d1_databases??[]).filter(Boolean);for(const r of n){const s=typeof r.database_id=="string"?r.database_id:"";if(I(s)){const o=typeof r.binding=="string"&&r.binding.length>0?r.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 ("${s||"<empty>"}").`})}}},T=(a,e)=>{if(a===void 0)return;const n=(a.send_email??[]).filter(Boolean);for(const r of n){const s=typeof r.destination_address=="string"?r.destination_address:"";if(s!==""&&w(s)){const o=typeof r.name=="string"&&r.name.length>0?r.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 ("${s}").`})}}},V=(a,e)=>{const n=u(a,h);if(!p(n))return;let r;try{r=v(n,"utf8")}catch{return}const s=x(r).filter(o=>C.test(o.key)&&w(o.value)).map(o=>o.key);s.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:`${h} has unfilled secret value(s): ${s.join(", ")}.`})},F=a=>{const e=process.env.LUNORA_ADMIN_TOKEN;e===void 0||e.trim()===""?a.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."}):a.push({code:"admin-token-set",level:"pass",message:"LUNORA_ADMIN_TOKEN is set."})},P=async(a,e)=>{let n;try{n=await $({projectRoot:a})}catch{return}const r=_(n),s=[...n.containers.map(o=>({...o,kind:"container"})),...n.workflows.map(o=>({...o,kind:"workflow"})),...n.agents.map(o=>({...o,kind:"agent"}))];for(const o of s.filter(i=>i.exported))e.push({code:"declared-export-ok",level:"pass",message:`${o.kind} "${o.exportName}" is exported by the worker entry.`});for(const o of r)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.`})},U=/^[\^~><= ]+/u,B=/^(?<core>\d+\.\d+\.\d+)(?:-(?<channel>[a-z]+)\.(?<counter>\d+))?$/u,J=a=>{const e=a.replace(U,"").trim(),n=B.exec(e);if(!n?.groups)return;const{channel:r,core:s,counter:o}=n.groups;return{channel:r??"",core:s??"",counter:o===void 0?void 0:Number.parseInt(o,10),raw:e}},q=(a,e)=>{const n=u(a,"package.json");if(!p(n))return;let r;try{r=JSON.parse(v(n,"utf8"))}catch{return}const s={...r.devDependencies,...r.dependencies},o=[];for(const[t,l]of Object.entries(s)){if(t!=="lunorash"&&!t.startsWith("@lunora/"))continue;const m=J(l);m&&o.push({name:t,version:m})}if(o.length<2)return;const i=t=>t.map(l=>`${l.name}@${l.version.raw}`).join(", "),d=new Set(o.map(t=>t.version.core)),f=new Set(o.map(t=>t.version.channel));if(d.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(d.size)} different versions: ${i(o)}.`});return}if(f.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 (${[...f].map(t=>t===""?"stable":t).join(" + ")}): ${i(o)}.`});return}const c=o.map(t=>t.version.counter).filter(t=>t!==void 0);if(c.length>1){const t=Math.min(...c),l=Math.max(...c);e.push({code:"version-counter-spread",level:"info",message:c.length===o.length&&t===l?`Lunora packages are all at ${[...f][0]??""}.${String(l)}.`:`Lunora pre-release counters span ${String(t)}–${String(l)}: ${i(o)}.`})}},H=[u("@lunora","cli"),"lunorash"],G=(a,e,n)=>{if(e===void 0)return;const r=[];for(const o of H){const i=u(a,"node_modules",o);if(p(i))try{r.push(g(i))}catch{}}if(r.length===0)return;let s;try{s=g(e)}catch{return}r.some(o=>O(o,s))||n.push({code:"cli-shadowed",fix:"Run the project's own CLI: `pnpm exec lunora …` (or `npx lunora …`).",level:"warn",message:`the running lunora (${s}) is not the project's own install (${r.join(", ")}).`})},Y=async a=>{const e=a.cwd??process.cwd(),n=[],{parsed:r,path:s}=W(e);M(r,s,n),K(r,n),T(r,n),V(e,n),F(n),q(e,n),z(e,n),G(e,a.executablePath??process.argv[1],n),await P(e,n);const o={fail:0,info:0,pass:0,warn:0};for(const d of n)o[d.level]+=1;const i=o.fail>0?1:0;return{code:i,findings:n,ok:i===0,summary:o}},Q={fail:"FAIL",info:"INFO",pass:"PASS",warn:"WARN"},X=(a,e)=>{e.info("lunora doctor — project preflight");for(const s of a.findings){const o=`[${Q[s.level]}] ${s.message}`;s.level==="fail"?e.error(o):s.level==="warn"?e.warn(o):e.info(o),s.fix!==void 0&&s.level!=="pass"&&e.info(` fix: ${s.fix}`)}const{fail:n,warn:r}=a.summary;n>0?e.error(`${String(n)} failure(s), ${String(r)} warning(s).`):r>0?e.warn(`0 failures, ${String(r)} warning(s).`):e.success("all checks passed.")},Z=async a=>{const e=R("doctor",a.format);if(e!==void 0)return a.logger.error(e),{code:1,findings:[],ok:!1,summary:{fail:0,info:0,pass:0,warn:0}};const n=L(a.format,a.logger),r=await Y({...a});return X(r,n),j(a.format)&&A(r),r},le=N(async({cwd:a,logger:e,options:n})=>({code:(await Z({cwd:a,format:n.format,logger:e})).code}));export{le as execute,Y as runDoctor,Z as runDoctorCommand};