@lunora/cli 1.0.0-alpha.440 → 1.0.0-alpha.442

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.
Files changed (57) hide show
  1. package/dist/cli.mjs +1 -1
  2. package/dist/index.d.mts +22 -1
  3. package/dist/index.d.ts +22 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/packem_chunks/cli.mjs +2 -2
  6. package/dist/packem_chunks/dispatch.mjs +1 -1
  7. package/dist/packem_chunks/handler.mjs +1 -1
  8. package/dist/packem_chunks/handler10.mjs +1 -1
  9. package/dist/packem_chunks/handler11.mjs +1 -1
  10. package/dist/packem_chunks/handler12.mjs +1 -1
  11. package/dist/packem_chunks/handler13.mjs +1 -1
  12. package/dist/packem_chunks/handler14.mjs +1 -1
  13. package/dist/packem_chunks/handler15.mjs +1 -1
  14. package/dist/packem_chunks/handler16.mjs +1 -1
  15. package/dist/packem_chunks/handler17.mjs +1 -1
  16. package/dist/packem_chunks/handler18.mjs +1 -1
  17. package/dist/packem_chunks/handler19.mjs +1 -1
  18. package/dist/packem_chunks/handler2.mjs +1 -1
  19. package/dist/packem_chunks/handler20.mjs +1 -1
  20. package/dist/packem_chunks/handler21.mjs +1 -1
  21. package/dist/packem_chunks/handler22.mjs +1 -1
  22. package/dist/packem_chunks/handler23.mjs +1 -1
  23. package/dist/packem_chunks/handler24.mjs +1 -1
  24. package/dist/packem_chunks/handler25.mjs +1 -1
  25. package/dist/packem_chunks/handler26.mjs +1 -1
  26. package/dist/packem_chunks/handler27.mjs +1 -1
  27. package/dist/packem_chunks/handler28.mjs +1 -1
  28. package/dist/packem_chunks/handler3.mjs +1 -1
  29. package/dist/packem_chunks/handler4.mjs +1 -1
  30. package/dist/packem_chunks/handler5.mjs +1 -1
  31. package/dist/packem_chunks/handler6.mjs +1 -1
  32. package/dist/packem_chunks/handler7.mjs +1 -1
  33. package/dist/packem_chunks/handler8.mjs +1 -1
  34. package/dist/packem_chunks/handler9.mjs +1 -1
  35. package/dist/packem_chunks/runCodegenCommand.mjs +1 -1
  36. package/dist/packem_chunks/runDevCommand.mjs +4 -4
  37. package/dist/packem_chunks/runInitCommand.mjs +83 -59
  38. package/dist/packem_chunks/runResetCommand.mjs +1 -1
  39. package/dist/packem_chunks/runRpcCommand.mjs +1 -1
  40. package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-DoFxIuJF.mjs +1 -0
  41. package/dist/packem_shared/{EXIT_CODE-D_XCixv8.mjs → EXIT_CODE-DVjUgIaG.mjs} +1 -1
  42. package/dist/packem_shared/{command-CB4JM-Jl.mjs → command-B6vu47iR.mjs} +1 -1
  43. package/dist/packem_shared/{commands-DvSfLvyt.mjs → commands-phAlD1KR.mjs} +1 -1
  44. package/dist/packem_shared/{import-ZdIj7W66.mjs → import-U8wA5qmm.mjs} +1 -1
  45. package/dist/packem_shared/{plan-lzllz0Cn.mjs → plan-BH4ZfiFs.mjs} +1 -1
  46. package/dist/packem_shared/{planDevCommand-HmQ01PRq.mjs → planDevCommand-I8-NB_CN.mjs} +1 -1
  47. package/dist/packem_shared/{render-lunora-error-B6893vbU.mjs → render-lunora-error-DUo0pA61.mjs} +1 -1
  48. package/dist/packem_shared/runAddCommand-CQxucNIH.mjs +1 -0
  49. package/dist/packem_shared/{runDeployCommand-CwgnRrbK.mjs → runDeployCommand-C5L6ZhGV.mjs} +1 -1
  50. package/dist/packem_shared/{runExportCommand-slAq5BCF.mjs → runExportCommand-DiMCQ-Il.mjs} +1 -1
  51. package/dist/packem_shared/{runMigrateGenerateCommand-rZdsVcFj.mjs → runMigrateGenerateCommand-D5azbBCr.mjs} +1 -1
  52. package/dist/packem_shared/storage-BlIF1T0Y.mjs +1 -0
  53. package/package.json +10 -10
  54. package/skills/lunora-setup-auth/SKILL.md +43 -1
  55. package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-BwTREKH3.mjs +0 -1
  56. package/dist/packem_shared/runAddCommand-BChdJQ5L.mjs +0 -1
  57. package/dist/packem_shared/storage-DNzYhXR-.mjs +0 -1
@@ -1 +1 @@
1
- import{d as Ue}from"../packem_shared/command-CB4JM-Jl.mjs";import{r as De}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{exitCodeForStatus as j,EXIT_CODE as u}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{writeFileSync as ve,mkdirSync as Re,renameSync as Me,rmSync as je}from"node:fs";import{findWranglerFile as J,readWranglerJsonc as W,applyModify as Le}from"@lunora/config/cloudflare";import{c as L}from"../packem_shared/cap-error-body-YBKO32BF.mjs";import{r as H,n as X,c as Pe}from"../packem_shared/cloudflare-credentials-BVmZnDZk.mjs";import{c as Ge,b as Fe}from"../packem_shared/variables-CPYyJtsL.mjs";import{b as xe}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{i as qe}from"../packem_shared/docker-DpVxvYpL.mjs";import{defaultSpawner as be}from"../packem_shared/createRecordingSpawner-01ZngIwK.mjs";import{execArgsFor as $e,detectPackageManager as Ae}from"@lunora/config/cli";import{a as O}from"./cli.mjs";import{resolve as Be,dirname as Je}from"node:path";const We="https://api.cloudflare.com/client/v4/accounts",Q=64,Z={byok:"https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/",unifiedBilling:"https://developers.cloudflare.com/ai-gateway/features/unified-billing/"},$=(e,t,r)=>(e.error(r),{code:t,error:r}),ee=async(e,t,r,o={})=>{const n=await e(t,{...o,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}}),a=await n.text();let s;try{s=JSON.parse(a)}catch{}return{body:s,ok:n.ok&&s?.success!==!1,status:n.status,text:a}},te=e=>{const t=(e.body?.errors??[]).map(r=>r.message).filter(r=>typeof r=="string");return`${String(e.status)}: ${L(t.length>0?t.join("; "):e.text)}`},He=(e,t,r)=>{let o=e;const n=[];for(const[a,s]of Object.entries(r))t?.[a]!==s&&(o=Le(o,["vars",a],s),n.push(a));return{changed:n,text:o}},Ye=(e,t)=>{e.info(`next: \`<provider>/<model>\` slugs (e.g. "anthropic/claude-sonnet-5") now route through gateway "${t}". Before the first call,`),e.info(` configure how the provider is paid for in the Cloudflare dashboard (AI > AI Gateway > ${t}):`),e.info(` - Unified Billing (OpenAI, Anthropic, Google, xAI, Groq, DeepSeek): load credits — ${Z.unifiedBilling}`),e.info(` - a key stored on the gateway (Mistral, Perplexity, OpenRouter, …): ${Z.byok}`),e.info(" Then redeploy (`lunora deploy`) so the Worker picks up the new vars.")},Ke=async(e,t,r,o,n,a)=>{const s=`${We}/${encodeURIComponent(r)}/ai-gateway/gateways`,i=await ee(t,`${s}/${encodeURIComponent(n)}`,o);if(i.ok){const d=i.body?.result?.collect_logs;return{action:"existing",collectLogs:typeof d=="boolean"?d:a}}if(i.status===401||i.status===403)return $(e,j(i.status),`ai-gateway: could not read AI Gateway "${n}" (${te(i)})`);const c=await ee(t,s,o,{body:JSON.stringify({cache_invalidate_on_update:!0,cache_ttl:0,collect_logs:a,id:n,rate_limiting_interval:0,rate_limiting_limit:0}),method:"POST"});return c.ok?{action:"created",collectLogs:a}:$(e,j(c.status),`ai-gateway: could not create AI Gateway "${n}" (${te(c)})`)},Ve=e=>{const{logger:t}=e,r=J(e.cwd);if(r===void 0)return $(t,u.NOT_FOUND,"ai-gateway: no wrangler.jsonc found — run `lunora init` (or `lunora dev`) first.");const{parsed:o,text:n}=W(r);if(o===void 0)return $(t,u.USAGE,`ai-gateway: could not parse ${r} as JSONC.`);const a=X(e.id)??X(o.name);return a===void 0?$(t,u.USAGE,"ai-gateway: no gateway id — pass --id <id>, or set `name` in wrangler.jsonc."):a.length>Q?$(t,u.USAGE,`ai-gateway: gateway id "${a}" is longer than ${String(Q)} characters — pass a shorter --id.`):{gatewayId:a,parsed:o,text:n,wranglerPath:r}},P=e=>e?"on":"off",ze=(e,t)=>(e.info(`ai-gateway (dry run): would create or reuse AI Gateway "${t.gatewayId}" (log collection ${P(t.collectLogs)}).`),e.info(t.varsWritten.length===0?`ai-gateway (dry run): ${t.wranglerPath} vars are already up to date.`:`ai-gateway (dry run): would write ${t.varsWritten.join(", ")} into ${t.wranglerPath} vars.`),t.accountId===void 0&&e.warn("ai-gateway (dry run): no CLOUDFLARE_ACCOUNT_ID (or `account_id` in wrangler.jsonc) — a real run will stop there."),{code:0,data:t}),Xe=async e=>{const{logger:t}=e,r=Ve(e);if("code"in r)return r;const{gatewayId:o,parsed:n,text:a,wranglerPath:s}=r,i=e.logs!==!1,{accountId:c,token:d}=H(e.environment??process.env,n.account_id),f={[Fe]:o,...c===void 0?{}:{[Ge]:c}},l=He(a,n.vars,f);if(e.dryRun===!0)return ze(t,{accountId:c,action:"planned",collectLogs:i,dryRun:!0,gatewayId:o,varsWritten:l.changed,wranglerPath:s});if(d===void 0||c===void 0){const g=[d===void 0?"CLOUDFLARE_API_TOKEN (an API token with AI Gateway Write)":void 0,c===void 0?"CLOUDFLARE_ACCOUNT_ID (or `account_id` in wrangler.jsonc)":void 0].filter(y=>y!==void 0);return $(t,u.AUTH,`ai-gateway: missing ${g.join(" and ")}.`)}const h=await Ke(t,e.fetch??globalThis.fetch.bind(globalThis),c,d,o,i);return"code"in h?h:(h.action==="created"?t.success(`ai-gateway: created AI Gateway "${o}" (log collection ${P(h.collectLogs)}).`):t.info(`ai-gateway: AI Gateway "${o}" already exists — reusing it (log collection ${P(h.collectLogs)}; change it in the dashboard).`),l.changed.length>0?(ve(s,l.text,"utf8"),t.success(`ai-gateway: wrote ${l.changed.join(", ")} into ${s} vars.`)):t.info(`ai-gateway: ${s} vars are already up to date.`),Ye(t,o),{code:0,data:{accountId:c,action:h.action,collectLogs:h.collectLogs,dryRun:!1,gatewayId:o,varsWritten:l.changed,wranglerPath:s}})},Qe=15e3,Ze="https://api.cloudflare.com/client/v4/graphql",E={analytics:"Account Analytics Read",notificationsRead:"Notifications Read",notificationsWrite:"Notifications Write"},et={auth:u.AUTH,failure:u.FAILURE,"not-found":u.NOT_FOUND,permission:u.PERMISSION,"rate-limited":u.RATE_LIMITED,rejected:u.USAGE,unavailable:u.UNAVAILABLE};class _ extends Error{kind;status;constructor(t,r,o){super(t),this.name="CloudflareApiError",this.kind=r,this.status=o}}const tt=e=>e===401?"auth":e===403?"permission":e===404?"not-found":e===429?"rate-limited":e>=500?"unavailable":"rejected",Se=e=>{try{const r=JSON.parse(e);if(Array.isArray(r.errors)){const o=r.errors.map(n=>typeof n=="object"&&n!==null&&"message"in n?String(n.message):void 0).filter(n=>n!==void 0&&n.length>0);if(o.length>0)return o}}catch{}const t=e.trim().slice(0,200);return t.length>0?[t]:[]},rt=9106,ot=(e,t)=>{try{const r=JSON.parse(e);return Array.isArray(r.errors)&&r.errors.some(o=>typeof o=="object"&&o!==null&&"code"in o&&o.code===t)}catch{return!1}},re=(e,t,r,o)=>{const n=t===400&&ot(r,rt)?"auth":tt(t),a=Se(r).join("; ");let s="";return n==="auth"?s=` — check that CLOUDFLARE_API_TOKEN is a valid token; it needs the "${o}" permission on this account`:n==="permission"&&(s=` — the API token needs the "${o}" permission on this account`),new _(`${e} failed (HTTP ${String(t)})${a.length>0?`: ${a}`:""}${s}.`,n,t)},oe=(e,t)=>{const r=t instanceof Error&&(t.name==="TimeoutError"||t.name==="AbortError");let o=t instanceof Error?t.message:String(t);return r&&(o="no answer in time"),new _(`${e} failed: ${o}.`,"unavailable")},nt=10,at=e=>{const{accountId:t,token:r}=e,o=e.timeoutMs??Qe,n=e.fetch??globalThis.fetch.bind(globalThis),a=async(s,i,c,d)=>{let f;try{f=await Pe({accountId:t,apiToken:r,fetch:n,init:{method:i,signal:AbortSignal.timeout(o),...d===void 0?{}:{body:JSON.stringify(d)}},path:c})}catch(l){throw oe(s,l)}if(!f.ok)throw re(s,f.status,f.text,i==="GET"?E.notificationsRead:E.notificationsWrite);return f.body};return{accountId:t,graphql:async(s,i,c={})=>{let d;try{d=await n(Ze,{body:JSON.stringify({query:i,variables:c}),headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(o)})}catch(l){throw oe(s,l)}const f=await d.text();if(!d.ok)throw re(s,d.status,f,E.analytics);try{const l=JSON.parse(f);return{data:l.data??void 0,errors:Array.isArray(l.errors)&&l.errors.length>0?Se(f):[]}}catch{throw new _(`${s} failed: the GraphQL API answered with something that is not JSON.`,"unavailable",d.status)}},notifications:async(s,i,c,d)=>(await a(s,i,c,d)).result,notificationsList:async(s,i,c=nt)=>{const d=async f=>{const l=await a(s,"GET",`${i}${i.includes("?")?"&":"?"}page=${String(f)}`),h=Array.isArray(l.result)?l.result:[],g=l.result_info;return typeof g?.total_pages=="number"&&f<g.total_pages&&f<c?[...h,...await d(f+1)]:h};return d(1)}}},A="billing_usage_alert",ne={"do-duration":"worker_durable_objects_duration","do-requests":"worker_durable_objects_requests","do-rows-read":"worker_durable_objects_storage_reads","do-rows-written":"worker_durable_objects_storage_writes","workers-requests":"worker_requests"},st=6,C=e=>typeof e=="object"&&e!==null&&!Array.isArray(e)?e:void 0,U=(e,t)=>{const r=t.toLowerCase(),o=Object.keys(e).find(n=>n.toLowerCase()===r);return o===void 0?void 0:e[o]},G=(e,t)=>{for(const r of t){const o=U(e,r);if(typeof o=="string"&&o.length>0)return o}},it=e=>{if(typeof e=="string"&&e.length>0)return{id:e,source:"available-alerts"};const t=C(e),r=t===void 0?void 0:G(t,["id","value","key","name"]);if(t===void 0||r===void 0)return;const o=G(t,["description","label","display_name","displayName","name"]);return{id:r,source:"available-alerts",...o===void 0||o===r?{}:{label:o}}},ct=["AvailableValues","available_values","values","options","enum","choices"],_e=e=>e.map(t=>it(t)).filter(t=>t!==void 0),dt=e=>G(e,["key","name","filter","id"])!=="product"?[]:ct.flatMap(t=>{const r=U(e,t);return Array.isArray(r)?_e(r):[]}),F=(e,t,r)=>{if(t>st)return;if(Array.isArray(e)){for(const n of e)F(n,t+1,r);return}const o=C(e);if(o!==void 0){r.push(...dt(o));for(const[n,a]of Object.entries(o))n.toLowerCase()==="product"&&Array.isArray(a)?r.push(..._e(a)):typeof a=="object"&&a!==null&&F(a,t+1,r)}},lt=e=>{const t=C(e);if(t!==void 0){for(const r of Object.values(t))if(Array.isArray(r)){const o=r.map(n=>C(n)).find(n=>n!==void 0&&U(n,"type")===A);if(o!==void 0)return o}}},ut=e=>e.filter(t=>t.alert_type===A&&Array.isArray(t.filters?.product)).flatMap(t=>(t.filters?.product).filter(r=>typeof r=="string"&&r.length>0)).map(t=>({id:t,source:"existing-policy"})),ft=(e,t,r)=>{const o=lt(e),n=[];o!==void 0&&F(U(o,"filter_options"),0,n);const a=new Map;for(const s of[...n,...ut(r)])a.has(s.id)||a.set(s.id,s);return{eligible:t?o!==void 0:void 0,products:[...a.values()]}},mt=/[^a-z\d]+/u,ht=e=>`${e.id} ${e.label??""}`.toLowerCase().split(mt).filter(t=>t.length>0),gt={"d1-rows-read":{all:[["d1"],["read"]]},"d1-rows-written":{all:[["d1"],["writ"]]},"do-duration":{all:[["durable"],["duration"]]},"do-requests":{all:[["durable"],["request"]]},"do-rows-read":{all:[["durable"],["read"]],none:["request"]},"do-rows-written":{all:[["durable"],["writ"]]},"workers-cpu":{all:[["worker"],["cpu"]],none:["durable"]},"workers-requests":{all:[["worker"],["request"]],none:["durable","cpu","kv","d1"]}},pt=(e,t)=>{const r=ht(t),o=s=>r.some(i=>i.startsWith(s)),{all:n,none:a=[]}=gt[e];return n.every(s=>s.some(i=>o(i)))&&!a.some(s=>o(s))},Ee=(e,t)=>{const r=ne[e],o=r===void 0?void 0:t.find(s=>s.id===r);if(o!==void 0)return{product:o,status:"matched"};const n=new Set(Object.values(ne)),a=t.filter(s=>!n.has(s.id)&&pt(e,s));return a.length===1&&a[0]!==void 0?{product:a[0],status:"matched"}:a.length>1?{candidates:a.map(s=>s.id),status:"ambiguous"}:r===void 0?{status:"no-product"}:{product:{id:r,source:"documented"},status:"matched"}},ae="workersInvocationsAdaptive",yt="durableObjectsInvocationsAdaptiveGroups",M="durableObjectsPeriodicGroups",se="d1AnalyticsAdaptiveGroups",S=[{candidates:[{field:"requests",unit:"count"}],dataset:ae,filter:"datetime",id:"workers-requests",label:"Workers requests"},{candidates:[{field:"cpuTimeUs",unit:"microseconds"}],dataset:ae,filter:"datetime",id:"workers-cpu",label:"Workers CPU time"},{candidates:[{field:"requests",unit:"count"}],dataset:yt,filter:"date",id:"do-requests",label:"Durable Objects requests"},{candidates:[{field:"activeTime",unit:"unknown"}],dataset:M,filter:"date",id:"do-duration",label:"Durable Objects duration"},{candidates:[{field:"rowsRead",unit:"count"}],dataset:M,filter:"date",id:"do-rows-read",label:"Durable Objects rows read"},{candidates:[{field:"rowsWritten",unit:"count"}],dataset:M,filter:"date",id:"do-rows-written",label:"Durable Objects rows written"},{candidates:[{field:"rowsRead",unit:"count"}],dataset:se,filter:"datetime-hour",id:"d1-rows-read",label:"D1 rows read"},{candidates:[{field:"rowsWritten",unit:"count"}],dataset:se,filter:"datetime-hour",id:"d1-rows-written",label:"D1 rows written"}],ie=e=>String(e).padStart(2,"0"),wt=e=>{const t=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth()-1,1)),r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),0)),o=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),1)),n=a=>`${String(a.getUTCFullYear())}-${ie(a.getUTCMonth()+1)}-${ie(a.getUTCDate())}`;return{endDate:n(r),nextStartDate:n(o),startDate:n(t)}},vt=(e,t)=>{const r=JSON.stringify(`${t.startDate}T00:00:00Z`),o=JSON.stringify(`${t.nextStartDate}T00:00:00Z`);switch(e){case"date":return`{ date_geq: ${JSON.stringify(t.startDate)}, date_lt: ${JSON.stringify(t.nextStartDate)} }`;case"datetime":return`{ datetime_geq: ${r}, datetime_lt: ${o} }`;default:return`{ AND: [{ datetimeHour_geq: ${r}, datetimeHour_lt: ${o} }] }`}},bt=e=>`query LunoraTypeFields { __type(name: ${JSON.stringify(e)}) { fields { name type { name ofType { name ofType { name ofType { name } } } } } } }`,$t=e=>{let t=e;for(;t!=null;){if(typeof t.name=="string"&&t.name.length>0)return t.name;t=t.ofType}},At=e=>{const t=new Map;return async r=>{const o=t.get(r);if(o!==void 0)return o;const n=e.graphql(`Reading the analytics schema (${r})`,bt(r)).then(a=>{if(a.errors.length>0)throw new _(`Reading the analytics schema failed: ${a.errors.join("; ")}.`,"rejected");const i=a.data?.__type?.fields??[];return new Map(i.map(c=>[c.name,$t(c.type)]))});return t.set(r,n),n}},Te=async(e,t,r)=>{const o=await e(t),[n,...a]=r;if(n===void 0)return o;const s=o.get(n);if(s===void 0)throw new _(`the analytics schema has no \`${n}\` on \`${t}\``,"rejected");return Te(e,s,a)},St=e=>e instanceof Error?e.message:String(e),_t=async(e,t,r,o,n)=>{const{id:a,label:s}=o;try{const i=await Te(t,r,["viewer","accounts",o.dataset,"sum"]),c=o.candidates.find(y=>i.has(y.field));if(c===void 0){const y=o.candidates.map(m=>`\`${m.field}\``).join(", ");return{id:a,label:s,reason:`\`${o.dataset}.sum\` exposes no ${y} field`,status:"unavailable"}}const d=`query LunoraUsage($accountTag: string) { viewer { accounts(filter: { accountTag: $accountTag }) { ${o.dataset}(limit: 1, filter: ${vt(o.filter,n)}) { sum { ${c.field} } } } } }`,f=await e.graphql(`Reading ${s}`,d,{accountTag:e.accountId});if(f.errors.length>0)return{id:a,label:s,reason:f.errors.join("; "),status:"unavailable"};const l=f.data?.viewer?.accounts?.[0]?.[o.dataset],h=Array.isArray(l)?l[0]:void 0,g=h===void 0?0:h.sum?.[c.field];return typeof g!="number"||!Number.isFinite(g)?{id:a,label:s,reason:`the response carried no number for \`${c.field}\``,status:"unavailable"}:{field:`${o.dataset}.sum.${c.field}`,id:a,label:s,status:"ok",unit:c.unit,value:g}}catch(i){return{id:a,label:s,reason:St(i),status:"unavailable"}}},Et=async(e,t)=>{const r=await e.graphql("Reading the analytics schema","query LunoraQueryRoot { __schema { queryType { name } } }"),o=r.data?.__schema?.queryType?.name;if(r.errors.length>0||o===void 0){const a=r.errors.length>0?r.errors.join("; "):"the schema root could not be read";return S.map(({id:s,label:i})=>({id:s,label:i,reason:a,status:"unavailable"}))}const n=At(e);return Promise.all(S.map(async a=>_t(e,n,o,a,t)))},D="Lunora usage: ",Tt=3,Y="Cloudflare does not document the unit of a usage alert's limit; limits are sent as plain request counts. Check the stored limit in the dashboard.",It={"do-requests":1e6,"workers-requests":1e7},kt=100,Ot=e=>{const t=Math.ceil(e);if(t<=0)return 0;const r=10**Math.max(0,Math.floor(Math.log10(t))-1);return Math.ceil(t/r)*r},Ct=(e,t,r)=>{const o=Ot(e*t);return o>r?{basis:"history",threshold:o}:{basis:"floor",threshold:r}},x=e=>{const t=e.filters?.product;return Array.isArray(t)?t.filter(r=>typeof r=="string"):[]},Nt=e=>{const t=e.filters?.limit,r=Array.isArray(t)?t[0]:void 0,o=typeof r=="string"?Number(r):Number.NaN;return Number.isFinite(o)?o:void 0},K=e=>{const t={};for(const[r,o]of Object.entries(e?.mechanisms??{}))Array.isArray(o)&&(t[r]=o.filter(n=>typeof n=="object"&&n!==null&&"id"in n&&typeof n.id=="string"));return t},Ut=(e,t)=>{const r={};for(const[o,n]of[...Object.entries(e),...Object.entries(t)]){const a=new Set((r[o]??[]).map(s=>s.id));r[o]=[...r[o]??[],...n.filter(s=>!a.has(s.id))]}return Object.fromEntries(Object.entries(r).filter(([,o])=>o.length>0))},N=e=>Object.entries(e).flatMap(([t,r])=>r.map(o=>`${t}:${o.id}`)),Dt=(e,t)=>JSON.stringify(N(e).toSorted((r,o)=>r.localeCompare(o)))===JSON.stringify(N(t).toSorted((r,o)=>r.localeCompare(o))),Rt=(e,t)=>e.enabled===!0&&e.name===t.name&&JSON.stringify(e.filters?.limit)===JSON.stringify(t.filters.limit)&&JSON.stringify(x(e))===JSON.stringify(t.filters.product)&&Dt(K(e),t.mechanisms),Mt=(e,t)=>{const r={};return e.length>0&&(r.email=e.map(o=>({id:o}))),t.length>0&&(r.webhooks=t.map(o=>({id:o}))),r},jt=(e,t,r)=>{const o=r.thresholds?.[e],n=r.usage.find(c=>c.id===e),a=n?.status==="ok"?n.value:void 0;if(o!==void 0)return{basis:"explicit",lastMonth:a,threshold:o};const s=It[e];return s===void 0?{kind:"unit-unverified",reason:`the unit Cloudflare meters "${t.id}" in is not documented and cannot be matched to the usage reading; set one yourself with --threshold ${e}=<n>`}:a!==void 0?{...Ct(a,r.multiplier,s),lastMonth:a}:r.allowFloor===!0?{basis:"floor-usage-unavailable",lastMonth:void 0,threshold:s}:{kind:"usage-unavailable",reason:`last month's usage could not be read (${n?.status==="unavailable"?n.reason:"no reading"}), and an alert at the floor could fire every month on an account that already exceeds it; pass --threshold ${e}=<n>, or --allow-floor`}},Lt=(e,t)=>{const r=Nt(e);if(r===void 0||t===void 0||r<=0||t<=0)return;const o=Math.max(r/t,t/r);return o>=kt?`its limit ${String(r)} differs from the ${String(t)} this would plan by ${String(Math.round(o))}× — one of the two may be in the wrong unit`:void 0},q=e=>`${D}${e}`,Pt=(e,t)=>e===void 0?"create":Rt(e,t)?"unchanged":"update",Gt=(e,t)=>{switch(e){case"explicit":return"set with --threshold";case"floor-usage-unavailable":return"the included monthly allowance, applied with --allow-floor because usage could not be read";case"history":return`${String(t)}× last month`;default:return"the included monthly allowance (floor)"}},Ft=(e,t,r)=>{const{id:o,label:n}=e,a=Ee(o,t.products);if(a.status==="no-product")return{kind:"no-product",metric:o,reason:"Cloudflare has no usage alert for this product; the account-wide budget alert is its guard"};if(a.status==="ambiguous")return{kind:"ambiguous",metric:o,reason:`several offered products could mean this metric (${a.candidates.join(", ")})`};const{product:s}=a,i=r.find(p=>p.name===q(n)),c=r.find(p=>p.enabled===!0&&p.name?.startsWith(D)!==!0&&x(p).includes(s.id)),d=jt(o,s,t);if(i===void 0&&c!==void 0){const p=Lt(c,"threshold"in d?d.threshold:void 0);return{kind:"covered",metric:o,reason:`already alerted by the existing policy "${c.name??c.id??"?"}"`,...p===void 0?{}:{warning:p}}}if(!("threshold"in d))return{...d,metric:o};const f=Mt(t.emails,t.webhooks),l=K(i),h=i===void 0||t.replaceRecipients===!0?f:Ut(l,f),g=new Set(N(l)),y=new Set(N(h)),m={alert_type:A,description:`Managed by \`lunora cloudflare alerts setup\`: ${Gt(d.basis,t.multiplier)}.`,enabled:!0,filters:{limit:[String(d.threshold)],product:[s.id]},mechanisms:h,name:q(n)},w=i===void 0?[]:x(i);return{action:Pt(i,m),basis:d.basis,body:m,lastMonth:d.lastMonth,metric:o,...i?.id===void 0?{}:{policyId:i.id},...i!==void 0&&JSON.stringify(w)!==JSON.stringify([s.id])?{previousProduct:w}:{},product:s.id,productSource:s.source,recipients:{added:[...y].filter(p=>!g.has(p)),removed:[...g].filter(p=>!y.has(p))},threshold:d.threshold}},xt=e=>{const t=e.policies.filter(n=>n.alert_type===A),r=new Set(S.map(n=>q(n.label))),o={alerts:[],duplicates:[],orphans:t.filter(n=>n.name?.startsWith(D)===!0&&!r.has(n.name)),skipped:[]};for(const n of r)o.duplicates.push(...t.filter(a=>a.name===n).slice(1));for(const n of S){const a=Ft(n,e,t);"action"in a?o.alerts.push(a):o.skipped.push(a)}return o},qt="Cloudflare's API has no endpoint that sends a test notification — not for webhooks and not for email — so `lunora cloudflare alerts test` sends nothing.",Bt="To test a webhook for real, create it in the dashboard (Alerts > Destinations > Webhooks > Create) and finish with Save and Test.",Jt="Email destinations cannot be tested at all: Cloudflare offers no test send for them. Check the address on each policy below instead.",ce=e=>Array.isArray(e)?e:[],de=30,Wt=async(e,t,r,o,n)=>{const a=t.filter(m=>m.alert_type===A&&m.name?.startsWith(D)===!0),s=a.map(m=>K(m)),i=new Set([...s.flatMap(m=>(m.webhooks??[]).map(w=>w.id)),...r]),c=[...new Set(s.flatMap(m=>(m.email??[]).map(w=>w.id)))],d=new Set(a.map(m=>m.id)),f=new Date(n.getTime()-de*24*60*60*1e3).toISOString(),[l,h]=await Promise.all([e.notifications("Listing webhook destinations","GET","/alerting/v3/destinations/webhooks"),a.length===0?Promise.resolve([]):e.notificationsList("Reading notification history",`/alerting/v3/history?per_page=25&since=${encodeURIComponent(f)}`)]),g=ce(l).map(m=>({...m,referenced:m.id!==void 0&&i.has(m.id)})),y=ce(h).filter(m=>m.alert_type===A&&m.policy_id!==void 0&&d.has(m.policy_id));o.warn(qt),a.length===0&&o.info("No `lunora cloudflare alerts setup` policies exist on this account yet — run `lunora cloudflare alerts setup` first.");for(const m of[...i].filter(w=>!g.some(p=>p.id===w)))o.warn(`Webhook destination ${m} is referenced but does not exist on this account.`);for(const m of g.filter(w=>w.referenced))o.info(`webhook "${m.name??m.id??"?"}" (${m.type??"generic"}): last delivered ${m.last_success??"never"}, last failed ${m.last_failure??"never"}`);if(o.info(Bt),c.length>0&&o.info(`${Jt} Addresses on the policies: ${c.join(", ")}.`),y.length===0)o.info(`Cloudflare's notification history shows nothing sent for these policies in the last ${String(de)} days (they only fire once a threshold is crossed).`);else for(const m of y)o.info(`sent ${m.sent??"?"}: "${m.name??"?"}" via ${m.mechanism_type??"?"} to ${m.mechanism??"?"}`);return{emails:c,history:y,testSent:!1,webhooks:g}},R="Budget alert (account-wide USD threshold): Cloudflare documents no API to create or read one, so this cannot be checked. Create it in the dashboard: Manage Account > Billing > Billable Usage > Create budget alert.",Ht="Alerts > Overview > Create an Alert, alert type Usage Based Billing",b=(e,t,r,o)=>(e.error(r),{code:t,error:r,...o===void 0?{}:{data:o}}),V=(e,t,r)=>{if(t instanceof _)return b(e,et[t.kind],t.message,r);throw t},z=(e,t)=>({accountId:e.accountId,budgetAlert:{checkable:!1,dashboardPath:"Manage Account > Billing > Billable Usage"},limitUnitCaveat:Y,...t===void 0?{}:{eligible:t.discovery.eligible,period:t.period,products:t.discovery.products}}),I=e=>e.toLocaleString("en-US",{maximumFractionDigits:0}),Ie=(e,t)=>{t===!1&&e.warn("This account is not eligible for Usage Based Billing notifications (they are offered to Pay-as-you-go accounts; most Enterprise contracts are not supported).")},ke=e=>({code:u.SUCCESS,data:e}),Yt=/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/u,Oe=new Set(S.map(e=>e.id)),Kt=e=>Oe.has(e),Vt=e=>{const t={};for(const r of e.flatMap(o=>o.split(","))){const[o="",n=""]=r.split("=").map(s=>s.trim()),a=Number(n);if(!Kt(o))return{error:`--threshold "${r}": unknown metric "${o}" — one of ${[...Oe].join(", ")}.`};if(!Number.isFinite(a)||a<=0)return{error:`--threshold "${r}": the limit must be a positive number.`};t[o]=Math.ceil(a)}return t},zt=e=>{const t=(e.emails??[]).flatMap(s=>s.split(",")).map(s=>s.trim()).filter(s=>s.length>0),r=(e.webhooks??[]).map(s=>s.trim()).filter(s=>s.length>0),o=e.multiplier===void 0?Tt:Number(e.multiplier),n=Vt(e.thresholds??[]);if(t.length===0&&r.length===0)return{error:"alerts setup needs somewhere to send the alerts: pass --email <address> and/or --webhook <destination-id>."};const a=t.find(s=>!Yt.test(s));return a!==void 0?{error:`--email "${a}" is not an email address.`}:!Number.isFinite(o)||o<1?{error:`--multiplier must be a number of at least 1 (got "${e.multiplier??""}").`}:"error"in n?n:{allowFloor:e.allowFloor===!0,emails:t,multiplier:o,replaceRecipients:e.replaceRecipients===!0,thresholds:n,webhooks:r}},Xt={explicit:()=>"set with --threshold",floor:e=>`floor: included monthly allowance; last month ${I(e.lastMonth??0)}`,"floor-usage-unavailable":()=>"floor: usage could not be read, applied with --allow-floor",history:e=>`last month ${I(e.lastMonth??0)}`},Qt=e=>{const t=[...e.added.map(r=>`+${r}`),...e.removed.map(r=>`-${r}`)];return t.length===0?"recipients unchanged":`recipients ${t.join(" ")}`},Zt=e=>{const t=e.productSource==="documented"?"documented id":"offered by the account",r=e.previousProduct===void 0?"":`, product was ${e.previousProduct.join(", ")||"none"}`;return`${e.action.padEnd(9)} ${e.body.name} — product ${e.product} (${t}${r}), limit ${I(e.threshold)} (${Xt[e.basis](e)}); ${Qt(e.recipients)}`},B=e=>S.find(t=>t.id===e)?.label??e,er=(e,t,r,o)=>{o.info(`Usage alerts at ${String(t)}× last month (${r.startDate} to ${r.endDate}), never below the included allowance:`);for(const n of e.alerts)o.info(` ${Zt(n)}`);for(const n of e.skipped)o.info(` skipped ${B(n.metric)} — ${n.reason}`),n.warning!==void 0&&o.warn(`${B(n.metric)}: the existing alert ${n.warning}.`);for(const n of e.orphans)o.warn(`"${n.name??"?"}" (${n.id??"?"}) is named like a \`lunora cloudflare alerts\` policy but matches no metric; it was left alone.`);for(const n of e.duplicates)o.warn(`"${n.name??"?"}" exists more than once; ${n.id??"?"} was left alone — delete the extra one in the dashboard.`);e.skipped.some(n=>n.kind==="unit-unverified"||n.kind==="ambiguous")&&o.info(`To set the skipped ones by hand: ${Ht}.`),o.info(Y),o.info(R)},tr=async(e,t)=>{if(e.yes===!0)return;if(e.confirm===void 0&&(!process.stdin.isTTY||e.format==="json"))return b(e.logger,u.USAGE,"alerts setup: refusing to change notification policies without confirmation — re-run with --yes.");const r=t.flatMap(a=>a.recipients.removed),o=r.length===0?"":` This REMOVES recipients: ${[...new Set(r)].join(", ")}.`;if(!await(e.confirm??xe)(`Apply ${String(t.length)} notification policy change(s)?${o}`))return e.logger.info("alerts setup: aborted"),{code:u.CANCELLED,error:"alerts setup: aborted at the confirmation prompt"}},rr=async(e,t,r,o)=>{const a=(await Promise.all(t.map(async s=>e.notifications(`Reading webhook destination ${s}`,"GET",`/alerting/v3/destinations/webhooks/${encodeURIComponent(s)}`).then(()=>{},i=>({error:i,id:s}))))).find(s=>s!==void 0);if(a!==void 0)return a.error instanceof _&&a.error.kind==="not-found"?b(r,u.USAGE,`--webhook ${a.id}: no such webhook destination on account ${e.accountId}.`,o):V(r,a.error,o)},or=async(e,t)=>{const r=t.action==="create"?await e.notifications(`Creating "${t.body.name}"`,"POST","/alerting/v3/policies",t.body):await e.notifications(`Updating "${t.body.name}"`,"PUT",`/alerting/v3/policies/${encodeURIComponent(t.policyId??"")}`,t.body);return typeof r=="object"&&r!==null&&"id"in r&&typeof r.id=="string"?r.id:t.policyId},nr=async(e,t,r)=>await Promise.all(t.map(async({alert:n,id:a})=>{try{const s=await e.notifications(`Reading back "${n.body.name}"`,"GET",`/alerting/v3/policies/${encodeURIComponent(a)}`),i={enabled:s?.enabled,id:a,limit:s?.filters?.limit,name:s?.name,product:s?.filters?.product};return r.info(`Cloudflare stored "${i.name??n.body.name}": product ${JSON.stringify(i.product)}, limit ${JSON.stringify(i.limit)}.`),JSON.stringify(i.limit)!==JSON.stringify(n.body.filters.limit)&&r.warn(`"${n.body.name}": Cloudflare stored limit ${JSON.stringify(i.limit)}, not the ${JSON.stringify(n.body.filters.limit)} sent.`),i}catch(s){return r.warn(`Could not read "${n.body.name}" back: ${s instanceof Error?s.message:String(s)}`),{id:a}}})),ar=async(e,t,r,o)=>{const n=[],a=[];let s;for(const c of t)try{const d=await or(e,c);n.push({action:c.action,metric:c.metric,...d===void 0?{}:{policyId:d}}),d!==void 0&&a.push({alert:c,id:d}),r.success(`${c.action==="create"?"Created":"Updated"} "${c.body.name}".`)}catch(d){s=d;break}const i={...o,applied:n,stored:await nr(e,a,r)};return s===void 0?i:V(r,s,i)},le=(e,t,r)=>{const o=t.skipped.filter(n=>n.kind==="usage-unavailable");return o.length>0?b(r,u.FAILURE,`alerts setup: no alert was set for ${o.map(n=>B(n.metric)).join(", ")} because last month's usage could not be read — pass --threshold <metric>=<n>, or --allow-floor to accept the floor.`,e):{code:u.SUCCESS,data:e}},sr=async(e,t,r,o)=>{const{logger:n}=r,a=z(e,t),s=await rr(e,o.webhooks,n,a);if(s!==void 0)return s;if(t.discovery.eligible===!1)return Ie(n,!1),n.info(R),b(n,u.PERMISSION,"alerts setup: the account cannot create Usage Based Billing notifications.",a);const i=xt({...o,policies:t.policies,products:t.discovery.products,usage:t.usage});a.plan=i,er(i,o.multiplier,t.period,n);const c=i.alerts.filter(l=>l.action!=="unchanged");if(r.dryRun===!0||c.length===0)return n.info(r.dryRun===!0?"Dry run: nothing was changed.":"Every alert is already up to date."),le(a,i,n);const d=await tr(r,c);if(d!==void 0)return{...d,data:a};const f=await ar(e,c,n,a);return"code"in f?f:le(f,i,n)},ir=e=>e.status!=="ok"?`unavailable (${e.reason})`:e.unit==="microseconds"?`${I(e.value)} µs`:I(e.value),cr=(e,t,r)=>t?"no-product":e===void 0?"unknown":r.some(o=>o.enabled)?"active":r.length>0?"disabled-only":"none",ue=e=>e.map(t=>`"${t.name??t.id??"?"}" (limit ${JSON.stringify(t.limit)})${t.enabled?"":" — disabled, not protecting you"}`).join(", "),dr={active:e=>`alert: ${ue(e.alerts)}`,"disabled-only":e=>`alert: none active; ${ue(e.alerts)}`,"no-product":()=>"alert: none possible — Cloudflare has no usage alert for this; the budget alert is its guard",none:()=>"alert: none",unknown:()=>"alert: unknown (several offered products could mean this)"},lr=(e,t,r)=>{const o=Ee(e.id,t.discovery.products),n=o.status==="matched"?o.product:void 0,a=(n===void 0?[]:r.filter(s=>Array.isArray(s.filters?.product)&&s.filters.product.includes(n.id))).map(s=>({enabled:s.enabled===!0,id:s.id,limit:s.filters?.limit,name:s.name}));return{alerts:a,coverage:cr(n?.id,o.status==="no-product",a),id:e.id,label:e.label,...n===void 0?{}:{product:n.id,productSource:n.source},usage:e}},ur=(e,t,r)=>{const o=t.policies.filter(a=>a.alert_type===A),n=t.usage.map(a=>lr(a,t,o));r.info(`Cloudflare account ${e.accountId} — usage ${t.period.startDate} to ${t.period.endDate}`);for(const a of n)r.info(` ${a.label}: ${ir(a.usage)} — ${dr[a.coverage](a)}`);return r.info(`Usage Based Billing notifications on the account: ${String(o.length)} (${String(o.filter(a=>a.enabled===!0).length)} enabled).`),r.info(Y),Ie(r,t.discovery.eligible),r.info(R),ke({...z(e,t),metrics:n})},fr=e=>{const t=J(e);return t===void 0?void 0:W(t).parsed?.account_id},mr=e=>{const{accountId:t,token:r}=H(e.environment??process.env,fr(e.cwd));return r===void 0?b(e.logger,u.AUTH,`CLOUDFLARE_API_TOKEN is not set. \`lunora cloudflare alerts\` calls the Cloudflare API with an API token (a wrangler login session is not reused); create one with the "${E.notificationsWrite}" and "${E.analytics}" permissions ("${E.notificationsRead}" is enough for status and test).`):t===void 0?b(e.logger,u.USAGE,"No Cloudflare account: set CLOUDFLARE_ACCOUNT_ID or `account_id` in wrangler.jsonc."):at({accountId:t,token:r,...e.fetch===void 0?{}:{fetch:e.fetch}})},Ce=async e=>{const t=await e.notifications("Listing notification policies","GET","/alerting/v3/policies");return Array.isArray(t)?t:[]},hr=async(e,t)=>{const r=wt(t.now??new Date),{logger:o}=t,[n,a,s]=await Promise.all([Ce(e),e.notifications("Listing the alert types this account is eligible for","GET","/alerting/v3/available_alerts").then(i=>({ok:!0,value:i}),i=>({error:i,ok:!1})),Et(e,r).catch(i=>{const c=i instanceof Error?i.message:String(i);return o.warn(`Usage could not be read: ${c}`),S.map(({id:d,label:f})=>({id:d,label:f,reason:c,status:"unavailable"}))})]);return a.ok||o.warn(`Could not read which alert types the account is eligible for: ${a.error instanceof Error?a.error.message:String(a.error)}`),{discovery:ft(a.ok?a.value:void 0,a.ok,n),period:r,policies:n,usage:s}},gr=async(e,t)=>{const r=await Ce(e),o=await Wt(e,r,t.webhooks??[],t.logger,t.now??new Date);return t.logger.info(R),ke({...z(e),deliveries:o})},pr=async e=>{const t=e.subcommand==="setup"?zt(e):void 0;if(t!==void 0&&"error"in t)return b(e.logger,u.USAGE,t.error);const r=mr(e);if(!("graphql"in r))return r;try{if(e.subcommand==="test")return await gr(r,e);const o=await hr(r,e);return t===void 0?ur(r,o,e.logger):await sr(r,o,e,t)}catch(o){return V(e.logger,o)}},fe=new Set(["build","delete","images","info","list","push"]),yr=new Set(["build","push"]),me=new Set(["images list","info","list"]),wr=async e=>{const[t,...r]=e.argument;if(t===void 0||!fe.has(t)){const l=`lunora cloudflare containers requires a subcommand: ${[...fe].toSorted((h,g)=>h.localeCompare(g)).join(" | ")}. Example: lunora cloudflare containers build ./containers/app --tag app:v1 --push`;return e.logger.error(l),{code:u.USAGE,error:l}}const o=e.format==="json",n=t==="images"?`images ${r[0]??""}`.trim():t;if(o&&!me.has(n)){const l=`containers ${n}: --format json is only available for the read subcommands (${[...me].toSorted((h,g)=>h.localeCompare(g)).join(" | ")}) — wrangler has no JSON rendering for the rest.`;return e.logger.error(l),{code:u.USAGE,error:l}}if(yr.has(t)&&!(e.dockerAvailable??qe)()){const l=`containers ${t} needs a running Docker-compatible engine (it builds/pushes images locally). Start Docker or Colima and retry. Note: container images must target linux/amd64.`;return e.logger.error(l),{code:u.MISSING_DEPENDENCY,error:l}}const a=["containers",t,...r];o&&a.push("--json"),e.tag!==void 0&&a.push("--tag",e.tag),e.push===!0&&a.push("--push"),e.env!==void 0&&a.push("--env",e.env);const s=e.cwd??process.cwd(),i=$e(Ae(s),"wrangler",a),c={args:i.args,command:i.command,cwd:s};return e.logger.info(`running ${c.command} ${c.args.join(" ")}`),{code:(await(e.spawner??be)(c)).code,delegated:o,descriptor:c}},k=(e,t)=>(t!==void 0&&e.push("--env",t),e),vr=e=>{const t=k(["deployments","list"],e.env);return e.format==="json"&&t.push("--json"),t},br=e=>{switch(e.subcommand){case"inspect":return e.versionId===void 0?{error:"deployments inspect requires a version id. Usage: lunora cloudflare deployments inspect <version-id>"}:{args:k(["versions","view",e.versionId],e.env)};case"list":return{args:vr(e)};case"promote":{if(e.versionId===void 0)return{error:"deployments promote requires a version id. Usage: lunora cloudflare deployments promote <version-id> --yes"};if(!e.yes)return{error:"deployments promote shifts 100% of live traffic. Re-run with --yes to confirm."};const t=k(["versions","deploy",`${e.versionId}@100%`,"--yes"],e.env);return e.message!==void 0&&t.push("--message",e.message),{args:t}}case"rollback":{if(!e.yes)return{error:"deployments rollback changes the live version. Re-run with --yes to confirm."};const t=k(["rollback"],e.env);return e.versionId!==void 0&&t.push(e.versionId),t.push("--yes"),e.message!==void 0&&t.push("--message",e.message),{args:t}}default:return{error:`deployments: unknown subcommand "${e.subcommand}"`}}},$r=async e=>{if(e.format==="json"&&e.subcommand!=="list"){const d=`deployments ${e.subcommand}: --format json is only available for \`deployments list\` — wrangler has no JSON rendering for the others.`;return e.logger.error(d),{code:u.USAGE,descriptor:void 0,error:d}}const{args:t,error:r}=br(e);if(r!==void 0||t===void 0)return e.logger.error(r??"deployments: nothing to run"),{code:u.USAGE,descriptor:void 0,error:r};const{logger:o}=e,n=e.cwd??process.cwd(),a=$e(Ae(n),"wrangler",t),s={args:a.args,command:a.command,cwd:n};return o.info(`${s.command} ${s.args.join(" ")}`),{code:(await(e.spawner??be)(s)).code,delegated:e.format==="json",descriptor:s}},Ar=e=>e==="list"||e==="inspect"||e==="rollback"||e==="promote",Sr="https://api.cloudflare.com/client/v4/accounts",Ne="Workers Scripts Read",he=1e3,ge=5e4,_r=1e4,Er=3e4,pe=[31,139],ye=["cpu","heap"],Tr=/^[\da-f]{64}$/iu,v=(e,t,r)=>(e.error(r),{code:t,error:r}),we=e=>{try{const t=JSON.parse(e);if(Array.isArray(t.errors)){const r=t.errors.map(o=>typeof o=="object"&&o!==null&&"message"in o?String(o.message):"").filter(o=>o.length>0);if(r.length>0)return L(r.join("; "))}}catch{}return L(e.trim())},Ir=e=>{switch(e){case 401:case 403:return` — check that CLOUDFLARE_API_TOKEN is valid and has the "${Ne}" permission on this account`;case 404:return" — check the worker name, version, and (for a Durable Object) the namespace and actor ids";case 429:return" — rate limited; wait and retry";default:return""}},kr=e=>{const t=e.type??"cpu";if(!ye.includes(t))return{error:`profile: invalid --type "${e.type??""}" — expected ${ye.join(" | ")}`};const r=e.durationMs===void 0?_r:Number(e.durationMs);return!Number.isInteger(r)||r<he||r>ge?{error:`profile: invalid --duration-ms "${e.durationMs??""}" — expected an integer from ${String(he)} to ${String(ge)}`}:e.namespaceId===void 0!=(e.actorId===void 0)?{error:"profile: --namespace-id and --actor-id must be passed together to profile a Durable Object"}:e.actorId!==void 0&&!Tr.test(e.actorId)?{error:"profile: --actor-id must be a 64-character hexadecimal Durable Object id"}:{durationMs:r,profileType:t}},Or=e=>{const{logger:t}=e,r=J(e.cwd),o=r===void 0?void 0:W(r).parsed,{accountId:n,token:a}=H(e.environment??process.env,o?.account_id);if(a===void 0)return v(t,u.AUTH,`CLOUDFLARE_API_TOKEN is not set. \`lunora cloudflare profile\` calls the Cloudflare API with an API token (a wrangler login session is not reused); create one with the "${Ne}" permission.`);if(n===void 0)return v(t,u.USAGE,"No Cloudflare account: set CLOUDFLARE_ACCOUNT_ID or `account_id` in wrangler.jsonc.");const s=typeof o?.name=="string"&&o.name.length>0?o.name:void 0,i=e.worker??s;if(i===void 0)return v(t,u.USAGE,"profile: no Worker name — pass one, or set `name` in wrangler.jsonc.");const c=e.env===void 0?void 0:o?.env?.[e.env]?.name;let d=i;return e.worker===void 0&&e.env!==void 0&&(d=typeof c=="string"&&c.length>0?c:`${i}-${e.env}`),{accountId:n,sourceMapsMissing:o!==void 0&&o.upload_source_maps!==!0,token:a,worker:d}},Cr=(e,t)=>{const r=`${e}.${String(process.pid)}.tmp`;try{Re(Je(e),{recursive:!0}),ve(r,t),Me(r,e);return}catch(o){try{je(r,{force:!0})}catch{}return o instanceof Error?o.message:String(o)}},Nr=async(e,{accountId:t,token:r,worker:o},{durationMs:n,profileType:a,version:s})=>{const{logger:i}=e,c={duration_ms:n,profile_type:a,...e.namespaceId===void 0||e.actorId===void 0?{}:{actor_id:e.actorId,namespace_id:e.namespaceId}},d=`${Sr}/${encodeURIComponent(t)}/workers/workers/${encodeURIComponent(o)}/versions/${encodeURIComponent(s)}/profile`,f=e.fetch??globalThis.fetch.bind(globalThis);let l,h;try{l=await f(d,{body:JSON.stringify(c),headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(n+Er)}),h=new Uint8Array(await l.arrayBuffer())}catch(g){return v(i,u.UNAVAILABLE,`profile failed: ${g instanceof Error?g.message:String(g)}.`)}if(!l.ok){const g=we(new TextDecoder().decode(h));return v(i,j(l.status),`profile failed (HTTP ${String(l.status)})${g.length>0?`: ${g}`:""}${Ir(l.status)}.`)}if(h[0]!==pe[0]||h[1]!==pe[1]){const g=we(new TextDecoder().decode(h));return v(i,u.FAILURE,`profile failed: the API answered 200 with something that is not a gzip profile${g.length>0?`: ${g}`:""}.`)}return{bytes:h}},Ur=async e=>{const{logger:t}=e,r=kr(e);if("error"in r)return v(t,u.USAGE,r.error);const o=Or(e);if("code"in o)return o;const{durationMs:n,profileType:a}=r,{worker:s}=o,i=e.version??"latest";t.info(`Profiling ${s}@${i} (${a}) for ${String(n)} ms — keep traffic flowing to it…`);const c=await Nr(e,o,{durationMs:n,profileType:a,version:i});if("code"in c)return c;const{bytes:d}=c,f=(e.now??new Date).toISOString().replaceAll(/[:.]/gu,"-"),l=Be(e.cwd,e.out??`${s}-${a}-${f}.pprof.gz`),h=Cr(l,d);return h!==void 0?v(t,u.FAILURE,`profile failed: could not write ${l}: ${h}.`):(t.success(`Wrote ${String(d.byteLength)} bytes to ${l}`),t.info("Open it with `go tool pprof -http=: <file>` (pprof reads gzip directly) or upload it to a flamegraph viewer."),o.sourceMapsMissing&&t.warn("wrangler config has no `upload_source_maps: true` — TypeScript function names in the profile may be mangled."),{code:u.SUCCESS,data:{bytes:d.byteLength,durationMs:n,file:l,profileType:a,version:i,worker:s}})},Dr={ai:{absorbs:"gateway",tool:"ai-gateway"},alert:{tool:"alerts"}},Rr=(e,t)=>{const r=Dr[e];if(r!==void 0)return{rest:r.absorbs!==void 0&&t[0]===r.absorbs?t.slice(1):t,tool:r.tool};const o=O.find(n=>n.name===e)?.name;return o===void 0?void 0:{rest:t,tool:o}},Mr={celld:"celld",node:"Node"},jr=e=>e==="status"||e==="setup"||e==="test",T=(e,t,r)=>(e.error(r),{code:t,error:r}),Lr=e=>{const t=Math.max(...O.map(r=>r.name.length));e.info("lunora cloudflare <tool> — tools for an app deployed to your own Cloudflare account:");for(const r of O)e.info(` ${r.name.padEnd(t)} ${r.summary}`),r.usage!==""&&e.info(` ${" ".repeat(t)} lunora cloudflare ${r.name} ${r.usage}`);return e.info("Run `lunora cloudflare --help` for every tool's arguments and options."),{code:0}},Pr=(e,t,r)=>{const{error:o,target:n}=De(e,r);return n===void 0?o??"no deploy target":n==="cloudflare"?void 0:`${r===void 0?`this project deploys to ${Mr[n]??`"${n}"`}`:`\`--target ${n}\``} — \`lunora cloudflare ${t}\` only applies to Cloudflare.`},Gr=(e,{logger:t,rest:r})=>r.length===0?void 0:T(t,u.USAGE,`cloudflare ${e}: takes no arguments, got "${r.join(" ")}"`),Fr=async e=>{const{cwd:t,logger:r,options:o}=e;return Gr("ai-gateway",e)??Xe({cwd:t,dryRun:o.dryRun===!0,id:o.id,logger:r,logs:o.logs})},xr=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const a=n[0]??"status";return jr(a)?pr({allowFloor:o.allowFloor===!0,cwd:e,dryRun:o.dryRun===!0,format:t,logger:r,replaceRecipients:o.replaceRecipients===!0,subcommand:a,yes:o.yes===!0,...o.email===void 0?{}:{emails:o.email},...o.webhook===void 0?{}:{webhooks:o.webhook},...o.multiplier===void 0?{}:{multiplier:o.multiplier},...o.threshold===void 0?{}:{thresholds:o.threshold}}):T(r,u.USAGE,`cloudflare alerts: unknown subcommand "${a}" — expected status | setup | test`)},qr=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const a=await wr({argument:n,cwd:e,env:o.env,format:t,logger:r,push:o.push===!0,tag:o.tag});return{code:a.code,delegated:a.delegated,error:a.error}},Br=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const[a,s]=n;if(!Ar(a))return T(r,u.USAGE,`cloudflare deployments: unknown subcommand "${a??""}" — expected list | inspect | rollback | promote`);const i=await $r({cwd:e,env:o.env,format:t,logger:r,message:o.message,subcommand:a,versionId:s,yes:o.yes===!0});return{code:i.code,delegated:i.delegated,error:i.error}},Jr=async({cwd:e,logger:t,options:r,rest:o})=>o.length>1?T(t,u.USAGE,`cloudflare profile: takes at most one argument (the worker), got "${o.join(" ")}"`):Ur({actorId:r.actorId,cwd:e,durationMs:r.durationMs,env:r.env,logger:t,namespaceId:r.namespaceId,out:r.out,type:r.type,version:r.versionId,worker:o[0]}),Wr={"ai-gateway":Fr,alerts:xr,containers:qr,deployments:Br,profile:Jr},so=Ue(async({argument:e,cwd:t,format:r,logger:o,options:n})=>{const[a,...s]=e;if(a===void 0||a==="")return Lr(o);const i=Rr(a,s);if(i===void 0)return T(o,u.USAGE,`cloudflare: unknown tool "${a}" — expected ${O.map(f=>f.name).join(" | ")}`);const{tool:c}=i,d=Pr(t,c,n.target);return d!==void 0?T(o,u.USAGE,d):Wr[c]({cwd:t,format:r,logger:o,options:n,rest:i.rest})});export{so as execute};
1
+ import{d as Ue}from"../packem_shared/command-B6vu47iR.mjs";import{r as De}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{exitCodeForStatus as j,EXIT_CODE as u}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{writeFileSync as ve,mkdirSync as Re,renameSync as Me,rmSync as je}from"node:fs";import{findWranglerFile as J,readWranglerJsonc as W,applyModify as Le}from"@lunora/config/cloudflare";import{c as L}from"../packem_shared/cap-error-body-YBKO32BF.mjs";import{r as H,n as X,c as Pe}from"../packem_shared/cloudflare-credentials-BVmZnDZk.mjs";import{c as Ge,b as Fe}from"../packem_shared/variables-CPYyJtsL.mjs";import{b as xe}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{i as qe}from"../packem_shared/docker-DpVxvYpL.mjs";import{defaultSpawner as be}from"../packem_shared/createRecordingSpawner-01ZngIwK.mjs";import{execArgsFor as $e,detectPackageManager as Ae}from"@lunora/config/cli";import{a as O}from"./cli.mjs";import{resolve as Be,dirname as Je}from"node:path";const We="https://api.cloudflare.com/client/v4/accounts",Q=64,Z={byok:"https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/",unifiedBilling:"https://developers.cloudflare.com/ai-gateway/features/unified-billing/"},$=(e,t,r)=>(e.error(r),{code:t,error:r}),ee=async(e,t,r,o={})=>{const n=await e(t,{...o,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}}),a=await n.text();let s;try{s=JSON.parse(a)}catch{}return{body:s,ok:n.ok&&s?.success!==!1,status:n.status,text:a}},te=e=>{const t=(e.body?.errors??[]).map(r=>r.message).filter(r=>typeof r=="string");return`${String(e.status)}: ${L(t.length>0?t.join("; "):e.text)}`},He=(e,t,r)=>{let o=e;const n=[];for(const[a,s]of Object.entries(r))t?.[a]!==s&&(o=Le(o,["vars",a],s),n.push(a));return{changed:n,text:o}},Ye=(e,t)=>{e.info(`next: \`<provider>/<model>\` slugs (e.g. "anthropic/claude-sonnet-5") now route through gateway "${t}". Before the first call,`),e.info(` configure how the provider is paid for in the Cloudflare dashboard (AI > AI Gateway > ${t}):`),e.info(` - Unified Billing (OpenAI, Anthropic, Google, xAI, Groq, DeepSeek): load credits — ${Z.unifiedBilling}`),e.info(` - a key stored on the gateway (Mistral, Perplexity, OpenRouter, …): ${Z.byok}`),e.info(" Then redeploy (`lunora deploy`) so the Worker picks up the new vars.")},Ke=async(e,t,r,o,n,a)=>{const s=`${We}/${encodeURIComponent(r)}/ai-gateway/gateways`,i=await ee(t,`${s}/${encodeURIComponent(n)}`,o);if(i.ok){const d=i.body?.result?.collect_logs;return{action:"existing",collectLogs:typeof d=="boolean"?d:a}}if(i.status===401||i.status===403)return $(e,j(i.status),`ai-gateway: could not read AI Gateway "${n}" (${te(i)})`);const c=await ee(t,s,o,{body:JSON.stringify({cache_invalidate_on_update:!0,cache_ttl:0,collect_logs:a,id:n,rate_limiting_interval:0,rate_limiting_limit:0}),method:"POST"});return c.ok?{action:"created",collectLogs:a}:$(e,j(c.status),`ai-gateway: could not create AI Gateway "${n}" (${te(c)})`)},Ve=e=>{const{logger:t}=e,r=J(e.cwd);if(r===void 0)return $(t,u.NOT_FOUND,"ai-gateway: no wrangler.jsonc found — run `lunora init` (or `lunora dev`) first.");const{parsed:o,text:n}=W(r);if(o===void 0)return $(t,u.USAGE,`ai-gateway: could not parse ${r} as JSONC.`);const a=X(e.id)??X(o.name);return a===void 0?$(t,u.USAGE,"ai-gateway: no gateway id — pass --id <id>, or set `name` in wrangler.jsonc."):a.length>Q?$(t,u.USAGE,`ai-gateway: gateway id "${a}" is longer than ${String(Q)} characters — pass a shorter --id.`):{gatewayId:a,parsed:o,text:n,wranglerPath:r}},P=e=>e?"on":"off",ze=(e,t)=>(e.info(`ai-gateway (dry run): would create or reuse AI Gateway "${t.gatewayId}" (log collection ${P(t.collectLogs)}).`),e.info(t.varsWritten.length===0?`ai-gateway (dry run): ${t.wranglerPath} vars are already up to date.`:`ai-gateway (dry run): would write ${t.varsWritten.join(", ")} into ${t.wranglerPath} vars.`),t.accountId===void 0&&e.warn("ai-gateway (dry run): no CLOUDFLARE_ACCOUNT_ID (or `account_id` in wrangler.jsonc) — a real run will stop there."),{code:0,data:t}),Xe=async e=>{const{logger:t}=e,r=Ve(e);if("code"in r)return r;const{gatewayId:o,parsed:n,text:a,wranglerPath:s}=r,i=e.logs!==!1,{accountId:c,token:d}=H(e.environment??process.env,n.account_id),f={[Fe]:o,...c===void 0?{}:{[Ge]:c}},l=He(a,n.vars,f);if(e.dryRun===!0)return ze(t,{accountId:c,action:"planned",collectLogs:i,dryRun:!0,gatewayId:o,varsWritten:l.changed,wranglerPath:s});if(d===void 0||c===void 0){const g=[d===void 0?"CLOUDFLARE_API_TOKEN (an API token with AI Gateway Write)":void 0,c===void 0?"CLOUDFLARE_ACCOUNT_ID (or `account_id` in wrangler.jsonc)":void 0].filter(y=>y!==void 0);return $(t,u.AUTH,`ai-gateway: missing ${g.join(" and ")}.`)}const h=await Ke(t,e.fetch??globalThis.fetch.bind(globalThis),c,d,o,i);return"code"in h?h:(h.action==="created"?t.success(`ai-gateway: created AI Gateway "${o}" (log collection ${P(h.collectLogs)}).`):t.info(`ai-gateway: AI Gateway "${o}" already exists — reusing it (log collection ${P(h.collectLogs)}; change it in the dashboard).`),l.changed.length>0?(ve(s,l.text,"utf8"),t.success(`ai-gateway: wrote ${l.changed.join(", ")} into ${s} vars.`)):t.info(`ai-gateway: ${s} vars are already up to date.`),Ye(t,o),{code:0,data:{accountId:c,action:h.action,collectLogs:h.collectLogs,dryRun:!1,gatewayId:o,varsWritten:l.changed,wranglerPath:s}})},Qe=15e3,Ze="https://api.cloudflare.com/client/v4/graphql",E={analytics:"Account Analytics Read",notificationsRead:"Notifications Read",notificationsWrite:"Notifications Write"},et={auth:u.AUTH,failure:u.FAILURE,"not-found":u.NOT_FOUND,permission:u.PERMISSION,"rate-limited":u.RATE_LIMITED,rejected:u.USAGE,unavailable:u.UNAVAILABLE};class _ extends Error{kind;status;constructor(t,r,o){super(t),this.name="CloudflareApiError",this.kind=r,this.status=o}}const tt=e=>e===401?"auth":e===403?"permission":e===404?"not-found":e===429?"rate-limited":e>=500?"unavailable":"rejected",Se=e=>{try{const r=JSON.parse(e);if(Array.isArray(r.errors)){const o=r.errors.map(n=>typeof n=="object"&&n!==null&&"message"in n?String(n.message):void 0).filter(n=>n!==void 0&&n.length>0);if(o.length>0)return o}}catch{}const t=e.trim().slice(0,200);return t.length>0?[t]:[]},rt=9106,ot=(e,t)=>{try{const r=JSON.parse(e);return Array.isArray(r.errors)&&r.errors.some(o=>typeof o=="object"&&o!==null&&"code"in o&&o.code===t)}catch{return!1}},re=(e,t,r,o)=>{const n=t===400&&ot(r,rt)?"auth":tt(t),a=Se(r).join("; ");let s="";return n==="auth"?s=` — check that CLOUDFLARE_API_TOKEN is a valid token; it needs the "${o}" permission on this account`:n==="permission"&&(s=` — the API token needs the "${o}" permission on this account`),new _(`${e} failed (HTTP ${String(t)})${a.length>0?`: ${a}`:""}${s}.`,n,t)},oe=(e,t)=>{const r=t instanceof Error&&(t.name==="TimeoutError"||t.name==="AbortError");let o=t instanceof Error?t.message:String(t);return r&&(o="no answer in time"),new _(`${e} failed: ${o}.`,"unavailable")},nt=10,at=e=>{const{accountId:t,token:r}=e,o=e.timeoutMs??Qe,n=e.fetch??globalThis.fetch.bind(globalThis),a=async(s,i,c,d)=>{let f;try{f=await Pe({accountId:t,apiToken:r,fetch:n,init:{method:i,signal:AbortSignal.timeout(o),...d===void 0?{}:{body:JSON.stringify(d)}},path:c})}catch(l){throw oe(s,l)}if(!f.ok)throw re(s,f.status,f.text,i==="GET"?E.notificationsRead:E.notificationsWrite);return f.body};return{accountId:t,graphql:async(s,i,c={})=>{let d;try{d=await n(Ze,{body:JSON.stringify({query:i,variables:c}),headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(o)})}catch(l){throw oe(s,l)}const f=await d.text();if(!d.ok)throw re(s,d.status,f,E.analytics);try{const l=JSON.parse(f);return{data:l.data??void 0,errors:Array.isArray(l.errors)&&l.errors.length>0?Se(f):[]}}catch{throw new _(`${s} failed: the GraphQL API answered with something that is not JSON.`,"unavailable",d.status)}},notifications:async(s,i,c,d)=>(await a(s,i,c,d)).result,notificationsList:async(s,i,c=nt)=>{const d=async f=>{const l=await a(s,"GET",`${i}${i.includes("?")?"&":"?"}page=${String(f)}`),h=Array.isArray(l.result)?l.result:[],g=l.result_info;return typeof g?.total_pages=="number"&&f<g.total_pages&&f<c?[...h,...await d(f+1)]:h};return d(1)}}},A="billing_usage_alert",ne={"do-duration":"worker_durable_objects_duration","do-requests":"worker_durable_objects_requests","do-rows-read":"worker_durable_objects_storage_reads","do-rows-written":"worker_durable_objects_storage_writes","workers-requests":"worker_requests"},st=6,C=e=>typeof e=="object"&&e!==null&&!Array.isArray(e)?e:void 0,U=(e,t)=>{const r=t.toLowerCase(),o=Object.keys(e).find(n=>n.toLowerCase()===r);return o===void 0?void 0:e[o]},G=(e,t)=>{for(const r of t){const o=U(e,r);if(typeof o=="string"&&o.length>0)return o}},it=e=>{if(typeof e=="string"&&e.length>0)return{id:e,source:"available-alerts"};const t=C(e),r=t===void 0?void 0:G(t,["id","value","key","name"]);if(t===void 0||r===void 0)return;const o=G(t,["description","label","display_name","displayName","name"]);return{id:r,source:"available-alerts",...o===void 0||o===r?{}:{label:o}}},ct=["AvailableValues","available_values","values","options","enum","choices"],_e=e=>e.map(t=>it(t)).filter(t=>t!==void 0),dt=e=>G(e,["key","name","filter","id"])!=="product"?[]:ct.flatMap(t=>{const r=U(e,t);return Array.isArray(r)?_e(r):[]}),F=(e,t,r)=>{if(t>st)return;if(Array.isArray(e)){for(const n of e)F(n,t+1,r);return}const o=C(e);if(o!==void 0){r.push(...dt(o));for(const[n,a]of Object.entries(o))n.toLowerCase()==="product"&&Array.isArray(a)?r.push(..._e(a)):typeof a=="object"&&a!==null&&F(a,t+1,r)}},lt=e=>{const t=C(e);if(t!==void 0){for(const r of Object.values(t))if(Array.isArray(r)){const o=r.map(n=>C(n)).find(n=>n!==void 0&&U(n,"type")===A);if(o!==void 0)return o}}},ut=e=>e.filter(t=>t.alert_type===A&&Array.isArray(t.filters?.product)).flatMap(t=>(t.filters?.product).filter(r=>typeof r=="string"&&r.length>0)).map(t=>({id:t,source:"existing-policy"})),ft=(e,t,r)=>{const o=lt(e),n=[];o!==void 0&&F(U(o,"filter_options"),0,n);const a=new Map;for(const s of[...n,...ut(r)])a.has(s.id)||a.set(s.id,s);return{eligible:t?o!==void 0:void 0,products:[...a.values()]}},mt=/[^a-z\d]+/u,ht=e=>`${e.id} ${e.label??""}`.toLowerCase().split(mt).filter(t=>t.length>0),gt={"d1-rows-read":{all:[["d1"],["read"]]},"d1-rows-written":{all:[["d1"],["writ"]]},"do-duration":{all:[["durable"],["duration"]]},"do-requests":{all:[["durable"],["request"]]},"do-rows-read":{all:[["durable"],["read"]],none:["request"]},"do-rows-written":{all:[["durable"],["writ"]]},"workers-cpu":{all:[["worker"],["cpu"]],none:["durable"]},"workers-requests":{all:[["worker"],["request"]],none:["durable","cpu","kv","d1"]}},pt=(e,t)=>{const r=ht(t),o=s=>r.some(i=>i.startsWith(s)),{all:n,none:a=[]}=gt[e];return n.every(s=>s.some(i=>o(i)))&&!a.some(s=>o(s))},Ee=(e,t)=>{const r=ne[e],o=r===void 0?void 0:t.find(s=>s.id===r);if(o!==void 0)return{product:o,status:"matched"};const n=new Set(Object.values(ne)),a=t.filter(s=>!n.has(s.id)&&pt(e,s));return a.length===1&&a[0]!==void 0?{product:a[0],status:"matched"}:a.length>1?{candidates:a.map(s=>s.id),status:"ambiguous"}:r===void 0?{status:"no-product"}:{product:{id:r,source:"documented"},status:"matched"}},ae="workersInvocationsAdaptive",yt="durableObjectsInvocationsAdaptiveGroups",M="durableObjectsPeriodicGroups",se="d1AnalyticsAdaptiveGroups",S=[{candidates:[{field:"requests",unit:"count"}],dataset:ae,filter:"datetime",id:"workers-requests",label:"Workers requests"},{candidates:[{field:"cpuTimeUs",unit:"microseconds"}],dataset:ae,filter:"datetime",id:"workers-cpu",label:"Workers CPU time"},{candidates:[{field:"requests",unit:"count"}],dataset:yt,filter:"date",id:"do-requests",label:"Durable Objects requests"},{candidates:[{field:"activeTime",unit:"unknown"}],dataset:M,filter:"date",id:"do-duration",label:"Durable Objects duration"},{candidates:[{field:"rowsRead",unit:"count"}],dataset:M,filter:"date",id:"do-rows-read",label:"Durable Objects rows read"},{candidates:[{field:"rowsWritten",unit:"count"}],dataset:M,filter:"date",id:"do-rows-written",label:"Durable Objects rows written"},{candidates:[{field:"rowsRead",unit:"count"}],dataset:se,filter:"datetime-hour",id:"d1-rows-read",label:"D1 rows read"},{candidates:[{field:"rowsWritten",unit:"count"}],dataset:se,filter:"datetime-hour",id:"d1-rows-written",label:"D1 rows written"}],ie=e=>String(e).padStart(2,"0"),wt=e=>{const t=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth()-1,1)),r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),0)),o=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),1)),n=a=>`${String(a.getUTCFullYear())}-${ie(a.getUTCMonth()+1)}-${ie(a.getUTCDate())}`;return{endDate:n(r),nextStartDate:n(o),startDate:n(t)}},vt=(e,t)=>{const r=JSON.stringify(`${t.startDate}T00:00:00Z`),o=JSON.stringify(`${t.nextStartDate}T00:00:00Z`);switch(e){case"date":return`{ date_geq: ${JSON.stringify(t.startDate)}, date_lt: ${JSON.stringify(t.nextStartDate)} }`;case"datetime":return`{ datetime_geq: ${r}, datetime_lt: ${o} }`;default:return`{ AND: [{ datetimeHour_geq: ${r}, datetimeHour_lt: ${o} }] }`}},bt=e=>`query LunoraTypeFields { __type(name: ${JSON.stringify(e)}) { fields { name type { name ofType { name ofType { name ofType { name } } } } } } }`,$t=e=>{let t=e;for(;t!=null;){if(typeof t.name=="string"&&t.name.length>0)return t.name;t=t.ofType}},At=e=>{const t=new Map;return async r=>{const o=t.get(r);if(o!==void 0)return o;const n=e.graphql(`Reading the analytics schema (${r})`,bt(r)).then(a=>{if(a.errors.length>0)throw new _(`Reading the analytics schema failed: ${a.errors.join("; ")}.`,"rejected");const i=a.data?.__type?.fields??[];return new Map(i.map(c=>[c.name,$t(c.type)]))});return t.set(r,n),n}},Te=async(e,t,r)=>{const o=await e(t),[n,...a]=r;if(n===void 0)return o;const s=o.get(n);if(s===void 0)throw new _(`the analytics schema has no \`${n}\` on \`${t}\``,"rejected");return Te(e,s,a)},St=e=>e instanceof Error?e.message:String(e),_t=async(e,t,r,o,n)=>{const{id:a,label:s}=o;try{const i=await Te(t,r,["viewer","accounts",o.dataset,"sum"]),c=o.candidates.find(y=>i.has(y.field));if(c===void 0){const y=o.candidates.map(m=>`\`${m.field}\``).join(", ");return{id:a,label:s,reason:`\`${o.dataset}.sum\` exposes no ${y} field`,status:"unavailable"}}const d=`query LunoraUsage($accountTag: string) { viewer { accounts(filter: { accountTag: $accountTag }) { ${o.dataset}(limit: 1, filter: ${vt(o.filter,n)}) { sum { ${c.field} } } } } }`,f=await e.graphql(`Reading ${s}`,d,{accountTag:e.accountId});if(f.errors.length>0)return{id:a,label:s,reason:f.errors.join("; "),status:"unavailable"};const l=f.data?.viewer?.accounts?.[0]?.[o.dataset],h=Array.isArray(l)?l[0]:void 0,g=h===void 0?0:h.sum?.[c.field];return typeof g!="number"||!Number.isFinite(g)?{id:a,label:s,reason:`the response carried no number for \`${c.field}\``,status:"unavailable"}:{field:`${o.dataset}.sum.${c.field}`,id:a,label:s,status:"ok",unit:c.unit,value:g}}catch(i){return{id:a,label:s,reason:St(i),status:"unavailable"}}},Et=async(e,t)=>{const r=await e.graphql("Reading the analytics schema","query LunoraQueryRoot { __schema { queryType { name } } }"),o=r.data?.__schema?.queryType?.name;if(r.errors.length>0||o===void 0){const a=r.errors.length>0?r.errors.join("; "):"the schema root could not be read";return S.map(({id:s,label:i})=>({id:s,label:i,reason:a,status:"unavailable"}))}const n=At(e);return Promise.all(S.map(async a=>_t(e,n,o,a,t)))},D="Lunora usage: ",Tt=3,Y="Cloudflare does not document the unit of a usage alert's limit; limits are sent as plain request counts. Check the stored limit in the dashboard.",It={"do-requests":1e6,"workers-requests":1e7},kt=100,Ot=e=>{const t=Math.ceil(e);if(t<=0)return 0;const r=10**Math.max(0,Math.floor(Math.log10(t))-1);return Math.ceil(t/r)*r},Ct=(e,t,r)=>{const o=Ot(e*t);return o>r?{basis:"history",threshold:o}:{basis:"floor",threshold:r}},x=e=>{const t=e.filters?.product;return Array.isArray(t)?t.filter(r=>typeof r=="string"):[]},Nt=e=>{const t=e.filters?.limit,r=Array.isArray(t)?t[0]:void 0,o=typeof r=="string"?Number(r):Number.NaN;return Number.isFinite(o)?o:void 0},K=e=>{const t={};for(const[r,o]of Object.entries(e?.mechanisms??{}))Array.isArray(o)&&(t[r]=o.filter(n=>typeof n=="object"&&n!==null&&"id"in n&&typeof n.id=="string"));return t},Ut=(e,t)=>{const r={};for(const[o,n]of[...Object.entries(e),...Object.entries(t)]){const a=new Set((r[o]??[]).map(s=>s.id));r[o]=[...r[o]??[],...n.filter(s=>!a.has(s.id))]}return Object.fromEntries(Object.entries(r).filter(([,o])=>o.length>0))},N=e=>Object.entries(e).flatMap(([t,r])=>r.map(o=>`${t}:${o.id}`)),Dt=(e,t)=>JSON.stringify(N(e).toSorted((r,o)=>r.localeCompare(o)))===JSON.stringify(N(t).toSorted((r,o)=>r.localeCompare(o))),Rt=(e,t)=>e.enabled===!0&&e.name===t.name&&JSON.stringify(e.filters?.limit)===JSON.stringify(t.filters.limit)&&JSON.stringify(x(e))===JSON.stringify(t.filters.product)&&Dt(K(e),t.mechanisms),Mt=(e,t)=>{const r={};return e.length>0&&(r.email=e.map(o=>({id:o}))),t.length>0&&(r.webhooks=t.map(o=>({id:o}))),r},jt=(e,t,r)=>{const o=r.thresholds?.[e],n=r.usage.find(c=>c.id===e),a=n?.status==="ok"?n.value:void 0;if(o!==void 0)return{basis:"explicit",lastMonth:a,threshold:o};const s=It[e];return s===void 0?{kind:"unit-unverified",reason:`the unit Cloudflare meters "${t.id}" in is not documented and cannot be matched to the usage reading; set one yourself with --threshold ${e}=<n>`}:a!==void 0?{...Ct(a,r.multiplier,s),lastMonth:a}:r.allowFloor===!0?{basis:"floor-usage-unavailable",lastMonth:void 0,threshold:s}:{kind:"usage-unavailable",reason:`last month's usage could not be read (${n?.status==="unavailable"?n.reason:"no reading"}), and an alert at the floor could fire every month on an account that already exceeds it; pass --threshold ${e}=<n>, or --allow-floor`}},Lt=(e,t)=>{const r=Nt(e);if(r===void 0||t===void 0||r<=0||t<=0)return;const o=Math.max(r/t,t/r);return o>=kt?`its limit ${String(r)} differs from the ${String(t)} this would plan by ${String(Math.round(o))}× — one of the two may be in the wrong unit`:void 0},q=e=>`${D}${e}`,Pt=(e,t)=>e===void 0?"create":Rt(e,t)?"unchanged":"update",Gt=(e,t)=>{switch(e){case"explicit":return"set with --threshold";case"floor-usage-unavailable":return"the included monthly allowance, applied with --allow-floor because usage could not be read";case"history":return`${String(t)}× last month`;default:return"the included monthly allowance (floor)"}},Ft=(e,t,r)=>{const{id:o,label:n}=e,a=Ee(o,t.products);if(a.status==="no-product")return{kind:"no-product",metric:o,reason:"Cloudflare has no usage alert for this product; the account-wide budget alert is its guard"};if(a.status==="ambiguous")return{kind:"ambiguous",metric:o,reason:`several offered products could mean this metric (${a.candidates.join(", ")})`};const{product:s}=a,i=r.find(p=>p.name===q(n)),c=r.find(p=>p.enabled===!0&&p.name?.startsWith(D)!==!0&&x(p).includes(s.id)),d=jt(o,s,t);if(i===void 0&&c!==void 0){const p=Lt(c,"threshold"in d?d.threshold:void 0);return{kind:"covered",metric:o,reason:`already alerted by the existing policy "${c.name??c.id??"?"}"`,...p===void 0?{}:{warning:p}}}if(!("threshold"in d))return{...d,metric:o};const f=Mt(t.emails,t.webhooks),l=K(i),h=i===void 0||t.replaceRecipients===!0?f:Ut(l,f),g=new Set(N(l)),y=new Set(N(h)),m={alert_type:A,description:`Managed by \`lunora cloudflare alerts setup\`: ${Gt(d.basis,t.multiplier)}.`,enabled:!0,filters:{limit:[String(d.threshold)],product:[s.id]},mechanisms:h,name:q(n)},w=i===void 0?[]:x(i);return{action:Pt(i,m),basis:d.basis,body:m,lastMonth:d.lastMonth,metric:o,...i?.id===void 0?{}:{policyId:i.id},...i!==void 0&&JSON.stringify(w)!==JSON.stringify([s.id])?{previousProduct:w}:{},product:s.id,productSource:s.source,recipients:{added:[...y].filter(p=>!g.has(p)),removed:[...g].filter(p=>!y.has(p))},threshold:d.threshold}},xt=e=>{const t=e.policies.filter(n=>n.alert_type===A),r=new Set(S.map(n=>q(n.label))),o={alerts:[],duplicates:[],orphans:t.filter(n=>n.name?.startsWith(D)===!0&&!r.has(n.name)),skipped:[]};for(const n of r)o.duplicates.push(...t.filter(a=>a.name===n).slice(1));for(const n of S){const a=Ft(n,e,t);"action"in a?o.alerts.push(a):o.skipped.push(a)}return o},qt="Cloudflare's API has no endpoint that sends a test notification — not for webhooks and not for email — so `lunora cloudflare alerts test` sends nothing.",Bt="To test a webhook for real, create it in the dashboard (Alerts > Destinations > Webhooks > Create) and finish with Save and Test.",Jt="Email destinations cannot be tested at all: Cloudflare offers no test send for them. Check the address on each policy below instead.",ce=e=>Array.isArray(e)?e:[],de=30,Wt=async(e,t,r,o,n)=>{const a=t.filter(m=>m.alert_type===A&&m.name?.startsWith(D)===!0),s=a.map(m=>K(m)),i=new Set([...s.flatMap(m=>(m.webhooks??[]).map(w=>w.id)),...r]),c=[...new Set(s.flatMap(m=>(m.email??[]).map(w=>w.id)))],d=new Set(a.map(m=>m.id)),f=new Date(n.getTime()-de*24*60*60*1e3).toISOString(),[l,h]=await Promise.all([e.notifications("Listing webhook destinations","GET","/alerting/v3/destinations/webhooks"),a.length===0?Promise.resolve([]):e.notificationsList("Reading notification history",`/alerting/v3/history?per_page=25&since=${encodeURIComponent(f)}`)]),g=ce(l).map(m=>({...m,referenced:m.id!==void 0&&i.has(m.id)})),y=ce(h).filter(m=>m.alert_type===A&&m.policy_id!==void 0&&d.has(m.policy_id));o.warn(qt),a.length===0&&o.info("No `lunora cloudflare alerts setup` policies exist on this account yet — run `lunora cloudflare alerts setup` first.");for(const m of[...i].filter(w=>!g.some(p=>p.id===w)))o.warn(`Webhook destination ${m} is referenced but does not exist on this account.`);for(const m of g.filter(w=>w.referenced))o.info(`webhook "${m.name??m.id??"?"}" (${m.type??"generic"}): last delivered ${m.last_success??"never"}, last failed ${m.last_failure??"never"}`);if(o.info(Bt),c.length>0&&o.info(`${Jt} Addresses on the policies: ${c.join(", ")}.`),y.length===0)o.info(`Cloudflare's notification history shows nothing sent for these policies in the last ${String(de)} days (they only fire once a threshold is crossed).`);else for(const m of y)o.info(`sent ${m.sent??"?"}: "${m.name??"?"}" via ${m.mechanism_type??"?"} to ${m.mechanism??"?"}`);return{emails:c,history:y,testSent:!1,webhooks:g}},R="Budget alert (account-wide USD threshold): Cloudflare documents no API to create or read one, so this cannot be checked. Create it in the dashboard: Manage Account > Billing > Billable Usage > Create budget alert.",Ht="Alerts > Overview > Create an Alert, alert type Usage Based Billing",b=(e,t,r,o)=>(e.error(r),{code:t,error:r,...o===void 0?{}:{data:o}}),V=(e,t,r)=>{if(t instanceof _)return b(e,et[t.kind],t.message,r);throw t},z=(e,t)=>({accountId:e.accountId,budgetAlert:{checkable:!1,dashboardPath:"Manage Account > Billing > Billable Usage"},limitUnitCaveat:Y,...t===void 0?{}:{eligible:t.discovery.eligible,period:t.period,products:t.discovery.products}}),I=e=>e.toLocaleString("en-US",{maximumFractionDigits:0}),Ie=(e,t)=>{t===!1&&e.warn("This account is not eligible for Usage Based Billing notifications (they are offered to Pay-as-you-go accounts; most Enterprise contracts are not supported).")},ke=e=>({code:u.SUCCESS,data:e}),Yt=/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/u,Oe=new Set(S.map(e=>e.id)),Kt=e=>Oe.has(e),Vt=e=>{const t={};for(const r of e.flatMap(o=>o.split(","))){const[o="",n=""]=r.split("=").map(s=>s.trim()),a=Number(n);if(!Kt(o))return{error:`--threshold "${r}": unknown metric "${o}" — one of ${[...Oe].join(", ")}.`};if(!Number.isFinite(a)||a<=0)return{error:`--threshold "${r}": the limit must be a positive number.`};t[o]=Math.ceil(a)}return t},zt=e=>{const t=(e.emails??[]).flatMap(s=>s.split(",")).map(s=>s.trim()).filter(s=>s.length>0),r=(e.webhooks??[]).map(s=>s.trim()).filter(s=>s.length>0),o=e.multiplier===void 0?Tt:Number(e.multiplier),n=Vt(e.thresholds??[]);if(t.length===0&&r.length===0)return{error:"alerts setup needs somewhere to send the alerts: pass --email <address> and/or --webhook <destination-id>."};const a=t.find(s=>!Yt.test(s));return a!==void 0?{error:`--email "${a}" is not an email address.`}:!Number.isFinite(o)||o<1?{error:`--multiplier must be a number of at least 1 (got "${e.multiplier??""}").`}:"error"in n?n:{allowFloor:e.allowFloor===!0,emails:t,multiplier:o,replaceRecipients:e.replaceRecipients===!0,thresholds:n,webhooks:r}},Xt={explicit:()=>"set with --threshold",floor:e=>`floor: included monthly allowance; last month ${I(e.lastMonth??0)}`,"floor-usage-unavailable":()=>"floor: usage could not be read, applied with --allow-floor",history:e=>`last month ${I(e.lastMonth??0)}`},Qt=e=>{const t=[...e.added.map(r=>`+${r}`),...e.removed.map(r=>`-${r}`)];return t.length===0?"recipients unchanged":`recipients ${t.join(" ")}`},Zt=e=>{const t=e.productSource==="documented"?"documented id":"offered by the account",r=e.previousProduct===void 0?"":`, product was ${e.previousProduct.join(", ")||"none"}`;return`${e.action.padEnd(9)} ${e.body.name} — product ${e.product} (${t}${r}), limit ${I(e.threshold)} (${Xt[e.basis](e)}); ${Qt(e.recipients)}`},B=e=>S.find(t=>t.id===e)?.label??e,er=(e,t,r,o)=>{o.info(`Usage alerts at ${String(t)}× last month (${r.startDate} to ${r.endDate}), never below the included allowance:`);for(const n of e.alerts)o.info(` ${Zt(n)}`);for(const n of e.skipped)o.info(` skipped ${B(n.metric)} — ${n.reason}`),n.warning!==void 0&&o.warn(`${B(n.metric)}: the existing alert ${n.warning}.`);for(const n of e.orphans)o.warn(`"${n.name??"?"}" (${n.id??"?"}) is named like a \`lunora cloudflare alerts\` policy but matches no metric; it was left alone.`);for(const n of e.duplicates)o.warn(`"${n.name??"?"}" exists more than once; ${n.id??"?"} was left alone — delete the extra one in the dashboard.`);e.skipped.some(n=>n.kind==="unit-unverified"||n.kind==="ambiguous")&&o.info(`To set the skipped ones by hand: ${Ht}.`),o.info(Y),o.info(R)},tr=async(e,t)=>{if(e.yes===!0)return;if(e.confirm===void 0&&(!process.stdin.isTTY||e.format==="json"))return b(e.logger,u.USAGE,"alerts setup: refusing to change notification policies without confirmation — re-run with --yes.");const r=t.flatMap(a=>a.recipients.removed),o=r.length===0?"":` This REMOVES recipients: ${[...new Set(r)].join(", ")}.`;if(!await(e.confirm??xe)(`Apply ${String(t.length)} notification policy change(s)?${o}`))return e.logger.info("alerts setup: aborted"),{code:u.CANCELLED,error:"alerts setup: aborted at the confirmation prompt"}},rr=async(e,t,r,o)=>{const a=(await Promise.all(t.map(async s=>e.notifications(`Reading webhook destination ${s}`,"GET",`/alerting/v3/destinations/webhooks/${encodeURIComponent(s)}`).then(()=>{},i=>({error:i,id:s}))))).find(s=>s!==void 0);if(a!==void 0)return a.error instanceof _&&a.error.kind==="not-found"?b(r,u.USAGE,`--webhook ${a.id}: no such webhook destination on account ${e.accountId}.`,o):V(r,a.error,o)},or=async(e,t)=>{const r=t.action==="create"?await e.notifications(`Creating "${t.body.name}"`,"POST","/alerting/v3/policies",t.body):await e.notifications(`Updating "${t.body.name}"`,"PUT",`/alerting/v3/policies/${encodeURIComponent(t.policyId??"")}`,t.body);return typeof r=="object"&&r!==null&&"id"in r&&typeof r.id=="string"?r.id:t.policyId},nr=async(e,t,r)=>await Promise.all(t.map(async({alert:n,id:a})=>{try{const s=await e.notifications(`Reading back "${n.body.name}"`,"GET",`/alerting/v3/policies/${encodeURIComponent(a)}`),i={enabled:s?.enabled,id:a,limit:s?.filters?.limit,name:s?.name,product:s?.filters?.product};return r.info(`Cloudflare stored "${i.name??n.body.name}": product ${JSON.stringify(i.product)}, limit ${JSON.stringify(i.limit)}.`),JSON.stringify(i.limit)!==JSON.stringify(n.body.filters.limit)&&r.warn(`"${n.body.name}": Cloudflare stored limit ${JSON.stringify(i.limit)}, not the ${JSON.stringify(n.body.filters.limit)} sent.`),i}catch(s){return r.warn(`Could not read "${n.body.name}" back: ${s instanceof Error?s.message:String(s)}`),{id:a}}})),ar=async(e,t,r,o)=>{const n=[],a=[];let s;for(const c of t)try{const d=await or(e,c);n.push({action:c.action,metric:c.metric,...d===void 0?{}:{policyId:d}}),d!==void 0&&a.push({alert:c,id:d}),r.success(`${c.action==="create"?"Created":"Updated"} "${c.body.name}".`)}catch(d){s=d;break}const i={...o,applied:n,stored:await nr(e,a,r)};return s===void 0?i:V(r,s,i)},le=(e,t,r)=>{const o=t.skipped.filter(n=>n.kind==="usage-unavailable");return o.length>0?b(r,u.FAILURE,`alerts setup: no alert was set for ${o.map(n=>B(n.metric)).join(", ")} because last month's usage could not be read — pass --threshold <metric>=<n>, or --allow-floor to accept the floor.`,e):{code:u.SUCCESS,data:e}},sr=async(e,t,r,o)=>{const{logger:n}=r,a=z(e,t),s=await rr(e,o.webhooks,n,a);if(s!==void 0)return s;if(t.discovery.eligible===!1)return Ie(n,!1),n.info(R),b(n,u.PERMISSION,"alerts setup: the account cannot create Usage Based Billing notifications.",a);const i=xt({...o,policies:t.policies,products:t.discovery.products,usage:t.usage});a.plan=i,er(i,o.multiplier,t.period,n);const c=i.alerts.filter(l=>l.action!=="unchanged");if(r.dryRun===!0||c.length===0)return n.info(r.dryRun===!0?"Dry run: nothing was changed.":"Every alert is already up to date."),le(a,i,n);const d=await tr(r,c);if(d!==void 0)return{...d,data:a};const f=await ar(e,c,n,a);return"code"in f?f:le(f,i,n)},ir=e=>e.status!=="ok"?`unavailable (${e.reason})`:e.unit==="microseconds"?`${I(e.value)} µs`:I(e.value),cr=(e,t,r)=>t?"no-product":e===void 0?"unknown":r.some(o=>o.enabled)?"active":r.length>0?"disabled-only":"none",ue=e=>e.map(t=>`"${t.name??t.id??"?"}" (limit ${JSON.stringify(t.limit)})${t.enabled?"":" — disabled, not protecting you"}`).join(", "),dr={active:e=>`alert: ${ue(e.alerts)}`,"disabled-only":e=>`alert: none active; ${ue(e.alerts)}`,"no-product":()=>"alert: none possible — Cloudflare has no usage alert for this; the budget alert is its guard",none:()=>"alert: none",unknown:()=>"alert: unknown (several offered products could mean this)"},lr=(e,t,r)=>{const o=Ee(e.id,t.discovery.products),n=o.status==="matched"?o.product:void 0,a=(n===void 0?[]:r.filter(s=>Array.isArray(s.filters?.product)&&s.filters.product.includes(n.id))).map(s=>({enabled:s.enabled===!0,id:s.id,limit:s.filters?.limit,name:s.name}));return{alerts:a,coverage:cr(n?.id,o.status==="no-product",a),id:e.id,label:e.label,...n===void 0?{}:{product:n.id,productSource:n.source},usage:e}},ur=(e,t,r)=>{const o=t.policies.filter(a=>a.alert_type===A),n=t.usage.map(a=>lr(a,t,o));r.info(`Cloudflare account ${e.accountId} — usage ${t.period.startDate} to ${t.period.endDate}`);for(const a of n)r.info(` ${a.label}: ${ir(a.usage)} — ${dr[a.coverage](a)}`);return r.info(`Usage Based Billing notifications on the account: ${String(o.length)} (${String(o.filter(a=>a.enabled===!0).length)} enabled).`),r.info(Y),Ie(r,t.discovery.eligible),r.info(R),ke({...z(e,t),metrics:n})},fr=e=>{const t=J(e);return t===void 0?void 0:W(t).parsed?.account_id},mr=e=>{const{accountId:t,token:r}=H(e.environment??process.env,fr(e.cwd));return r===void 0?b(e.logger,u.AUTH,`CLOUDFLARE_API_TOKEN is not set. \`lunora cloudflare alerts\` calls the Cloudflare API with an API token (a wrangler login session is not reused); create one with the "${E.notificationsWrite}" and "${E.analytics}" permissions ("${E.notificationsRead}" is enough for status and test).`):t===void 0?b(e.logger,u.USAGE,"No Cloudflare account: set CLOUDFLARE_ACCOUNT_ID or `account_id` in wrangler.jsonc."):at({accountId:t,token:r,...e.fetch===void 0?{}:{fetch:e.fetch}})},Ce=async e=>{const t=await e.notifications("Listing notification policies","GET","/alerting/v3/policies");return Array.isArray(t)?t:[]},hr=async(e,t)=>{const r=wt(t.now??new Date),{logger:o}=t,[n,a,s]=await Promise.all([Ce(e),e.notifications("Listing the alert types this account is eligible for","GET","/alerting/v3/available_alerts").then(i=>({ok:!0,value:i}),i=>({error:i,ok:!1})),Et(e,r).catch(i=>{const c=i instanceof Error?i.message:String(i);return o.warn(`Usage could not be read: ${c}`),S.map(({id:d,label:f})=>({id:d,label:f,reason:c,status:"unavailable"}))})]);return a.ok||o.warn(`Could not read which alert types the account is eligible for: ${a.error instanceof Error?a.error.message:String(a.error)}`),{discovery:ft(a.ok?a.value:void 0,a.ok,n),period:r,policies:n,usage:s}},gr=async(e,t)=>{const r=await Ce(e),o=await Wt(e,r,t.webhooks??[],t.logger,t.now??new Date);return t.logger.info(R),ke({...z(e),deliveries:o})},pr=async e=>{const t=e.subcommand==="setup"?zt(e):void 0;if(t!==void 0&&"error"in t)return b(e.logger,u.USAGE,t.error);const r=mr(e);if(!("graphql"in r))return r;try{if(e.subcommand==="test")return await gr(r,e);const o=await hr(r,e);return t===void 0?ur(r,o,e.logger):await sr(r,o,e,t)}catch(o){return V(e.logger,o)}},fe=new Set(["build","delete","images","info","list","push"]),yr=new Set(["build","push"]),me=new Set(["images list","info","list"]),wr=async e=>{const[t,...r]=e.argument;if(t===void 0||!fe.has(t)){const l=`lunora cloudflare containers requires a subcommand: ${[...fe].toSorted((h,g)=>h.localeCompare(g)).join(" | ")}. Example: lunora cloudflare containers build ./containers/app --tag app:v1 --push`;return e.logger.error(l),{code:u.USAGE,error:l}}const o=e.format==="json",n=t==="images"?`images ${r[0]??""}`.trim():t;if(o&&!me.has(n)){const l=`containers ${n}: --format json is only available for the read subcommands (${[...me].toSorted((h,g)=>h.localeCompare(g)).join(" | ")}) — wrangler has no JSON rendering for the rest.`;return e.logger.error(l),{code:u.USAGE,error:l}}if(yr.has(t)&&!(e.dockerAvailable??qe)()){const l=`containers ${t} needs a running Docker-compatible engine (it builds/pushes images locally). Start Docker or Colima and retry. Note: container images must target linux/amd64.`;return e.logger.error(l),{code:u.MISSING_DEPENDENCY,error:l}}const a=["containers",t,...r];o&&a.push("--json"),e.tag!==void 0&&a.push("--tag",e.tag),e.push===!0&&a.push("--push"),e.env!==void 0&&a.push("--env",e.env);const s=e.cwd??process.cwd(),i=$e(Ae(s),"wrangler",a),c={args:i.args,command:i.command,cwd:s};return e.logger.info(`running ${c.command} ${c.args.join(" ")}`),{code:(await(e.spawner??be)(c)).code,delegated:o,descriptor:c}},k=(e,t)=>(t!==void 0&&e.push("--env",t),e),vr=e=>{const t=k(["deployments","list"],e.env);return e.format==="json"&&t.push("--json"),t},br=e=>{switch(e.subcommand){case"inspect":return e.versionId===void 0?{error:"deployments inspect requires a version id. Usage: lunora cloudflare deployments inspect <version-id>"}:{args:k(["versions","view",e.versionId],e.env)};case"list":return{args:vr(e)};case"promote":{if(e.versionId===void 0)return{error:"deployments promote requires a version id. Usage: lunora cloudflare deployments promote <version-id> --yes"};if(!e.yes)return{error:"deployments promote shifts 100% of live traffic. Re-run with --yes to confirm."};const t=k(["versions","deploy",`${e.versionId}@100%`,"--yes"],e.env);return e.message!==void 0&&t.push("--message",e.message),{args:t}}case"rollback":{if(!e.yes)return{error:"deployments rollback changes the live version. Re-run with --yes to confirm."};const t=k(["rollback"],e.env);return e.versionId!==void 0&&t.push(e.versionId),t.push("--yes"),e.message!==void 0&&t.push("--message",e.message),{args:t}}default:return{error:`deployments: unknown subcommand "${e.subcommand}"`}}},$r=async e=>{if(e.format==="json"&&e.subcommand!=="list"){const d=`deployments ${e.subcommand}: --format json is only available for \`deployments list\` — wrangler has no JSON rendering for the others.`;return e.logger.error(d),{code:u.USAGE,descriptor:void 0,error:d}}const{args:t,error:r}=br(e);if(r!==void 0||t===void 0)return e.logger.error(r??"deployments: nothing to run"),{code:u.USAGE,descriptor:void 0,error:r};const{logger:o}=e,n=e.cwd??process.cwd(),a=$e(Ae(n),"wrangler",t),s={args:a.args,command:a.command,cwd:n};return o.info(`${s.command} ${s.args.join(" ")}`),{code:(await(e.spawner??be)(s)).code,delegated:e.format==="json",descriptor:s}},Ar=e=>e==="list"||e==="inspect"||e==="rollback"||e==="promote",Sr="https://api.cloudflare.com/client/v4/accounts",Ne="Workers Scripts Read",he=1e3,ge=5e4,_r=1e4,Er=3e4,pe=[31,139],ye=["cpu","heap"],Tr=/^[\da-f]{64}$/iu,v=(e,t,r)=>(e.error(r),{code:t,error:r}),we=e=>{try{const t=JSON.parse(e);if(Array.isArray(t.errors)){const r=t.errors.map(o=>typeof o=="object"&&o!==null&&"message"in o?String(o.message):"").filter(o=>o.length>0);if(r.length>0)return L(r.join("; "))}}catch{}return L(e.trim())},Ir=e=>{switch(e){case 401:case 403:return` — check that CLOUDFLARE_API_TOKEN is valid and has the "${Ne}" permission on this account`;case 404:return" — check the worker name, version, and (for a Durable Object) the namespace and actor ids";case 429:return" — rate limited; wait and retry";default:return""}},kr=e=>{const t=e.type??"cpu";if(!ye.includes(t))return{error:`profile: invalid --type "${e.type??""}" — expected ${ye.join(" | ")}`};const r=e.durationMs===void 0?_r:Number(e.durationMs);return!Number.isInteger(r)||r<he||r>ge?{error:`profile: invalid --duration-ms "${e.durationMs??""}" — expected an integer from ${String(he)} to ${String(ge)}`}:e.namespaceId===void 0!=(e.actorId===void 0)?{error:"profile: --namespace-id and --actor-id must be passed together to profile a Durable Object"}:e.actorId!==void 0&&!Tr.test(e.actorId)?{error:"profile: --actor-id must be a 64-character hexadecimal Durable Object id"}:{durationMs:r,profileType:t}},Or=e=>{const{logger:t}=e,r=J(e.cwd),o=r===void 0?void 0:W(r).parsed,{accountId:n,token:a}=H(e.environment??process.env,o?.account_id);if(a===void 0)return v(t,u.AUTH,`CLOUDFLARE_API_TOKEN is not set. \`lunora cloudflare profile\` calls the Cloudflare API with an API token (a wrangler login session is not reused); create one with the "${Ne}" permission.`);if(n===void 0)return v(t,u.USAGE,"No Cloudflare account: set CLOUDFLARE_ACCOUNT_ID or `account_id` in wrangler.jsonc.");const s=typeof o?.name=="string"&&o.name.length>0?o.name:void 0,i=e.worker??s;if(i===void 0)return v(t,u.USAGE,"profile: no Worker name — pass one, or set `name` in wrangler.jsonc.");const c=e.env===void 0?void 0:o?.env?.[e.env]?.name;let d=i;return e.worker===void 0&&e.env!==void 0&&(d=typeof c=="string"&&c.length>0?c:`${i}-${e.env}`),{accountId:n,sourceMapsMissing:o!==void 0&&o.upload_source_maps!==!0,token:a,worker:d}},Cr=(e,t)=>{const r=`${e}.${String(process.pid)}.tmp`;try{Re(Je(e),{recursive:!0}),ve(r,t),Me(r,e);return}catch(o){try{je(r,{force:!0})}catch{}return o instanceof Error?o.message:String(o)}},Nr=async(e,{accountId:t,token:r,worker:o},{durationMs:n,profileType:a,version:s})=>{const{logger:i}=e,c={duration_ms:n,profile_type:a,...e.namespaceId===void 0||e.actorId===void 0?{}:{actor_id:e.actorId,namespace_id:e.namespaceId}},d=`${Sr}/${encodeURIComponent(t)}/workers/workers/${encodeURIComponent(o)}/versions/${encodeURIComponent(s)}/profile`,f=e.fetch??globalThis.fetch.bind(globalThis);let l,h;try{l=await f(d,{body:JSON.stringify(c),headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(n+Er)}),h=new Uint8Array(await l.arrayBuffer())}catch(g){return v(i,u.UNAVAILABLE,`profile failed: ${g instanceof Error?g.message:String(g)}.`)}if(!l.ok){const g=we(new TextDecoder().decode(h));return v(i,j(l.status),`profile failed (HTTP ${String(l.status)})${g.length>0?`: ${g}`:""}${Ir(l.status)}.`)}if(h[0]!==pe[0]||h[1]!==pe[1]){const g=we(new TextDecoder().decode(h));return v(i,u.FAILURE,`profile failed: the API answered 200 with something that is not a gzip profile${g.length>0?`: ${g}`:""}.`)}return{bytes:h}},Ur=async e=>{const{logger:t}=e,r=kr(e);if("error"in r)return v(t,u.USAGE,r.error);const o=Or(e);if("code"in o)return o;const{durationMs:n,profileType:a}=r,{worker:s}=o,i=e.version??"latest";t.info(`Profiling ${s}@${i} (${a}) for ${String(n)} ms — keep traffic flowing to it…`);const c=await Nr(e,o,{durationMs:n,profileType:a,version:i});if("code"in c)return c;const{bytes:d}=c,f=(e.now??new Date).toISOString().replaceAll(/[:.]/gu,"-"),l=Be(e.cwd,e.out??`${s}-${a}-${f}.pprof.gz`),h=Cr(l,d);return h!==void 0?v(t,u.FAILURE,`profile failed: could not write ${l}: ${h}.`):(t.success(`Wrote ${String(d.byteLength)} bytes to ${l}`),t.info("Open it with `go tool pprof -http=: <file>` (pprof reads gzip directly) or upload it to a flamegraph viewer."),o.sourceMapsMissing&&t.warn("wrangler config has no `upload_source_maps: true` — TypeScript function names in the profile may be mangled."),{code:u.SUCCESS,data:{bytes:d.byteLength,durationMs:n,file:l,profileType:a,version:i,worker:s}})},Dr={ai:{absorbs:"gateway",tool:"ai-gateway"},alert:{tool:"alerts"}},Rr=(e,t)=>{const r=Dr[e];if(r!==void 0)return{rest:r.absorbs!==void 0&&t[0]===r.absorbs?t.slice(1):t,tool:r.tool};const o=O.find(n=>n.name===e)?.name;return o===void 0?void 0:{rest:t,tool:o}},Mr={celld:"celld",node:"Node"},jr=e=>e==="status"||e==="setup"||e==="test",T=(e,t,r)=>(e.error(r),{code:t,error:r}),Lr=e=>{const t=Math.max(...O.map(r=>r.name.length));e.info("lunora cloudflare <tool> — tools for an app deployed to your own Cloudflare account:");for(const r of O)e.info(` ${r.name.padEnd(t)} ${r.summary}`),r.usage!==""&&e.info(` ${" ".repeat(t)} lunora cloudflare ${r.name} ${r.usage}`);return e.info("Run `lunora cloudflare --help` for every tool's arguments and options."),{code:0}},Pr=(e,t,r)=>{const{error:o,target:n}=De(e,r);return n===void 0?o??"no deploy target":n==="cloudflare"?void 0:`${r===void 0?`this project deploys to ${Mr[n]??`"${n}"`}`:`\`--target ${n}\``} — \`lunora cloudflare ${t}\` only applies to Cloudflare.`},Gr=(e,{logger:t,rest:r})=>r.length===0?void 0:T(t,u.USAGE,`cloudflare ${e}: takes no arguments, got "${r.join(" ")}"`),Fr=async e=>{const{cwd:t,logger:r,options:o}=e;return Gr("ai-gateway",e)??Xe({cwd:t,dryRun:o.dryRun===!0,id:o.id,logger:r,logs:o.logs})},xr=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const a=n[0]??"status";return jr(a)?pr({allowFloor:o.allowFloor===!0,cwd:e,dryRun:o.dryRun===!0,format:t,logger:r,replaceRecipients:o.replaceRecipients===!0,subcommand:a,yes:o.yes===!0,...o.email===void 0?{}:{emails:o.email},...o.webhook===void 0?{}:{webhooks:o.webhook},...o.multiplier===void 0?{}:{multiplier:o.multiplier},...o.threshold===void 0?{}:{thresholds:o.threshold}}):T(r,u.USAGE,`cloudflare alerts: unknown subcommand "${a}" — expected status | setup | test`)},qr=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const a=await wr({argument:n,cwd:e,env:o.env,format:t,logger:r,push:o.push===!0,tag:o.tag});return{code:a.code,delegated:a.delegated,error:a.error}},Br=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const[a,s]=n;if(!Ar(a))return T(r,u.USAGE,`cloudflare deployments: unknown subcommand "${a??""}" — expected list | inspect | rollback | promote`);const i=await $r({cwd:e,env:o.env,format:t,logger:r,message:o.message,subcommand:a,versionId:s,yes:o.yes===!0});return{code:i.code,delegated:i.delegated,error:i.error}},Jr=async({cwd:e,logger:t,options:r,rest:o})=>o.length>1?T(t,u.USAGE,`cloudflare profile: takes at most one argument (the worker), got "${o.join(" ")}"`):Ur({actorId:r.actorId,cwd:e,durationMs:r.durationMs,env:r.env,logger:t,namespaceId:r.namespaceId,out:r.out,type:r.type,version:r.versionId,worker:o[0]}),Wr={"ai-gateway":Fr,alerts:xr,containers:qr,deployments:Br,profile:Jr},so=Ue(async({argument:e,cwd:t,format:r,logger:o,options:n})=>{const[a,...s]=e;if(a===void 0||a==="")return Lr(o);const i=Rr(a,s);if(i===void 0)return T(o,u.USAGE,`cloudflare: unknown tool "${a}" — expected ${O.map(f=>f.name).join(" | ")}`);const{tool:c}=i,d=Pr(t,c,n.target);return d!==void 0?T(o,u.USAGE,d):Wr[c]({cwd:t,format:r,logger:o,options:n,rest:i.rest})});export{so as execute};
@@ -1 +1 @@
1
- import{mkdtempSync as p,rmSync as u,existsSync as m,readdirSync as $,statSync as g}from"node:fs";import{tmpdir as S}from"node:os";import{join as y,relative as w}from"node:path";import{d as z}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as B}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{defaultSpawner as F}from"../packem_shared/createRecordingSpawner-01ZngIwK.mjs";import{execArgsFor as h,detectPackageManager as x}from"@lunora/config/cli";const v=e=>{const t=[],n=o=>{for(const s of $(o)){const r=y(o,s),i=g(r);i.isDirectory()?n(r):i.isFile()&&t.push({path:w(e,r),sizeBytes:i.size})}};return n(e),t},l=e=>e<1024?`${String(e)} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:`${(e/(1024*1024)).toFixed(2)} MiB`,M=e=>{const t=v(e),n=t.toSorted((r,i)=>i.sizeBytes-r.sizeBytes),o=t.reduce((r,i)=>r+i.sizeBytes,0);return{generatedFiles:t.filter(r=>r.path.includes("_generated")),outdir:e,topModules:n.slice(0,10),totalBytes:o,totalFiles:t.length}},O=(e,t)=>{if(t.info(`outdir: ${e.outdir}`),t.info(`total: ${String(e.totalFiles)} files, ${l(e.totalBytes)}`),e.topModules.length>0){t.info(""),t.info("top modules by size:");for(const n of e.topModules)t.info(` ${l(n.sizeBytes).padStart(10," ")} ${n.path}`)}if(e.generatedFiles.length>0){t.info(""),t.info(`_generated/ files: ${String(e.generatedFiles.length)}`);for(const n of e.generatedFiles)t.info(` ${l(n.sizeBytes).padStart(10," ")} ${n.path}`)}},j=async e=>{const t=e.cwd??process.cwd(),n=e.format==="json",{logger:o}=e;let s,r,i=!1;if(e.inspectOnly)s=e.inspectOnly;else{s=p(y(S(),"lunora-analyze-")),i=!0;const a=h(x(t),"wrangler",["deploy","--dry-run","--outdir",s]);r={args:a.args,command:a.command,cwd:t,stdoutToStderr:n},o.info(`analyze: building via ${r.command} ${r.args.join(" ")}`);const d=await(e.spawner??F)(r);if(d.code!==0){const f=`analyze: wrangler dry-run failed (exit ${String(d.code)})`;return o.error(f),u(s,{force:!0,recursive:!0}),{code:d.code,descriptor:r,error:f,report:void 0}}}try{if(!m(s)){const c=`analyze: outdir not found at ${s}`;return o.error(c),{code:B.NOT_FOUND,descriptor:r,error:c,report:void 0}}const a=M(s);return n||O(a,o),{code:0,descriptor:r,report:a}}finally{if(i&&m(s))try{u(s,{force:!0,recursive:!0})}catch{}}},N=z(async({cwd:e,format:t,logger:n})=>{const o=await j({cwd:e,format:t,logger:n});return{code:o.code,data:o.report,error:o.error}});export{N as execute,j as runAnalyzeCommand};
1
+ import{mkdtempSync as p,rmSync as u,existsSync as m,readdirSync as $,statSync as g}from"node:fs";import{tmpdir as S}from"node:os";import{join as y,relative as w}from"node:path";import{d as z}from"../packem_shared/command-B6vu47iR.mjs";import{EXIT_CODE as B}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{defaultSpawner as F}from"../packem_shared/createRecordingSpawner-01ZngIwK.mjs";import{execArgsFor as h,detectPackageManager as x}from"@lunora/config/cli";const v=e=>{const t=[],n=o=>{for(const s of $(o)){const r=y(o,s),i=g(r);i.isDirectory()?n(r):i.isFile()&&t.push({path:w(e,r),sizeBytes:i.size})}};return n(e),t},l=e=>e<1024?`${String(e)} B`:e<1024*1024?`${(e/1024).toFixed(1)} KiB`:`${(e/(1024*1024)).toFixed(2)} MiB`,M=e=>{const t=v(e),n=t.toSorted((r,i)=>i.sizeBytes-r.sizeBytes),o=t.reduce((r,i)=>r+i.sizeBytes,0);return{generatedFiles:t.filter(r=>r.path.includes("_generated")),outdir:e,topModules:n.slice(0,10),totalBytes:o,totalFiles:t.length}},O=(e,t)=>{if(t.info(`outdir: ${e.outdir}`),t.info(`total: ${String(e.totalFiles)} files, ${l(e.totalBytes)}`),e.topModules.length>0){t.info(""),t.info("top modules by size:");for(const n of e.topModules)t.info(` ${l(n.sizeBytes).padStart(10," ")} ${n.path}`)}if(e.generatedFiles.length>0){t.info(""),t.info(`_generated/ files: ${String(e.generatedFiles.length)}`);for(const n of e.generatedFiles)t.info(` ${l(n.sizeBytes).padStart(10," ")} ${n.path}`)}},j=async e=>{const t=e.cwd??process.cwd(),n=e.format==="json",{logger:o}=e;let s,r,i=!1;if(e.inspectOnly)s=e.inspectOnly;else{s=p(y(S(),"lunora-analyze-")),i=!0;const a=h(x(t),"wrangler",["deploy","--dry-run","--outdir",s]);r={args:a.args,command:a.command,cwd:t,stdoutToStderr:n},o.info(`analyze: building via ${r.command} ${r.args.join(" ")}`);const d=await(e.spawner??F)(r);if(d.code!==0){const f=`analyze: wrangler dry-run failed (exit ${String(d.code)})`;return o.error(f),u(s,{force:!0,recursive:!0}),{code:d.code,descriptor:r,error:f,report:void 0}}}try{if(!m(s)){const c=`analyze: outdir not found at ${s}`;return o.error(c),{code:B.NOT_FOUND,descriptor:r,error:c,report:void 0}}const a=M(s);return n||O(a,o),{code:0,descriptor:r,report:a}}finally{if(i&&m(s))try{u(s,{force:!0,recursive:!0})}catch{}}},N=z(async({cwd:e,format:t,logger:n})=>{const o=await j({cwd:e,format:t,logger:n});return{code:o.code,data:o.report,error:o.error}});export{N as execute,j as runAnalyzeCommand};
@@ -1 +1 @@
1
- import{d as s}from"../packem_shared/command-CB4JM-Jl.mjs";import{o as c}from"../packem_shared/open-url-EnKy--w-.mjs";const n="https://lunora.sh/docs",l=r=>{let e=0,t=r.length;for(;e<t&&r[e]==="/";)e+=1;for(;t>e&&r[t-1]==="/";)t-=1;return r.slice(e,t)},i=r=>{if(!r||r.length===0)return n;const e=l(r);return e.length===0?n:`${n}/${e}`},a=async r=>{const e=i(r.section);r.logger.info(`opening ${e}`);try{await c(e,{opener:r.opener})}catch(t){const o=t instanceof Error?t.message:String(t);return r.logger.error(`docs: failed to open URL: ${o}`),{code:1,url:e}}return{code:0,url:e}},m=s(({argument:r,logger:e})=>a({logger:e,section:r[0]}));export{m as execute,a as runDocsCommand};
1
+ import{d as s}from"../packem_shared/command-B6vu47iR.mjs";import{o as c}from"../packem_shared/open-url-EnKy--w-.mjs";const n="https://lunora.sh/docs",l=r=>{let e=0,t=r.length;for(;e<t&&r[e]==="/";)e+=1;for(;t>e&&r[t-1]==="/";)t-=1;return r.slice(e,t)},i=r=>{if(!r||r.length===0)return n;const e=l(r);return e.length===0?n:`${n}/${e}`},a=async r=>{const e=i(r.section);r.logger.info(`opening ${e}`);try{await c(e,{opener:r.opener})}catch(t){const o=t instanceof Error?t.message:String(t);return r.logger.error(`docs: failed to open URL: ${o}`),{code:1,url:e}}return{code:0,url:e}},m=s(({argument:r,logger:e})=>a({logger:e,section:r[0]}));export{m as execute,a as runDocsCommand};
@@ -1,3 +1,3 @@
1
- import{existsSync as E,readFileSync as U}from"node:fs";import{join as P}from"node:path";import{DEV_VARS_FILE as m,DEV_VARS_KEY_PATTERN as b,writeDevVariablesFileAtomically as w,removeDevVariableLine as N,upsertDevVariableLine as D,isPlaceholderValue as S,resolveDeployDriver as K,resolveProjectTarget as j,generateSecretValue as z,DEV_VARS_EXAMPLE_FILE as g,parseDevVariableEntries as _,secretSourcesFromBindings as T,inferLunoraBindings as C,resolveSchemaDirectory as F,mintableSecretKeysFor as M,requiredSecrets as O}from"@lunora/config";import{d as Y}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as d}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{defaultSpawner as q}from"../packem_shared/createRecordingSpawner-01ZngIwK.mjs";import{l as Z}from"../packem_shared/wrangler-secrets-DlSlWvRH.mjs";import{detectPackageManager as B,toolchainExecArgs as I}from"@lunora/config/cli";const X=/[\r\n]/u,H=/["\\]/u,G=e=>new Map(_(e).map(s=>[s.key,s])),h=e=>E(e)?U(e,"utf8"):"",L=e=>e.length<=4?"****":`${e.slice(0,4)}${"*".repeat(Math.min(8,e.length-4))}`,p=e=>G(h(e)),$=e=>e.env??(e.prod===!0?"production":void 0),W=e=>{const s=p(e.devVariablesPath),t={file:m,keys:[...s.keys()],subcommand:"list"};if(s.size===0)return e.logger.info(`${m}: (empty)`),{code:0,data:t,descriptors:[]};for(const r of s.values())e.logger.info(`${r.key}=${L(r.value)}`);return{code:0,data:t,descriptors:[]}},J=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env get requires a key. Usage: lunora env get <KEY>"),{code:d.USAGE,descriptors:[]};const o=p(s).get(r.key);return o?(r.format!=="json"&&process.stdout.write(`${o.value}
1
+ import{existsSync as E,readFileSync as U}from"node:fs";import{join as P}from"node:path";import{DEV_VARS_FILE as m,DEV_VARS_KEY_PATTERN as b,writeDevVariablesFileAtomically as w,removeDevVariableLine as N,upsertDevVariableLine as D,isPlaceholderValue as S,resolveDeployDriver as K,resolveProjectTarget as j,generateSecretValue as z,DEV_VARS_EXAMPLE_FILE as g,parseDevVariableEntries as _,secretSourcesFromBindings as T,inferLunoraBindings as C,resolveSchemaDirectory as F,mintableSecretKeysFor as M,requiredSecrets as O}from"@lunora/config";import{d as Y}from"../packem_shared/command-B6vu47iR.mjs";import{EXIT_CODE as d}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{defaultSpawner as q}from"../packem_shared/createRecordingSpawner-01ZngIwK.mjs";import{l as Z}from"../packem_shared/wrangler-secrets-DlSlWvRH.mjs";import{detectPackageManager as B,toolchainExecArgs as I}from"@lunora/config/cli";const X=/[\r\n]/u,H=/["\\]/u,G=e=>new Map(_(e).map(s=>[s.key,s])),h=e=>E(e)?U(e,"utf8"):"",L=e=>e.length<=4?"****":`${e.slice(0,4)}${"*".repeat(Math.min(8,e.length-4))}`,p=e=>G(h(e)),$=e=>e.env??(e.prod===!0?"production":void 0),W=e=>{const s=p(e.devVariablesPath),t={file:m,keys:[...s.keys()],subcommand:"list"};if(s.size===0)return e.logger.info(`${m}: (empty)`),{code:0,data:t,descriptors:[]};for(const r of s.values())e.logger.info(`${r.key}=${L(r.value)}`);return{code:0,data:t,descriptors:[]}},J=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env get requires a key. Usage: lunora env get <KEY>"),{code:d.USAGE,descriptors:[]};const o=p(s).get(r.key);return o?(r.format!=="json"&&process.stdout.write(`${o.value}
2
2
  `),{code:0,data:{key:r.key,subcommand:"get",value:o.value},descriptors:[]}):(t.error(`env: ${r.key} is not set in ${m}`),{code:d.NOT_FOUND,descriptors:[]})},Q=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env set requires a key. Usage: lunora env set <KEY> <VALUE>"),{code:d.USAGE,descriptors:[]};if(!b.test(r.key))return t.error(`env: invalid key "${r.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};if(r.value===void 0)return t.error("env set requires a value. Usage: lunora env set <KEY> <VALUE>"),{code:d.USAGE,descriptors:[]};if(X.test(r.value))return t.error(`env: value for "${r.key}" contains a newline, which .dev.vars cannot represent`),{code:d.USAGE,descriptors:[]};if(H.test(r.value))return t.error(`env: value for "${r.key}" contains a double-quote or backslash, which .dev.vars cannot round-trip`),{code:d.USAGE,descriptors:[]};const o=h(s);return w(s,D(o,r.key,r.value)),t.success(`env: set ${r.key} (${L(r.value)}) in ${m}`),{code:0,data:{key:r.key,subcommand:"set"},descriptors:[]}},x=e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.key)return t.error("env unset requires a key. Usage: lunora env unset <KEY>"),{code:d.USAGE,descriptors:[]};if(!b.test(r.key))return t.error(`env: invalid key "${r.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};const o=h(s);return G(o).has(r.key)?(w(s,N(o,r.key)),t.success(`env: unset ${r.key} in ${m}`),{code:0,data:{key:r.key,removed:!0,subcommand:"unset"},descriptors:[]}):(t.warn(`env: ${r.key} was not set in ${m}`),{code:0,data:{key:r.key,removed:!1,subcommand:"unset"},descriptors:[]})},ee=async e=>{const{devVariablesPath:s,logger:t,options:r}=e;if(!r.yes)return t.error("env push uploads secrets to Cloudflare. Re-run with --yes to confirm."),{code:d.USAGE,descriptors:[]};const o=p(s);if(o.size===0)return t.warn(`${m}: nothing to push (empty)`),{code:0,data:{pushed:[],subcommand:"push",target:$(r)},descriptors:[]};const n=[...o.values()].filter(a=>S(a.value)).map(a=>a.key);if(n.length>0)return t.error(`env push: refusing to upload placeholder value(s) for ${n.join(", ")} — run \`lunora env doctor\` to review, and \`lunora env generate --set\` (or \`lunora env set <KEY> <VALUE>\`) to fill them, then re-run.`),{code:d.USAGE,descriptors:[]};const c=r.spawner??q,u=r.cwd??process.cwd(),l=B(u),i=[],v=$(r),f=K(j(u)),y=f.toolchain?.secretPut;if(y===void 0)return t.error(`deploy target "${f.id}" has no secret store; cannot push secrets`),{code:d.USAGE,descriptors:[]};for(const a of o.values()){const R=y({environment:v,key:a.key,temporary:r.temporary}),A=I(l,R),V={args:A.args,command:A.command,cwd:u,input:a.value};i.push(V),t.info(`pushing ${a.key} -> wrangler secret${v===void 0?"":` (${v})`}`);const k=await c(V);if(k.code!==0)return t.error(`env push: failed at ${a.key} (exit ${String(k.code)})`),{code:k.code,descriptors:i}}return t.success(`env: pushed ${String(o.size)} secret(s)`),{code:0,data:{pushed:[...o.keys()],subcommand:"push",target:v},descriptors:i}},re=e=>(e.options.secretLister??Z)({cwd:e.cwd,env:$(e.options),temporary:e.options.temporary}),te=async e=>{const{devVariablesPath:s,logger:t}=e,r=await re(e);if(!r.ok)return t.error(`env diff: ${r.error??"failed to list remote secrets"}`),{code:1,descriptors:[]};const o=new Set(p(s).keys()),n=new Set(r.names),c=[...o].filter(i=>!n.has(i)).toSorted((i,v)=>i.localeCompare(v)),u=r.names.filter(i=>!o.has(i)),l=[...o].filter(i=>n.has(i)).toSorted((i,v)=>i.localeCompare(v));for(const i of c)t.info(`local only (run \`lunora env push\`): ${i}`);for(const i of u)t.info(`remote only (in Cloudflare, not ${m}): ${i}`);return t.info(`in both: ${String(l.length)} secret(s)`),c.length===0&&u.length===0&&t.success("env diff: local and remote secret names match"),{code:0,data:{both:l,localOnly:c,remoteOnly:u,subcommand:"diff"},descriptors:[]}},se=e=>{const{cwd:s,devVariablesPath:t,logger:r}=e,o=P(s,g);if(!E(o))return r.info(`env doctor: no ${g} to check against — nothing to validate.`),{code:0,data:{extra:[],missing:[],ok:!0,placeholders:[],subcommand:"doctor"},descriptors:[]};const n=_(U(o,"utf8")).map(a=>a.key),c=p(t);if(!E(t))return r.error(`env doctor: ${m} is missing. Run \`lunora dev\` to scaffold it, or \`lunora env set <KEY> <VALUE>\`.`),r.info(`expected (from ${g}): ${n.join(", ")}`),{code:d.NOT_FOUND,descriptors:[]};const u=n.filter(a=>!c.has(a)),l=[...c.values()].filter(a=>S(a.value)).map(a=>a.key),i=new Set(n),v=[...c.keys()].filter(a=>!i.has(a));for(const a of u)r.error(`missing: ${a} is in ${g} but not ${m}`);for(const a of l)r.error(`unset: ${a} still has a placeholder value`);for(const a of v)r.info(`extra: ${a} is set locally but not listed in ${g}`);const f=u.length===0&&l.length===0,y={extra:v,missing:u,ok:f,placeholders:l,subcommand:"doctor"};return f?(r.success(`env doctor: ${m} looks good (${String(c.size)} var(s)).`),{code:0,data:y,descriptors:[]}):{code:1,data:y,descriptors:[]}},oe=async e=>{let s=[];try{s=T(await C({projectRoot:e.cwd,schemaDir:F(e.cwd)}))}catch{}const t=M(s),r=O(s).map(n=>n.key).filter(n=>t.has(n)),o=[...p(e.devVariablesPath).keys()].filter(n=>t.has(n));return[...new Set([...r,...o])]},ne=(e,s,t)=>{const r=p(e),o=n=>{const c=r.get(n);return c!==void 0&&c.value!==""&&!S(c.value)};return{live:s.filter(n=>o(n.key)).map(n=>n.key),writable:t?s:s.filter(n=>!o(n.key))}},ae=(e,s)=>{const{devVariablesPath:t,logger:r,options:o}=e,{live:n,writable:c}=ne(t,s,o.yes===!0);if(c.length===0)return r.error(`env generate --set: ${n.join(", ")} already hold(s) a live secret and rotating it is irreversible (outstanding signed URLs and bearers stop verifying). Re-run with --yes to rotate anyway.`),{code:d.USAGE,descriptors:[]};n.length>0&&r.warn(`env generate --set: left ${n.join(", ")} untouched (already set) — re-run with --yes to rotate them too`);let u=h(t);for(const l of c)u=D(u,l.key,l.value);return w(t,u),r.success(`env: generated ${String(c.length)} secret(s) into ${m}: ${c.map(l=>l.key).join(", ")}`),{code:0,data:{subcommand:"generate",written:c.map(l=>l.key)},descriptors:[]}},ce=async e=>{const{logger:s,options:t}=e;let r;if(t.key===void 0){if(r=await oe(e),r.length===0)return s.info("env generate: no locally-generatable secrets for this project. Name one explicitly: lunora env generate <KEY>"),{code:0,data:{secrets:[],subcommand:"generate"},descriptors:[]}}else{if(!b.test(t.key))return s.error(`env: invalid key "${t.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};r=[t.key]}const o=r.map(n=>({key:n,value:z()}));if(t.set===!0)return ae(e,o);if(t.format!=="json")for(const n of o)process.stdout.write(`${n.key}=${n.value}
3
3
  `);return{code:0,data:{secrets:o,subcommand:"generate"},descriptors:[]}},ie=async e=>{const{options:s}=e;switch(s.subcommand){case"diff":return te(e);case"doctor":return se(e);case"generate":return ce(e);case"get":return J(e);case"list":return W(e);case"push":return ee(e);case"set":return Q(e);case"unset":return x(e);default:return e.logger.error(`env: unknown subcommand "${s.subcommand}"`),{code:d.USAGE,descriptors:[]}}},de=async e=>{const s=e.cwd??process.cwd(),t={cwd:s,devVariablesPath:P(s,m),logger:e.logger,options:e};return ie(t)},ue=new Set(["diff","doctor","generate","get","list","push","set","unset"]),le=e=>typeof e=="string"&&ue.has(e),Ee=Y(({argument:e,cwd:s,format:t,logger:r,options:o})=>{const n=e[0];if(!le(n)){const c=`env: unknown subcommand "${n??""}" — expected list | get | set | unset | push | diff | doctor | generate`;return r.error(c),{code:d.USAGE,error:c}}return de({cwd:s,env:o.env,format:t,key:e[1],logger:r,prod:o.prod===!0,set:o.set===!0,subcommand:n,temporary:o.temporary===!0,value:e[2],yes:o.yes===!0})});export{Ee as execute,de as runEnvCommand};
@@ -1 +1 @@
1
- import{d}from"../packem_shared/command-CB4JM-Jl.mjs";import{a as l}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{runExportCommand as m}from"../packem_shared/runExportCommand-slAq5BCF.mjs";const k=d(({argument:o,cwd:e,format:t,logger:u,options:r})=>m({cwd:e,format:t,logger:u,out:o[0]??r.out,prod:r.prod===!0,tables:r.tables,token:r.token,url:l({cwd:e,prod:r.prod===!0,url:r.url})}));export{k as execute};
1
+ import{d}from"../packem_shared/command-B6vu47iR.mjs";import{a as l}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{runExportCommand as m}from"../packem_shared/runExportCommand-DiMCQ-Il.mjs";const k=d(({argument:o,cwd:e,format:t,logger:u,options:r})=>m({cwd:e,format:t,logger:u,out:o[0]??r.out,prod:r.prod===!0,tables:r.tables,token:r.token,url:l({cwd:e,prod:r.prod===!0,url:r.url})}));export{k as execute};
@@ -1 +1 @@
1
- import{d}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as m}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{a as n}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{I as u,r as i}from"../packem_shared/import-ZdIj7W66.mjs";const b=d(({argument:f,cwd:a,format:c,logger:t,options:r})=>{const o=f[0];if(!o){const e="import requires a path. Usage: lunora import <file.ndjson | convex-export-dir> [--table <name>]";return t.error(e),{code:m.USAGE,error:e}}if(r.from!==void 0&&!u.includes(r.from)){const e=`--from ${r.from} is not a known source. Expected one of: ${u.join(", ")}.`;return t.error(e),{code:m.USAGE,error:e}}return i({batchSize:r.batchSize,cwd:a,file:o,format:c,from:r.from,logger:t,prod:r.prod===!0,scan:r.scan===!0,storageDir:r.storageDir,table:r.table,token:r.token,url:n({cwd:a,prod:r.prod===!0,url:r.url}),verify:r.verify===!0,withStorage:r.withStorage===!0,yes:r.yes===!0})});export{b as execute};
1
+ import{d}from"../packem_shared/command-B6vu47iR.mjs";import{EXIT_CODE as m}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{a as n}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{I as u,r as i}from"../packem_shared/import-U8wA5qmm.mjs";const b=d(({argument:f,cwd:a,format:c,logger:t,options:r})=>{const o=f[0];if(!o){const e="import requires a path. Usage: lunora import <file.ndjson | convex-export-dir> [--table <name>]";return t.error(e),{code:m.USAGE,error:e}}if(r.from!==void 0&&!u.includes(r.from)){const e=`--from ${r.from} is not a known source. Expected one of: ${u.join(", ")}.`;return t.error(e),{code:m.USAGE,error:e}}return i({batchSize:r.batchSize,cwd:a,file:o,format:c,from:r.from,logger:t,prod:r.prod===!0,scan:r.scan===!0,storageDir:r.storageDir,table:r.table,token:r.token,url:n({cwd:a,prod:r.prod===!0,url:r.url}),verify:r.verify===!0,withStorage:r.withStorage===!0,yes:r.yes===!0})});export{b as execute};
@@ -1 +1 @@
1
- import{readFileSync as g,existsSync as b}from"node:fs";import{join as y}from"node:path";import{discoverSchema as h}from"@lunora/codegen";import{readLinkedProject as v,readProjectManifest as $}from"@lunora/config";import{findWranglerFile as p}from"@lunora/config/cloudflare";import{parse as k}from"jsonc-parser";import{Project as w}from"ts-morph";import{w as j,d as m}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{d as S}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as f}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";const d=(n,e)=>{if(n===null||typeof n!="object")return;const i=n[e];return typeof i=="string"&&i.length>0?i:void 0},x=(n,e)=>{if(n===null||typeof n!="object")return[];const i=n[e];return Array.isArray(i)?i:[]},E=(n,e)=>{const{manifest:i}=m(e);return{bindings:(i?.bindings??[]).map(r=>`${r.type}:${r.binding}`),compatibilityDate:d(n,"compatibility_date"),compatibilityFlags:x(n,"compatibility_flags").filter(r=>typeof r=="string"),main:d(n,"main"),name:d(n,"name")}},F=n=>({tables:n.tables.map(e=>{let i="root";return e.shardMode==="global"?i="global":typeof e.shardMode=="object"&&(i=`shardBy(${e.shardMode.field})`),{indexes:e.indexes.length,name:e.name,shard:i}}),vectorIndexes:n.vectorIndexes.length}),P=n=>{const e=$(n);if(e===void 0)return[];const i=["dependencies","devDependencies","peerDependencies","optionalDependencies"],r=new Map;for(const s of i){const a=e[s];if(!(a===null||typeof a!="object"))for(const[t,o]of Object.entries(a))t.startsWith("@lunora/")&&typeof o=="string"&&!r.has(t)&&r.set(t,o)}return[...r.entries()].toSorted(([s],[a])=>s.localeCompare(a)).map(([s,a])=>({name:s,version:a}))},M=n=>{const e=P(n),i=p(n);let r;if(i)try{r=E(k(g(i,"utf8")),n)}catch{r=void 0}const s=y(n,"lunora","schema.ts");let a,t;if(b(s))try{const o=new w({skipAddingFilesFromTsConfig:!0,useInMemoryFileSystem:!1});a=F(h(o,s))}catch(o){t=o instanceof Error?o.message:String(o)}return{link:v(n),lunoraPackages:e,projectRoot:n,schema:a,schemaError:t,wrangler:r,wranglerPath:i}},I=(n,e)=>{if(e.info(`project: ${n.projectRoot}`),e.info(""),e.info("@lunora/* packages:"),n.lunoraPackages.length===0)e.info(" (none found in package.json)");else for(const i of n.lunoraPackages)e.info(` ${i.name}@${i.version}`);if(e.info(""),n.wrangler?(e.info(`wrangler: ${n.wranglerPath??""}`),e.info(` name: ${n.wrangler.name??"<unset>"}`),e.info(` main: ${n.wrangler.main??"<unset>"}`),e.info(` compatibility_date: ${n.wrangler.compatibilityDate??"<unset>"}`),e.info(` compatibility_flags: ${n.wrangler.compatibilityFlags.join(", ")||"<none>"}`),e.info(` bindings: ${n.wrangler.bindings.join(", ")||"<none>"}`)):e.info("wrangler: (not found)"),e.info(""),n.link?(e.info(`link: ${n.link.workerName??"(unnamed)"} -> ${n.link.workerUrl??"<no url>"}`),n.link.env!==void 0&&e.info(` env: ${n.link.env}`)):e.info("link: (not linked — run `lunora link --url <https://your-worker>`)"),e.info(""),n.schemaError!==void 0)e.warn(`schema: parse error — ${n.schemaError}`);else if(n.schema){e.info(`schema: ${String(n.schema.tables.length)} table(s), ${String(n.schema.vectorIndexes)} vector index(es)`);for(const i of n.schema.tables)e.info(` ${i.name} [${i.shard}, ${String(i.indexes)} index(es)]`)}else e.info("schema: (no lunora/schema.ts)")},A=n=>{const{cwd:e,json:i,logger:r,out:s}=n;if(s!==void 0){const{error:o}=j({destination:s,logger:r,projectRoot:e});return o!==void 0?(r.error(o),{code:f.USAGE,error:o}):{code:0}}const{error:a,manifest:t}=m(e);if(t===void 0){const o=a??"could not derive the binding manifest";return r.error(o),{code:f.USAGE,error:o}}if(i)return{code:0,data:t};if(t.bindings.length===0)r.info("No bindings declared.");else{const o=Math.max(...t.bindings.map(c=>c.type.length));r.info(`${String(t.bindings.length)} binding(s):`);for(const c of t.bindings){const l=[c.resource,c.className,c.resourceId].filter(u=>u!==void 0).join(" ");r.info(` ${c.type.padEnd(o)} ${c.binding}${l===""?"":` ${l}`}`)}}return t.crons.length>0&&r.info(`${String(t.crons.length)} cron trigger(s): ${t.crons.join(", ")}`),t.vars.length>0&&r.info(`${String(t.vars.length)} var(s): ${t.vars.join(", ")}`),t.unknown.length>0&&r.warn(`not modelled by the manifest: ${t.unknown.join(", ")} — anything they bind must be provisioned by hand.`),{code:0,data:t}},D=n=>{const e=n.cwd??process.cwd(),i=n.format==="json",{logger:r}=n;if(n.bindings===!0)return A({cwd:e,json:i,logger:r,out:n.out});if(n.out!==void 0){const a="--out only applies with --bindings; the full info snapshot prints to stdout under --format json.";return r.error(a),{code:f.USAGE,error:a}}const s=M(e);return i||I(s,r),{code:0,data:s,snapshot:s}},H=S(({cwd:n,format:e,logger:i,options:r})=>D({bindings:r.bindings===!0,cwd:n,format:e,logger:i,out:r.out}));export{M as collectInfo,H as execute,D as runInfoCommand};
1
+ import{readFileSync as g,existsSync as b}from"node:fs";import{join as y}from"node:path";import{discoverSchema as h}from"@lunora/codegen";import{readLinkedProject as v,readProjectManifest as $}from"@lunora/config";import{findWranglerFile as p}from"@lunora/config/cloudflare";import{parse as k}from"jsonc-parser";import{Project as w}from"ts-morph";import{w as j,d as m}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{d as S}from"../packem_shared/command-B6vu47iR.mjs";import{EXIT_CODE as f}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";const d=(n,e)=>{if(n===null||typeof n!="object")return;const i=n[e];return typeof i=="string"&&i.length>0?i:void 0},x=(n,e)=>{if(n===null||typeof n!="object")return[];const i=n[e];return Array.isArray(i)?i:[]},E=(n,e)=>{const{manifest:i}=m(e);return{bindings:(i?.bindings??[]).map(r=>`${r.type}:${r.binding}`),compatibilityDate:d(n,"compatibility_date"),compatibilityFlags:x(n,"compatibility_flags").filter(r=>typeof r=="string"),main:d(n,"main"),name:d(n,"name")}},F=n=>({tables:n.tables.map(e=>{let i="root";return e.shardMode==="global"?i="global":typeof e.shardMode=="object"&&(i=`shardBy(${e.shardMode.field})`),{indexes:e.indexes.length,name:e.name,shard:i}}),vectorIndexes:n.vectorIndexes.length}),P=n=>{const e=$(n);if(e===void 0)return[];const i=["dependencies","devDependencies","peerDependencies","optionalDependencies"],r=new Map;for(const s of i){const a=e[s];if(!(a===null||typeof a!="object"))for(const[t,o]of Object.entries(a))t.startsWith("@lunora/")&&typeof o=="string"&&!r.has(t)&&r.set(t,o)}return[...r.entries()].toSorted(([s],[a])=>s.localeCompare(a)).map(([s,a])=>({name:s,version:a}))},M=n=>{const e=P(n),i=p(n);let r;if(i)try{r=E(k(g(i,"utf8")),n)}catch{r=void 0}const s=y(n,"lunora","schema.ts");let a,t;if(b(s))try{const o=new w({skipAddingFilesFromTsConfig:!0,useInMemoryFileSystem:!1});a=F(h(o,s))}catch(o){t=o instanceof Error?o.message:String(o)}return{link:v(n),lunoraPackages:e,projectRoot:n,schema:a,schemaError:t,wrangler:r,wranglerPath:i}},I=(n,e)=>{if(e.info(`project: ${n.projectRoot}`),e.info(""),e.info("@lunora/* packages:"),n.lunoraPackages.length===0)e.info(" (none found in package.json)");else for(const i of n.lunoraPackages)e.info(` ${i.name}@${i.version}`);if(e.info(""),n.wrangler?(e.info(`wrangler: ${n.wranglerPath??""}`),e.info(` name: ${n.wrangler.name??"<unset>"}`),e.info(` main: ${n.wrangler.main??"<unset>"}`),e.info(` compatibility_date: ${n.wrangler.compatibilityDate??"<unset>"}`),e.info(` compatibility_flags: ${n.wrangler.compatibilityFlags.join(", ")||"<none>"}`),e.info(` bindings: ${n.wrangler.bindings.join(", ")||"<none>"}`)):e.info("wrangler: (not found)"),e.info(""),n.link?(e.info(`link: ${n.link.workerName??"(unnamed)"} -> ${n.link.workerUrl??"<no url>"}`),n.link.env!==void 0&&e.info(` env: ${n.link.env}`)):e.info("link: (not linked — run `lunora link --url <https://your-worker>`)"),e.info(""),n.schemaError!==void 0)e.warn(`schema: parse error — ${n.schemaError}`);else if(n.schema){e.info(`schema: ${String(n.schema.tables.length)} table(s), ${String(n.schema.vectorIndexes)} vector index(es)`);for(const i of n.schema.tables)e.info(` ${i.name} [${i.shard}, ${String(i.indexes)} index(es)]`)}else e.info("schema: (no lunora/schema.ts)")},A=n=>{const{cwd:e,json:i,logger:r,out:s}=n;if(s!==void 0){const{error:o}=j({destination:s,logger:r,projectRoot:e});return o!==void 0?(r.error(o),{code:f.USAGE,error:o}):{code:0}}const{error:a,manifest:t}=m(e);if(t===void 0){const o=a??"could not derive the binding manifest";return r.error(o),{code:f.USAGE,error:o}}if(i)return{code:0,data:t};if(t.bindings.length===0)r.info("No bindings declared.");else{const o=Math.max(...t.bindings.map(c=>c.type.length));r.info(`${String(t.bindings.length)} binding(s):`);for(const c of t.bindings){const l=[c.resource,c.className,c.resourceId].filter(u=>u!==void 0).join(" ");r.info(` ${c.type.padEnd(o)} ${c.binding}${l===""?"":` ${l}`}`)}}return t.crons.length>0&&r.info(`${String(t.crons.length)} cron trigger(s): ${t.crons.join(", ")}`),t.vars.length>0&&r.info(`${String(t.vars.length)} var(s): ${t.vars.join(", ")}`),t.unknown.length>0&&r.warn(`not modelled by the manifest: ${t.unknown.join(", ")} — anything they bind must be provisioned by hand.`),{code:0,data:t}},D=n=>{const e=n.cwd??process.cwd(),i=n.format==="json",{logger:r}=n;if(n.bindings===!0)return A({cwd:e,json:i,logger:r,out:n.out});if(n.out!==void 0){const a="--out only applies with --bindings; the full info snapshot prints to stdout under --format json.";return r.error(a),{code:f.USAGE,error:a}}const s=M(e);return i||I(s,r),{code:0,data:s,snapshot:s}},H=S(({cwd:n,format:e,logger:i,options:r})=>D({bindings:r.bindings===!0,cwd:n,format:e,logger:i,out:r.out}));export{M as collectInfo,H as execute,D as runInfoCommand};
@@ -1,2 +1,2 @@
1
- import{r as v}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as M,a as y}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as x}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as u,exitCodeForStatus as T}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{a as I}from"../packem_shared/resolve-target-C_ZloTvd.mjs";const O="__lunora_admin__:getFunctionStats",_=10,f=(r,o)=>({calls:r.calls,conflicts:r.conflicts??0,errors:r.errors,lastErrorMessage:r.lastErrorMessage,maxDurationMs:r.maxDurationMs,meanDurationMs:r.calls===0?0:r.totalDurationMs/r.calls,path:r.path,rate:o}),k=(r,o)=>{const t=r.filter(e=>(e.conflicts??0)>0).map(e=>f(e,e.calls===0?0:(e.conflicts??0)/e.calls)).toSorted((e,s)=>s.rate-e.rate||s.conflicts-e.conflicts).slice(0,o),n=r.filter(e=>e.errors>0).map(e=>f(e,e.calls===0?0:e.errors/e.calls)).toSorted((e,s)=>s.rate-e.rate||s.errors-e.errors).slice(0,o),a=r.map(e=>f(e,0)).toSorted((e,s)=>s.maxDurationMs-e.maxDurationMs).slice(0,o);return{errorHotspots:n,latencyOutliers:a,totalFunctions:r.length,writeContention:t}},p=r=>`${(r*100).toFixed(1)}%`,$=r=>r<1e3?`${Math.round(r).toString()}ms`:`${(r/1e3).toFixed(2)}s`,m=(r,o,t,n)=>[r,...o.length===0?[` ${t}`]:o.map(a=>` ${n(a)}`)],E=r=>{const o=t=>t.lastErrorMessage?` — ${t.lastErrorMessage}`:"";return[`Insights over ${r.totalFunctions.toString()} function${r.totalFunctions===1?"":"s"}`,"",...m("Write-conflict hot-spots (OCC contention — candidates for sharding):",r.writeContention,"none — no write conflicts observed",t=>`${t.path} ${t.conflicts.toString()}/${t.calls.toString()} calls (${p(t.rate)})`),"",...m("Error hot-spots:",r.errorHotspots,"none — no errors observed",t=>`${t.path} ${t.errors.toString()}/${t.calls.toString()} calls (${p(t.rate)})${o(t)}`),"",...m("Latency outliers (slowest single call):",r.latencyOutliers,"none — no functions have run",t=>`${t.path} max ${$(t.maxDurationMs)}, mean ${$(t.meanDurationMs)} over ${t.calls.toString()} calls`)].join(`
1
+ import{r as v}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as M,a as y}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as x}from"../packem_shared/command-B6vu47iR.mjs";import{EXIT_CODE as u,exitCodeForStatus as T}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{a as I}from"../packem_shared/resolve-target-C_ZloTvd.mjs";const O="__lunora_admin__:getFunctionStats",_=10,f=(r,o)=>({calls:r.calls,conflicts:r.conflicts??0,errors:r.errors,lastErrorMessage:r.lastErrorMessage,maxDurationMs:r.maxDurationMs,meanDurationMs:r.calls===0?0:r.totalDurationMs/r.calls,path:r.path,rate:o}),k=(r,o)=>{const t=r.filter(e=>(e.conflicts??0)>0).map(e=>f(e,e.calls===0?0:(e.conflicts??0)/e.calls)).toSorted((e,s)=>s.rate-e.rate||s.conflicts-e.conflicts).slice(0,o),n=r.filter(e=>e.errors>0).map(e=>f(e,e.calls===0?0:e.errors/e.calls)).toSorted((e,s)=>s.rate-e.rate||s.errors-e.errors).slice(0,o),a=r.map(e=>f(e,0)).toSorted((e,s)=>s.maxDurationMs-e.maxDurationMs).slice(0,o);return{errorHotspots:n,latencyOutliers:a,totalFunctions:r.length,writeContention:t}},p=r=>`${(r*100).toFixed(1)}%`,$=r=>r<1e3?`${Math.round(r).toString()}ms`:`${(r/1e3).toFixed(2)}s`,m=(r,o,t,n)=>[r,...o.length===0?[` ${t}`]:o.map(a=>` ${n(a)}`)],E=r=>{const o=t=>t.lastErrorMessage?` — ${t.lastErrorMessage}`:"";return[`Insights over ${r.totalFunctions.toString()} function${r.totalFunctions===1?"":"s"}`,"",...m("Write-conflict hot-spots (OCC contention — candidates for sharding):",r.writeContention,"none — no write conflicts observed",t=>`${t.path} ${t.conflicts.toString()}/${t.calls.toString()} calls (${p(t.rate)})`),"",...m("Error hot-spots:",r.errorHotspots,"none — no errors observed",t=>`${t.path} ${t.errors.toString()}/${t.calls.toString()} calls (${p(t.rate)})${o(t)}`),"",...m("Latency outliers (slowest single call):",r.latencyOutliers,"none — no functions have run",t=>`${t.path} max ${$(t.maxDurationMs)}, mean ${$(t.meanDurationMs)} over ${t.calls.toString()} calls`)].join(`
2
2
  `)},i=(r,o,t)=>(r.error(o),{code:t,error:o}),F=r=>r===void 0||!Number.isFinite(r)||r<=0?_:Math.floor(r),A=async r=>{const{logger:o}=r;if(r.prod&&r.url===void 0)return i(o,"--prod requires an explicit --url (refusing to report from the implicit localhost worker)",u.USAGE);const t=M(r.url,o,r.cwd);if(t===void 0)return{code:u.USAGE,error:"could not resolve a usable worker URL"};const{token:n}=v({cwd:r.cwd??process.cwd(),token:r.token,url:t});if(!n)return i(o,"admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)",u.AUTH);const a=`${t}/_lunora/rpc`,e=y,s={args:{},functionPath:O};r.shard!==void 0&&(s.shardKey=r.shard),o.info(`POST ${a} -> insights`);const l=await e(a,{body:JSON.stringify(s),headers:{authorization:`Bearer ${n}`,"content-type":"application/json"},method:"POST"}),c=await l.text();if(!l.ok)return i(o,`insights failed: HTTP ${String(l.status)}: ${c}`,T(l.status));let d;try{d=JSON.parse(c)}catch{return i(o,`insights failed: worker returned non-JSON: ${c}`,1)}const S=d.result??d,{functions:h}=S;if(!Array.isArray(h))return i(o,"insights failed: response carried no `functions` array",1);const g=k(h,F(r.limit));return r.format!=="json"&&o.info(E(g)),{code:0,report:g}},L=x(async({cwd:r,format:o,logger:t,options:n})=>{const a=n.limit===void 0?void 0:Number.parseInt(n.limit,10),e=await A({cwd:r,fetchImpl:void 0,format:o,limit:a,logger:t,prod:n.prod,shard:n.shard,token:n.token,url:I({cwd:r,prod:n.prod===!0,url:n.url})});return{code:e.code,data:e.report,error:e.error}});export{k as buildInsightsReport,L as execute,E as formatInsightsReport,A as runInsightsCommand};
@@ -1,4 +1,4 @@
1
- import{runCodegen as h}from"@lunora/codegen";import{resolveLazyFunctions as m,applyLintIgnores as w,detectLintTools as y,inferLunoraBindings as $,resolveSchemaDirectory as A}from"@lunora/config";import{warnCloudflareCliConfigOnce as T,wranglerCodegenInputs as C,collectExportGaps as D}from"@lunora/config/cloudflare";import{r as S,a as x,e as k}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{p as E}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{d as b}from"../packem_shared/command-CB4JM-Jl.mjs";import{r as I}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as L}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{r as O}from"../packem_shared/lint-ignore-report-DKZagpqk.mjs";const u=3,R=r=>{const s=r.cwd??process.cwd(),{logger:i}=r,e=S(r),o=I(s,r.target);if(o.target===void 0)return r.logger.error(o.error??"unknown deploy target"),{advisories:[],code:L.USAGE,cronTriggers:[],error:o.error??"unknown deploy target",failedAdvisories:0,outputDirectory:""};const{target:a}=o,t=h({apiSpec:r.apiSpec,lazyFunctions:m(r.lazyFunctions),projectRoot:s,target:a,...C(s)}),d={advisories:t.advisories.map(n=>({detail:n.detail,level:n.level,name:n.name,remediation:n.remediation})),cronTriggers:t.cronTriggers,failedAdvisories:0,outputDirectory:t.outputDirectory};if(i.success(`codegen wrote ${t.writtenFiles.join(", ")} to ${t.outputDirectory}`),t.advisories.length>0){const n=t.advisories.length,v=t.advisories.map(c=>` [${c.level}] ${c.name} — ${c.detail}
1
+ import{runCodegen as h}from"@lunora/codegen";import{resolveLazyFunctions as m,applyLintIgnores as w,detectLintTools as y,inferLunoraBindings as $,resolveSchemaDirectory as A}from"@lunora/config";import{warnCloudflareCliConfigOnce as T,wranglerCodegenInputs as C,collectExportGaps as D}from"@lunora/config/cloudflare";import{r as S,a as x,e as k}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{p as E}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{d as b}from"../packem_shared/command-B6vu47iR.mjs";import{r as I}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as L}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{r as O}from"../packem_shared/lint-ignore-report-DKZagpqk.mjs";const u=3,R=r=>{const s=r.cwd??process.cwd(),{logger:i}=r,e=S(r),o=I(s,r.target);if(o.target===void 0)return r.logger.error(o.error??"unknown deploy target"),{advisories:[],code:L.USAGE,cronTriggers:[],error:o.error??"unknown deploy target",failedAdvisories:0,outputDirectory:""};const{target:a}=o,t=h({apiSpec:r.apiSpec,lazyFunctions:m(r.lazyFunctions),projectRoot:s,target:a,...C(s)}),d={advisories:t.advisories.map(n=>({detail:n.detail,level:n.level,name:n.name,remediation:n.remediation})),cronTriggers:t.cronTriggers,failedAdvisories:0,outputDirectory:t.outputDirectory};if(i.success(`codegen wrote ${t.writtenFiles.join(", ")} to ${t.outputDirectory}`),t.advisories.length>0){const n=t.advisories.length,v=t.advisories.map(c=>` [${c.level}] ${c.name} — ${c.detail}
2
2
  ↳ ${c.remediation}`);i.warn(`${n.toString()} schema ${n===1?"advisory":"advisories"}:
3
3
  ${v.join(`
4
4
  `)}`)}const g=x(t.platformDiagnostics,i);g.errors.length>0&&(d.error=g.errors.join("; "));const{errorAdvisories:l,names:p,shouldBlock:f}=k(d.advisories,e);return f&&i.error(`${l.length.toString()} ERROR-level ${l.length===1?"advisory":"advisories"} (${p.join(", ")}). Codegen wrote its output; this exit code is the gate. Pass --no-strict-advisories to downgrade it to a warning.`),t.cronTriggers.length>u&&i.warn(`${t.cronTriggers.length.toString()} distinct cron expressions declared — Cloudflare allows at most ${u.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).`),{...d,failedAdvisories:e?l.length:0}},F=async(r,s)=>{let i;try{i=D(await $({projectRoot:r,schemaDir:A(r)}))}catch(e){s.warn(`could not check whether declared containers/workflows/agents are re-exported by the worker entry: ${e instanceof Error?e.message:String(e)}. Run \`lunora doctor\` to check explicitly.`);return}for(const e of i)s.warn(`${e.kind} "${e.exportName}" is declared but ${e.className} is not exported by the worker entry — add \`export * from "./lunora/_generated/${e.module}"\` so wrangler can provision its binding. Until then it deploys with nothing to run.`)},G=(r,s)=>{try{O(w(r,y(r)),s)}catch{}},W=b(async({cwd:r,format:s,logger:i,options:e})=>{const o=R({apiSpec:E(e.apiSpec),cwd:r,format:s,lazyFunctions:m(e.lazyFunctions),logger:i,strictAdvisories:e.strictAdvisories,target:e.target});if(o.outputDirectory!==""){const a=i;G(r,a),await F(r,a),T(r,t=>{a.warn(t)})}return{code:o.code??(o.error===void 0&&o.failedAdvisories===0?0:1),data:{advisories:o.advisories,cronTriggers:o.cronTriggers,failedAdvisories:o.failedAdvisories,outputDirectory:o.outputDirectory},error:o.error}});export{W as execute,R as runCodegenCommand};
@@ -1,6 +1,6 @@
1
- import{readServiceBindings as re,createCodegenProject as te,refreshCodegenProject as ne,findTsconfig as oe,projectConfigCandidatePaths as ae,PROJECT_CONFIG_FILENAMES as ie,runCodegen as V}from"@lunora/codegen";import{runPostCodegenHook as se,resolveLazyFunctions as k,detectAgentRules as B,updateDevServerState as z,readDevServerState as ce,DEV_DAEMON_ENV as G,detectAiAgent as de,readProjectRemotePreference as le,readLiveDevServerState as X,createDiscoveryCache as ue,DEV_HANDOFF_ENV as D,clearDevServerState as fe,claimDevServerState as ge,DEV_LOG_FILE_ENV as ve,secretSourcesFromBindings as me,inferLunoraBindings as we,resolveSchemaDirectory as he,ensureDevVarsExample as ye,ensureDevVariables as Se,isInteractive as pe,DEV_VARS_FILE as $,DEV_VARS_EXAMPLE_FILE as Ee,fillDevSecrets as be,DEV_BINDINGS_FILE as _e,DEV_STATE_FILE as J,writeCelldDevProjection as ke,claimAgentRulesHint as Pe,AGENT_RULES_HINT as Ce}from"@lunora/config";import{wranglerCodegenInputs as Y,resolveRemoteEnabled as Re,startLocalHyperdrive as De,describeWithheld as Te}from"@lunora/config/cloudflare";import{p as $e}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{w as Ae}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{existsSync as Oe,watch as A,readFileSync as Fe,statSync as Ie}from"node:fs";import{join as P,dirname as K}from"node:path";import{STUDIO_WRITE_TEMP_SUFFIX as Le,consumeStudioWrite as O,loadStudioAssets as F,studioAssetsStamp as I,createStudioDocumentCache as je,renderStudioHtml as Ne,resolveAdminToken as Me,requestPathname as L,transportRejectionReason as j,SCHEMA_EDIT_ENDPOINT as We,POLICY_SCAFFOLD_ENDPOINT as He,SEED_ENDPOINT as xe,sendStudioDocument as Ue,serveJsonHandler as Ve,isStandaloneModulePath as Be,readStandaloneAsset as ze,assetContentType as Ge,handleSchemaEditRequest as Xe,handlePolicyScaffoldRequest as Je,handleSeedRequest as Ye,applyStudioAssetCache as Ke}from"@lunora/config/studio-host";import{ts as N}from"ts-morph";import{a as Qe}from"../packem_shared/codegen-error-AmH54ofi.mjs";import{a as Ze}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{d as qe}from"../packem_shared/command-CB4JM-Jl.mjs";import{a as er}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as C}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{forceJsonLogging as rr}from"../packem_shared/createLogger-4TujevyB.mjs";import{createServer as tr,request as nr}from"node:http";import{connect as or}from"node:net";import{pipeline as ar}from"node:stream";import{c as ir}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{setTimeout as sr}from"node:timers/promises";import{r as cr,d as dr,P as lr,a as ur,b as T,c as fr,s as gr,n as Q,e as vr,f as mr,g as wr,h as hr,i as M,w as yr,j as Sr,k as pr,t as Er,l as br,m as _r,o as kr}from"../packem_shared/plan-lzllz0Cn.mjs";import{b as Pr}from"./handler.mjs";const Cr=100,Rr=/(?:\.[cm]?[jt]sx?|(?:^|[/\\])tsconfig(?:\..+)?\.json)$/u,Dr=300,W=/[/\\]/u,H=e=>{try{return Fe(e,"utf8")}catch{return""}},Tr=e=>{try{const{mtimeMs:r,size:n}=Ie(e);return`${String(r)}:${String(n)}`}catch{return""}},$r=["pnpm-lock.yaml","package-lock.json","yarn.lock","bun.lock","node_modules/.modules.yaml","node_modules/.package-lock.json","node_modules/.yarn-integrity"],Z=e=>{const r=K(e);return r===e?[e]:[e,...Z(r)]},Ar=e=>{const r={...N.sys,onUnRecoverableConfigFileDiagnostic:()=>{},readDirectory:()=>[]},n=N.getParsedCommandLineOfConfigFile(e,{},r);return n===void 0?"":JSON.stringify(n.options)},Or=(e,r)=>{const n=oe(r),o=[...new Set([...ae(r),...ie.map(s=>P(e,s))])].map(s=>`${s}
1
+ import{readServiceBindings as re,createCodegenProject as te,refreshCodegenProject as ne,findTsconfig as oe,projectConfigCandidatePaths as ae,PROJECT_CONFIG_FILENAMES as ie,runCodegen as V}from"@lunora/codegen";import{runPostCodegenHook as se,resolveLazyFunctions as k,detectAgentRules as B,updateDevServerState as z,readDevServerState as ce,DEV_DAEMON_ENV as G,detectAiAgent as de,readProjectRemotePreference as le,readLiveDevServerState as X,createDiscoveryCache as ue,DEV_HANDOFF_ENV as T,clearDevServerState as fe,claimDevServerState as ge,DEV_LOG_FILE_ENV as ve,secretSourcesFromBindings as me,inferLunoraBindings as we,resolveSchemaDirectory as he,ensureDevVarsExample as ye,ensureDevVariables as Se,isInteractive as pe,DEV_VARS_FILE as $,DEV_VARS_EXAMPLE_FILE as Ee,fillDevSecrets as be,DEV_BINDINGS_FILE as _e,DEV_STATE_FILE as J,writeCelldDevProjection as ke,claimAgentRulesHint as Pe,AGENT_RULES_HINT as Re}from"@lunora/config";import{wranglerCodegenInputs as Y,resolveRemoteEnabled as De,startLocalHyperdrive as Te,describeWithheld as Ce}from"@lunora/config/cloudflare";import{p as $e}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{w as Ae}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{existsSync as Oe,watch as A,readFileSync as Fe,statSync as Ie}from"node:fs";import{join as P,dirname as K}from"node:path";import{STUDIO_WRITE_TEMP_SUFFIX as Le,consumeStudioWrite as O,loadStudioAssets as F,studioAssetsStamp as I,createStudioDocumentCache as je,renderStudioHtml as Ne,resolveAdminToken as Me,requestPathname as L,transportRejectionReason as j,SCHEMA_EDIT_ENDPOINT as We,POLICY_SCAFFOLD_ENDPOINT as He,SEED_ENDPOINT as xe,sendStudioDocument as Ue,serveJsonHandler as Ve,isStandaloneModulePath as Be,readStandaloneAsset as ze,assetContentType as Ge,handleSchemaEditRequest as Xe,handlePolicyScaffoldRequest as Je,handleSeedRequest as Ye,sendStudioAsset as Ke}from"@lunora/config/studio-host";import{ts as N}from"ts-morph";import{a as Qe}from"../packem_shared/codegen-error-AmH54ofi.mjs";import{a as Ze}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{d as qe}from"../packem_shared/command-B6vu47iR.mjs";import{a as er}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as R}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{forceJsonLogging as rr}from"../packem_shared/createLogger-4TujevyB.mjs";import{createServer as tr,request as nr}from"node:http";import{connect as or}from"node:net";import{pipeline as ar}from"node:stream";import{c as ir}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{setTimeout as sr}from"node:timers/promises";import{r as cr,d as dr,P as lr,a as ur,b as C,c as fr,s as gr,n as Q,e as vr,f as mr,g as wr,h as hr,i as M,w as yr,j as Sr,k as pr,t as Er,l as br,m as _r,o as kr}from"../packem_shared/plan-BH4ZfiFs.mjs";import{b as Pr}from"./handler.mjs";const Rr=100,Dr=/(?:\.[cm]?[jt]sx?|(?:^|[/\\])tsconfig(?:\..+)?\.json)$/u,Tr=300,W=/[/\\]/u,H=e=>{try{return Fe(e,"utf8")}catch{return""}},Cr=e=>{try{const{mtimeMs:r,size:n}=Ie(e);return`${String(r)}:${String(n)}`}catch{return""}},$r=["pnpm-lock.yaml","package-lock.json","yarn.lock","bun.lock","node_modules/.modules.yaml","node_modules/.package-lock.json","node_modules/.yarn-integrity"],Z=e=>{const r=K(e);return r===e?[e]:[e,...Z(r)]},Ar=e=>{const r={...N.sys,onUnRecoverableConfigFileDiagnostic:()=>{},readDirectory:()=>[]},n=N.getParsedCommandLineOfConfigFile(e,{},r);return n===void 0?"":JSON.stringify(n.options)},Or=(e,r)=>{const n=oe(r),o=[...new Set([...ae(r),...ie.map(s=>P(e,s))])].map(s=>`${s}
2
2
  ${H(s)}`),i=Z(e).flatMap(s=>$r.map(a=>`${P(s,a)}
3
- ${Tr(P(s,a))}`));return[n===void 0?"":`${n}
3
+ ${Cr(P(s,a))}`));return[n===void 0?"":`${n}
4
4
  ${H(n)}
5
- ${Ar(n)}`,...o,...i].join("\0")},Fr=(e,r)=>{let n,t;return{drop:()=>{n=void 0},get:()=>{const o=Or(e,r);return n===void 0||o!==t?(t=o,n=te(r)):ne(n,r),n}}},Ir=(e,r,n,t)=>{const{apiSpec:o,lazyFunctions:i,logger:l,projectRoot:s,target:a}=e;try{const u=V({apiSpec:o,lazyFunctions:k(i),lunoraDirectory:r,project:t.get(),projectRoot:s,target:a,...Y(s)});l.success(`codegen: wrote ${r}/_generated (${n})`),Ze(u.platformDiagnostics,l)}catch(u){return t.drop(),l.error(Qe(u,n)),!1}return!0},Lr=e=>{const r=e.lunoraDirectory??"lunora",n=P(e.projectRoot,r),t=e.debounceMs??Cr;let o=!1,i=!1,l=!1,s,a=0,u=Promise.resolve();const b=Fr(e.projectRoot,n);let w;const c=new Promise(v=>{w=v}),d=(v,m)=>{o||(s={codegen:m||s?.codegen===!0,reason:v},!i&&(i=!0,u=(async()=>{try{for(;s!==void 0;){const h=s;if(s=void 0,!(h.codegen&&!Ir(e,r,h.reason,b))){l=!0;try{(await se({cwd:e.projectRoot,logger:e.logger,spawner:e.spawner,stdoutToStderr:e.jsonLogs})).ran&&(a=Date.now())}finally{l=!1}}}}catch(h){e.logger.error(`codegen watch: ${h instanceof Error?h.message:String(h)}`)}finally{i=!1,w()}})()))};d("startup",!0);const f=v=>(e.logger.warn(`codegen watch unavailable (${v}) — schema edits will NOT auto-regenerate. Run \`lunora codegen\` manually after each edit.`),{close:()=>(o=!0,s=void 0,u),ready:c,watchAvailable:!1});let g,y;if(!Oe(n))return f(`no such directory: ${n}`);const S=new Set;let p=!1;const _=(v,m)=>{if(l||Date.now()-a<Dr){g&&(clearTimeout(g),g=void 0);let h=!1;for(const E of[...S,v])E!==void 0&&O(E)&&(h=!0);S.clear(),p=!1,h&&d(`change: ${m}`,!1);return}v===void 0?p=!0:S.add(v),g&&clearTimeout(g),g=setTimeout(()=>{const h=[...S].map(ee=>O(ee)),E=p||h.includes(!1);S.clear(),p=!1,d(`change: ${m}`,E)},t)},R=[];try{y=A(n,{recursive:!0},(v,m)=>{if(typeof m!="string"){_(void 0,"?");return}m.split(W).includes("_generated")||m.endsWith(Le)||_(P(n,m),m)});for(const v of re(e.projectRoot).services)if(v.rpcEntrypoint!==void 0){const m=A(K(v.wranglerPath),{recursive:!0},(h,E)=>{typeof E=="string"&&Rr.test(E)&&!E.split(W).includes("node_modules")&&_(void 0,E)});m.on("error",h=>{e.logger.warn(`codegen watch: stopped watching service "${v.name}" (${h.message})`),m.close()}),R.push(m)}}catch(v){y?.close();for(const m of R)m.close();return f(v instanceof Error?v.message:String(v))}const q=y;return{close:()=>{o=!0,s=void 0,g&&clearTimeout(g),q.close();for(const v of R)v.close();return u},ready:c,watchAvailable:!0}},x="/_lunora",jr=3e4,U=(e,r,n,t=jr)=>{const o=nr({headers:{...e.headers,host:n.host},hostname:n.hostname,method:e.method,path:e.url,port:n.port},i=>{o.setTimeout(0),r.writeHead(i.statusCode??502,i.headers),ar(i,r,()=>{})});o.setTimeout(t,()=>{o.destroy(new Error(`no response within ${String(t)}ms`))}),o.on("error",i=>{if(r.headersSent){r.destroy();return}r.statusCode=i.message.startsWith("no response within")?504:502,r.end(`lunora dev: worker unreachable (${i.message})`)}),r.on("close",()=>{r.writableFinished||o.destroy()}),e.pipe(o)},Nr=5e3,Mr=(e,r,n,t)=>{const o=or({host:t.hostname,port:Number(t.port)},()=>{o.setTimeout(0);const i=[`${e.method??"GET"} ${e.url??"/"} HTTP/1.1`];for(const[l,s]of Object.entries(e.headers))if(l!=="host")for(const a of Array.isArray(s)?s:[s])a!==void 0&&i.push(`${l}: ${a}`);i.push(`host: ${t.host}`,"",""),o.write(i.join(`\r
6
- `)),n.length>0&&o.write(n),o.pipe(r),r.pipe(o)});o.setTimeout(Nr,()=>o.destroy()),o.on("error",()=>r.destroy()),r.on("error",()=>o.destroy())},Wr=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=F(e.logger,import.meta.url),i=I(import.meta.url);const l=je(e.cwd,()=>Buffer.from(Ne({adminToken:n?Me(e.cwd):void 0,basePath:"/",dataEditable:n,rulesInstalled:B(e.cwd).installed,runAsIdentity:n,schemaEditable:n,scriptSrc:"/studio.js",styleHref:"/styles.css"}))),s=(c,d,f,g,y)=>{Ke(c,d,y,i)||(d.statusCode=200,d.setHeader("Content-Type",g),d.end(f))},a=(c,d,f)=>{c.statusCode=d,c.setHeader("Content-Type","text/plain"),c.end(f)},u=(c,d,f,g)=>{if(n){Ve(c,d,f,e.cwd,{apiSpec:e.apiSpec,lazyFunctions:k(e.lazyFunctions)});return}a(d,403,g)},b=(c,d,f)=>{const g=c==="/styles.css",y=Be(c);if(!g&&!y)return!1;const S=I(import.meta.url);if(S!==i&&(o=F(e.logger,import.meta.url),i=S),o===void 0)return a(f,501,"Lunora studio assets not found — install and build @lunora/studio."),!0;if(g)return s(d,f,o.styles,"text/css; charset=utf-8","styles.css"),!0;const p=c.slice(c.lastIndexOf("/")+1),_=ze(p,import.meta.url);return _===void 0?(a(f,404,"Not found"),!0):(s(d,f,_,Ge(p),p),!0)};l();const w=tr((c,d)=>{const f=L(c.url??"/");if(n){const y=j(c,e.logger);if(y!==void 0){a(d,403,y);return}}if(f.startsWith(x)){if(!n){a(d,403,"Lunora studio: the worker admin proxy is only available on a loopback bind.");return}U(c,d,t);return}if(f===We){u(c,d,Xe,"Lunora schema editing is only available on loopback hosts in dev.");return}if(f===He){u(c,d,Je,"Lunora policy scaffolding is only available on loopback hosts in dev.");return}if(f===xe){u(c,d,Ye,"Lunora data seeding is only available on loopback hosts in dev.");return}if(b(f,c,d))return;const g=c.headers["sec-fetch-mode"];if(n&&typeof g=="string"&&g!=="navigate"){U(c,d,t);return}Ue(d,l())});return w.on("upgrade",(c,d,f)=>{if(!n||j(c,e.logger)!==void 0){d.destroy();return}L(c.url??"").startsWith(x)?Mr(c,d,f,t):d.destroy()}),await new Promise((c,d)=>{w.once("error",f=>{f.code==="EADDRINUSE"?d(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})):d(f)}),w.listen(e.port,r,()=>{c()})}),{close:()=>new Promise(c=>{w.close(()=>{c()})}),url:`http://${r==="0.0.0.0"||r==="::"?"localhost":r}:${String(e.port)}`}},Hr=async e=>{const{cwd:r,logger:n,origin:t,signal:o}=e,i=e.probe??dr,l=cr(e.readyTimeoutMs),s=Date.now()+l,a=()=>o.aborted;for(;!a()&&Date.now()<s;){let u=!1;try{u=await i(t,o)}catch{}if(a()||Date.now()>=s)break;if(u)return z(r,{readyAt:new Date().toISOString()},{expectedPid:process.pid}),ce(r)?.readyAt!==void 0;try{await sr(lr,void 0,{signal:o})}catch{}}return a()||n.warn(`server did not answer on ${t} within ${String(Math.round(l/1e3))}s — .lunora/dev.json will have no readyAt, so anything polling for readiness will keep waiting. The server itself is unaffected; raise LUNORA_DEV_READY_TIMEOUT_MS if it is just slow to boot.`),!1},xr=(e,r,n,t)=>{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/"),t!==void 0&&e.info(` ➜ Supervisor: ${t} (needs) · ${J} (status)`),r.remote.enabled&&(r.remote.bindings.length>0?e.info(` ➜ Remote: ${r.remote.bindings.join(", ")} → deployed worker`):r.remote.reason!==void 0&&e.warn(` ➜ Remote: requested but inactive (${r.remote.reason}) — running fully local`));for(const o of r.remote.withheld)e.warn(` ➜ ${Te(o)}`);e.info("")},Ur=(e,r)=>{B(r).installed||!Pe()||(e.info(` ⓘ ${Ce}`),e.info(""))},Vr=async(e,r,n)=>{let t;try{t=me(await we({cache:n,projectRoot:r,schemaDir:he(r)}))}catch{}if(t!==void 0)try{const i=(e.ensureExample??ye)(r,t);if(i.length>0){const l=t.map(s=>s.includes(":")?`${s.replace(":"," (")})`:s);e.logger.info(`Updated .dev.vars.example with secrets for: ${l.join(", ")} (${i.join(", ")})`)}}catch{}(await(e.ensureEnv??Se)({confirm:ir(),cwd:r,info:i=>{e.logger.info(i)}})).status==="declined"&&!pe()&&e.logger.info(`hint: ${$} was not scaffolded (non-interactive run). Copy ${Ee} → ${$} and fill in secrets, or run \`lunora dev\` in an interactive terminal to scaffold automatically.`);try{(e.fillSecrets??be)({cwd:r,secretSources:t,info:i=>{e.logger.info(i)}})}catch{}},Br=(e,r,n,t,o)=>{if(e.flavor!=="wrangler")return;t!==void 0&&z(r,{studioUrl:t});let i;try{i=kr(r,n)}catch{}return xr(n,e,t,o),i},zr=(e,r)=>{const n=Number(process.env[D]),t=ge(r,{background:process.env[G]==="1",logFile:process.env[ve],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},Gr=e=>{const{cwd:r,destination:n,logger:t,plan:o}=e,i=n!==void 0,l=n??_e,s=Ae({destination:l,dev:{...o.flavor==="wrangler"?{origin:o.workerOrigin}:{},statusFile:J},logger:i?t:{...t,success:()=>{},warn:()=>{}},projectRoot:r});return s.error!==void 0?i?s:(t.debug?.(`skipped the default binding manifest: ${s.error}`),{}):{written:l}},Xr=async(e,r,n,t)=>{if(r.studioEnabled)try{return await(e.startStudio??Wr)({apiSpec:e.apiSpec,lazyFunctions:k(e.lazyFunctions),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}},Jr=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}.`},Yr=(e,r,n,t,o)=>{if(!(e.sidecar===void 0||!br(r)))try{V({apiSpec:r.apiSpec,lazyFunctions:k(r.lazyFunctions),lunoraDirectory:"lunora",projectRoot:n,target:o,...Y(n)})}catch(i){t.warn(`codegen (pre-sidecar) failed: ${i instanceof Error?i.message:String(i)} — the framework dev server will retry`)}},Kr=e=>e.flavor==="vite"?"starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)":`starting ${e.wrangler.tag} dev + studio`,Qr=(e,r)=>{e.ipv4LoopbackForced&&r.info("no IPv6 loopback (::1) on this host — binding the worker to 127.0.0.1 (--ip) so wrangler dev doesn't crash. Pin `dev.ip` in wrangler.jsonc to override.")},Zr=async(e,r,n,t)=>{if(e.celldSession!==!0||e.workerEnabled)return!0;try{for(const o of await(r.writeCelldProjection??ke)({projectRoot:n}))t.warn(o);return!0}catch(o){return t.error(`could not write the celld projection: ${o instanceof Error?o.message:String(o)}`),!1}},qr=async(e,r,n,t,o)=>{try{return e.celldSession===!0?await _r({logger:o,port:e.workerPort,projectRoot:t,start:r.startCelldSession}):n(e.wrangler,o)}catch(i){o.error(`could not start the worker: ${i instanceof Error?i.message:String(i)}`);return}},et=async(e,r,n)=>{const t=await(e.startHyperdrive??De)({onWarning:o=>{n.warn(o)},projectRoot:r});return t!==void 0&&t.bindings.length>0&&n.info(`Hyperdrive ${t.bindings.join(", ")}: embedded Postgres (PGlite), data in .wrangler/state/lunora-pglite. Set CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING> to use your own.`),t},rt=async e=>{const{logger:r}=e,n=ue(),t=e.cwd??process.cwd(),o=e.flavor??vr(t),i=er(t,e.target);if(i.target===void 0)throw new Error(i.error??"unknown deploy target");const{target:l}=i,s=mr(l,o,r);s==="wrangler"&&await Pr(t,r,void 0,l,void 0,n);const a=await wr({...e,flavor:s,target:l}),u={remoteCleanup:a.remote.cleanup,serviceConfigCleanup:a.serviceConfigCleanup};try{const b=Number(process.env[D]),w=X(t);if(w!==void 0&&w.pid!==process.pid&&w.pid!==b)return T(r,w),{code:0,plan:a};const c=zr(a,t);if(c!==void 0)return T(r,c),{code:0,plan:a};(a.flavor==="vite"||a.flavor==="framework-worker")&&(a.wrangler.env={...a.wrangler.env,[D]:String(process.pid)}),a.workerEnabled&&l==="cloudflare"&&(u.hyperdrive=await et(e,t,r)),await Vr(e,t,n),r.info(Kr(a)),hr(r,t),Qr(a,r),a.runsCodegenWatch&&(u.codegen=(e.startCodegen??Lr)({apiSpec:e.apiSpec,lazyFunctions:k(e.lazyFunctions),jsonLogs:e.jsonLogs,logger:r,projectRoot:t,target:l})),u.studio=await Xr(e,a,t,r);const d=Gr({cwd:t,destination:e.emitBindings,logger:r,plan:a});if(d.error!==void 0)return r.error(d.error),{code:C.USAGE,plan:a};await u.codegen?.ready;const f=u.studio?.url;a.frameworkHint!==void 0&&r.warn(a.frameworkHint);const g=()=>{a.flavor==="wrangler"&&(u.readyProbe=new AbortController,Hr({cwd:t,logger:r,origin:a.workerOrigin,probe:e.probeReady,signal:u.readyProbe.signal}))};if(!await Zr(a,e,t,r))return{code:C.FAILURE,plan:a};if(!a.workerEnabled)return g(),r.info(Jr(a)),u.tunnel=M({cwd:t,logger:r,plan:a,tunnel:e.tunnel}),{code:await(e.waitForInterrupt??yr)(r),plan:a};Yr(a,e,t,r,l);const y=e.startWorker??Sr,S=await qr(a,e,y,t,r);if(S===void 0)return{code:C.FAILURE,plan:a};const p=a.sidecar===void 0?void 0:y(a.sidecar,r);return g(),u.containerLogs=Br(a,t,r,f,d.written),u.tunnel=M({cwd:t,logger:r,plan:a,tunnel:e.tunnel}),Ur(r,t),{code:await pr(S,p,r),plan:a}}finally{u.readyProbe?.abort(),fe(t,process.pid),await Er(u)}},tt=e=>({codegen:e.codegen===!1?!1:void 0,studio:e.studio===!1?!1:void 0,worker:e.worker===!1?!1:void 0}),nt=e=>{if(e.local===!0&&e.remote===!0)return"`--local` and `--remote` are mutually exclusive — pass at most one.";const{entries:r,invalid:n}=Q(e.allowMail);if(n.length>0)return`\`--allow-mail\` takes email addresses or '*@domain' — not ${n.map(t=>`"${t}"`).join(", ")}.`;if(r.length>0&&e.tunnel!==!0)return"`--allow-mail` only applies to a tunnel — add `--tunnel`."},ot=e=>e.tunnel===!0?{allowMail:Q(e.allowMail).entries}:void 0,Rt=qe(async({argument:e,cwd:r,logger:n,options:t})=>{const o=t.json===!0,i=ur({cwd:r,json:o,lines:t.lines,logger:n,subcommand:e[0]});if(i!==void 0)return i;const l=process.env[G]==="1",s=l?void 0:de(),a=o||s!==void 0;a&&rr(),s!==void 0&&t.background!==!0&&n.info(`AI agent detected (${s.name} via ${s.variable}) — starting the dev server in background mode with JSON logs. Set LUNORA_AGENT_MODE=0 to opt out.`);const u=nt(t);if(u!==void 0)return n.error(`dev: ${u}`),{code:C.USAGE};const b=ot(t),w=t.local===!0?!1:Re({configPreference:le(r),envValue:process.env.LUNORA_REMOTE,flag:t.remote});if(!l&&(t.background===!0||s!==void 0)){const c=X(r);return c!==void 0?(T(n,c),{code:0}):(b?.allowMail.length===0&&fr(n),gr({cwd:r,jsonLogs:a,logger:n,options:t,remote:w}))}return rt({apiSpec:$e(t.apiSpec),cwd:r,emitBindings:t.emitBindings,inspectorPort:t.inspectorPort,lazyFunctions:k(t.lazyFunctions),jsonLogs:a,local:t.local,logger:n,port:t.port,remote:w,target:t.target,tunnel:b,workerPort:t.workerPort,...tt(t)})});export{nt as devFlagConflict,Rt as execute,tt as negatableDevFlags,rt as runDevCommand};
5
+ ${Ar(n)}`,...o,...i].join("\0")},Fr=(e,r)=>{let n,t;return{drop:()=>{n=void 0},get:()=>{const o=Or(e,r);return n===void 0||o!==t?(t=o,n=te(r)):ne(n,r),n}}},Ir=(e,r,n,t)=>{const{apiSpec:o,lazyFunctions:i,logger:l,projectRoot:s,target:a}=e;try{const u=V({apiSpec:o,lazyFunctions:k(i),lunoraDirectory:r,project:t.get(),projectRoot:s,target:a,...Y(s)});l.success(`codegen: wrote ${r}/_generated (${n})`),Ze(u.platformDiagnostics,l)}catch(u){return t.drop(),l.error(Qe(u,n)),!1}return!0},Lr=e=>{const r=e.lunoraDirectory??"lunora",n=P(e.projectRoot,r),t=e.debounceMs??Rr;let o=!1,i=!1,l=!1,s,a=0,u=Promise.resolve();const b=Fr(e.projectRoot,n);let w;const c=new Promise(v=>{w=v}),d=(v,m)=>{o||(s={codegen:m||s?.codegen===!0,reason:v},!i&&(i=!0,u=(async()=>{try{for(;s!==void 0;){const h=s;if(s=void 0,!(h.codegen&&!Ir(e,r,h.reason,b))){l=!0;try{(await se({cwd:e.projectRoot,logger:e.logger,spawner:e.spawner,stdoutToStderr:e.jsonLogs})).ran&&(a=Date.now())}finally{l=!1}}}}catch(h){e.logger.error(`codegen watch: ${h instanceof Error?h.message:String(h)}`)}finally{i=!1,w()}})()))};d("startup",!0);const f=v=>(e.logger.warn(`codegen watch unavailable (${v}) — schema edits will NOT auto-regenerate. Run \`lunora codegen\` manually after each edit.`),{close:()=>(o=!0,s=void 0,u),ready:c,watchAvailable:!1});let g,y;if(!Oe(n))return f(`no such directory: ${n}`);const S=new Set;let p=!1;const _=(v,m)=>{if(l||Date.now()-a<Tr){g&&(clearTimeout(g),g=void 0);let h=!1;for(const E of[...S,v])E!==void 0&&O(E)&&(h=!0);S.clear(),p=!1,h&&d(`change: ${m}`,!1);return}v===void 0?p=!0:S.add(v),g&&clearTimeout(g),g=setTimeout(()=>{const h=[...S].map(ee=>O(ee)),E=p||h.includes(!1);S.clear(),p=!1,d(`change: ${m}`,E)},t)},D=[];try{y=A(n,{recursive:!0},(v,m)=>{if(typeof m!="string"){_(void 0,"?");return}m.split(W).includes("_generated")||m.endsWith(Le)||_(P(n,m),m)});for(const v of re(e.projectRoot).services)if(v.rpcEntrypoint!==void 0){const m=A(K(v.wranglerPath),{recursive:!0},(h,E)=>{typeof E=="string"&&Dr.test(E)&&!E.split(W).includes("node_modules")&&_(void 0,E)});m.on("error",h=>{e.logger.warn(`codegen watch: stopped watching service "${v.name}" (${h.message})`),m.close()}),D.push(m)}}catch(v){y?.close();for(const m of D)m.close();return f(v instanceof Error?v.message:String(v))}const q=y;return{close:()=>{o=!0,s=void 0,g&&clearTimeout(g),q.close();for(const v of D)v.close();return u},ready:c,watchAvailable:!0}},x="/_lunora",jr=3e4,U=(e,r,n,t=jr)=>{const o=nr({headers:{...e.headers,host:n.host},hostname:n.hostname,method:e.method,path:e.url,port:n.port},i=>{o.setTimeout(0),r.writeHead(i.statusCode??502,i.headers),ar(i,r,()=>{})});o.setTimeout(t,()=>{o.destroy(new Error(`no response within ${String(t)}ms`))}),o.on("error",i=>{if(r.headersSent){r.destroy();return}r.statusCode=i.message.startsWith("no response within")?504:502,r.end(`lunora dev: worker unreachable (${i.message})`)}),r.on("close",()=>{r.writableFinished||o.destroy()}),e.pipe(o)},Nr=5e3,Mr=(e,r,n,t)=>{const o=or({host:t.hostname,port:Number(t.port)},()=>{o.setTimeout(0);const i=[`${e.method??"GET"} ${e.url??"/"} HTTP/1.1`];for(const[l,s]of Object.entries(e.headers))if(l!=="host")for(const a of Array.isArray(s)?s:[s])a!==void 0&&i.push(`${l}: ${a}`);i.push(`host: ${t.host}`,"",""),o.write(i.join(`\r
6
+ `)),n.length>0&&o.write(n),o.pipe(r),r.pipe(o)});o.setTimeout(Nr,()=>o.destroy()),o.on("error",()=>r.destroy()),r.on("error",()=>o.destroy())},Wr=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=F(e.logger,import.meta.url),i=I(import.meta.url);const l=je(e.cwd,()=>Buffer.from(Ne({adminToken:n?Me(e.cwd):void 0,basePath:"/",dataEditable:n,rulesInstalled:B(e.cwd).installed,runAsIdentity:n,schemaEditable:n,scriptSrc:"/studio.js",styleHref:"/styles.css"}))),s=(c,d,f,g,y)=>{Ke(c,d,f,g,y,i)},a=(c,d,f)=>{c.statusCode=d,c.setHeader("Content-Type","text/plain"),c.end(f)},u=(c,d,f,g)=>{if(n){Ve(c,d,f,e.cwd,{apiSpec:e.apiSpec,lazyFunctions:k(e.lazyFunctions)});return}a(d,403,g)},b=(c,d,f)=>{const g=c==="/styles.css",y=Be(c);if(!g&&!y)return!1;const S=I(import.meta.url);if(S!==i&&(o=F(e.logger,import.meta.url),i=S),o===void 0)return a(f,501,"Lunora studio assets not found — install and build @lunora/studio."),!0;if(g)return s(d,f,o.styles,"text/css; charset=utf-8","styles.css"),!0;const p=c.slice(c.lastIndexOf("/")+1),_=ze(p,import.meta.url);return _===void 0?(a(f,404,"Not found"),!0):(s(d,f,_,Ge(p),p),!0)};l();const w=tr((c,d)=>{const f=L(c.url??"/");if(n){const y=j(c,e.logger);if(y!==void 0){a(d,403,y);return}}if(f.startsWith(x)){if(!n){a(d,403,"Lunora studio: the worker admin proxy is only available on a loopback bind.");return}U(c,d,t);return}if(f===We){u(c,d,Xe,"Lunora schema editing is only available on loopback hosts in dev.");return}if(f===He){u(c,d,Je,"Lunora policy scaffolding is only available on loopback hosts in dev.");return}if(f===xe){u(c,d,Ye,"Lunora data seeding is only available on loopback hosts in dev.");return}if(b(f,c,d))return;const g=c.headers["sec-fetch-mode"];if(n&&typeof g=="string"&&g!=="navigate"){U(c,d,t);return}Ue(d,l())});return w.on("upgrade",(c,d,f)=>{if(!n||j(c,e.logger)!==void 0){d.destroy();return}L(c.url??"").startsWith(x)?Mr(c,d,f,t):d.destroy()}),await new Promise((c,d)=>{w.once("error",f=>{f.code==="EADDRINUSE"?d(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})):d(f)}),w.listen(e.port,r,()=>{c()})}),{close:()=>new Promise(c=>{w.close(()=>{c()})}),url:`http://${r==="0.0.0.0"||r==="::"?"localhost":r}:${String(e.port)}`}},Hr=async e=>{const{cwd:r,logger:n,origin:t,signal:o}=e,i=e.probe??dr,l=cr(e.readyTimeoutMs),s=Date.now()+l,a=()=>o.aborted;for(;!a()&&Date.now()<s;){let u=!1;try{u=await i(t,o)}catch{}if(a()||Date.now()>=s)break;if(u)return z(r,{readyAt:new Date().toISOString()},{expectedPid:process.pid}),ce(r)?.readyAt!==void 0;try{await sr(lr,void 0,{signal:o})}catch{}}return a()||n.warn(`server did not answer on ${t} within ${String(Math.round(l/1e3))}s — .lunora/dev.json will have no readyAt, so anything polling for readiness will keep waiting. The server itself is unaffected; raise LUNORA_DEV_READY_TIMEOUT_MS if it is just slow to boot.`),!1},xr=(e,r,n,t)=>{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/"),t!==void 0&&e.info(` ➜ Supervisor: ${t} (needs) · ${J} (status)`),r.remote.enabled&&(r.remote.bindings.length>0?e.info(` ➜ Remote: ${r.remote.bindings.join(", ")} → deployed worker`):r.remote.reason!==void 0&&e.warn(` ➜ Remote: requested but inactive (${r.remote.reason}) — running fully local`));for(const o of r.remote.withheld)e.warn(` ➜ ${Ce(o)}`);e.info("")},Ur=(e,r)=>{B(r).installed||!Pe()||(e.info(` ⓘ ${Re}`),e.info(""))},Vr=async(e,r,n)=>{let t;try{t=me(await we({cache:n,projectRoot:r,schemaDir:he(r)}))}catch{}if(t!==void 0)try{const i=(e.ensureExample??ye)(r,t);if(i.length>0){const l=t.map(s=>s.includes(":")?`${s.replace(":"," (")})`:s);e.logger.info(`Updated .dev.vars.example with secrets for: ${l.join(", ")} (${i.join(", ")})`)}}catch{}(await(e.ensureEnv??Se)({confirm:ir(),cwd:r,info:i=>{e.logger.info(i)}})).status==="declined"&&!pe()&&e.logger.info(`hint: ${$} was not scaffolded (non-interactive run). Copy ${Ee} → ${$} and fill in secrets, or run \`lunora dev\` in an interactive terminal to scaffold automatically.`);try{(e.fillSecrets??be)({cwd:r,secretSources:t,info:i=>{e.logger.info(i)}})}catch{}},Br=(e,r,n,t,o)=>{if(e.flavor!=="wrangler")return;t!==void 0&&z(r,{studioUrl:t});let i;try{i=kr(r,n)}catch{}return xr(n,e,t,o),i},zr=(e,r)=>{const n=Number(process.env[T]),t=ge(r,{background:process.env[G]==="1",logFile:process.env[ve],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},Gr=e=>{const{cwd:r,destination:n,logger:t,plan:o}=e,i=n!==void 0,l=n??_e,s=Ae({destination:l,dev:{...o.flavor==="wrangler"?{origin:o.workerOrigin}:{},statusFile:J},logger:i?t:{...t,success:()=>{},warn:()=>{}},projectRoot:r});return s.error!==void 0?i?s:(t.debug?.(`skipped the default binding manifest: ${s.error}`),{}):{written:l}},Xr=async(e,r,n,t)=>{if(r.studioEnabled)try{return await(e.startStudio??Wr)({apiSpec:e.apiSpec,lazyFunctions:k(e.lazyFunctions),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}},Jr=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}.`},Yr=(e,r,n,t,o)=>{if(!(e.sidecar===void 0||!br(r)))try{V({apiSpec:r.apiSpec,lazyFunctions:k(r.lazyFunctions),lunoraDirectory:"lunora",projectRoot:n,target:o,...Y(n)})}catch(i){t.warn(`codegen (pre-sidecar) failed: ${i instanceof Error?i.message:String(i)} — the framework dev server will retry`)}},Kr=e=>e.flavor==="vite"?"starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)":`starting ${e.wrangler.tag} dev + studio`,Qr=(e,r)=>{e.ipv4LoopbackForced&&r.info("no IPv6 loopback (::1) on this host — binding the worker to 127.0.0.1 (--ip) so wrangler dev doesn't crash. Pin `dev.ip` in wrangler.jsonc to override.")},Zr=async(e,r,n,t)=>{if(e.celldSession!==!0||e.workerEnabled)return!0;try{for(const o of await(r.writeCelldProjection??ke)({projectRoot:n}))t.warn(o);return!0}catch(o){return t.error(`could not write the celld projection: ${o instanceof Error?o.message:String(o)}`),!1}},qr=async(e,r,n,t,o)=>{try{return e.celldSession===!0?await _r({logger:o,port:e.workerPort,projectRoot:t,start:r.startCelldSession}):n(e.wrangler,o)}catch(i){o.error(`could not start the worker: ${i instanceof Error?i.message:String(i)}`);return}},et=async(e,r,n)=>{const t=await(e.startHyperdrive??Te)({onWarning:o=>{n.warn(o)},projectRoot:r});return t!==void 0&&t.bindings.length>0&&n.info(`Hyperdrive ${t.bindings.join(", ")}: embedded Postgres (PGlite), data in .wrangler/state/lunora-pglite. Set CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING> to use your own.`),t},rt=async e=>{const{logger:r}=e,n=ue(),t=e.cwd??process.cwd(),o=e.flavor??vr(t),i=er(t,e.target);if(i.target===void 0)throw new Error(i.error??"unknown deploy target");const{target:l}=i,s=mr(l,o,r);s==="wrangler"&&await Pr(t,r,void 0,l,void 0,n);const a=await wr({...e,flavor:s,target:l}),u={remoteCleanup:a.remote.cleanup,serviceConfigCleanup:a.serviceConfigCleanup};try{const b=Number(process.env[T]),w=X(t);if(w!==void 0&&w.pid!==process.pid&&w.pid!==b)return C(r,w),{code:0,plan:a};const c=zr(a,t);if(c!==void 0)return C(r,c),{code:0,plan:a};(a.flavor==="vite"||a.flavor==="framework-worker")&&(a.wrangler.env={...a.wrangler.env,[T]:String(process.pid)}),a.workerEnabled&&l==="cloudflare"&&(u.hyperdrive=await et(e,t,r)),await Vr(e,t,n),r.info(Kr(a)),hr(r,t),Qr(a,r),a.runsCodegenWatch&&(u.codegen=(e.startCodegen??Lr)({apiSpec:e.apiSpec,lazyFunctions:k(e.lazyFunctions),jsonLogs:e.jsonLogs,logger:r,projectRoot:t,target:l})),u.studio=await Xr(e,a,t,r);const d=Gr({cwd:t,destination:e.emitBindings,logger:r,plan:a});if(d.error!==void 0)return r.error(d.error),{code:R.USAGE,plan:a};await u.codegen?.ready;const f=u.studio?.url;a.frameworkHint!==void 0&&r.warn(a.frameworkHint);const g=()=>{a.flavor==="wrangler"&&(u.readyProbe=new AbortController,Hr({cwd:t,logger:r,origin:a.workerOrigin,probe:e.probeReady,signal:u.readyProbe.signal}))};if(!await Zr(a,e,t,r))return{code:R.FAILURE,plan:a};if(!a.workerEnabled)return g(),r.info(Jr(a)),u.tunnel=M({cwd:t,logger:r,plan:a,tunnel:e.tunnel}),{code:await(e.waitForInterrupt??yr)(r),plan:a};Yr(a,e,t,r,l);const y=e.startWorker??Sr,S=await qr(a,e,y,t,r);if(S===void 0)return{code:R.FAILURE,plan:a};const p=a.sidecar===void 0?void 0:y(a.sidecar,r);return g(),u.containerLogs=Br(a,t,r,f,d.written),u.tunnel=M({cwd:t,logger:r,plan:a,tunnel:e.tunnel}),Ur(r,t),{code:await pr(S,p,r),plan:a}}finally{u.readyProbe?.abort(),fe(t,process.pid),await Er(u)}},tt=e=>({codegen:e.codegen===!1?!1:void 0,studio:e.studio===!1?!1:void 0,worker:e.worker===!1?!1:void 0}),nt=e=>{if(e.local===!0&&e.remote===!0)return"`--local` and `--remote` are mutually exclusive — pass at most one.";const{entries:r,invalid:n}=Q(e.allowMail);if(n.length>0)return`\`--allow-mail\` takes email addresses or '*@domain' — not ${n.map(t=>`"${t}"`).join(", ")}.`;if(r.length>0&&e.tunnel!==!0)return"`--allow-mail` only applies to a tunnel — add `--tunnel`."},ot=e=>e.tunnel===!0?{allowMail:Q(e.allowMail).entries}:void 0,Dt=qe(async({argument:e,cwd:r,logger:n,options:t})=>{const o=t.json===!0,i=ur({cwd:r,json:o,lines:t.lines,logger:n,subcommand:e[0]});if(i!==void 0)return i;const l=process.env[G]==="1",s=l?void 0:de(),a=o||s!==void 0;a&&rr(),s!==void 0&&t.background!==!0&&n.info(`AI agent detected (${s.name} via ${s.variable}) — starting the dev server in background mode with JSON logs. Set LUNORA_AGENT_MODE=0 to opt out.`);const u=nt(t);if(u!==void 0)return n.error(`dev: ${u}`),{code:R.USAGE};const b=ot(t),w=t.local===!0?!1:De({configPreference:le(r),envValue:process.env.LUNORA_REMOTE,flag:t.remote});if(!l&&(t.background===!0||s!==void 0)){const c=X(r);return c!==void 0?(C(n,c),{code:0}):(b?.allowMail.length===0&&fr(n),gr({cwd:r,jsonLogs:a,logger:n,options:t,remote:w}))}return rt({apiSpec:$e(t.apiSpec),cwd:r,emitBindings:t.emitBindings,inspectorPort:t.inspectorPort,lazyFunctions:k(t.lazyFunctions),jsonLogs:a,local:t.local,logger:n,port:t.port,remote:w,target:t.target,tunnel:b,workerPort:t.workerPort,...tt(t)})});export{nt as devFlagConflict,Dt as execute,tt as negatableDevFlags,rt as runDevCommand};