@lunora/cli 1.0.0-alpha.201 → 1.0.0-alpha.203
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/bin.mjs +1 -1
- package/dist/index.d.mts +36 -6
- package/dist/index.d.ts +36 -6
- package/dist/index.mjs +1 -1
- package/dist/packem_chunks/handler.mjs +1 -1
- package/dist/packem_chunks/handler13.mjs +1 -1
- package/dist/packem_chunks/handler14.mjs +1 -1
- package/dist/packem_chunks/handler17.mjs +1 -1
- package/dist/packem_chunks/handler19.mjs +1 -1
- package/dist/packem_chunks/handler2.mjs +2 -2
- package/dist/packem_chunks/handler21.mjs +2 -2
- package/dist/packem_chunks/handler25.mjs +2 -2
- package/dist/packem_chunks/handler26.mjs +4 -4
- package/dist/packem_chunks/handler3.mjs +1 -1
- package/dist/packem_chunks/handler4.mjs +1 -1
- package/dist/packem_chunks/handler7.mjs +3 -3
- package/dist/packem_chunks/planDevCommand.mjs +6 -6
- package/dist/packem_chunks/runCodegenCommand.mjs +3 -3
- package/dist/packem_chunks/runDeployCommand.mjs +1 -1
- package/dist/packem_chunks/runInitCommand.mjs +11 -11
- package/dist/packem_shared/COMMANDS-D-2fKisr.mjs +1 -0
- package/dist/packem_shared/cli-HouJioGN.mjs +3 -0
- package/dist/packem_shared/{commands-BOgN2nF7.mjs → commands-BNKj7r1R.mjs} +7 -7
- package/dist/packem_shared/{lint-ignore-report-CDbcbtv7.mjs → lint-ignore-report-ajVhvqY-.mjs} +1 -1
- package/dist/packem_shared/runAddCommand-D1Dj8-pK.mjs +1 -0
- package/dist/packem_shared/wrangler-secrets-CCdt0mUl.mjs +1 -0
- package/package.json +7 -8
- package/dist/packem_shared/COMMANDS-su5nmqYP.mjs +0 -1
- package/dist/packem_shared/cli-RB-BoDnW.mjs +0 -3
- package/dist/packem_shared/detect-package-manager-DSaAqRof.mjs +0 -1
- package/dist/packem_shared/post-codegen-hook-DRdEoTsQ.mjs +0 -1
- package/dist/packem_shared/runAddCommand-BXz-PXbb.mjs +0 -1
- package/dist/packem_shared/wrangler-secrets-CE7jcf8E.mjs +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{spawnSync as
|
|
2
|
-
`)),n.length>0&&o.write(n),o.pipe(r),r.pipe(o)});o.setTimeout(
|
|
3
|
-
`)+1)}finally{
|
|
4
|
-
`);return t.at(-1)===""&&t.pop(),r>0?t.slice(-r):t},
|
|
1
|
+
import{spawnSync as O,spawn as z}from"node:child_process";import{existsSync as P,watch as ye,readFileSync as Q,mkdirSync as Ee,openSync as Z,closeSync as q,statSync as ke,readSync as be}from"node:fs";import{join as L,dirname as Te}from"node:path";import{runCodegen as ee}from"@lunora/codegen";import{runPostCodegenHook as Le,detectAgentRules as re,claimDevServerState as te,DEV_HANDOFF_ENV as b,clearDevServerState as S,readDevServerState as D,readLiveDevServerState as R,detectFramework as ne,readProjectDependencyNames as Re,DEV_LOG_FILE_ENV as oe,DEV_DAEMON_ENV as A,updateDevServerState as se,detectPackageManager as ie,execArgsFor as _,isRecordedProcessCurrent as _e,DEV_LOG_FILE as Ie,CODEGEN_ENV as ae,isCodegenDisabled as Oe,detectAiAgent as Pe,readProjectRemotePreference as De,resolveTargetOrThrow as Ae,inferLunoraBindings as Ne,packageNamesFromBindings as $e,ensureDevVarsExample as Ce,ensureDevVariables as xe,isInteractive as Me,DEV_VARS_FILE as C,DEV_VARS_EXAMPLE_FILE as Fe,fillDevSecrets as je,claimAgentRulesHint as Ge,AGENT_RULES_HINT as Ue,resolveDeployDriver as Ve,resolveProjectTarget as He,runScriptCommand as We,discoverContainerInfo as Be,streamContainerLogs as Ke,formatLunoraEvent as Je}from"@lunora/config";import{resolveRemoteEnabled as Ye,findWranglerFile as ce,readWranglerJsonc as de,materializeRemoteWranglerConfig as Xe}from"@lunora/config/cloudflare";import{p as ze}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{r as Qe}from"../packem_shared/codegen-error-C04fGQjf.mjs";import{r as Ze}from"../packem_shared/platform-diagnostics-uOXqysk3.mjs";import{d as qe}from"../packem_shared/command-DwC11Cwe.mjs";import{createServer as er,connect as rr}from"node:net";import{forceJsonLogging as tr}from"../packem_shared/createLogger-hGafwf-3.mjs";import{networkInterfaces as nr}from"node:os";import{spawnShellCompat as le}from"../packem_shared/createRecordingSpawner-DJlNacZ6.mjs";import{createServer as or,request as sr}from"node:http";import{pipeline as ir}from"node:stream";import{loadStudioAssets as x,studioAssetsStamp as M,renderStudioHtml as ar,resolveAdminToken as cr,transportRejectionReason as F,SCHEMA_EDIT_ENDPOINT as dr,POLICY_SCAFFOLD_ENDPOINT as lr,SEED_ENDPOINT as ur,sendStudioDocument as fr,serveJsonHandler as gr,isStandaloneModulePath as vr,readStandaloneAsset as mr,assetContentType as pr,handleSchemaEditRequest as wr,handlePolicyScaffoldRequest as hr,handleSeedRequest as Sr,applyStudioAssetCache as yr}from"@lunora/config/studio-host";import{c as Er}from"../packem_shared/tui-prompts-Dd0pDuNr.mjs";import{setTimeout as I}from"node:timers/promises";import{p as y}from"../packem_shared/output-format-DfogXx0p.mjs";const kr=100,br=300,Tr=/[/\\]/u,Lr=(e,r,n)=>{const{apiSpec:t,logger:o,projectRoot:s,target:a}=e;try{const i=ee({apiSpec:t,lunoraDirectory:r,projectRoot:s,target:a});o.success(`codegen: wrote ${r}/_generated (${n})`),Ze(i.platformDiagnostics,o)}catch(i){return o.error(Qe(i,n)),!1}return!0},Rr=e=>{const r=e.lunoraDirectory??"lunora",n=L(e.projectRoot,r),t=e.debounceMs??kr;let o=!1,s=!1,a=!1,i,l=0,u=Promise.resolve(),g;const m=new Promise(v=>{g=v}),p=v=>{o||(i=v,!s&&(s=!0,u=(async()=>{try{for(;i!==void 0;){const w=i;if(i=void 0,!!Lr(e,r,w)){a=!0;try{(await Le({cwd:e.projectRoot,logger:e.logger,spawner:e.spawner,stdoutToStderr:e.jsonLogs})).ran&&(l=Date.now())}finally{a=!1}}}}catch(w){e.logger.error(`codegen watch: ${w instanceof Error?w.message:String(w)}`)}finally{s=!1,g()}})()))};p("startup");const d=v=>(e.logger.warn(`codegen watch unavailable (${v}) — schema edits will NOT auto-regenerate. Run \`lunora codegen\` manually after each edit.`),{close:()=>(o=!0,i=void 0,u),ready:m,watchAvailable:!1});let c,f;if(!P(n))return d(`no such directory: ${n}`);try{f=ye(n,{recursive:!0},(v,w)=>{if(!(typeof w=="string"&&w.split(Tr).includes("_generated"))){if(a||Date.now()-l<br){c&&(clearTimeout(c),c=void 0);return}c&&clearTimeout(c),c=setTimeout(()=>{p(`change: ${w??"?"}`)},t)}})}catch(v){return d(v instanceof Error?v.message:String(v))}const h=f;return{close:()=>(o=!0,i=void 0,c&&clearTimeout(c),h.close(),u),ready:m,watchAvailable:!0}},_r=64,ue="127.0.0.1",Ir=65535,Or=(e,r=ue)=>new Promise(n=>{const t=er();t.once("error",()=>{n(!1)}),t.once("listening",()=>{t.close(()=>{n(!0)})}),t.listen(e,r)}),Pr=async(e,r=ue,n=_r)=>{for(let t=e;t<e+n&&t<=Ir;t+=1)if(await Or(t,r))return t;return e},j=(e=nr)=>Object.values(e()).some(r=>r?.some(n=>n.internal&&n.address==="::1")),G="/_lunora",U=e=>{const r=e.indexOf("?");return r===-1?e:e.slice(0,r)},V=3e4,H=(e,r,n)=>{const t=sr({headers:{...e.headers,host:n.host},hostname:n.hostname,method:e.method,path:e.url,port:n.port},o=>{r.writeHead(o.statusCode??502,o.headers),ir(o,r,()=>{})});t.setTimeout(V,()=>{t.destroy(new Error(`no response within ${String(V)}ms`))}),t.on("error",o=>{if(r.headersSent){r.destroy();return}r.statusCode=o.message.startsWith("no response within")?504:502,r.end(`lunora dev: worker unreachable (${o.message})`)}),r.on("close",()=>{r.writableFinished||t.destroy()}),e.pipe(t)},Dr=5e3,Ar=(e,r,n,t)=>{const o=rr({host:t.hostname,port:Number(t.port)},()=>{o.setTimeout(0);const s=[`${e.method??"GET"} ${e.url??"/"} HTTP/1.1`];for(const[a,i]of Object.entries(e.headers))if(a!=="host")for(const l of Array.isArray(i)?i:[i])l!==void 0&&s.push(`${a}: ${l}`);s.push(`host: ${t.host}`,"",""),o.write(s.join(`\r
|
|
2
|
+
`)),n.length>0&&o.write(n),o.pipe(r),r.pipe(o)});o.setTimeout(Dr,()=>o.destroy()),o.on("error",()=>r.destroy()),r.on("error",()=>o.destroy())},Nr=async e=>{const r=e.host??"127.0.0.1",n=r==="127.0.0.1"||r==="localhost"||r==="::1",t=new URL(e.workerOrigin);let o=x(e.logger,import.meta.url),s=M(import.meta.url);const a=ar({adminToken:n?cr(e.cwd):void 0,basePath:"/",dataEditable:n,rulesInstalled:re(e.cwd).installed,runAsIdentity:n,schemaEditable:n,scriptSrc:"/studio.js",styleHref:"/styles.css"}),i=(d,c,f,h,v)=>{yr(d,c,v,s)||(c.statusCode=200,c.setHeader("Content-Type",h),c.end(f))},l=(d,c,f)=>{d.statusCode=c,d.setHeader("Content-Type","text/plain"),d.end(f)},u=(d,c,f,h)=>{if(n){gr(d,c,f,e.cwd);return}l(c,403,h)},g=(d,c,f)=>{const h=d==="/styles.css",v=vr(d);if(!h&&!v)return!1;const w=M(import.meta.url);if(w!==s&&(o=x(e.logger,import.meta.url),s=w),o===void 0)return l(f,501,"Lunora studio assets not found — install and build @lunora/studio."),!0;if(h)return i(c,f,o.styles,"text/css; charset=utf-8","styles.css"),!0;const E=d.slice(d.lastIndexOf("/")+1),$=mr(E,import.meta.url);return $===void 0?(l(f,404,"Not found"),!0):(i(c,f,$,pr(E),E),!0)},m=Buffer.from(a),p=or((d,c)=>{const f=U(d.url??"/");if(n){const v=F(d,e.logger);if(v!==void 0){l(c,403,v);return}}if(f.startsWith(G)){if(!n){l(c,403,"Lunora studio: the worker admin proxy is only available on a loopback bind.");return}H(d,c,t);return}if(f===dr){u(d,c,wr,"Lunora schema editing is only available on loopback hosts in dev.");return}if(f===lr){u(d,c,hr,"Lunora policy scaffolding is only available on loopback hosts in dev.");return}if(f===ur){u(d,c,Sr,"Lunora data seeding is only available on loopback hosts in dev.");return}if(g(f,d,c))return;const h=d.headers["sec-fetch-mode"];if(n&&typeof h=="string"&&h!=="navigate"){H(d,c,t);return}fr(c,m)});return p.on("upgrade",(d,c,f)=>{if(!n||F(d,e.logger)!==void 0){c.destroy();return}U(d.url??"").startsWith(G)?Ar(d,c,f,t):c.destroy()}),await new Promise((d,c)=>{p.once("error",f=>{f.code==="EADDRINUSE"?c(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})):c(f)}),p.listen(e.port,r,()=>{d()})}),{close:()=>new Promise(d=>{p.close(()=>{d()})}),url:`http://${r==="0.0.0.0"||r==="::"?"localhost":r}:${String(e.port)}`}},$r=12e4,fe=250,Cr=1e4,xr=100,Mr=40,k=256*1024,Fr="LUNORA_DEV_READY_TIMEOUT_MS",jr=new Set(["nuxt","sveltekit"]),N=e=>jr.has(ne(e).framework)?"framework-worker":Re(e).has("@lunora/vite")?"vite":"wrangler",ge=e=>{const r=ie(e);let n;try{const t=Q(L(e,"package.json"),"utf8");n=JSON.parse(t).scripts?.dev}catch{}if(n===void 0||n.trim()===""||n.includes("lunora")){const t=_(r,"vite",["dev"]);return{args:t.args,command:t.command}}return{args:["run","dev"],command:r}},Gr=async e=>{try{return await fetch(new URL("/_lunora/status",e),{signal:AbortSignal.timeout(1e3)}),!0}catch{return!1}},Ur=e=>{Ee(Te(e.logPath),{recursive:!0});const r=Z(e.logPath,"w",384);let n;const t=le(e.command,e.args);try{n=z(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 n.unref(),{exited:new Promise(o=>{n.on("error",()=>{o(1)}),n.on("exit",(s,a)=>{o(s??(a?1:0))})}),pid:n.pid}},Vr=e=>{try{const{size:r}=ke(e);if(r<=k)return Q(e,"utf8");const n=Z(e,"r");try{const t=Buffer.alloc(k),o=be(n,t,0,k,r-k),s=t.toString("utf8",0,o);return s.slice(s.indexOf(`
|
|
3
|
+
`)+1)}finally{q(n)}}catch{return}},ve=(e,r)=>{const n=Vr(e);if(n===void 0)return[];const t=n.split(`
|
|
4
|
+
`);return t.at(-1)===""&&t.pop(),r>0?t.slice(-r):t},me=(e,r)=>r?.logFile??L(e,Ie),pe=e=>{e.info(" Stop: lunora dev stop"),e.info(" Status: lunora dev status"),e.info(" Logs: lunora dev logs")},T=(e,r)=>{e.warn(`Dev server already running at ${r.url} (pid ${String(r.pid)})`),pe(e)},Hr=(e,r)=>{e.success(`Dev server running at ${r.url} (pid ${String(r.pid)})`),r.studioUrl!==void 0&&e.info(` Studio: ${r.studioUrl}`),pe(e)},Wr=async e=>{const{child:r,cwd:n,deadline:t,pollIntervalMs:o,probe:s}=e,a=r.exited.then(i=>({exitCode:i}));for(;Date.now()<t;){const i=R(n);if(i!==void 0&&i.pid!==process.pid&&await s(i.url))return{state:i,status:"ready"};const l=await Promise.race([a,I(o)]);if(l!==void 0)return{...l,status:"exited"}}return{status:"timeout"}},Br=async e=>{const{cwd:r,logger:n}=e,t=me(r,void 0),o=Number(process.env[Fr]??""),s=e.readyTimeoutMs??(Number.isFinite(o)&&o>0?o:$r),a=(e.spawnDetached??Ur)({args:e.command.args,command:e.command.command,cwd:r,env:{...process.env,...e.env,[A]:"1",[oe]:t,...process.env.FORCE_COLOR===void 0?{NO_COLOR:"1"}:{},...e.json?{LUNORA_LOG_JSON:"1"}:{}},logPath:t}),i=await Wr({child:a,cwd:r,deadline:Date.now()+s,pollIntervalMs:e.pollIntervalMs??fe,probe:e.probe??Gr});if(i.status==="ready")return Hr(n,i.state),{code:0};if(i.status==="exited"){n.error(`dev server exited before becoming ready (exit code ${String(i.exitCode)}). Last output:`);for(const u of ve(t,Mr))n.error(` ${u}`);return S(r,a.pid??process.pid),{code:i.exitCode===0?1:i.exitCode}}a.pid!==void 0&&D(r)?.pid===process.pid&&se(r,{logFile:t,pid:a.pid});const l=a.pid===void 0?"":` (pid ${String(a.pid)})`;return n.warn(`dev server did not confirm ready within ${String(Math.round(s/1e3))}s — it may still be compiling${l}. Check \`lunora dev status\` and \`lunora dev logs\`; \`lunora dev stop\` shuts it down.`),{code:1}},we=e=>e.codegen!==!1&&!Oe(process.env[ae]),he=e=>{const r={...e.remote===!0?{LUNORA_REMOTE:"1"}:{},...e.codegen===!1?{[ae]:"0"}:{}};return Object.keys(r).length>0?{env:r}:{}},Kr=(e,r)=>{const n=["dev"];return 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.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),r&&n.push("--remote"),n},Jr=async e=>{const{cwd:r,jsonLogs:n,logger:t,options:o,remote:s}=e,a=e.run??Br,i=N(r),l=i==="wrangler"?`http://localhost:${String(o.workerPort??8787)}`:"http://localhost:5173",u=te(r,{background:!0,mode:"cli",pid:process.pid,startedAt:new Date().toISOString(),url:l});if(!u.ok)return u.existing!==void 0&&T(t,u.existing),{code:0};const g={[b]:String(process.pid)};try{return i==="vite"?await a({command:ge(r),cwd:r,env:{...he({...o,remote:s}).env,...g},json:n,logger:t}):await a({command:{args:[process.argv[1]??"lunora",...Kr(o,s)],command:process.execPath},cwd:r,env:g,json:n,logger:t})}finally{S(r,process.pid)}},Yr=e=>{try{const r=O("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=O)=>{if(n==="win32"){try{t("taskkill",["/pid",String(e.pid),"/T","/F"],{stdio:"ignore"})}catch{}return}if(e.background===!0)try{r(-(Yr(e.pid)??e.pid),"SIGKILL");return}catch{}try{r(e.pid,"SIGKILL")}catch{}},zr=(e,r)=>(r.json?y({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}),Qr=async e=>{const{cwd:r,logger:n}=e,t=D(r),o=e.alive??(m=>m===t?.pid&&_e(t)),s=e.signal??((m,p)=>{process.kill(m,p)}),a=e.pollIntervalMs??fe,i=e.stopGraceMs??Cr,l=e.platform??process.platform,u=e.spawnSyncImpl??O;if(t===void 0||!o(t.pid))return t!==void 0&&S(r,t.pid),e.json?y({running:!1,stopped:!1}):n.info("No dev server running."),{code:0};try{s(t.pid,"SIGTERM")}catch{}const g=Date.now()+i;for(;o(t.pid)&&Date.now()<g;)await I(a);return o(t.pid)&&(Xr(t,s,l,u),await I(a),o(t.pid))?zr(t,e):(S(r,t.pid),e.json?y({pid:t.pid,stopped:!0}):n.success(`Stopped dev server (pid ${String(t.pid)}).`),{code:0})},Zr=e=>{const{cwd:r,logger:n}=e,t=R(r);if(t===void 0)return e.json?y({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),a=Number.isFinite(s)?Math.max(0,Math.round((o()-s)/1e3)):void 0;if(e.json)return y({background:t.background===!0,logFile:t.logFile,mode:t.mode,pid:t.pid,running:!0,startedAt:t.startedAt,studioUrl:t.studioUrl,uptimeSeconds:a,url:t.url}),{code:0};const i=[`pid ${String(t.pid)}`,...a===void 0?[]:[`uptime ${String(a)}s`],t.background===!0?"background":"foreground"];return n.success(`Dev server running at ${t.url} (${i.join(", ")})`),t.studioUrl!==void 0&&n.info(` Studio: ${t.studioUrl}`),t.logFile!==void 0&&n.info(` Logs: lunora dev logs (${t.logFile})`),{code:0}},qr=e=>{const{cwd:r,logger:n}=e,t=D(r),o=me(r,t);if(!P(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??xr);return s.length>0&&process.stdout.write(`${s.join(`
|
|
5
5
|
`)}
|
|
6
|
-
`),{code:0}},
|
|
7
|
-
`);a=
|
|
6
|
+
`),{code:0}},et=e=>{const{cwd:r,json:n,lines:t,logger:o,subcommand:s}=e;if(s==="stop")return Qr({cwd:r,json:n,logger:o});if(s==="status")return Zr({cwd:r,json:n,logger:o});if(s==="logs")return qr({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:1}},W="wrangler.dev.jsonc",B=6173,Se=8787,K=5173,rt=5e3,tt=(e,r)=>{const n=()=>{};if(!e.remote)return{args:[],plan:{bindings:[],cleanup:n,enabled:!1}};const o=(e.materializeRemote??Xe)({enabled:!0,projectRoot:r}),s=o.remoteBindings.map(i=>`${i.binding} (${i.kind})`),{cleanup:a}=o;return o.configPath===void 0?{args:[],plan:{bindings:s,cleanup:a,enabled:!0,reason:o.reason}}:{args:["--config",o.configPath],plan:{bindings:s,cleanup:a,enabled:!0}}},J=e=>de(e).parsed?.dev?.ip,Y=(e,r,n)=>{if(n!==void 0){const o=L(e,n);if(P(o)&&J(o)!==void 0)return[]}const t=ce(e);return t!==void 0&&J(t)!==void 0?[]:r()?[]:["--ip","127.0.0.1"]},nt=async(e,r)=>{if(e.workerPort!==void 0)return e.workerPort;const n=ce(r);if(n!==void 0){const{parsed:t}=de(n);if(typeof t?.dev?.port=="number")return t.dev.port}return(e.findFreePort??Pr)(Se)},ot=e=>{const r=e.cwd??process.cwd(),n=ie(r),t=e.flavor??N(r);if(t==="vite"||t==="framework-worker"){const g=ge(r);let m;if(t==="framework-worker"){const p=Y(r,e.hasIpv6Loopback??j,W),d=Ve(He(r)).toolchain?.dev({configPath:W,extraArgs:[...p,"--var","WORKER_ENV:development"]}),c=_(n,d?.tool??"wrangler",d?.args??[]);m={args:c.args,command:c.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.`),{runsCodegenWatch:!1,flavor:t,ipv4LoopbackForced:!1,remote:{bindings:[],cleanup:()=>{},enabled:e.remote===!0},...m?{sidecar:m}:{},studioEnabled:!1,studioPort:e.port??B,workerEnabled:!0,workerOrigin:`http://localhost:${String(K)}`,workerPort:K,wrangler:{args:g.args,command:g.command,cwd:r,...he(e),tag:"vite"}}}const o=ne(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.`,a=e.workerPort??Se,i=tt(e,r),l=Y(r,e.hasIpv6Loopback??j),u=_(n,"wrangler",["dev","--port",String(a),...l,"--var","WORKER_ENV:development",...i.args]);return{runsCodegenWatch:we(e),flavor:t,frameworkHint:s,ipv4LoopbackForced:l.length>0,remote:i.plan,studioEnabled:e.studio!==!1,studioPort:e.port??B,workerEnabled:e.worker!==!1,workerOrigin:`http://localhost:${String(a)}`,workerPort:a,wrangler:{args:u.args,command:u.command,cwd:r,tag:"wrangler"}}},X=(e,r,n,t)=>{if(e.length===0)return;const o=Je(e);if(o){t[o.level](`[lunora] ${o.text}`);return}const s=`[${r}] ${e}`;n==="stderr"?t.warn(s):t.info(s)},st=(e,r,n)=>{const t=(o,s)=>{if(!o)return;let a="";o.on("data",i=>{a+=i.toString("utf8");const l=a.split(`
|
|
7
|
+
`);a=l.pop()??"";for(const u of l)X(u.trimEnd(),r,s,n)}),o.on("end",()=>{X(a.trimEnd(),r,s,n),a=""})};t(e.stdout,"stdout"),t(e.stderr,"stderr")},it=(e,r)=>{const n=le(e.command,e.args),t=z(n.command,n.args,{cwd:e.cwd??process.cwd(),env:e.env?{...process.env,...e.env}:process.env,shell:n.shell,stdio:["inherit","pipe","pipe"]});return st(t,e.tag,r),{exited:new Promise(o=>{t.on("error",s=>{r.error(`[${e.tag}] failed to start: ${s.message}`),o(1)}),t.on("exit",s=>{o(s??0)})}),kill:o=>{try{t.kill(o)}catch{}}}},at=(e,r,n)=>{e.info(""),e.success("Lunora dev"),e.info(` ➜ Worker: ${r.workerOrigin}`),n!==void 0&&e.info(` ➜ Studio: ${n}`),r.runsCodegenWatch&&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("")},ct=(e,r)=>{re(r).installed||!Ge()||(e.info(` ⓘ ${Ue}`),e.info(""))},dt=(e,r)=>{if(process.env.LUNORA_CONTAINER_LOGS==="0")return;const t=Be(e,"lunora").containers.map(o=>({className:o.className,exportName:o.exportName}));if(t.length!==0)return Ke({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})`)}})},lt=async e=>{await e.codegen?.close().catch(()=>{}),e.containerLogs?.close(),await e.studio?.close().catch(()=>{});try{e.remoteCleanup?.()}catch{}},ut=async(e,r)=>{try{const t=await Ne({projectRoot:r}),o=$e(t),s=(e.ensureExample??Ce)(r,o);s.length>0&&e.logger.info(`Updated .dev.vars.example with secrets for: ${o.join(", ")} (${s.join(", ")})`)}catch{}(await(e.ensureEnv??xe)({confirm:Er(),cwd:r,info:t=>{e.logger.info(t)}})).status==="declined"&&!Me()&&e.logger.info(`hint: ${C} was not scaffolded (non-interactive run). Copy ${Fe} → ${C} and fill in secrets, or run \`lunora dev\` in an interactive terminal to scaffold automatically.`);try{(e.fillSecrets??je)({cwd:r,info:t=>{e.logger.info(t)}})}catch{}},ft=(e,r,n,t)=>{if(e.flavor!=="wrangler")return;t!==void 0&&se(r,{studioUrl:t});let o;try{o=dt(r,n)}catch{}return at(n,e,t),o},gt=(e,r)=>{const n=Number(process.env[b]),t=te(r,{background:process.env[A]==="1",logFile:process.env[oe],mode:"cli",pid:process.pid,startedAt:new Date().toISOString(),url:e.workerOrigin},Number.isInteger(n)&&n>0?{supersedePid:n}:void 0);return t.ok?void 0:t.existing},vt=async e=>{const r=e.cwd??process.cwd(),n=e.flavor??N(r),t=n==="wrangler"?await nt(e,r):e.workerPort;return ot({...e,cwd:r,flavor:n,workerPort:t})},mt=async e=>await new Promise(r=>{const n={},t=a=>{e.info(`received ${a} — 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)}),pt=async(e,r,n)=>{let t=0,o;const s=u=>{e.kill(u),r?.kill(u)},a=()=>{t+=1,t===1?(n.info("received SIGINT — shutting down (press Ctrl-C again to force-kill)"),s("SIGTERM"),o=setTimeout(()=>{s("SIGKILL")},rt),o.unref()):s("SIGKILL")},i=()=>{s("SIGTERM")};process.on("SIGINT",a),process.on("SIGTERM",i);const l=await Promise.race([e.exited.then(u=>({code:u,who:"worker"})),...r===void 0?[]:[r.exited.then(u=>({code:u,who:"sidecar"}))]]);return r!==void 0&&(l.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",a),process.off("SIGTERM",i),l.code},wt=async(e,r,n,t)=>{if(r.studioEnabled)try{return await(e.startStudio??Nr)({cwd:n,logger:{warnOnce:o=>{t.warn(o)}},port:r.studioPort,workerOrigin:r.workerOrigin})}catch(o){t.warn(`studio server failed to start (${o instanceof Error?o.message:String(o)}) — continuing without it`);return}},ht=e=>{const r=[e.runsCodegenWatch?"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}.`},St=(e,r,n,t,o)=>{if(!(e.sidecar===void 0||!we(r)))try{ee({apiSpec:r.apiSpec,lunoraDirectory:"lunora",projectRoot:n,target:o})}catch(s){t.warn(`codegen (pre-sidecar) failed: ${s instanceof Error?s.message:String(s)} — the framework dev server will retry`)}},yt=async e=>{const r=await vt(e),{logger:n}=e,t=r.wrangler.cwd??process.cwd(),o=Ae(t,e.target),s={remoteCleanup:r.remote.cleanup};try{const a=Number(process.env[b]),i=R(t);if(i!==void 0&&i.pid!==process.pid&&i.pid!==a)return T(n,i),{code:0,plan:r};const l=gt(r,t);if(l!==void 0)return T(n,l),{code:0,plan:r};(r.flavor==="vite"||r.flavor==="framework-worker")&&(r.wrangler.env={...r.wrangler.env,[b]:String(process.pid)}),await ut(e,t),n.info(r.flavor==="vite"?"starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)":"starting wrangler dev + studio"),r.ipv4LoopbackForced&&n.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.runsCodegenWatch&&(s.codegen=(e.startCodegen??Rr)({apiSpec:e.apiSpec,jsonLogs:e.jsonLogs,logger:n,projectRoot:t,target:o})),s.studio=await wt(e,r,t,n),await s.codegen?.ready;const u=s.studio?.url;if(r.frameworkHint!==void 0&&n.warn(r.frameworkHint),!r.workerEnabled)return n.info(ht(r)),{code:await mt(n),plan:r};St(r,e,t,n,o);const g=e.startWorker??it,m=g(r.wrangler,n),p=r.sidecar===void 0?void 0:g(r.sidecar,n);return s.containerLogs=ft(r,t,n,u),ct(n,t),{code:await pt(m,p,n),plan:r}}finally{S(t,process.pid),await lt(s)}},Ut=qe(async({argument:e,cwd:r,logger:n,options:t})=>{const o=t.json===!0,s=et({cwd:r,json:o,lines:t.lines,logger:n,subcommand:e[0]});if(s!==void 0)return s;const a=process.env[A]==="1",i=a?void 0:Pe(),l=o||i!==void 0;l&&tr(),i!==void 0&&t.background!==!0&&n.info(`AI agent detected (${i.name} via ${i.variable}) — starting the dev server in background mode with JSON logs. Set LUNORA_AGENT_MODE=0 to opt out.`);const u=Ye({configPreference:De(r),envValue:process.env.LUNORA_REMOTE,flag:t.remote});if(!a&&(t.background===!0||i!==void 0)){const g=R(r);return g!==void 0?(T(n,g),{code:0}):Jr({cwd:r,jsonLogs:l,logger:n,options:t,remote:u})}return yt({apiSpec:ze(t.apiSpec),codegen:t.codegen===!1?!1:void 0,cwd:r,jsonLogs:l,logger:n,port:t.port,remote:u,studio:t.studio===!1?!1:void 0,target:t.target,workerPort:t.workerPort})});export{N as detectDevFlavor,Ut as execute,ot as planDevCommand,nt as resolveWorkerPort,yt as runDevCommand};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{runCodegen as A}from"@lunora/codegen";import{inferLunoraBindings as y}from"@lunora/config";import{collectWranglerSecretVariables as
|
|
2
|
-
↳ ${n.remediation}`);
|
|
1
|
+
import{runCodegen as A}from"@lunora/codegen";import{inferLunoraBindings as y}from"@lunora/config";import{collectWranglerSecretVariables as x,collectExportGaps as S}from"@lunora/config/cloudflare";import{r as k,e as T}from"../packem_shared/advisory-gate-Cl8IEFIj.mjs";import{p as b}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{d as R}from"../packem_shared/command-DwC11Cwe.mjs";import{r as D}from"../packem_shared/deploy-target-D9GPy_tq.mjs";import{l as f,v as C,p as E,i as j}from"../packem_shared/output-format-DfogXx0p.mjs";import{r as G}from"../packem_shared/platform-diagnostics-uOXqysk3.mjs";const p=3,O=e=>{const i=e.cwd??process.cwd(),o=j(e.format),r=f(e.format,e.logger),c=C("codegen",e.format);if(c!==void 0)return e.logger.error(c),{advisories:[],cronTriggers:[],error:c,failedAdvisories:0,outputDirectory:""};const g=k(e),a=D(i,e.target);if(a.target===void 0)return e.logger.error(a.error??"unknown deploy target"),{advisories:[],cronTriggers:[],error:a.error??"unknown deploy target",failedAdvisories:0,outputDirectory:""};const{target:v}=a,t=A({apiSpec:e.apiSpec,projectRoot:i,target:v,wranglerVariables:x(i)}),l={advisories:t.advisories.map(s=>({detail:s.detail,level:s.level,name:s.name,remediation:s.remediation})),cronTriggers:t.cronTriggers,failedAdvisories:0,outputDirectory:t.outputDirectory};if(r.success(`codegen wrote dataModel.ts, api.ts, server.ts to ${t.outputDirectory}`),t.advisories.length>0){const s=t.advisories.length,$=t.advisories.map(n=>` [${n.level}] ${n.name} — ${n.detail}
|
|
2
|
+
↳ ${n.remediation}`);r.warn(`${s.toString()} schema ${s===1?"advisory":"advisories"}:
|
|
3
3
|
${$.join(`
|
|
4
|
-
`)}`)}const m=G(t.platformDiagnostics,
|
|
4
|
+
`)}`)}const m=G(t.platformDiagnostics,r);m!==void 0&&(l.error=m);const{errorAdvisories:d,names:h,shouldBlock:w}=T(l.advisories,g);w&&r.error(`${d.length.toString()} ERROR-level ${d.length===1?"advisory":"advisories"} (${h.join(", ")}). Codegen wrote its output; this exit code is the gate. Pass --no-strict-advisories to downgrade it to a warning.`),t.cronTriggers.length>p&&r.warn(`${t.cronTriggers.length.toString()} distinct cron expressions declared — Cloudflare allows at most ${p.toString()} Cron Triggers per Worker. Consolidate schedules (jobs can share one expression) or move finer-grained work to Durable Object alarms via @lunora/scheduler (runAfter/runAt).`);const u={...l,failedAdvisories:g?d.length:0};return o&&E(u),u},F=async(e,i)=>{let o;try{o=S(await y({projectRoot:e}))}catch(r){i.warn(`could not check whether declared containers/workflows/agents are re-exported by the worker entry: ${r instanceof Error?r.message:String(r)}. Run \`lunora doctor\` to check explicitly.`);return}for(const r of o)i.warn(`${r.kind} "${r.exportName}" is declared but ${r.className} is not exported by the worker entry — add \`export * from "./lunora/_generated/${r.module}"\` so wrangler can provision its binding. Until then it deploys with nothing to run.`)},W=R(async({cwd:e,logger:i,options:o})=>{const r=O({apiSpec:b(o.apiSpec),cwd:e,format:o.format,logger:i,strictAdvisories:o.strictAdvisories,target:o.target});return r.outputDirectory!==""&&await F(e,f(o.format,i)),{code:r.error===void 0&&r.failedAdvisories===0?0:1}});export{W as execute,O as runCodegenCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as w,readFileSync as R}from"node:fs";import{runCodegen as F,discoverMigrations as O}from"@lunora/codegen";import{readLinkedProject as N,writeLinkedProject as H,resolveDeployDriver as A,inferLunoraBindings as T,DEV_VARS_FILE as k,parseDevVariableEntries as _,isMintableSecretKey as x,discoverContainerInfo as j,packageNamesFromBindings as M,requiredSecrets as W,generateSecretValue as V,upsertDevVariableLine as B,writeDevVariablesFileAtomically as K,discoverSchemaInfo as q}from"@lunora/config";import{validateWranglerProject as Y,reconcileWranglerBindings as G,reconcileWranglerCompatibilityDate as z,reconcileWranglerCrons as J,findWranglerFile as Q,readWranglerJsonc as X}from"@lunora/config/cloudflare";import{join as p}from"@visulima/path";import{Spinner as Z}from"@visulima/spinner";import{Project as ee}from"ts-morph";import{i as re,m as te,e as ae}from"../packem_shared/vectorize-metadata-DbziRKpX.mjs";import{r as ne,e as oe}from"../packem_shared/advisory-gate-Cl8IEFIj.mjs";import{p as ie}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{r as C}from"../packem_shared/wrangler-name-Dsk5K1f-.mjs";import{d as se}from"../packem_shared/command-DwC11Cwe.mjs";import{r as ce}from"../packem_shared/deploy-target-D9GPy_tq.mjs";import{e as b,d as $}from"../packem_shared/detect-package-manager-DSaAqRof.mjs";import{a as le,i as de}from"../packem_shared/docker-DpVxvYpL.mjs";import{p as ue,H as ge,a as me}from"../packem_shared/health-probe-QEPfFdgJ.mjs";import{v as fe,l as he,i as S,p as ve}from"../packem_shared/output-format-DfogXx0p.mjs";import{r as pe}from"../packem_shared/platform-diagnostics-uOXqysk3.mjs";import{r as ye}from"../packem_shared/post-codegen-hook-DRdEoTsQ.mjs";import{containerBuildTag as ke}from"@lunora/container";import{defaultSpawner as D}from"../packem_shared/createRecordingSpawner-DJlNacZ6.mjs";import{r as I}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{r as we}from"../packem_shared/schema-drift-gate-AscQpYH5.mjs";import{c as be}from"../packem_shared/tui-prompts-Dd0pDuNr.mjs";import{l as $e}from"../packem_shared/wrangler-secrets-CE7jcf8E.mjs";import{runMigrateDataCommand as Se}from"./runMigrateGenerateCommand.mjs";const De=/https?:\/\/[^\s"'<>]+\.workers\.dev[^\s"'<>]*/u,Re=/https:\/\/[^\s"'<>]+/u,Ae=e=>{if(e===void 0)return;const r=De.exec(e);return r?r[0]:Re.exec(e)?.[0]},P=e=>e===void 0?"":` (--env ${e})`,Ce=({cwd:e,env:r,logger:t,now:a,url:n})=>{if(n!==void 0)try{const o=N(e);if(o!==void 0){if(o.workerUrl===n&&o.env===r)return;t.warn(`link: .lunora/project.json records ${o.workerUrl??"(no url)"}${P(o.env)}, but this deploy published ${n}${P(r)}. Keeping the recorded value — run \`lunora link --url ${n}${r===void 0?"":` --env ${r}`}\` to update it.`);return}const i=(a??(()=>new Date().toISOString()))();H(e,{env:r,linkedAt:i,workerName:C(e),workerUrl:n}),t.success(`link: recorded ${n} in .lunora/project.json (run \`lunora link\` to change)`)}catch{}},Ee=e=>{const{cwd:r,env:t,logger:a,migrated:n,mintedSecretsFile:o}=e;try{const i=N(r),s=i?.workerName??C(r),l=e.url??i?.workerUrl;a.success("deploy complete"),a.info(` worker: ${s??"(see wrangler output above)"}`),t!==void 0&&a.info(` env: ${t}`),l===void 0?a.info(" url: run `lunora link --url <https://your-worker>` to record it"):a.info(` url: ${l}`),n&&a.info(" migrations: applied"),o!==void 0&&a.info(` secrets: generated value(s) recorded in ${o}`),a.info(" studio: lunora view --remote"),a.info(" logs: lunora logs")}catch{}},xe=async e=>{if(e.targets.length===0)return{builtTags:[],code:0};if(!(e.railpackAvailable??le)()){const a="deploy blocked: a container uses `image: { build }` (Railpack), but Railpack isn't ready. Install the `railpack` CLI and start a BuildKit instance, e.g. `docker run --rm --privileged -d --name buildkit moby/buildkit` then `export BUILDKIT_HOST=docker-container://buildkit`. Alternatively switch the container's `image` to a Dockerfile path or a pre-built registry reference.";return e.logger.error(a),{builtTags:[],code:1,error:a}}const r=e.spawner??D,t=[];for(const a of e.targets){const n=ke(a.exportName),o={args:["build",a.buildDir,"--name",n],command:"railpack",cwd:e.cwd},i=b($(e.cwd),"wrangler",["containers","push",n]),s={args:i.args,command:i.command,cwd:e.cwd};e.logger.info(`railpack: building "${a.exportName}" → ${n} from ${a.buildDir}`);const l=await r(o);if(l.code!==0){const c=`railpack build failed for container "${a.exportName}" (${a.buildDir})`;return e.logger.error(c),{builtTags:t,code:l.code,error:c}}e.logger.info(`railpack: pushing ${n} to the Cloudflare Registry`);const d=await r(s);if(d.code!==0){const c=`wrangler containers push failed for "${a.exportName}" (${n})`;return e.logger.error(c),{builtTags:t,code:d.code,error:c}}t.push(n)}return{builtTags:t,code:0}},U="<replace-with-d1-create-id>",E=e=>{const r=Q(e);return r?X(r).parsed:void 0},Pe=["LUNORA_ORIGIN_URL","LUNORA_WORKER_ORIGIN","AUTH_URL"],Le=e=>{try{const{hostname:r}=new URL(e);return r==="localhost"||r==="127.0.0.1"||r==="::1"||r==="[::1]"}catch{return!1}},Ne=e=>e.startsWith("./")||e.startsWith("../")||e.startsWith("/")||e.includes("Dockerfile"),Te=(e,r,t)=>{const a=(E(e)?.containers??[]).filter(o=>typeof o?.image=="string"&&Ne(o.image));if(a.length===0||t())return;const n=`deploy blocked: wrangler.jsonc declares ${String(a.length)} container(s) built from a local Dockerfile, but no Docker-compatible engine is available. Start Docker (or Colima), or point the container's \`image\` at a pre-built registry reference. Note: container images must target linux/amd64.`;return r.error(n),n},_e=e=>w(p(e,"src","worker.ts"))?"src/worker.ts":void 0,je=(e,r)=>{for(const t of j(e,"lunora").containers){const{image:a}=t;if(a.kind==="dockerfile"&&!w(p(e,a.dockerfilePath))){const n=`deploy blocked: container "${t.exportName}" references a Dockerfile at "${a.dockerfilePath}" that does not exist. Create it or fix the \`image\` path in lunora/containers.ts.`;return r.error(n),n}if(a.kind==="build"&&!w(p(e,a.buildDir))){const n=`deploy blocked: container "${t.exportName}" references a Railpack build directory "${a.buildDir}" that does not exist. Create it or fix the \`image.build\` path in lunora/containers.ts.`;return r.error(n),n}}},Ie=e=>S(e.format)?!1:e.interactive!==void 0?e.interactive:process.stdout.isTTY&&!process.env.CI,Ue=e=>(E(e)?.d1_databases??[]).find(r=>r.database_id===U)?.binding,Fe=async(e,r)=>{const t=j(e,"lunora").containers.filter(n=>n.image.kind==="build").map(n=>({buildDir:n.image.buildDir,exportName:n.exportName}));if(t.length===0)return;const a=await xe({cwd:e,logger:r.logger,railpackAvailable:r.railpackAvailable,spawner:r.spawner,targets:t});return a.code===0?void 0:a.error??"railpack build failed"},Oe=(e,r,t)=>{if(t!==void 0)try{const a=J(e,t);a.changed&&r.success(`synced ${String(t.length)} cron trigger(s) → ${a.wranglerPath??"wrangler.jsonc"}`)}catch(a){const n=a instanceof Error?a.message:String(a);r.warn(`cron trigger sync skipped: ${n}`)}},He=async(e,r,t,a,n)=>{try{A(a);const o=await T({projectRoot:e}),i=G(e,o,n);i.changed&&r.success(`provisioned bindings: ${i.added.join(", ")} → ${i.wranglerPath??"wrangler.jsonc"}`);for(const s of i.warnings)r.warn(s)}catch(o){const i=o instanceof Error?o.message:String(o);r.warn(`binding inference skipped: ${i}`)}try{const o=z(e);o.changed&&r.success(`bumped compatibility_date to ${o.date??"unknown"} (Workers Cache enabled) → ${o.wranglerPath??"wrangler.jsonc"}`)}catch(o){const i=o instanceof Error?o.message:String(o);r.warn(`compatibility date sync skipped: ${i}`)}Oe(e,r,t)},Me=(e,r)=>{const t=p(e,k);if(!w(t))return;let a;try{a=_(R(t,"utf8")).length}catch{return}a!==0&&r.warn(`Note: \`lunora deploy\` does not push secrets. ${k} has ${String(a)} key(s); if you changed them, run \`lunora env push --yes\` to update the deployed secrets.`)},We=async e=>{let r=[];try{r=M(await T({projectRoot:e}))}catch{}const t=W(r).map(n=>n.key);let a=[];try{const n=p(e,k);w(n)&&(a=_(R(n,"utf8")).map(o=>o.key).filter(o=>re(o)))}catch{}return[...new Set([...t,...a])]},Ve=async(e,r,t,a)=>{const{logger:n}=r,o=r.spawner??D,i=$(e),s=r.env===void 0?"":` --env ${r.env}`,{toolchain:l}=A(a);if(l===void 0)return n.error("deploy target has no command-line toolchain; cannot push secrets"),{minted:[],ok:!1};const d=[];for(const c of t){const m=V(),u=l.secretPut({environment:r.env,key:c,temporary:r.temporary}),g=b(i,u.tool,u.args),f=await o({args:g.args,command:g.command,cwd:e,input:m});if(f.code!==0)return n.error(`failed to push secret ${c} (exit ${String(f.code)}); set it manually with \`wrangler secret put ${c}${s}\`.`),{minted:d,ok:!1};d.push({key:c,value:m}),n.success(`generated + pushed ${c}`)}return{minted:d,ok:!0}},Be=/^[\w-]+$/u,Ke=(e,r,t)=>{if(t.length===0)return;const a=t.map(s=>s.key).join(", ");if(r.env!==void 0&&!Be.test(r.env)){r.logger.warn(`${a}: minted and pushed for --env ${r.env}, but "${r.env}" isn't a safe filename fragment — the value could not be recorded. Capture it manually if you need it again.`);return}const n=r.env===void 0?k:`${k}.${r.env}`,o=p(e,n);let i=w(o)?R(o,"utf8"):"";for(const s of t)i=B(i,s.key,s.value);return K(o,i),r.logger.success(r.env===void 0?`${a}: value(s) recorded in ${n}`:`${a}: value(s) recorded in ${n} (kept separate from ${k} so \`lunora dev\` and a plain \`lunora env push\` don't inherit the --env ${r.env} value)`),n},qe=async(e,r,t,a)=>{if(r.dryRun===!0||r.preview===!0)return{};const{logger:n}=r,o=r.env===void 0?"":` --env ${r.env}`;let i;try{i=await(r.secretLister??$e)({cwd:e,env:r.env,temporary:r.temporary})}catch{return{}}if(!i.ok)return{};const s=new Set(i.names),d=(await We(e)).filter(u=>!s.has(u));if(d.length===0)return{};if(!t)return{error:`missing required secret(s) on the deploy target: ${d.join(", ")}. Set them with \`wrangler secret put <KEY>${o}\` (or \`lunora env generate --set\` then \`lunora env push --yes${o}\`), then re-deploy.`};for(const u of d.filter(g=>!x(g)))n.warn(`required secret ${u} is not set on the target — set it with: wrangler secret put ${u}${o}`);const c=d.filter(u=>x(u));if(c.length===0)return{};if(await(r.secretConfirm??be())(`${String(c.length)} required secret(s) not set on the target (${c.join(", ")}). Generate strong values and push them now?`)){const{minted:u,ok:g}=await Ve(e,r,c,a),f=Ke(e,r,u);return g?{mintedSecretsFile:f}:{error:"failed to push required secret(s) — set them manually and re-deploy",mintedSecretsFile:f}}return n.warn(`${String(c.length)} required secret(s) not set on the target: ${c.join(", ")}. Generate + push with \`lunora env generate --set\` then \`lunora env push --yes${o}\`.`),{}},Ye=async(e,r)=>{const t=new ee({skipAddingFilesFromTsConfig:!0}),a=p(r,"lunora");let n;try{n=O(t,a)}catch(o){const i=o instanceof Error?o.message:String(o);return e.logger.warn(`--migrate: could not discover migrations (${i}); skipping`),0}if(n.length===0)return e.logger.info("--migrate: no data migrations declared in lunora/"),0;e.logger.info(`--migrate: running ${String(n.length)} migration(s) against deployed worker`);for(const o of n){e.logger.info(`--migrate: up "${o.id}" (table "${o.table}")`);const i={cwd:r,fetchImpl:e.fetchImpl,id:o.id,logger:e.logger,prod:!0,subcommand:"up",token:e.migrateToken,url:e.migrateUrl,yes:e.migrateYes===!0},s=await Se(i);if(s.code!==0)return e.logger.error(`--migrate: migration "${o.id}" failed — see output above`),s.code;e.logger.success(`--migrate: "${o.id}" applied`)}return 0},Ge=e=>{if(!e.migrate||e.dryRun||e.preview)return;if(e.migrateUrl===void 0){const t="--migrate requires --migrate-url <https://your-worker> — the deploy target URL is only known after wrangler runs, and this gate runs before it; refusing to default to localhost";return e.logger.error(t),t}if(e.migrateYes!==!0){const t="--migrate runs production data migrations after deploy. Re-run with --migrate-yes to confirm.";return e.logger.error(t),t}const r=e.migrateToken??process.env.LUNORA_ADMIN_TOKEN;if(r===void 0||r===""){const t="admin token required for --migrate — pass --migrate-token or set LUNORA_ADMIN_TOKEN";return e.logger.error(t),t}},ze=async(e,r,t,a,n,o,i,s)=>{let l;r?(l=new Z({name:"dots"}),l.start("running codegen")):t.info("running codegen");try{const d=F({apiSpec:a,projectRoot:e,target:n});l?.succeed("codegen complete"),l||t.success("codegen complete");const c=pe(d.platformDiagnostics,t);if(c!==void 0)return{error:c};const{errorAdvisories:m,names:u,shouldBlock:g}=oe(d.advisories,s);if(g){const v=`${m.length.toString()} ERROR-level ${m.length===1?"advisory":"advisories"} (${u.join(", ")}). Pass --no-strict-advisories to downgrade this to a warning and deploy anyway.`;return t.error(v),{error:v}}const f=await ye({cwd:e,logger:t,spawner:o,stdoutToStderr:i});return f.error!==void 0?(t.error(f.error),{error:f.error}):{result:d}}catch(d){l?.failed("codegen failed");const c=d instanceof Error?d.message:String(d);return t.error(`codegen failed: ${c}`),{error:`codegen failed: ${c}`}}},Je=(e,r)=>{const t=Ue(e);if(t===void 0)return;const a=`deploy blocked: the "${t}" D1 binding has a placeholder database_id ("${U}"). Run \`wrangler d1 create <name>\` to create the database, then replace the placeholder in wrangler.jsonc with the real id before deploying.`;return r.error(a),a},Qe=(e,r)=>{const t=E(e)?.vars;if(!t)return;const a=Pe.filter(o=>typeof t[o]=="string"&&Le(t[o]));if(a.length===0)return;const n=`deploy blocked: ${a.join(", ")} in wrangler.jsonc point at localhost. A deployed Worker can't reach a loopback address, so this silently breaks scheduled-job dispatch / auth callbacks. Set each to the deployed worker's public URL (or move it to a secret with \`wrangler secret put\`) before deploying.`;return r.error(n),n},Xe=async(e,r)=>{const{info:t}=q(r,"lunora"),a=t?.vectorMetadata??[];if(a.length===0)return;const n=[];for(const s of a){const l=te(s.kind);if(l===void 0){e.logger.warn(`vector index "${s.index}" declares metadata "${s.property}", whose column type cannot be filtered on in Vectorize — it is stored with each vector but no filter will match it.`);continue}n.push({index:s.index,property:s.property,type:l})}if(n.length===0)return;const i=(await ae({cwd:r,entries:n,exec:b($(r),"wrangler",[]),logger:e.logger,spawner:e.spawner??D})).filter(s=>s.status!=="failed").length;i>0&&e.logger.success(`vectorize metadata indexes ready: ${String(i)}/${String(n.length)}`)},L=5,Ze=2e3,er=async(e,r,t)=>{if(e.healthCheck!==!0)return;const a=t??I({cwd:r,env:e.env});if(a===void 0){const o="--health-check: the deploy succeeded, but no URL to probe could be resolved — wrangler's output carried none and this checkout has no link for this environment. Run `lunora link --url <https://your-worker>` and re-deploy, or drop --health-check.";return e.logger.error(o),{error:o,ok:!1,url:""}}const n=await ue({attempts:L,baseUrl:a,delayMs:Ze,fetchImpl:e.healthFetch,paths:[ge,me],sleep:e.healthSleep});return n.error===void 0?(e.logger.success(`health check ok (${n.url})`),{ok:!0,url:n.url}):(e.logger.error(`--health-check: the deploy succeeded, but the new version did not answer after ${String(L)} attempt(s) — ${n.error}`),{error:n.error,ok:!1,url:n.url})},rr=async(e,r,t,a,n,o)=>{await Xe(e,r);const i=e.migrate?await Ye(e,r):0;return i===0&&n?.(),{code:i,descriptor:t,mintedSecretsFile:o,validation:a}},tr=async(e,r)=>{const t=Je(e,r.logger);if(t!==void 0)return t;const a=Qe(e,r.logger);if(a!==void 0)return a;const n=je(e,r.logger);if(n!==void 0)return n;const o=Te(e,r.logger,r.dockerAvailable??de);return o!==void 0?o:Fe(e,r)},ar=(e,r,t)=>{const a=_e(e);a!==void 0&&r.logger.info(`class-B composition: deploying ${a} (overrides wrangler main)`),r.temporary&&r.logger.info("temporary account: deploying to a short-lived Cloudflare account (~60min); wrangler will print a claim URL"),r.dryRun&&r.logger.info("dry run: validating + bundling without publishing"),r.outDir!==void 0&&r.logger.info(`build artifact: emitting bundle to ${r.outDir}`);const n=A(t),o={dryRun:r.dryRun,entry:a,environment:r.env,outDir:r.outDir,preview:r.preview,temporary:r.temporary};if(n.toolchain===void 0)throw new Error(`deploy target "${n.id}" has no command-line toolchain`);return n.toolchain.deploy(o)},y=(e,r)=>({code:1,descriptor:void 0,error:e,validation:{problems:[],wranglerPath:void 0},...r}),nr=(e,r)=>{if(e.problems.length===0)return!1;r.error("wrangler.jsonc validation failed:");for(const t of e.problems)r.error(` - ${t}`);return!0},or=(e,r,t)=>{const a=S(r.format),n=r.dryRun!==!0,o=ar(e,r,t),i=b($(e),o.tool,o.args);return{args:i.args,captureStdout:n&&!a,captureStdoutSilently:n&&a,command:i.command,cwd:e,stdoutToStderr:a&&!n}},ir=async({cwd:e,descriptor:r,mintedSecretsFile:t,options:a,reblessSchemaBaseline:n,stdout:o,validation:i})=>{const s={deployedAt:new Date().toISOString(),dryRun:a.dryRun===!0,env:a.env,preview:a.preview===!0,url:a.dryRun===!0?void 0:Ae(o),workerName:C(e)};if(a.dryRun===!0||a.preview===!0)return a.healthCheck===!0&&a.logger.warn(`--health-check skipped: ${a.dryRun===!0?"a dry run publishes nothing":"a preview version serves no live traffic"}`),{code:0,deployment:s,descriptor:r,mintedSecretsFile:t,validation:i};a.temporary!==!0&&Ce({cwd:e,env:a.env,logger:a.logger,url:s.url});const l=await er(a,e,s.url);return l?.error!==void 0?{code:1,deployment:s,descriptor:r,healthCheck:l,mintedSecretsFile:t,validation:i}:{...await rr(a,e,r,i,n,t),deployment:s,healthCheck:l}},sr=async e=>{const r=e.cwd??process.cwd(),t=Ie(e),a=ne(e),n=ce(r,e.target);if(n.target===void 0){const h=n.error??"unknown deploy target";return e.logger.error(h),y(h)}const{target:o}=n;let i;if(!e.skipCodegen){const h=await ze(r,t,e.logger,e.apiSpec,o,e.spawner,S(e.format),a);if(h.error!==void 0)return y(h.error);i=h.result}let s;if(i!==void 0){const h=we({allowDrift:e.allowSchemaDrift===!0,codegen:i,command:"deploy",logger:e.logger,updateBaseline:e.updateSchemaBaseline===!0});if(h.blocked)return y("schema drift gate blocked deploy",{schemaDrift:{blocked:!0,reason:h.reason}});s=h.rebless}await He(r,e.logger,i?.cronTriggers,o,e.env);const l=Ge(e);if(l!==void 0)return y(l);const d=await tr(r,e);if(d!==void 0)return y(d);const c=Y({environment:e.env,projectRoot:r});if(nr(c,e.logger))return{code:1,descriptor:void 0,error:"wrangler validation failed",validation:c};Me(r,e.logger);const{error:m,mintedSecretsFile:u}=await qe(r,e,t,o);if(m!==void 0)return e.logger.error(m),{code:1,descriptor:void 0,error:m,mintedSecretsFile:u,validation:c};const g=or(r,e,o);e.logger.info(`deploying via ${g.command} ${g.args.join(" ")}`);const v=await(e.spawner??D)(g);return g.captureStdoutSilently===!0&&v.stdout!==void 0&&v.stdout!==""&&process.stderr.write(v.stdout),v.code!==0?{code:v.code,descriptor:g,mintedSecretsFile:u,validation:c}:ir({cwd:r,descriptor:g,mintedSecretsFile:u,options:e,reblessSchemaBaseline:s,stdout:v.stdout,validation:c})},cr=async e=>{const r=fe("deploy",e.format);if(r!==void 0)return e.logger.error(r),y(r);const t=await sr({...e,logger:he(e.format,e.logger)});if(S(e.format))return ve(t),t;if(t.code===0&&e.dryRun!==!0&&e.preview!==!0)Ee({cwd:e.cwd??process.cwd(),env:e.env,logger:e.logger,migrated:e.migrate===!0,mintedSecretsFile:t.mintedSecretsFile,url:t.deployment?.url});else if(t.code===0&&e.preview===!0){const a=t.deployment?.url;e.logger.success(a===void 0?"preview version uploaded":`preview version uploaded — ${a}`)}return t},jr=se(async({cwd:e,logger:r,options:t})=>({code:(await cr({allowSchemaDrift:t.allowSchemaDrift===!0,apiSpec:ie(t.apiSpec),cwd:e,dryRun:t.dryRun===!0,env:t.env,format:t.format,healthCheck:t.healthCheck===!0,logger:r,migrate:t.migrate===!0,migrateToken:t.migrateToken,migrateUrl:I({cwd:e,env:t.env,url:t.migrateUrl}),migrateYes:t.migrateYes===!0,preview:t.preview===!0,skipCodegen:t.prebuilt===!0,strictAdvisories:t.strictAdvisories,target:t.target,temporary:t.temporary===!0,updateSchemaBaseline:t.updateSchemaBaseline===!0})).code}));export{jr as execute,cr as runDeployCommand};
|
|
1
|
+
import{existsSync as w,readFileSync as R}from"node:fs";import{runCodegen as F,discoverMigrations as O}from"@lunora/codegen";import{readLinkedProject as N,writeLinkedProject as H,execArgsFor as b,detectPackageManager as $,runPostCodegenHook as M,resolveDeployDriver as A,inferLunoraBindings as T,DEV_VARS_FILE as k,parseDevVariableEntries as _,isMintableSecretKey as x,discoverContainerInfo as j,packageNamesFromBindings as W,requiredSecrets as V,generateSecretValue as B,upsertDevVariableLine as K,writeDevVariablesFileAtomically as q,discoverSchemaInfo as Y}from"@lunora/config";import{validateWranglerProject as G,reconcileWranglerBindings as z,reconcileWranglerCompatibilityDate as J,reconcileWranglerCrons as Q,findWranglerFile as X,readWranglerJsonc as Z}from"@lunora/config/cloudflare";import{join as p}from"@visulima/path";import{Spinner as ee}from"@visulima/spinner";import{Project as re}from"ts-morph";import{i as te,m as ae,e as ne}from"../packem_shared/vectorize-metadata-DbziRKpX.mjs";import{r as oe,e as ie}from"../packem_shared/advisory-gate-Cl8IEFIj.mjs";import{p as se}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{r as C}from"../packem_shared/wrangler-name-Dsk5K1f-.mjs";import{d as ce}from"../packem_shared/command-DwC11Cwe.mjs";import{r as le}from"../packem_shared/deploy-target-D9GPy_tq.mjs";import{a as de,i as ue}from"../packem_shared/docker-DpVxvYpL.mjs";import{p as ge,H as me,a as fe}from"../packem_shared/health-probe-QEPfFdgJ.mjs";import{v as he,l as ve,i as S,p as pe}from"../packem_shared/output-format-DfogXx0p.mjs";import{r as ye}from"../packem_shared/platform-diagnostics-uOXqysk3.mjs";import{containerBuildTag as ke}from"@lunora/container";import{defaultSpawner as D}from"../packem_shared/createRecordingSpawner-DJlNacZ6.mjs";import{r as I}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{r as we}from"../packem_shared/schema-drift-gate-AscQpYH5.mjs";import{c as be}from"../packem_shared/tui-prompts-Dd0pDuNr.mjs";import{l as $e}from"../packem_shared/wrangler-secrets-CCdt0mUl.mjs";import{runMigrateDataCommand as Se}from"./runMigrateGenerateCommand.mjs";const De=/https?:\/\/[^\s"'<>]+\.workers\.dev[^\s"'<>]*/u,Re=/https:\/\/[^\s"'<>]+/u,Ae=e=>{if(e===void 0)return;const r=De.exec(e);return r?r[0]:Re.exec(e)?.[0]},P=e=>e===void 0?"":` (--env ${e})`,Ce=({cwd:e,env:r,logger:t,now:a,url:n})=>{if(n!==void 0)try{const o=N(e);if(o!==void 0){if(o.workerUrl===n&&o.env===r)return;t.warn(`link: .lunora/project.json records ${o.workerUrl??"(no url)"}${P(o.env)}, but this deploy published ${n}${P(r)}. Keeping the recorded value — run \`lunora link --url ${n}${r===void 0?"":` --env ${r}`}\` to update it.`);return}const i=(a??(()=>new Date().toISOString()))();H(e,{env:r,linkedAt:i,workerName:C(e),workerUrl:n}),t.success(`link: recorded ${n} in .lunora/project.json (run \`lunora link\` to change)`)}catch{}},Ee=e=>{const{cwd:r,env:t,logger:a,migrated:n,mintedSecretsFile:o}=e;try{const i=N(r),s=i?.workerName??C(r),l=e.url??i?.workerUrl;a.success("deploy complete"),a.info(` worker: ${s??"(see wrangler output above)"}`),t!==void 0&&a.info(` env: ${t}`),l===void 0?a.info(" url: run `lunora link --url <https://your-worker>` to record it"):a.info(` url: ${l}`),n&&a.info(" migrations: applied"),o!==void 0&&a.info(` secrets: generated value(s) recorded in ${o}`),a.info(" studio: lunora view --remote"),a.info(" logs: lunora logs")}catch{}},xe=async e=>{if(e.targets.length===0)return{builtTags:[],code:0};if(!(e.railpackAvailable??de)()){const a="deploy blocked: a container uses `image: { build }` (Railpack), but Railpack isn't ready. Install the `railpack` CLI and start a BuildKit instance, e.g. `docker run --rm --privileged -d --name buildkit moby/buildkit` then `export BUILDKIT_HOST=docker-container://buildkit`. Alternatively switch the container's `image` to a Dockerfile path or a pre-built registry reference.";return e.logger.error(a),{builtTags:[],code:1,error:a}}const r=e.spawner??D,t=[];for(const a of e.targets){const n=ke(a.exportName),o={args:["build",a.buildDir,"--name",n],command:"railpack",cwd:e.cwd},i=b($(e.cwd),"wrangler",["containers","push",n]),s={args:i.args,command:i.command,cwd:e.cwd};e.logger.info(`railpack: building "${a.exportName}" → ${n} from ${a.buildDir}`);const l=await r(o);if(l.code!==0){const c=`railpack build failed for container "${a.exportName}" (${a.buildDir})`;return e.logger.error(c),{builtTags:t,code:l.code,error:c}}e.logger.info(`railpack: pushing ${n} to the Cloudflare Registry`);const d=await r(s);if(d.code!==0){const c=`wrangler containers push failed for "${a.exportName}" (${n})`;return e.logger.error(c),{builtTags:t,code:d.code,error:c}}t.push(n)}return{builtTags:t,code:0}},U="<replace-with-d1-create-id>",E=e=>{const r=X(e);return r?Z(r).parsed:void 0},Pe=["LUNORA_ORIGIN_URL","LUNORA_WORKER_ORIGIN","AUTH_URL"],Le=e=>{try{const{hostname:r}=new URL(e);return r==="localhost"||r==="127.0.0.1"||r==="::1"||r==="[::1]"}catch{return!1}},Ne=e=>e.startsWith("./")||e.startsWith("../")||e.startsWith("/")||e.includes("Dockerfile"),Te=(e,r,t)=>{const a=(E(e)?.containers??[]).filter(o=>typeof o?.image=="string"&&Ne(o.image));if(a.length===0||t())return;const n=`deploy blocked: wrangler.jsonc declares ${String(a.length)} container(s) built from a local Dockerfile, but no Docker-compatible engine is available. Start Docker (or Colima), or point the container's \`image\` at a pre-built registry reference. Note: container images must target linux/amd64.`;return r.error(n),n},_e=e=>w(p(e,"src","worker.ts"))?"src/worker.ts":void 0,je=(e,r)=>{for(const t of j(e,"lunora").containers){const{image:a}=t;if(a.kind==="dockerfile"&&!w(p(e,a.dockerfilePath))){const n=`deploy blocked: container "${t.exportName}" references a Dockerfile at "${a.dockerfilePath}" that does not exist. Create it or fix the \`image\` path in lunora/containers.ts.`;return r.error(n),n}if(a.kind==="build"&&!w(p(e,a.buildDir))){const n=`deploy blocked: container "${t.exportName}" references a Railpack build directory "${a.buildDir}" that does not exist. Create it or fix the \`image.build\` path in lunora/containers.ts.`;return r.error(n),n}}},Ie=e=>S(e.format)?!1:e.interactive!==void 0?e.interactive:process.stdout.isTTY&&!process.env.CI,Ue=e=>(E(e)?.d1_databases??[]).find(r=>r.database_id===U)?.binding,Fe=async(e,r)=>{const t=j(e,"lunora").containers.filter(n=>n.image.kind==="build").map(n=>({buildDir:n.image.buildDir,exportName:n.exportName}));if(t.length===0)return;const a=await xe({cwd:e,logger:r.logger,railpackAvailable:r.railpackAvailable,spawner:r.spawner,targets:t});return a.code===0?void 0:a.error??"railpack build failed"},Oe=(e,r,t)=>{if(t!==void 0)try{const a=Q(e,t);a.changed&&r.success(`synced ${String(t.length)} cron trigger(s) → ${a.wranglerPath??"wrangler.jsonc"}`)}catch(a){const n=a instanceof Error?a.message:String(a);r.warn(`cron trigger sync skipped: ${n}`)}},He=async(e,r,t,a,n)=>{try{A(a);const o=await T({projectRoot:e}),i=z(e,o,n);i.changed&&r.success(`provisioned bindings: ${i.added.join(", ")} → ${i.wranglerPath??"wrangler.jsonc"}`);for(const s of i.warnings)r.warn(s)}catch(o){const i=o instanceof Error?o.message:String(o);r.warn(`binding inference skipped: ${i}`)}try{const o=J(e);o.changed&&r.success(`bumped compatibility_date to ${o.date??"unknown"} (Workers Cache enabled) → ${o.wranglerPath??"wrangler.jsonc"}`)}catch(o){const i=o instanceof Error?o.message:String(o);r.warn(`compatibility date sync skipped: ${i}`)}Oe(e,r,t)},Me=(e,r)=>{const t=p(e,k);if(!w(t))return;let a;try{a=_(R(t,"utf8")).length}catch{return}a!==0&&r.warn(`Note: \`lunora deploy\` does not push secrets. ${k} has ${String(a)} key(s); if you changed them, run \`lunora env push --yes\` to update the deployed secrets.`)},We=async e=>{let r=[];try{r=W(await T({projectRoot:e}))}catch{}const t=V(r).map(n=>n.key);let a=[];try{const n=p(e,k);w(n)&&(a=_(R(n,"utf8")).map(o=>o.key).filter(o=>te(o)))}catch{}return[...new Set([...t,...a])]},Ve=async(e,r,t,a)=>{const{logger:n}=r,o=r.spawner??D,i=$(e),s=r.env===void 0?"":` --env ${r.env}`,{toolchain:l}=A(a);if(l===void 0)return n.error("deploy target has no command-line toolchain; cannot push secrets"),{minted:[],ok:!1};const d=[];for(const c of t){const m=B(),u=l.secretPut({environment:r.env,key:c,temporary:r.temporary}),g=b(i,u.tool,u.args),h=await o({args:g.args,command:g.command,cwd:e,input:m});if(h.code!==0)return n.error(`failed to push secret ${c} (exit ${String(h.code)}); set it manually with \`wrangler secret put ${c}${s}\`.`),{minted:d,ok:!1};d.push({key:c,value:m}),n.success(`generated + pushed ${c}`)}return{minted:d,ok:!0}},Be=/^[\w-]+$/u,Ke=(e,r,t)=>{if(t.length===0)return;const a=t.map(s=>s.key).join(", ");if(r.env!==void 0&&!Be.test(r.env)){r.logger.warn(`${a}: minted and pushed for --env ${r.env}, but "${r.env}" isn't a safe filename fragment — the value could not be recorded. Capture it manually if you need it again.`);return}const n=r.env===void 0?k:`${k}.${r.env}`,o=p(e,n);let i=w(o)?R(o,"utf8"):"";for(const s of t)i=K(i,s.key,s.value);return q(o,i),r.logger.success(r.env===void 0?`${a}: value(s) recorded in ${n}`:`${a}: value(s) recorded in ${n} (kept separate from ${k} so \`lunora dev\` and a plain \`lunora env push\` don't inherit the --env ${r.env} value)`),n},qe=async(e,r,t,a)=>{if(r.dryRun===!0||r.preview===!0)return{};const{logger:n}=r,o=r.env===void 0?"":` --env ${r.env}`;let i;try{i=await(r.secretLister??$e)({cwd:e,env:r.env,temporary:r.temporary})}catch{return{}}if(!i.ok)return{};const s=new Set(i.names),d=(await We(e)).filter(u=>!s.has(u));if(d.length===0)return{};if(!t)return{error:`missing required secret(s) on the deploy target: ${d.join(", ")}. Set them with \`wrangler secret put <KEY>${o}\` (or \`lunora env generate --set\` then \`lunora env push --yes${o}\`), then re-deploy.`};for(const u of d.filter(g=>!x(g)))n.warn(`required secret ${u} is not set on the target — set it with: wrangler secret put ${u}${o}`);const c=d.filter(u=>x(u));if(c.length===0)return{};if(await(r.secretConfirm??be())(`${String(c.length)} required secret(s) not set on the target (${c.join(", ")}). Generate strong values and push them now?`)){const{minted:u,ok:g}=await Ve(e,r,c,a),h=Ke(e,r,u);return g?{mintedSecretsFile:h}:{error:"failed to push required secret(s) — set them manually and re-deploy",mintedSecretsFile:h}}return n.warn(`${String(c.length)} required secret(s) not set on the target: ${c.join(", ")}. Generate + push with \`lunora env generate --set\` then \`lunora env push --yes${o}\`.`),{}},Ye=async(e,r)=>{const t=new re({skipAddingFilesFromTsConfig:!0}),a=p(r,"lunora");let n;try{n=O(t,a)}catch(o){const i=o instanceof Error?o.message:String(o);return e.logger.warn(`--migrate: could not discover migrations (${i}); skipping`),0}if(n.length===0)return e.logger.info("--migrate: no data migrations declared in lunora/"),0;e.logger.info(`--migrate: running ${String(n.length)} migration(s) against deployed worker`);for(const o of n){e.logger.info(`--migrate: up "${o.id}" (table "${o.table}")`);const i={cwd:r,fetchImpl:e.fetchImpl,id:o.id,logger:e.logger,prod:!0,subcommand:"up",token:e.migrateToken,url:e.migrateUrl,yes:e.migrateYes===!0},s=await Se(i);if(s.code!==0)return e.logger.error(`--migrate: migration "${o.id}" failed — see output above`),s.code;e.logger.success(`--migrate: "${o.id}" applied`)}return 0},Ge=e=>{if(!e.migrate||e.dryRun||e.preview)return;if(e.migrateUrl===void 0){const t="--migrate requires --migrate-url <https://your-worker> — the deploy target URL is only known after wrangler runs, and this gate runs before it; refusing to default to localhost";return e.logger.error(t),t}if(e.migrateYes!==!0){const t="--migrate runs production data migrations after deploy. Re-run with --migrate-yes to confirm.";return e.logger.error(t),t}const r=e.migrateToken??process.env.LUNORA_ADMIN_TOKEN;if(r===void 0||r===""){const t="admin token required for --migrate — pass --migrate-token or set LUNORA_ADMIN_TOKEN";return e.logger.error(t),t}},ze=async(e,r,t,a,n,o,i,s)=>{let l;r?(l=new ee({name:"dots"}),l.start("running codegen")):t.info("running codegen");try{const d=F({apiSpec:a,projectRoot:e,target:n});l?.succeed("codegen complete"),l||t.success("codegen complete");const c=ye(d.platformDiagnostics,t);if(c!==void 0)return{error:c};const{errorAdvisories:m,names:u,shouldBlock:g}=ie(d.advisories,s);if(g){const v=`${m.length.toString()} ERROR-level ${m.length===1?"advisory":"advisories"} (${u.join(", ")}). Pass --no-strict-advisories to downgrade this to a warning and deploy anyway.`;return t.error(v),{error:v}}const h=await M({cwd:e,logger:t,spawner:o,stdoutToStderr:i});return h.error!==void 0?{error:h.error}:{result:d}}catch(d){l?.failed("codegen failed");const c=d instanceof Error?d.message:String(d);return t.error(`codegen failed: ${c}`),{error:`codegen failed: ${c}`}}},Je=(e,r)=>{const t=Ue(e);if(t===void 0)return;const a=`deploy blocked: the "${t}" D1 binding has a placeholder database_id ("${U}"). Run \`wrangler d1 create <name>\` to create the database, then replace the placeholder in wrangler.jsonc with the real id before deploying.`;return r.error(a),a},Qe=(e,r)=>{const t=E(e)?.vars;if(!t)return;const a=Pe.filter(o=>typeof t[o]=="string"&&Le(t[o]));if(a.length===0)return;const n=`deploy blocked: ${a.join(", ")} in wrangler.jsonc point at localhost. A deployed Worker can't reach a loopback address, so this silently breaks scheduled-job dispatch / auth callbacks. Set each to the deployed worker's public URL (or move it to a secret with \`wrangler secret put\`) before deploying.`;return r.error(n),n},Xe=async(e,r)=>{const{info:t}=Y(r,"lunora"),a=t?.vectorMetadata??[];if(a.length===0)return;const n=[];for(const s of a){const l=ae(s.kind);if(l===void 0){e.logger.warn(`vector index "${s.index}" declares metadata "${s.property}", whose column type cannot be filtered on in Vectorize — it is stored with each vector but no filter will match it.`);continue}n.push({index:s.index,property:s.property,type:l})}if(n.length===0)return;const i=(await ne({cwd:r,entries:n,exec:b($(r),"wrangler",[]),logger:e.logger,spawner:e.spawner??D})).filter(s=>s.status!=="failed").length;i>0&&e.logger.success(`vectorize metadata indexes ready: ${String(i)}/${String(n.length)}`)},L=5,Ze=2e3,er=async(e,r,t)=>{if(e.healthCheck!==!0)return;const a=t??I({cwd:r,env:e.env});if(a===void 0){const o="--health-check: the deploy succeeded, but no URL to probe could be resolved — wrangler's output carried none and this checkout has no link for this environment. Run `lunora link --url <https://your-worker>` and re-deploy, or drop --health-check.";return e.logger.error(o),{error:o,ok:!1,url:""}}const n=await ge({attempts:L,baseUrl:a,delayMs:Ze,fetchImpl:e.healthFetch,paths:[me,fe],sleep:e.healthSleep});return n.error===void 0?(e.logger.success(`health check ok (${n.url})`),{ok:!0,url:n.url}):(e.logger.error(`--health-check: the deploy succeeded, but the new version did not answer after ${String(L)} attempt(s) — ${n.error}`),{error:n.error,ok:!1,url:n.url})},rr=async(e,r,t,a,n,o)=>{await Xe(e,r);const i=e.migrate?await Ye(e,r):0;return i===0&&n?.(),{code:i,descriptor:t,mintedSecretsFile:o,validation:a}},tr=async(e,r)=>{const t=Je(e,r.logger);if(t!==void 0)return t;const a=Qe(e,r.logger);if(a!==void 0)return a;const n=je(e,r.logger);if(n!==void 0)return n;const o=Te(e,r.logger,r.dockerAvailable??ue);return o!==void 0?o:Fe(e,r)},ar=(e,r,t)=>{const a=_e(e);a!==void 0&&r.logger.info(`class-B composition: deploying ${a} (overrides wrangler main)`),r.temporary&&r.logger.info("temporary account: deploying to a short-lived Cloudflare account (~60min); wrangler will print a claim URL"),r.dryRun&&r.logger.info("dry run: validating + bundling without publishing"),r.outDir!==void 0&&r.logger.info(`build artifact: emitting bundle to ${r.outDir}`);const n=A(t),o={dryRun:r.dryRun,entry:a,environment:r.env,outDir:r.outDir,preview:r.preview,temporary:r.temporary};if(n.toolchain===void 0)throw new Error(`deploy target "${n.id}" has no command-line toolchain`);return n.toolchain.deploy(o)},y=(e,r)=>({code:1,descriptor:void 0,error:e,validation:{problems:[],wranglerPath:void 0},...r}),nr=(e,r)=>{if(e.problems.length===0)return!1;r.error("wrangler.jsonc validation failed:");for(const t of e.problems)r.error(` - ${t}`);return!0},or=(e,r,t)=>{const a=S(r.format),n=r.dryRun!==!0,o=ar(e,r,t),i=b($(e),o.tool,o.args);return{args:i.args,captureStdout:n&&!a,captureStdoutSilently:n&&a,command:i.command,cwd:e,stdoutToStderr:a&&!n}},ir=async({cwd:e,descriptor:r,mintedSecretsFile:t,options:a,reblessSchemaBaseline:n,stdout:o,validation:i})=>{const s={deployedAt:new Date().toISOString(),dryRun:a.dryRun===!0,env:a.env,preview:a.preview===!0,url:a.dryRun===!0?void 0:Ae(o),workerName:C(e)};if(a.dryRun===!0||a.preview===!0)return a.healthCheck===!0&&a.logger.warn(`--health-check skipped: ${a.dryRun===!0?"a dry run publishes nothing":"a preview version serves no live traffic"}`),{code:0,deployment:s,descriptor:r,mintedSecretsFile:t,validation:i};a.temporary!==!0&&Ce({cwd:e,env:a.env,logger:a.logger,url:s.url});const l=await er(a,e,s.url);return l?.error!==void 0?{code:1,deployment:s,descriptor:r,healthCheck:l,mintedSecretsFile:t,validation:i}:{...await rr(a,e,r,i,n,t),deployment:s,healthCheck:l}},sr=async e=>{const r=e.cwd??process.cwd(),t=Ie(e),a=oe(e),n=le(r,e.target);if(n.target===void 0){const f=n.error??"unknown deploy target";return e.logger.error(f),y(f)}const{target:o}=n;let i;if(!e.skipCodegen){const f=await ze(r,t,e.logger,e.apiSpec,o,e.spawner,S(e.format),a);if(f.error!==void 0)return y(f.error);i=f.result}let s;if(i!==void 0){const f=we({allowDrift:e.allowSchemaDrift===!0,codegen:i,command:"deploy",logger:e.logger,updateBaseline:e.updateSchemaBaseline===!0});if(f.blocked)return y("schema drift gate blocked deploy",{schemaDrift:{blocked:!0,reason:f.reason}});s=f.rebless}await He(r,e.logger,i?.cronTriggers,o,e.env);const l=Ge(e);if(l!==void 0)return y(l);const d=await tr(r,e);if(d!==void 0)return y(d);const c=G({environment:e.env,projectRoot:r});if(nr(c,e.logger))return{code:1,descriptor:void 0,error:"wrangler validation failed",validation:c};Me(r,e.logger);const{error:m,mintedSecretsFile:u}=await qe(r,e,t,o);if(m!==void 0)return e.logger.error(m),{code:1,descriptor:void 0,error:m,mintedSecretsFile:u,validation:c};const g=or(r,e,o);e.logger.info(`deploying via ${g.command} ${g.args.join(" ")}`);const v=await(e.spawner??D)(g);return g.captureStdoutSilently===!0&&v.stdout!==void 0&&v.stdout!==""&&process.stderr.write(v.stdout),v.code!==0?{code:v.code,descriptor:g,mintedSecretsFile:u,validation:c}:ir({cwd:r,descriptor:g,mintedSecretsFile:u,options:e,reblessSchemaBaseline:s,stdout:v.stdout,validation:c})},cr=async e=>{const r=he("deploy",e.format);if(r!==void 0)return e.logger.error(r),y(r);const t=await sr({...e,logger:ve(e.format,e.logger)});if(S(e.format))return pe(t),t;if(t.code===0&&e.dryRun!==!0&&e.preview!==!0)Ee({cwd:e.cwd??process.cwd(),env:e.env,logger:e.logger,migrated:e.migrate===!0,mintedSecretsFile:t.mintedSecretsFile,url:t.deployment?.url});else if(t.code===0&&e.preview===!0){const a=t.deployment?.url;e.logger.success(a===void 0?"preview version uploaded":`preview version uploaded — ${a}`)}return t},Tr=ce(async({cwd:e,logger:r,options:t})=>({code:(await cr({allowSchemaDrift:t.allowSchemaDrift===!0,apiSpec:se(t.apiSpec),cwd:e,dryRun:t.dryRun===!0,env:t.env,format:t.format,healthCheck:t.healthCheck===!0,logger:r,migrate:t.migrate===!0,migrateToken:t.migrateToken,migrateUrl:I({cwd:e,env:t.env,url:t.migrateUrl}),migrateYes:t.migrateYes===!0,preview:t.preview===!0,skipCodegen:t.prebuilt===!0,strictAdvisories:t.strictAdvisories,target:t.target,temporary:t.temporary===!0,updateSchemaBaseline:t.updateSchemaBaseline===!0})).code}));export{Tr as execute,cr as runDeployCommand};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{existsSync as m,mkdirSync as M,writeFileSync as v,readFileSync as b,readdirSync as re,rmSync as $,mkdtempSync as oe,cpSync as G,renameSync as Re,lstatSync as Oe}from"node:fs";import{tmpdir as se}from"node:os";import{
|
|
1
|
+
import{existsSync as m,mkdirSync as M,writeFileSync as v,readFileSync as b,readdirSync as re,rmSync as $,mkdtempSync as oe,cpSync as G,renameSync as Re,lstatSync as Oe}from"node:fs";import{tmpdir as se}from"node:os";import{installArgsFor as ne,execArgsFor as Te,detectFramework as Pe,isInteractive as g,LUNA_ART as De,LUNA_NAME as Be,LUNA_SIGNOFF as je,BADGES as f,paintAnswer as Ve,detectPackageManager as V,detectInstalledManagers as Ue,runScriptCommand as Fe,detectLintTools as He,addArgsFor as We,applyLintIgnores as ze}from"@lunora/config";import{walkSync as qe}from"@visulima/fs";import{join as u,dirname as A,basename as E,resolve as ie,relative as Ke}from"@visulima/path";import{downloadTemplate as le}from"giget";import{modify as Ge,applyEdits as Je}from"jsonc-parser";import{join as U,dirname as Ye}from"node:path";import{d as Xe}from"../packem_shared/command-DwC11Cwe.mjs";import Qe from"magic-string";import{Project as Ze,SyntaxKind as w}from"ts-morph";import{P as F}from"../packem_shared/prompt-cancelled-C8Sj7OqI.mjs";import{h as ce,d as de,i as ue,b as et}from"../packem_shared/resolve-DZAGzg67.mjs";import{defaultSpawner as pe}from"../packem_shared/createRecordingSpawner-DJlNacZ6.mjs";import{d as tt,e as at,f as rt,a as he,t as H,g as me,h as ot,b as fe,i as ve,w as st,j as nt,k as ge,l as it}from"../packem_shared/tui-prompts-Dd0pDuNr.mjs";import{E as we,p as lt,w as ct,M as dt,r as ut,k as pt,f as ht,c as ye,l as be,e as mt,g as ft}from"../packem_shared/lint-ignore-report-ajVhvqY-.mjs";import{logStep as J}from"../packem_shared/createLogger-hGafwf-3.mjs";import vt from"node:dns/promises";import{r as gt}from"../packem_shared/commands-BNKj7r1R.mjs";const W={bun:{githubCacheKey:void 0,githubSetupStep:` - uses: oven-sh/setup-bun@v2
|
|
2
2
|
`,gitlabImage:"oven/bun:1",gitlabProvisionScript:[],installCmd:"bun install --frozen-lockfile",lockfile:"bun.lockb"},npm:{githubCacheKey:"npm",githubSetupStep:"",gitlabImage:"node:22",gitlabProvisionScript:[],installCmd:"npm ci",lockfile:"package-lock.json"},pnpm:{githubCacheKey:"pnpm",githubSetupStep:` - uses: pnpm/action-setup@v4
|
|
3
|
-
`,gitlabImage:"node:22",gitlabProvisionScript:["corepack enable"],installCmd:"pnpm install --frozen-lockfile",lockfile:"pnpm-lock.yaml"},yarn:{githubCacheKey:"yarn",githubSetupStep:"",gitlabImage:"node:22",gitlabProvisionScript:["corepack enable"],installCmd:"yarn install --frozen-lockfile",lockfile:"yarn.lock"}},C=(e,t)=>{const a=
|
|
3
|
+
`,gitlabImage:"node:22",gitlabProvisionScript:["corepack enable"],installCmd:"pnpm install --frozen-lockfile",lockfile:"pnpm-lock.yaml"},yarn:{githubCacheKey:"yarn",githubSetupStep:"",gitlabImage:"node:22",gitlabProvisionScript:["corepack enable"],installCmd:"yarn install --frozen-lockfile",lockfile:"yarn.lock"}},C=(e,t)=>{const a=Te(e,"lunora",t);return[a.command,...a.args].join(" ")},Y=e=>` - uses: actions/checkout@v4
|
|
4
4
|
${e.githubSetupStep} - uses: actions/setup-node@v4
|
|
5
5
|
with:
|
|
6
6
|
node-version: 22
|
|
@@ -82,16 +82,16 @@ preview:
|
|
|
82
82
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
83
83
|
script:
|
|
84
84
|
- ${C(e,["deploy","--preview"])}
|
|
85
|
-
`},xe={github:{file:
|
|
85
|
+
`},xe={github:{file:U(".github","workflows","deploy.yml"),secretsHint:"repository secrets (Settings → Secrets and variables → Actions)"},gitlab:{file:".gitlab-ci.yml",secretsHint:"masked CI/CD variables (Settings → CI/CD → Variables)"}},bt=(e,t)=>e==="github"?wt(t):yt(t),xt=e=>e==="github"||e==="gitlab",kt=(e,t,a,r={})=>{const o=U(e,xe[t].file);return m(o)&&r.overwrite!==!0?{path:o,skipped:!0,written:!1}:(M(Ye(o),{recursive:!0}),v(o,bt(t,a),"utf8"),{path:o,skipped:!1,written:!0})},Ct=(e,t,a,r,o={})=>{const s=xe[t];try{const n=kt(e,t,a,o);if(n.skipped)r.info(`--ci ${t}: ${s.file} already exists — left unchanged (re-run with overwrite to replace).`);else{const i=ne(a),l=[i.command,...i.args].join(" "),{installCmd:d,lockfile:c}=W[a];r.success(`--ci ${t}: wrote ${s.file}`),r.info(`--ci ${t}: set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as ${s.secretsHint} to enable deploys.`),r.info(`--ci ${t}: run \`${l}\` and commit ${c} before pushing — the pipeline runs \`${d}\`.`)}return n}catch(n){const i=n instanceof Error?n.message:String(n);return r.warn(`--ci ${t}: could not write ${s.file} (${i})`),{path:U(e,s.file),skipped:!1,written:!1}}},St={astro:"@lunora/react",none:"@lunora/react",nuxt:"@lunora/vue","react-router":"@lunora/react","solid-start":"@lunora/solid",sveltekit:"@lunora/svelte","tanstack-start":"@lunora/react","tanstack-start-solid":"@lunora/solid",vinext:"@lunora/react"},Lt=e=>{const t=Pe(e);return{...t,adapter:St[t.framework]}},_="lunora()",X='import { lunora } from "@lunora/vite";',Mt=/\blunora\s*\(/u,At='"@lunora/vite"',Et="'@lunora/vite'",Nt=e=>{for(const t of e.getStatements()){if(t.getKind()!==w.ExportAssignment)continue;const a=t.asKindOrThrow(w.ExportAssignment).getExpression();if(a.getKind()!==w.CallExpression)continue;const r=a.asKindOrThrow(w.CallExpression);if(r.getExpression().getText()!=="defineConfig")continue;const o=r.getArguments()[0];if(o?.getKind()===w.ObjectLiteralExpression)return o.asKindOrThrow(w.ObjectLiteralExpression)}},It=e=>{for(const t of e.getStatements()){if(t.getKind()!==w.ExportAssignment)continue;const a=t.asKindOrThrow(w.ExportAssignment).getExpression();if(a.getKind()===w.ObjectLiteralExpression)return a.asKindOrThrow(w.ObjectLiteralExpression)}},_t=e=>new Ze({compilerOptions:{allowJs:!0},useInMemoryFileSystem:!0}).createSourceFile("vite.config.ts",e,{overwrite:!0}),$t=(e,t)=>{const a=t.getImportDeclarations().at(-1)?.getEnd()??0;a===0?e.prepend(`${X}
|
|
86
86
|
`):e.appendLeft(a,`
|
|
87
87
|
${X}`)},Rt=(e,t)=>{const a=t.getProperty("plugins");if(a===void 0){const s=t.getProperties(),n=t.getStart()+1;if(s.length===0)e.appendLeft(n,` plugins: [${_}] `);else{const i=s[0];i!==void 0&&e.appendLeft(i.getStart(),`plugins: [${_}],
|
|
88
88
|
`)}return}const r=a.getDescendantsOfKind(w.ArrayLiteralExpression)[0];if(r===void 0)return;const o=r.getElements();if(o.length===0)e.appendLeft(r.getStart()+1,_);else{const s=o[0];s!==void 0&&e.appendLeft(s.getStart(),`${_}, `)}},ke=e=>{if(Mt.test(e))return{changed:!1,code:e,reason:"lunora plugin already present"};const t=_t(e),a=Nt(t)??It(t);if(a===void 0)return{changed:!1,code:e,reason:"could not locate a Vite config plugins array to patch"};const r=new Qe(e);return!e.includes(At)&&!e.includes(Et)&&$t(r,t),Rt(r,a),{changed:!0,code:r.toString()}},Ot=/enotfound|eai_again|econnrefused|etimedout|network|fetch failed|getaddrinfo/u,Tt=/404|not found|could not find|no such/u,Pt=(e,t)=>{const a=e instanceof Error?e.message:String(e),r=a.toLowerCase(),o=`failed to download template "${t.templateType}" from ${t.remote}: ${a}`;return Tt.test(r)?{hints:[`Check the template name "${t.templateType}" and the ref "${t.ref}".`,"List/inspect available templates, or target a branch/tag with `--ref <branch>`."],message:`template "${t.templateType}" not found at ${t.remote}: ${a}`}:Ot.test(r)?{hints:["You appear to be offline or unable to reach GitHub.","To scaffold without a network, point at a local template root: `lunora init --from <dir>`."],message:o}:{hints:["If this is a network/offline issue, scaffold from a local root with `lunora init --from <dir>`."],message:o}},R=async(e,t,a)=>{if(g()){await at(f[e],t,a);return}if(process.stdout.write(`
|
|
89
89
|
`),a===void 0||a===""){J(e,t);return}const r=a.split(`
|
|
90
|
-
`).map(o=>
|
|
90
|
+
`).map(o=>Ve(o)).join(`
|
|
91
91
|
`);J(e,`${t}
|
|
92
92
|
${r}`)},Dt=async e=>{if(g()){await tt();return}e.info(`
|
|
93
|
-
${
|
|
94
|
-
${
|
|
93
|
+
${De}
|
|
94
|
+
${Be}: ${je}`)},Ce=[{description:"LLMs via Workers AI (summarize, generate, stream)",label:"AI",value:"ai"},{description:"Sign-up / sign-in (asks which provider)",label:"Authentication",value:"auth"},{description:"Copy-in auth screens for your framework (sign in/up, reset, 2FA)",label:"Auth UI",value:"auth-ui"},{description:"Snapshot + restore your Durable Object data",label:"Backups",value:"backup"},{description:"Headless browser screenshots + PDFs",label:"Browser rendering",value:"browser"},{description:"Zero Trust identity via Cloudflare Access",label:"Cloudflare Access",value:"cloudflare-access"},{description:"Scheduled jobs via Cron Triggers (@lunora/scheduler)",label:"Cron jobs",value:"crons"},{description:"Cloudflare Email Workers + a dev mail catcher",label:"Transactional email",value:"email"},{description:"OpenFeature feature flags (ctx.flags)",label:"Feature flags",value:"flags"},{description:"External Postgres/MySQL via Hyperdrive",label:"Hyperdrive",value:"hyperdrive"},{description:"Stripe-first payments (checkout, subscription, webhooks)",label:"Payments",value:"payment"},{description:"Live presence / who's-online over hibernated WebSockets",label:"Presence",value:"presence"},{description:"Async message queues (push/pull consumers)",label:"Queues",value:"queue"},{description:"Typed R2 buckets + signed URLs (@lunora/storage)",label:"File storage",value:"storage"},{description:"Durable long-running workflows (step.do, sleep, branch)",label:"Workflows",value:"workflow"}],Q=Ce.map(e=>e.value),Bt=e=>e==="email"?we:e,jt=(e,t)=>{const a=[];for(const r of e.split(",").map(o=>o.trim()).filter(Boolean))Q.includes(r)?a.includes(r)||a.push(r):t(`init: unknown --add feature "${r}" — expected ${Q.join(" | ")}; skipping.`);return a},Vt=async e=>{const t=await ht(e.select),a=await ye(e.text,e.projectName);return{label:"auth",names:[t],transformManifest:r=>be(r,a)}},Ut=async e=>{const t=await e.text(dt,{placeholder:"you@yourdomain.com"}),a=ut(t,r=>{e.logger.warn(r)});return{label:"email",names:[we],transformManifest:a===void 0?void 0:r=>pt(r,a)}},Ft=async(e,t)=>{const a=e.resolveAuthUiItem?.()??"auth-ui-react";if(t)return{label:"auth-ui",names:[a]};const r=await ye(e.text,e.projectName);return{label:"auth-ui",names:[a],transformManifest:o=>be(o,r)}},Ht=async e=>{const t=await lt(e.text,e.projectName);return{label:"storage",names:["storage"],transformManifest:a=>ct(a,t)}},Wt={auth:Vt,"auth-ui":async(e,t)=>Ft(e,t.includes("auth")),email:Ut,storage:Ht},Z=async e=>{if(e.preselected!==void 0&&e.preselected.length>0){await e.applyAll(e.preselected.map(r=>{const o=r==="auth-ui"?e.resolveAuthUiItem?.()??"auth-ui-react":Bt(r);return{label:r,names:[o]}}));return}if(!e.interactive){e.logger.info("tip: add features later with `lunora add <ai|auth|backup|browser|cloudflare-access|crons|email|flags|hyperdrive|payment|presence|queue|storage|workflow>`.");return}const t=await e.multiSelect("Which features do you want to add?",Ce,{defaults:[]});if(t.length===0)return;const a=[];for(const r of t){const o=Wt[r];a.push(o?await o(e,t):{label:r,names:[r]})}await e.applyAll(a)},zt=[{description:"Adds an ignores entry to eslint.config.js",label:"ESLint",value:"eslint"},{description:"Writes .prettierignore",label:"Prettier",value:"prettier"},{description:"Adds negated patterns to biome.json",label:"Biome",value:"biome"},{description:"Writes ignorePatterns to .oxlintrc.json",label:"oxlint (oxc)",value:"oxlint"}],qt="Which linter/formatter do you use? We'll tell it to skip Lunora's generated files.",Kt=async e=>{const t=e.interactive?await e.multiSelect(qt,zt,{defaults:e.detected}):[...e.detected];if(t.length===0)return[];const a=e.apply(t);return mt(a,e.logger),a},N="M 259.500 10.552 C 220.080 15.859, 182.424 32.566, 152.500 58.025 C 110.179 94.031, 85.380 137.183, 77.518 188.500 C 75.410 202.255, 74.569 225.677, 75.796 236.466 C 76.757 244.917, 76.683 245.692, 74.518 249.966 C 63.118 272.466, 53.141 303.876, 51.382 322.799 L 50.718 329.943 71.960 320.471 C 83.643 315.262, 93.326 311, 93.478 311 C 93.630 311, 96.547 316.063, 99.959 322.250 C 103.371 328.438, 107.249 334.850, 108.577 336.500 L 110.990 339.500 110.981 336 C 110.977 334.075, 111.499 324.991, 112.143 315.813 L 113.312 299.127 121.406 293.336 C 132.495 285.403, 149.593 271.554, 161 261.268 C 171.556 251.748, 189.116 235, 188.540 235 C 188.337 235, 183.069 238.648, 176.835 243.106 C 142.318 267.789, 68.537 314, 63.646 314 C 61.843 314, 72.791 281.179, 80.905 262.259 C 92.233 235.845, 107.473 212.389, 132.106 183.453 L 138.451 176 148.268 176 C 176.192 176, 197.512 187.154, 212.868 209.797 C 216.470 215.108, 217.035 216.595, 216.477 219.297 C 211.386 243.968, 202.359 274.496, 193.797 296 C 183.898 320.861, 167.147 352.101, 152.395 373.215 L 147.004 380.930 152.891 385.830 C 161.400 392.911, 165.563 396, 166.594 395.998 C 167.092 395.998, 168.772 391.641, 170.327 386.317 C 176.279 365.934, 188.422 338.749, 200.942 317.778 C 223.060 280.731, 256.432 244.369, 294.500 215.836 C 309.956 204.252, 313.937 201.603, 314.719 202.385 C 315.116 202.783, 315.449 213.096, 315.460 225.304 C 315.474 241.855, 315.021 250.405, 313.680 258.924 C 307.009 301.272, 291.175 336.677, 263.112 372 C 255.259 381.883, 227.182 410.673, 218.516 417.727 L 213.532 421.783 223.439 424.880 C 281.705 443.093, 349.165 436.018, 398.616 406.508 C 446.728 377.797, 483.322 331.466, 497.366 281.481 C 503.381 260.075, 504.480 250.741, 504.491 221 C 504.501 191.997, 503.598 184.047, 497.987 163.732 C 484.768 115.871, 452.505 72.708, 407.718 42.964 C 381.051 25.254, 352.818 14.828, 319.695 10.460 C 305.932 8.645, 273.298 8.695, 259.500 10.552",L=`/* The welcome page is a full-bleed starter — reset the browser's default
|
|
95
95
|
body margin/padding so it sits flush; everything else is scoped under
|
|
96
96
|
.lunora-welcome (collision-safe). */
|
|
97
97
|
body {
|
|
@@ -1221,8 +1221,8 @@ export const send = mutation
|
|
|
1221
1221
|
.mutation(async ({ args }) => {
|
|
1222
1222
|
return { channelId: args.channelId, text: args.text };
|
|
1223
1223
|
});
|
|
1224
|
-
`,Na="gh:anolilab/lunora/templates",Ia=e=>{const t=e.lastIndexOf(".");return t===-1?!1:La.has(e.slice(t))},_a=(e,t)=>e.replaceAll("{{name}}",t),$a=async(e,t)=>{const a=new Set;for(const r of e)if(E(r)==="package.json")try{const o=JSON.parse(b(r,"utf8"));for(const s of["dependencies","devDependencies"])for(const n of Object.keys(o[s]??{}))P(n)&&a.add(n)}catch{}return ce(a,t)},Ra=(e,t,a)=>{let r;try{r=JSON.parse(e)}catch{return e}let o=e;for(const s of["dependencies","devDependencies"])for(const n of Object.keys(r[s]??{})){if(!P(n))continue;const i=a.get(n)??t,l=
|
|
1225
|
-
`),q=e=>{const t=[];for(const a of
|
|
1226
|
-
`),e.success(`scaffolded ${String(t.length)} files into ${a}`)},Ba=(e,t)=>{const{args:a,command:r}=
|
|
1227
|
-
`))},Ha=e=>e.yes!==!0&&(e.installPrompt!==void 0||g()),Wa=async(e,t)=>{if(!Ha(e)||Ie(A(t)))return;const a=
|
|
1228
|
-
`),t.info(p?.commit?`template: ${p.source} @ ${p.commit} (${String(I.length)} files)`:`template: ${p?.source??c} (${String(I.length)} files)`),K(t,h,s),{code:0,files:h,target:s}}catch(d){if(d instanceof F)throw d;const{hints:c,message:p}=Pt(d,{ref:ue(r),remote:B(n,o,r),templateType:n});t.error(p);for(const h of c)t.warn(h);return{code:1,files:[],target:s}}finally{$(i,{force:!0,recursive:!0})}},Ka=e=>{for(const t of["_gitignore","_npmrc","_gitattributes"]){const a=u(e,t);m(a)&&Re(a,u(e,`.${t.slice(1)}`))}},Ga=async e=>{const{framework:t,logger:a,name:r,overlayBaseFrom:o,target:s}=e,n=T[t],i=oe(u(se(),"lunora-vite-base-"));try{let l;if(o!==void 0&&(l=u(o,`template-${n.createViteTemplate}`),!m(l)))return a.error(`create-vite base not found on disk: ${l}`),{code:1,files:[],target:s};const d=async()=>{if(l!==void 0){G(l,s,{recursive:!0});return}const p=u(i,"base"),h=`github:vitejs/vite/packages/create-vite/template-${n.createViteTemplate}#main`;await ie(h,{cwd:i,dir:p,force:!0,install:!1,silent:!0}),Ka(p),G(p,s,{recursive:!0})};let c=[];return await ge([{label:`create-vite (${n.label}) base ready`,run:d},{label:`Lunora overlay applied (${n.label})`,run:async()=>{c=await wa({adapter:n,distTag:de(),logger:a,name:r,target:s})}}],{end:"Project initialized!",start:"Project initializing…"}),K(a,c,s),{code:0,files:[...q(s)],target:s}}catch(l){if(l instanceof F)throw l;const d=l instanceof Error?l.message:String(l);return a.error(`failed to scaffold the ${n.label} base: ${d}`),{code:1,files:[],target:s}}finally{$(i,{force:!0,recursive:!0})}},j=(e,t,a,r,o)=>(o.error(`init --in-place: could not ${e} ${t}: ${a instanceof Error?a.message:String(a)}`),{code:1,files:[],target:r}),Ja=(e,t)=>{const a=u(e,"vite.config.ts");try{v(a,Ma,"utf8")}catch(r){return j("write",a,r,e,t)}return t.success(`created ${a} with lunora() plugin`),{code:0,files:[a],target:e}},Ya=(e,t,a)=>{let r;try{r=b(e,"utf8")}catch(s){return j("read",e,s,t,a)}const o=ke(r);if(!o.changed)return a.info(`${e}: ${o.reason??"no changes needed"}`),{code:0,files:[],target:t};try{v(e,o.code,"utf8")}catch(s){return j("write",e,s,t,a)}return a.success(`patched ${e} — added lunora() plugin`),{code:0,files:[e],target:t}},Xa=(e,t)=>{const a=u(e,"lunora"),r=u(a,"schema.ts");if(m(r))return t.info(`lunora/ already present — left ${r} untouched`),[];const o=[];try{M(a,{recursive:!0}),v(r,Aa,"utf8"),o.push(r);const s=u(a,"messages.ts");m(s)||(v(s,Ea,"utf8"),o.push(s)),t.success(`scaffolded lunora/ (${String(o.length)} file(s))`)}catch(s){const n=s instanceof Error?s.message:String(s);t.error(`init --here: could not scaffold lunora/: ${n}`)}return o},Qa=(e,t,a)=>{const{adapter:r,class:o,framework:s}=e;a.info(""),a.info(`detected framework: ${s} (class ${o})`),a.info("next steps:"),a.info(` 1. install the adapter: ${Ba(t,[r,"@lunora/client","@lunora/runtime","@lunora/server"])}`),a.info(" 2. run codegen: lunora codegen"),o==="A"?(a.info(" 3. compose one worker: wrap your worker entry with"),a.info(" createWorker({ httpRouter: <your framework SSR handler>, shardDO: ShardDO, ... })"),a.info(` 4. add the provider: mount the ${r} provider in your root layout/route`),a.info(" 5. make a loader live: preloadQuery() in a loader, usePreloadedQuery() in the component"),a.info(" see https://lunora.sh/docs/frameworks/reactive-loaders")):o==="B"?(a.info(" 3. inject Lunora: mount Lunora realtime under /_lunora/* in your server hook"),a.info(` (${s} owns its Cloudflare adapter — Lunora composes into its server entry)`),a.info(` 4. add the provider: mount the ${r} provider in your root layout`),a.info(" 5. read the guide: https://lunora.sh/docs/frameworks/deploy")):(a.info(" 3. add the provider: wrap your app with the LunoraProvider from @lunora/react"),a.info(" 4. read the guide: https://lunora.sh/docs/frameworks/bring-your-framework")),a.info("")},Za=(e,t,a)=>{const r=Se(e);return r===void 0?t==="sveltekit"||t==="nuxt"||t==="astro"?(a.info(`no Vite config found — ${t} wires Lunora through its server entry (see next steps)`),{code:0,files:[],target:e}):Ja(e,a):Ya(r,e,a)},er=(e,t)=>{const a=Lt(e),r=Za(e,a.framework,t);if(r.code!==0)return r;const o=Xa(e,t);return Qa(a,U(e),t),{code:0,files:[...r.files,...o],target:e}},tr=e=>e.yes!==!0&&(e.prompt!==void 0||(e.interactive??g())),ae=async(e,t,a)=>{a.dryRun!==!0&&await Kt({apply:r=>Ue(e,r),detected:Ve(e),interactive:t,logger:a.logger,multiSelect:a.lintPrompt??((r,o,s)=>me(r,o,{...s,badge:f.add}))})},ar=async(e,t)=>{const a=tr(e),r=e.add===void 0?[]:jt(e.add,n=>{e.logger.warn(n)}),s={applyAll:async n=>{if(n.length===0)return!0;if(e.dryRun===!0)return S(e.logger,`add ${n.map(h=>h.label).join(", ")}`),!0;const i=[],l=g()?{error:h=>{i.push({level:"error",message:h})},info:()=>{},success:()=>{},warn:h=>{i.push({level:"warn",message:h})}}:e.logger,d=n.map(h=>({running:`adding ${h.label}…`,task:()=>gt({allowUnsafeSource:e.allowUnsafeSource,cwd:t,from:e.registryFrom,logger:l,names:[...h.names],ref:e.ref,source:e.registrySource,transformManifest:h.transformManifest,yes:!0})})),c=`added ${n.map(h=>h.label).join(", ")}`,p=await it(f.add,d,c);for(const{level:h,message:I}of i)e.logger[h](I);return p.every(h=>h.code===0)},interactive:a,logger:e.logger,multiSelect:e.prompt?.multiSelect??((n,i,l)=>me(n,i,{...l,badge:f.add})),preselected:r.length>0?r:void 0,projectName:E(t),resolveAuthUiItem:()=>{try{const n=JSON.parse(b(u(t,"package.json"),"utf8"));return ft({...n.dependencies,...n.devDependencies})??"auth-ui-react"}catch{return"auth-ui-react"}},select:e.prompt?.select??((n,i,l)=>H(n,i,{...l,badge:f.add})),text:e.prompt?.text??((n,i)=>he(n,{...i,badge:f.add}))};if(r.length>0){await Z(s),await ae(t,a,e);return}g()&&await ot(y.extras),await Z(s),await ae(t,a,e)},D="react",_e=[{description:"React SPA — official create-vite base + the Lunora layer (the default)",label:"React",value:"react"},{description:"Vue SPA — create-vite base + Lunora",label:"Vue",value:"vue"},{description:"Solid SPA — create-vite base + Lunora",label:"Solid",value:"solid"},{description:"Solid 2.0 SPA — the Solid 2 line (@solidjs/web, vite-plugin-solid 3)",label:"Solid 2",value:"solid-v2"},{description:"Svelte SPA — create-vite base + Lunora",label:"Svelte",value:"svelte"},{description:"Next.js (App Router) — OpenNext on Cloudflare + a standalone Lunora worker",label:"Next.js",value:"next"},{description:"TanStack Start (React) — SSR with live-loader routes",label:"TanStack Start · React",value:"tanstack-start-react"},{description:"TanStack Start (Solid)",label:"TanStack Start · Solid",value:"tanstack-start-solid"},{description:"Next.js App Router on Vite (vinext) — composed into the Lunora worker (experimental)",label:"vinext · App Router",value:"vinext"},{description:"Next.js Pages Router on Vite (vinext) — composed into one worker (experimental)",label:"vinext · Pages Router",value:"vinext-pages"},{description:"React Router (v7, framework mode) — SSR composed into the Lunora worker",label:"React Router",value:"react-router"},{description:"Astro + a standalone Lunora worker",label:"Astro",value:"astro"},{description:"AnalogJS (Angular) — single-worker, Lunora mounted in Nitro",label:"Analog",value:"analog"},{description:"Nuxt (Vue) — single-worker, Lunora mounted in Nitro",label:"Nuxt",value:"nuxt"},{description:"SvelteKit + a standalone Lunora worker",label:"SvelteKit",value:"sveltekit"},{description:"React Native (Expo) — an iOS/Android/web app + a Lunora worker backend",label:"React Native · Expo",value:"expo"},{description:"Worker only — no frontend",label:"Standalone",value:"standalone"}],rr=Object.keys(T).join("|"),$e=_e.filter(e=>!z(e.value)).map(e=>e.value),or=$e.join("|"),sr=e=>z(e)?{framework:e,kind:"overlay"}:{kind:"template",templateType:e},nr=async e=>e.vite!==void 0?{framework:e.vite,kind:"overlay"}:e.templateType!==void 0?{kind:"template",templateType:e.templateType}:!g()||e.yes===!0?{framework:D,kind:"overlay"}:sr(await H(y.framework,_e,{badge:f.tmpl,default:D})??D),ir=e=>{if(g()||e.yes===!0)return;const t=[];if(e.name===void 0&&t.push("a project name (`lunora init <name>`)"),e.templateType===void 0&&e.vite===void 0&&t.push(`a framework — \`--vite <${rr}>\` for an SPA, or \`-t <${or}>\` for a bespoke template`),t.length!==0)return`lunora init can't prompt in a non-interactive terminal — provide ${t.join(" and ")}, or pass --yes to accept the defaults.`},lr=async(e,t,a,r)=>z(t)?await Me({isLocal:e.overlayBaseFrom!==void 0,logger:e.logger})?(M(r,{recursive:!0}),Ga({framework:t,logger:e.logger,name:a,overlayBaseFrom:e.overlayBaseFrom,target:r})):{code:1,files:[],target:r}:(e.logger.error(`init: unknown framework "${t}". Supported overlays: ${Object.keys(T).join(", ")}.`),{code:1,files:[],target:r}),cr=async(e,t,a,r)=>e.from!==void 0?await za(e.from,t,r,a,e.logger):e.source!==void 0&&e.source.length>0&&!e.allowUnsafeSource&&!Da(e.source)?(e.logger.error(`init: refusing --source ${e.source} — only gh:, github:, or https:// sources are allowed (and may not contain ".."). Re-run with --allow-unsafe-source if you really want this.`),{code:1,files:[],target:r}):await Me({isLocal:!1,logger:e.logger,source:B(t,e.source,e.ref)})?qa({logger:e.logger,name:a,ref:e.ref,source:e.source,target:r,templateType:t}):{code:1,files:[],target:r},dr=async(e,t,a)=>{await rt("realtime backend on Cloudflare Workers + Durable Objects");const r=ir(e);if(r!==void 0)return e.logger.error(r),{code:1,files:[],target:""};const o=xa(),s=e.name??await he(y.name,{badge:f.dir,default:o,placeholder:o}),n=await nr(e),i=s.trim();if(i.length===0)return e.logger.error("init: refusing an empty project name — pass a directory name (e.g. `lunora init my-app`)."),{code:1,files:[],target:""};if(i.includes("/")||i.includes("\\")||i===".."||i===".")return e.logger.error(`init: refusing project name "${i}" — must not contain path separators or be "." / "..".`),{code:1,files:[],target:""};const l=ne(t,i),d=m(l);if(d&&re(l).length>0)return e.logger.error(`target directory not empty: ${l}`),{code:1,files:[],target:l};if(e.dryRun===!0){const c=n.kind==="overlay"?`the ${n.framework} create-vite overlay`:`the ${n.templateType} template`;return S(e.logger,`scaffold ${c} into ${l}`),{code:0,files:[],target:l}}return a(l,d),n.kind==="overlay"?lr(e,n.framework,i,l):cr(e,n.templateType,i,l)},ur=(e,t)=>{const{target:a,targetPreExisted:r}=e;if(!(a===void 0||!m(a))){if(r===!0)for(const o of re(a))$(u(a,o),{force:!0,recursive:!0});else $(a,{force:!0,recursive:!0});t.info(`removed the partially-created project at ${a}`)}},pr=async(e,t,a)=>e.inPlace!==!0?dr(e,t,a):e.dryRun===!0?(S(e.logger,`configure Lunora into ${t}`),{code:0,files:[],target:t}):er(t,e.logger),hr=async(e,t,a)=>{await ar(e,t.target);const r=e.inPlace===!0?void 0:await Wa(e,t.target);if(e.inPlace!==!0){await Ua(e,t.target);const o=r??U(t.target);await Fa(E(t.target),r,o,Ie(a)),await Dt(e.logger)}},mr=(e,t,a)=>{if(t.code!==0||e.ci===void 0)return;if(e.dryRun===!0){S(e.logger,`scaffold a ${e.ci} CI deploy pipeline`);return}const r=e.inPlace===!0?a:t.target;try{const o=U(r);Ct(r,e.ci,o,e.logger)}catch{e.logger.warn(`--ci ${e.ci}: could not detect a package manager for this project — skipped the CI pipeline.`)}},fr=async e=>{const t=e.cwd??process.cwd(),a={};let r;try{r=await pr(e,t,(o,s)=>{a.target=o,a.targetPreExisted=s}),r.code===0&&r.target!==""&&(a.target=void 0,await hr(e,r,t))}catch(o){if(o instanceof F)return ur(a,e.logger),process.stdout.write("\n ✖ Setup cancelled — run `lunora init` again whenever you're ready. 🌙\n"),{code:130,files:[],target:""};throw o}return mr(e,r,t),r},vr=e=>typeof e=="string"&&$e.includes(e),gr=(e,t)=>{if(e!==void 0){if(xt(e))return e;t.warn(`init: unknown --ci "${e}" — expected github | gitlab; skipping CI scaffold.`)}},Br=Xe(({argument:e,cwd:t,logger:a,options:r})=>{const o=r.template!==void 0&&vr(r.template)?r.template:void 0;return fr({add:r.add,allowUnsafeSource:r.allowUnsafeSource===!0,cwd:t,ci:gr(r.ci,a),dryRun:r.dryRun===!0,from:r.from,inPlace:r.here===!0,interactive:r.interactive===!0?!0:void 0,logger:a,name:e[0],ref:r.ref,source:r.source,templateType:o,vite:r.vite,yes:r.yes===!0})});export{Br as execute,vr as isTemplate,B as resolveTemplateSource,fr as runInitCommand};
|
|
1224
|
+
`,Na="gh:anolilab/lunora/templates",Ia=e=>{const t=e.lastIndexOf(".");return t===-1?!1:La.has(e.slice(t))},_a=(e,t)=>e.replaceAll("{{name}}",t),$a=async(e,t)=>{const a=new Set;for(const r of e)if(E(r)==="package.json")try{const o=JSON.parse(b(r,"utf8"));for(const s of["dependencies","devDependencies"])for(const n of Object.keys(o[s]??{}))P(n)&&a.add(n)}catch{}return ce(a,t)},Ra=(e,t,a)=>{let r;try{r=JSON.parse(e)}catch{return e}let o=e;for(const s of["dependencies","devDependencies"])for(const n of Object.keys(r[s]??{})){if(!P(n))continue;const i=a.get(n)??t,l=Ge(o,[s,n],i,{formattingOptions:{insertSpaces:!0,tabSize:4}});o=Je(o,l)}return o},Oa=["@parcel/watcher","esbuild","lmdb","msgpackr-extract","rs-module-lexer","sharp","unrs-resolver","vue-demi","workerd"],Ta=["cpu-features","protobufjs","ssh2"],Ae="pnpm-workspace.yaml",Pa=()=>["# pnpm reads its settings from here (the package.json `pnpm` field is no longer read).","# Pre-approve the toolchain's native build scripts so `pnpm install` runs them","# without the interactive `pnpm approve-builds` step; deny the optional native","# builds a scaffold doesn't need (so no C/C++ toolchain is required).","allowBuilds:",...Oa.map(e=>` '${e}': true`),...Ta.map(e=>` '${e}': false`),""].join(`
|
|
1225
|
+
`),q=e=>{const t=[];for(const a of qe(e,{includeDirs:!1,includeFiles:!0}))Oe(a.path).isSymbolicLink()||t.push(a.path);return t},Ee=async(e,t,a)=>{const r=q(e),o=[],s=de(),n=await $a(r,s);for(const i of r){const l=Ke(e,i),d=u(t,l);M(A(d),{recursive:!0});const c=b(i);let p=Ia(i)?_a(c.toString("utf8"),a):void 0;p!==void 0&&E(i)==="package.json"&&(p=Ra(p,s,n)),p===void 0?v(d,c):v(d,p,"utf8"),o.push(d)}return o},B=(e,t,a)=>t!==void 0&&t.length>0?t:`${Na}/${e}#${ue(a)}`,Da=e=>e.includes("..")?!1:e.startsWith("gh:")||e.startsWith("github:")||e.startsWith("https://"),S=(e,t)=>{e.info(`[dry-run] would ${t}`)},K=(e,t,a)=>{g()&&process.stdout.write(`
|
|
1226
|
+
`),e.success(`scaffolded ${String(t.length)} files into ${a}`)},Ba=(e,t)=>{const{args:a,command:r}=We(e,t);return`${r} ${a.join(" ")}`},Ne=(e,t)=>{let a=ie(e);for(;;){if(t(a))return!0;const r=A(a);if(r===a)return!1;a=r}},ja=e=>{if(m(u(e,Ae)))return!0;const t=u(e,"package.json");if(!m(t))return!1;try{return JSON.parse(b(t,"utf8")).workspaces!==void 0}catch{return!1}},Ie=e=>Ne(e,ja),Va=e=>Ne(e,t=>m(u(t,".git"))),Ua=async(e,t)=>{if(e.yes===!0||!g()||Va(A(t)))return;if(!await fe(y.git,{badge:f.git,defaultYes:!1})){await ve("Sounds good! You can always run git init manually.");return}if(e.dryRun===!0){S(e.logger,"initialize a git repository");return}const a=e.spawner??pe;(await st("Initializing a git repository…",()=>a({args:["init"],command:"git",cwd:t}))).code===0?await R("git","Initialized an empty git repository."):e.logger.warn("`git init` failed — initialize it yourself later with `git init`.")},Fa=async(e,t,a,r)=>{const o=[{code:`cd ./${e}`,lead:"Enter your project directory using"}];t===void 0&&o.push({code:`${a} install`,lead:"Install dependencies with",tail:r?" from the workspace root":void 0}),o.push({code:Fe(a,"dev"),lead:"Run",tail:" to start the dev server."},{code:"lunora add",lead:"Add features like auth or storage using"});const s=[{code:"https://lunora.sh/docs",lead:"Read the docs at"},{code:"https://lunora.sh/chat",lead:"Stuck? Join the chat at"}];if(g()){await nt(f.next,y.nextHeader,o,s);return}const n=o.map(i=>`${i.lead} ${i.code}${i.tail??""}`);n.push("",...s.map(i=>`${i.lead} ${i.code}${i.tail??""}`)),await R("next",y.nextHeader,n.join(`
|
|
1227
|
+
`))},Ha=e=>e.yes!==!0&&(e.installPrompt!==void 0||g()),Wa=async(e,t)=>{if(!Ha(e)||Ie(A(t)))return;const a=Ue(e.packageManagerProbe),[r]=a;if(r===void 0)return;if(!await(e.installPrompt?.confirmInstall??(async()=>fe(y.install,{badge:f.deps,defaultYes:!0})))()){await ve("No problem! Remember to install dependencies after setup.");return}let s=r;if(a.length>1&&(s=e.installPrompt?await e.installPrompt.selectManager(a):await H(y.packageManager,a.map(c=>({label:c,value:c})),{badge:f.deps,default:r})??r),e.dryRun===!0){S(e.logger,`install dependencies with ${s}`);return}if(s==="pnpm"){const c=u(t,Ae);m(c)||v(c,Pa(),"utf8")}const n=e.spawner??pe,{args:i,command:l}=ne(s);await R("deps",`Installing dependencies with ${s}…`);const d=await n({args:i,command:l,cwd:t});if(d.code!==0){e.logger.warn(`\`${l} install\` exited with code ${String(d.code)} — run it yourself in ${E(t)}/.`);return}return await R("deps",`Dependencies installed with ${s}.`),s},za=async(e,t,a,r,o)=>{const s=u(e,t);if(!m(s))return o.error(`template not found in local source: ${s}`),{code:1,files:[],target:a};const n=await Ee(s,a,r);return K(o,n,a),{code:0,files:n,target:a}},qa=async e=>{const{logger:t,name:a,ref:r,source:o,target:s,templateType:n}=e,i=oe(u(se(),"lunora-init-fetch-")),l=u(i,"template");try{const d=o!==void 0&&o.length>0?r:await et(r,t),c=B(n,o,d);let p,h=[];await ge([{label:`${n} template fetched`,run:async()=>{p=await le(c,{cwd:i,dir:l,force:!0,install:!1,silent:!0})}},{label:`files copied into ${a}/`,run:async()=>{h=await Ee(l,s,a)}}],{end:"Project initialized!",start:"Project initializing…"});const I=q(l);return g()&&process.stdout.write(`
|
|
1228
|
+
`),t.info(p?.commit?`template: ${p.source} @ ${p.commit} (${String(I.length)} files)`:`template: ${p?.source??c} (${String(I.length)} files)`),K(t,h,s),{code:0,files:h,target:s}}catch(d){if(d instanceof F)throw d;const{hints:c,message:p}=Pt(d,{ref:ue(r),remote:B(n,o,r),templateType:n});t.error(p);for(const h of c)t.warn(h);return{code:1,files:[],target:s}}finally{$(i,{force:!0,recursive:!0})}},Ka=e=>{for(const t of["_gitignore","_npmrc","_gitattributes"]){const a=u(e,t);m(a)&&Re(a,u(e,`.${t.slice(1)}`))}},Ga=async e=>{const{framework:t,logger:a,name:r,overlayBaseFrom:o,target:s}=e,n=T[t],i=oe(u(se(),"lunora-vite-base-"));try{let l;if(o!==void 0&&(l=u(o,`template-${n.createViteTemplate}`),!m(l)))return a.error(`create-vite base not found on disk: ${l}`),{code:1,files:[],target:s};const d=async()=>{if(l!==void 0){G(l,s,{recursive:!0});return}const p=u(i,"base"),h=`github:vitejs/vite/packages/create-vite/template-${n.createViteTemplate}#main`;await le(h,{cwd:i,dir:p,force:!0,install:!1,silent:!0}),Ka(p),G(p,s,{recursive:!0})};let c=[];return await ge([{label:`create-vite (${n.label}) base ready`,run:d},{label:`Lunora overlay applied (${n.label})`,run:async()=>{c=await wa({adapter:n,distTag:de(),logger:a,name:r,target:s})}}],{end:"Project initialized!",start:"Project initializing…"}),K(a,c,s),{code:0,files:[...q(s)],target:s}}catch(l){if(l instanceof F)throw l;const d=l instanceof Error?l.message:String(l);return a.error(`failed to scaffold the ${n.label} base: ${d}`),{code:1,files:[],target:s}}finally{$(i,{force:!0,recursive:!0})}},j=(e,t,a,r,o)=>(o.error(`init --in-place: could not ${e} ${t}: ${a instanceof Error?a.message:String(a)}`),{code:1,files:[],target:r}),Ja=(e,t)=>{const a=u(e,"vite.config.ts");try{v(a,Ma,"utf8")}catch(r){return j("write",a,r,e,t)}return t.success(`created ${a} with lunora() plugin`),{code:0,files:[a],target:e}},Ya=(e,t,a)=>{let r;try{r=b(e,"utf8")}catch(s){return j("read",e,s,t,a)}const o=ke(r);if(!o.changed)return a.info(`${e}: ${o.reason??"no changes needed"}`),{code:0,files:[],target:t};try{v(e,o.code,"utf8")}catch(s){return j("write",e,s,t,a)}return a.success(`patched ${e} — added lunora() plugin`),{code:0,files:[e],target:t}},Xa=(e,t)=>{const a=u(e,"lunora"),r=u(a,"schema.ts");if(m(r))return t.info(`lunora/ already present — left ${r} untouched`),[];const o=[];try{M(a,{recursive:!0}),v(r,Aa,"utf8"),o.push(r);const s=u(a,"messages.ts");m(s)||(v(s,Ea,"utf8"),o.push(s)),t.success(`scaffolded lunora/ (${String(o.length)} file(s))`)}catch(s){const n=s instanceof Error?s.message:String(s);t.error(`init --here: could not scaffold lunora/: ${n}`)}return o},Qa=(e,t,a)=>{const{adapter:r,class:o,framework:s}=e;a.info(""),a.info(`detected framework: ${s} (class ${o})`),a.info("next steps:"),a.info(` 1. install the adapter: ${Ba(t,[r,"@lunora/client","@lunora/runtime","@lunora/server"])}`),a.info(" 2. run codegen: lunora codegen"),o==="A"?(a.info(" 3. compose one worker: wrap your worker entry with"),a.info(" createWorker({ httpRouter: <your framework SSR handler>, shardDO: ShardDO, ... })"),a.info(` 4. add the provider: mount the ${r} provider in your root layout/route`),a.info(" 5. make a loader live: preloadQuery() in a loader, usePreloadedQuery() in the component"),a.info(" see https://lunora.sh/docs/frameworks/reactive-loaders")):o==="B"?(a.info(" 3. inject Lunora: mount Lunora realtime under /_lunora/* in your server hook"),a.info(` (${s} owns its Cloudflare adapter — Lunora composes into its server entry)`),a.info(` 4. add the provider: mount the ${r} provider in your root layout`),a.info(" 5. read the guide: https://lunora.sh/docs/frameworks/deploy")):(a.info(" 3. add the provider: wrap your app with the LunoraProvider from @lunora/react"),a.info(" 4. read the guide: https://lunora.sh/docs/frameworks/bring-your-framework")),a.info("")},Za=(e,t,a)=>{const r=Se(e);return r===void 0?t==="sveltekit"||t==="nuxt"||t==="astro"?(a.info(`no Vite config found — ${t} wires Lunora through its server entry (see next steps)`),{code:0,files:[],target:e}):Ja(e,a):Ya(r,e,a)},er=(e,t)=>{const a=Lt(e),r=Za(e,a.framework,t);if(r.code!==0)return r;const o=Xa(e,t);return Qa(a,V(e),t),{code:0,files:[...r.files,...o],target:e}},tr=e=>e.yes!==!0&&(e.prompt!==void 0||(e.interactive??g())),ae=async(e,t,a)=>{a.dryRun!==!0&&await Kt({apply:r=>ze(e,r),detected:He(e),interactive:t,logger:a.logger,multiSelect:a.lintPrompt??((r,o,s)=>me(r,o,{...s,badge:f.add}))})},ar=async(e,t)=>{const a=tr(e),r=e.add===void 0?[]:jt(e.add,n=>{e.logger.warn(n)}),s={applyAll:async n=>{if(n.length===0)return!0;if(e.dryRun===!0)return S(e.logger,`add ${n.map(h=>h.label).join(", ")}`),!0;const i=[],l=g()?{error:h=>{i.push({level:"error",message:h})},info:()=>{},success:()=>{},warn:h=>{i.push({level:"warn",message:h})}}:e.logger,d=n.map(h=>({running:`adding ${h.label}…`,task:()=>gt({allowUnsafeSource:e.allowUnsafeSource,cwd:t,from:e.registryFrom,logger:l,names:[...h.names],ref:e.ref,source:e.registrySource,transformManifest:h.transformManifest,yes:!0})})),c=`added ${n.map(h=>h.label).join(", ")}`,p=await it(f.add,d,c);for(const{level:h,message:I}of i)e.logger[h](I);return p.every(h=>h.code===0)},interactive:a,logger:e.logger,multiSelect:e.prompt?.multiSelect??((n,i,l)=>me(n,i,{...l,badge:f.add})),preselected:r.length>0?r:void 0,projectName:E(t),resolveAuthUiItem:()=>{try{const n=JSON.parse(b(u(t,"package.json"),"utf8"));return ft({...n.dependencies,...n.devDependencies})??"auth-ui-react"}catch{return"auth-ui-react"}},select:e.prompt?.select??((n,i,l)=>H(n,i,{...l,badge:f.add})),text:e.prompt?.text??((n,i)=>he(n,{...i,badge:f.add}))};if(r.length>0){await Z(s),await ae(t,a,e);return}g()&&await ot(y.extras),await Z(s),await ae(t,a,e)},D="react",_e=[{description:"React SPA — official create-vite base + the Lunora layer (the default)",label:"React",value:"react"},{description:"Vue SPA — create-vite base + Lunora",label:"Vue",value:"vue"},{description:"Solid SPA — create-vite base + Lunora",label:"Solid",value:"solid"},{description:"Solid 2.0 SPA — the Solid 2 line (@solidjs/web, vite-plugin-solid 3)",label:"Solid 2",value:"solid-v2"},{description:"Svelte SPA — create-vite base + Lunora",label:"Svelte",value:"svelte"},{description:"Next.js (App Router) — OpenNext on Cloudflare + a standalone Lunora worker",label:"Next.js",value:"next"},{description:"TanStack Start (React) — SSR with live-loader routes",label:"TanStack Start · React",value:"tanstack-start-react"},{description:"TanStack Start (Solid)",label:"TanStack Start · Solid",value:"tanstack-start-solid"},{description:"Next.js App Router on Vite (vinext) — composed into the Lunora worker (experimental)",label:"vinext · App Router",value:"vinext"},{description:"Next.js Pages Router on Vite (vinext) — composed into one worker (experimental)",label:"vinext · Pages Router",value:"vinext-pages"},{description:"React Router (v7, framework mode) — SSR composed into the Lunora worker",label:"React Router",value:"react-router"},{description:"Astro + a standalone Lunora worker",label:"Astro",value:"astro"},{description:"AnalogJS (Angular) — single-worker, Lunora mounted in Nitro",label:"Analog",value:"analog"},{description:"Nuxt (Vue) — single-worker, Lunora mounted in Nitro",label:"Nuxt",value:"nuxt"},{description:"SvelteKit + a standalone Lunora worker",label:"SvelteKit",value:"sveltekit"},{description:"React Native (Expo) — an iOS/Android/web app + a Lunora worker backend",label:"React Native · Expo",value:"expo"},{description:"Worker only — no frontend",label:"Standalone",value:"standalone"}],rr=Object.keys(T).join("|"),$e=_e.filter(e=>!z(e.value)).map(e=>e.value),or=$e.join("|"),sr=e=>z(e)?{framework:e,kind:"overlay"}:{kind:"template",templateType:e},nr=async e=>e.vite!==void 0?{framework:e.vite,kind:"overlay"}:e.templateType!==void 0?{kind:"template",templateType:e.templateType}:!g()||e.yes===!0?{framework:D,kind:"overlay"}:sr(await H(y.framework,_e,{badge:f.tmpl,default:D})??D),ir=e=>{if(g()||e.yes===!0)return;const t=[];if(e.name===void 0&&t.push("a project name (`lunora init <name>`)"),e.templateType===void 0&&e.vite===void 0&&t.push(`a framework — \`--vite <${rr}>\` for an SPA, or \`-t <${or}>\` for a bespoke template`),t.length!==0)return`lunora init can't prompt in a non-interactive terminal — provide ${t.join(" and ")}, or pass --yes to accept the defaults.`},lr=async(e,t,a,r)=>z(t)?await Me({isLocal:e.overlayBaseFrom!==void 0,logger:e.logger})?(M(r,{recursive:!0}),Ga({framework:t,logger:e.logger,name:a,overlayBaseFrom:e.overlayBaseFrom,target:r})):{code:1,files:[],target:r}:(e.logger.error(`init: unknown framework "${t}". Supported overlays: ${Object.keys(T).join(", ")}.`),{code:1,files:[],target:r}),cr=async(e,t,a,r)=>e.from!==void 0?await za(e.from,t,r,a,e.logger):e.source!==void 0&&e.source.length>0&&!e.allowUnsafeSource&&!Da(e.source)?(e.logger.error(`init: refusing --source ${e.source} — only gh:, github:, or https:// sources are allowed (and may not contain ".."). Re-run with --allow-unsafe-source if you really want this.`),{code:1,files:[],target:r}):await Me({isLocal:!1,logger:e.logger,source:B(t,e.source,e.ref)})?qa({logger:e.logger,name:a,ref:e.ref,source:e.source,target:r,templateType:t}):{code:1,files:[],target:r},dr=async(e,t,a)=>{await rt("realtime backend on Cloudflare Workers + Durable Objects");const r=ir(e);if(r!==void 0)return e.logger.error(r),{code:1,files:[],target:""};const o=xa(),s=e.name??await he(y.name,{badge:f.dir,default:o,placeholder:o}),n=await nr(e),i=s.trim();if(i.length===0)return e.logger.error("init: refusing an empty project name — pass a directory name (e.g. `lunora init my-app`)."),{code:1,files:[],target:""};if(i.includes("/")||i.includes("\\")||i===".."||i===".")return e.logger.error(`init: refusing project name "${i}" — must not contain path separators or be "." / "..".`),{code:1,files:[],target:""};const l=ie(t,i),d=m(l);if(d&&re(l).length>0)return e.logger.error(`target directory not empty: ${l}`),{code:1,files:[],target:l};if(e.dryRun===!0){const c=n.kind==="overlay"?`the ${n.framework} create-vite overlay`:`the ${n.templateType} template`;return S(e.logger,`scaffold ${c} into ${l}`),{code:0,files:[],target:l}}return a(l,d),n.kind==="overlay"?lr(e,n.framework,i,l):cr(e,n.templateType,i,l)},ur=(e,t)=>{const{target:a,targetPreExisted:r}=e;if(!(a===void 0||!m(a))){if(r===!0)for(const o of re(a))$(u(a,o),{force:!0,recursive:!0});else $(a,{force:!0,recursive:!0});t.info(`removed the partially-created project at ${a}`)}},pr=async(e,t,a)=>e.inPlace!==!0?dr(e,t,a):e.dryRun===!0?(S(e.logger,`configure Lunora into ${t}`),{code:0,files:[],target:t}):er(t,e.logger),hr=async(e,t,a)=>{await ar(e,t.target);const r=e.inPlace===!0?void 0:await Wa(e,t.target);if(e.inPlace!==!0){await Ua(e,t.target);const o=r??V(t.target);await Fa(E(t.target),r,o,Ie(a)),await Dt(e.logger)}},mr=(e,t,a)=>{if(t.code!==0||e.ci===void 0)return;if(e.dryRun===!0){S(e.logger,`scaffold a ${e.ci} CI deploy pipeline`);return}const r=e.inPlace===!0?a:t.target;try{const o=V(r);Ct(r,e.ci,o,e.logger)}catch{e.logger.warn(`--ci ${e.ci}: could not detect a package manager for this project — skipped the CI pipeline.`)}},fr=async e=>{const t=e.cwd??process.cwd(),a={};let r;try{r=await pr(e,t,(o,s)=>{a.target=o,a.targetPreExisted=s}),r.code===0&&r.target!==""&&(a.target=void 0,await hr(e,r,t))}catch(o){if(o instanceof F)return ur(a,e.logger),process.stdout.write("\n ✖ Setup cancelled — run `lunora init` again whenever you're ready. 🌙\n"),{code:130,files:[],target:""};throw o}return mr(e,r,t),r},vr=e=>typeof e=="string"&&$e.includes(e),gr=(e,t)=>{if(e!==void 0){if(xt(e))return e;t.warn(`init: unknown --ci "${e}" — expected github | gitlab; skipping CI scaffold.`)}},Dr=Xe(({argument:e,cwd:t,logger:a,options:r})=>{const o=r.template!==void 0&&vr(r.template)?r.template:void 0;return fr({add:r.add,allowUnsafeSource:r.allowUnsafeSource===!0,cwd:t,ci:gr(r.ci,a),dryRun:r.dryRun===!0,from:r.from,inPlace:r.here===!0,interactive:r.interactive===!0?!0:void 0,logger:a,name:e[0],ref:r.ref,source:r.source,templateType:o,vite:r.vite,yes:r.yes===!0})});export{Dr as execute,vr as isTemplate,B as resolveTemplateSource,fr as runInitCommand};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{C as a,R as s,V as C,r as N}from"./cli-HouJioGN.mjs";import"./createLogger-hGafwf-3.mjs";export{a as COMMANDS,s as REGISTERED_COMMAND_NAMES,C as VERSION,N as runCli};
|