UNPKG

@lunora/cli

Version:

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

3 lines (2 loc) • 14.3 kB
import{runCodegen as L}from"@lunora/codegen";import{runPostCodegenHook as H,detectAgentRules as N,updateDevServerState as I,readDevServerState as V,DEV_DAEMON_ENV as M,detectAiAgent as W,readProjectRemotePreference as B,readLiveDevServerState as j,DEV_HANDOFF_ENV as p,clearDevServerState as U,claimDevServerState as G,DEV_LOG_FILE_ENV as X,inferLunoraBindings as Y,resolveSchemaDirectory as J,packageNamesFromBindings as K,ensureDevVarsExample as z,ensureDevVariables as Q,isInteractive as Z,DEV_VARS_FILE as D,DEV_VARS_EXAMPLE_FILE as q,fillDevSecrets as ee,DEV_BINDINGS_FILE as re,DEV_STATE_FILE as F,claimAgentRulesHint as te,AGENT_RULES_HINT as ne}from"@lunora/config";import{resolveRemoteEnabled as oe}from"@lunora/config/cloudflare";import{p as ae,a as ie}from"../packem_shared/deploy-target-DCWwiuAe.mjs";import{w as se}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{existsSync as ce,watch as de}from"node:fs";import{join as le}from"node:path";import{a as ue}from"../packem_shared/codegen-error-AmH54ofi.mjs";import{a as fe}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{d as ge}from"../packem_shared/command-CAXl2A1S.mjs";import{EXIT_CODE as E}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{forceJsonLogging as ve}from"../packem_shared/createLogger-Cl18I8AX.mjs";import{createServer as me,request as we}from"node:http";import{connect as he}from"node:net";import{pipeline as Se}from"node:stream";import{loadStudioAssets as T,studioAssetsStamp as A,renderStudioHtml as ye,resolveAdminToken as pe,requestPathname as _,transportRejectionReason as P,SCHEMA_EDIT_ENDPOINT as Ee,POLICY_SCAFFOLD_ENDPOINT as be,SEED_ENDPOINT as ke,sendStudioDocument as De,serveJsonHandler as Te,isStandaloneModulePath as Ae,readStandaloneAsset as _e,assetContentType as Pe,handleSchemaEditRequest as Re,handlePolicyScaffoldRequest as Oe,handleSeedRequest as $e,applyStudioAssetCache as Ce}from"@lunora/config/studio-host";import{c as Le}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{setTimeout as Ne}from"node:timers/promises";import{r as Ie,d as Me,P as je,a as Fe,b,c as xe,s as He,n as x,e as Ve,f as We,g as Be,h as Ue,i as R,w as Ge,j as Xe,k as Ye,t as Je,l as Ke,m as ze,o as Qe}from"../packem_shared/plan-TuKFsXOj.mjs";import{provisionBindings as Ze}from"./runDeployCommand.mjs";const qe=100,er=300,rr=/[/\\]/u,tr=(e,r,t)=>{const{apiSpec:n,logger:a,projectRoot:i,target:u}=e;try{const o=L({apiSpec:n,lunoraDirectory:r,projectRoot:i,target:u});a.success(`codegen: wrote ${r}/_generated (${t})`),fe(o.platformDiagnostics,a)}catch(o){return a.error(ue(o,t)),!1}return!0},nr=e=>{const r=e.lunoraDirectory??"lunora",t=le(e.projectRoot,r),n=e.debounceMs??qe;let a=!1,i=!1,u=!1,o,d=0,g=Promise.resolve(),h;const S=new Promise(f=>{h=f}),v=f=>{a||(o=f,!i&&(i=!0,g=(async()=>{try{for(;o!==void 0;){const w=o;if(o=void 0,!!tr(e,r,w)){u=!0;try{(await H({cwd:e.projectRoot,logger:e.logger,spawner:e.spawner,stdoutToStderr:e.jsonLogs})).ran&&(d=Date.now())}finally{u=!1}}}}catch(w){e.logger.error(`codegen watch: ${w instanceof Error?w.message:String(w)}`)}finally{i=!1,h()}})()))};v("startup");const c=f=>(e.logger.warn(`codegen watch unavailable (${f}) — schema edits will NOT auto-regenerate. Run \`lunora codegen\` manually after each edit.`),{close:()=>(a=!0,o=void 0,g),ready:S,watchAvailable:!1});let s,l;if(!ce(t))return c(`no such directory: ${t}`);try{l=de(t,{recursive:!0},(f,w)=>{if(!(typeof w=="string"&&w.split(rr).includes("_generated"))){if(u||Date.now()-d<er){s&&(clearTimeout(s),s=void 0);return}s&&clearTimeout(s),s=setTimeout(()=>{v(`change: ${w??"?"}`)},n)}})}catch(f){return c(f instanceof Error?f.message:String(f))}const m=l;return{close:()=>(a=!0,o=void 0,s&&clearTimeout(s),m.close(),g),ready:S,watchAvailable:!0}},O="/_lunora",$=3e4,C=(e,r,t)=>{const n=we({headers:{...e.headers,host:t.host},hostname:t.hostname,method:e.method,path:e.url,port:t.port},a=>{r.writeHead(a.statusCode??502,a.headers),Se(a,r,()=>{})});n.setTimeout($,()=>{n.destroy(new Error(`no response within ${String($)}ms`))}),n.on("error",a=>{if(r.headersSent){r.destroy();return}r.statusCode=a.message.startsWith("no response within")?504:502,r.end(`lunora dev: worker unreachable (${a.message})`)}),r.on("close",()=>{r.writableFinished||n.destroy()}),e.pipe(n)},or=5e3,ar=(e,r,t,n)=>{const a=he({host:n.hostname,port:Number(n.port)},()=>{a.setTimeout(0);const i=[`${e.method??"GET"} ${e.url??"/"} HTTP/1.1`];for(const[u,o]of Object.entries(e.headers))if(u!=="host")for(const d of Array.isArray(o)?o:[o])d!==void 0&&i.push(`${u}: ${d}`);i.push(`host: ${n.host}`,"",""),a.write(i.join(`\r `)),t.length>0&&a.write(t),a.pipe(r),r.pipe(a)});a.setTimeout(or,()=>a.destroy()),a.on("error",()=>r.destroy()),r.on("error",()=>a.destroy())},ir=async e=>{const r=e.host??"127.0.0.1",t=r==="127.0.0.1"||r==="localhost"||r==="::1",n=new URL(e.workerOrigin);let a=T(e.logger,import.meta.url),i=A(import.meta.url);const u=ye({adminToken:t?pe(e.cwd):void 0,basePath:"/",dataEditable:t,rulesInstalled:N(e.cwd).installed,runAsIdentity:t,schemaEditable:t,scriptSrc:"/studio.js",styleHref:"/styles.css"}),o=(c,s,l,m,f)=>{Ce(c,s,f,i)||(s.statusCode=200,s.setHeader("Content-Type",m),s.end(l))},d=(c,s,l)=>{c.statusCode=s,c.setHeader("Content-Type","text/plain"),c.end(l)},g=(c,s,l,m)=>{if(t){Te(c,s,l,e.cwd,{apiSpec:e.apiSpec});return}d(s,403,m)},h=(c,s,l)=>{const m=c==="/styles.css",f=Ae(c);if(!m&&!f)return!1;const w=A(import.meta.url);if(w!==i&&(a=T(e.logger,import.meta.url),i=w),a===void 0)return d(l,501,"Lunora studio assets not found — install and build @lunora/studio."),!0;if(m)return o(s,l,a.styles,"text/css; charset=utf-8","styles.css"),!0;const y=c.slice(c.lastIndexOf("/")+1),k=_e(y,import.meta.url);return k===void 0?(d(l,404,"Not found"),!0):(o(s,l,k,Pe(y),y),!0)},S=Buffer.from(u),v=me((c,s)=>{const l=_(c.url??"/");if(t){const f=P(c,e.logger);if(f!==void 0){d(s,403,f);return}}if(l.startsWith(O)){if(!t){d(s,403,"Lunora studio: the worker admin proxy is only available on a loopback bind.");return}C(c,s,n);return}if(l===Ee){g(c,s,Re,"Lunora schema editing is only available on loopback hosts in dev.");return}if(l===be){g(c,s,Oe,"Lunora policy scaffolding is only available on loopback hosts in dev.");return}if(l===ke){g(c,s,$e,"Lunora data seeding is only available on loopback hosts in dev.");return}if(h(l,c,s))return;const m=c.headers["sec-fetch-mode"];if(t&&typeof m=="string"&&m!=="navigate"){C(c,s,n);return}De(s,S)});return v.on("upgrade",(c,s,l)=>{if(!t||P(c,e.logger)!==void 0){s.destroy();return}_(c.url??"").startsWith(O)?ar(c,s,l,n):s.destroy()}),await new Promise((c,s)=>{v.once("error",l=>{l.code==="EADDRINUSE"?s(new Error(`studio port ${String(e.port)} is already in use — pass a different port with --port, or stop the process using it`,{cause:l})):s(l)}),v.listen(e.port,r,()=>{c()})}),{close:()=>new Promise(c=>{v.close(()=>{c()})}),url:`http://${r==="0.0.0.0"||r==="::"?"localhost":r}:${String(e.port)}`}},sr=async e=>{const{cwd:r,logger:t,origin:n,signal:a}=e,i=e.probe??Me,u=Ie(e.readyTimeoutMs),o=Date.now()+u,d=()=>a.aborted;for(;!d()&&Date.now()<o;){let g=!1;try{g=await i(n,a)}catch{}if(d()||Date.now()>=o)break;if(g)return I(r,{readyAt:new Date().toISOString()},{expectedPid:process.pid}),V(r)?.readyAt!==void 0;try{await Ne(je,void 0,{signal:a})}catch{}}return d()||t.warn(`server did not answer on ${n} within ${String(Math.round(u/1e3))}s — .lunora/dev.json will have no readyAt, so anything polling for readiness will keep waiting. The server itself is unaffected; raise LUNORA_DEV_READY_TIMEOUT_MS if it is just slow to boot.`),!1},cr=(e,r,t,n)=>{e.info(""),e.success("Lunora dev"),e.info(` ➜ Worker: ${r.workerOrigin}`),t!==void 0&&e.info(` ➜ Studio: ${t}`),r.runsCodegenWatch&&e.info(" ➜ Codegen: watching lunora/"),n!==void 0&&e.info(` ➜ Supervisor: ${n} (needs) · ${F} (status)`),r.remote.enabled&&(r.remote.bindings.length>0?e.info(` ➜ Remote: ${r.remote.bindings.join(", ")} → deployed worker`):e.warn(` ➜ Remote: requested but inactive (${r.remote.reason??"no eligible bindings"}) — running fully local`)),e.info("")},dr=(e,r)=>{N(r).installed||!te()||(e.info(` ⓘ ${ne}`),e.info(""))},lr=async(e,r)=>{try{const n=await Y({projectRoot:r,schemaDir:J(r)}),a=K(n),i=(e.ensureExample??z)(r,a);i.length>0&&e.logger.info(`Updated .dev.vars.example with secrets for: ${a.join(", ")} (${i.join(", ")})`)}catch{}(await(e.ensureEnv??Q)({confirm:Le(),cwd:r,info:n=>{e.logger.info(n)}})).status==="declined"&&!Z()&&e.logger.info(`hint: ${D} was not scaffolded (non-interactive run). Copy ${q} → ${D} and fill in secrets, or run \`lunora dev\` in an interactive terminal to scaffold automatically.`);try{(e.fillSecrets??ee)({cwd:r,info:n=>{e.logger.info(n)}})}catch{}},ur=(e,r,t,n,a)=>{if(e.flavor!=="wrangler")return;n!==void 0&&I(r,{studioUrl:n});let i;try{i=Qe(r,t)}catch{}return cr(t,e,n,a),i},fr=(e,r)=>{const t=Number(process.env[p]),n=G(r,{background:process.env[M]==="1",logFile:process.env[X],mode:"cli",pid:process.pid,startedAt:new Date().toISOString(),url:e.workerOrigin},Number.isInteger(t)&&t>0?{supersedePid:t}:void 0);return n.ok?void 0:n.existing},gr=e=>{const{cwd:r,destination:t,logger:n,plan:a}=e,i=t!==void 0,u=t??re,o=se({destination:u,dev:{...a.flavor==="wrangler"?{origin:a.workerOrigin}:{},statusFile:F},logger:i?n:{...n,success:()=>{},warn:()=>{}},projectRoot:r});return o.error!==void 0?i?o:(n.debug?.(`skipped the default binding manifest: ${o.error}`),{}):{written:u}},vr=async(e,r,t,n)=>{if(r.studioEnabled)try{return await(e.startStudio??ir)({apiSpec:e.apiSpec,cwd:t,logger:{warnOnce:a=>{n.warn(a)}},port:r.studioPort,workerOrigin:r.workerOrigin})}catch(a){n.warn(`studio server failed to start (${a instanceof Error?a.message:String(a)}) — continuing without it`);return}},mr=e=>{const r=[e.runsCodegenWatch?"codegen watch":void 0,e.studioEnabled?"studio":void 0].filter(n=>n!==void 0);return`--no-worker: not starting wrangler. ${r.length>0?`${r.join(" + ")} running`:"nothing else to run"}; your task runner owns the worker on ${e.workerOrigin}.`},wr=(e,r,t,n,a)=>{if(!(e.sidecar===void 0||!Ke(r)))try{L({apiSpec:r.apiSpec,lunoraDirectory:"lunora",projectRoot:t,target:a})}catch(i){n.warn(`codegen (pre-sidecar) failed: ${i instanceof Error?i.message:String(i)} — the framework dev server will retry`)}},hr=e=>e.flavor==="vite"?"starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)":`starting ${e.wrangler.tag} dev + studio`,Sr=async(e,r,t,n,a)=>{try{return e.celldSession===!0?await ze({logger:a,port:e.workerPort,projectRoot:n,start:r.startCelldSession}):t(e.wrangler,a)}catch(i){a.error(`could not start the worker: ${i instanceof Error?i.message:String(i)}`);return}},yr=async e=>{const{logger:r}=e,t=e.cwd??process.cwd(),n=e.flavor??Ve(t),a=ie(t,e.target);if(a.target===void 0)throw new Error(a.error??"unknown deploy target");const{target:i}=a,u=We(i,n,r);u==="wrangler"&&await Ze(t,r,void 0,i,void 0);const o=await Be({...e,flavor:u,target:i}),d={remoteCleanup:o.remote.cleanup,serviceConfigCleanup:o.serviceConfigCleanup};try{const g=Number(process.env[p]),h=j(t);if(h!==void 0&&h.pid!==process.pid&&h.pid!==g)return b(r,h),{code:0,plan:o};const S=fr(o,t);if(S!==void 0)return b(r,S),{code:0,plan:o};(o.flavor==="vite"||o.flavor==="framework-worker")&&(o.wrangler.env={...o.wrangler.env,[p]:String(process.pid)}),await lr(e,t),r.info(hr(o)),Ue(r,t),o.ipv4LoopbackForced&&r.info("no IPv6 loopback (::1) on this host — binding the worker to 127.0.0.1 (--ip) so wrangler dev doesn't crash. Pin `dev.ip` in wrangler.jsonc to override."),o.runsCodegenWatch&&(d.codegen=(e.startCodegen??nr)({apiSpec:e.apiSpec,jsonLogs:e.jsonLogs,logger:r,projectRoot:t,target:i})),d.studio=await vr(e,o,t,r);const v=gr({cwd:t,destination:e.emitBindings,logger:r,plan:o});if(v.error!==void 0)return r.error(v.error),{code:E.USAGE,plan:o};await d.codegen?.ready;const c=d.studio?.url;o.frameworkHint!==void 0&&r.warn(o.frameworkHint);const s=()=>{o.flavor==="wrangler"&&(d.readyProbe=new AbortController,sr({cwd:t,logger:r,origin:o.workerOrigin,probe:e.probeReady,signal:d.readyProbe.signal}))};if(!o.workerEnabled)return s(),r.info(mr(o)),d.tunnel=R({cwd:t,logger:r,plan:o,tunnel:e.tunnel}),{code:await(e.waitForInterrupt??Ge)(r),plan:o};wr(o,e,t,r,i);const l=e.startWorker??Xe,m=await Sr(o,e,l,t,r);if(m===void 0)return{code:E.FAILURE,plan:o};const f=o.sidecar===void 0?void 0:l(o.sidecar,r);return s(),d.containerLogs=ur(o,t,r,c,v.written),d.tunnel=R({cwd:t,logger:r,plan:o,tunnel:e.tunnel}),dr(r,t),{code:await Ye(m,f,r),plan:o}}finally{d.readyProbe?.abort(),U(t,process.pid),await Je(d)}},pr=e=>({codegen:e.codegen===!1?!1:void 0,studio:e.studio===!1?!1:void 0,worker:e.worker===!1?!1:void 0}),Er=e=>{if(e.local===!0&&e.remote===!0)return"`--local` and `--remote` are mutually exclusive — pass at most one.";const{entries:r,invalid:t}=x(e.allowMail);if(t.length>0)return`\`--allow-mail\` takes email addresses or '*@domain' — not ${t.map(n=>`"${n}"`).join(", ")}.`;if(r.length>0&&e.tunnel!==!0)return"`--allow-mail` only applies to a tunnel — add `--tunnel`."},br=e=>e.tunnel===!0?{allowMail:x(e.allowMail).entries}:void 0,Br=ge(async({argument:e,cwd:r,logger:t,options:n})=>{const a=n.json===!0,i=Fe({cwd:r,json:a,lines:n.lines,logger:t,subcommand:e[0]});if(i!==void 0)return i;const u=process.env[M]==="1",o=u?void 0:W(),d=a||o!==void 0;d&&ve(),o!==void 0&&n.background!==!0&&t.info(`AI agent detected (${o.name} via ${o.variable}) — starting the dev server in background mode with JSON logs. Set LUNORA_AGENT_MODE=0 to opt out.`);const g=Er(n);if(g!==void 0)return t.error(`dev: ${g}`),{code:E.USAGE};const h=br(n),S=n.local===!0?!1:oe({configPreference:B(r),envValue:process.env.LUNORA_REMOTE,flag:n.remote});if(!u&&(n.background===!0||o!==void 0)){const v=j(r);return v!==void 0?(b(t,v),{code:0}):(h?.allowMail.length===0&&xe(t),He({cwd:r,jsonLogs:d,logger:t,options:n,remote:S}))}return yr({apiSpec:ae(n.apiSpec),cwd:r,emitBindings:n.emitBindings,inspectorPort:n.inspectorPort,jsonLogs:d,local:n.local,logger:t,port:n.port,remote:S,target:n.target,tunnel:h,workerPort:n.workerPort,...pr(n)})});export{Er as devFlagConflict,Br as execute,pr as negatableDevFlags,yr as runDevCommand};