@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands
2 lines (1 loc) • 6.16 kB
JavaScript
import{existsSync as g,readFileSync as m}from"node:fs";import{join as h}from"node:path";import{findWranglerFile as w,readWranglerJsonc as $,validateWranglerConfig as x,isPlaceholderValue as v,DEV_VARS_FILE as p,parseDevVariableEntries as y,discoverSchemaInfo as b,inferLunoraBindings as S}from"@lunora/config";import{i as _}from"../packem_shared/command-Bjv4VmYA.mjs";import{o as N,p as R}from"../packem_shared/vectorize-metadata-BvZbbqUD.mjs";const k=/(?:KEY|PASSWORD|SECRET|TOKEN)$/u,A=a=>{const e=a.trim();if(e==="")return!0;const n=e.toLowerCase();return n.includes("replace")||n.startsWith("<")&&n.endsWith(">")},j=a=>{const e=w(a);if(e===void 0)return{parsed:void 0,path:void 0};const{parsed:n}=$(e);return{parsed:n,path:e}},O=(a,e,n)=>{if(e===void 0){n.push({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({fix:`Check ${e} is valid JSONC.`,level:"fail",message:`Could not parse ${e}.`});return}const r=x(a).errors.find(t=>t.includes("SHARD"));r===void 0?n.push({level:"pass",message:"wrangler.jsonc present with a SHARD durable-object binding."}):n.push({fix:"Run `lunora dev` to auto-reconcile, or add the binding manually.",level:"fail",message:r})},D=(a,e)=>{const{info:n}=b(a,"lunora");for(const r of n?.vectorMetadata??[]){const t=R(r.kind);if(t===void 0){e.push({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({fix:`wrangler ${N({index:r.index,property:r.property,type:t}).join(" ")}`,level:"info",message:`vector index "${r.index}" filters on metadata "${r.property}" — that needs a Vectorize metadata index (\`lunora deploy\` creates it).`})}},E=(a,e)=>{if(a===void 0)return;const n=(a.d1_databases??[]).filter(Boolean);for(const r of n){const t=typeof r.database_id=="string"?r.database_id:"";if(A(t)){const o=typeof r.binding=="string"&&r.binding.length>0?r.binding:"<unnamed>";e.push({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>"}").`})}}},L=(a,e)=>{if(a===void 0)return;const n=(a.send_email??[]).filter(Boolean);for(const r of n){const t=typeof r.destination_address=="string"?r.destination_address:"";if(t!==""&&v(t)){const o=typeof r.name=="string"&&r.name.length>0?r.name:"send_email";e.push({fix:"Set destination_address to a verified Cloudflare Email Routing address.",level:"warn",message:`send_email binding "${o}" has a placeholder destination_address ("${t}").`})}}},I=(a,e)=>{const n=h(a,p);if(!g(n))return;let r;try{r=m(n,"utf8")}catch{return}const t=y(r).filter(o=>k.test(o.key)&&v(o.value)).map(o=>o.key);t.length>0&&e.push({fix:"Run `lunora dev` to auto-generate secrets, or fill them in by hand.",level:"warn",message:`${p} has unfilled secret value(s): ${t.join(", ")}.`})},C=a=>{const e=process.env.LUNORA_ADMIN_TOKEN;e===void 0||e.trim()===""?a.push({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({level:"pass",message:"LUNORA_ADMIN_TOKEN is set."})},W=async(a,e)=>{let n;try{({containers:n}=await S({projectRoot:a}))}catch{return}for(const r of n)r.exported?e.push({level:"pass",message:`container "${r.exportName}" is exported by the worker entry.`}):e.push({fix:'Add `export * from "./lunora/_generated/containers"` to your worker entry (or re-run `vis generate lunora-container`).',level:"fail",message:`container "${r.exportName}" is declared but ${r.className} is not exported by the worker entry.`})},M=/^[\^~><= ]+/u,z=/^(?<core>\d+\.\d+\.\d+)(?:-(?<channel>[a-z]+)\.(?<counter>\d+))?$/u,F=a=>{const e=a.replace(M,"").trim(),n=z.exec(e);if(!n?.groups)return;const{channel:r,core:t,counter:o}=n.groups;return{channel:r??"",core:t??"",counter:o===void 0?void 0:Number.parseInt(o,10),raw:e}},K=(a,e)=>{const n=h(a,"package.json");if(!g(n))return;let r;try{r=JSON.parse(m(n,"utf8"))}catch{return}const t={...r.devDependencies,...r.dependencies},o=[];for(const[s,i]of Object.entries(t)){if(s!=="lunorash"&&!s.startsWith("@lunora/"))continue;const f=F(i);f&&o.push({name:s,version:f})}if(o.length<2)return;const d=s=>s.map(i=>`${i.name}@${i.version.raw}`).join(", "),u=new Set(o.map(s=>s.version.core)),c=new Set(o.map(s=>s.version.channel));if(u.size>1){e.push({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(u.size)} different versions: ${d(o)}.`});return}if(c.size>1){e.push({fix:"Pick one channel (all stable, or all alpha/beta) and update the odd package out.",level:"warn",message:`Lunora packages mix release channels (${[...c].map(s=>s===""?"stable":s).join(" + ")}): ${d(o)}.`});return}const l=o.map(s=>s.version.counter).filter(s=>s!==void 0);if(l.length>1){const s=Math.min(...l),i=Math.max(...l);e.push({level:"info",message:l.length===o.length&&s===i?`Lunora packages are all at ${[...c][0]??""}.${String(i)}.`:`Lunora pre-release counters span ${String(s)}–${String(i)}: ${d(o)}.`})}},T=async a=>{const e=a.cwd??process.cwd(),n=[],{parsed:r,path:t}=j(e);return O(r,t,n),E(r,n),L(r,n),I(e,n),C(n),K(e,n),D(e,n),await W(e,n),{code:n.some(o=>o.level==="fail")?1:0,findings:n}},V={fail:"FAIL",info:"INFO",pass:"PASS",warn:"WARN"},P=(a,e)=>{e.info("lunora doctor — project preflight");for(const t of a.findings){const o=`[${V[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 n=a.findings.filter(t=>t.level==="fail").length,r=a.findings.filter(t=>t.level==="warn").length;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.")},Y=_(async({cwd:a,logger:e})=>{const n=await T({cwd:a});return P(n,e),{code:n.code}});export{Y as execute,T as runDoctor};