@lunora/cli 1.0.0-alpha.406 → 1.0.0-alpha.408
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/packem_chunks/dispatch.mjs +1 -1
- package/dist/packem_chunks/handler.mjs +1 -1
- package/dist/packem_chunks/handler10.mjs +1 -1
- package/dist/packem_chunks/handler11.mjs +1 -1
- package/dist/packem_chunks/handler12.mjs +1 -1
- package/dist/packem_chunks/handler13.mjs +1 -1
- package/dist/packem_chunks/handler14.mjs +1 -1
- package/dist/packem_chunks/handler15.mjs +1 -1
- package/dist/packem_chunks/handler16.mjs +1 -1
- package/dist/packem_chunks/handler17.mjs +1 -1
- package/dist/packem_chunks/handler18.mjs +1 -1
- package/dist/packem_chunks/handler19.mjs +1 -1
- package/dist/packem_chunks/handler2.mjs +1 -1
- package/dist/packem_chunks/handler20.mjs +1 -1
- package/dist/packem_chunks/handler21.mjs +1 -1
- package/dist/packem_chunks/handler22.mjs +1 -1
- package/dist/packem_chunks/handler23.mjs +1 -1
- package/dist/packem_chunks/handler24.mjs +1 -1
- package/dist/packem_chunks/handler25.mjs +1 -1
- package/dist/packem_chunks/handler26.mjs +1 -1
- package/dist/packem_chunks/handler27.mjs +1 -1
- package/dist/packem_chunks/handler3.mjs +1 -1
- package/dist/packem_chunks/handler4.mjs +1 -1
- package/dist/packem_chunks/handler5.mjs +1 -1
- package/dist/packem_chunks/handler6.mjs +1 -1
- package/dist/packem_chunks/handler7.mjs +1 -1
- package/dist/packem_chunks/handler8.mjs +1 -1
- package/dist/packem_chunks/handler9.mjs +1 -1
- package/dist/packem_chunks/runCodegenCommand.mjs +1 -1
- package/dist/packem_chunks/runDeployCommand.mjs +1 -1
- package/dist/packem_chunks/runDevCommand.mjs +1 -1
- package/dist/packem_chunks/runInitCommand.mjs +1 -1
- package/dist/packem_chunks/runResetCommand.mjs +1 -1
- package/dist/packem_chunks/runRpcCommand.mjs +1 -1
- package/dist/packem_shared/{COMMANDS-Dsd6ZAe1.mjs → COMMANDS-BpfWKiiZ.mjs} +1 -1
- package/dist/packem_shared/{cli-B9Fs3QXL.mjs → cli-CPcfZdt0.mjs} +1 -1
- package/dist/packem_shared/{command-BgjsvUFg.mjs → command-CB4JM-Jl.mjs} +1 -1
- package/dist/packem_shared/render-lunora-error-B6893vbU.mjs +2 -0
- package/package.json +10 -10
- package/dist/packem_shared/render-lunora-error-C3OJY5wk.mjs +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d as Ue}from"../packem_shared/command-BgjsvUFg.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{a as qe}from"../packem_shared/docker-AqLAkOyY.mjs";import{defaultSpawner as be}from"../packem_shared/createRecordingSpawner-Ch7zuQQE.mjs";import{execArgsFor as $e,detectPackageManager as Ae}from"@lunora/config/cli";import{a as O}from"../packem_shared/cli-B9Fs3QXL.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-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{a as qe}from"../packem_shared/docker-AqLAkOyY.mjs";import{defaultSpawner as be}from"../packem_shared/createRecordingSpawner-Ch7zuQQE.mjs";import{execArgsFor as $e,detectPackageManager as Ae}from"@lunora/config/cli";import{a as O}from"../packem_shared/cli-CPcfZdt0.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{d as s}from"../packem_shared/command-
|
|
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,3 +1,3 @@
|
|
|
1
|
-
import{existsSync as E,readFileSync as P}from"node:fs";import{join as D}from"node:path";import{DEV_VARS_FILE as m,DEV_VARS_KEY_PATTERN as b,writeDevVariablesFileAtomically as w,removeDevVariableLine as K,upsertDevVariableLine as _,isPlaceholderValue as S,resolveDeployDriver as j,resolveProjectTarget as z,generateSecretValue as M,DEV_VARS_EXAMPLE_FILE as g,parseDevVariableEntries as G,secretSourcesFromBindings as T,inferLunoraBindings as C,resolveSchemaDirectory as O,requiredSecrets as Y,isMintableSecretKey as U}from"@lunora/config";import{d as F}from"../packem_shared/command-
|
|
1
|
+
import{existsSync as E,readFileSync as P}from"node:fs";import{join as D}from"node:path";import{DEV_VARS_FILE as m,DEV_VARS_KEY_PATTERN as b,writeDevVariablesFileAtomically as w,removeDevVariableLine as K,upsertDevVariableLine as _,isPlaceholderValue as S,resolveDeployDriver as j,resolveProjectTarget as z,generateSecretValue as M,DEV_VARS_EXAMPLE_FILE as g,parseDevVariableEntries as G,secretSourcesFromBindings as T,inferLunoraBindings as C,resolveSchemaDirectory as O,requiredSecrets as Y,isMintableSecretKey as U}from"@lunora/config";import{d as F}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-Ch7zuQQE.mjs";import{l as Z}from"../packem_shared/wrangler-secrets-CTQPpalP.mjs";import{detectPackageManager as B,toolchainExecArgs as I}from"@lunora/config/cli";const X=/[\r\n]/u,H=/["\\]/u,L=e=>new Map(G(e).map(o=>[o.key,o])),h=e=>E(e)?P(e,"utf8"):"",R=e=>e.length<=4?"****":`${e.slice(0,4)}${"*".repeat(Math.min(8,e.length-4))}`,p=e=>L(h(e)),$=e=>e.env??(e.prod===!0?"production":void 0),W=e=>{const o=p(e.devVariablesPath),t={file:m,keys:[...o.keys()],subcommand:"list"};if(o.size===0)return e.logger.info(`${m}: (empty)`),{code:0,data:t,descriptors:[]};for(const r of o.values())e.logger.info(`${r.key}=${R(r.value)}`);return{code:0,data:t,descriptors:[]}},J=e=>{const{devVariablesPath:o,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 s=p(o).get(r.key);return s?(r.format!=="json"&&process.stdout.write(`${s.value}
|
|
2
2
|
`),{code:0,data:{key:r.key,subcommand:"get",value:s.value},descriptors:[]}):(t.error(`env: ${r.key} is not set in ${m}`),{code:d.NOT_FOUND,descriptors:[]})},Q=e=>{const{devVariablesPath:o,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 s=h(o);return w(o,_(s,r.key,r.value)),t.success(`env: set ${r.key} (${R(r.value)}) in ${m}`),{code:0,data:{key:r.key,subcommand:"set"},descriptors:[]}},x=e=>{const{devVariablesPath:o,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 s=h(o);return L(s).has(r.key)?(w(o,K(s,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:o,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 s=p(o);if(s.size===0)return t.warn(`${m}: nothing to push (empty)`),{code:0,data:{pushed:[],subcommand:"push",target:$(r)},descriptors:[]};const n=[...s.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=j(z(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 s.values()){const N=y({environment:v,key:a.key,temporary:r.temporary}),A=I(l,N),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(s.size)} secret(s)`),{code:0,data:{pushed:[...s.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:o,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 s=new Set(p(o).keys()),n=new Set(r.names),c=[...s].filter(i=>!n.has(i)).toSorted((i,v)=>i.localeCompare(v)),u=r.names.filter(i=>!s.has(i)),l=[...s].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:o,devVariablesPath:t,logger:r}=e,s=D(o,g);if(!E(s))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=G(P(s,"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 o=[];try{o=T(await C({projectRoot:e.cwd,schemaDir:O(e.cwd)}))}catch{}const t=Y(o).map(s=>s.key).filter(s=>U(s)),r=[...p(e.devVariablesPath).keys()].filter(s=>U(s));return[...new Set([...t,...r])]},ne=(e,o,t)=>{const r=p(e),s=n=>{const c=r.get(n);return c!==void 0&&c.value!==""&&!S(c.value)};return{live:o.filter(n=>s(n.key)).map(n=>n.key),writable:t?o:o.filter(n=>!s(n.key))}},ae=(e,o)=>{const{devVariablesPath:t,logger:r,options:s}=e,{live:n,writable:c}=ne(t,o,s.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=_(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:o,options:t}=e;let r;if(t.key===void 0){if(r=await oe(e),r.length===0)return o.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 o.error(`env: invalid key "${t.key}" — must match [A-Za-z_][A-Za-z0-9_]*`),{code:d.USAGE,descriptors:[]};r=[t.key]}const s=r.map(n=>({key:n,value:M()}));if(t.set===!0)return ae(e,s);if(t.format!=="json")for(const n of s)process.stdout.write(`${n.key}=${n.value}
|
|
3
3
|
`);return{code:0,data:{secrets:s,subcommand:"generate"},descriptors:[]}},ie=async e=>{const{options:o}=e;switch(o.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 "${o.subcommand}"`),{code:d.USAGE,descriptors:[]}}},de=async e=>{const o=e.cwd??process.cwd(),t={cwd:o,devVariablesPath:D(o,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=F(({argument:e,cwd:o,format:t,logger:r,options:s})=>{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:o,env:s.env,format:t,key:e[1],logger:r,prod:s.prod===!0,set:s.set===!0,subcommand:n,temporary:s.temporary===!0,value:e[2],yes:s.yes===!0})});export{Ee as execute,de as runEnvCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d}from"../packem_shared/command-
|
|
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 +1 @@
|
|
|
1
|
-
import{d}from"../packem_shared/command-
|
|
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-BRM1hs7j.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-
|
|
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,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-
|
|
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(`
|
|
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 +1 @@
|
|
|
1
|
-
import{existsSync as u,rmSync as a}from"node:fs";import{join as i}from"node:path";import{writeLinkedProject as d,LINKED_PROJECT_FILE as o}from"@lunora/config";import{d as k}from"../packem_shared/command-
|
|
1
|
+
import{existsSync as u,rmSync as a}from"node:fs";import{join as i}from"node:path";import{writeLinkedProject as d,LINKED_PROJECT_FILE as o}from"@lunora/config";import{d as k}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as c}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{r as s}from"../packem_shared/wrangler-name-Dsk5K1f-.mjs";const v=r=>{try{const{protocol:n}=new URL(r);return n==="http:"||n==="https:"}catch{return!1}},f=(r,n)=>{const e=i(r,o);return u(e)?(a(e),n.success(`link: removed ${o}`),{code:0}):(n.warn(`link: no ${o} to remove`),{code:0})},w=r=>{const n=r.cwd??process.cwd(),{logger:e}=r;if(r.remove)return f(n,e);if(r.url===void 0||r.url==="")return e.error("link requires a deployed Worker URL. Usage: lunora link --url <https://your-worker>"),{code:c.USAGE};if(!v(r.url))return e.error(`link: invalid --url "${r.url}" — expected an http(s) URL`),{code:c.USAGE};const l=r.now??(()=>new Date().toISOString()),t={env:r.env,linkedAt:l(),workerName:r.name??s(n),workerUrl:r.url},m=d(n,t);return e.success(`link: ${t.workerName??"(unnamed worker)"} -> ${r.url}`),e.info(`link: wrote ${m}`),t.env!==void 0&&e.info(`link: environment "${t.env}"`),{code:0,link:t}},y=k(({cwd:r,logger:n,options:e})=>w({cwd:r,env:e.env,logger:n,name:e.name,remove:e.remove===!0,url:e.url}));export{y as execute,w as runLinkCommand};
|
|
@@ -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-
|
|
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}
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import{dirname as L,join as w,resolve as q,relative as F,extname as Ne,sep as re}from"node:path";import{discoverMigrations as xe,runCodegen as Le,readServiceBindings as Oe}from"@lunora/codegen";import{readLinkedProject as he,resolveSchemaDirectory as P,startDevProcess as Me,acceptsConnection as Ue,COMPOSED_WORKER_ENTRY as te,discoverContainerInfo as pe,writeLinkedProject as Fe,discoverSchemaInfo as ve,DEV_VARS_FILE as S,parseDevVariableEntries as ye,isMintableSecretKey as ne,secretSourcesFromBindings as He,inferLunoraBindings as we,requiredSecrets as We,generateSecretValue as Be,upsertDevVariableLine as Ge,writeDevVariablesFileAtomically as Ve,resolveLazyFunctions as ke,createDiscoveryCache as ze,resolveDeployDriver as K,planToolchainInvocation as be,runPostCodegenHook as qe}from"@lunora/config";import{bindingsNeedingDatabase as Ke,findWranglerFile as O,readWranglerJsonc as A,mergeWranglerEnvironment as Y,applyModify as $e,HYPERDRIVE_PLACEHOLDER_ID as Ye,warnCloudflareCliConfigOnce as Je,validateWranglerProject as Xe,wranglerCodegenInputs as Qe,reconcileWranglerBindings as Ze,reconcileWranglerCompatibilityDate as er,reconcileWranglerCrons as rr,describePreservedCrons as tr}from"@lunora/config/cloudflare";import{Spinner as nr}from"@visulima/spinner";import{r as or,a as ar,e as ir}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{p as sr}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{mkdirSync as Se,writeFileSync as D,existsSync as v,readFileSync as k,rmSync as cr,mkdtempSync as dr,readdirSync as lr}from"node:fs";import{m as ur,e as gr,i as fr,b as mr,a as hr}from"../packem_shared/secret-key-8DPOH98z.mjs";import{w as pr}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{d as vr}from"../packem_shared/command-
|
|
1
|
+
import{dirname as L,join as w,resolve as q,relative as F,extname as Ne,sep as re}from"node:path";import{discoverMigrations as xe,runCodegen as Le,readServiceBindings as Oe}from"@lunora/codegen";import{readLinkedProject as he,resolveSchemaDirectory as P,startDevProcess as Me,acceptsConnection as Ue,COMPOSED_WORKER_ENTRY as te,discoverContainerInfo as pe,writeLinkedProject as Fe,discoverSchemaInfo as ve,DEV_VARS_FILE as S,parseDevVariableEntries as ye,isMintableSecretKey as ne,secretSourcesFromBindings as He,inferLunoraBindings as we,requiredSecrets as We,generateSecretValue as Be,upsertDevVariableLine as Ge,writeDevVariablesFileAtomically as Ve,resolveLazyFunctions as ke,createDiscoveryCache as ze,resolveDeployDriver as K,planToolchainInvocation as be,runPostCodegenHook as qe}from"@lunora/config";import{bindingsNeedingDatabase as Ke,findWranglerFile as O,readWranglerJsonc as A,mergeWranglerEnvironment as Y,applyModify as $e,HYPERDRIVE_PLACEHOLDER_ID as Ye,warnCloudflareCliConfigOnce as Je,validateWranglerProject as Xe,wranglerCodegenInputs as Qe,reconcileWranglerBindings as Ze,reconcileWranglerCompatibilityDate as er,reconcileWranglerCrons as rr,describePreservedCrons as tr}from"@lunora/config/cloudflare";import{Spinner as nr}from"@visulima/spinner";import{r as or,a as ar,e as ir}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{p as sr}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{mkdirSync as Se,writeFileSync as D,existsSync as v,readFileSync as k,rmSync as cr,mkdtempSync as dr,readdirSync as lr}from"node:fs";import{m as ur,e as gr,i as fr,b as mr,a as hr}from"../packem_shared/secret-key-8DPOH98z.mjs";import{w as pr}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{d as vr}from"../packem_shared/command-CB4JM-Jl.mjs";import{r as J}from"../packem_shared/wrangler-name-Dsk5K1f-.mjs";import{a as yr}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as y}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{spawn as wr}from"node:child_process";import{randomUUID as kr,createHash as br}from"node:crypto";import{setTimeout as $r}from"node:timers/promises";import{toolchainExecArgs as C,detectPackageManager as R,execArgsFor as Ee}from"@lunora/config/cli";import{f as Sr}from"../packem_shared/free-port-DjbFhaaT.mjs";import{containerBuildTag as Er}from"@lunora/container";import{i as Ar,a as Dr}from"../packem_shared/docker-AqLAkOyY.mjs";import{defaultSpawner as M}from"../packem_shared/createRecordingSpawner-Ch7zuQQE.mjs";import{r as Ae}from"../packem_shared/resolve-target-C_ZloTvd.mjs";import{p as Rr,H as Cr,a as _r,r as Pr}from"../packem_shared/health-probe-Bw9JFfIy.mjs";import{join as $}from"@visulima/path";import{r as Ir,c as Tr}from"../packem_shared/cloudflare-credentials-BVmZnDZk.mjs";import{Project as jr}from"ts-morph";import{runMigrateDataCommand as Nr}from"../packem_shared/runMigrateGenerateCommand-BwemPIOz.mjs";import{c as xr}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{l as Lr}from"../packem_shared/wrangler-secrets-CTQPpalP.mjs";const Or=Object.freeze({edges:[],nodes:[],modules:[],unresolved:[],version:1}),Mr=20,oe=e=>`${e.from} -${e.kind}-> ${e.to}`,N=(e,r)=>{const n=new Set(e);return r.filter(t=>!n.has(t)).toSorted((t,o)=>t.localeCompare(o))},Ur=(e,r)=>{const n=e.modules.map(i=>i.name),t=r.modules.map(i=>i.name),o=e.edges.map(i=>oe(i)),a=r.edges.map(i=>oe(i));return{addedEdges:N(o,a),addedModules:N(n,t),removedEdges:N(a,o),removedModules:N(t,n)}},x=(e,r,n)=>{if(n.length===0)return[];const t=n.slice(0,Mr).map(a=>` ${e} ${a}`),o=n.length-t.length;return[`${r}:`,...t,...o>0?[` … and ${o.toString()} more`]:[]]},Fr=e=>{const r=[...x("+","modules added",e.addedModules),...x("-","modules removed",e.removedModules),...x("+","edges added",e.addedEdges),...x("-","edges removed",e.removedEdges)];return r.length===0?void 0:`architecture changes since the last deploy:
|
|
2
2
|
${r.join(`
|
|
3
3
|
`)}`},Hr=(e,r)=>w(e,".lunora",r===void 0?"architecture.json":`architecture.${r}.json`),Wr=e=>{if(v(e))try{const r=JSON.parse(k(e,"utf8"));return r.version===1&&Array.isArray(r.modules)&&Array.isArray(r.edges)?r:void 0}catch{return}},Br=e=>{const{cwd:r,environment:n,logger:t}=e,o=Hr(r,n),a=Wr(o),i=e.current??(a===void 0?void 0:Or);if(i===void 0)return;const s=a===void 0?void 0:Fr(Ur(a,i));return s!==void 0&&t.info(s),()=>{try{Se(L(o),{recursive:!0}),D(o,`${JSON.stringify(i,void 0,2)}
|
|
4
4
|
`,"utf8")}catch(c){t.warn(`could not record the deployed architecture at ${o}: ${c instanceof Error?c.message:String(c)}`)}}},Gr=e=>{const{cwd:r,env:n,logger:t,mintedSecretsFile:o}=e;try{const a=he(r),i=a?.workerName??J(r),s=e.url??a?.workerUrl;t.success("deploy complete"),t.info(` worker: ${i??"(see wrangler output above)"}`),n!==void 0&&t.info(` env: ${n}`),s===void 0?t.info(" url: run `lunora link --url <https://your-worker>` to record it"):t.info(` url: ${s}`),o!==void 0&&t.info(` secrets: generated value(s) recorded in ${o}`),e.logsAvailable&&t.info(" logs: lunora logs")}catch{}},Vr=/\/index$/u,zr=/[^\dA-Za-z]/gu,qr=e=>e.replace(Vr,"").split("/").map(r=>r.replaceAll(zr,"_")),Kr=e=>qr(e).join("_"),Yr=18787,ae=3,De=12e4,Jr=5e3,H=10,Xr=new Set([".js",".mjs",".mts",".ts",".tsx"]),Qr=/\(void 0\)|is not a function|before initialization|of undefined/u,Zr=e=>typeof e=="object"&&e!==null&&typeof e.functionPath=="string"&&typeof e.message=="string",et=e=>{const r=P(e),n=new Map;let t;try{t=lr(w(e,r),{recursive:!0,encoding:"utf8"})}catch{return n}for(const o of t){const a=Ne(o);Xr.has(a)&&!o.split(re).some(i=>i==="_generated"||i==="node_modules")&&n.set(Kr(o.slice(0,-a.length).split(re).join("/")),w(r,o))}return n},rt=(e,r)=>{const n=et(e),t=new Map;for(const{functionPath:o,message:a}of r){const i=o.lastIndexOf(":"),s=o.slice(0,i),c=`${s}\0${a}`,d=t.get(c)??{exports:[],message:a,namespace:s};d.exports.push(o.slice(i+1)),t.set(c,d)}return[...t.values()].map(({exports:o,message:a,namespace:i})=>{const s=Qr.test(a)?`
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readServiceBindings as G,runCodegen as M}from"@lunora/codegen";import{runPostCodegenHook as X,resolveLazyFunctions as E,detectAgentRules as W,updateDevServerState as H,readDevServerState as Y,DEV_DAEMON_ENV as x,detectAiAgent as J,readProjectRemotePreference as K,readLiveDevServerState as V,createDiscoveryCache as Q,DEV_HANDOFF_ENV as _,clearDevServerState as Z,claimDevServerState as q,DEV_LOG_FILE_ENV as ee,inferLunoraBindings as re,resolveSchemaDirectory as te,secretSourcesFromBindings as ne,ensureDevVarsExample as oe,ensureDevVariables as ae,isInteractive as ie,DEV_VARS_FILE as T,DEV_VARS_EXAMPLE_FILE as se,fillDevSecrets as ce,DEV_BINDINGS_FILE as de,DEV_STATE_FILE as B,claimAgentRulesHint as le,AGENT_RULES_HINT as ue}from"@lunora/config";import{wranglerCodegenInputs as U,resolveRemoteEnabled as fe,startLocalHyperdrive as ve,describeWithheld as ge}from"@lunora/config/cloudflare";import{p as me}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{w as we}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{existsSync as he,watch as A}from"node:fs";import{join as ye,dirname as Se}from"node:path";import{a as pe}from"../packem_shared/codegen-error-AmH54ofi.mjs";import{a as Ee}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{d as be}from"../packem_shared/command-
|
|
1
|
+
import{readServiceBindings as G,runCodegen as M}from"@lunora/codegen";import{runPostCodegenHook as X,resolveLazyFunctions as E,detectAgentRules as W,updateDevServerState as H,readDevServerState as Y,DEV_DAEMON_ENV as x,detectAiAgent as J,readProjectRemotePreference as K,readLiveDevServerState as V,createDiscoveryCache as Q,DEV_HANDOFF_ENV as _,clearDevServerState as Z,claimDevServerState as q,DEV_LOG_FILE_ENV as ee,inferLunoraBindings as re,resolveSchemaDirectory as te,secretSourcesFromBindings as ne,ensureDevVarsExample as oe,ensureDevVariables as ae,isInteractive as ie,DEV_VARS_FILE as T,DEV_VARS_EXAMPLE_FILE as se,fillDevSecrets as ce,DEV_BINDINGS_FILE as de,DEV_STATE_FILE as B,claimAgentRulesHint as le,AGENT_RULES_HINT as ue}from"@lunora/config";import{wranglerCodegenInputs as U,resolveRemoteEnabled as fe,startLocalHyperdrive as ve,describeWithheld as ge}from"@lunora/config/cloudflare";import{p as me}from"../packem_shared/api-spec-lYyq4vL6.mjs";import{w as we}from"../packem_shared/binding-manifest-file-CQ1eQYy1.mjs";import{existsSync as he,watch as A}from"node:fs";import{join as ye,dirname as Se}from"node:path";import{a as pe}from"../packem_shared/codegen-error-AmH54ofi.mjs";import{a as Ee}from"../packem_shared/platform-diagnostics-Cn2g6Jh-.mjs";import{d as be}from"../packem_shared/command-CB4JM-Jl.mjs";import{a as De}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{EXIT_CODE as k}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{forceJsonLogging as _e}from"../packem_shared/createLogger-4TujevyB.mjs";import{createServer as ke,request as Re}from"node:http";import{connect as Te}from"node:net";import{pipeline as Ae}from"node:stream";import{loadStudioAssets as P,studioAssetsStamp as C,renderStudioHtml as Pe,resolveAdminToken as Ce,requestPathname as O,transportRejectionReason as $,SCHEMA_EDIT_ENDPOINT as Oe,POLICY_SCAFFOLD_ENDPOINT as $e,SEED_ENDPOINT as Le,sendStudioDocument as Fe,serveJsonHandler as Ne,isStandaloneModulePath as Ie,readStandaloneAsset as je,assetContentType as Me,handleSchemaEditRequest as We,handlePolicyScaffoldRequest as He,handleSeedRequest as xe,applyStudioAssetCache as Ve}from"@lunora/config/studio-host";import{c as Be}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{setTimeout as Ue}from"node:timers/promises";import{r as ze,d as Ge,P as Xe,a as Ye,b as R,c as Je,s as Ke,n as z,e as Qe,f as Ze,g as qe,h as er,i as L,w as rr,j as tr,k as nr,t as or,l as ar,m as ir,o as sr}from"../packem_shared/plan-DFY3CqHD.mjs";import{provisionBindings as cr}from"./runDeployCommand.mjs";const dr=100,lr=/(?:\.[cm]?[jt]sx?|(?:^|[/\\])tsconfig(?:\..+)?\.json)$/u,ur=300,F=/[/\\]/u,fr=(e,r,n)=>{const{apiSpec:t,lazyFunctions:o,logger:i,projectRoot:l,target:d}=e;try{const a=M({apiSpec:t,lazyFunctions:E(o),lunoraDirectory:r,projectRoot:l,target:d,...U(l)});i.success(`codegen: wrote ${r}/_generated (${n})`),Ee(a.platformDiagnostics,i)}catch(a){return i.error(pe(a,n)),!1}return!0},vr=e=>{const r=e.lunoraDirectory??"lunora",n=ye(e.projectRoot,r),t=e.debounceMs??dr;let o=!1,i=!1,l=!1,d,a=0,f=Promise.resolve(),S;const y=new Promise(v=>{S=v}),w=v=>{o||(d=v,!i&&(i=!0,f=(async()=>{try{for(;d!==void 0;){const g=d;if(d=void 0,!!fr(e,r,g)){l=!0;try{(await X({cwd:e.projectRoot,logger:e.logger,spawner:e.spawner,stdoutToStderr:e.jsonLogs})).ran&&(a=Date.now())}finally{l=!1}}}}catch(g){e.logger.error(`codegen watch: ${g instanceof Error?g.message:String(g)}`)}finally{i=!1,S()}})()))};w("startup");const c=v=>(e.logger.warn(`codegen watch unavailable (${v}) — schema edits will NOT auto-regenerate. Run \`lunora codegen\` manually after each edit.`),{close:()=>(o=!0,d=void 0,f),ready:y,watchAvailable:!1});let s,u;if(!he(n))return c(`no such directory: ${n}`);const m=v=>{if(l||Date.now()-a<ur){s&&(clearTimeout(s),s=void 0);return}s&&clearTimeout(s),s=setTimeout(()=>{w(`change: ${v??"?"}`)},t)},h=[];try{u=A(n,{recursive:!0},(v,g)=>{typeof g=="string"&&g.split(F).includes("_generated")||m(g)});for(const v of G(e.projectRoot).services)if(v.rpcEntrypoint!==void 0){const g=A(Se(v.wranglerPath),{recursive:!0},(D,b)=>{typeof b=="string"&&lr.test(b)&&!b.split(F).includes("node_modules")&&m(b)});g.on("error",D=>{e.logger.warn(`codegen watch: stopped watching service "${v.name}" (${D.message})`),g.close()}),h.push(g)}}catch(v){u?.close();for(const g of h)g.close();return c(v instanceof Error?v.message:String(v))}const p=u;return{close:()=>{o=!0,d=void 0,s&&clearTimeout(s),p.close();for(const v of h)v.close();return f},ready:y,watchAvailable:!0}},N="/_lunora",I=3e4,j=(e,r,n)=>{const t=Re({headers:{...e.headers,host:n.host},hostname:n.hostname,method:e.method,path:e.url,port:n.port},o=>{r.writeHead(o.statusCode??502,o.headers),Ae(o,r,()=>{})});t.setTimeout(I,()=>{t.destroy(new Error(`no response within ${String(I)}ms`))}),t.on("error",o=>{if(r.headersSent){r.destroy();return}r.statusCode=o.message.startsWith("no response within")?504:502,r.end(`lunora dev: worker unreachable (${o.message})`)}),r.on("close",()=>{r.writableFinished||t.destroy()}),e.pipe(t)},gr=5e3,mr=(e,r,n,t)=>{const o=Te({host:t.hostname,port:Number(t.port)},()=>{o.setTimeout(0);const i=[`${e.method??"GET"} ${e.url??"/"} HTTP/1.1`];for(const[l,d]of Object.entries(e.headers))if(l!=="host")for(const a of Array.isArray(d)?d:[d])a!==void 0&&i.push(`${l}: ${a}`);i.push(`host: ${t.host}`,"",""),o.write(i.join(`\r
|
|
2
2
|
`)),n.length>0&&o.write(n),o.pipe(r),r.pipe(o)});o.setTimeout(gr,()=>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=P(e.logger,import.meta.url),i=C(import.meta.url);const l=Pe({adminToken:n?Ce(e.cwd):void 0,basePath:"/",dataEditable:n,rulesInstalled:W(e.cwd).installed,runAsIdentity:n,schemaEditable:n,scriptSrc:"/studio.js",styleHref:"/styles.css"}),d=(c,s,u,m,h)=>{Ve(c,s,h,i)||(s.statusCode=200,s.setHeader("Content-Type",m),s.end(u))},a=(c,s,u)=>{c.statusCode=s,c.setHeader("Content-Type","text/plain"),c.end(u)},f=(c,s,u,m)=>{if(n){Ne(c,s,u,e.cwd,{apiSpec:e.apiSpec,lazyFunctions:E(e.lazyFunctions)});return}a(s,403,m)},S=(c,s,u)=>{const m=c==="/styles.css",h=Ie(c);if(!m&&!h)return!1;const p=C(import.meta.url);if(p!==i&&(o=P(e.logger,import.meta.url),i=p),o===void 0)return a(u,501,"Lunora studio assets not found — install and build @lunora/studio."),!0;if(m)return d(s,u,o.styles,"text/css; charset=utf-8","styles.css"),!0;const v=c.slice(c.lastIndexOf("/")+1),g=je(v,import.meta.url);return g===void 0?(a(u,404,"Not found"),!0):(d(s,u,g,Me(v),v),!0)},y=Buffer.from(l),w=ke((c,s)=>{const u=O(c.url??"/");if(n){const h=$(c,e.logger);if(h!==void 0){a(s,403,h);return}}if(u.startsWith(N)){if(!n){a(s,403,"Lunora studio: the worker admin proxy is only available on a loopback bind.");return}j(c,s,t);return}if(u===Oe){f(c,s,We,"Lunora schema editing is only available on loopback hosts in dev.");return}if(u===$e){f(c,s,He,"Lunora policy scaffolding is only available on loopback hosts in dev.");return}if(u===Le){f(c,s,xe,"Lunora data seeding is only available on loopback hosts in dev.");return}if(S(u,c,s))return;const m=c.headers["sec-fetch-mode"];if(n&&typeof m=="string"&&m!=="navigate"){j(c,s,t);return}Fe(s,y)});return w.on("upgrade",(c,s,u)=>{if(!n||$(c,e.logger)!==void 0){s.destroy();return}O(c.url??"").startsWith(N)?mr(c,s,u,t):s.destroy()}),await new Promise((c,s)=>{w.once("error",u=>{u.code==="EADDRINUSE"?s(new Error(`studio port ${String(e.port)} is already in use — pass a different port with --port, or stop the process using it`,{cause:u})):s(u)}),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??Ge,l=ze(e.readyTimeoutMs),d=Date.now()+l,a=()=>o.aborted;for(;!a()&&Date.now()<d;){let f=!1;try{f=await i(t,o)}catch{}if(a()||Date.now()>=d)break;if(f)return H(r,{readyAt:new Date().toISOString()},{expectedPid:process.pid}),Y(r)?.readyAt!==void 0;try{await Ue(Xe,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},yr=(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) · ${B} (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(` ➜ ${ge(o)}`);e.info("")},Sr=(e,r)=>{W(r).installed||!le()||(e.info(` ⓘ ${ue}`),e.info(""))},pr=async(e,r,n)=>{try{const o=await re({cache:n,projectRoot:r,schemaDir:te(r)}),i=ne(o),l=(e.ensureExample??oe)(r,i);if(l.length>0){const d=i.map(a=>a.includes(":")?`${a.replace(":"," (")})`:a);e.logger.info(`Updated .dev.vars.example with secrets for: ${d.join(", ")} (${l.join(", ")})`)}}catch{}(await(e.ensureEnv??ae)({confirm:Be(),cwd:r,info:o=>{e.logger.info(o)}})).status==="declined"&&!ie()&&e.logger.info(`hint: ${T} was not scaffolded (non-interactive run). Copy ${se} → ${T} and fill in secrets, or run \`lunora dev\` in an interactive terminal to scaffold automatically.`);try{(e.fillSecrets??ce)({cwd:r,info:o=>{e.logger.info(o)}})}catch{}},Er=(e,r,n,t,o)=>{if(e.flavor!=="wrangler")return;t!==void 0&&H(r,{studioUrl:t});let i;try{i=sr(r,n)}catch{}return yr(n,e,t,o),i},br=(e,r)=>{const n=Number(process.env[_]),t=q(r,{background:process.env[x]==="1",logFile:process.env[ee],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},Dr=e=>{const{cwd:r,destination:n,logger:t,plan:o}=e,i=n!==void 0,l=n??de,d=we({destination:l,dev:{...o.flavor==="wrangler"?{origin:o.workerOrigin}:{},statusFile:B},logger:i?t:{...t,success:()=>{},warn:()=>{}},projectRoot:r});return d.error!==void 0?i?d:(t.debug?.(`skipped the default binding manifest: ${d.error}`),{}):{written:l}},_r=async(e,r,n,t)=>{if(r.studioEnabled)try{return await(e.startStudio??wr)({apiSpec:e.apiSpec,lazyFunctions:E(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}},kr=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}.`},Rr=(e,r,n,t,o)=>{if(!(e.sidecar===void 0||!ar(r)))try{M({apiSpec:r.apiSpec,lazyFunctions:E(r.lazyFunctions),lunoraDirectory:"lunora",projectRoot:n,target:o,...U(n)})}catch(i){t.warn(`codegen (pre-sidecar) failed: ${i instanceof Error?i.message:String(i)} — the framework dev server will retry`)}},Tr=e=>e.flavor==="vite"?"starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)":`starting ${e.wrangler.tag} dev + studio`,Ar=async(e,r,n,t,o)=>{try{return e.celldSession===!0?await ir({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}},Pr=async(e,r,n)=>{const t=await(e.startHyperdrive??ve)({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},Cr=async e=>{const{logger:r}=e,n=Q(),t=e.cwd??process.cwd(),o=e.flavor??Qe(t),i=De(t,e.target);if(i.target===void 0)throw new Error(i.error??"unknown deploy target");const{target:l}=i,d=Ze(l,o,r);d==="wrangler"&&await cr(t,r,void 0,l,void 0,n);const a=await qe({...e,flavor:d,target:l}),f={remoteCleanup:a.remote.cleanup,serviceConfigCleanup:a.serviceConfigCleanup};try{const S=Number(process.env[_]),y=V(t);if(y!==void 0&&y.pid!==process.pid&&y.pid!==S)return R(r,y),{code:0,plan:a};const w=br(a,t);if(w!==void 0)return R(r,w),{code:0,plan:a};(a.flavor==="vite"||a.flavor==="framework-worker")&&(a.wrangler.env={...a.wrangler.env,[_]:String(process.pid)}),a.workerEnabled&&l==="cloudflare"&&(f.hyperdrive=await Pr(e,t,r)),await pr(e,t,n),r.info(Tr(a)),er(r,t),a.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."),a.runsCodegenWatch&&(f.codegen=(e.startCodegen??vr)({apiSpec:e.apiSpec,lazyFunctions:E(e.lazyFunctions),jsonLogs:e.jsonLogs,logger:r,projectRoot:t,target:l})),f.studio=await _r(e,a,t,r);const c=Dr({cwd:t,destination:e.emitBindings,logger:r,plan:a});if(c.error!==void 0)return r.error(c.error),{code:k.USAGE,plan:a};await f.codegen?.ready;const s=f.studio?.url;a.frameworkHint!==void 0&&r.warn(a.frameworkHint);const u=()=>{a.flavor==="wrangler"&&(f.readyProbe=new AbortController,hr({cwd:t,logger:r,origin:a.workerOrigin,probe:e.probeReady,signal:f.readyProbe.signal}))};if(!a.workerEnabled)return u(),r.info(kr(a)),f.tunnel=L({cwd:t,logger:r,plan:a,tunnel:e.tunnel}),{code:await(e.waitForInterrupt??rr)(r),plan:a};Rr(a,e,t,r,l);const m=e.startWorker??tr,h=await Ar(a,e,m,t,r);if(h===void 0)return{code:k.FAILURE,plan:a};const p=a.sidecar===void 0?void 0:m(a.sidecar,r);return u(),f.containerLogs=Er(a,t,r,s,c.written),f.tunnel=L({cwd:t,logger:r,plan:a,tunnel:e.tunnel}),Sr(r,t),{code:await nr(h,p,r),plan:a}}finally{f.readyProbe?.abort(),Z(t,process.pid),await or(f)}},Or=e=>({codegen:e.codegen===!1?!1:void 0,studio:e.studio===!1?!1:void 0,worker:e.worker===!1?!1:void 0}),$r=e=>{if(e.local===!0&&e.remote===!0)return"`--local` and `--remote` are mutually exclusive — pass at most one.";const{entries:r,invalid:n}=z(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`."},Lr=e=>e.tunnel===!0?{allowMail:z(e.allowMail).entries}:void 0,rt=be(async({argument:e,cwd:r,logger:n,options:t})=>{const o=t.json===!0,i=Ye({cwd:r,json:o,lines:t.lines,logger:n,subcommand:e[0]});if(i!==void 0)return i;const l=process.env[x]==="1",d=l?void 0:J(),a=o||d!==void 0;a&&_e(),d!==void 0&&t.background!==!0&&n.info(`AI agent detected (${d.name} via ${d.variable}) — starting the dev server in background mode with JSON logs. Set LUNORA_AGENT_MODE=0 to opt out.`);const f=$r(t);if(f!==void 0)return n.error(`dev: ${f}`),{code:k.USAGE};const S=Lr(t),y=t.local===!0?!1:fe({configPreference:K(r),envValue:process.env.LUNORA_REMOTE,flag:t.remote});if(!l&&(t.background===!0||d!==void 0)){const w=V(r);return w!==void 0?(R(n,w),{code:0}):(S?.allowMail.length===0&&Je(n),Ke({cwd:r,jsonLogs:a,logger:n,options:t,remote:y}))}return Cr({apiSpec:me(t.apiSpec),cwd:r,emitBindings:t.emitBindings,inspectorPort:t.inspectorPort,lazyFunctions:E(t.lazyFunctions),jsonLogs:a,local:t.local,logger:n,port:t.port,remote:y,target:t.target,tunnel:S,workerPort:t.workerPort,...Or(t)})});export{$r as devFlagConflict,rt as execute,Or as negatableDevFlags,Cr as runDevCommand};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync as v,mkdirSync as M,writeFileSync as w,readFileSync as T,readdirSync as de,rmSync as $,lstatSync as ue,mkdtempSync as pe,copyFileSync as Je,renameSync as Xe}from"node:fs";import{tmpdir as he}from"node:os";import{detectFramework as Qe,isInteractive as y,LUNA_ART as Ze,LUNA_NAME as et,LUNA_SIGNOFF as tt,BADGES as g,paintAnswer as rt,detectLintTools as at,readProjectDependencies as me,applyLintIgnores as ot,readProjectManifest as nt}from"@lunora/config";import{readJsonSync as it,walkSync as st}from"@visulima/fs";import{join as p,dirname as S,basename as N,resolve as fe,relative as ge}from"@visulima/path";import{downloadTemplate as ve}from"giget";import{modify as lt,applyEdits as ct}from"jsonc-parser";import{join as K,dirname as dt}from"node:path";import{installArgsFor as we,execArgsFor as ut,detectPackageManager as P,detectInstalledManagers as pt,runScriptCommand as ht,addArgsFor as mt}from"@lunora/config/cli";import{d as ft}from"../packem_shared/command-
|
|
1
|
+
import{existsSync as v,mkdirSync as M,writeFileSync as w,readFileSync as T,readdirSync as de,rmSync as $,lstatSync as ue,mkdtempSync as pe,copyFileSync as Je,renameSync as Xe}from"node:fs";import{tmpdir as he}from"node:os";import{detectFramework as Qe,isInteractive as y,LUNA_ART as Ze,LUNA_NAME as et,LUNA_SIGNOFF as tt,BADGES as g,paintAnswer as rt,detectLintTools as at,readProjectDependencies as me,applyLintIgnores as ot,readProjectManifest as nt}from"@lunora/config";import{readJsonSync as it,walkSync as st}from"@visulima/fs";import{join as p,dirname as S,basename as N,resolve as fe,relative as ge}from"@visulima/path";import{downloadTemplate as ve}from"giget";import{modify as lt,applyEdits as ct}from"jsonc-parser";import{join as K,dirname as dt}from"node:path";import{installArgsFor as we,execArgsFor as ut,detectPackageManager as P,detectInstalledManagers as pt,runScriptCommand as ht,addArgsFor as mt}from"@lunora/config/cli";import{d as ft}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as m}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import gt from"magic-string";import{Project as vt,SyntaxKind as f}from"ts-morph";import{P as q}from"../packem_shared/prompt-cancelled-BvsNxg_Q.mjs";import{l as ye,m as wt,n as be,o as xe,a as yt}from"../packem_shared/resolve-Bdc9KEUv.mjs";import{defaultSpawner as ke}from"../packem_shared/createRecordingSpawner-Ch7zuQQE.mjs";import{d as bt,e as xt,f as kt,a as Ce,t as G,g as Se,h as Ct,b as Le,i as Me,w as St,j as Lt,k as Ae,l as Mt}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{A as Ee,E as Ne,p as At,w as Et,M as Nt,r as It,k as Rt,e as _t,c as Ie,l as Re,i as $t,f as Ot}from"../packem_shared/storage-DNzYhXR-.mjs";import{r as Tt}from"../packem_shared/commands-DvSfLvyt.mjs";import{logStep as Q}from"../packem_shared/createLogger-4TujevyB.mjs";import{r as Pt}from"../packem_shared/lint-ignore-report-DKZagpqk.mjs";import{DEFAULT_OBSERVABILITY as Dt,CLOUDFLARE_TOOLCHAIN_VERSIONS as Z}from"@lunora/config/cloudflare";import Bt from"node:dns/promises";const Y={bun:{githubCacheKey:void 0,githubSetupStep:` - uses: oven-sh/setup-bun@v2
|
|
2
2
|
`,gitlabImage:"oven/bun:1",gitlabProvisionScript:[],installCmd:"bun install --frozen-lockfile",lockfile:"bun.lockb"},npm:{githubCacheKey:"npm",githubSetupStep:"",gitlabImage:"node:22",gitlabProvisionScript:[],installCmd:"npm ci",lockfile:"package-lock.json"},pnpm:{githubCacheKey:"pnpm",githubSetupStep:` - uses: pnpm/action-setup@v4
|
|
3
3
|
with:
|
|
4
4
|
version: latest
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as m,rmSync as d}from"node:fs";import{join as n,relative as f}from"@visulima/path";import{d as a}from"../packem_shared/command-
|
|
1
|
+
import{existsSync as m,rmSync as d}from"node:fs";import{join as n,relative as f}from"@visulima/path";import{d as a}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as s}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{b as l}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";const u=async e=>{const o=e.cwd??process.cwd(),t=[n(o,".wrangler","state")];if(e.all&&t.push(n(o,".lunora-cache")),!e.yes){const r=t.map(i=>f(o,i)).join(" and ");if(!process.stdin.isTTY&&e.confirm===void 0)return e.logger.error(`reset: stdin is not a TTY — re-run with --yes to confirm deleting ${r}`),{code:s.USAGE,removed:[]};if(!await(e.confirm??l)(`This will delete ${r}. Continue?`))return e.logger.info("reset: aborted"),{code:s.CANCELLED,removed:[]}}const c=[];for(const r of t)m(r)?(d(r,{force:!0,recursive:!0}),c.push(r),e.logger.success(`removed ${r}`)):e.logger.info(`skipped (not present): ${r}`);return{code:0,removed:c}},E=a(({cwd:e,logger:o,options:t})=>u({all:t.all===!0,cwd:e,logger:o,yes:t.yes===!0}));export{E as execute,u as runResetCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as h}from"@lunora/errors";import{r as S,d as b}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as _,b as N,a as U}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as P}from"../packem_shared/command-
|
|
1
|
+
import{LunoraError as h}from"@lunora/errors";import{r as S,d as b}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as _,b as N,a as U}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as P}from"../packem_shared/command-CB4JM-Jl.mjs";import{EXIT_CODE as i,exitCodeForStatus as E}from"../packem_shared/EXIT_CODE-D_XCixv8.mjs";import{r as I}from"../packem_shared/resolve-target-C_ZloTvd.mjs";const y=(r,t="--args")=>{if(r===void 0||r.length===0)return{};let e;try{e=JSON.parse(r)}catch(a){const s=a instanceof Error?a.message:String(a);throw new h("INTERNAL",`failed to parse ${t} as JSON: ${s}`,{cause:a})}if(typeof e!="object"||e===null||Array.isArray(e))throw new h("INTERNAL",`${t} must be a JSON object (e.g. '{"id":1}'), got ${Array.isArray(e)?"an array":typeof e}`);return e},O=/\/$/u,$="__lunora_admin__:runAs",T="__lunora_admin__:",k=async(r,t)=>{const e=await r.text();let a;try{a=JSON.parse(e)}catch{a=e}return t.info(JSON.stringify(a,void 0,2)),a},p=(r,t)=>{const e=t.body?.error?.code;t.runAs||t.status!==403||e!=="FORBIDDEN_SHARD"||r.info("this app authorizes per shard, so an anonymous call is denied — retry with --as <userId> to run as an authenticated user")},R=({args:r,claims:t,options:e,runAs:a})=>{const s=a?{args:{args:r,functionPath:e.functionPath,identity:t,userId:e.as},functionPath:$}:{args:r,functionPath:e.functionPath};return e.shard!==void 0&&(s.shardKey=e.shard),s},w=({cwd:r,logger:t,needsBearer:e,url:a})=>e?_(a,t,r):(I({cwd:r,url:a})??N(r)).replace(O,""),D=({args:r,claims:t},e)=>{try{return{args:y(r),claims:t===void 0?void 0:y(t,"--claims")}}catch(a){e.error(a instanceof Error?a.message:String(a));return}},J=async r=>{const t=r.cwd??process.cwd(),{logger:e}=r,a=r.as!==void 0&&r.as!=="";if(r.claims!==void 0&&!a){const d="--claims requires --as <userId> — extra identity claims only travel inside the admin-gated `runAs` dispatch.";return e.error(d),{body:void 0,code:i.USAGE,error:d,requestUrl:r.url??""}}const s=a||r.functionPath.startsWith(T),n=w({cwd:t,logger:e,needsBearer:s,url:r.url});if(n===void 0)return{body:void 0,code:i.USAGE,error:"could not resolve a usable worker URL",requestUrl:r.url??""};const o=`${n}/_lunora/rpc`,u=r.fetchImpl??U,m=D(r,e);if(m===void 0)return{body:void 0,code:i.USAGE,error:"could not parse --args / --claims as JSON",requestUrl:o};const{source:g,token:l}=s?S({cwd:t,token:r.token,url:n}):{};if(s&&l===void 0){const d="admin token required — pass --token, set LUNORA_ADMIN_TOKEN, or add it to .dev.vars (local targets only)";return e.error(d),{body:void 0,code:i.AUTH,error:d,requestUrl:o}}l!==void 0&&e.debug?.(`admin bearer from ${b(g)}`);const v={"content-type":"application/json",...l===void 0?{}:{authorization:`Bearer ${l}`}},A=R({args:m.args,claims:m.claims,options:r,runAs:a});e.info(`POST ${o} -> ${r.functionPath}${a?` (as ${r.as??""})`:""}`);const c=await u(o,{body:JSON.stringify(A),headers:v,method:"POST"}),f=await k(c,e);return p(e,{body:f,runAs:a,status:c.status}),{body:f,code:c.ok?0:E(c.status),error:c.ok?void 0:`${r.functionPath} failed: HTTP ${String(c.status)}`,requestUrl:o}},j=P(async({argument:r,cwd:t,format:e,logger:a,options:s})=>{const n=r[0];if(!n){const u="missing function path. Usage: lunora run <functionPath> [--args <json>]";return a.error(u),{code:i.USAGE,error:u}}const o=await J({args:s.args,as:s.as,claims:s.claims,cwd:t,format:e,functionPath:n,logger:a,shard:s.shard,token:s.token,url:s.url});return{code:o.code,data:{functionPath:n,requestUrl:o.requestUrl,result:o.body},error:o.error}});export{j as execute,k as readAndLogBody,J as runRpcCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{C as E,R as M,V as a,r as s}from"./cli-
|
|
1
|
+
import{C as E,R as M,V as a,r as s}from"./cli-CPcfZdt0.mjs";import"./cli-manifest-DoQ_3bzs.mjs";import"./EXIT_CODE-D_XCixv8.mjs";import"./createLogger-4TujevyB.mjs";import"./render-lunora-error-B6893vbU.mjs";export{E as COMMANDS,M as REGISTERED_COMMAND_NAMES,a as VERSION,s as runCli};
|