@lunora/cli 1.0.0-alpha.393 → 1.0.0-alpha.394
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{existsSync as p,readFileSync as m,writeFileSync as U,realpathSync as _}from"node:fs";import{join as f}from"node:path";import{readProjectTarget as y,DEFAULT_TARGET as k,readServiceBindings as F}from"@lunora/codegen";import{DEV_VARS_FILE as d,parseDevVariableEntries as b,upsertDevVariableLine as B,generateSecretValue as V,writeDevVariablesFileAtomically as G,isPlaceholderValue as v,isMintableSecretKey as W,toolchainExecArgs as K,detectPackageManager as z,readProjectDependencies as J,discoverSchemaInfo as q,inferLunoraBindings as H,resolveSchemaDirectory as Y}from"@lunora/config";import{GLOBAL_FETCH_STRICTLY_PUBLIC_FLAG as h,resolveObservabilitySampling as X,findWranglerFile as L,readWranglerJsonc as D,buildBindingManifest as Q,validateWranglerProject as Z,UNEXPORTED_CLASS_MARKER as x,detectCloudflareCliConfig as ee,collectExportGaps as ne}from"@lunora/config/cloudflare";import{i as N,m as te,c as re,a as ae,b as
|
|
2
|
-
`,insertSpaces:!0,tabSize:4}});s=ue(s,i),t.applied.push(`${o.binding}: database_id set to the id of D1 "${o.name}"`)}}!r.dryRun&&s!==n&&U(r.configPath,s)},ke=r=>{const t=le("git",["check-ignore","-q",d],{cwd:r,stdio:"ignore"});if(t.status===0)return!0;if(t.status===1)return!1;const e=f(r,".gitignore");return p(e)&&m(e,"utf8").split(P).some(n=>n.trim()===d)},$e=(r,t,e,n)=>{const a=[],s=new Set(t.split(P).flatMap(o=>{const[c]=b(o);return c!==void 0&&!v(c.value)?[c.key]:[]}));O({cwd:r}).source===void 0&&!s.has("LUNORA_ADMIN_TOKEN")&&a.push("LUNORA_ADMIN_TOKEN");for(const o of e)!N(o.key)||!v(o.value)||s.has(o.key)||(W(o.key)?a.push(o.key):n.skipped.push(`${d}: ${o.key} is a provider key and cannot be minted here; fill it in by hand.`));return a},_e=(r,t)=>{const e=f(r.cwd,d),n=p(e)?m(e,"utf8"):"",a=$e(r.cwd,n,b(n),t);if(a.length===0)return;if(!ke(r.cwd)){t.skipped.push(`${d} is not in .gitignore, so no secret is written there. Add \`${d}\` to .gitignore first.`);return}if(r.dryRun){t.applied.push(`would mint ${a.join(", ")} into ${d}`);return}let s=n;for(const o of a)s=B(s,o,V());G(e,s),t.applied.push(`minted ${a.join(", ")} into ${d}`)},xe=async r=>{const t={applied:[],skipped:[]};return await ye(r,t),_e(r,t),t},I=r=>{const{logs:t,traces:e}=X(r);return Object.entries({logs:t,traces:e}).filter(([,n])=>n.enabled&&n.headSamplingRate>=1).map(([n])=>n)},Ae=(r,t)=>{if(r===void 0)return;const e=[],n=I(r.observability);n.length>0&&e.push(`${n.join(" + ")} (top level)`);for(const[a,s]of Object.entries(r.env??{})){if(s.observability===void 0)continue;const o=I(s.observability);o.length>0&&e.push(`${o.join(" + ")} (env.${a})`)}e.length!==0&&t.push({code:"observability-full-sampling",fix:"For a high-traffic Worker, lower head_sampling_rate in wrangler.jsonc: under observability for logs, under observability.traces for traces — e.g. 0.1 keeps 10% of requests. Lunora never changes a rate you set.",level:"info",message:`observability samples every request (head_sampling_rate 1) for ${e.join(", ")}. From 2026-12-01 Workers Observability is billed: Free caps ingestion at 0.5 GB/day; Paid includes 50 GB per billing cycle, then $0.25/GB.`})},T={ai:"Workers AI",browser:"Browser Rendering",images:"Cloudflare Images (Image Transformations)"},j=["Argo Smart Routing","Log Explorer"],Re=r=>{const t=[["top level",r],...Object.entries(r.env??{}).flatMap(([n,a])=>a?[[`env.${n}`,a]]:[])],e=new Map;for(const[n,a]of t)for(const s of Q(a).bindings)T[s.type]!==void 0&&e.set(s.type,[...e.get(s.type)??[],n]);return e},Se=(r,t)=>{if((y(r)??k)!=="cloudflare")return;const e=L(r),n=e===void 0?void 0:D(e).parsed;if(n!==void 0)for(const[a,s]of Re(n)){const o=T[a]??a;t.push({code:"paid-product-unguarded",fix:"Create an account budget alert in the dashboard: Manage Account > Billing > Billable Usage > Create budget alert. Lunora cannot create or read one.",level:"warn",message:`${o} is bound (${s.join(", ")}). No documented usage-alert product covers it, and lunora cannot read budget alerts, so nothing it can see caps this spend.`})}t.push({code:"paid-product-unknown",level:"info",message:`enablement of ${j.join(" and ")} cannot be read by lunora (dashboard settings); check the dashboard if your account uses ${j.length>1?"them":"it"}.`})},Ie=(r,t)=>{for(const e of F(r).services)for(const n of e.publicScopes){const a=n===""?"":` (env.${n})`;t.push({code:"service-workers-dev",fix:`If ${e.name} has no public route (custom domain or route, including ones set outside ${e.wranglerPath}), set \`"workers_dev": false\`${a}. Keep its internal auth (HMAC) either way.`,level:"warn",message:`service ${e.name} (${e.worker}) has no route in its wrangler config and still serves on workers.dev${a}; a custom domain set elsewhere would also be public.`})}},M=r=>{const t=L(r);if(t===void 0)return{parsed:void 0,path:void 0};const{parsed:e}=D(t);return{parsed:e,path:t}},je=(r,t,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(t===void 0){n.push({code:"wrangler-unparseable",fix:`Check ${e} is valid JSONC.`,level:"fail",message:`Could not parse ${e}.`});return}const{report:a}=Z({projectRoot:r});for(const c of a.errors.filter(i=>i.includes(x)))n.push({code:"wrangler-class-unexported",level:"fail",message:c});for(const c of a.warnings.filter(i=>i.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:c});const s=c=>c.includes(x)||c.includes("SHARD");for(const c of a.errors.filter(i=>!s(i)))n.push({code:"wrangler-invalid",fix:"Fix the reported wrangler.jsonc problem; `lunora verify` reports the same set.",level:"fail",message:c});for(const c of a.warnings.filter(i=>!i.includes("LUNORA_ORIGIN_URL")))n.push({code:"wrangler-advisory",level:"warn",message:c});const o=a.errors.find(c=>c.includes("SHARD"));o===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:o})},Le=(r,t)=>{p(f(r,"lunora.json"))&&t.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."})},De=(r,t)=>{const e=ee(r);e!==void 0&&t.push({code:"cf-config-present",fix:e.fix,level:"warn",message:e.message})},Ne=(r,t)=>{r===void 0||r.limits?.cpu_ms!==void 0||t.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."})},Oe=(r,t)=>{const e=(r?.r2_buckets??[]).map(n=>n?.bucket_name).filter(n=>typeof n=="string"&&n.length>0);e.length!==0&&t.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) ${e.join(", ")}: verify a lifecycle rule aborts incomplete multipart uploads — orphaned parts are billed but invisible in a listing.`})},Ee=(r,t)=>{const{error:e,info:n}=q(r,"lunora");if(e!==void 0){t.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: ${e}`});return}for(const a of n?.vectorMetadata??[]){const s=te(a.kind);if(s===void 0){t.push({code:"vector-metadata-unfilterable",fix:"Filter on a string, number or boolean column, or drop it from `metadata`.",level:"warn",message:`vector index "${a.index}" declares metadata "${a.property}", whose type Vectorize cannot filter on.`});continue}t.push({code:"vector-metadata-index-required",fix:`wrangler ${re({index:a.index,property:a.property,type:s}).join(" ")}`,level:"info",message:`vector index "${a.index}" filters on metadata "${a.property}" — that needs a Vectorize metadata index (\`lunora deploy\` creates it).`})}},Ce=(r,t)=>{if(r===void 0)return;const e=(r.d1_databases??[]).filter(Boolean);for(const n of e){const a=typeof n.database_id=="string"?n.database_id:"";if(C(a)){const s=typeof n.binding=="string"&&n.binding.length>0?n.binding:"<unnamed>";t.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 "${s}" has a placeholder database_id ("${a||"<empty>"}").`})}}},Pe=(r,t)=>{if(r===void 0)return;const e=(r.send_email??[]).filter(Boolean);for(const n of e){const a=typeof n.destination_address=="string"?n.destination_address:"";if(a!==""&&v(a)){const s=typeof n.name=="string"&&n.name.length>0?n.name:"send_email";t.push({code:"email-destination-placeholder",fix:"Set destination_address to a verified Cloudflare Email Routing address.",level:"warn",message:`send_email binding "${s}" has a placeholder destination_address ("${a}").`})}}},Te=(r,t)=>{const e=f(r,d);if(!p(e))return;let n;try{n=m(e,"utf8")}catch{return}const a=b(n).filter(s=>N(s.key)&&v(s.value)).map(s=>s.key);a.length>0&&t.push({code:"dev-vars-missing-secret",fix:"Run `lunora dev` to auto-generate secrets, or fill them in by hand.",level:"warn",message:`${d} has unfilled secret value(s): ${a.join(", ")}.`})},Me=(r,t)=>{const{source:e}=O({cwd:r});e===void 0?t.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."}):t.push({code:"admin-token-set",level:"pass",message:`LUNORA_ADMIN_TOKEN is set (${oe(e)}).`})},Ue=async(r,t)=>{try{return await H({projectRoot:r,schemaDir:Y(r)})}catch(e){t.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: ${e instanceof Error?e.message:String(e)}`});return}},Fe=(r,t)=>{const e=ne(r),n=[...r.containers.map(a=>({...a,kind:"container"})),...r.workflows.map(a=>({...a,kind:"workflow"})),...r.agents.map(a=>({...a,kind:"agent"}))];for(const a of n.filter(s=>s.exported))t.push({code:"declared-export-ok",level:"pass",message:`${a.kind} "${a.exportName}" is exported by the worker entry.`});for(const a of e)t.push({code:"declared-export-missing",fix:`Add \`export * from "./lunora/_generated/${a.module}"\` to your worker entry (or re-run \`vis generate lunora-${a.kind}\`).`,level:"fail",message:`${a.kind} "${a.exportName}" is declared but ${a.className} is not exported by the worker entry.`})},Be=/^[\^~><= ]+/u,Ve=/^(?<core>\d+\.\d+\.\d+)(?:-(?<channel>[a-z]+)\.(?<counter>\d+))?$/u,Ge=r=>{const t=r.replace(Be,"").trim(),e=Ve.exec(t);if(!e?.groups)return;const{channel:n,core:a,counter:s}=e.groups;return{channel:n??"",core:a??"",counter:s===void 0?void 0:Number.parseInt(s,10),raw:t}},We=(r,t)=>{const e=J(r),n=[];for(const[i,l]of Object.entries(e)){if(i!=="lunorash"&&!i.startsWith("@lunora/"))continue;const u=Ge(l);u&&n.push({name:i,version:u})}if(n.length<2)return;const a=i=>i.map(l=>`${l.name}@${l.version.raw}`).join(", "),s=new Set(n.map(i=>i.version.core)),o=new Set(n.map(i=>i.version.channel));if(s.size>1){t.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(s.size)} different versions: ${a(n)}.`});return}if(o.size>1){t.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 (${[...o].map(i=>i===""?"stable":i).join(" + ")}): ${a(n)}.`});return}const c=n.map(i=>i.version.counter).filter(i=>i!==void 0);if(c.length>1){const i=Math.min(...c),l=Math.max(...c);t.push({code:"version-counter-spread",level:"info",message:c.length===n.length&&i===l?`Lunora packages are all at ${[...o][0]??""}.${String(l)}.`:`Lunora pre-release counters span ${String(i)}–${String(l)}: ${a(n)}.`})}},Ke=[f("@lunora","cli"),"lunorash"],ze=(r,t,e)=>{if(t===void 0)return;const n=[];for(const s of Ke){const o=f(r,"node_modules",s);if(p(o))try{n.push(_(o))}catch{}}if(n.length===0)return;let a;try{a=_(t)}catch{return}n.some(s=>ce(s,a))||e.push({code:"cli-shadowed",fix:"Run the project's own CLI: `pnpm exec lunora …` (or `npx lunora …`).",level:"warn",message:`the running lunora (${a}) is not the project's own install (${n.join(", ")}).`})},Je={full:{code:"do-class-budget-full",level:"warn"},near:{code:"do-class-budget-near",level:"warn"},ok:{code:"do-class-budget-ok",level:"pass"},unchecked:{code:"do-class-budget-unchecked",level:"info"}},qe=r=>{const t=new Map,e=(n,a)=>{const s=E(n);s!==void 0&&t.set(s,[...t.get(s)??[],a])};e(r?.account_id,"top level");for(const[n,a]of Object.entries(r?.env??{}))e(a?.account_id,`env.${n}`);return t},He=async(r,t,e,n)=>{if((y(r)??k)!=="cloudflare")return;const s=E((e.environment??process.env).CLOUDFLARE_ACCOUNT_ID)!==void 0?[]:[...qe(t)],o=s.length===0?[[void 0,[]]]:s,c=await Promise.all(o.map(async([i,l])=>({accountId:i,blocks:l,budget:await ae({accountId:i,environment:e.environment,fetch:e.fetch})})));for(const{accountId:i,blocks:l,budget:u}of c)n.push({...Je[u.verdict],message:c.length>1?`account ${String(i)} (${l.join(", ")}): ${u.message}`:u.message,...u.fix===void 0?{}:{fix:u.fix}})},Ye={mismatch:{code:"artifacts-jurisdiction-mismatch",level:"fail"},missing:{code:"artifacts-namespace-missing",level:"fail"},ok:{code:"artifacts-jurisdiction-ok",level:"pass"},unchecked:{code:"artifacts-jurisdiction-unchecked",level:"warn"}},Xe=async(r,t,e,n,a)=>{if(e===void 0||t===void 0||(y(r)??k)!=="cloudflare")return;const s=[["",t],...Object.entries(t.env??{}).map(([i,l])=>[`env.${i}: `,l])],o=new Map;for(const[i,l]of s){const u=l.account_id??t.account_id;for(const g of l.artifacts??[]){const $=`${String(u)}/${String(g?.namespace)}`;typeof g?.binding=="string"&&typeof g.namespace=="string"&&!o.has($)&&o.set($,{accountId:u,binding:g.binding,label:i,namespace:g.namespace})}}const c=await Promise.all([...o.values()].map(async({label:i,...l})=>({check:await se({...l,environment:n.environment,fetch:n.fetch,jurisdiction:e}),label:i})));for(const{check:i,label:l}of c)a.push({...Ye[i.verdict],message:`${l}${i.message}`,...i.fix===void 0?{}:{fix:i.fix}})},Qe=async r=>{const t=r.cwd??process.cwd(),e=[],{parsed:n,path:a}=M(t);je(t,n,a,e),De(t,e),Ce(n,e),Pe(n,e),Ne(n,e),Oe(n,e),Ae(n,e),Te(t,e),Me(t,e),We(t,e),Ee(t,e),Le(t,e),ze(t,r.executablePath??process.argv[1],e),Ie(t,e),Se(t,e);const s=await Ue(t,e);s!==void 0&&Fe(s,e),me(n,t,s?.usesAi===!0,e),await He(t,n,r,e),await Xe(t,n,s?.jurisdiction,r,e),he(n,s?.usesCimdWorkers===!0,e);const o={fail:0,info:0,pass:0,warn:0};for(const i of e)o[i.level]+=1;const c=o.fail>0?1:0;return{code:c,findings:e,ok:c===0,summary:o}},Ze={fail:"FAIL",info:"INFO",pass:"PASS",warn:"WARN"},en=(r,t)=>{t.info("lunora doctor — project preflight");for(const a of r.findings){const s=`[${Ze[a.level]}] ${a.message}`;a.level==="fail"?t.error(s):a.level==="warn"?t.warn(s):t.info(s),a.fix!==void 0&&a.level!=="pass"&&t.info(` fix: ${a.fix}`)}const{fail:e,warn:n}=r.summary;e>0?t.error(`${String(e)} failure(s), ${String(n)} warning(s).`):n>0?t.warn(`0 failures, ${String(n)} warning(s).`):t.success("all checks passed.")},nn=async r=>{const{logger:t}=r,e=await Qe({...r});return en(e,t),e},vn=ie(async({cwd:r,format:t,logger:e,options:n})=>{if(n.fix===!0){const{path:s}=M(r),o=await xe({configPath:s,cwd:r,dryRun:n.dryRun===!0,logger:e});for(const c of o.applied)e.info(`fix: ${c}`);for(const c of o.skipped)e.warn(`not fixed: ${c}`)}const a=await nn({cwd:r,format:t,logger:e});return{code:a.code,data:{findings:a.findings,summary:a.summary}}});export{vn as execute,Qe as runDoctor,nn as runDoctorCommand};
|
|
1
|
+
import{existsSync as p,readFileSync as m,writeFileSync as U,realpathSync as _}from"node:fs";import{join as f}from"node:path";import{readProjectTarget as y,DEFAULT_TARGET as k,readServiceBindings as F}from"@lunora/codegen";import{DEV_VARS_FILE as d,parseDevVariableEntries as b,upsertDevVariableLine as B,generateSecretValue as V,writeDevVariablesFileAtomically as G,isPlaceholderValue as v,isMintableSecretKey as W,toolchainExecArgs as K,detectPackageManager as z,readProjectDependencies as J,discoverSchemaInfo as q,inferLunoraBindings as H,resolveSchemaDirectory as Y}from"@lunora/config";import{GLOBAL_FETCH_STRICTLY_PUBLIC_FLAG as h,resolveObservabilitySampling as X,findWranglerFile as L,readWranglerJsonc as D,buildBindingManifest as Q,validateWranglerProject as Z,UNEXPORTED_CLASS_MARKER as x,detectCloudflareCliConfig as ee,collectExportGaps as ne}from"@lunora/config/cloudflare";import{i as N,m as te,c as re,a as ae,b as oe}from"../packem_shared/secret-key-6bCftsxj.mjs";import{r as O,d as se}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{n as E}from"../packem_shared/cloudflare-credentials-BVmZnDZk.mjs";import{d as ie}from"../packem_shared/command-CAXl2A1S.mjs";import{i as ce}from"../packem_shared/path-containment-CgxYZggb.mjs";import{A,a as R,b as w}from"../packem_shared/variables-CPYyJtsL.mjs";import{spawnSync as le}from"node:child_process";import{modify as de,applyEdits as ue,parse as fe}from"jsonc-parser";import{defaultSpawner as pe}from"../packem_shared/createRecordingSpawner-Ch7zuQQE.mjs";const ge=(r,t)=>{const e=new Set,n=o=>{for(const[s,c]of Object.entries(o??{}))typeof c=="string"&&c.trim().length>0&&e.add(s)};n(r.vars);for(const o of Object.values(r.env??{}))n(o.vars);const a=f(t,d);if(p(a))try{n(Object.fromEntries(b(m(a,"utf8")).map(o=>[o.key,o.value])))}catch{}return e},me=(r,t,e,n)=>{if(r===void 0||!e)return;const a=ge(r,t),o=a.has(A),s=r.ai?.binding;!o&&(typeof s!="string"||s.length===0)&&n.push({code:"ai-binding-missing",fix:`Run \`lunora dev\` (it adds the binding), add \`"ai": { "binding": "AI" }\` to wrangler.jsonc, or set ${A} to an OpenAI-compatible proxy.`,level:"warn",message:"the project uses ctx.ai (@lunora/ai / env.AI) but wrangler.jsonc declares no `ai` binding — model calls will fail at runtime."}),a.has(R)&&n.push({code:"ai-gateway-token-unused",fix:"Make the gateway unauthenticated for ctx.ai, or remove the token. A bring-your-own AI SDK provider built with `resolveAiGateway` does send it.",level:"warn",message:`${R} is set, but ctx.ai's Workers AI binding cannot send a gateway token — it is ignored on that path.`}),!o&&!a.has(w)&&n.push({code:"ai-gateway-default",fix:`Run \`lunora cloudflare ai-gateway\` to create a gateway for this app and write ${w} into wrangler vars.`,level:"info",message:`no ${w} in vars or ${d} — \`<provider>/<model>\` slugs route through the account's \`default\` AI Gateway.`})},he=(r,t,e)=>{if(r===void 0||!t)return;const n=[];(r.compatibility_flags??[]).includes(h)||n.push("the top level");for(const[a,o]of Object.entries(r.env??{}))o.compatibility_flags!==void 0&&!o.compatibility_flags.includes(h)&&n.push(`env.${a}`);n.length>0&&e.push({code:"cimd-fetch-not-strictly-public",fix:`Add "${h}" to "compatibility_flags" in wrangler.jsonc.`,level:"warn",message:`@lunora/auth/cimd/workers is imported, but ${n.join(", ")} of wrangler.jsonc lacks the ${h} compatibility flag — workersCimdFetch() refuses to build without it.`})},C=r=>{const t=r.trim();if(t==="")return!0;const e=t.toLowerCase();return e.includes("replace")||e.startsWith("<")&&e.endsWith(">")},P=/\r?\n/u,ve=async(r,t)=>{const{args:e,command:n}=K(z(t),{args:r,tool:"wrangler"});try{const a=await pe({args:e,captureStderr:!0,captureStdoutSilently:!0,command:n,cwd:t});return{code:a.code,stderr:a.stderr??"",stdout:a.stdout??""}}catch(a){return{code:1,stderr:a instanceof Error?a.message:String(a),stdout:""}}},S=async(r,t)=>{const e=await r(["d1","list","--json"],t);if(e.code!==0)return;let n;try{n=JSON.parse(e.stdout)}catch{return}if(!Array.isArray(n))return;const a=new Map;for(const o of n)typeof o.name=="string"&&typeof o.uuid=="string"&&a.set(o.name,o.uuid);return a},be=r=>{const t=fe(r);return(Array.isArray(t?.d1_databases)?t.d1_databases:[]).flatMap((n,a)=>{if(n===null||typeof n!="object")return[];const o=n,s=typeof o.database_id=="string"?o.database_id:"";return C(s)?[{binding:typeof o.binding=="string"&&o.binding!==""?o.binding:"<unnamed>",index:a,name:typeof o.database_name=="string"&&o.database_name!==""?o.database_name:void 0}]:[]})},we=async(r,t,e,n)=>{const a=await S(e,t.cwd);if(a===void 0){n.skipped.push(`D1 "${r.binding}": could not run \`wrangler d1 list --json\`. Check that wrangler is installed in this project and logged in (\`wrangler login\`), then retry.`);return}const o=a.get(r.name);if(o!==void 0)return o;if(t.dryRun){n.applied.push(`would run \`wrangler d1 create ${r.name}\` and write its id into ${r.binding}`);return}const s=await e(["d1","create",r.name],t.cwd);if(s.code!==0){const l=s.stderr.trim()||`exit ${String(s.code)}`;n.skipped.push(`D1 "${r.binding}": \`wrangler d1 create ${r.name}\` failed: ${l}`);return}const i=(await S(e,t.cwd))?.get(r.name);return i===void 0&&n.skipped.push(`D1 "${r.binding}": created ${r.name} but could not read back its id.`),i},ye=async(r,t)=>{if(r.configPath===void 0)return;const e=r.runner??ve,n=m(r.configPath,"utf8"),a=be(n);let o=n;for(const s of a){if(s.name===void 0){t.skipped.push(`D1 "${s.binding}" has no database_name, so there is no database to create or reuse.`);continue}const c=await we({...s,name:s.name},r,e,t);if(c!==void 0){const i=de(o,["d1_databases",s.index,"database_id"],c,{formattingOptions:{eol:`
|
|
2
|
+
`,insertSpaces:!0,tabSize:4}});o=ue(o,i),t.applied.push(`${s.binding}: database_id set to the id of D1 "${s.name}"`)}}!r.dryRun&&o!==n&&U(r.configPath,o)},ke=r=>{const t=le("git",["check-ignore","-q",d],{cwd:r,stdio:"ignore"});if(t.status===0)return!0;if(t.status===1)return!1;const e=f(r,".gitignore");return p(e)&&m(e,"utf8").split(P).some(n=>n.trim()===d)},$e=(r,t,e,n)=>{const a=[],o=new Set(t.split(P).flatMap(s=>{const[c]=b(s);return c!==void 0&&!v(c.value)?[c.key]:[]}));O({cwd:r}).source===void 0&&!o.has("LUNORA_ADMIN_TOKEN")&&a.push("LUNORA_ADMIN_TOKEN");for(const s of e)!N(s.key)||!v(s.value)||o.has(s.key)||(W(s.key)?a.push(s.key):n.skipped.push(`${d}: ${s.key} is a provider key and cannot be minted here; fill it in by hand.`));return a},_e=(r,t)=>{const e=f(r.cwd,d),n=p(e)?m(e,"utf8"):"",a=$e(r.cwd,n,b(n),t);if(a.length===0)return;if(!ke(r.cwd)){t.skipped.push(`${d} is not in .gitignore, so no secret is written there. Add \`${d}\` to .gitignore first.`);return}if(r.dryRun){t.applied.push(`would mint ${a.join(", ")} into ${d}`);return}let o=n;for(const s of a)o=B(o,s,V());G(e,o),t.applied.push(`minted ${a.join(", ")} into ${d}`)},xe=async r=>{const t={applied:[],skipped:[]};return await ye(r,t),_e(r,t),t},I=r=>{const{logs:t,traces:e}=X(r);return Object.entries({logs:t,traces:e}).filter(([,n])=>n.enabled&&n.headSamplingRate>=1).map(([n])=>n)},Ae=(r,t)=>{if(r===void 0)return;const e=[],n=I(r.observability);n.length>0&&e.push(`${n.join(" + ")} (top level)`);for(const[a,o]of Object.entries(r.env??{})){if(o.observability===void 0)continue;const s=I(o.observability);s.length>0&&e.push(`${s.join(" + ")} (env.${a})`)}e.length!==0&&t.push({code:"observability-full-sampling",fix:"For a high-traffic Worker, lower head_sampling_rate in wrangler.jsonc: under observability for logs, under observability.traces for traces — e.g. 0.1 keeps 10% of requests. Lunora never changes a rate you set.",level:"info",message:`observability samples every request (head_sampling_rate 1) for ${e.join(", ")}. From 2026-12-01 Workers Observability is billed: Free caps ingestion at 0.5 GB/day; Paid includes 50 GB per billing cycle, then $0.25/GB.`})},T={ai:"Workers AI",browser:"Browser Rendering",images:"Cloudflare Images (Image Transformations)"},j=["Argo Smart Routing","Log Explorer"],Re=r=>{const t=[["top level",r],...Object.entries(r.env??{}).flatMap(([n,a])=>a?[[`env.${n}`,a]]:[])],e=new Map;for(const[n,a]of t)for(const o of Q(a).bindings)T[o.type]!==void 0&&e.set(o.type,[...e.get(o.type)??[],n]);return e},Se=(r,t)=>{if((y(r)??k)!=="cloudflare")return;const e=L(r),n=e===void 0?void 0:D(e).parsed;if(n!==void 0)for(const[a,o]of Re(n)){const s=T[a]??a;t.push({code:"paid-product-bound",fix:"Create an account budget alert in the dashboard: Manage Account > Billing > Billable Usage > Create budget alert. Lunora cannot create or read one.",level:"info",message:`${s} is bound (${o.join(", ")}) and billed per use. No documented usage-alert product covers it, and lunora cannot read budget alerts, so an account budget alert is the only cap.`})}t.push({code:"paid-product-unknown",level:"info",message:`enablement of ${j.join(" and ")} cannot be read by lunora (dashboard settings); check the dashboard if your account uses ${j.length>1?"them":"it"}.`})},Ie=(r,t)=>{for(const e of F(r).services)for(const n of e.publicScopes){const a=n===""?"":` (env.${n})`;t.push({code:"service-workers-dev",fix:`If ${e.name} has no public route (custom domain or route, including ones set outside ${e.wranglerPath}), set \`"workers_dev": false\`${a}. Keep its internal auth (HMAC) either way.`,level:"warn",message:`service ${e.name} (${e.worker}) has no route in its wrangler config and still serves on workers.dev${a}; a custom domain set elsewhere would also be public.`})}},M=r=>{const t=L(r);if(t===void 0)return{parsed:void 0,path:void 0};const{parsed:e}=D(t);return{parsed:e,path:t}},je=(r,t,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(t===void 0){n.push({code:"wrangler-unparseable",fix:`Check ${e} is valid JSONC.`,level:"fail",message:`Could not parse ${e}.`});return}const{report:a}=Z({projectRoot:r});for(const c of a.errors.filter(i=>i.includes(x)))n.push({code:"wrangler-class-unexported",level:"fail",message:c});for(const c of a.warnings.filter(i=>i.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:c});const o=c=>c.includes(x)||c.includes("SHARD");for(const c of a.errors.filter(i=>!o(i)))n.push({code:"wrangler-invalid",fix:"Fix the reported wrangler.jsonc problem; `lunora verify` reports the same set.",level:"fail",message:c});for(const c of a.warnings.filter(i=>!i.includes("LUNORA_ORIGIN_URL")))n.push({code:"wrangler-advisory",level:"warn",message:c});const s=a.errors.find(c=>c.includes("SHARD"));s===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:s})},Le=(r,t)=>{p(f(r,"lunora.json"))&&t.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."})},De=(r,t)=>{const e=ee(r);e!==void 0&&t.push({code:"cf-config-present",fix:e.fix,level:"warn",message:e.message})},Ne=(r,t)=>{r===void 0||r.limits?.cpu_ms!==void 0||t.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."})},Oe=(r,t)=>{const e=(r?.r2_buckets??[]).map(n=>n?.bucket_name).filter(n=>typeof n=="string"&&n.length>0);e.length!==0&&t.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) ${e.join(", ")}: verify a lifecycle rule aborts incomplete multipart uploads — orphaned parts are billed but invisible in a listing.`})},Ee=(r,t)=>{const{error:e,info:n}=q(r,"lunora");if(e!==void 0){t.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: ${e}`});return}for(const a of n?.vectorMetadata??[]){const o=te(a.kind);if(o===void 0){t.push({code:"vector-metadata-unfilterable",fix:"Filter on a string, number or boolean column, or drop it from `metadata`.",level:"warn",message:`vector index "${a.index}" declares metadata "${a.property}", whose type Vectorize cannot filter on.`});continue}t.push({code:"vector-metadata-index-required",fix:`wrangler ${re({index:a.index,property:a.property,type:o}).join(" ")}`,level:"info",message:`vector index "${a.index}" filters on metadata "${a.property}" — that needs a Vectorize metadata index (\`lunora deploy\` creates it).`})}},Ce=(r,t)=>{if(r===void 0)return;const e=(r.d1_databases??[]).filter(Boolean);for(const n of e){const a=typeof n.database_id=="string"?n.database_id:"";if(C(a)){const o=typeof n.binding=="string"&&n.binding.length>0?n.binding:"<unnamed>";t.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 ("${a||"<empty>"}").`})}}},Pe=(r,t)=>{if(r===void 0)return;const e=(r.send_email??[]).filter(Boolean);for(const n of e){const a=typeof n.destination_address=="string"?n.destination_address:"";if(a!==""&&v(a)){const o=typeof n.name=="string"&&n.name.length>0?n.name:"send_email";t.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 ("${a}").`})}}},Te=(r,t)=>{const e=f(r,d);if(!p(e))return;let n;try{n=m(e,"utf8")}catch{return}const a=b(n).filter(o=>N(o.key)&&v(o.value)).map(o=>o.key);a.length>0&&t.push({code:"dev-vars-missing-secret",fix:"Run `lunora dev` to auto-generate secrets, or fill them in by hand.",level:"warn",message:`${d} has unfilled secret value(s): ${a.join(", ")}.`})},Me=(r,t)=>{const{source:e}=O({cwd:r});e===void 0?t.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."}):t.push({code:"admin-token-set",level:"pass",message:`LUNORA_ADMIN_TOKEN is set (${se(e)}).`})},Ue=async(r,t)=>{try{return await H({projectRoot:r,schemaDir:Y(r)})}catch(e){t.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: ${e instanceof Error?e.message:String(e)}`});return}},Fe=(r,t)=>{const e=ne(r),n=[...r.containers.map(a=>({...a,kind:"container"})),...r.workflows.map(a=>({...a,kind:"workflow"})),...r.agents.map(a=>({...a,kind:"agent"}))];for(const a of n.filter(o=>o.exported))t.push({code:"declared-export-ok",level:"pass",message:`${a.kind} "${a.exportName}" is exported by the worker entry.`});for(const a of e)t.push({code:"declared-export-missing",fix:`Add \`export * from "./lunora/_generated/${a.module}"\` to your worker entry (or re-run \`vis generate lunora-${a.kind}\`).`,level:"fail",message:`${a.kind} "${a.exportName}" is declared but ${a.className} is not exported by the worker entry.`})},Be=/^[\^~><= ]+/u,Ve=/^(?<core>\d+\.\d+\.\d+)(?:-(?<channel>[a-z]+)\.(?<counter>\d+))?$/u,Ge=r=>{const t=r.replace(Be,"").trim(),e=Ve.exec(t);if(!e?.groups)return;const{channel:n,core:a,counter:o}=e.groups;return{channel:n??"",core:a??"",counter:o===void 0?void 0:Number.parseInt(o,10),raw:t}},We=(r,t)=>{const e=J(r),n=[];for(const[i,l]of Object.entries(e)){if(i!=="lunorash"&&!i.startsWith("@lunora/"))continue;const u=Ge(l);u&&n.push({name:i,version:u})}if(n.length<2)return;const a=i=>i.map(l=>`${l.name}@${l.version.raw}`).join(", "),o=new Set(n.map(i=>i.version.core)),s=new Set(n.map(i=>i.version.channel));if(o.size>1){t.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(o.size)} different versions: ${a(n)}.`});return}if(s.size>1){t.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 (${[...s].map(i=>i===""?"stable":i).join(" + ")}): ${a(n)}.`});return}const c=n.map(i=>i.version.counter).filter(i=>i!==void 0);if(c.length>1){const i=Math.min(...c),l=Math.max(...c);t.push({code:"version-counter-spread",level:"info",message:c.length===n.length&&i===l?`Lunora packages are all at ${[...s][0]??""}.${String(l)}.`:`Lunora pre-release counters span ${String(i)}–${String(l)}: ${a(n)}.`})}},Ke=[f("@lunora","cli"),"lunorash"],ze=(r,t,e)=>{if(t===void 0)return;const n=[];for(const o of Ke){const s=f(r,"node_modules",o);if(p(s))try{n.push(_(s))}catch{}}if(n.length===0)return;let a;try{a=_(t)}catch{return}n.some(o=>ce(o,a))||e.push({code:"cli-shadowed",fix:"Run the project's own CLI: `pnpm exec lunora …` (or `npx lunora …`).",level:"warn",message:`the running lunora (${a}) is not the project's own install (${n.join(", ")}).`})},Je={full:{code:"do-class-budget-full",level:"warn"},near:{code:"do-class-budget-near",level:"warn"},ok:{code:"do-class-budget-ok",level:"pass"},unchecked:{code:"do-class-budget-unchecked",level:"info"}},qe=r=>{const t=new Map,e=(n,a)=>{const o=E(n);o!==void 0&&t.set(o,[...t.get(o)??[],a])};e(r?.account_id,"top level");for(const[n,a]of Object.entries(r?.env??{}))e(a?.account_id,`env.${n}`);return t},He=async(r,t,e,n)=>{if((y(r)??k)!=="cloudflare")return;const o=E((e.environment??process.env).CLOUDFLARE_ACCOUNT_ID)!==void 0?[]:[...qe(t)],s=o.length===0?[[void 0,[]]]:o,c=await Promise.all(s.map(async([i,l])=>({accountId:i,blocks:l,budget:await ae({accountId:i,environment:e.environment,fetch:e.fetch})})));for(const{accountId:i,blocks:l,budget:u}of c)n.push({...Je[u.verdict],message:c.length>1?`account ${String(i)} (${l.join(", ")}): ${u.message}`:u.message,...u.fix===void 0?{}:{fix:u.fix}})},Ye={mismatch:{code:"artifacts-jurisdiction-mismatch",level:"fail"},missing:{code:"artifacts-namespace-missing",level:"fail"},ok:{code:"artifacts-jurisdiction-ok",level:"pass"},unchecked:{code:"artifacts-jurisdiction-unchecked",level:"warn"}},Xe=async(r,t,e,n,a)=>{if(e===void 0||t===void 0||(y(r)??k)!=="cloudflare")return;const o=[["",t],...Object.entries(t.env??{}).map(([i,l])=>[`env.${i}: `,l])],s=new Map;for(const[i,l]of o){const u=l.account_id??t.account_id;for(const g of l.artifacts??[]){const $=`${String(u)}/${String(g?.namespace)}`;typeof g?.binding=="string"&&typeof g.namespace=="string"&&!s.has($)&&s.set($,{accountId:u,binding:g.binding,label:i,namespace:g.namespace})}}const c=await Promise.all([...s.values()].map(async({label:i,...l})=>({check:await oe({...l,environment:n.environment,fetch:n.fetch,jurisdiction:e}),label:i})));for(const{check:i,label:l}of c)a.push({...Ye[i.verdict],message:`${l}${i.message}`,...i.fix===void 0?{}:{fix:i.fix}})},Qe=async r=>{const t=r.cwd??process.cwd(),e=[],{parsed:n,path:a}=M(t);je(t,n,a,e),De(t,e),Ce(n,e),Pe(n,e),Ne(n,e),Oe(n,e),Ae(n,e),Te(t,e),Me(t,e),We(t,e),Ee(t,e),Le(t,e),ze(t,r.executablePath??process.argv[1],e),Ie(t,e),Se(t,e);const o=await Ue(t,e);o!==void 0&&Fe(o,e),me(n,t,o?.usesAi===!0,e),await He(t,n,r,e),await Xe(t,n,o?.jurisdiction,r,e),he(n,o?.usesCimdWorkers===!0,e);const s={fail:0,info:0,pass:0,warn:0};for(const i of e)s[i.level]+=1;const c=s.fail>0?1:0;return{code:c,findings:e,ok:c===0,summary:s}},Ze={fail:"FAIL",info:"INFO",pass:"PASS",warn:"WARN"},en=(r,t)=>{t.info("lunora doctor — project preflight");for(const a of r.findings){const o=`[${Ze[a.level]}] ${a.message}`;a.level==="fail"?t.error(o):a.level==="warn"?t.warn(o):t.info(o),a.fix!==void 0&&a.level!=="pass"&&t.info(` fix: ${a.fix}`)}const{fail:e,warn:n}=r.summary;e>0?t.error(`${String(e)} failure(s), ${String(n)} warning(s).`):n>0?t.warn(`0 failures, ${String(n)} warning(s).`):t.success("all checks passed.")},nn=async r=>{const{logger:t}=r,e=await Qe({...r});return en(e,t),e},vn=ie(async({cwd:r,format:t,logger:e,options:n})=>{if(n.fix===!0){const{path:o}=M(r),s=await xe({configPath:o,cwd:r,dryRun:n.dryRun===!0,logger:e});for(const c of s.applied)e.info(`fix: ${c}`);for(const c of s.skipped)e.warn(`not fixed: ${c}`)}const a=await nn({cwd:r,format:t,logger:e});return{code:a.code,data:{findings:a.findings,summary:a.summary}}});export{vn as execute,Qe as runDoctor,nn as runDoctorCommand};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/cli",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.394",
|
|
4
4
|
"description": "The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -52,16 +52,16 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@bomb.sh/tab": "0.0.22",
|
|
55
|
-
"@lunora/advisor": "1.0.0-alpha.
|
|
56
|
-
"@lunora/bindings": "1.0.0-alpha.
|
|
57
|
-
"@lunora/codegen": "1.0.0-alpha.
|
|
58
|
-
"@lunora/config": "1.0.0-alpha.
|
|
55
|
+
"@lunora/advisor": "1.0.0-alpha.214",
|
|
56
|
+
"@lunora/bindings": "1.0.0-alpha.111",
|
|
57
|
+
"@lunora/codegen": "1.0.0-alpha.300",
|
|
58
|
+
"@lunora/config": "1.0.0-alpha.348",
|
|
59
59
|
"@lunora/container": "1.0.0-alpha.74",
|
|
60
|
-
"@lunora/d1": "1.0.0-alpha.
|
|
60
|
+
"@lunora/d1": "1.0.0-alpha.178",
|
|
61
61
|
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
62
|
-
"@lunora/mcp": "1.0.0-alpha.
|
|
63
|
-
"@lunora/runtime": "1.0.0-alpha.
|
|
64
|
-
"@lunora/seed": "1.0.0-alpha.
|
|
62
|
+
"@lunora/mcp": "1.0.0-alpha.238",
|
|
63
|
+
"@lunora/runtime": "1.0.0-alpha.198",
|
|
64
|
+
"@lunora/seed": "1.0.0-alpha.217",
|
|
65
65
|
"@visulima/cerebro": "3.1.2",
|
|
66
66
|
"@visulima/error": "6.1.0",
|
|
67
67
|
"@visulima/fs": "6.0.29",
|