@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands
2 lines (1 loc) • 4.5 kB
JavaScript
import{c as u}from"../packem_shared/cap-error-body-YBKO32BF.mjs";import{r as y}from"../packem_shared/admin-token-VdUnvnKW.mjs";import{r as S,a as A}from"../packem_shared/admin-url-DJB8P6Jh.mjs";import{d as w}from"../packem_shared/command-B6vu47iR.mjs";import{r as E}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{exitCodeForStatus as $,EXIT_CODE as n}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{resolve as N}from"node:path";import{w as U}from"../packem_shared/mcp-config-file-GR1Y_CVw.mjs";const f=["cpu","heap"],p=1e3,g=5e4,b=1e4,T=3e4,h=[31,139],v=(r,e)=>{const t=r??"cpu";if(!f.includes(t))return{error:`profile: invalid --type "${t}" — expected ${f.join(" | ")}`};const o=e===void 0?b:Number(e);return!Number.isInteger(o)||o<p||o>g?{error:`profile: invalid --duration-ms "${e??""}" — expected an integer from ${String(p)} to ${String(g)}`}:{durationMs:o,profileType:t}},I=async r=>{r.logger.info(`Profiling ${r.url} for ${String(r.durationMs)} ms — keep traffic flowing to it…`);try{const e=await r.fetch(r.url,{body:JSON.stringify(r.body),headers:{...r.headers,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(r.durationMs+T)});if(!e.ok){const o=r.describeBody(await e.text());return{code:$(e.status),error:`profile failed (HTTP ${String(e.status)})${o.length>0?`: ${o}`:""}${r.hintForStatus(e.status)}.`}}const t=new Uint8Array(await e.arrayBuffer());if(t[0]!==h[0]||t[1]!==h[1]){const o=r.describeBody(new TextDecoder().decode(t));return{code:n.FAILURE,error:`profile failed: the endpoint answered 200 with something that is not a gzip profile${o.length>0?`: ${o}`:""}.`}}return{bytes:t}}catch(e){const t=e instanceof Error?e.message:String(e);return{code:n.UNAVAILABLE,error:`profile failed: ${t}.`}}},_=(r,e,t,o)=>{const a=N(r,e??t);try{return U(a,o),{file:a}}catch(s){return{error:`could not write ${a}: ${s instanceof Error?s.message:String(s)}`}}},i=(r,e,t)=>(r.error(t),{code:e,error:t}),M=r=>{try{const e=JSON.parse(r);if(typeof e.error?.message=="string"&&e.error.message.length>0)return u(e.error.message)}catch{}return u(r.trim())},O=r=>{switch(r){case 401:case 403:return" — check that the token matches the one the app passes to createNodeProfileHandler";case 404:return" — check the URL: it must be the route the app mounted createNodeProfileHandler on";case 409:return" — a capture is already running in that app process; wait for it to finish";default:return""}},P=r=>{const{logger:e}=r,t=E(r.cwd,r.target);if(t.error!==void 0)return i(e,n.USAGE,`profile: ${t.error}`);if(t.target==="cloudflare")return i(e,n.USAGE,"profile: this project deploys to Cloudflare. `lunora profile` profiles a Node-target app; on Cloudflare run `lunora cloudflare profile`.");if(t.target!=="node")return i(e,n.USAGE,"profile: only the node target is supported here — pass --target node")},k=r=>{const{logger:e}=r;if(r.url===void 0||r.url==="")return i(e,n.USAGE,"profile: pass --url with the full URL of the route the app mounts createNodeProfileHandler on (there is no default for a Node app)");const t=S(r.url,e,r.cwd);if(t===void 0)return{code:n.USAGE,error:"could not resolve a usable profile URL"};const{token:o}=y({cwd:r.cwd,token:r.token,url:t});return o===void 0?i(e,n.AUTH,"profile: no admin token — pass --token, or set LUNORA_ADMIN_TOKEN"):{token:o,url:t}},R=async r=>{const{logger:e}=r,t=v(r.profileType,r.durationMs);if("error"in t)return i(e,n.USAGE,t.error);const o=P(r);if(o!==void 0)return o;const a=k(r);if("code"in a)return a;const{durationMs:s,profileType:d}=t,c=await I({body:{duration_ms:s,profile_type:d},describeBody:M,durationMs:s,fetch:r.fetchImpl??A,headers:{Authorization:`Bearer ${a.token}`},hintForStatus:O,logger:e,url:a.url});if("code"in c)return i(e,c.code,c.error);const m=(r.now??new Date).toISOString().replaceAll(/[:.]/gu,"-"),l=_(r.cwd,r.out,`profile-${d}-${m}.pprof.gz`,c.bytes);return"error"in l?i(e,n.FAILURE,`profile failed: ${l.error}.`):(e.success(`Wrote ${String(c.bytes.byteLength)} bytes to ${l.file}`),e.info("Open it with `go tool pprof -http=: <file>` (pprof reads gzip directly) or upload it to a flamegraph viewer."),{code:n.SUCCESS,data:{bytes:c.bytes.byteLength,durationMs:s,file:l.file,profileType:d,url:a.url}})},z=w(async({cwd:r,logger:e,options:t})=>{const o=await R({cwd:r,durationMs:t.durationMs,logger:e,out:t.out,profileType:t.type,target:t.target,token:t.token,url:t.url});return{code:o.code,data:o.data,error:o.error}});export{z as execute,R as runProfileCommand};