@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands
8 lines (7 loc) • 22.7 kB
JavaScript
import{spawnSync as I,spawn as Y}from"node:child_process";import{existsSync as R,watch as ge,readFileSync as z,mkdirSync as pe,openSync as X,closeSync as Q,statSync as we,readSync as he}from"node:fs";import{join as y,dirname as Se}from"node:path";import{runCodegen as Z}from"@lunora/codegen";import{detectAgentRules as q,claimDevServerState as ee,DEV_HANDOFF_ENV as E,clearDevServerState as w,readDevServerState as _,readLiveDevServerState as b,detectFramework as re,readProjectDependencyNames as Ee,DEV_LOG_FILE_ENV as te,DEV_DAEMON_ENV as O,updateDevServerState as oe,isRecordedProcessCurrent as ke,DEV_LOG_FILE as ye,detectAiAgent as be,readProjectRemotePreference as Te,resolveTargetOrThrow as Le,inferLunoraBindings as Ie,packageNamesFromBindings as Re,ensureDevVarsExample as _e,ensureDevVariables as Oe,isInteractive as Pe,DEV_VARS_FILE as $,DEV_VARS_EXAMPLE_FILE as Ae,fillDevSecrets as De,claimAgentRulesHint as Ne,AGENT_RULES_HINT as $e,resolveDeployDriver as xe,resolveProjectTarget as Ce,discoverContainerInfo as Me,streamContainerLogs as Fe,formatLunoraEvent as Ge}from"@lunora/config";import{resolveRemoteEnabled as je,findWranglerFile as ne,readWranglerJsonc as se,materializeRemoteWranglerConfig as Ue}from"@lunora/config/cloudflare";import{p as Ve}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{r as He}from"../packem_shared/codegen-error-C04fGQjf.mjs";import{r as We}from"../packem_shared/platform-diagnostics-uOXqysk3.mjs";import{d as Be}from"../packem_shared/command-DwC11Cwe.mjs";import{d as ae,e as T,a as Ke}from"../packem_shared/detect-package-manager-DSaAqRof.mjs";import{createServer as Je,connect as Ye}from"node:net";import{forceJsonLogging as ze}from"../packem_shared/createLogger-hGafwf-3.mjs";import{networkInterfaces as Xe}from"node:os";import{spawnShellCompat as ie}from"../packem_shared/createRecordingSpawner-DJlNacZ6.mjs";import{createServer as Qe,request as Ze}from"node:http";import{loadStudioAssets as x,studioAssetsStamp as C,renderStudioHtml as qe,resolveAdminToken as er,transportRejectionReason as M,SCHEMA_EDIT_ENDPOINT as rr,POLICY_SCAFFOLD_ENDPOINT as tr,SEED_ENDPOINT as or,serveJsonHandler as nr,isStandaloneModulePath as sr,readStandaloneAsset as ar,assetContentType as ir,handleSchemaEditRequest as cr,handlePolicyScaffoldRequest as dr,handleSeedRequest as lr}from"@lunora/config/studio-host";import{c as ur}from"../packem_shared/tui-prompts-Dd0pDuNr.mjs";import{setTimeout as L}from"node:timers/promises";import{p as h}from"../packem_shared/output-format-DfogXx0p.mjs";const fr=100,mr=/[/\\]/u,F=(e,r,o)=>{const{apiSpec:t,logger:n,projectRoot:s,target:i}=e;try{const a=Z({apiSpec:t,lunoraDirectory:r,projectRoot:s,target:i});n.success(`codegen: wrote ${r}/_generated (${o})`),We(a.platformDiagnostics,n)}catch(a){n.error(He(a,o))}},vr=e=>{const r=e.lunoraDirectory??"lunora",o=y(e.projectRoot,r),t=e.debounceMs??fr;F(e,r,"startup");const n=c=>(e.logger.warn(`codegen watch unavailable (${c}) — schema edits will NOT auto-regenerate. Run \`lunora codegen\` manually after each edit.`),{close:()=>{},watchAvailable:!1});let s,i;if(!R(o))return n(`no such directory: ${o}`);try{i=ge(o,{recursive:!0},(c,d)=>{typeof d=="string"&&d.split(mr).includes("_generated")||(s&&clearTimeout(s),s=setTimeout(()=>{F(e,r,`change: ${d??"?"}`)},t))})}catch(c){return n(c instanceof Error?c.message:String(c))}const a=i;return{close:()=>{s&&clearTimeout(s),a.close()},watchAvailable:!0}},gr=64,ce="127.0.0.1",pr=65535,wr=(e,r=ce)=>new Promise(o=>{const t=Je();t.once("error",()=>{o(!1)}),t.once("listening",()=>{t.close(()=>{o(!0)})}),t.listen(e,r)}),hr=async(e,r=ce,o=gr)=>{for(let t=e;t<e+o&&t<=pr;t+=1)if(await wr(t,r))return t;return e},G=(e=Xe)=>Object.values(e()).some(r=>r?.some(o=>o.internal&&o.address==="::1")),j="/_lunora",U=e=>{const r=e.indexOf("?");return r===-1?e:e.slice(0,r)},Sr=(e,r,o)=>{const t=Ze({headers:{...e.headers,host:o.host},hostname:o.hostname,method:e.method,path:e.url,port:o.port},n=>{r.writeHead(n.statusCode??502,n.headers),n.pipe(r)});t.on("error",n=>{r.statusCode=502,r.end(`lunora dev: worker unreachable (${n.message})`)}),e.pipe(t)},Er=5e3,kr=(e,r,o,t)=>{const n=Ye({host:t.hostname,port:Number(t.port)},()=>{n.setTimeout(0);const s=[`${e.method??"GET"} ${e.url??"/"} HTTP/1.1`];for(const[i,a]of Object.entries(e.headers))if(i!=="host")for(const c of Array.isArray(a)?a:[a])c!==void 0&&s.push(`${i}: ${c}`);s.push(`host: ${t.host}`,"",""),n.write(s.join(`\r
`)),o.length>0&&n.write(o),n.pipe(r),r.pipe(n)});n.setTimeout(Er,()=>n.destroy()),n.on("error",()=>r.destroy()),r.on("error",()=>n.destroy())},yr=async e=>{const r=e.host??"127.0.0.1",o=r==="127.0.0.1"||r==="localhost"||r==="::1",t=new URL(e.workerOrigin);let n=x(e.logger,import.meta.url),s=C(import.meta.url);const i=qe({adminToken:o?er(e.cwd):void 0,basePath:"/",dataEditable:o,rulesInstalled:q(e.cwd).installed,runAsIdentity:o,schemaEditable:o,scriptSrc:"/studio.js",styleHref:"/styles.css"}),a=(l,u,f)=>{l.statusCode=200,l.setHeader("Content-Type",f),l.end(u)},c=(l,u,f)=>{l.statusCode=u,l.setHeader("Content-Type","text/plain"),l.end(f)},d=(l,u,f,p)=>{if(o){nr(l,u,f,e.cwd);return}c(u,403,p)},m=(l,u)=>{const f=l==="/styles.css",p=sr(l);if(!f&&!p)return!1;const A=C(import.meta.url);if(A!==s&&(n=x(e.logger,import.meta.url),s=A),n===void 0)return c(u,501,"Lunora studio assets not found — install and build @lunora/studio."),!0;if(f)return a(u,n.styles,"text/css; charset=utf-8"),!0;const D=l.slice(l.lastIndexOf("/")+1),N=ar(D,import.meta.url);return N===void 0?(c(u,404,"Not found"),!0):(a(u,N,ir(D)),!0)},v=Buffer.from(i),g=Qe((l,u)=>{const f=U(l.url??"/");if(o){const p=M(l,e.logger);if(p!==void 0){c(u,403,p);return}}if(f.startsWith(j)){if(!o){c(u,403,"Lunora studio: the worker admin proxy is only available on a loopback bind.");return}Sr(l,u,t);return}if(f===rr){d(l,u,cr,"Lunora schema editing is only available on loopback hosts in dev.");return}if(f===tr){d(l,u,dr,"Lunora policy scaffolding is only available on loopback hosts in dev.");return}if(f===or){d(l,u,lr,"Lunora data seeding is only available on loopback hosts in dev.");return}m(f,u)||a(u,v,"text/html; charset=utf-8")});return g.on("upgrade",(l,u,f)=>{if(!o||M(l,e.logger)!==void 0){u.destroy();return}U(l.url??"").startsWith(j)?kr(l,u,f,t):u.destroy()}),await new Promise((l,u)=>{g.once("error",f=>{f.code==="EADDRINUSE"?u(new Error(`studio port ${String(e.port)} is already in use — pass a different port with --port, or stop the process using it`,{cause:f})):u(f)}),g.listen(e.port,r,()=>{l()})}),{close:()=>new Promise(l=>{g.close(()=>{l()})}),url:`http://${r==="0.0.0.0"||r==="::"?"localhost":r}:${String(e.port)}`}},br=12e4,de=250,Tr=1e4,Lr=100,Ir=40,S=256*1024,Rr="LUNORA_DEV_READY_TIMEOUT_MS",_r=new Set(["nuxt","sveltekit"]),P=e=>_r.has(re(e).framework)?"framework-worker":Ee(e).has("@lunora/vite")?"vite":"wrangler",le=e=>{const r=ae(e);let o;try{const t=z(y(e,"package.json"),"utf8");o=JSON.parse(t).scripts?.dev}catch{}if(o===void 0||o.trim()===""||o.includes("lunora")){const t=T(r,"vite",["dev"]);return{args:t.args,command:t.command}}return{args:["run","dev"],command:r}},Or=async e=>{try{return await fetch(new URL("/_lunora/status",e),{signal:AbortSignal.timeout(1e3)}),!0}catch{return!1}},Pr=e=>{pe(Se(e.logPath),{recursive:!0});const r=X(e.logPath,"w",384);let o;const t=ie(e.command,e.args);try{o=Y(t.command,t.args,{cwd:e.cwd,detached:!0,env:e.env,shell:t.shell,stdio:["ignore",r,r],windowsHide:!0})}finally{Q(r)}return o.unref(),{exited:new Promise(n=>{o.on("error",()=>{n(1)}),o.on("exit",(s,i)=>{n(s??(i?1:0))})}),pid:o.pid}},Ar=e=>{try{const{size:r}=we(e);if(r<=S)return z(e,"utf8");const o=X(e,"r");try{const t=Buffer.alloc(S),n=he(o,t,0,S,r-S),s=t.toString("utf8",0,n);return s.slice(s.indexOf(`
`)+1)}finally{Q(o)}}catch{return}},ue=(e,r)=>{const o=Ar(e);if(o===void 0)return[];const t=o.split(`
`);return t.at(-1)===""&&t.pop(),r>0?t.slice(-r):t},fe=(e,r)=>r?.logFile??y(e,ye),me=e=>{e.info(" Stop: lunora dev stop"),e.info(" Status: lunora dev status"),e.info(" Logs: lunora dev logs")},k=(e,r)=>{e.warn(`Dev server already running at ${r.url} (pid ${String(r.pid)})`),me(e)},Dr=(e,r)=>{e.success(`Dev server running at ${r.url} (pid ${String(r.pid)})`),r.studioUrl!==void 0&&e.info(` Studio: ${r.studioUrl}`),me(e)},Nr=async e=>{const{child:r,cwd:o,deadline:t,pollIntervalMs:n,probe:s}=e,i=r.exited.then(a=>({exitCode:a}));for(;Date.now()<t;){const a=b(o);if(a!==void 0&&a.pid!==process.pid&&await s(a.url))return{state:a,status:"ready"};const c=await Promise.race([i,L(n)]);if(c!==void 0)return{...c,status:"exited"}}return{status:"timeout"}},$r=async e=>{const{cwd:r,logger:o}=e,t=fe(r,void 0),n=Number(process.env[Rr]??""),s=e.readyTimeoutMs??(Number.isFinite(n)&&n>0?n:br),i=(e.spawnDetached??Pr)({args:e.command.args,command:e.command.command,cwd:r,env:{...process.env,...e.env,[O]:"1",[te]:t,...process.env.FORCE_COLOR===void 0?{NO_COLOR:"1"}:{},...e.json?{LUNORA_LOG_JSON:"1"}:{}},logPath:t}),a=await Nr({child:i,cwd:r,deadline:Date.now()+s,pollIntervalMs:e.pollIntervalMs??de,probe:e.probe??Or});if(a.status==="ready")return Dr(o,a.state),{code:0};if(a.status==="exited"){o.error(`dev server exited before becoming ready (exit code ${String(a.exitCode)}). Last output:`);for(const d of ue(t,Ir))o.error(` ${d}`);return w(r,i.pid??process.pid),{code:a.exitCode===0?1:a.exitCode}}i.pid!==void 0&&_(r)?.pid===process.pid&&oe(r,{logFile:t,pid:i.pid});const c=i.pid===void 0?"":` (pid ${String(i.pid)})`;return o.warn(`dev server did not confirm ready within ${String(Math.round(s/1e3))}s — it may still be compiling${c}. Check \`lunora dev status\` and \`lunora dev logs\`; \`lunora dev stop\` shuts it down.`),{code:1}},xr=(e,r)=>{const o=["dev"];return e.apiSpec!==void 0&&o.push("--api-spec",e.apiSpec),e.port!==void 0&&o.push("--port",String(e.port)),e.workerPort!==void 0&&o.push("--worker-port",String(e.workerPort)),e.codegen===!1&&o.push("--no-codegen"),e.studio===!1&&o.push("--no-studio"),e.worker===!1&&o.push("--no-worker"),e.target!==void 0&&o.push("--target",e.target),r&&o.push("--remote"),o},Cr=async e=>{const{cwd:r,jsonLogs:o,logger:t,options:n,remote:s}=e,i=e.run??$r,a=P(r),c=a==="wrangler"?`http://localhost:${String(n.workerPort??8787)}`:"http://localhost:5173",d=ee(r,{background:!0,mode:"cli",pid:process.pid,startedAt:new Date().toISOString(),url:c});if(!d.ok)return d.existing!==void 0&&k(t,d.existing),{code:0};const m={[E]:String(process.pid)};try{return a==="vite"?await i({command:le(r),cwd:r,env:{...s?{LUNORA_REMOTE:"1"}:{},...m},json:o,logger:t}):await i({command:{args:[process.argv[1]??"lunora",...xr(n,s)],command:process.execPath},cwd:r,env:m,json:o,logger:t})}finally{w(r,process.pid)}},Mr=e=>{try{const r=I("ps",["-o","pgid=","-p",String(e)],{encoding:"utf8"}),o=Number.parseInt(r.stdout.trim(),10);return Number.isInteger(o)&&o>0?o:void 0}catch{return}},Fr=(e,r,o=process.platform,t=I)=>{if(o==="win32"){try{t("taskkill",["/pid",String(e.pid),"/T","/F"],{stdio:"ignore"})}catch{}return}if(e.background===!0)try{r(-(Mr(e.pid)??e.pid),"SIGKILL");return}catch{}try{r(e.pid,"SIGKILL")}catch{}},Gr=(e,r)=>(r.json?h({pid:e.pid,stopped:!1}):r.logger.error(`dev server (pid ${String(e.pid)}) survived the force-kill — record kept; retry \`lunora dev stop\` or kill the process manually.`),{code:1}),jr=async e=>{const{cwd:r,logger:o}=e,t=_(r),n=e.alive??(v=>v===t?.pid&&ke(t)),s=e.signal??((v,g)=>{process.kill(v,g)}),i=e.pollIntervalMs??de,a=e.stopGraceMs??Tr,c=e.platform??process.platform,d=e.spawnSyncImpl??I;if(t===void 0||!n(t.pid))return t!==void 0&&w(r,t.pid),e.json?h({running:!1,stopped:!1}):o.info("No dev server running."),{code:0};try{s(t.pid,"SIGTERM")}catch{}const m=Date.now()+a;for(;n(t.pid)&&Date.now()<m;)await L(i);return n(t.pid)&&(Fr(t,s,c,d),await L(i),n(t.pid))?Gr(t,e):(w(r,t.pid),e.json?h({pid:t.pid,stopped:!0}):o.success(`Stopped dev server (pid ${String(t.pid)}).`),{code:0})},Ur=e=>{const{cwd:r,logger:o}=e,t=b(r);if(t===void 0)return e.json?h({running:!1}):o.info("No dev server running."),{code:0};const n=e.now??Date.now,s=t.startedAt===void 0?Number.NaN:Date.parse(t.startedAt),i=Number.isFinite(s)?Math.max(0,Math.round((n()-s)/1e3)):void 0;if(e.json)return h({background:t.background===!0,logFile:t.logFile,mode:t.mode,pid:t.pid,running:!0,startedAt:t.startedAt,studioUrl:t.studioUrl,uptimeSeconds:i,url:t.url}),{code:0};const a=[`pid ${String(t.pid)}`,...i===void 0?[]:[`uptime ${String(i)}s`],t.background===!0?"background":"foreground"];return o.success(`Dev server running at ${t.url} (${a.join(", ")})`),t.studioUrl!==void 0&&o.info(` Studio: ${t.studioUrl}`),t.logFile!==void 0&&o.info(` Logs: lunora dev logs (${t.logFile})`),{code:0}},Vr=e=>{const{cwd:r,logger:o}=e,t=_(r),n=fe(r,t);if(!R(n))return o.info("No dev server logs found — output is captured when the server runs in background mode (`lunora dev --background`)."),{code:0};const s=ue(n,e.lines??Lr);return s.length>0&&process.stdout.write(`${s.join(`
`)}
`),{code:0}},Hr=e=>{const{cwd:r,json:o,lines:t,logger:n,subcommand:s}=e;if(s==="stop")return jr({cwd:r,json:o,logger:n});if(s==="status")return Ur({cwd:r,json:o,logger:n});if(s==="logs")return Vr({cwd:r,lines:t,logger:n});if(s!==void 0)return n.error(`dev: unknown subcommand "${s}" — expected stop | status | logs (or no subcommand to start the dev server)`),{code:1}},V="wrangler.dev.jsonc",H=6173,ve=8787,W=5173,Wr=5e3,Br=(e,r)=>{const o=()=>{};if(!e.remote)return{args:[],plan:{bindings:[],cleanup:o,enabled:!1}};const n=(e.materializeRemote??Ue)({enabled:!0,projectRoot:r}),s=n.remoteBindings.map(a=>`${a.binding} (${a.kind})`),{cleanup:i}=n;return n.configPath===void 0?{args:[],plan:{bindings:s,cleanup:i,enabled:!0,reason:n.reason}}:{args:["--config",n.configPath],plan:{bindings:s,cleanup:i,enabled:!0}}},B=e=>se(e).parsed?.dev?.ip,K=(e,r,o)=>{if(o!==void 0){const n=y(e,o);if(R(n)&&B(n)!==void 0)return[]}const t=ne(e);return t!==void 0&&B(t)!==void 0?[]:r()?[]:["--ip","127.0.0.1"]},Kr=async(e,r)=>{if(e.workerPort!==void 0)return e.workerPort;const o=ne(r);if(o!==void 0){const{parsed:t}=se(o);if(typeof t?.dev?.port=="number")return t.dev.port}return(e.findFreePort??hr)(ve)},Jr=e=>{const r=e.cwd??process.cwd(),o=ae(r),t=e.flavor??P(r);if(t==="vite"||t==="framework-worker"){const m=le(r);let v;if(t==="framework-worker"){const g=K(r,e.hasIpv6Loopback??G,V),l=xe(Ce(r)).toolchain?.dev({configPath:V,extraArgs:[...g,"--var","WORKER_ENV:development"]}),u=T(o,l?.tool??"wrangler",l?.args??[]);v={args:u.args,command:u.command,cwd:r,tag:"worker"}}return e.worker===!1&&e.logger.warn(`--no-worker does not apply to the ${t} flavor: Vite owns the worker, codegen and studio in-process. Run your framework's dev script instead.`),{codegenEnabled:!1,flavor:t,ipv4LoopbackForced:!1,remote:{bindings:[],cleanup:()=>{},enabled:e.remote===!0},...v?{sidecar:v}:{},studioEnabled:!1,studioPort:e.port??H,workerEnabled:!0,workerOrigin:`http://localhost:${String(W)}`,workerPort:W,wrangler:{args:m.args,command:m.command,cwd:r,...e.remote===!0?{env:{LUNORA_REMOTE:"1"}}:{},tag:"vite"}}}const n=re(r),s=n.framework==="none"?void 0:`this project uses ${n.framework} — the worker runs inside Vite there. run \`${Ke(o,"dev")}\` for the full app (frontend + HMR); \`lunora dev\` starts only the worker.`,i=e.workerPort??ve,a=Br(e,r),c=K(r,e.hasIpv6Loopback??G),d=T(o,"wrangler",["dev","--port",String(i),...c,"--var","WORKER_ENV:development",...a.args]);return{codegenEnabled:e.codegen!==!1,flavor:t,frameworkHint:s,ipv4LoopbackForced:c.length>0,remote:a.plan,studioEnabled:e.studio!==!1,studioPort:e.port??H,workerEnabled:e.worker!==!1,workerOrigin:`http://localhost:${String(i)}`,workerPort:i,wrangler:{args:d.args,command:d.command,cwd:r,tag:"wrangler"}}},J=(e,r,o,t)=>{if(e.length===0)return;const n=Ge(e);if(n){t[n.level](`[lunora] ${n.text}`);return}const s=`[${r}] ${e}`;o==="stderr"?t.warn(s):t.info(s)},Yr=(e,r,o)=>{const t=(n,s)=>{if(!n)return;let i="";n.on("data",a=>{i+=a.toString("utf8");const c=i.split(`
`);i=c.pop()??"";for(const d of c)J(d.trimEnd(),r,s,o)}),n.on("end",()=>{J(i.trimEnd(),r,s,o),i=""})};t(e.stdout,"stdout"),t(e.stderr,"stderr")},zr=(e,r)=>{const o=ie(e.command,e.args),t=Y(o.command,o.args,{cwd:e.cwd??process.cwd(),env:e.env?{...process.env,...e.env}:process.env,shell:o.shell,stdio:["inherit","pipe","pipe"]});return Yr(t,e.tag,r),{exited:new Promise(n=>{t.on("error",s=>{r.error(`[${e.tag}] failed to start: ${s.message}`),n(1)}),t.on("exit",s=>{n(s??0)})}),kill:n=>{try{t.kill(n)}catch{}}}},Xr=(e,r,o)=>{e.info(""),e.success("Lunora dev"),e.info(` ➜ Worker: ${r.workerOrigin}`),o!==void 0&&e.info(` ➜ Studio: ${o}`),r.codegenEnabled&&e.info(" ➜ Codegen: watching lunora/"),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("")},Qr=(e,r)=>{q(r).installed||!Ne()||(e.info(` ⓘ ${$e}`),e.info(""))},Zr=(e,r)=>{if(process.env.LUNORA_CONTAINER_LOGS==="0")return;const t=Me(e,"lunora").containers.map(n=>({className:n.className,exportName:n.exportName}));if(t.length!==0)return Fe({containers:t,onLine:n=>{const s=`[container:${n.name}] ${n.text}`;n.level==="error"?r.warn(s):r.info(s)},onUnavailable:n=>{r.warn(`[container] Docker engine unreachable — container logs unavailable (${n})`)}})},qr=async e=>{e.codegen?.close(),e.containerLogs?.close(),await e.studio?.close().catch(()=>{});try{e.remoteCleanup?.()}catch{}},et=async(e,r)=>{try{const t=await Ie({projectRoot:r}),n=Re(t),s=(e.ensureExample??_e)(r,n);s.length>0&&e.logger.info(`Updated .dev.vars.example with secrets for: ${n.join(", ")} (${s.join(", ")})`)}catch{}(await(e.ensureEnv??Oe)({confirm:ur(),cwd:r,info:t=>{e.logger.info(t)}})).status==="declined"&&!Pe()&&e.logger.info(`hint: ${$} was not scaffolded (non-interactive run). Copy ${Ae} → ${$} and fill in secrets, or run \`lunora dev\` in an interactive terminal to scaffold automatically.`);try{(e.fillSecrets??De)({cwd:r,info:t=>{e.logger.info(t)}})}catch{}},rt=(e,r,o,t)=>{if(e.flavor!=="wrangler")return;t!==void 0&&oe(r,{studioUrl:t});let n;try{n=Zr(r,o)}catch{}return Xr(o,e,t),n},tt=(e,r)=>{const o=Number(process.env[E]),t=ee(r,{background:process.env[O]==="1",logFile:process.env[te],mode:"cli",pid:process.pid,startedAt:new Date().toISOString(),url:e.workerOrigin},Number.isInteger(o)&&o>0?{supersedePid:o}:void 0);return t.ok?void 0:t.existing},ot=async e=>{const r=e.cwd??process.cwd(),o=e.flavor??P(r),t=o==="wrangler"?await Kr(e,r):e.workerPort;return Jr({...e,cwd:r,flavor:o,workerPort:t})},nt=async e=>await new Promise(r=>{const o={},t=i=>{e.info(`received ${i} — shutting down`),o.sigint&&process.off("SIGINT",o.sigint),o.sigterm&&process.off("SIGTERM",o.sigterm),r(0)},n=()=>{t("SIGINT")},s=()=>{t("SIGTERM")};o.sigint=n,o.sigterm=s,process.on("SIGINT",n),process.on("SIGTERM",s)}),st=async(e,r,o)=>{let t=0,n;const s=d=>{e.kill(d),r?.kill(d)},i=()=>{t+=1,t===1?(o.info("received SIGINT — shutting down (press Ctrl-C again to force-kill)"),s("SIGTERM"),n=setTimeout(()=>{s("SIGKILL")},Wr),n.unref()):s("SIGKILL")},a=()=>{s("SIGTERM")};process.on("SIGINT",i),process.on("SIGTERM",a);const c=await Promise.race([e.exited.then(d=>({code:d,who:"worker"})),...r===void 0?[]:[r.exited.then(d=>({code:d,who:"sidecar"}))]]);return r!==void 0&&(c.who==="sidecar"?(o.warn("[worker] the Lunora sidecar (wrangler dev) exited — shutting down the framework dev server"),e.kill("SIGTERM")):r.kill("SIGTERM"),await Promise.allSettled([e.exited,r.exited])),n&&clearTimeout(n),process.off("SIGINT",i),process.off("SIGTERM",a),c.code},at=async(e,r,o,t)=>{if(r.studioEnabled)try{return await(e.startStudio??yr)({cwd:o,logger:{warnOnce:n=>{t.warn(n)}},port:r.studioPort,workerOrigin:r.workerOrigin})}catch(n){t.warn(`studio server failed to start (${n instanceof Error?n.message:String(n)}) — continuing without it`);return}},it=e=>{const r=[e.codegenEnabled?"codegen watch":void 0,e.studioEnabled?"studio":void 0].filter(t=>t!==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}.`},ct=(e,r,o,t,n)=>{if(e.sidecar!==void 0)try{Z({apiSpec:r.apiSpec,lunoraDirectory:"lunora",projectRoot:o,target:n})}catch(s){t.warn(`codegen (pre-sidecar) failed: ${s instanceof Error?s.message:String(s)} — the framework dev server will retry`)}},dt=async e=>{const r=await ot(e),{logger:o}=e,t=r.wrangler.cwd??process.cwd(),n=Le(t,e.target),s={remoteCleanup:r.remote.cleanup};try{const i=Number(process.env[E]),a=b(t);if(a!==void 0&&a.pid!==process.pid&&a.pid!==i)return k(o,a),{code:0,plan:r};const c=tt(r,t);if(c!==void 0)return k(o,c),{code:0,plan:r};(r.flavor==="vite"||r.flavor==="framework-worker")&&(r.wrangler.env={...r.wrangler.env,[E]:String(process.pid)}),await et(e,t),o.info(r.flavor==="vite"?"starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)":"starting wrangler dev + studio"),r.ipv4LoopbackForced&&o.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."),r.codegenEnabled&&(s.codegen=(e.startCodegen??vr)({apiSpec:e.apiSpec,logger:o,projectRoot:t,target:n})),s.studio=await at(e,r,t,o);const d=s.studio?.url;if(r.frameworkHint!==void 0&&o.warn(r.frameworkHint),!r.workerEnabled)return o.info(it(r)),{code:await nt(o),plan:r};ct(r,e,t,o,n);const m=e.startWorker??zr,v=m(r.wrangler,o),g=r.sidecar===void 0?void 0:m(r.sidecar,o);return s.containerLogs=rt(r,t,o,d),Qr(o,t),{code:await st(v,g,o),plan:r}}finally{w(t,process.pid),await qr(s)}},Pt=Be(async({argument:e,cwd:r,logger:o,options:t})=>{const n=t.json===!0,s=Hr({cwd:r,json:n,lines:t.lines,logger:o,subcommand:e[0]});if(s!==void 0)return s;const i=process.env[O]==="1",a=i?void 0:be(),c=n||a!==void 0;c&&ze(),a!==void 0&&t.background!==!0&&o.info(`AI agent detected (${a.name} via ${a.variable}) — starting the dev server in background mode with JSON logs. Set LUNORA_AGENT_MODE=0 to opt out.`);const d=je({configPreference:Te(r),envValue:process.env.LUNORA_REMOTE,flag:t.remote});if(!i&&(t.background===!0||a!==void 0)){const m=b(r);return m!==void 0?(k(o,m),{code:0}):Cr({cwd:r,jsonLogs:c,logger:o,options:t,remote:d})}return dt({apiSpec:Ve(t.apiSpec),codegen:t.codegen===!1?!1:void 0,cwd:r,logger:o,port:t.port,remote:d,studio:t.studio===!1?!1:void 0,target:t.target,workerPort:t.workerPort})});export{P as detectDevFlavor,Pt as execute,Jr as planDevCommand,Kr as resolveWorkerPort,dt as runDevCommand};