@lunora/cli 1.0.0-alpha.379 → 1.0.0-alpha.380

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.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CodegenOptions, Finding, FieldSnapshot, SchemaIR } from '@lunora/codegen';
2
2
  import { ensureDevVariables, ensureDevVarsExample, fillDevSecrets, startCelldDevSession, LintTool, LintIgnoreOutcome, PackageManager, PackageManagerProbe } from '@lunora/config';
3
- import { materializeRemoteWranglerConfig, materializeServiceDevConfigs } from '@lunora/config/cloudflare';
3
+ import { materializeDevWranglerConfig, materializeServiceDevConfigs } from '@lunora/config/cloudflare';
4
4
  export { REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, type WranglerProjectValidationOptions as WranglerValidationOptions, type WranglerValidationReport, type WranglerProjectValidationResult as WranglerValidationResult, validateWranglerProject as validateWrangler, validateWranglerConfig } from '@lunora/config/cloudflare';
5
5
  /** Every command name the CLI registers (drives the `CommandName` type + tests). */
6
6
  declare const COMMANDS: readonly ["init", "add", "dev", "codegen", "build", "deploy", "prepare", "link", "cloudflare", "logs", "run", "insights", "reset", "migrate", "export", "import", "seed", "shards", "backup", "eval", "verify", "info", "doctor", "env", "analyze", "view", "docs", "registry", "rules", "mcp"];
@@ -965,8 +965,8 @@ interface DevCommandOptions {
965
965
  /** Pass `--local` to `wrangler dev`: no remote proxy session, so a binding with no local mode (`ai`) cannot stop the session from starting. */
966
966
  local?: boolean;
967
967
  logger: Logger;
968
- /** Injection seam for tests — defaults to the real remote-config materializer. */
969
- materializeRemote?: typeof materializeRemoteWranglerConfig;
968
+ /** Injection seam for tests — defaults to the real dev-config materializer (remote bindings and the logged-out `ai` withholding). */
969
+ materializeDev?: typeof materializeDevWranglerConfig;
970
970
  /** Injection seam for tests — defaults to the real per-service dev-config materializer. */
971
971
  materializeServiceConfigs?: typeof materializeServiceDevConfigs;
972
972
  /** Studio server port. */
@@ -1016,6 +1016,8 @@ interface DevRemotePlan {
1016
1016
  enabled: boolean;
1017
1017
  /** Why remote mode didn't take effect despite being requested, for logging. */
1018
1018
  reason?: string;
1019
+ /** `ai` bindings a session left out for lack of Cloudflare credentials, for the banner warning. */
1020
+ withheld: string[];
1019
1021
  }
1020
1022
  interface DevCommandPlan {
1021
1023
  /**
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CodegenOptions, Finding, FieldSnapshot, SchemaIR } from '@lunora/codegen';
2
2
  import { ensureDevVariables, ensureDevVarsExample, fillDevSecrets, startCelldDevSession, LintTool, LintIgnoreOutcome, PackageManager, PackageManagerProbe } from '@lunora/config';
3
- import { materializeRemoteWranglerConfig, materializeServiceDevConfigs } from '@lunora/config/cloudflare';
3
+ import { materializeDevWranglerConfig, materializeServiceDevConfigs } from '@lunora/config/cloudflare';
4
4
  export { REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, type WranglerProjectValidationOptions as WranglerValidationOptions, type WranglerValidationReport, type WranglerProjectValidationResult as WranglerValidationResult, validateWranglerProject as validateWrangler, validateWranglerConfig } from '@lunora/config/cloudflare';
5
5
  /** Every command name the CLI registers (drives the `CommandName` type + tests). */
6
6
  declare const COMMANDS: readonly ["init", "add", "dev", "codegen", "build", "deploy", "prepare", "link", "cloudflare", "logs", "run", "insights", "reset", "migrate", "export", "import", "seed", "shards", "backup", "eval", "verify", "info", "doctor", "env", "analyze", "view", "docs", "registry", "rules", "mcp"];
@@ -965,8 +965,8 @@ interface DevCommandOptions {
965
965
  /** Pass `--local` to `wrangler dev`: no remote proxy session, so a binding with no local mode (`ai`) cannot stop the session from starting. */
966
966
  local?: boolean;
967
967
  logger: Logger;
968
- /** Injection seam for tests — defaults to the real remote-config materializer. */
969
- materializeRemote?: typeof materializeRemoteWranglerConfig;
968
+ /** Injection seam for tests — defaults to the real dev-config materializer (remote bindings and the logged-out `ai` withholding). */
969
+ materializeDev?: typeof materializeDevWranglerConfig;
970
970
  /** Injection seam for tests — defaults to the real per-service dev-config materializer. */
971
971
  materializeServiceConfigs?: typeof materializeServiceDevConfigs;
972
972
  /** Studio server port. */
@@ -1016,6 +1016,8 @@ interface DevRemotePlan {
1016
1016
  enabled: boolean;
1017
1017
  /** Why remote mode didn't take effect despite being requested, for logging. */
1018
1018
  reason?: string;
1019
+ /** `ai` bindings a session left out for lack of Cloudflare credentials, for the banner warning. */
1020
+ withheld: string[];
1019
1021
  }
1020
1022
  interface DevCommandPlan {
1021
1023
  /**
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{C as o,V as a,r as n}from"./packem_shared/cli-B5fWPcrA.mjs";import{runCodegenCommand as m}from"./packem_chunks/runCodegenCommand.mjs";import{runDeployCommand as p}from"./packem_chunks/runDeployCommand.mjs";import{runDevCommand as f}from"./packem_chunks/runDevCommand.mjs";import{p as i}from"./packem_shared/plan-DJeIwzFu.mjs";import{runInitCommand as l}from"./packem_chunks/runInitCommand.mjs";import{runMigrateGenerateCommand as I}from"./packem_shared/runMigrateGenerateCommand-Ii_WMZEA.mjs";import{runResetCommand as g}from"./packem_chunks/runResetCommand.mjs";import{runRpcCommand as T}from"./packem_chunks/runRpcCommand.mjs";import{EXIT_CODE as S,exitCodeForCode as A,exitCodeForError as c,exitCodeForStatus as M}from"./packem_shared/EXIT_CODE-D_XCixv8.mjs";import{insertSchemaExtension as v}from"./packem_shared/insertSchemaExtension-DuiV6cba.mjs";import{createLogger as b,pail as O}from"./packem_shared/createLogger-Cl18I8AX.mjs";import{diffSnapshots as y,renderAddColumn as L,renderCreateIndex as w,renderCreateTable as B,renderDropIndex as P,renderDropTable as U,renderMigrationFile as V,validatorKindToSqlType as W}from"./packem_shared/diffSnapshots-WWwx-KvZ.mjs";import{default as N}from"./packem_shared/schemaIrToSnapshot-BjK-0IRo.mjs";import{createRecordingSpawner as j,defaultSpawner as q}from"./packem_shared/createRecordingSpawner-Ch7zuQQE.mjs";import{default as K}from"./packem_shared/parseManifest-CwPTKdtS.mjs";import{D as Y,r as Z}from"./packem_shared/import-BRM1hs7j.mjs";import{REQUIRED_COMPATIBILITY_DATE as z,REQUIRED_FLAG as J,validateWranglerProject as $,validateWranglerConfig as rr}from"@lunora/config/cloudflare";import{b as or}from"./packem_shared/catalog-CahzmDLV.mjs";import{r as nr,a as tr,b as mr}from"./packem_shared/commands-DgUDxkA8.mjs";import{runExportCommand as pr}from"./packem_shared/runExportCommand-slAq5BCF.mjs";export{o as COMMANDS,Y as DEFAULT_IMPORT_BATCH_SIZE,S as EXIT_CODE,z as REQUIRED_COMPATIBILITY_DATE,J as REQUIRED_FLAG,a as VERSION,or as buildRegistryIndex,b as createLogger,j as createRecordingSpawner,q as defaultSpawner,y as diffSnapshots,A as exitCodeForCode,c as exitCodeForError,M as exitCodeForStatus,v as insertSchemaExtension,O as pail,K as parseManifest,i as planDevCommand,L as renderAddColumn,w as renderCreateIndex,B as renderCreateTable,P as renderDropIndex,U as renderDropTable,V as renderMigrationFile,nr as runAddCommand,tr as runBuildIndexCommand,n as runCli,m as runCodegenCommand,p as runDeployCommand,f as runDevCommand,pr as runExportCommand,Z as runImportCommand,l as runInitCommand,I as runMigrateGenerateCommand,mr as runRegistryViewCommand,g as runResetCommand,T as runRpcCommand,N as schemaIrToSnapshot,$ as validateWrangler,rr as validateWranglerConfig,W as validatorKindToSqlType};
1
+ import{C as o,V as a,r as n}from"./packem_shared/cli-B5fWPcrA.mjs";import{runCodegenCommand as m}from"./packem_chunks/runCodegenCommand.mjs";import{runDeployCommand as p}from"./packem_chunks/runDeployCommand.mjs";import{runDevCommand as f}from"./packem_chunks/runDevCommand.mjs";import{p as i}from"./packem_shared/plan-Dl57T11P.mjs";import{runInitCommand as l}from"./packem_chunks/runInitCommand.mjs";import{runMigrateGenerateCommand as I}from"./packem_shared/runMigrateGenerateCommand-Ii_WMZEA.mjs";import{runResetCommand as g}from"./packem_chunks/runResetCommand.mjs";import{runRpcCommand as T}from"./packem_chunks/runRpcCommand.mjs";import{EXIT_CODE as S,exitCodeForCode as A,exitCodeForError as c,exitCodeForStatus as M}from"./packem_shared/EXIT_CODE-D_XCixv8.mjs";import{insertSchemaExtension as v}from"./packem_shared/insertSchemaExtension-DuiV6cba.mjs";import{createLogger as b,pail as O}from"./packem_shared/createLogger-Cl18I8AX.mjs";import{diffSnapshots as y,renderAddColumn as L,renderCreateIndex as w,renderCreateTable as B,renderDropIndex as P,renderDropTable as U,renderMigrationFile as V,validatorKindToSqlType as W}from"./packem_shared/diffSnapshots-WWwx-KvZ.mjs";import{default as N}from"./packem_shared/schemaIrToSnapshot-BjK-0IRo.mjs";import{createRecordingSpawner as j,defaultSpawner as q}from"./packem_shared/createRecordingSpawner-Ch7zuQQE.mjs";import{default as K}from"./packem_shared/parseManifest-CwPTKdtS.mjs";import{D as Y,r as Z}from"./packem_shared/import-BRM1hs7j.mjs";import{REQUIRED_COMPATIBILITY_DATE as z,REQUIRED_FLAG as J,validateWranglerProject as $,validateWranglerConfig as rr}from"@lunora/config/cloudflare";import{b as or}from"./packem_shared/catalog-CahzmDLV.mjs";import{r as nr,a as tr,b as mr}from"./packem_shared/commands-DgUDxkA8.mjs";import{runExportCommand as pr}from"./packem_shared/runExportCommand-slAq5BCF.mjs";export{o as COMMANDS,Y as DEFAULT_IMPORT_BATCH_SIZE,S as EXIT_CODE,z as REQUIRED_COMPATIBILITY_DATE,J as REQUIRED_FLAG,a as VERSION,or as buildRegistryIndex,b as createLogger,j as createRecordingSpawner,q as defaultSpawner,y as diffSnapshots,A as exitCodeForCode,c as exitCodeForError,M as exitCodeForStatus,v as insertSchemaExtension,O as pail,K as parseManifest,i as planDevCommand,L as renderAddColumn,w as renderCreateIndex,B as renderCreateTable,P as renderDropIndex,U as renderDropTable,V as renderMigrationFile,nr as runAddCommand,tr as runBuildIndexCommand,n as runCli,m as runCodegenCommand,p as runDeployCommand,f as runDevCommand,pr as runExportCommand,Z as runImportCommand,l as runInitCommand,I as runMigrateGenerateCommand,mr as runRegistryViewCommand,g as runResetCommand,T as runRpcCommand,N as schemaIrToSnapshot,$ as validateWrangler,rr as validateWranglerConfig,W as validatorKindToSqlType};
@@ -1,2 +1,2 @@
1
- import{readServiceBindings as G,runCodegen as M}from"@lunora/codegen";import{runPostCodegenHook as X,detectAgentRules as F,updateDevServerState as W,readDevServerState as Y,DEV_DAEMON_ENV as x,detectAiAgent as J,readProjectRemotePreference as K,readLiveDevServerState as V,DEV_HANDOFF_ENV as k,clearDevServerState as z,claimDevServerState as Q,DEV_LOG_FILE_ENV as Z,inferLunoraBindings as q,resolveSchemaDirectory as ee,secretSourcesFromBindings as re,ensureDevVarsExample as te,ensureDevVariables as ne,isInteractive as oe,DEV_VARS_FILE as T,DEV_VARS_EXAMPLE_FILE as ae,fillDevSecrets as ie,DEV_BINDINGS_FILE as se,DEV_STATE_FILE as H,claimAgentRulesHint as ce,AGENT_RULES_HINT as de}from"@lunora/config";import{wranglerCodegenInputs as B,resolveRemoteEnabled as le}from"@lunora/config/cloudflare";import{p as ue,a as fe}from"../packem_shared/deploy-target-DCWwiuAe.mjs";import{w as ge}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{existsSync as ve,watch as R}from"node:fs";import{join as me,dirname as we}from"node:path";import{a as he}from"../packem_shared/codegen-error-AmH54ofi.mjs";import{a as Se}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{d as ye}from"../packem_shared/command-CAXl2A1S.mjs";import{EXIT_CODE as D}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{forceJsonLogging as pe}from"../packem_shared/createLogger-Cl18I8AX.mjs";import{createServer as Ee,request as be}from"node:http";import{connect as ke}from"node:net";import{pipeline as De}from"node:stream";import{loadStudioAssets as A,studioAssetsStamp as P,renderStudioHtml as _e,resolveAdminToken as Te,requestPathname as O,transportRejectionReason as $,SCHEMA_EDIT_ENDPOINT as Re,POLICY_SCAFFOLD_ENDPOINT as Ae,SEED_ENDPOINT as Pe,sendStudioDocument as Oe,serveJsonHandler as $e,isStandaloneModulePath as Ce,readStandaloneAsset as Le,assetContentType as Ne,handleSchemaEditRequest as Ie,handlePolicyScaffoldRequest as je,handleSeedRequest as Me,applyStudioAssetCache as Fe}from"@lunora/config/studio-host";import{c as We}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{setTimeout as xe}from"node:timers/promises";import{r as Ve,d as He,P as Be,a as Ue,b as _,c as Ge,s as Xe,n as U,e as Ye,f as Je,g as Ke,h as ze,i as C,w as Qe,j as Ze,k as qe,t as er,l as rr,m as tr,o as nr}from"../packem_shared/plan-DJeIwzFu.mjs";import{provisionBindings as or}from"./runDeployCommand.mjs";const ar=100,ir=/(?:\.[cm]?[jt]sx?|(?:^|[/\\])tsconfig(?:\..+)?\.json)$/u,sr=300,L=/[/\\]/u,cr=(e,r,t)=>{const{apiSpec:n,logger:a,projectRoot:i,target:u}=e;try{const o=M({apiSpec:n,lunoraDirectory:r,projectRoot:i,target:u,...B(i)});a.success(`codegen: wrote ${r}/_generated (${t})`),Se(o.platformDiagnostics,a)}catch(o){return a.error(he(o,t)),!1}return!0},dr=e=>{const r=e.lunoraDirectory??"lunora",t=me(e.projectRoot,r),n=e.debounceMs??ar;let a=!1,i=!1,u=!1,o,d=0,v=Promise.resolve(),h;const y=new Promise(f=>{h=f}),w=f=>{a||(o=f,!i&&(i=!0,v=(async()=>{try{for(;o!==void 0;){const g=o;if(o=void 0,!!cr(e,r,g)){u=!0;try{(await X({cwd:e.projectRoot,logger:e.logger,spawner:e.spawner,stdoutToStderr:e.jsonLogs})).ran&&(d=Date.now())}finally{u=!1}}}}catch(g){e.logger.error(`codegen watch: ${g instanceof Error?g.message:String(g)}`)}finally{i=!1,h()}})()))};w("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,v),ready:y,watchAvailable:!1});let s,l;if(!ve(t))return c(`no such directory: ${t}`);const m=f=>{if(u||Date.now()-d<sr){s&&(clearTimeout(s),s=void 0);return}s&&clearTimeout(s),s=setTimeout(()=>{w(`change: ${f??"?"}`)},n)},S=[];try{l=R(t,{recursive:!0},(f,g)=>{typeof g=="string"&&g.split(L).includes("_generated")||m(g)});for(const f of G(e.projectRoot).services)if(f.rpcEntrypoint!==void 0){const g=R(we(f.wranglerPath),{recursive:!0},(b,E)=>{typeof E=="string"&&ir.test(E)&&!E.split(L).includes("node_modules")&&m(E)});g.on("error",b=>{e.logger.warn(`codegen watch: stopped watching service "${f.name}" (${b.message})`),g.close()}),S.push(g)}}catch(f){l?.close();for(const g of S)g.close();return c(f instanceof Error?f.message:String(f))}const p=l;return{close:()=>{a=!0,o=void 0,s&&clearTimeout(s),p.close();for(const f of S)f.close();return v},ready:y,watchAvailable:!0}},N="/_lunora",I=3e4,j=(e,r,t)=>{const n=be({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),De(a,r,()=>{})});n.setTimeout(I,()=>{n.destroy(new Error(`no response within ${String(I)}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)},lr=5e3,ur=(e,r,t,n)=>{const a=ke({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
2
- `)),t.length>0&&a.write(t),a.pipe(r),r.pipe(a)});a.setTimeout(lr,()=>a.destroy()),a.on("error",()=>r.destroy()),r.on("error",()=>a.destroy())},fr=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=A(e.logger,import.meta.url),i=P(import.meta.url);const u=_e({adminToken:t?Te(e.cwd):void 0,basePath:"/",dataEditable:t,rulesInstalled:F(e.cwd).installed,runAsIdentity:t,schemaEditable:t,scriptSrc:"/studio.js",styleHref:"/styles.css"}),o=(c,s,l,m,S)=>{Fe(c,s,S,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)},v=(c,s,l,m)=>{if(t){$e(c,s,l,e.cwd,{apiSpec:e.apiSpec});return}d(s,403,m)},h=(c,s,l)=>{const m=c==="/styles.css",S=Ce(c);if(!m&&!S)return!1;const p=P(import.meta.url);if(p!==i&&(a=A(e.logger,import.meta.url),i=p),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 f=c.slice(c.lastIndexOf("/")+1),g=Le(f,import.meta.url);return g===void 0?(d(l,404,"Not found"),!0):(o(s,l,g,Ne(f),f),!0)},y=Buffer.from(u),w=Ee((c,s)=>{const l=O(c.url??"/");if(t){const S=$(c,e.logger);if(S!==void 0){d(s,403,S);return}}if(l.startsWith(N)){if(!t){d(s,403,"Lunora studio: the worker admin proxy is only available on a loopback bind.");return}j(c,s,n);return}if(l===Re){v(c,s,Ie,"Lunora schema editing is only available on loopback hosts in dev.");return}if(l===Ae){v(c,s,je,"Lunora policy scaffolding is only available on loopback hosts in dev.");return}if(l===Pe){v(c,s,Me,"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"){j(c,s,n);return}Oe(s,y)});return w.on("upgrade",(c,s,l)=>{if(!t||$(c,e.logger)!==void 0){s.destroy();return}O(c.url??"").startsWith(N)?ur(c,s,l,n):s.destroy()}),await new Promise((c,s)=>{w.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)}),w.listen(e.port,r,()=>{c()})}),{close:()=>new Promise(c=>{w.close(()=>{c()})}),url:`http://${r==="0.0.0.0"||r==="::"?"localhost":r}:${String(e.port)}`}},gr=async e=>{const{cwd:r,logger:t,origin:n,signal:a}=e,i=e.probe??He,u=Ve(e.readyTimeoutMs),o=Date.now()+u,d=()=>a.aborted;for(;!d()&&Date.now()<o;){let v=!1;try{v=await i(n,a)}catch{}if(d()||Date.now()>=o)break;if(v)return W(r,{readyAt:new Date().toISOString()},{expectedPid:process.pid}),Y(r)?.readyAt!==void 0;try{await xe(Be,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},vr=(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) · ${H} (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("")},mr=(e,r)=>{F(r).installed||!ce()||(e.info(` ⓘ ${de}`),e.info(""))},wr=async(e,r)=>{try{const n=await q({projectRoot:r,schemaDir:ee(r)}),a=re(n),i=(e.ensureExample??te)(r,a);if(i.length>0){const u=a.map(o=>o.includes(":")?`${o.replace(":"," (")})`:o);e.logger.info(`Updated .dev.vars.example with secrets for: ${u.join(", ")} (${i.join(", ")})`)}}catch{}(await(e.ensureEnv??ne)({confirm:We(),cwd:r,info:n=>{e.logger.info(n)}})).status==="declined"&&!oe()&&e.logger.info(`hint: ${T} was not scaffolded (non-interactive run). Copy ${ae} → ${T} and fill in secrets, or run \`lunora dev\` in an interactive terminal to scaffold automatically.`);try{(e.fillSecrets??ie)({cwd:r,info:n=>{e.logger.info(n)}})}catch{}},hr=(e,r,t,n,a)=>{if(e.flavor!=="wrangler")return;n!==void 0&&W(r,{studioUrl:n});let i;try{i=nr(r,t)}catch{}return vr(t,e,n,a),i},Sr=(e,r)=>{const t=Number(process.env[k]),n=Q(r,{background:process.env[x]==="1",logFile:process.env[Z],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},yr=e=>{const{cwd:r,destination:t,logger:n,plan:a}=e,i=t!==void 0,u=t??se,o=ge({destination:u,dev:{...a.flavor==="wrangler"?{origin:a.workerOrigin}:{},statusFile:H},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}},pr=async(e,r,t,n)=>{if(r.studioEnabled)try{return await(e.startStudio??fr)({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}},Er=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}.`},br=(e,r,t,n,a)=>{if(!(e.sidecar===void 0||!rr(r)))try{M({apiSpec:r.apiSpec,lunoraDirectory:"lunora",projectRoot:t,target:a,...B(t)})}catch(i){n.warn(`codegen (pre-sidecar) failed: ${i instanceof Error?i.message:String(i)} — the framework dev server will retry`)}},kr=e=>e.flavor==="vite"?"starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)":`starting ${e.wrangler.tag} dev + studio`,Dr=async(e,r,t,n,a)=>{try{return e.celldSession===!0?await tr({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}},_r=async e=>{const{logger:r}=e,t=e.cwd??process.cwd(),n=e.flavor??Ye(t),a=fe(t,e.target);if(a.target===void 0)throw new Error(a.error??"unknown deploy target");const{target:i}=a,u=Je(i,n,r);u==="wrangler"&&await or(t,r,void 0,i,void 0);const o=await Ke({...e,flavor:u,target:i}),d={remoteCleanup:o.remote.cleanup,serviceConfigCleanup:o.serviceConfigCleanup};try{const v=Number(process.env[k]),h=V(t);if(h!==void 0&&h.pid!==process.pid&&h.pid!==v)return _(r,h),{code:0,plan:o};const y=Sr(o,t);if(y!==void 0)return _(r,y),{code:0,plan:o};(o.flavor==="vite"||o.flavor==="framework-worker")&&(o.wrangler.env={...o.wrangler.env,[k]:String(process.pid)}),await wr(e,t),r.info(kr(o)),ze(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??dr)({apiSpec:e.apiSpec,jsonLogs:e.jsonLogs,logger:r,projectRoot:t,target:i})),d.studio=await pr(e,o,t,r);const w=yr({cwd:t,destination:e.emitBindings,logger:r,plan:o});if(w.error!==void 0)return r.error(w.error),{code:D.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,gr({cwd:t,logger:r,origin:o.workerOrigin,probe:e.probeReady,signal:d.readyProbe.signal}))};if(!o.workerEnabled)return s(),r.info(Er(o)),d.tunnel=C({cwd:t,logger:r,plan:o,tunnel:e.tunnel}),{code:await(e.waitForInterrupt??Qe)(r),plan:o};br(o,e,t,r,i);const l=e.startWorker??Ze,m=await Dr(o,e,l,t,r);if(m===void 0)return{code:D.FAILURE,plan:o};const S=o.sidecar===void 0?void 0:l(o.sidecar,r);return s(),d.containerLogs=hr(o,t,r,c,w.written),d.tunnel=C({cwd:t,logger:r,plan:o,tunnel:e.tunnel}),mr(r,t),{code:await qe(m,S,r),plan:o}}finally{d.readyProbe?.abort(),z(t,process.pid),await er(d)}},Tr=e=>({codegen:e.codegen===!1?!1:void 0,studio:e.studio===!1?!1:void 0,worker:e.worker===!1?!1:void 0}),Rr=e=>{if(e.local===!0&&e.remote===!0)return"`--local` and `--remote` are mutually exclusive — pass at most one.";const{entries:r,invalid:t}=U(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`."},Ar=e=>e.tunnel===!0?{allowMail:U(e.allowMail).entries}:void 0,Kr=ye(async({argument:e,cwd:r,logger:t,options:n})=>{const a=n.json===!0,i=Ue({cwd:r,json:a,lines:n.lines,logger:t,subcommand:e[0]});if(i!==void 0)return i;const u=process.env[x]==="1",o=u?void 0:J(),d=a||o!==void 0;d&&pe(),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 v=Rr(n);if(v!==void 0)return t.error(`dev: ${v}`),{code:D.USAGE};const h=Ar(n),y=n.local===!0?!1:le({configPreference:K(r),envValue:process.env.LUNORA_REMOTE,flag:n.remote});if(!u&&(n.background===!0||o!==void 0)){const w=V(r);return w!==void 0?(_(t,w),{code:0}):(h?.allowMail.length===0&&Ge(t),Xe({cwd:r,jsonLogs:d,logger:t,options:n,remote:y}))}return _r({apiSpec:ue(n.apiSpec),cwd:r,emitBindings:n.emitBindings,inspectorPort:n.inspectorPort,jsonLogs:d,local:n.local,logger:t,port:n.port,remote:y,target:n.target,tunnel:h,workerPort:n.workerPort,...Tr(n)})});export{Rr as devFlagConflict,Kr as execute,Tr as negatableDevFlags,_r as runDevCommand};
1
+ import{readServiceBindings as G,runCodegen as M}from"@lunora/codegen";import{runPostCodegenHook as X,detectAgentRules as F,updateDevServerState as W,readDevServerState as Y,DEV_DAEMON_ENV as x,detectAiAgent as J,readProjectRemotePreference as K,readLiveDevServerState as V,DEV_HANDOFF_ENV as k,clearDevServerState as z,claimDevServerState as Q,DEV_LOG_FILE_ENV as Z,inferLunoraBindings as q,resolveSchemaDirectory as ee,secretSourcesFromBindings as re,ensureDevVarsExample as te,ensureDevVariables as oe,isInteractive as ne,DEV_VARS_FILE as T,DEV_VARS_EXAMPLE_FILE as ae,fillDevSecrets as ie,DEV_BINDINGS_FILE as se,DEV_STATE_FILE as H,claimAgentRulesHint as ce,AGENT_RULES_HINT as de}from"@lunora/config";import{wranglerCodegenInputs as B,resolveRemoteEnabled as le,describeWithheldWorkersAi as ue}from"@lunora/config/cloudflare";import{p as fe,a as ge}from"../packem_shared/deploy-target-DCWwiuAe.mjs";import{w as ve}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{existsSync as me,watch as R}from"node:fs";import{join as we,dirname as he}from"node:path";import{a as Se}from"../packem_shared/codegen-error-AmH54ofi.mjs";import{a as ye}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{d as pe}from"../packem_shared/command-CAXl2A1S.mjs";import{EXIT_CODE as D}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{forceJsonLogging as Ee}from"../packem_shared/createLogger-Cl18I8AX.mjs";import{createServer as be,request as ke}from"node:http";import{connect as De}from"node:net";import{pipeline as _e}from"node:stream";import{loadStudioAssets as A,studioAssetsStamp as P,renderStudioHtml as Te,resolveAdminToken as Re,requestPathname as $,transportRejectionReason as O,SCHEMA_EDIT_ENDPOINT as Ae,POLICY_SCAFFOLD_ENDPOINT as Pe,SEED_ENDPOINT as $e,sendStudioDocument as Oe,serveJsonHandler as Ce,isStandaloneModulePath as Le,readStandaloneAsset as Ne,assetContentType as Ie,handleSchemaEditRequest as je,handlePolicyScaffoldRequest as Me,handleSeedRequest as Fe,applyStudioAssetCache as We}from"@lunora/config/studio-host";import{c as xe}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{setTimeout as Ve}from"node:timers/promises";import{r as He,d as Be,P as Ue,a as Ge,b as _,c as Xe,s as Ye,n as U,e as Je,f as Ke,g as ze,h as Qe,i as C,w as Ze,j as qe,k as er,t as rr,l as tr,m as or,o as nr}from"../packem_shared/plan-Dl57T11P.mjs";import{provisionBindings as ar}from"./runDeployCommand.mjs";const ir=100,sr=/(?:\.[cm]?[jt]sx?|(?:^|[/\\])tsconfig(?:\..+)?\.json)$/u,cr=300,L=/[/\\]/u,dr=(e,r,t)=>{const{apiSpec:o,logger:a,projectRoot:i,target:u}=e;try{const n=M({apiSpec:o,lunoraDirectory:r,projectRoot:i,target:u,...B(i)});a.success(`codegen: wrote ${r}/_generated (${t})`),ye(n.platformDiagnostics,a)}catch(n){return a.error(Se(n,t)),!1}return!0},lr=e=>{const r=e.lunoraDirectory??"lunora",t=we(e.projectRoot,r),o=e.debounceMs??ir;let a=!1,i=!1,u=!1,n,d=0,v=Promise.resolve(),h;const y=new Promise(f=>{h=f}),w=f=>{a||(n=f,!i&&(i=!0,v=(async()=>{try{for(;n!==void 0;){const g=n;if(n=void 0,!!dr(e,r,g)){u=!0;try{(await X({cwd:e.projectRoot,logger:e.logger,spawner:e.spawner,stdoutToStderr:e.jsonLogs})).ran&&(d=Date.now())}finally{u=!1}}}}catch(g){e.logger.error(`codegen watch: ${g instanceof Error?g.message:String(g)}`)}finally{i=!1,h()}})()))};w("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,n=void 0,v),ready:y,watchAvailable:!1});let s,l;if(!me(t))return c(`no such directory: ${t}`);const m=f=>{if(u||Date.now()-d<cr){s&&(clearTimeout(s),s=void 0);return}s&&clearTimeout(s),s=setTimeout(()=>{w(`change: ${f??"?"}`)},o)},S=[];try{l=R(t,{recursive:!0},(f,g)=>{typeof g=="string"&&g.split(L).includes("_generated")||m(g)});for(const f of G(e.projectRoot).services)if(f.rpcEntrypoint!==void 0){const g=R(he(f.wranglerPath),{recursive:!0},(b,E)=>{typeof E=="string"&&sr.test(E)&&!E.split(L).includes("node_modules")&&m(E)});g.on("error",b=>{e.logger.warn(`codegen watch: stopped watching service "${f.name}" (${b.message})`),g.close()}),S.push(g)}}catch(f){l?.close();for(const g of S)g.close();return c(f instanceof Error?f.message:String(f))}const p=l;return{close:()=>{a=!0,n=void 0,s&&clearTimeout(s),p.close();for(const f of S)f.close();return v},ready:y,watchAvailable:!0}},N="/_lunora",I=3e4,j=(e,r,t)=>{const o=ke({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),_e(a,r,()=>{})});o.setTimeout(I,()=>{o.destroy(new Error(`no response within ${String(I)}ms`))}),o.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||o.destroy()}),e.pipe(o)},ur=5e3,fr=(e,r,t,o)=>{const a=De({host:o.hostname,port:Number(o.port)},()=>{a.setTimeout(0);const i=[`${e.method??"GET"} ${e.url??"/"} HTTP/1.1`];for(const[u,n]of Object.entries(e.headers))if(u!=="host")for(const d of Array.isArray(n)?n:[n])d!==void 0&&i.push(`${u}: ${d}`);i.push(`host: ${o.host}`,"",""),a.write(i.join(`\r
2
+ `)),t.length>0&&a.write(t),a.pipe(r),r.pipe(a)});a.setTimeout(ur,()=>a.destroy()),a.on("error",()=>r.destroy()),r.on("error",()=>a.destroy())},gr=async e=>{const r=e.host??"127.0.0.1",t=r==="127.0.0.1"||r==="localhost"||r==="::1",o=new URL(e.workerOrigin);let a=A(e.logger,import.meta.url),i=P(import.meta.url);const u=Te({adminToken:t?Re(e.cwd):void 0,basePath:"/",dataEditable:t,rulesInstalled:F(e.cwd).installed,runAsIdentity:t,schemaEditable:t,scriptSrc:"/studio.js",styleHref:"/styles.css"}),n=(c,s,l,m,S)=>{We(c,s,S,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)},v=(c,s,l,m)=>{if(t){Ce(c,s,l,e.cwd,{apiSpec:e.apiSpec});return}d(s,403,m)},h=(c,s,l)=>{const m=c==="/styles.css",S=Le(c);if(!m&&!S)return!1;const p=P(import.meta.url);if(p!==i&&(a=A(e.logger,import.meta.url),i=p),a===void 0)return d(l,501,"Lunora studio assets not found — install and build @lunora/studio."),!0;if(m)return n(s,l,a.styles,"text/css; charset=utf-8","styles.css"),!0;const f=c.slice(c.lastIndexOf("/")+1),g=Ne(f,import.meta.url);return g===void 0?(d(l,404,"Not found"),!0):(n(s,l,g,Ie(f),f),!0)},y=Buffer.from(u),w=be((c,s)=>{const l=$(c.url??"/");if(t){const S=O(c,e.logger);if(S!==void 0){d(s,403,S);return}}if(l.startsWith(N)){if(!t){d(s,403,"Lunora studio: the worker admin proxy is only available on a loopback bind.");return}j(c,s,o);return}if(l===Ae){v(c,s,je,"Lunora schema editing is only available on loopback hosts in dev.");return}if(l===Pe){v(c,s,Me,"Lunora policy scaffolding is only available on loopback hosts in dev.");return}if(l===$e){v(c,s,Fe,"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"){j(c,s,o);return}Oe(s,y)});return w.on("upgrade",(c,s,l)=>{if(!t||O(c,e.logger)!==void 0){s.destroy();return}$(c.url??"").startsWith(N)?fr(c,s,l,o):s.destroy()}),await new Promise((c,s)=>{w.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)}),w.listen(e.port,r,()=>{c()})}),{close:()=>new Promise(c=>{w.close(()=>{c()})}),url:`http://${r==="0.0.0.0"||r==="::"?"localhost":r}:${String(e.port)}`}},vr=async e=>{const{cwd:r,logger:t,origin:o,signal:a}=e,i=e.probe??Be,u=He(e.readyTimeoutMs),n=Date.now()+u,d=()=>a.aborted;for(;!d()&&Date.now()<n;){let v=!1;try{v=await i(o,a)}catch{}if(d()||Date.now()>=n)break;if(v)return W(r,{readyAt:new Date().toISOString()},{expectedPid:process.pid}),Y(r)?.readyAt!==void 0;try{await Ve(Ue,void 0,{signal:a})}catch{}}return d()||t.warn(`server did not answer on ${o} 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},mr=(e,r,t,o)=>{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/"),o!==void 0&&e.info(` ➜ Supervisor: ${o} (needs) · ${H} (status)`),r.remote.enabled&&(r.remote.bindings.length>0?e.info(` ➜ Remote: ${r.remote.bindings.join(", ")} → deployed worker`):r.remote.reason!==void 0&&e.warn(` ➜ Remote: requested but inactive (${r.remote.reason}) — running fully local`));for(const a of r.remote.withheld)e.warn(` ➜ ${ue(a)}`);e.info("")},wr=(e,r)=>{F(r).installed||!ce()||(e.info(` ⓘ ${de}`),e.info(""))},hr=async(e,r)=>{try{const o=await q({projectRoot:r,schemaDir:ee(r)}),a=re(o),i=(e.ensureExample??te)(r,a);if(i.length>0){const u=a.map(n=>n.includes(":")?`${n.replace(":"," (")})`:n);e.logger.info(`Updated .dev.vars.example with secrets for: ${u.join(", ")} (${i.join(", ")})`)}}catch{}(await(e.ensureEnv??oe)({confirm:xe(),cwd:r,info:o=>{e.logger.info(o)}})).status==="declined"&&!ne()&&e.logger.info(`hint: ${T} was not scaffolded (non-interactive run). Copy ${ae} → ${T} and fill in secrets, or run \`lunora dev\` in an interactive terminal to scaffold automatically.`);try{(e.fillSecrets??ie)({cwd:r,info:o=>{e.logger.info(o)}})}catch{}},Sr=(e,r,t,o,a)=>{if(e.flavor!=="wrangler")return;o!==void 0&&W(r,{studioUrl:o});let i;try{i=nr(r,t)}catch{}return mr(t,e,o,a),i},yr=(e,r)=>{const t=Number(process.env[k]),o=Q(r,{background:process.env[x]==="1",logFile:process.env[Z],mode:"cli",pid:process.pid,startedAt:new Date().toISOString(),url:e.workerOrigin},Number.isInteger(t)&&t>0?{supersedePid:t}:void 0);return o.ok?void 0:o.existing},pr=e=>{const{cwd:r,destination:t,logger:o,plan:a}=e,i=t!==void 0,u=t??se,n=ve({destination:u,dev:{...a.flavor==="wrangler"?{origin:a.workerOrigin}:{},statusFile:H},logger:i?o:{...o,success:()=>{},warn:()=>{}},projectRoot:r});return n.error!==void 0?i?n:(o.debug?.(`skipped the default binding manifest: ${n.error}`),{}):{written:u}},Er=async(e,r,t,o)=>{if(r.studioEnabled)try{return await(e.startStudio??gr)({apiSpec:e.apiSpec,cwd:t,logger:{warnOnce:a=>{o.warn(a)}},port:r.studioPort,workerOrigin:r.workerOrigin})}catch(a){o.warn(`studio server failed to start (${a instanceof Error?a.message:String(a)}) — continuing without it`);return}},br=e=>{const r=[e.runsCodegenWatch?"codegen watch":void 0,e.studioEnabled?"studio":void 0].filter(o=>o!==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}.`},kr=(e,r,t,o,a)=>{if(!(e.sidecar===void 0||!tr(r)))try{M({apiSpec:r.apiSpec,lunoraDirectory:"lunora",projectRoot:t,target:a,...B(t)})}catch(i){o.warn(`codegen (pre-sidecar) failed: ${i instanceof Error?i.message:String(i)} — the framework dev server will retry`)}},Dr=e=>e.flavor==="vite"?"starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)":`starting ${e.wrangler.tag} dev + studio`,_r=async(e,r,t,o,a)=>{try{return e.celldSession===!0?await or({logger:a,port:e.workerPort,projectRoot:o,start:r.startCelldSession}):t(e.wrangler,a)}catch(i){a.error(`could not start the worker: ${i instanceof Error?i.message:String(i)}`);return}},Tr=async e=>{const{logger:r}=e,t=e.cwd??process.cwd(),o=e.flavor??Je(t),a=ge(t,e.target);if(a.target===void 0)throw new Error(a.error??"unknown deploy target");const{target:i}=a,u=Ke(i,o,r);u==="wrangler"&&await ar(t,r,void 0,i,void 0);const n=await ze({...e,flavor:u,target:i}),d={remoteCleanup:n.remote.cleanup,serviceConfigCleanup:n.serviceConfigCleanup};try{const v=Number(process.env[k]),h=V(t);if(h!==void 0&&h.pid!==process.pid&&h.pid!==v)return _(r,h),{code:0,plan:n};const y=yr(n,t);if(y!==void 0)return _(r,y),{code:0,plan:n};(n.flavor==="vite"||n.flavor==="framework-worker")&&(n.wrangler.env={...n.wrangler.env,[k]:String(process.pid)}),await hr(e,t),r.info(Dr(n)),Qe(r,t),n.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."),n.runsCodegenWatch&&(d.codegen=(e.startCodegen??lr)({apiSpec:e.apiSpec,jsonLogs:e.jsonLogs,logger:r,projectRoot:t,target:i})),d.studio=await Er(e,n,t,r);const w=pr({cwd:t,destination:e.emitBindings,logger:r,plan:n});if(w.error!==void 0)return r.error(w.error),{code:D.USAGE,plan:n};await d.codegen?.ready;const c=d.studio?.url;n.frameworkHint!==void 0&&r.warn(n.frameworkHint);const s=()=>{n.flavor==="wrangler"&&(d.readyProbe=new AbortController,vr({cwd:t,logger:r,origin:n.workerOrigin,probe:e.probeReady,signal:d.readyProbe.signal}))};if(!n.workerEnabled)return s(),r.info(br(n)),d.tunnel=C({cwd:t,logger:r,plan:n,tunnel:e.tunnel}),{code:await(e.waitForInterrupt??Ze)(r),plan:n};kr(n,e,t,r,i);const l=e.startWorker??qe,m=await _r(n,e,l,t,r);if(m===void 0)return{code:D.FAILURE,plan:n};const S=n.sidecar===void 0?void 0:l(n.sidecar,r);return s(),d.containerLogs=Sr(n,t,r,c,w.written),d.tunnel=C({cwd:t,logger:r,plan:n,tunnel:e.tunnel}),wr(r,t),{code:await er(m,S,r),plan:n}}finally{d.readyProbe?.abort(),z(t,process.pid),await rr(d)}},Rr=e=>({codegen:e.codegen===!1?!1:void 0,studio:e.studio===!1?!1:void 0,worker:e.worker===!1?!1:void 0}),Ar=e=>{if(e.local===!0&&e.remote===!0)return"`--local` and `--remote` are mutually exclusive — pass at most one.";const{entries:r,invalid:t}=U(e.allowMail);if(t.length>0)return`\`--allow-mail\` takes email addresses or '*@domain' — not ${t.map(o=>`"${o}"`).join(", ")}.`;if(r.length>0&&e.tunnel!==!0)return"`--allow-mail` only applies to a tunnel — add `--tunnel`."},Pr=e=>e.tunnel===!0?{allowMail:U(e.allowMail).entries}:void 0,zr=pe(async({argument:e,cwd:r,logger:t,options:o})=>{const a=o.json===!0,i=Ge({cwd:r,json:a,lines:o.lines,logger:t,subcommand:e[0]});if(i!==void 0)return i;const u=process.env[x]==="1",n=u?void 0:J(),d=a||n!==void 0;d&&Ee(),n!==void 0&&o.background!==!0&&t.info(`AI agent detected (${n.name} via ${n.variable}) — starting the dev server in background mode with JSON logs. Set LUNORA_AGENT_MODE=0 to opt out.`);const v=Ar(o);if(v!==void 0)return t.error(`dev: ${v}`),{code:D.USAGE};const h=Pr(o),y=o.local===!0?!1:le({configPreference:K(r),envValue:process.env.LUNORA_REMOTE,flag:o.remote});if(!u&&(o.background===!0||n!==void 0)){const w=V(r);return w!==void 0?(_(t,w),{code:0}):(h?.allowMail.length===0&&Xe(t),Ye({cwd:r,jsonLogs:d,logger:t,options:o,remote:y}))}return Tr({apiSpec:fe(o.apiSpec),cwd:r,emitBindings:o.emitBindings,inspectorPort:o.inspectorPort,jsonLogs:d,local:o.local,logger:t,port:o.port,remote:y,target:o.target,tunnel:h,workerPort:o.workerPort,...Rr(o)})});export{Ar as devFlagConflict,zr as execute,Rr as negatableDevFlags,Tr as runDevCommand};
@@ -0,0 +1,5 @@
1
+ import{existsSync as q,mkdirSync as Te,openSync as J,closeSync as X,statSync as Pe,readFileSync as Q,readSync as be}from"node:fs";import{join as C,dirname as _e}from"node:path";import{readServiceBindings as $e}from"@lunora/codegen";import{readLiveDevServerState as N,discoverContainerInfo as Re,streamContainerLogs as Ce,formatLunoraEvent as Ne,updateDevServerState as b,targetRunsOwnDevServer as O,resolveProjectTarget as Z,claimDevServerState as Oe,DEV_HANDOFF_ENV as De,clearDevServerState as y,readDevServerState as D,isDevServerReady as j,detectFramework as ee,readProjectDependencyNames as Ae,DEV_LOG_FILE_ENV as xe,DEV_DAEMON_ENV as Me,detectPackageManager as A,readProjectManifest as Ue,execArgsFor as re,isRecordedProcessCurrent as Ge,DEV_LOG_FILE as Fe,CODEGEN_ENV as te,isCodegenDisabled as je,planToolchainInvocation as Ve,toolchainExecArgs as ne,startCelldDevSession as We,resolveDeployDriver as oe,DEV_VARS_FILE as ie,runScriptCommand as Be,parseDevVariableEntries as Ke}from"@lunora/config";import{warnCloudflareCliConfigOnce as He,findWranglerFile as L,readWranglerJsonc as x,materializeDevWranglerConfig as ze,materializeServiceDevConfigs as Ye}from"@lunora/config/cloudflare";import{createServer as qe}from"node:net";import{networkInterfaces as Je}from"node:os";import{spawn as se,spawnSync as T}from"node:child_process";import{setTimeout as w}from"node:timers/promises";import{EXIT_CODE as ae}from"./EXIT_CODE-D_XCixv8.mjs";import{a as h}from"./output-format-B4eQHi5y.mjs";import{spawnShellCompat as ce,defaultSpawner as Xe}from"./createRecordingSpawner-Ch7zuQQE.mjs";import{isLunoraError as Qe}from"@lunora/errors";import{s as V}from"./index-oHZBGBwB.mjs";import{createInterface as Ze}from"node:readline";const er=12e4,_=250,rr=1e3,tr="LUNORA_DEV_READY_TIMEOUT_MS",nr=e=>{if(e!==void 0)return e;const r=Number(process.env[tr]??"");return Number.isFinite(r)&&r>0?r:er},or=async(e,r)=>{try{const n=AbortSignal.timeout(rr);return await(await fetch(new URL("/_lunora/status",e),{redirect:"manual",signal:r===void 0?n:AbortSignal.any([r,n])})).body?.cancel(),!0}catch{return!1}},le=async(e,r,n)=>{const t=Date.now()+n.timeoutMs;for(;n.signal?.aborted!==!0;){const o=r(N(e));if(o!==void 0)return o;if(Date.now()>=t)return;try{await w(n.intervalMs??500,void 0,{signal:n.signal})}catch{return}}},W=(e,r)=>{Ze({crlfDelay:Number.POSITIVE_INFINITY,input:e}).on("line",r)},ir=5e3,de=(e,r,n,t)=>{if(e.length===0)return;const o=Ne(e);if(o){t[o.level](`[lunora] ${o.text}`);return}const i=`[${r}] ${e}`;n==="stderr"?t.warn(i):t.info(i)},sr=(e,r,n=process.platform,t=T)=>{try{if(r==="SIGKILL"&&n==="win32"&&e.pid!==void 0){t("taskkill",["/pid",String(e.pid),"/T","/F"],{stdio:"ignore"});return}e.kill(r)}catch{}},ue=(e,r,n)=>{const t=e.direct===!0?{args:[...e.args],command:e.command,shell:!1}:ce(e.command,e.args),o=se(t.command,t.args,{cwd:e.cwd??process.cwd(),env:e.env?{...process.env,...e.env}:process.env,shell:t.shell,stdio:[e.direct===!0?"ignore":"inherit","pipe","pipe"]});return W(o.stdout,i=>{r(i,"stdout")}),W(o.stderr,i=>{r(i,"stderr")}),{exited:new Promise(i=>{o.on("error",s=>{n?.(s),i(1)}),o.on("exit",(s,a)=>{i(s??(a?1:0))})}),kill:i=>{sr(o,i)}}},pt=(e,r)=>ue(e,(n,t)=>{de(n.trimEnd(),e.tag,t,r)},n=>{r.error(`[${e.tag}] failed to start: ${n.message}`)}),mt=(e,r)=>{if(process.env.LUNORA_CONTAINER_LOGS==="0")return;const t=Re(e,"lunora").containers.map(o=>({className:o.className,exportName:o.exportName}));if(t.length!==0)return Ce({containers:t,onLine:o=>{const i=`[container:${o.name}] ${o.text}`;o.level==="error"?r.warn(i):r.info(i)},onUnavailable:o=>{r.warn(`[container] Docker engine unreachable — container logs unavailable (${o})`)}})},wt=async e=>{e.readyProbe?.abort(),await e.tunnel?.close(),await e.codegen?.close().catch(()=>{}),e.containerLogs?.close(),await e.studio?.close().catch(()=>{});try{e.remoteCleanup?.(),e.serviceConfigCleanup?.()}catch{}},ht=async e=>await new Promise(r=>{const n={},t=s=>{e.info(`received ${s} — shutting down`),n.sigint&&process.off("SIGINT",n.sigint),n.sigterm&&process.off("SIGTERM",n.sigterm),r(0)},o=()=>{t("SIGINT")},i=()=>{t("SIGTERM")};n.sigint=o,n.sigterm=i,process.on("SIGINT",o),process.on("SIGTERM",i)}),St=async(e,r,n)=>{let t=0,o;const i=l=>{e.kill(l),r?.kill(l)},s=()=>{t+=1,t===1?(n.info("received SIGINT — shutting down (press Ctrl-C again to force-kill)"),i("SIGTERM"),o=setTimeout(()=>{i("SIGKILL")},ir),o.unref()):i("SIGKILL")},a=()=>{i("SIGTERM")};process.on("SIGINT",s),process.on("SIGTERM",a);const c=await Promise.race([e.exited.then(l=>({code:l,who:"worker"})),...r===void 0?[]:[r.exited.then(l=>({code:l,who:"sidecar"}))]]);return r!==void 0&&(c.who==="sidecar"?(n.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])),o&&clearTimeout(o),process.off("SIGINT",s),process.off("SIGTERM",a),c.code},E="2026.9.3",ar="https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/",cr=/version (\S+)/u,B=/https:\/\/[\da-z-]+\.trycloudflare\.com/u,lr="1s",dr=5e3,K=async(e,r)=>await Promise.race([e.then(()=>!0),w(r,!1,{ref:!1})]),ur=6e4,fr='Vite answers 403 for hosts it does not know — add `server: { allowedHosts: [".trycloudflare.com"] }` to vite.config if the URL is blocked.',vr=async e=>{const r=`Install or update it: ${ar} (macOS: \`brew install cloudflared\`).`;let n;try{const o=await e({args:["--version"],captureStdoutSilently:!0,command:"cloudflared"});if(o.code!==0)return{message:`\`cloudflared --version\` exited ${String(o.code)}. ${r}`,ok:!1};n=o.stdout??""}catch(o){return Qe(o)&&o.code==="LOCAL_DEPENDENCY_MISSING"?{message:`--tunnel needs \`cloudflared\` ${E} or newer, and it is not on your PATH. ${r}`,ok:!1}:{message:`\`cloudflared --version\` could not run: ${o instanceof Error?o.message:String(o)}. ${r}`,ok:!1}}const t=V.coerce(cr.exec(n)?.[1])?.version;return t===void 0?{ok:!0,version:void 0}:V.gte(t,E)?{ok:!0,version:t}:{message:`--tunnel needs \`cloudflared\` ${E} or newer (protected quick tunnels); found ${t}. ${r}`,ok:!1}},gr=e=>{const r=[],n=[];for(const t of e??[]){const o=t.split(",").map(i=>i.trim()).filter(i=>i.length>0);o.length>0&&(n.push(...o.filter(i=>!i.includes("@"))),r.push(o.join(",")))}return{entries:r,invalid:n}},pr=(e,r)=>["tunnel","--url",e,"--output","json","--grace-period",lr,...r.flatMap(n=>["--allowed-mail",n])],mr=e=>{let r;try{r=JSON.parse(e)}catch{r=void 0}if(typeof r=="object"&&r!==null){const n=r,t=typeof n.message=="string"?n.message:"";return{level:typeof n.level=="string"?n.level:void 0,text:t,url:B.exec(t)?.[0]}}return{level:void 0,text:e,url:B.exec(e)?.[0]}},wr=e=>{e.warn(" ⚠ PUBLIC TUNNEL: anyone with the tunnel URL can reach this dev server — there is no sign-in in front of it."),e.warn(" Protect it with a one-time-PIN email allow-list: lunora dev --tunnel --allow-mail you@example.com"),e.warn(" (repeat --allow-mail, pass a comma-separated list, or allow a whole domain with '*@example.com')")},hr=async(e,r,n,{intervalMs:t,timeoutMs:o=ur}={})=>{const i=await le(e,s=>s?.mode==="vite"?{origin:s.url,ownerPid:s.pid}:void 0,{intervalMs:t,signal:n,timeoutMs:o});return i===void 0&&!n.aborted&&r.warn(`--tunnel: the Vite dev server did not report its URL within ${String(o/1e3)}s — continuing without a tunnel.`),i},Sr=e=>{const{allowMail:r,cwd:n,logger:t}=e,o=r.length===0,i=new AbortController,{signal:s}=i,a=()=>s.aborted,c=e.closeTimeoutMs??dr;let l;const d=()=>{i.abort()};process.once("SIGINT",d),process.once("SIGTERM",d);const v=(async()=>{const u=await vr(e.spawner??Xe);if(!u.ok){t.error(u.message),t.warn("continuing without a tunnel.");return}u.version===void 0&&t.warn(`could not read the cloudflared version — --tunnel needs ${E} or newer; trying anyway.`);const g=e.origin.kind==="fixed"?{origin:e.origin.origin,ownerPid:process.pid}:await hr(n,t,s);if(g===void 0||s.aborted)return;o&&wr(t),t.info(`starting a Cloudflare quick tunnel to ${g.origin}${o?"":" (email one-time-PIN protected)"}…`);let S;const U={failedToStart:!1},Le=k=>{if(k.length===0)return;const p=mr(k);if(p.url!==void 0&&S===void 0){S=p.url,t.success(` ➜ Tunnel: ${p.url}${o?" (PUBLIC)":""}`),o||t.info(` Allowed: ${r.join(", ")} — visitors confirm with a one-time PIN; a session lasts up to 4 hours`),e.origin.kind==="vite"&&t.info(` ${fr}`),b(n,{tunnelUrl:p.url},{expectedPid:g.ownerPid});return}p.level==="error"||p.level==="fatal"?t.warn(`[cloudflared] ${p.text}`):t.debug?.(`[cloudflared] ${p.text}`)},P=(e.startChild??ue)({args:pr(g.origin,r),command:"cloudflared",direct:!0},Le,k=>{U.failedToStart=!0,t.error(`could not start cloudflared (${k.message}) — continuing without a tunnel.`)});l=P;const G=()=>{P.kill("SIGTERM")};s.addEventListener("abort",G,{once:!0});const F=await P.exited;s.removeEventListener("abort",G),l=void 0,S!==void 0&&b(n,{tunnelUrl:void 0},{expectedPid:g.ownerPid}),!(a()||U.failedToStart)&&t.warn(S===void 0?`cloudflared exited (${String(F)}) before a tunnel URL was assigned — continuing without a tunnel.`:`cloudflared exited (${String(F)}) — the tunnel is closed; the dev server keeps running.`)})().catch(u=>{t.error(`--tunnel failed (${u instanceof Error?u.message:String(u)}) — continuing without a tunnel.`)});return{close:async()=>{if(process.off("SIGINT",d),process.off("SIGTERM",d),i.abort(),await K(v,c))return;const u=l;u!==void 0&&(t.warn(`cloudflared did not exit within ${String(c/1e3)}s of SIGTERM — force-killing it.`),u.kill("SIGKILL"),!await K(u.exited,c)&&t.error(`could not confirm that cloudflared stopped — ${o?"the PUBLIC tunnel":"the tunnel"} may still be reachable. Stop the \`cloudflared\` process manually.`))}}},kt=e=>{const{cwd:r,logger:n,plan:t,tunnel:o}=e;if(o!==void 0)return Sr({allowMail:o.allowMail,cwd:r,logger:n,origin:t.flavor==="wrangler"?{kind:"fixed",origin:t.workerOrigin}:{kind:"vite"},spawner:o.spawner,startChild:o.startChild})},kr=(e,r)=>{const n=["dev"];if(e.apiSpec!==void 0&&n.push("--api-spec",e.apiSpec),e.port!==void 0&&n.push("--port",String(e.port)),e.workerPort!==void 0&&n.push("--worker-port",String(e.workerPort)),e.inspectorPort!==void 0&&n.push("--inspector-port",String(e.inspectorPort)),e.emitBindings!==void 0&&n.push("--emit-bindings",e.emitBindings),e.codegen===!1&&n.push("--no-codegen"),e.studio===!1&&n.push("--no-studio"),e.worker===!1&&n.push("--no-worker"),e.target!==void 0&&n.push("--target",e.target),e.tunnel===!0){n.push("--tunnel");for(const t of gr(e.allowMail).entries)n.push("--allow-mail",t)}return r&&n.push("--remote"),e.local===!0&&n.push("--local"),n},Ir=1e4,Er=100,yr=40,I=256*1024,Lr=new Set(["nuxt","sveltekit"]),Tr=["@lunora/rspack","@lunora/vite"],M=e=>{if(Lr.has(ee(e).framework))return"framework-worker";const r=Ae(e);return Tr.some(n=>r.has(n))?"vite":"wrangler"},fe=e=>{const r=A(e),n=Ue(e)?.scripts,t=typeof n=="object"&&n!==null?n.dev:void 0;if(typeof t!="string"||t.trim()===""||t.includes("lunora")){const o=re(r,"vite",["dev"]);return{args:o.args,command:o.command}}return{args:["run","dev"],command:r}},Pr=e=>{Te(_e(e.logPath),{recursive:!0});const r=J(e.logPath,"w",384);let n;const t=ce(e.command,e.args);try{n=se(t.command,t.args,{cwd:e.cwd,detached:!0,env:e.env,shell:t.shell,stdio:["ignore",r,r],windowsHide:!0})}finally{X(r)}return n.unref(),{exited:new Promise(o=>{n.on("error",()=>{o(1)}),n.on("exit",(i,s)=>{o(i??(s?1:0))})}),pid:n.pid}},br=e=>{try{const{size:r}=Pe(e);if(r<=I)return Q(e,"utf8");const n=J(e,"r");try{const t=Buffer.alloc(I),o=be(n,t,0,I,r-I),i=t.toString("utf8",0,o);return i.slice(i.indexOf(`
2
+ `)+1)}finally{X(n)}}catch{return}},ve=(e,r)=>{const n=br(e);if(n===void 0)return[];const t=n.split(`
3
+ `);return t.at(-1)===""&&t.pop(),r>0?t.slice(-r):t},ge=(e,r)=>r?.logFile??C(e,Fe),pe=e=>{e.info(" Stop: lunora dev stop"),e.info(" Status: lunora dev status"),e.info(" Logs: lunora dev logs")},_r=(e,r)=>{He(r,n=>{e.warn(n)})},$r=(e,r)=>{e.warn(`Dev server already running at ${r.url} (pid ${String(r.pid)})`),pe(e)},Rr=(e,r)=>{e.success(`Dev server running at ${r.url} (pid ${String(r.pid)})`),r.studioUrl!==void 0&&e.info(` Studio: ${r.studioUrl}`),r.tunnelUrl!==void 0&&e.info(` Tunnel: ${r.tunnelUrl}`),pe(e)},Cr=3e4,Nr=async(e,r,n,t)=>{const o=await le(e,i=>i?.tunnelUrl,{intervalMs:n,timeoutMs:t});o===void 0?r.warn(" Tunnel: no URL yet — `lunora dev status` shows it once cloudflared reports one; `lunora dev logs` says why if it does not."):r.success(` Tunnel: ${o}`)},Or=async e=>{const{child:r,cwd:n,deadline:t,pollIntervalMs:o,probe:i}=e,s=r.exited.then(a=>({exitCode:a}));for(;Date.now()<t;){const a=N(n);if(a!==void 0&&a.pid!==process.pid&&await i(a.url))return{state:a,status:"ready"};const c=await Promise.race([s,w(o)]);if(c!==void 0)return{...c,status:"exited"}}return{status:"timeout"}},Dr=async e=>{const{cwd:r,logger:n}=e,t=ge(r,void 0),o=nr(e.readyTimeoutMs),i=(e.spawnDetached??Pr)({args:e.command.args,command:e.command.command,cwd:r,env:{...process.env,...e.env,[Me]:"1",[xe]:t,...process.env.FORCE_COLOR===void 0?{NO_COLOR:"1"}:{},...e.json?{LUNORA_LOG_JSON:"1"}:{}},logPath:t}),s=await Or({child:i,cwd:r,deadline:Date.now()+o,pollIntervalMs:e.pollIntervalMs??_,probe:e.probe??or});if(s.status==="ready")return Rr(n,s.state),e.tunnel===!0&&s.state.tunnelUrl===void 0&&await Nr(r,n,e.pollIntervalMs??_,e.tunnelUrlTimeoutMs??Cr),{code:0};if(s.status==="exited"){n.error(`dev server exited before becoming ready (exit code ${String(s.exitCode)}). Last output:`);for(const c of ve(t,yr))n.error(` ${c}`);return y(r,i.pid??process.pid),{code:s.exitCode===0?1:s.exitCode}}i.pid!==void 0&&D(r)?.pid===process.pid&&b(r,{logFile:t,pid:i.pid});const a=i.pid===void 0?"":` (pid ${String(i.pid)})`;return n.warn(`dev server did not confirm ready within ${String(Math.round(o/1e3))}s — it may still be compiling${a}. Check \`lunora dev status\` and \`lunora dev logs\`; \`lunora dev stop\` shuts it down.`),{code:ae.UNAVAILABLE}},me=e=>e.codegen!==!1&&!je(process.env[te]),we=e=>{const r={...e.remote===!0?{LUNORA_REMOTE:"1"}:{},...e.codegen===!1?{[te]:"0"}:{}};return Object.keys(r).length>0?{env:r}:{}},It=async e=>{const{cwd:r,jsonLogs:n,logger:t,options:o,remote:i}=e,s=e.run??Dr,a=M(r),c=a==="vite"&&O(o.target??Z(r))?"wrangler":a,l=c==="wrangler"?`http://localhost:${String(o.workerPort??8787)}`:"http://localhost:5173",d=Oe(r,{background:!0,mode:"cli",pid:process.pid,startedAt:new Date().toISOString(),url:l});if(!d.ok)return d.existing!==void 0&&$r(t,d.existing),{code:0};const f={[De]:String(process.pid)};_r(t,r);try{return c==="vite"&&o.tunnel!==!0?await s({command:fe(r),cwd:r,env:{...we({...o,remote:i}).env,...f},json:n,logger:t}):await s({command:{args:[process.argv[1]??"lunora",...kr(o,i)],command:process.execPath},cwd:r,env:f,json:n,logger:t,tunnel:o.tunnel===!0})}finally{y(r,process.pid)}},Ar=e=>{try{const r=T("ps",["-o","pgid=","-p",String(e)],{encoding:"utf8"}),n=Number.parseInt(r.stdout.trim(),10);return Number.isInteger(n)&&n>0?n:void 0}catch{return}},xr=(e,r,n=process.platform,t=T)=>{if(n==="win32"){try{t("taskkill",["/pid",String(e.pid),"/T","/F"],{stdio:"ignore"})}catch{}return}if(e.background===!0)try{r(-(Ar(e.pid)??e.pid),"SIGKILL");return}catch{}try{r(e.pid,"SIGKILL")}catch{}},Mr=(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}),Ur=async e=>{const{cwd:r,logger:n}=e,t=D(r),o=e.alive??(f=>f===t?.pid&&Ge(t)),i=e.signal??((f,v)=>{process.kill(f,v)}),s=e.pollIntervalMs??_,a=e.stopGraceMs??Ir,c=e.platform??process.platform,l=e.spawnSyncImpl??T;if(t===void 0||!o(t.pid))return t!==void 0&&y(r,t.pid),e.json?h({running:!1,stopped:!1}):n.info("No dev server running."),{code:0};try{i(t.pid,"SIGTERM")}catch{}const d=Date.now()+a;for(;o(t.pid)&&Date.now()<d;)await w(s);return o(t.pid)&&(xr(t,i,c,l),await w(s),o(t.pid))?Mr(t,e):(y(r,t.pid),e.json?h({pid:t.pid,stopped:!0}):n.success(`Stopped dev server (pid ${String(t.pid)}).`),{code:0})},Gr=e=>{const{cwd:r,logger:n}=e,t=N(r);if(t===void 0)return e.json?h({running:!1}):n.info("No dev server running."),{code:0};const o=e.now??Date.now,i=t.startedAt===void 0?Number.NaN:Date.parse(t.startedAt),s=Number.isFinite(i)?Math.max(0,Math.round((o()-i)/1e3)):void 0;if(e.json)return h({background:t.background===!0,logFile:t.logFile,mode:t.mode,pid:t.pid,ready:j(t),readyAt:t.readyAt,running:!0,startedAt:t.startedAt,studioUrl:t.studioUrl,tunnelUrl:t.tunnelUrl,uptimeSeconds:s,url:t.url}),{code:0};const a=[`pid ${String(t.pid)}`,...s===void 0?[]:[`uptime ${String(s)}s`],t.background===!0?"background":"foreground",j(t)?"ready":"starting"];return n.success(`Dev server running at ${t.url} (${a.join(", ")})`),t.studioUrl!==void 0&&n.info(` Studio: ${t.studioUrl}`),t.tunnelUrl!==void 0&&n.info(` Tunnel: ${t.tunnelUrl}`),t.logFile!==void 0&&n.info(` Logs: lunora dev logs (${t.logFile})`),{code:0}},Fr=e=>{const{cwd:r,logger:n}=e,t=D(r),o=ge(r,t);if(!q(o))return n.info("No dev server logs found — output is captured when the server runs in background mode (`lunora dev --background`)."),{code:0};const i=ve(o,e.lines??Er);return i.length>0&&process.stdout.write(`${i.join(`
4
+ `)}
5
+ `),{code:0}},Et=e=>{const{cwd:r,json:n,lines:t,logger:o,subcommand:i}=e;if(i==="stop")return Ur({cwd:r,json:n,logger:o});if(i==="status")return Gr({cwd:r,json:n,logger:o});if(i==="logs")return Fr({cwd:r,lines:t,logger:o});if(i!==void 0)return o.error(`dev: unknown subcommand "${i}" — expected stop | status | logs (or no subcommand to start the dev server)`),{code:ae.USAGE}},yt=(e,r,n)=>O(e)?(r!=="wrangler"&&n.info(`target ${e} runs its own dev server, so lunora dev serves the worker on it — start the frontend's dev server separately`),"wrangler"):r,Lt=async e=>{const{logger:r}=e,n=await(e.start??We)({log:(i,{stream:s,tag:a})=>{de(i,a,s,r)},port:e.port,projectRoot:e.projectRoot});let t=()=>{};const o=new Promise(i=>{t=i});return{exited:Promise.race([o,n.exited]),kill:()=>{n.stop().then(()=>0,()=>1).then(t).catch(()=>{})}}},jr=e=>{const{cwd:r,driver:n,options:t,studioPort:o,workerPort:i}=e,{projectConfig:s,toolchain:a}=n;if(s===void 0||a===void 0)throw new Error(`deploy target "${n.id}" runs its own dev server but ships no config projection or toolchain for it`);if(t.remote===!0)throw new Error(`--remote proxies bindings to Cloudflare; ${n.name} has no remote bindings to proxy to`);t.inspectorPort!==void 0&&t.logger.warn(`--inspector-port is a wrangler dev flag; ${n.name} dev has no inspector to pin`);const{command:c,commit:l,projection:d}=Ve(n,r,"dev",v=>a.dev({configPath:v,extraArgs:["--port",String(i)]}));l(),d!==void 0&&d.dropped.length>0&&t.logger.info(`${n.name} ignores these wrangler keys, so its dev server runs without them: ${d.dropped.join(", ")}`);const f=ne(A(r),c);return{flavor:"wrangler",ipv4LoopbackForced:!1,remote:{bindings:[],cleanup:()=>{},enabled:!1,withheld:[]},runsCodegenWatch:me(t),celldSession:!0,studioEnabled:t.studio!==!1,studioPort:o,workerEnabled:t.worker!==!1,workerOrigin:`http://localhost:${String(i)}`,workerPort:i,wrangler:{args:f.args,command:f.command,cwd:r,tag:c.tool}}},Vr=64,he="127.0.0.1",Wr=65535,Br=(e,r=he)=>new Promise(n=>{const t=qe();t.once("error",()=>{n(!1)}),t.once("listening",()=>{t.close(()=>{n(!0)})}),t.listen(e,r)}),Kr=async(e,r=he,n=Vr)=>{for(let t=e;t<e+n&&t<=Wr;t+=1)if(await Br(t,r))return t;return e},Se=(e=Je)=>Object.values(e()).some(r=>r?.some(n=>n.internal&&n.address==="::1")),$="wrangler.dev.jsonc",R=6173,m=8787,H=5173,ke=()=>{},Hr=(e,r)=>{if(!e.remote&&e.local===!0)return{args:[],plan:{bindings:[],cleanup:ke,enabled:!1,withheld:[]}};const t=(e.materializeDev??ze)({projectRoot:r,remote:e.remote===!0}),i={bindings:t.remoteBindings.map(s=>`${s.binding} (${s.kind})`),cleanup:t.cleanup,enabled:e.remote===!0,reason:t.reason,withheld:t.withheld};return{args:t.configPath===void 0?[]:["--config",t.configPath],plan:i}},Ie=(e,r)=>{const n=[...new Set($e(r).services.map(s=>s.wranglerPath))],{cleanup:t,configPaths:o,withheld:i}=(e.materializeServiceConfigs??Ye)(n,{projectRoot:r});return{args:o.flatMap(s=>["--config",s]),cleanup:t,withheld:i}},zr=(e,r,n)=>{const t=L(e);return r.length===0||t===void 0?[]:[...n.length>0?[]:["--config",t],...r]},Ee=e=>e.local===!0?["--local"]:[],z=e=>x(e).parsed?.dev?.ip,ye=(e,r,n)=>{if(n!==void 0){const o=C(e,n);if(q(o)&&z(o)!==void 0)return[]}const t=L(e);return t!==void 0&&z(t)!==void 0?[]:r()?[]:["--ip","127.0.0.1"]},Yr=new Set(["127.0.0.1","::1","[::1]","localhost"]),qr=(e,r)=>{let n;try{n=Q(C(e,ie),"utf8")}catch{return[]}const t=[];for(const o of Ke(n)){let i;try{i=new URL(o.value)}catch{continue}Yr.has(i.hostname)&&i.port===String(r)&&t.push(o.key)}return t},Y=async(e,r)=>{if(e.workerPort!==void 0)return e.workerPort;const n=L(r);if(n!==void 0){const{parsed:i}=x(n);if(typeof i?.dev?.port=="number")return i.dev.port}const t=await(e.findFreePort??Kr)(m);if(t===m)return t;const o=qr(r,m);if(o.length>0)throw new Error(`port ${String(m)} is in use, but ${ie} pins the worker origin to it (${o.join(", ")}). Serving on ${String(t)} would leave those URLs pointing at nothing. Stop whatever holds ${String(m)}, or run \`lunora dev --worker-port ${String(t)}\` and update those values to match.`);return t},Jr=(e,r)=>{if(e.inspectorPort!==void 0)return e.inspectorPort;const n=L(r);if(n===void 0)return;const{parsed:t}=x(n),o=t?.dev?.inspector_port;return typeof o=="number"?o:void 0},Xr=(e,r,n)=>{const t=ye(r,e.hasIpv6Loopback??Se,$),o=oe(e.target??Z(r));if(o.toolchain===void 0)throw new Error(`deploy target "${o.id}" has no dev server to run the worker sidecar`);const i=Ie(e,r),s=o.toolchain.dev({configPath:$,extraArgs:[...t,...Ee(e),"--var","WORKER_ENV:development",...i.args]}),a=ne(n,s);return{cleanup:i.cleanup,sidecar:{args:a.args,command:a.command,cwd:r,tag:"worker"}}},Qr=(e,r)=>{e.worker===!1&&e.logger.warn(`--no-worker does not apply to the ${r} flavor: Vite owns the worker, codegen and studio in-process. Run your framework's dev script instead.`),e.inspectorPort!==void 0&&e.logger.warn(r==="framework-worker"?`--inspector-port does not apply to the ${r} flavor: pin \`dev.inspector_port\` in ${$} — that is the config the worker sidecar runs.`:`--inspector-port does not apply to the ${r} flavor: Vite owns the worker. Pin it in vite.config — \`lunora({ cloudflare: { inspectorPort: ${String(e.inspectorPort)} } })\`.`),e.local===!0&&r==="vite"&&e.logger.warn("--local does not apply to the vite flavor: Vite owns the worker. Turn remote bindings off in vite.config — `lunora({ cloudflare: { remoteBindings: false } })`.")},Zr=e=>{const r=e.cwd??process.cwd(),n=A(r),t=e.flavor??M(r);if(t==="vite"||t==="framework-worker"){const v=fe(r),u=t==="framework-worker"?Xr(e,r,n):void 0,g=u?.sidecar;return Qr(e,t),{runsCodegenWatch:!1,flavor:t,ipv4LoopbackForced:!1,remote:{bindings:[],cleanup:ke,enabled:e.remote===!0,withheld:[]},...u?{serviceConfigCleanup:u.cleanup}:{},...g?{sidecar:g}:{},studioEnabled:!1,studioPort:e.port??R,workerEnabled:!0,workerOrigin:`http://localhost:${String(H)}`,workerPort:H,wrangler:{args:v.args,command:v.command,cwd:r,...we(e),tag:"vite"}}}const o=ee(r),i=o.framework==="none"?void 0:`this project uses ${o.framework} — the worker runs inside Vite there. run \`${Be(n,"dev")}\` for the full app (frontend + HMR); \`lunora dev\` starts only the worker.`,s=e.workerPort??m,a=Hr(e,r),c=ye(r,e.hasIpv6Loopback??Se),l=e.inspectorPort===void 0?[]:["--inspector-port",String(e.inspectorPort)],d=Ie(e,r),f=re(n,"wrangler",["dev","--port",String(s),...l,...c,...Ee(e),"--var","WORKER_ENV:development",...a.args,...zr(r,d.args,a.args)]);return{runsCodegenWatch:me(e),flavor:t,frameworkHint:i,ipv4LoopbackForced:c.length>0,remote:{...a.plan,withheld:[...a.plan.withheld,...d.withheld]},serviceConfigCleanup:d.cleanup,studioEnabled:e.studio!==!1,studioPort:e.port??R,workerEnabled:e.worker!==!1,workerOrigin:`http://localhost:${String(s)}`,workerPort:s,wrangler:{args:f.args,command:f.command,cwd:r,tag:"wrangler"}}},Tt=async e=>{const r=e.cwd??process.cwd();if(e.target!==void 0&&O(e.target))return jr({cwd:r,driver:oe(e.target),options:e,studioPort:e.port??R,workerPort:await Y(e,r)});const n=e.flavor??M(r),t=n==="wrangler"?await Y(e,r):e.workerPort,o=n==="wrangler"?Jr(e,r):e.inspectorPort;return Zr({...e,cwd:r,flavor:n,inspectorPort:o,workerPort:t})};export{_ as P,Et as a,$r as b,wr as c,or as d,M as e,yt as f,Tt as g,_r as h,kt as i,pt as j,St as k,me as l,Lt as m,gr as n,mt as o,Zr as p,Jr as q,nr as r,It as s,wt as t,Y as u,ht as w};
@@ -1 +1 @@
1
- import{g as r,p as a,q as s,u as l}from"./plan-DJeIwzFu.mjs";export{r as buildDevPlan,a as planDevCommand,s as resolveInspectorPort,l as resolveWorkerPort};
1
+ import{g as r,p as a,q as s,u as l}from"./plan-Dl57T11P.mjs";export{r as buildDevPlan,a as planDevCommand,s as resolveInspectorPort,l as resolveWorkerPort};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cli",
3
- "version": "1.0.0-alpha.379",
3
+ "version": "1.0.0-alpha.380",
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",
@@ -54,14 +54,14 @@
54
54
  "@bomb.sh/tab": "0.0.22",
55
55
  "@lunora/advisor": "1.0.0-alpha.206",
56
56
  "@lunora/bindings": "1.0.0-alpha.103",
57
- "@lunora/codegen": "1.0.0-alpha.290",
58
- "@lunora/config": "1.0.0-alpha.335",
57
+ "@lunora/codegen": "1.0.0-alpha.291",
58
+ "@lunora/config": "1.0.0-alpha.336",
59
59
  "@lunora/container": "1.0.0-alpha.72",
60
60
  "@lunora/d1": "1.0.0-alpha.169",
61
61
  "@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
62
62
  "@lunora/mcp": "1.0.0-alpha.227",
63
63
  "@lunora/runtime": "1.0.0-alpha.189",
64
- "@lunora/seed": "1.0.0-alpha.208",
64
+ "@lunora/seed": "1.0.0-alpha.209",
65
65
  "@visulima/cerebro": "3.1.2",
66
66
  "@visulima/error": "6.1.0",
67
67
  "@visulima/fs": "6.0.29",
@@ -1,5 +0,0 @@
1
- import{existsSync as q,mkdirSync as Le,openSync as J,closeSync as X,statSync as Te,readFileSync as Q,readSync as Pe}from"node:fs";import{join as C,dirname as be}from"node:path";import{readServiceBindings as _e}from"@lunora/codegen";import{readLiveDevServerState as N,discoverContainerInfo as Re,streamContainerLogs as $e,formatLunoraEvent as Ce,updateDevServerState as b,targetRunsOwnDevServer as O,resolveProjectTarget as Z,claimDevServerState as Ne,DEV_HANDOFF_ENV as Oe,clearDevServerState as y,readDevServerState as D,isDevServerReady as j,detectFramework as ee,readProjectDependencyNames as De,DEV_LOG_FILE_ENV as Ae,DEV_DAEMON_ENV as xe,detectPackageManager as A,readProjectManifest as Me,execArgsFor as re,isRecordedProcessCurrent as Ue,DEV_LOG_FILE as Ge,CODEGEN_ENV as te,isCodegenDisabled as Fe,planToolchainInvocation as je,toolchainExecArgs as ne,startCelldDevSession as Ve,resolveDeployDriver as oe,DEV_VARS_FILE as se,runScriptCommand as We,parseDevVariableEntries as Be}from"@lunora/config";import{warnCloudflareCliConfigOnce as Ke,findWranglerFile as L,readWranglerJsonc as x,materializeRemoteWranglerConfig as He,materializeServiceDevConfigs as ze}from"@lunora/config/cloudflare";import{createServer as Ye}from"node:net";import{networkInterfaces as qe}from"node:os";import{spawn as ie,spawnSync as T}from"node:child_process";import{setTimeout as w}from"node:timers/promises";import{EXIT_CODE as ae}from"./EXIT_CODE-D_XCixv8.mjs";import{a as h}from"./output-format-B4eQHi5y.mjs";import{spawnShellCompat as ce,defaultSpawner as Je}from"./createRecordingSpawner-Ch7zuQQE.mjs";import{isLunoraError as Xe}from"@lunora/errors";import{s as V}from"./index-oHZBGBwB.mjs";import{createInterface as Qe}from"node:readline";const Ze=12e4,_=250,er=1e3,rr="LUNORA_DEV_READY_TIMEOUT_MS",tr=e=>{if(e!==void 0)return e;const r=Number(process.env[rr]??"");return Number.isFinite(r)&&r>0?r:Ze},nr=async(e,r)=>{try{const n=AbortSignal.timeout(er);return await(await fetch(new URL("/_lunora/status",e),{redirect:"manual",signal:r===void 0?n:AbortSignal.any([r,n])})).body?.cancel(),!0}catch{return!1}},le=async(e,r,n)=>{const t=Date.now()+n.timeoutMs;for(;n.signal?.aborted!==!0;){const o=r(N(e));if(o!==void 0)return o;if(Date.now()>=t)return;try{await w(n.intervalMs??500,void 0,{signal:n.signal})}catch{return}}},W=(e,r)=>{Qe({crlfDelay:Number.POSITIVE_INFINITY,input:e}).on("line",r)},or=5e3,de=(e,r,n,t)=>{if(e.length===0)return;const o=Ce(e);if(o){t[o.level](`[lunora] ${o.text}`);return}const s=`[${r}] ${e}`;n==="stderr"?t.warn(s):t.info(s)},sr=(e,r,n=process.platform,t=T)=>{try{if(r==="SIGKILL"&&n==="win32"&&e.pid!==void 0){t("taskkill",["/pid",String(e.pid),"/T","/F"],{stdio:"ignore"});return}e.kill(r)}catch{}},ue=(e,r,n)=>{const t=e.direct===!0?{args:[...e.args],command:e.command,shell:!1}:ce(e.command,e.args),o=ie(t.command,t.args,{cwd:e.cwd??process.cwd(),env:e.env?{...process.env,...e.env}:process.env,shell:t.shell,stdio:[e.direct===!0?"ignore":"inherit","pipe","pipe"]});return W(o.stdout,s=>{r(s,"stdout")}),W(o.stderr,s=>{r(s,"stderr")}),{exited:new Promise(s=>{o.on("error",i=>{n?.(i),s(1)}),o.on("exit",(i,a)=>{s(i??(a?1:0))})}),kill:s=>{sr(o,s)}}},gt=(e,r)=>ue(e,(n,t)=>{de(n.trimEnd(),e.tag,t,r)},n=>{r.error(`[${e.tag}] failed to start: ${n.message}`)}),pt=(e,r)=>{if(process.env.LUNORA_CONTAINER_LOGS==="0")return;const t=Re(e,"lunora").containers.map(o=>({className:o.className,exportName:o.exportName}));if(t.length!==0)return $e({containers:t,onLine:o=>{const s=`[container:${o.name}] ${o.text}`;o.level==="error"?r.warn(s):r.info(s)},onUnavailable:o=>{r.warn(`[container] Docker engine unreachable — container logs unavailable (${o})`)}})},mt=async e=>{e.readyProbe?.abort(),await e.tunnel?.close(),await e.codegen?.close().catch(()=>{}),e.containerLogs?.close(),await e.studio?.close().catch(()=>{});try{e.remoteCleanup?.(),e.serviceConfigCleanup?.()}catch{}},wt=async e=>await new Promise(r=>{const n={},t=i=>{e.info(`received ${i} — shutting down`),n.sigint&&process.off("SIGINT",n.sigint),n.sigterm&&process.off("SIGTERM",n.sigterm),r(0)},o=()=>{t("SIGINT")},s=()=>{t("SIGTERM")};n.sigint=o,n.sigterm=s,process.on("SIGINT",o),process.on("SIGTERM",s)}),ht=async(e,r,n)=>{let t=0,o;const s=l=>{e.kill(l),r?.kill(l)},i=()=>{t+=1,t===1?(n.info("received SIGINT — shutting down (press Ctrl-C again to force-kill)"),s("SIGTERM"),o=setTimeout(()=>{s("SIGKILL")},or),o.unref()):s("SIGKILL")},a=()=>{s("SIGTERM")};process.on("SIGINT",i),process.on("SIGTERM",a);const c=await Promise.race([e.exited.then(l=>({code:l,who:"worker"})),...r===void 0?[]:[r.exited.then(l=>({code:l,who:"sidecar"}))]]);return r!==void 0&&(c.who==="sidecar"?(n.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])),o&&clearTimeout(o),process.off("SIGINT",i),process.off("SIGTERM",a),c.code},E="2026.9.3",ir="https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/",ar=/version (\S+)/u,B=/https:\/\/[\da-z-]+\.trycloudflare\.com/u,cr="1s",lr=5e3,K=async(e,r)=>await Promise.race([e.then(()=>!0),w(r,!1,{ref:!1})]),dr=6e4,ur='Vite answers 403 for hosts it does not know — add `server: { allowedHosts: [".trycloudflare.com"] }` to vite.config if the URL is blocked.',fr=async e=>{const r=`Install or update it: ${ir} (macOS: \`brew install cloudflared\`).`;let n;try{const o=await e({args:["--version"],captureStdoutSilently:!0,command:"cloudflared"});if(o.code!==0)return{message:`\`cloudflared --version\` exited ${String(o.code)}. ${r}`,ok:!1};n=o.stdout??""}catch(o){return Xe(o)&&o.code==="LOCAL_DEPENDENCY_MISSING"?{message:`--tunnel needs \`cloudflared\` ${E} or newer, and it is not on your PATH. ${r}`,ok:!1}:{message:`\`cloudflared --version\` could not run: ${o instanceof Error?o.message:String(o)}. ${r}`,ok:!1}}const t=V.coerce(ar.exec(n)?.[1])?.version;return t===void 0?{ok:!0,version:void 0}:V.gte(t,E)?{ok:!0,version:t}:{message:`--tunnel needs \`cloudflared\` ${E} or newer (protected quick tunnels); found ${t}. ${r}`,ok:!1}},vr=e=>{const r=[],n=[];for(const t of e??[]){const o=t.split(",").map(s=>s.trim()).filter(s=>s.length>0);o.length>0&&(n.push(...o.filter(s=>!s.includes("@"))),r.push(o.join(",")))}return{entries:r,invalid:n}},gr=(e,r)=>["tunnel","--url",e,"--output","json","--grace-period",cr,...r.flatMap(n=>["--allowed-mail",n])],pr=e=>{let r;try{r=JSON.parse(e)}catch{r=void 0}if(typeof r=="object"&&r!==null){const n=r,t=typeof n.message=="string"?n.message:"";return{level:typeof n.level=="string"?n.level:void 0,text:t,url:B.exec(t)?.[0]}}return{level:void 0,text:e,url:B.exec(e)?.[0]}},mr=e=>{e.warn(" ⚠ PUBLIC TUNNEL: anyone with the tunnel URL can reach this dev server — there is no sign-in in front of it."),e.warn(" Protect it with a one-time-PIN email allow-list: lunora dev --tunnel --allow-mail you@example.com"),e.warn(" (repeat --allow-mail, pass a comma-separated list, or allow a whole domain with '*@example.com')")},wr=async(e,r,n,{intervalMs:t,timeoutMs:o=dr}={})=>{const s=await le(e,i=>i?.mode==="vite"?{origin:i.url,ownerPid:i.pid}:void 0,{intervalMs:t,signal:n,timeoutMs:o});return s===void 0&&!n.aborted&&r.warn(`--tunnel: the Vite dev server did not report its URL within ${String(o/1e3)}s — continuing without a tunnel.`),s},hr=e=>{const{allowMail:r,cwd:n,logger:t}=e,o=r.length===0,s=new AbortController,{signal:i}=s,a=()=>i.aborted,c=e.closeTimeoutMs??lr;let l;const d=()=>{s.abort()};process.once("SIGINT",d),process.once("SIGTERM",d);const v=(async()=>{const u=await fr(e.spawner??Je);if(!u.ok){t.error(u.message),t.warn("continuing without a tunnel.");return}u.version===void 0&&t.warn(`could not read the cloudflared version — --tunnel needs ${E} or newer; trying anyway.`);const g=e.origin.kind==="fixed"?{origin:e.origin.origin,ownerPid:process.pid}:await wr(n,t,i);if(g===void 0||i.aborted)return;o&&mr(t),t.info(`starting a Cloudflare quick tunnel to ${g.origin}${o?"":" (email one-time-PIN protected)"}…`);let S;const U={failedToStart:!1},ye=k=>{if(k.length===0)return;const p=pr(k);if(p.url!==void 0&&S===void 0){S=p.url,t.success(` ➜ Tunnel: ${p.url}${o?" (PUBLIC)":""}`),o||t.info(` Allowed: ${r.join(", ")} — visitors confirm with a one-time PIN; a session lasts up to 4 hours`),e.origin.kind==="vite"&&t.info(` ${ur}`),b(n,{tunnelUrl:p.url},{expectedPid:g.ownerPid});return}p.level==="error"||p.level==="fatal"?t.warn(`[cloudflared] ${p.text}`):t.debug?.(`[cloudflared] ${p.text}`)},P=(e.startChild??ue)({args:gr(g.origin,r),command:"cloudflared",direct:!0},ye,k=>{U.failedToStart=!0,t.error(`could not start cloudflared (${k.message}) — continuing without a tunnel.`)});l=P;const G=()=>{P.kill("SIGTERM")};i.addEventListener("abort",G,{once:!0});const F=await P.exited;i.removeEventListener("abort",G),l=void 0,S!==void 0&&b(n,{tunnelUrl:void 0},{expectedPid:g.ownerPid}),!(a()||U.failedToStart)&&t.warn(S===void 0?`cloudflared exited (${String(F)}) before a tunnel URL was assigned — continuing without a tunnel.`:`cloudflared exited (${String(F)}) — the tunnel is closed; the dev server keeps running.`)})().catch(u=>{t.error(`--tunnel failed (${u instanceof Error?u.message:String(u)}) — continuing without a tunnel.`)});return{close:async()=>{if(process.off("SIGINT",d),process.off("SIGTERM",d),s.abort(),await K(v,c))return;const u=l;u!==void 0&&(t.warn(`cloudflared did not exit within ${String(c/1e3)}s of SIGTERM — force-killing it.`),u.kill("SIGKILL"),!await K(u.exited,c)&&t.error(`could not confirm that cloudflared stopped — ${o?"the PUBLIC tunnel":"the tunnel"} may still be reachable. Stop the \`cloudflared\` process manually.`))}}},St=e=>{const{cwd:r,logger:n,plan:t,tunnel:o}=e;if(o!==void 0)return hr({allowMail:o.allowMail,cwd:r,logger:n,origin:t.flavor==="wrangler"?{kind:"fixed",origin:t.workerOrigin}:{kind:"vite"},spawner:o.spawner,startChild:o.startChild})},Sr=(e,r)=>{const n=["dev"];if(e.apiSpec!==void 0&&n.push("--api-spec",e.apiSpec),e.port!==void 0&&n.push("--port",String(e.port)),e.workerPort!==void 0&&n.push("--worker-port",String(e.workerPort)),e.inspectorPort!==void 0&&n.push("--inspector-port",String(e.inspectorPort)),e.emitBindings!==void 0&&n.push("--emit-bindings",e.emitBindings),e.codegen===!1&&n.push("--no-codegen"),e.studio===!1&&n.push("--no-studio"),e.worker===!1&&n.push("--no-worker"),e.target!==void 0&&n.push("--target",e.target),e.tunnel===!0){n.push("--tunnel");for(const t of vr(e.allowMail).entries)n.push("--allow-mail",t)}return r&&n.push("--remote"),e.local===!0&&n.push("--local"),n},kr=1e4,Ir=100,Er=40,I=256*1024,yr=new Set(["nuxt","sveltekit"]),Lr=["@lunora/rspack","@lunora/vite"],M=e=>{if(yr.has(ee(e).framework))return"framework-worker";const r=De(e);return Lr.some(n=>r.has(n))?"vite":"wrangler"},fe=e=>{const r=A(e),n=Me(e)?.scripts,t=typeof n=="object"&&n!==null?n.dev:void 0;if(typeof t!="string"||t.trim()===""||t.includes("lunora")){const o=re(r,"vite",["dev"]);return{args:o.args,command:o.command}}return{args:["run","dev"],command:r}},Tr=e=>{Le(be(e.logPath),{recursive:!0});const r=J(e.logPath,"w",384);let n;const t=ce(e.command,e.args);try{n=ie(t.command,t.args,{cwd:e.cwd,detached:!0,env:e.env,shell:t.shell,stdio:["ignore",r,r],windowsHide:!0})}finally{X(r)}return n.unref(),{exited:new Promise(o=>{n.on("error",()=>{o(1)}),n.on("exit",(s,i)=>{o(s??(i?1:0))})}),pid:n.pid}},Pr=e=>{try{const{size:r}=Te(e);if(r<=I)return Q(e,"utf8");const n=J(e,"r");try{const t=Buffer.alloc(I),o=Pe(n,t,0,I,r-I),s=t.toString("utf8",0,o);return s.slice(s.indexOf(`
2
- `)+1)}finally{X(n)}}catch{return}},ve=(e,r)=>{const n=Pr(e);if(n===void 0)return[];const t=n.split(`
3
- `);return t.at(-1)===""&&t.pop(),r>0?t.slice(-r):t},ge=(e,r)=>r?.logFile??C(e,Ge),pe=e=>{e.info(" Stop: lunora dev stop"),e.info(" Status: lunora dev status"),e.info(" Logs: lunora dev logs")},br=(e,r)=>{Ke(r,n=>{e.warn(n)})},_r=(e,r)=>{e.warn(`Dev server already running at ${r.url} (pid ${String(r.pid)})`),pe(e)},Rr=(e,r)=>{e.success(`Dev server running at ${r.url} (pid ${String(r.pid)})`),r.studioUrl!==void 0&&e.info(` Studio: ${r.studioUrl}`),r.tunnelUrl!==void 0&&e.info(` Tunnel: ${r.tunnelUrl}`),pe(e)},$r=3e4,Cr=async(e,r,n,t)=>{const o=await le(e,s=>s?.tunnelUrl,{intervalMs:n,timeoutMs:t});o===void 0?r.warn(" Tunnel: no URL yet — `lunora dev status` shows it once cloudflared reports one; `lunora dev logs` says why if it does not."):r.success(` Tunnel: ${o}`)},Nr=async e=>{const{child:r,cwd:n,deadline:t,pollIntervalMs:o,probe:s}=e,i=r.exited.then(a=>({exitCode:a}));for(;Date.now()<t;){const a=N(n);if(a!==void 0&&a.pid!==process.pid&&await s(a.url))return{state:a,status:"ready"};const c=await Promise.race([i,w(o)]);if(c!==void 0)return{...c,status:"exited"}}return{status:"timeout"}},Or=async e=>{const{cwd:r,logger:n}=e,t=ge(r,void 0),o=tr(e.readyTimeoutMs),s=(e.spawnDetached??Tr)({args:e.command.args,command:e.command.command,cwd:r,env:{...process.env,...e.env,[xe]:"1",[Ae]:t,...process.env.FORCE_COLOR===void 0?{NO_COLOR:"1"}:{},...e.json?{LUNORA_LOG_JSON:"1"}:{}},logPath:t}),i=await Nr({child:s,cwd:r,deadline:Date.now()+o,pollIntervalMs:e.pollIntervalMs??_,probe:e.probe??nr});if(i.status==="ready")return Rr(n,i.state),e.tunnel===!0&&i.state.tunnelUrl===void 0&&await Cr(r,n,e.pollIntervalMs??_,e.tunnelUrlTimeoutMs??$r),{code:0};if(i.status==="exited"){n.error(`dev server exited before becoming ready (exit code ${String(i.exitCode)}). Last output:`);for(const c of ve(t,Er))n.error(` ${c}`);return y(r,s.pid??process.pid),{code:i.exitCode===0?1:i.exitCode}}s.pid!==void 0&&D(r)?.pid===process.pid&&b(r,{logFile:t,pid:s.pid});const a=s.pid===void 0?"":` (pid ${String(s.pid)})`;return n.warn(`dev server did not confirm ready within ${String(Math.round(o/1e3))}s — it may still be compiling${a}. Check \`lunora dev status\` and \`lunora dev logs\`; \`lunora dev stop\` shuts it down.`),{code:ae.UNAVAILABLE}},me=e=>e.codegen!==!1&&!Fe(process.env[te]),we=e=>{const r={...e.remote===!0?{LUNORA_REMOTE:"1"}:{},...e.codegen===!1?{[te]:"0"}:{}};return Object.keys(r).length>0?{env:r}:{}},kt=async e=>{const{cwd:r,jsonLogs:n,logger:t,options:o,remote:s}=e,i=e.run??Or,a=M(r),c=a==="vite"&&O(o.target??Z(r))?"wrangler":a,l=c==="wrangler"?`http://localhost:${String(o.workerPort??8787)}`:"http://localhost:5173",d=Ne(r,{background:!0,mode:"cli",pid:process.pid,startedAt:new Date().toISOString(),url:l});if(!d.ok)return d.existing!==void 0&&_r(t,d.existing),{code:0};const f={[Oe]:String(process.pid)};br(t,r);try{return c==="vite"&&o.tunnel!==!0?await i({command:fe(r),cwd:r,env:{...we({...o,remote:s}).env,...f},json:n,logger:t}):await i({command:{args:[process.argv[1]??"lunora",...Sr(o,s)],command:process.execPath},cwd:r,env:f,json:n,logger:t,tunnel:o.tunnel===!0})}finally{y(r,process.pid)}},Dr=e=>{try{const r=T("ps",["-o","pgid=","-p",String(e)],{encoding:"utf8"}),n=Number.parseInt(r.stdout.trim(),10);return Number.isInteger(n)&&n>0?n:void 0}catch{return}},Ar=(e,r,n=process.platform,t=T)=>{if(n==="win32"){try{t("taskkill",["/pid",String(e.pid),"/T","/F"],{stdio:"ignore"})}catch{}return}if(e.background===!0)try{r(-(Dr(e.pid)??e.pid),"SIGKILL");return}catch{}try{r(e.pid,"SIGKILL")}catch{}},xr=(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}),Mr=async e=>{const{cwd:r,logger:n}=e,t=D(r),o=e.alive??(f=>f===t?.pid&&Ue(t)),s=e.signal??((f,v)=>{process.kill(f,v)}),i=e.pollIntervalMs??_,a=e.stopGraceMs??kr,c=e.platform??process.platform,l=e.spawnSyncImpl??T;if(t===void 0||!o(t.pid))return t!==void 0&&y(r,t.pid),e.json?h({running:!1,stopped:!1}):n.info("No dev server running."),{code:0};try{s(t.pid,"SIGTERM")}catch{}const d=Date.now()+a;for(;o(t.pid)&&Date.now()<d;)await w(i);return o(t.pid)&&(Ar(t,s,c,l),await w(i),o(t.pid))?xr(t,e):(y(r,t.pid),e.json?h({pid:t.pid,stopped:!0}):n.success(`Stopped dev server (pid ${String(t.pid)}).`),{code:0})},Ur=e=>{const{cwd:r,logger:n}=e,t=N(r);if(t===void 0)return e.json?h({running:!1}):n.info("No dev server running."),{code:0};const o=e.now??Date.now,s=t.startedAt===void 0?Number.NaN:Date.parse(t.startedAt),i=Number.isFinite(s)?Math.max(0,Math.round((o()-s)/1e3)):void 0;if(e.json)return h({background:t.background===!0,logFile:t.logFile,mode:t.mode,pid:t.pid,ready:j(t),readyAt:t.readyAt,running:!0,startedAt:t.startedAt,studioUrl:t.studioUrl,tunnelUrl:t.tunnelUrl,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",j(t)?"ready":"starting"];return n.success(`Dev server running at ${t.url} (${a.join(", ")})`),t.studioUrl!==void 0&&n.info(` Studio: ${t.studioUrl}`),t.tunnelUrl!==void 0&&n.info(` Tunnel: ${t.tunnelUrl}`),t.logFile!==void 0&&n.info(` Logs: lunora dev logs (${t.logFile})`),{code:0}},Gr=e=>{const{cwd:r,logger:n}=e,t=D(r),o=ge(r,t);if(!q(o))return n.info("No dev server logs found — output is captured when the server runs in background mode (`lunora dev --background`)."),{code:0};const s=ve(o,e.lines??Ir);return s.length>0&&process.stdout.write(`${s.join(`
4
- `)}
5
- `),{code:0}},It=e=>{const{cwd:r,json:n,lines:t,logger:o,subcommand:s}=e;if(s==="stop")return Mr({cwd:r,json:n,logger:o});if(s==="status")return Ur({cwd:r,json:n,logger:o});if(s==="logs")return Gr({cwd:r,lines:t,logger:o});if(s!==void 0)return o.error(`dev: unknown subcommand "${s}" — expected stop | status | logs (or no subcommand to start the dev server)`),{code:ae.USAGE}},Et=(e,r,n)=>O(e)?(r!=="wrangler"&&n.info(`target ${e} runs its own dev server, so lunora dev serves the worker on it — start the frontend's dev server separately`),"wrangler"):r,yt=async e=>{const{logger:r}=e,n=await(e.start??Ve)({log:(s,{stream:i,tag:a})=>{de(s,a,i,r)},port:e.port,projectRoot:e.projectRoot});let t=()=>{};const o=new Promise(s=>{t=s});return{exited:Promise.race([o,n.exited]),kill:()=>{n.stop().then(()=>0,()=>1).then(t).catch(()=>{})}}},Fr=e=>{const{cwd:r,driver:n,options:t,studioPort:o,workerPort:s}=e,{projectConfig:i,toolchain:a}=n;if(i===void 0||a===void 0)throw new Error(`deploy target "${n.id}" runs its own dev server but ships no config projection or toolchain for it`);if(t.remote===!0)throw new Error(`--remote proxies bindings to Cloudflare; ${n.name} has no remote bindings to proxy to`);t.inspectorPort!==void 0&&t.logger.warn(`--inspector-port is a wrangler dev flag; ${n.name} dev has no inspector to pin`);const{command:c,commit:l,projection:d}=je(n,r,"dev",v=>a.dev({configPath:v,extraArgs:["--port",String(s)]}));l(),d!==void 0&&d.dropped.length>0&&t.logger.info(`${n.name} ignores these wrangler keys, so its dev server runs without them: ${d.dropped.join(", ")}`);const f=ne(A(r),c);return{flavor:"wrangler",ipv4LoopbackForced:!1,remote:{bindings:[],cleanup:()=>{},enabled:!1},runsCodegenWatch:me(t),celldSession:!0,studioEnabled:t.studio!==!1,studioPort:o,workerEnabled:t.worker!==!1,workerOrigin:`http://localhost:${String(s)}`,workerPort:s,wrangler:{args:f.args,command:f.command,cwd:r,tag:c.tool}}},jr=64,he="127.0.0.1",Vr=65535,Wr=(e,r=he)=>new Promise(n=>{const t=Ye();t.once("error",()=>{n(!1)}),t.once("listening",()=>{t.close(()=>{n(!0)})}),t.listen(e,r)}),Br=async(e,r=he,n=jr)=>{for(let t=e;t<e+n&&t<=Vr;t+=1)if(await Wr(t,r))return t;return e},Se=(e=qe)=>Object.values(e()).some(r=>r?.some(n=>n.internal&&n.address==="::1")),R="wrangler.dev.jsonc",$=6173,m=8787,H=5173,Kr=(e,r)=>{const n=()=>{};if(!e.remote)return{args:[],plan:{bindings:[],cleanup:n,enabled:!1}};const o=(e.materializeRemote??He)({enabled:!0,projectRoot:r}),s=o.remoteBindings.map(a=>`${a.binding} (${a.kind})`),{cleanup:i}=o;return o.configPath===void 0?{args:[],plan:{bindings:s,cleanup:i,enabled:!0,reason:o.reason}}:{args:["--config",o.configPath],plan:{bindings:s,cleanup:i,enabled:!0}}},ke=(e,r)=>{const n=[...new Set(_e(r).services.map(s=>s.wranglerPath))],{cleanup:t,configPaths:o}=(e.materializeServiceConfigs??ze)(n);return{args:o.flatMap(s=>["--config",s]),cleanup:t}},Hr=(e,r,n)=>{const t=L(e);return r.length===0||t===void 0?[]:[...n.length>0?[]:["--config",t],...r]},Ie=e=>e.local===!0?["--local"]:[],z=e=>x(e).parsed?.dev?.ip,Ee=(e,r,n)=>{if(n!==void 0){const o=C(e,n);if(q(o)&&z(o)!==void 0)return[]}const t=L(e);return t!==void 0&&z(t)!==void 0?[]:r()?[]:["--ip","127.0.0.1"]},zr=new Set(["127.0.0.1","::1","[::1]","localhost"]),Yr=(e,r)=>{let n;try{n=Q(C(e,se),"utf8")}catch{return[]}const t=[];for(const o of Be(n)){let s;try{s=new URL(o.value)}catch{continue}zr.has(s.hostname)&&s.port===String(r)&&t.push(o.key)}return t},Y=async(e,r)=>{if(e.workerPort!==void 0)return e.workerPort;const n=L(r);if(n!==void 0){const{parsed:s}=x(n);if(typeof s?.dev?.port=="number")return s.dev.port}const t=await(e.findFreePort??Br)(m);if(t===m)return t;const o=Yr(r,m);if(o.length>0)throw new Error(`port ${String(m)} is in use, but ${se} pins the worker origin to it (${o.join(", ")}). Serving on ${String(t)} would leave those URLs pointing at nothing. Stop whatever holds ${String(m)}, or run \`lunora dev --worker-port ${String(t)}\` and update those values to match.`);return t},qr=(e,r)=>{if(e.inspectorPort!==void 0)return e.inspectorPort;const n=L(r);if(n===void 0)return;const{parsed:t}=x(n),o=t?.dev?.inspector_port;return typeof o=="number"?o:void 0},Jr=(e,r,n)=>{const t=Ee(r,e.hasIpv6Loopback??Se,R),o=oe(e.target??Z(r));if(o.toolchain===void 0)throw new Error(`deploy target "${o.id}" has no dev server to run the worker sidecar`);const s=ke(e,r),i=o.toolchain.dev({configPath:R,extraArgs:[...t,...Ie(e),"--var","WORKER_ENV:development",...s.args]}),a=ne(n,i);return{cleanup:s.cleanup,sidecar:{args:a.args,command:a.command,cwd:r,tag:"worker"}}},Xr=(e,r)=>{e.worker===!1&&e.logger.warn(`--no-worker does not apply to the ${r} flavor: Vite owns the worker, codegen and studio in-process. Run your framework's dev script instead.`),e.inspectorPort!==void 0&&e.logger.warn(r==="framework-worker"?`--inspector-port does not apply to the ${r} flavor: pin \`dev.inspector_port\` in ${R} — that is the config the worker sidecar runs.`:`--inspector-port does not apply to the ${r} flavor: Vite owns the worker. Pin it in vite.config — \`lunora({ cloudflare: { inspectorPort: ${String(e.inspectorPort)} } })\`.`),e.local===!0&&r==="vite"&&e.logger.warn("--local does not apply to the vite flavor: Vite owns the worker. Turn remote bindings off in vite.config — `lunora({ cloudflare: { remoteBindings: false } })`.")},Qr=e=>{const r=e.cwd??process.cwd(),n=A(r),t=e.flavor??M(r);if(t==="vite"||t==="framework-worker"){const v=fe(r),u=t==="framework-worker"?Jr(e,r,n):void 0,g=u?.sidecar;return Xr(e,t),{runsCodegenWatch:!1,flavor:t,ipv4LoopbackForced:!1,remote:{bindings:[],cleanup:()=>{},enabled:e.remote===!0},...u?{serviceConfigCleanup:u.cleanup}:{},...g?{sidecar:g}:{},studioEnabled:!1,studioPort:e.port??$,workerEnabled:!0,workerOrigin:`http://localhost:${String(H)}`,workerPort:H,wrangler:{args:v.args,command:v.command,cwd:r,...we(e),tag:"vite"}}}const o=ee(r),s=o.framework==="none"?void 0:`this project uses ${o.framework} — the worker runs inside Vite there. run \`${We(n,"dev")}\` for the full app (frontend + HMR); \`lunora dev\` starts only the worker.`,i=e.workerPort??m,a=Kr(e,r),c=Ee(r,e.hasIpv6Loopback??Se),l=e.inspectorPort===void 0?[]:["--inspector-port",String(e.inspectorPort)],d=ke(e,r),f=re(n,"wrangler",["dev","--port",String(i),...l,...c,...Ie(e),"--var","WORKER_ENV:development",...a.args,...Hr(r,d.args,a.args)]);return{runsCodegenWatch:me(e),flavor:t,frameworkHint:s,ipv4LoopbackForced:c.length>0,remote:a.plan,serviceConfigCleanup:d.cleanup,studioEnabled:e.studio!==!1,studioPort:e.port??$,workerEnabled:e.worker!==!1,workerOrigin:`http://localhost:${String(i)}`,workerPort:i,wrangler:{args:f.args,command:f.command,cwd:r,tag:"wrangler"}}},Lt=async e=>{const r=e.cwd??process.cwd();if(e.target!==void 0&&O(e.target))return Fr({cwd:r,driver:oe(e.target),options:e,studioPort:e.port??$,workerPort:await Y(e,r)});const n=e.flavor??M(r),t=n==="wrangler"?await Y(e,r):e.workerPort,o=n==="wrangler"?qr(e,r):e.inspectorPort;return Qr({...e,cwd:r,flavor:n,inspectorPort:o,workerPort:t})};export{_ as P,It as a,_r as b,mr as c,nr as d,M as e,Et as f,Lt as g,br as h,St as i,gt as j,ht as k,me as l,yt as m,vr as n,pt as o,Qr as p,qr as q,tr as r,kt as s,mt as t,Y as u,wt as w};