@stacksjs/ts-cloud 0.16.67 → 0.16.69
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/aws/index.js +1 -1
- package/dist/bin/{chunk-vcs8wtay.js → chunk-17ewx21n.js} +11 -11
- package/dist/bin/{chunk-6rvejkny.js → chunk-1cb0r1j3.js} +1 -1
- package/dist/bin/{chunk-shxqbkrz.js → chunk-1tk64amh.js} +1 -1
- package/dist/bin/{chunk-jq4xyf7y.js → chunk-2247xk4n.js} +1 -1
- package/dist/bin/{chunk-sb29jzvd.js → chunk-370pghhq.js} +113 -113
- package/dist/bin/{chunk-yec39wzh.js → chunk-37xeqae4.js} +1 -1
- package/dist/bin/{chunk-bzjr9jwn.js → chunk-43a60zkj.js} +1 -1
- package/dist/bin/chunk-4vz5erjz.js +1 -0
- package/dist/bin/{chunk-f4v7nm9a.js → chunk-55j0x82p.js} +5 -5
- package/dist/bin/{chunk-7pxqdq44.js → chunk-5fde4d44.js} +1 -1
- package/dist/bin/{chunk-qjh516ah.js → chunk-5gf0je0v.js} +11 -11
- package/dist/bin/{chunk-245rppy4.js → chunk-5t1ky9re.js} +1 -1
- package/dist/bin/{chunk-pyxmdv8d.js → chunk-6cyr6gcg.js} +1 -1
- package/dist/bin/chunk-6rwewb7j.js +1 -0
- package/dist/bin/{chunk-xkcvfpd0.js → chunk-6tqzgm9z.js} +4 -4
- package/dist/bin/chunk-7d83f6sn.js +1 -0
- package/dist/bin/chunk-7v68hpgc.js +2 -0
- package/dist/bin/{chunk-djqrnrpx.js → chunk-7y56qyrj.js} +1 -1
- package/dist/bin/{chunk-jyfm20f3.js → chunk-8escxm7h.js} +1 -1
- package/dist/bin/{chunk-kwfbth5c.js → chunk-b0r8a5h5.js} +3 -3
- package/dist/bin/{chunk-6stcmq7g.js → chunk-bkvxzenb.js} +1 -1
- package/dist/bin/{chunk-0h1ae217.js → chunk-d28q9ath.js} +1 -1
- package/dist/bin/chunk-dmm1p4bs.js +1 -0
- package/dist/bin/chunk-dycx66c7.js +1 -0
- package/dist/bin/{chunk-9vjg754g.js → chunk-e4c7ba0g.js} +1 -1
- package/dist/bin/chunk-fjx816tt.js +1 -0
- package/dist/bin/chunk-fm6paqft.js +1 -0
- package/dist/bin/{chunk-p801tzxz.js → chunk-gga18c4g.js} +1 -1
- package/dist/bin/{chunk-er5zfr54.js → chunk-gmy8p7nr.js} +1 -1
- package/dist/bin/chunk-h5ccvped.js +1 -0
- package/dist/bin/chunk-h9h9dn0r.js +1 -0
- package/dist/bin/chunk-ht6s2cwm.js +1 -0
- package/dist/bin/{chunk-vwgpwjwh.js → chunk-jrxb5sg4.js} +2 -2
- package/dist/bin/{chunk-g47h6fg3.js → chunk-krt80e8w.js} +2 -2
- package/dist/bin/{chunk-vf5w5aed.js → chunk-m60wwxz1.js} +6 -6
- package/dist/bin/{chunk-yc1n2h5w.js → chunk-mjbd4pbz.js} +2 -2
- package/dist/bin/chunk-ncr1dwdt.js +1 -0
- package/dist/bin/{chunk-5zp3s61y.js → chunk-nxsx69p4.js} +2 -2
- package/dist/bin/chunk-nzhpva7z.js +1 -0
- package/dist/bin/chunk-p40tvp17.js +1 -0
- package/dist/bin/{chunk-k20dxfrh.js → chunk-pgbtdsj2.js} +2 -2
- package/dist/bin/{chunk-a68bhc0s.js → chunk-pr96tp0s.js} +2 -2
- package/dist/bin/chunk-qe8zh9kn.js +1 -0
- package/dist/bin/{chunk-qascek4k.js → chunk-qjxd8f5m.js} +3 -3
- package/dist/bin/{chunk-pxm3w8a3.js → chunk-qka0e661.js} +5 -5
- package/dist/bin/{chunk-n80ewf4z.js → chunk-rgnmxfvp.js} +1 -1
- package/dist/bin/{chunk-sz45s7mw.js → chunk-sn1zde06.js} +1 -1
- package/dist/bin/{chunk-2mdh3syn.js → chunk-sv7n9ape.js} +2 -2
- package/dist/bin/{chunk-mn0rzykt.js → chunk-szagab6z.js} +99 -99
- package/dist/bin/chunk-t6zpvaqr.js +1 -0
- package/dist/bin/{chunk-34r2fwwn.js → chunk-tdv5rwjv.js} +1 -1
- package/dist/bin/{chunk-dbtm1j98.js → chunk-v5js5pz6.js} +170 -170
- package/dist/bin/chunk-vrn0akcn.js +1 -0
- package/dist/bin/{chunk-nxsa7efz.js → chunk-w2eezm9z.js} +2 -2
- package/dist/bin/{chunk-h2f4f194.js → chunk-wjn2bpdb.js} +1 -1
- package/dist/bin/chunk-x00efaxm.js +1 -0
- package/dist/bin/{chunk-gvk2af4h.js → chunk-x40sd2m9.js} +2 -2
- package/dist/bin/chunk-zred4827.js +1 -0
- package/dist/bin/{chunk-jwby10he.js → chunk-zz9nj5kn.js} +3 -3
- package/dist/bin/cli.js +83 -83
- package/dist/bin/dashboard-server.js +1 -1
- package/dist/{chunk-y9589qef.js → chunk-040rbk7k.js} +35 -35
- package/dist/{chunk-bkkwtcnf.js → chunk-17bmm7sx.js} +70 -70
- package/dist/{chunk-9w5ncn9s.js → chunk-1wc2zfct.js} +3 -3
- package/dist/{chunk-axcxfz8p.js → chunk-1yj07hbw.js} +2 -2
- package/dist/{chunk-0kemzwpg.js → chunk-41fwhvp7.js} +1 -1
- package/dist/chunk-426k7fgf.js +1 -0
- package/dist/chunk-4b0ynr9y.js +1 -0
- package/dist/{chunk-a92gvctb.js → chunk-a07kew2q.js} +6 -6
- package/dist/{chunk-0dfyn0bv.js → chunk-c61x369p.js} +85 -85
- package/dist/chunk-emkp3dsp.js +1 -0
- package/dist/{chunk-pymc2aad.js → chunk-h360gwrf.js} +2 -2
- package/dist/{chunk-7mpbjr6c.js → chunk-h5yx54ks.js} +3 -3
- package/dist/{chunk-q4408fwg.js → chunk-hvttzk9q.js} +2 -2
- package/dist/{chunk-q8vvwmka.js → chunk-je8tky8z.js} +1 -1
- package/dist/{chunk-03eny26f.js → chunk-kbr9nc8y.js} +2 -2
- package/dist/{chunk-3ca6cb1s.js → chunk-p21vbrgk.js} +1 -1
- package/dist/chunk-pcagajw1.js +1 -0
- package/dist/{chunk-83fy36fn.js → chunk-qb25wesa.js} +2 -2
- package/dist/{chunk-hk1frmnn.js → chunk-qrpy0h2n.js} +1 -1
- package/dist/{chunk-xd67tr7p.js → chunk-s4wz0csj.js} +1 -1
- package/dist/chunk-sybwn2y6.js +1 -0
- package/dist/{chunk-r49ynkag.js → chunk-t8fbwcdk.js} +1 -1
- package/dist/{chunk-qgtydqpj.js → chunk-tb8azghz.js} +23 -23
- package/dist/{chunk-pah2r8a0.js → chunk-tq7nsdnd.js} +4 -4
- package/dist/{chunk-0860mm00.js → chunk-v0s8vpa3.js} +1 -1
- package/dist/{chunk-tngsyawj.js → chunk-xyem7g21.js} +1 -1
- package/dist/{chunk-zgfzc46r.js → chunk-yh9tmx00.js} +3 -3
- package/dist/{chunk-1t3rjvf9.js → chunk-z585dfjz.js} +12 -12
- package/dist/deploy/index.js +1 -1
- package/dist/dns/index.js +1 -1
- package/dist/drivers/index.js +1 -1
- package/dist/index.js +8 -8
- package/dist/mail/index.js +2 -2
- package/dist/object-storage/index.d.ts +6 -4
- package/dist/object-storage/migrate.d.ts +3 -1
- package/dist/operations/index.js +1 -1
- package/dist/protection/index.js +1 -1
- package/dist/spend/index.js +1 -1
- package/dist/ui/404.html +1 -1
- package/dist/ui/accept-invitation.html +1 -1
- package/dist/ui/access-denied.html +1 -1
- package/dist/ui/account/automation.html +5 -5
- package/dist/ui/account/security.html +1 -1
- package/dist/ui/applications/compose.html +6 -6
- package/dist/ui/applications/new.html +2 -2
- package/dist/ui/data/backups.html +6 -6
- package/dist/ui/data/services.html +6 -6
- package/dist/ui/data/volumes.html +6 -6
- package/dist/ui/index.html +6 -6
- package/dist/ui/infrastructure/topology.html +5 -5
- package/dist/ui/integrations.html +2 -2
- package/dist/ui/operations/alerts.html +6 -6
- package/dist/ui/operations/configuration.html +5 -5
- package/dist/ui/operations/jobs.html +5 -5
- package/dist/ui/operations/maintenance.html +6 -6
- package/dist/ui/operations/observability.html +6 -6
- package/dist/ui/operations/previews.html +6 -6
- package/dist/ui/operations/queue.html +5 -5
- package/dist/ui/operations/regions.html +6 -6
- package/dist/ui/operations/releases.html +6 -6
- package/dist/ui/operations/spend.html +6 -6
- package/dist/ui/operations/workloads.html +6 -6
- package/dist/ui/security.html +2 -2
- package/dist/ui/server/actions.html +6 -6
- package/dist/ui/server/activity.html +2 -2
- package/dist/ui/server/capacity.html +5 -5
- package/dist/ui/server/database.html +6 -6
- package/dist/ui/server/deployments.html +6 -6
- package/dist/ui/server/diagnostics.html +2 -2
- package/dist/ui/server/firewall.html +6 -6
- package/dist/ui/server/fleet.html +6 -6
- package/dist/ui/server/logs.html +6 -6
- package/dist/ui/server/metrics.html +6 -6
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +2 -2
- package/dist/ui/server/sites.html +5 -5
- package/dist/ui/server/ssh-keys.html +5 -5
- package/dist/ui/server/team.html +6 -6
- package/dist/ui/server/terminal.html +2 -2
- package/dist/ui/serverless/alarms.html +6 -6
- package/dist/ui/serverless/assets.html +2 -2
- package/dist/ui/serverless/cost.html +1 -1
- package/dist/ui/serverless/data.html +6 -6
- package/dist/ui/serverless/deployments.html +2 -2
- package/dist/ui/serverless/firewall.html +1 -1
- package/dist/ui/serverless/functions.html +6 -6
- package/dist/ui/serverless/logs.html +6 -6
- package/dist/ui/serverless/metrics.html +2 -2
- package/dist/ui/serverless/queues.html +6 -6
- package/dist/ui/serverless/secrets.html +6 -6
- package/dist/ui/serverless/traces.html +6 -6
- package/dist/ui/serverless.html +6 -6
- package/package.json +3 -3
- package/dist/bin/chunk-2r9748kw.js +0 -1
- package/dist/bin/chunk-483ppsv0.js +0 -1
- package/dist/bin/chunk-9fn41ztz.js +0 -1
- package/dist/bin/chunk-af8511dv.js +0 -1
- package/dist/bin/chunk-asvxjh7p.js +0 -1
- package/dist/bin/chunk-bphzq12c.js +0 -1
- package/dist/bin/chunk-c6tt4fj9.js +0 -1
- package/dist/bin/chunk-c6yrhzw5.js +0 -1
- package/dist/bin/chunk-cf2teb1b.js +0 -1
- package/dist/bin/chunk-dhb1xs5a.js +0 -1
- package/dist/bin/chunk-dwahpsj4.js +0 -1
- package/dist/bin/chunk-evbh466n.js +0 -1
- package/dist/bin/chunk-j9yq3b4d.js +0 -2
- package/dist/bin/chunk-n54ssj0h.js +0 -1
- package/dist/bin/chunk-nj4eg5vt.js +0 -1
- package/dist/bin/chunk-p6c2d6em.js +0 -1
- package/dist/bin/chunk-va3yvvs0.js +0 -1
- package/dist/bin/chunk-vy27rc50.js +0 -1
- package/dist/bin/chunk-y815amx5.js +0 -1
- package/dist/chunk-0c48hvb8.js +0 -1
- package/dist/chunk-8crqfe8x.js +0 -1
- package/dist/chunk-949tycxj.js +0 -1
- package/dist/chunk-s3pe89wj.js +0 -1
- package/dist/chunk-wxjd9gyb.js +0 -1
package/dist/bin/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import{C,qt,n,r,a,
|
|
2
|
+
import{C,qt,n,r,a,xr,e,u,i,s,I,d,Mo,c,hn,Ee,zs,Hs,Gs,qs,f}from"./chunk-qka0e661.js";import{pa,H,Br,A,B,Jo,l,o,T,ga,fa,ha,$e,ze,ot,Qo,wn,Fi,An,Yo,va,Ca,Ta,Ea,Aa,j,b,M,st,N,ye,es,Ge,Qt,Bi,Ie,ts,ji,Yt,kt,Ra,Ia}from"./chunk-v5js5pz6.js";import"./chunk-mjbd4pbz.js";import{Me,_,On,y,$n,Qi,Ln,ls,qr,ct,xe,Mn,Un,Bn,jn,Vn,zn,Hn,Gn,oe,or,sr,qn,Wr,Wn,Kn,Jn,Xn,Qn,Yn,Zn,ei,lt,ar,cr,Ot,Kr,lr,dr,Yi,$t,ti,de,ri,ni,ii,Jr,Xr,oi,Qr,ur,pr,dt,Lt,Mt,Yr,ue,si,pc,mc,mr,gc,G,Zi,qe,ut,pt,ai,Zr,ci,en,li,di,tn,ds,pe,ve,Ue,Ce,ui,gr}from"./chunk-370pghhq.js";import{p,aa}from"./chunk-b0r8a5h5.js";import{ce,h,Fr}from"./chunk-55j0x82p.js";import{Or,It,$r,Lr,Mr,Ur,vn}from"./chunk-qjxd8f5m.js";import{S,Ft,vi,Cs,Tr,Tc,Ec,wc,Ci,Ti,dn,Bt,un,yt,bt,Ze,Be,et,St,Er,je,tt,Ei,wi,rt,pn,mn,fo,yo,Rr,jt,vt,Vt,ee,Co,Ii,Pi,_s}from"./chunk-5gf0je0v.js";import{nt,zt,Pr,Ls,Ms,Dr,he,Di,kr,Ao,Ro,Io,Po,Do,ko,W,Ht,U,ki}from"./chunk-w2eezm9z.js";import{J,w,Uo,Xs,Qs,Wc,Kc,_i,At,Oi}from"./chunk-m60wwxz1.js";import{v}from"./chunk-5fde4d44.js";import{Q}from"./chunk-tdv5rwjv.js";import{Rt,g}from"./chunk-17ewx21n.js";import{Ke,us,me,eo,mi,to,ro,rn,no,io,Te,Z,Je,_e,nn,gt,Oe,fr,on,Xe,Qe,hr,sn,gi,fi,ge,ft,hi,br,Sr,vr,an,Cr,O,so,ao,yc,co,ps,ms,gs,fs,ht,hs,lo,uo,po}from"./chunk-szagab6z.js";import"./chunk-nxsx69p4.js";import{P}from"./chunk-e4c7ba0g.js";import{fe}from"./chunk-43a60zkj.js";import{K,Tt,$o}from"./chunk-jrxb5sg4.js";import{se,q,fc,hc,We}from"./chunk-pgbtdsj2.js";import{nr,xn,ir,Ji,_n,cs}from"./chunk-5t1ky9re.js";import"./chunk-x40sd2m9.js";import{E}from"./chunk-pr96tp0s.js";import{mt}from"./chunk-2247xk4n.js";import{te,we}from"./chunk-rgnmxfvp.js";import{V}from"./chunk-7y56qyrj.js";import"./chunk-zs1cpqhv.js";import{Xi}from"./chunk-6tqzgm9z.js";import"./chunk-wjn2bpdb.js";import"./chunk-1cb0r1j3.js";import{Si}from"./chunk-zz9nj5kn.js";import{Ir}from"./chunk-krt80e8w.js";import{Ct}from"./chunk-8escxm7h.js";import{F}from"./chunk-gga18c4g.js";import{x}from"./chunk-abk97qeq.js";import{existsSync as _u}from"node:fs";import Oa from"node:fs/promises";import Vl from"node:os";import La from"node:path";import mo from"node:process";import{EventEmitter as Mu}from"node:events";import zi from"node:process";import ql from"node:process";import Ba from"node:process";import Fs from"node:process";import Zo from"node:process";import Jl from"node:tty";import Pm,{stdin as sy,stdout as ay}from"node:process";class Yl{configPath;config=null;events=[];retryCount=0;maxRetries=3;retryDelayMs=1000;constructor(){let R=Vl.homedir(),t=La.join(R,".config","clapp");this.configPath=La.join(t,"telemetry.json")}async isEnabled(){if(mo.env.DO_NOT_TRACK==="1"||mo.env.DO_NOT_TRACK==="true")return!1;if(mo.env.NO_TELEMETRY==="1"||mo.env.NO_TELEMETRY==="true")return!1;return(await this.loadConfig()).enabled}async enable(){let R=await this.loadConfig();if(R.enabled=!0,!R.userId)R.userId=this.generateUserId();await this.saveConfig(R)}async disable(){let R=await this.loadConfig();R.enabled=!1,await this.saveConfig(R)}async track(R,t){if(!await this.isEnabled())return;let k={event:R,...t,timestamp:Date.now(),platform:Vl.platform(),nodeVersion:mo.version};if(this.events.push(k),this.events.length>=10)await this.send()}async trackCommand(R,t){await this.track("command",{command:R,duration:t})}async trackError(R,t){await this.track("error",{error:R,command:t})}async send(){if(!await this.isEnabled()||this.events.length===0)return!0;try{this.events=[],this.retryCount=0;let t=await this.loadConfig();return t.lastSent=Date.now(),await this.saveConfig(t),!0}catch{if(this.retryCount<this.maxRetries){this.retryCount++;let t=this.retryDelayMs*2**(this.retryCount-1);return await this.sleep(t),this.send()}return this.events=[],this.retryCount=0,!1}}async flush(){if(this.events.length===0)return!0;return this.send()}sleep(R){return new Promise((t)=>setTimeout(t,R))}async status(){let R=await this.loadConfig();return{enabled:R.enabled,doNotTrack:mo.env.DO_NOT_TRACK==="1"||mo.env.DO_NOT_TRACK==="true",eventsQueued:this.events.length,lastSent:R.lastSent}}async loadConfig(){if(this.config)return this.config;try{if(_u(this.configPath)){let R=await Oa.readFile(this.configPath,"utf-8");return this.config=JSON.parse(R),this.config}}catch{}return this.config={enabled:!1},this.config}async saveConfig(R){this.config=R;try{let t=La.dirname(this.configPath);await Oa.mkdir(t,{recursive:!0}),await Oa.writeFile(this.configPath,JSON.stringify(R,null,2),"utf-8")}catch{}}generateUserId(){let R=Math.random().toString(36).substring(2,15),t=Date.now().toString(36);return`${R}-${t}`}}var Gh=new Yl;function ja(R){return R.replace(/[<[].+/,"").trim()}function zu(R){let t=/<([^>]+)>/g,m=/\[([^\]]+)\]/g,k=[],D=(Y)=>{let X=!1,ne=Y[1];if(ne.startsWith("..."))ne=ne.slice(3),X=!0;return{required:Y[0].startsWith("<"),value:ne,variadic:X}},L;while(L=t.exec(R))k.push(D(L));let z;while(z=m.exec(R))k.push(D(z));return k}function Uu(R){let t={alias:{},boolean:[]};for(let[m,k]of R.entries()){if(k.names.length>1)t.alias[k.names[0]]=k.names.slice(1);if(k.isBoolean)if(k.negated){if(!R.some((L,z)=>z!==m&&L.names.some((Y)=>k.names.includes(Y))&&typeof L.required==="boolean"))t.boolean.push(k.names[0])}else t.boolean.push(k.names[0])}return t}function Hl(R){return R.reduce((t,m)=>t.length>=m.length?t:m,"")}function Fa(R,t){return R.length>=t?R:`${R}${" ".repeat(t-R.length)}`}function Os(R){return R.replace(/([a-z])-([a-z])/g,(t,m,k)=>m+k.toUpperCase())}function Wu(R,t,m){let k=0,D=t.length,L=R,z;for(;k<D;++k)z=L[t[k]],L=L[t[k]]=k===D-1?m:z!=null?z:!!~t[k+1].indexOf(".")||!(+t[k+1]>-1)?{}:[]}function Vu(R,t){for(let m of Object.keys(t)){let k=t[m];if(k.shouldTransform){if(R[m]=Array.prototype.concat.call([],R[m]),typeof k.transformFunction==="function")R[m]=R[m].map(k.transformFunction)}}}function qu(R){let t=/([^\\/]+)$/.exec(R);return t?t[1]:""}function Xo(R){return R.split(".").map((t,m)=>m===0?Os(t):t).join(".")}class ns extends Error{exitCode=2;isUsageError=!0;usage;constructor(R,t){super(R);if(this.name=this.constructor.name,t!==void 0)this.usage=t;if(typeof Error.captureStackTrace==="function")Error.captureStackTrace(this,this.constructor);else this.stack=Error(R).stack}format(R=!1){if(R&&this.stack)return`${this.message}
|
|
3
3
|
|
|
4
4
|
Stack trace:
|
|
5
|
-
${this.stack}`;return this.message}}function Hu(R){if(R instanceof ns)return!0;return!!R&&typeof R==="object"&&R.name==="ClappError"&&typeof R.message==="string"}function Gu(){let{env:R}=ql,{TERM:t,TERM_PROGRAM:m}=R;if(ql.platform!=="win32")return t!=="linux";return Boolean(R.WT_SESSION)||Boolean(R.TERMINUS_SUBLIME)||R.ConEmuTask==="{cmd::Cmder}"||m==="Terminus-Sublime"||m==="vscode"||t==="xterm-256color"||t==="alacritty"||t==="rxvt-unicode"||t==="rxvt-unicode-256color"||R.TERMINAL_EMULATOR==="JetBrains-JediTerm"}var js="\x1B",
|
|
5
|
+
${this.stack}`;return this.message}}function Hu(R){if(R instanceof ns)return!0;return!!R&&typeof R==="object"&&R.name==="ClappError"&&typeof R.message==="string"}function Gu(){let{env:R}=ql,{TERM:t,TERM_PROGRAM:m}=R;if(ql.platform!=="win32")return t!=="linux";return Boolean(R.WT_SESSION)||Boolean(R.TERMINUS_SUBLIME)||R.ConEmuTask==="{cmd::Cmder}"||m==="Terminus-Sublime"||m==="vscode"||t==="xterm-256color"||t==="alacritty"||t==="rxvt-unicode"||t==="rxvt-unicode-256color"||R.TERMINAL_EMULATOR==="JetBrains-JediTerm"}var js="\x1B",ln=`${js}[`;var Gl={to(R,t){if(!t)return`${ln}${R+1}G`;return`${ln}${t+1};${R+1}H`},move(R,t){let m="";if(R<0)m+=`${ln}${-R}D`;else if(R>0)m+=`${ln}${R}C`;if(t<0)m+=`${ln}${-t}A`;else if(t>0)m+=`${ln}${t}B`;return m},up:(R=1)=>`${ln}${R}A`,down:(R=1)=>`${ln}${R}B`,forward:(R=1)=>`${ln}${R}C`,backward:(R=1)=>`${ln}${R}D`,nextLine:(R=1)=>`${ln}E`.repeat(R),prevLine:(R=1)=>`${ln}F`.repeat(R),left:`${ln}G`,hide:`${ln}?25l`,show:`${ln}?25h`,save:`${js}7`,restore:`${js}8`};var Zh={screen:`${ln}2J`,up:(R=1)=>`${ln}1J`.repeat(R),down:(R=1)=>`${ln}J`.repeat(R),line:`${ln}2K`,lineEnd:`${ln}K`,lineStart:`${ln}1K`,lines(R){let t="";for(let m=0;m<R;m++)t+=this.line+(m<R-1?Gl.up():"");if(R)t+=Gl.left;return t}},Qh={screen:`${js}c`};function Ku(R,t){let m=[];for(let k=0;k<=t.length;k++)m[k]=[k];for(let k=0;k<=R.length;k++)m[0][k]=k;for(let k=1;k<=t.length;k++)for(let D=1;D<=R.length;D++)if(t.charAt(k-1)===R.charAt(D-1))m[k][D]=m[k-1][D-1];else m[k][D]=Math.min(m[k-1][D-1]+1,m[k][D-1]+1,m[k-1][D]+1);return m[t.length][R.length]}function Zl(R,t,m=2,k=3){return t.map((D)=>({cmd:D,distance:Ku(R,D)})).filter(({distance:D})=>D<=m).sort((D,L)=>D.distance-L.distance).slice(0,k).map(({cmd:D})=>D)}function Ju(R,t={}){let m={_:[]},k=t.alias||{},D=new Set(t.boolean||[]),L={};for(let X of Object.keys(k))for(let ne of k[X])L[ne]=X;for(let X of D)if(k[X])for(let ne of k[X])D.add(ne);function z(X,ne){if(X===void 0)return ne;if(typeof ne==="boolean")return ne;return Array.isArray(X)?[...X,ne]:[X,ne]}function Y(X,ne){let re=L[X]||X,ie=z(m[re],ne);if(m[re]=ie,k[re])for(let ae of k[re])m[ae]=ie;if(L[X]&&k[L[X]])for(let ae of k[L[X]])m[ae]=ie;m[X]=ie}for(let X=0;X<R.length;X++){let ne=R[X];if(ne==="--"){m._.push(...R.slice(X+1));break}if(ne.startsWith("--")){let re=ne.indexOf("=");if(re!==-1){let ie=Os(ne.slice(2,re)),ae=ne.slice(re+1);Y(ie,ae)}else{let ie=ne.slice(2);if(ie.startsWith("no-")){let Ae=Os(ie.slice(3));Y(Ae,!1);continue}let ae=Os(ie),be=L[ae]||ae;if(D.has(be)||D.has(ae))Y(ae,!0);else{let Ae=R[X+1];if(Ae!==void 0&&!Ae.startsWith("-"))Y(ae,Ae),X++;else Y(ae,!0)}}}else if(ne.startsWith("-")&&ne.length>1){let re=ne.slice(1);for(let ie=0;ie<re.length;ie++){let ae=re[ie],be=L[ae]||ae;if(ie===re.length-1&&!D.has(be)&&!D.has(ae)){let Ae=R[X+1];if(Ae!==void 0&&!Ae.startsWith("-"))Y(ae,Ae),X++;else Y(ae,!0)}else Y(ae,!0)}}else m._.push(ne)}return m}class Ql{rawName;description;name;names;isBoolean;required;config;negated;constructor(R,t,m){if(this.rawName=R,this.description=t,this.config=Object.assign({},m),R=R.replace(/\.\*/g,""),this.negated=!1,this.names=ja(R).split(",").map((k)=>{let D=k.trim().replace(/^-{1,2}/,"");if(D.startsWith("no-"))this.negated=!0,D=D.replace(/^no-/,"");return Xo(D)}).sort((k,D)=>k.length>D.length?1:-1),this.name=this.names[this.names.length-1],this.negated&&this.config.default==null)this.config.default=!0;if(R.includes("<"))this.required=!0;else if(R.includes("["))this.required=!1;else this.isBoolean=!0}}var ey=Ba.argv,Yu=`${Ba.platform}-${Ba.arch} bun-v${typeof Bun<"u"?Bun.version:"unknown"}`,Kl=Fs.argv,Zu=`${Fs.platform}-${Fs.arch} node-${Fs.version}`;class _a{rawName;description;config;cli;options;aliasNames;name;namespace;args;commandAction;usageText;versionNumber;examples;helpCallback;globalCommand;beforeHooks;afterHooks;middleware;constructor(R,t,m,k){this.rawName=R,this.description=t,this.config=m,this.cli=k,this.options=[],this.aliasNames=[],this.name=ja(R);let D=ja(R),L=D.indexOf(":");if(L>0)this.namespace=D.substring(0,L),this.name=D.substring(L+1);if(this.args=zu(R),this.examples=[],this.beforeHooks=[],this.afterHooks=[],this.middleware=[],!m)this.config={}}usage(R){return this.usageText=R,this}allowUnknownOptions(){return this.config.allowUnknownOptions=!0,this}ignoreOptionDefaultValue(){return this.config.ignoreOptionDefaultValue=!0,this}version(R,t="-v, --version"){return this.versionNumber=R,this.option(t,"Display version number"),this}example(R){return this.examples.push(R),this}option(R,t,m){let k=new Ql(R,t,m);return this.options.push(k),this}alias(R){return this.aliasNames.push(R),this}action(R){return this.commandAction=R,this}before(R){return this.beforeHooks.push(R),this}after(R){return this.afterHooks.push(R),this}use(R){return this.middleware.push(R),this}isMatched(R){if(this.aliasNames.includes(R))return!0;if(this.namespace)return`${this.namespace}:${this.name}`===R;return this.name===R}get isDefaultCommand(){return this.name===""||this.aliasNames.includes("!")}get isGlobalCommand(){return this instanceof Ma}get displayName(){return this.namespace?`${this.namespace}:${this.name}`:this.name}hasOption(R){let t=Xo(R.split(".")[0]);return!!this.options.find((m)=>m.names.some((k)=>Xo(k)===t))}outputHelp(){let{name:R,commands:t}=this.cli,{versionNumber:m,options:k,helpCallback:D}=this.cli.globalCommand,L=[{body:`${R}${m?`/${m}`:""}`}];if(L.push({title:"Usage",body:` $ ${R} ${this.usageText||this.rawName}`}),(this.isGlobalCommand||this.isDefaultCommand)&&t.length>0){let ne=Hl(t.map((Ae)=>Ae.rawName)),re=new Map,ie=[];for(let Ae of t)if(Ae.namespace){if(!re.has(Ae.namespace))re.set(Ae.namespace,[]);re.get(Ae.namespace).push(Ae)}else ie.push(Ae);let ae="";if(ie.sort((Ae,le)=>Ae.rawName<le.rawName?-1:Ae.rawName>le.rawName?1:0),ie.length>0)ae+=ie.map((Ae)=>` ${Fa(Ae.rawName,ne.length)} ${Ae.description}`).join(`
|
|
6
6
|
`);let be=Array.from(re.keys()).sort();for(let Ae of be){let le=re.get(Ae);if(ae.length>0)ae+=`
|
|
7
7
|
|
|
8
8
|
`;ae+=` ${Ae}:
|
|
@@ -28,7 +28,7 @@ Received ${m}, cleaning up...`),R)try{await R()}catch(k){console.error("Error du
|
|
|
28
28
|
`)),this.enableDidYouMean){let t=[];for(let k of this.commands){if(k.displayName)t.push(k.displayName);if(k.aliasNames)t.push(...k.aliasNames)}let m=Zl(R,t);if(m.length>0)console.log(Ko.yellow("Did you mean one of these?")),m.forEach((k)=>console.log(` ${Ko.dim("•")} ${this.name} ${k}`)),console.log("")}console.log(Ko.dim("Run"),`${this.name} --help`,Ko.dim("to see all available commands")),zi.exit(1)}async parse(R=Kl,t={}){let{run:m=!0,exitOnError:k=!1}=t;if(k)try{return await this.parse(R,{run:m})}catch(X){throw this.handleUsageError(X),X}if(this.rawArgs=R,!this.name)this.name=R[1]?qu(R[1]):"cli";let D=!0,L=R.slice(2),z=L[0];if(z&&!z.startsWith("-")){for(let X of this.commands)if(X.isMatched(z)){let ne=this.mri(L,X);D=!1;let re={...ne,args:ne.args.slice(1)};this.setParsedInfo(re,X,z),this.emit(`command:${z}`,X);break}if(D){let X=L[1];if(X&&!X.startsWith("-")){let ne=`${z}:${X}`;for(let re of this.commands)if(re.isMatched(ne)){let ie=this.mri(L,re);D=!1;let ae={...ie,args:ie.args.slice(2)};this.setParsedInfo(ae,re,ne),this.emit(`command:${ne}`,re);break}}}}if(D){for(let X of this.commands)if(X.name===""){D=!1;let ne=this.mri(L,X);this.setParsedInfo(ne,X),this.emit("command:!",X);break}}if(D){let X=this.mri(R.slice(2));this.setParsedInfo(X)}if(this.options.verbose)this.isVerbose=!0;if(this.options.quiet)this.isQuiet=!0;if(this.options.debug)this.isDebug=!0;if(this.options.noInteraction||this.options.interaction===!1)this.isNoInteraction=!0;if(this.options.env)this.environment=String(this.options.env);if(this.options.dryRun)this.isDryRun=!0;if(this.options.force)this.isForce=!0;if(this.options.noEmoji||this.options.emoji===!1)this.useEmoji=!1;if(this.options.theme)this.theme=String(this.options.theme);if(this.options.noCache||this.options.cache===!1)this.isNoCache=!0;if(this.options.help&&this.showHelpOnExit)this.outputHelp(),m=!1,this.unsetMatchedCommand();if(this.options.version&&this.showVersionOnExit&&this.matchedCommandName==null)this.outputVersion(),m=!1,this.unsetMatchedCommand();let Y={args:this.args,options:this.options};if(m)await this.runMatchedCommand();if(!this.matchedCommand&&this.args[0]){if(this.emit("command:*"),!(this.listenerCount("command:*")>0))this.showCommandNotFound(this.args[0])}return Y}mri(R,t){let m=[...this.globalCommand.options,...t?t.options:[]],k=Uu(m),D=[],L=R.indexOf("--");if(L>-1)D=R.slice(L+1),R=R.slice(0,L);let z=Ju(R,k),Y={_:z._};for(let ae of Object.keys(z))if(ae!=="_")Y[Xo(ae)]=z[ae];let X=Y._,ne={"--":D},re=t&&t.config.ignoreOptionDefaultValue?t.config.ignoreOptionDefaultValue:this.globalCommand.config.ignoreOptionDefaultValue,ie=Object.create(null);for(let ae of m){if(!re&&ae.config.default!==void 0)for(let be of ae.names)ne[be]=ae.config.default;if(Array.isArray(ae.config.type)){if(ie[ae.name]===void 0)ie[ae.name]={shouldTransform:!0,transformFunction:ae.config.type[0]}}}for(let ae of Object.keys(Y))if(ae!=="_"){let be=ae.split(".");Wu(ne,be,Y[ae]),Vu(ne,ie)}return{args:X,options:ne}}async run(R=Kl){return this.parse(R,{run:!0,exitOnError:!0})}handleUsageError(R){if(!Hu(R)||R.isUsageError===!1)return;let t=R.message||"command-line error",m=this.name?`${this.name}: `:"",k=/--help/.test(t)?"":R.usage?`
|
|
29
29
|
Usage: ${R.usage}`:`
|
|
30
30
|
Run \`${this.name??"cli"} --help\` for usage.`;zi.stderr.write(`${m}${t}${k}
|
|
31
|
-
`),zi.exit(R.exitCode??2)}async runMatchedCommand(){let{args:R,options:t,matchedCommand:m}=this;if(!m||!m.commandAction)return;m.checkUnknownOptions(),m.checkOptionValue(),m.checkRequiredArgs();let k=[];m.args.forEach((Y,X)=>{if(Y.variadic)k.push(R.slice(X));else k.push(R[X])}),k.push(t);let D={command:m,args:k,options:t};for(let Y of m.beforeHooks)await Y(D);let L,z=async()=>{let Y=m.commandAction.apply(this,k);if(Y instanceof Promise)L=await Y;else L=Y;return L};if(m.middleware.length>0){let Y=0,X=async()=>{if(Y<m.middleware.length){let ne=m.middleware[Y++];await ne({...D,next:X})}else await z()};await X()}else await z();for(let Y of m.afterHooks)await Y(D);return L}removeSignalHandlers(){if(!this.signalHandlersSet)return this;return zi.removeAllListeners("SIGINT"),zi.removeAllListeners("SIGTERM"),this.signalHandlersSet=!1,this}destroy(){this.removeSignalHandlers(),this.commands=[],this.rawArgs=[],this.args=[],this.options={},this.matchedCommand=void 0,this.matchedCommandName=void 0,this.removeAllListeners()}}class Xl{cache;enabled;cleanupInterval=null;hits=0;misses=0;constructor(){this.cache=new Map,this.enabled=!0,this.startCleanupInterval()}startCleanupInterval(){if(this.cleanupInterval)return;this.cleanupInterval=setInterval(()=>{this.cleanup()},30000),this.cleanupInterval.unref()}stopCleanup(){if(this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}isEnabled(){return this.enabled}get(R){if(!this.enabled){this.misses++;return}let t=this.cache.get(R);if(!t){this.misses++;return}if(Date.now()-t.timestamp>t.ttl){this.cache.delete(R),this.misses++;return}return this.hits++,t.value}set(R,t,m=5000){if(!this.enabled)return;this.cache.set(R,{value:t,timestamp:Date.now(),ttl:m})}has(R){if(!this.enabled)return!1;let t=this.cache.get(R);if(!t)return!1;if(Date.now()-t.timestamp>t.ttl)return this.cache.delete(R),!1;return!0}delete(R){this.cache.delete(R)}clear(){this.cache.clear()}disable(){this.enabled=!1,this.clear()}enable(){this.enabled=!0}stats(){return{size:this.cache.size,enabled:this.enabled,hits:this.hits,misses:this.misses}}resetStats(){this.hits=0,this.misses=0}keys(){return Array.from(this.cache.keys())}cleanup(){let R=Date.now();for(let[t,m]of this.cache.entries())if(R-m.timestamp>m.ttl)this.cache.delete(t)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var ny=new Xl;function tm(){if("FORCE_COLOR"in Zo.env)return Zo.env.FORCE_COLOR!=="0";if("NO_COLOR"in Zo.env||Zo.env.TERM==="dumb")return!1;if(Zo.platform==="win32")return!0;return Jl.isatty(1)&&Jl.isatty(2)}var ed=tm();function
|
|
31
|
+
`),zi.exit(R.exitCode??2)}async runMatchedCommand(){let{args:R,options:t,matchedCommand:m}=this;if(!m||!m.commandAction)return;m.checkUnknownOptions(),m.checkOptionValue(),m.checkRequiredArgs();let k=[];m.args.forEach((Y,X)=>{if(Y.variadic)k.push(R.slice(X));else k.push(R[X])}),k.push(t);let D={command:m,args:k,options:t};for(let Y of m.beforeHooks)await Y(D);let L,z=async()=>{let Y=m.commandAction.apply(this,k);if(Y instanceof Promise)L=await Y;else L=Y;return L};if(m.middleware.length>0){let Y=0,X=async()=>{if(Y<m.middleware.length){let ne=m.middleware[Y++];await ne({...D,next:X})}else await z()};await X()}else await z();for(let Y of m.afterHooks)await Y(D);return L}removeSignalHandlers(){if(!this.signalHandlersSet)return this;return zi.removeAllListeners("SIGINT"),zi.removeAllListeners("SIGTERM"),this.signalHandlersSet=!1,this}destroy(){this.removeSignalHandlers(),this.commands=[],this.rawArgs=[],this.args=[],this.options={},this.matchedCommand=void 0,this.matchedCommandName=void 0,this.removeAllListeners()}}class Xl{cache;enabled;cleanupInterval=null;hits=0;misses=0;constructor(){this.cache=new Map,this.enabled=!0,this.startCleanupInterval()}startCleanupInterval(){if(this.cleanupInterval)return;this.cleanupInterval=setInterval(()=>{this.cleanup()},30000),this.cleanupInterval.unref()}stopCleanup(){if(this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}isEnabled(){return this.enabled}get(R){if(!this.enabled){this.misses++;return}let t=this.cache.get(R);if(!t){this.misses++;return}if(Date.now()-t.timestamp>t.ttl){this.cache.delete(R),this.misses++;return}return this.hits++,t.value}set(R,t,m=5000){if(!this.enabled)return;this.cache.set(R,{value:t,timestamp:Date.now(),ttl:m})}has(R){if(!this.enabled)return!1;let t=this.cache.get(R);if(!t)return!1;if(Date.now()-t.timestamp>t.ttl)return this.cache.delete(R),!1;return!0}delete(R){this.cache.delete(R)}clear(){this.cache.clear()}disable(){this.enabled=!1,this.clear()}enable(){this.enabled=!0}stats(){return{size:this.cache.size,enabled:this.enabled,hits:this.hits,misses:this.misses}}resetStats(){this.hits=0,this.misses=0}keys(){return Array.from(this.cache.keys())}cleanup(){let R=Date.now();for(let[t,m]of this.cache.entries())if(R-m.timestamp>m.ttl)this.cache.delete(t)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var ny=new Xl;function tm(){if("FORCE_COLOR"in Zo.env)return Zo.env.FORCE_COLOR!=="0";if("NO_COLOR"in Zo.env||Zo.env.TERM==="dumb")return!1;if(Zo.platform==="win32")return!0;return Jl.isatty(1)&&Jl.isatty(2)}var ed=tm();function cn(R,t){if(!ed)return(m)=>m;return(m)=>R+m+t}var nm=cn("\x1B[0m","\x1B[0m"),rm=cn("\x1B[31m","\x1B[39m"),im=cn("\x1B[32m","\x1B[39m"),om=cn("\x1B[33m","\x1B[39m"),sm=cn("\x1B[34m","\x1B[39m"),am=cn("\x1B[35m","\x1B[39m"),cm=cn("\x1B[36m","\x1B[39m"),lm=cn("\x1B[37m","\x1B[39m"),dm=cn("\x1B[90m","\x1B[39m"),um=cn("\x1B[1m","\x1B[22m"),mm=cn("\x1B[3m","\x1B[23m"),pm=cn("\x1B[4m","\x1B[24m"),fm=cn("\x1B[2m","\x1B[22m"),gm=cn("\x1B[7m","\x1B[27m"),hm=cn("\x1B[8m","\x1B[28m"),ym=cn("\x1B[9m","\x1B[29m"),vm=cn("\x1B[41m","\x1B[49m"),bm=cn("\x1B[42m","\x1B[49m"),wm=cn("\x1B[43m","\x1B[49m"),Sm=cn("\x1B[44m","\x1B[49m"),Cm=cn("\x1B[45m","\x1B[49m"),$m=cn("\x1B[46m","\x1B[49m"),km=cn("\x1B[47m","\x1B[49m"),Am=ed,Rm={reset:nm,red:rm,green:im,yellow:om,blue:sm,magenta:am,cyan:cm,white:lm,gray:dm,bold:um,italic:mm,underline:pm,dim:fm,inverse:gm,hidden:hm,strikethrough:ym,bgRed:vm,bgGreen:bm,bgYellow:wm,bgBlue:Sm,bgMagenta:Cm,bgCyan:$m,bgWhite:km,isColorSupported:Am},Im=Rm,xm=["up","down","left","right","space","enter","cancel"],cy={actions:new Set(xm),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};var ly=Pm.platform.startsWith("win"),dy=Symbol("clapp:cancel");var Dm=Gu();var Xt=(R,t)=>Dm?R:t,uy=Xt("◆","*"),my=Xt("■","x"),py=Xt("▲","x"),fy=Xt("◇","o"),gy=Xt("┌","T"),Em=Xt("│","|"),hy=Xt("└","—"),yy=Xt("●",">"),vy=Xt("○"," "),by=Xt("◻","[•]"),wy=Xt("◼","[+]"),Sy=Xt("◻","[ ]"),Cy=Xt("▪","•"),$y=Xt("─","-"),ky=Xt("╮","+"),Ay=Xt("├","+"),Ry=Xt("╯","+"),Iy=Xt("●","•"),Py=Xt("◆","*"),xy=Xt("▲","!"),Dy=Xt("■","x");var Ey={light:Xt("─","-"),heavy:Xt("━","="),block:Xt("█","#")};function Tm(){return`${Im.gray(Em)} `}var Ty=Tm();var Ua="0.16.69";function td(R,t,m=process.env){let k=t?.options.find((z)=>z.name==="profile");if((!k||/\baws\b/i.test(k.description))&&typeof R.profile==="string"&&R.profile)m.AWS_PROFILE=R.profile,delete m.AWS_ACCESS_KEY_ID,delete m.AWS_SECRET_ACCESS_KEY,delete m.AWS_SESSION_TOKEN;if(!t?.options.some((z)=>z.name==="region")&&typeof R.region==="string"&&R.region)m.AWS_REGION=R.region,m.AWS_DEFAULT_REGION=R.region}import{existsSync as Wa}from"node:fs";import{mkdir as Om,writeFile as nd}from"node:fs/promises";function Va(R){R.command("init","Initialize a new ts-cloud project").option("--mode <mode>","Deployment mode: server or serverless").option("--name <name>","Project name").option("--region <region>","AWS Region").action(async(t)=>{if(i("Initializing ts-cloud Project"),Wa("cloud.config.ts")){if(!await d("cloud.config.ts already exists. Overwrite?",!1)){e("Initialization cancelled");return}}let m=t?.name||await I("Project name","my-app"),k=t?.mode||await Mo("Select deployment mode",["serverless","server"]),D=t?.region||await Mo("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),L=new s("Creating configuration file...");L.start();let z=`import { defineConfig } from '@ts-cloud/core'
|
|
32
32
|
|
|
33
33
|
export default defineConfig({
|
|
34
34
|
project: {
|
|
@@ -64,9 +64,9 @@ cloudformation/
|
|
|
64
64
|
Next steps:
|
|
65
65
|
1. Edit cloud.config.ts to configure your infrastructure
|
|
66
66
|
2. Run 'cloud generate' to create CloudFormation templates
|
|
67
|
-
3. Run 'cloud deploy' to deploy your infrastructure`,"green")}),R.command("init:server","Initialize server-based (EC2) project").action(async()=>{i("Initializing Server-Based Project"),await R.parse(["init","--mode","server"])}),R.command("init:serverless","Initialize serverless (Fargate/Lambda) project").action(async()=>{i("Initializing Serverless Project"),await R.parse(["init","--mode","serverless"])}),R.command("init:hybrid","Initialize hybrid project").action(async()=>{i("Initializing Hybrid Project"),await R.parse(["init","--mode","hybrid"])})}import{readFile as rd,writeFile as Lm}from"node:fs/promises";import{resolve as Bs}from"node:path";async function Ne(){let R=await Qi();if(!R.project)throw Error("Missing required project configuration in cloud.config.ts");return R}function yn(R){if(R)switch(R.toLowerCase()){case"porkbun":{let k=process.env.PORKBUN_API_KEY,D=process.env.PORKBUN_SECRET_KEY;if(!k||!D)throw Error("PORKBUN_API_KEY and PORKBUN_SECRET_KEY environment variables are required for Porkbun provider");return{provider:"porkbun",apiKey:k,secretKey:D}}case"godaddy":{let k=process.env.GODADDY_API_KEY,D=process.env.GODADDY_API_SECRET;if(!k||!D)throw Error("GODADDY_API_KEY and GODADDY_API_SECRET environment variables are required for GoDaddy provider");let L=process.env.GODADDY_ENVIRONMENT||"production";return{provider:"godaddy",apiKey:k,apiSecret:D,environment:L}}case"route53":{let k=process.env.AWS_REGION||"us-east-1",D=process.env.AWS_HOSTED_ZONE_ID;return{provider:"route53",region:k,hostedZoneId:D}}case"cloudflare":{let k=process.env.CLOUDFLARE_API_TOKEN;if(!k)throw Error("CLOUDFLARE_API_TOKEN environment variable is required for Cloudflare provider");return{provider:"cloudflare",apiToken:k,zoneId:process.env.CLOUDFLARE_ZONE_ID,accountId:process.env.CLOUDFLARE_ACCOUNT_ID}}default:throw Error(`Unknown DNS provider: ${R}. Supported: porkbun, godaddy, route53, cloudflare`)}if(new Di().loadFromEnv().getAllProviders().length===0)return null;if(process.env.PORKBUN_API_KEY&&process.env.PORKBUN_SECRET_KEY)return{provider:"porkbun",apiKey:process.env.PORKBUN_API_KEY,secretKey:process.env.PORKBUN_SECRET_KEY};if(process.env.GODADDY_API_KEY&&process.env.GODADDY_API_SECRET)return{provider:"godaddy",apiKey:process.env.GODADDY_API_KEY,apiSecret:process.env.GODADDY_API_SECRET,environment:process.env.GODADDY_ENVIRONMENT||"production"};if(process.env.AWS_ACCESS_KEY_ID||process.env.AWS_PROFILE||process.env.AWS_REGION)return{provider:"route53",region:process.env.AWS_REGION||"us-east-1",hostedZoneId:process.env.AWS_HOSTED_ZONE_ID};if(process.env.CLOUDFLARE_API_TOKEN)return{provider:"cloudflare",apiToken:process.env.CLOUDFLARE_API_TOKEN,zoneId:process.env.CLOUDFLARE_ZONE_ID,accountId:process.env.CLOUDFLARE_ACCOUNT_ID};return null}function Vr(R){let t=yn(R);if(!t)throw Error("No DNS provider configured. Set environment variables for Porkbun (PORKBUN_API_KEY, PORKBUN_SECRET_KEY), GoDaddy (GODADDY_API_KEY, GODADDY_API_SECRET), Cloudflare (CLOUDFLARE_API_TOKEN), or Route53 (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)");return U(t)}async function Fm(R){let t=await Ne(),m=y(process.cwd(),t),k=R??Object.keys(t.environments??{})[0]??"production",D=m.environments.get(k);if(!D)throw m.store.close(),Error(`Environment ${k} was not found.`);let L=t.project?.region??"us-east-1",z=_(process.cwd()),Y=new _r(m.store),X=new Mr(Y,{encryptionKey:z,backends:[new It(m.store,z),new Or(new K(L),L),new $r(new Ke(L),L),new Lr]}),ne=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return await Sn(X,m,t,ne.id),{config:t,controlPlane:m,env:k,environmentRecord:D,store:Y,service:X,actor:ne}}async function Hr(R,t){let m=await Fm(R);try{return await t(m)}finally{m.controlPlane.store.close()}}async function In(R){try{await R()}catch(t){r(t instanceof Error?t.message:String(t))}}function jm(R){return R.map((t)=>[t.key,t.kind,t.scope.type,t.inherited?"inherited":t.overridden?"override":"direct",t.kind==="secret"?t.backend??"restricted":t.value??"",t.backendVersion??"—",t.required?"required":"optional",t.updatedAt])}function id(R,t=process.env){if(R.value!=null)throw Error("Secret values are not accepted in command arguments; use --from-env or the interactive prompt.");if(!R.fromEnv)return;let m=t[R.fromEnv];if(!m)throw Error(`${R.fromEnv} is empty or unavailable.`);return m}function hr(R,t){let m=t.scope??"environment";if(m==="project")return{type:m,id:R.controlPlane.project.id};if(m==="environment"){let k=t.target?R.controlPlane.store.listEnvironments(R.controlPlane.project.id).find((D)=>D.id===t.target||D.slug===t.target):R.environmentRecord;if(!k)throw Error(`Environment ${t.target} was not found.`);return{type:m,id:k.id,environmentId:k.id}}if(m==="service"){let k=R.controlPlane.store.listResources(R.controlPlane.project.id),D=k.find((L)=>L.id===t.target||L.slug===t.target);if(!D)throw Error("Service scope requires --target with a resource ID or slug.");return{type:m,id:D.id,environmentId:D.environmentId,resourceId:D.id}}if(m==="function"){let k=R.controlPlane.store.listResources(R.controlPlane.project.id),D=k.find((L)=>L.kind==="function"&&(L.id===t.target||L.slug===t.target));if(!D)throw Error("Function scope requires --target with a function resource ID or slug.");return{type:m,id:D.id,environmentId:D.environmentId,resourceId:D.id}}if(m==="preview"){let k=new Be(R.controlPlane.store).listInstances({projectId:R.controlPlane.project.id}).find((D)=>D.id===t.target||D.name===t.target);if(!k)throw Error("Preview scope requires --target with a preview ID or name.");return{type:m,id:k.id,environmentId:k.baseEnvironmentId,resourceId:k.resourceId,previewId:k.id}}throw Error("--scope must be project, environment, service, function, or preview.")}function qa(R){let t=R??"local_encrypted";if(!["local_encrypted","aws_secrets_manager","aws_ssm","external"].includes(t))throw Error("--backend must be local_encrypted, aws_secrets_manager, aws_ssm, or external.");return t}function To(R,t,m){let k=R.store.find(R.controlPlane.project.id,m,t);if(!k)throw Error(`${t} was not found in the selected scope.`);return k}async function Bm(R,t){if(R==="secret")return id(t)??await I("Enter secret value (input is write-only)","");if(t.fromEnv){let m=process.env[t.fromEnv];if(m==null)throw Error(`${t.fromEnv} is unavailable.`);return m}if(t.value!=null)return t.value;return await I("Enter variable value","")}function ui(R){return R.option("--env <environment>","Dashboard environment").option("--scope <scope>","project, environment, service, function, or preview",{default:"environment"}).option("--target <id>","Environment, resource, function, or preview ID/name")}function Ha(R){R.command("config","Show validated file configuration").action(async()=>In(async()=>{let t=await Ne();console.log(JSON.stringify(t,null,2))})),R.command("config:validate","Validate configuration file").action(async()=>In(async()=>{let t=await Ne();if(!t.project?.name||!t.project?.slug||!t.mode)throw Error("project.name, project.slug, and mode are required.");n(`Configuration is valid: ${t.project.name} · ${t.mode} · ${t.project.region||"us-east-1"}`)})),ui(R.command("config:list","List scoped variables and write-only secret metadata")).option("--kind <kind>","variable or secret").option("--search <text>","Filter key names").option("--json","Print structured JSON").action(async(t)=>In(async()=>Hr(t.env,async(m)=>{let k=hr(m,t),D=["variable","secret"].includes(t.kind??"")?t.kind:void 0,L=m.service.list({projectId:m.controlPlane.project.id,scope:k,kind:D,search:t.search,canReadSecretMetadata:!0});if(t.json)console.log(JSON.stringify(L,null,2));else c(["Key","Kind","Scope","Precedence","Value/backend","Backend version","Requirement","Updated"],jm(L))}))),ui(R.command("config:set <key>","Create or replace a scoped variable or secret")).option("--value <value>","Plaintext variable value only").option("--from-env <name>","Read the value from an environment variable").option("--secret","Store a write-only secret").option("--backend <backend>","local_encrypted, aws_secrets_manager, aws_ssm, or external").option("--reference <uri>","External provider reference").option("--required","Fail deployments when unavailable").option("--confirm <key>","Exact key confirmation for production replacements").option("--json","Print structured JSON").action(async(t,m)=>In(async()=>Hr(m.env,async(k)=>{let D=hr(k,m),L=m.secret?"secret":"variable",z=L==="secret"?qa(m.backend):void 0,Y=z==="external"?void 0:await Bm(L,m),X=await k.service.set({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,scope:D,key:t,kind:L,value:Y,reference:m.reference,backend:z,required:!!m.required,confirmed:m.confirm===t,actorId:k.actor.id,idempotencyKey:`cli:${crypto.randomUUID()}`});if(m.json)console.log(JSON.stringify({entry:k.service.list({projectId:k.controlPlane.project.id,scope:D,canReadSecretMetadata:!0}).find((ne)=>ne.id===X.entry.id),mutation:X.mutation,warnings:X.warnings},null,2));else n(`${t}: ${X.mutation.added.length?"created":X.mutation.changed.length?"updated":"unchanged"}; ${X.mutation.affectedResourceIds.length} affected service(s).`)}))),ui(R.command("config:delete <key>","Delete a scoped variable or secret")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>In(async()=>Hr(m.env,async(k)=>{let D=To(k,t,hr(k,m));await k.service.remove({entryId:D.id,expectedVersion:D.version,confirmed:m.confirm===t,actorId:k.actor.id}),n(`Deleted ${t}.`)}))),ui(R.command("config:import <file>","Validate and import a dotenv file")).option("--secret-keys <keys>","Comma-separated secret keys").option("--backend <backend>","Secret backend").option("--confirm <word>","Use import for production replacement confirmation").option("--json","Print structured JSON").action(async(t,m)=>In(async()=>Hr(m.env,async(k)=>{let D=await k.service.importDotenv({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,scope:hr(k,m),source:await rd(Bs(t),"utf8"),secretKeys:m.secretKeys?.split(",").map((L)=>L.trim()).filter(Boolean),backend:qa(m.backend),confirmed:m.confirm==="import",actorId:k.actor.id,idempotencyKey:`cli-import:${crypto.randomUUID()}`});if(!D.document.valid)throw Error(D.document.diagnostics.map((L)=>`line ${L.line}: ${L.message}`).join(`
|
|
68
|
-
`));if(m.json)console.log(JSON.stringify({mutation:D.mutation,diagnostics:D.document.diagnostics,warnings:D.warnings},null,2));else n(`Imported ${D.mutation?.added.length??0} new, ${D.mutation?.changed.length??0} changed, and ${D.mutation?.unchanged.length??0} unchanged entries.`)}))),
|
|
69
|
-
`));let z=k.service.plan({projectId:k.controlPlane.project.id,scope:
|
|
67
|
+
3. Run 'cloud deploy' to deploy your infrastructure`,"green")}),R.command("init:server","Initialize server-based (EC2) project").action(async()=>{i("Initializing Server-Based Project"),await R.parse(["init","--mode","server"])}),R.command("init:serverless","Initialize serverless (Fargate/Lambda) project").action(async()=>{i("Initializing Serverless Project"),await R.parse(["init","--mode","serverless"])}),R.command("init:hybrid","Initialize hybrid project").action(async()=>{i("Initializing Hybrid Project"),await R.parse(["init","--mode","hybrid"])})}import{readFile as rd,writeFile as Lm}from"node:fs/promises";import{resolve as Bs}from"node:path";async function Ne(){let R=await Qi();if(!R.project)throw Error("Missing required project configuration in cloud.config.ts");return R}function bn(R){if(R)switch(R.toLowerCase()){case"porkbun":{let k=process.env.PORKBUN_API_KEY,D=process.env.PORKBUN_SECRET_KEY;if(!k||!D)throw Error("PORKBUN_API_KEY and PORKBUN_SECRET_KEY environment variables are required for Porkbun provider");return{provider:"porkbun",apiKey:k,secretKey:D}}case"godaddy":{let k=process.env.GODADDY_API_KEY,D=process.env.GODADDY_API_SECRET;if(!k||!D)throw Error("GODADDY_API_KEY and GODADDY_API_SECRET environment variables are required for GoDaddy provider");let L=process.env.GODADDY_ENVIRONMENT||"production";return{provider:"godaddy",apiKey:k,apiSecret:D,environment:L}}case"route53":{let k=process.env.AWS_REGION||"us-east-1",D=process.env.AWS_HOSTED_ZONE_ID;return{provider:"route53",region:k,hostedZoneId:D}}case"cloudflare":{let k=process.env.CLOUDFLARE_API_TOKEN;if(!k)throw Error("CLOUDFLARE_API_TOKEN environment variable is required for Cloudflare provider");return{provider:"cloudflare",apiToken:k,zoneId:process.env.CLOUDFLARE_ZONE_ID,accountId:process.env.CLOUDFLARE_ACCOUNT_ID}}default:throw Error(`Unknown DNS provider: ${R}. Supported: porkbun, godaddy, route53, cloudflare`)}if(new ki().loadFromEnv().getAllProviders().length===0)return null;if(process.env.PORKBUN_API_KEY&&process.env.PORKBUN_SECRET_KEY)return{provider:"porkbun",apiKey:process.env.PORKBUN_API_KEY,secretKey:process.env.PORKBUN_SECRET_KEY};if(process.env.GODADDY_API_KEY&&process.env.GODADDY_API_SECRET)return{provider:"godaddy",apiKey:process.env.GODADDY_API_KEY,apiSecret:process.env.GODADDY_API_SECRET,environment:process.env.GODADDY_ENVIRONMENT||"production"};if(process.env.AWS_ACCESS_KEY_ID||process.env.AWS_PROFILE||process.env.AWS_REGION)return{provider:"route53",region:process.env.AWS_REGION||"us-east-1",hostedZoneId:process.env.AWS_HOSTED_ZONE_ID};if(process.env.CLOUDFLARE_API_TOKEN)return{provider:"cloudflare",apiToken:process.env.CLOUDFLARE_API_TOKEN,zoneId:process.env.CLOUDFLARE_ZONE_ID,accountId:process.env.CLOUDFLARE_ACCOUNT_ID};return null}function Vr(R){let t=bn(R);if(!t)throw Error("No DNS provider configured. Set environment variables for Porkbun (PORKBUN_API_KEY, PORKBUN_SECRET_KEY), GoDaddy (GODADDY_API_KEY, GODADDY_API_SECRET), Cloudflare (CLOUDFLARE_API_TOKEN), or Route53 (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)");return U(t)}async function Fm(R){let t=await Ne(),m=y(process.cwd(),t),k=R??Object.keys(t.environments??{})[0]??"production",D=m.environments.get(k);if(!D)throw m.store.close(),Error(`Environment ${k} was not found.`);let L=t.project?.region??"us-east-1",z=_(process.cwd()),Y=new Or(m.store),X=new Ur(Y,{encryptionKey:z,backends:[new It(m.store,z),new $r(new K(L),L),new Lr(new Ke(L),L),new Mr]}),ne=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return await vn(X,m,t,ne.id),{config:t,controlPlane:m,env:k,environmentRecord:D,store:Y,service:X,actor:ne}}async function Hr(R,t){let m=await Fm(R);try{return await t(m)}finally{m.controlPlane.store.close()}}async function Dn(R){try{await R()}catch(t){r(t instanceof Error?t.message:String(t))}}function jm(R){return R.map((t)=>[t.key,t.kind,t.scope.type,t.inherited?"inherited":t.overridden?"override":"direct",t.kind==="secret"?t.backend??"restricted":t.value??"",t.backendVersion??"—",t.required?"required":"optional",t.updatedAt])}function id(R,t=process.env){if(R.value!=null)throw Error("Secret values are not accepted in command arguments; use --from-env or the interactive prompt.");if(!R.fromEnv)return;let m=t[R.fromEnv];if(!m)throw Error(`${R.fromEnv} is empty or unavailable.`);return m}function rr(R,t){let m=t.scope??"environment";if(m==="project")return{type:m,id:R.controlPlane.project.id};if(m==="environment"){let k=t.target?R.controlPlane.store.listEnvironments(R.controlPlane.project.id).find((D)=>D.id===t.target||D.slug===t.target):R.environmentRecord;if(!k)throw Error(`Environment ${t.target} was not found.`);return{type:m,id:k.id,environmentId:k.id}}if(m==="service"){let k=R.controlPlane.store.listResources(R.controlPlane.project.id),D=k.find((L)=>L.id===t.target||L.slug===t.target);if(!D)throw Error("Service scope requires --target with a resource ID or slug.");return{type:m,id:D.id,environmentId:D.environmentId,resourceId:D.id}}if(m==="function"){let k=R.controlPlane.store.listResources(R.controlPlane.project.id),D=k.find((L)=>L.kind==="function"&&(L.id===t.target||L.slug===t.target));if(!D)throw Error("Function scope requires --target with a function resource ID or slug.");return{type:m,id:D.id,environmentId:D.environmentId,resourceId:D.id}}if(m==="preview"){let k=new Be(R.controlPlane.store).listInstances({projectId:R.controlPlane.project.id}).find((D)=>D.id===t.target||D.name===t.target);if(!k)throw Error("Preview scope requires --target with a preview ID or name.");return{type:m,id:k.id,environmentId:k.baseEnvironmentId,resourceId:k.resourceId,previewId:k.id}}throw Error("--scope must be project, environment, service, function, or preview.")}function qa(R){let t=R??"local_encrypted";if(!["local_encrypted","aws_secrets_manager","aws_ssm","external"].includes(t))throw Error("--backend must be local_encrypted, aws_secrets_manager, aws_ssm, or external.");return t}function To(R,t,m){let k=R.store.find(R.controlPlane.project.id,m,t);if(!k)throw Error(`${t} was not found in the selected scope.`);return k}async function Bm(R,t){if(R==="secret")return id(t)??await I("Enter secret value (input is write-only)","");if(t.fromEnv){let m=process.env[t.fromEnv];if(m==null)throw Error(`${t.fromEnv} is unavailable.`);return m}if(t.value!=null)return t.value;return await I("Enter variable value","")}function Gr(R){return R.option("--env <environment>","Dashboard environment").option("--scope <scope>","project, environment, service, function, or preview",{default:"environment"}).option("--target <id>","Environment, resource, function, or preview ID/name")}function Ha(R){R.command("config","Show validated file configuration").action(async()=>Dn(async()=>{let t=await Ne();console.log(JSON.stringify(t,null,2))})),R.command("config:validate","Validate configuration file").action(async()=>Dn(async()=>{let t=await Ne();if(!t.project?.name||!t.project?.slug||!t.mode)throw Error("project.name, project.slug, and mode are required.");n(`Configuration is valid: ${t.project.name} · ${t.mode} · ${t.project.region||"us-east-1"}`)})),Gr(R.command("config:list","List scoped variables and write-only secret metadata")).option("--kind <kind>","variable or secret").option("--search <text>","Filter key names").option("--json","Print structured JSON").action(async(t)=>Dn(async()=>Hr(t.env,async(m)=>{let k=rr(m,t),D=["variable","secret"].includes(t.kind??"")?t.kind:void 0,L=m.service.list({projectId:m.controlPlane.project.id,scope:k,kind:D,search:t.search,canReadSecretMetadata:!0});if(t.json)console.log(JSON.stringify(L,null,2));else c(["Key","Kind","Scope","Precedence","Value/backend","Backend version","Requirement","Updated"],jm(L))}))),Gr(R.command("config:set <key>","Create or replace a scoped variable or secret")).option("--value <value>","Plaintext variable value only").option("--from-env <name>","Read the value from an environment variable").option("--secret","Store a write-only secret").option("--backend <backend>","local_encrypted, aws_secrets_manager, aws_ssm, or external").option("--reference <uri>","External provider reference").option("--required","Fail deployments when unavailable").option("--confirm <key>","Exact key confirmation for production replacements").option("--json","Print structured JSON").action(async(t,m)=>Dn(async()=>Hr(m.env,async(k)=>{let D=rr(k,m),L=m.secret?"secret":"variable",z=L==="secret"?qa(m.backend):void 0,Y=z==="external"?void 0:await Bm(L,m),X=await k.service.set({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,scope:D,key:t,kind:L,value:Y,reference:m.reference,backend:z,required:!!m.required,confirmed:m.confirm===t,actorId:k.actor.id,idempotencyKey:`cli:${crypto.randomUUID()}`});if(m.json)console.log(JSON.stringify({entry:k.service.list({projectId:k.controlPlane.project.id,scope:D,canReadSecretMetadata:!0}).find((ne)=>ne.id===X.entry.id),mutation:X.mutation,warnings:X.warnings},null,2));else n(`${t}: ${X.mutation.added.length?"created":X.mutation.changed.length?"updated":"unchanged"}; ${X.mutation.affectedResourceIds.length} affected service(s).`)}))),Gr(R.command("config:delete <key>","Delete a scoped variable or secret")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>Dn(async()=>Hr(m.env,async(k)=>{let D=To(k,t,rr(k,m));await k.service.remove({entryId:D.id,expectedVersion:D.version,confirmed:m.confirm===t,actorId:k.actor.id}),n(`Deleted ${t}.`)}))),Gr(R.command("config:import <file>","Validate and import a dotenv file")).option("--secret-keys <keys>","Comma-separated secret keys").option("--backend <backend>","Secret backend").option("--confirm <word>","Use import for production replacement confirmation").option("--json","Print structured JSON").action(async(t,m)=>Dn(async()=>Hr(m.env,async(k)=>{let D=await k.service.importDotenv({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,scope:rr(k,m),source:await rd(Bs(t),"utf8"),secretKeys:m.secretKeys?.split(",").map((L)=>L.trim()).filter(Boolean),backend:qa(m.backend),confirmed:m.confirm==="import",actorId:k.actor.id,idempotencyKey:`cli-import:${crypto.randomUUID()}`});if(!D.document.valid)throw Error(D.document.diagnostics.map((L)=>`line ${L.line}: ${L.message}`).join(`
|
|
68
|
+
`));if(m.json)console.log(JSON.stringify({mutation:D.mutation,diagnostics:D.document.diagnostics,warnings:D.warnings},null,2));else n(`Imported ${D.mutation?.added.length??0} new, ${D.mutation?.changed.length??0} changed, and ${D.mutation?.unchanged.length??0} unchanged entries.`)}))),Gr(R.command("config:plan <file>","Preview dotenv drift using key names only")).option("--remove-missing","Include managed keys absent from the file").option("--json","Print structured JSON").action(async(t,m)=>Dn(async()=>Hr(m.env,async(k)=>{let{parseDotenv:D}=await import("./chunk-7d83f6sn.js"),L=D(await rd(Bs(t),"utf8"));if(!L.valid)throw Error(L.diagnostics.map((Y)=>`line ${Y.line}: ${Y.message}`).join(`
|
|
69
|
+
`));let z=k.service.plan({projectId:k.controlPlane.project.id,scope:rr(k,m),values:L.values,removeMissing:!!m.removeMissing});if(m.json)console.log(JSON.stringify(z,null,2));else c(["Action","Keys"],[["Add",z.added.join(", ")||"—"],["Change",z.changed.join(", ")||"—"],["Remove",z.removed.join(", ")||"—"],["Unchanged",z.unchanged.join(", ")||"—"]]),e(`${z.affectedResourceIds.length} affected service(s); no secret values were printed.`)}))),Gr(R.command("config:export [file]","Export plaintext variables only as dotenv")).action(async(t,m)=>Dn(async()=>Hr(m.env,async(k)=>{let D=k.service.exportVariables({projectId:k.controlPlane.project.id,scope:rr(k,m)});if(t)await Lm(Bs(t),D,{mode:384}),n(`Exported variables to ${Bs(t)}; secrets were excluded.`);else process.stdout.write(D)}))),Gr(R.command("config:transfer <key>","Copy or move an entry between scopes")).option("--to-scope <scope>","Destination scope").option("--to-target <id>","Destination ID or slug").option("--move","Remove the source after copying").option("--confirm <key>","Exact key confirmation").action(async(t,m)=>Dn(async()=>Hr(m.env,async(k)=>{let D=rr(k,m),L=To(k,t,D),z=rr(k,{env:m.env,scope:m.toScope,target:m.toTarget}),Y=await k.service.transfer({entryId:L.id,targetScope:z,mode:m.move?"move":"copy",confirmed:m.confirm===t,actorId:k.actor.id,idempotencyKey:`cli-transfer:${crypto.randomUUID()}`});n(`${t} ${m.move?"moved":"copied"}; ${Y.mutation.affectedResourceIds.length} affected service(s).`)}))),Gr(R.command("config:reveal <key>","Reveal a secret after exact confirmation")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>Dn(async()=>Hr(m.env,async(k)=>{if(m.confirm!==t)throw Error(`Use --confirm ${t} to reveal this secret.`);let D=To(k,t,rr(k,m));console.log(await k.service.reveal({entryId:D.id,canRevealSecrets:!0,recentlyAuthenticated:!0,actorId:k.actor.id}))}))),Gr(R.command("config:env","Backward-compatible variable management")).option("--list","List variables").option("--set <key=value>","Set a variable").option("--unset <key>","Delete a variable").option("--environment <environment>","Target environment").action(async(t)=>Dn(async()=>Hr(t.environment??t.env,async(m)=>{let k=rr(m,{env:t.environment,scope:"environment",target:t.environment});if(t.list)c(["Key","Value","Updated"],m.service.list({projectId:m.controlPlane.project.id,scope:k,kind:"variable"}).map((D)=>[D.key,D.value??"",D.updatedAt]));else if(t.set){let D=t.set.indexOf("=");if(D<1)throw Error("Use --set KEY=VALUE.");let L=t.set.slice(0,D),z=t.set.slice(D+1);await m.service.set({organizationId:m.controlPlane.organization.id,projectId:m.controlPlane.project.id,scope:k,key:L,kind:"variable",value:z,confirmed:!1,actorId:m.actor.id}),n(`Set ${L}.`)}else if(t.unset){let D=To(m,t.unset,k);await m.service.remove({entryId:D.id,expectedVersion:D.version,confirmed:await d(`Delete ${D.key}?`,!1),actorId:m.actor.id}),n(`Deleted ${D.key}.`)}else e("Use --list, --set KEY=VALUE, or --unset KEY.")}))),Gr(R.command("config:secrets","Backward-compatible write-only secret management")).option("--list","List secret metadata").option("--create <key>","Create a secret").option("--update <key>","Rotate a secret").option("--get <key>","Reveal a secret").option("--delete <key>","Delete a secret").option("--from-env <name>","Read new value from an environment variable").option("--backend <backend>","Secret backend").option("--confirm <key>","Exact key confirmation").action(async(t)=>Dn(async()=>Hr(t.env,async(m)=>{let k=rr(m,t);if(t.list)c(["Key","Backend","Version","Rotated"],m.service.list({projectId:m.controlPlane.project.id,scope:k,kind:"secret",canReadSecretMetadata:!0}).map((D)=>[D.key,D.backend??"restricted",D.backendVersion??"—",D.rotatedAt??"never"]));else if(t.create||t.update){let D=t.create??t.update,L=id(t)??await I("Enter secret value (write-only)","");await m.service.set({organizationId:m.controlPlane.organization.id,projectId:m.controlPlane.project.id,scope:k,key:D,kind:"secret",value:L,backend:qa(t.backend),confirmed:t.confirm===D,actorId:m.actor.id}),n(`${D} ${t.update?"rotated":"created"}.`)}else if(t.get){if(t.confirm!==t.get)throw Error(`Use --confirm ${t.get} to reveal.`);let D=To(m,t.get,k);console.log(await m.service.reveal({entryId:D.id,canRevealSecrets:!0,recentlyAuthenticated:!0,actorId:m.actor.id}))}else if(t.delete){let D=To(m,t.delete,k);await m.service.remove({entryId:D.id,expectedVersion:D.version,confirmed:t.confirm===D.key,actorId:m.actor.id}),n(`Deleted ${D.key}.`)}else e("Use --list, --create, --update, --get, or --delete.")})))}import{existsSync as Zm}from"node:fs";import{readFile as Qm,writeFile as Xm}from"node:fs/promises";async function Us(R){let t=[];for(let m of R.steps)try{t.push({step:m,state:await m.satisfied()?"satisfied":"pending"})}catch(k){t.push({step:m,state:"unknown",reason:k instanceof Error?k.message:String(k)})}return t}function od(R){return R.filter((t)=>t.state!=="satisfied")}function _m(R){return od(R).some((t)=>t.step.destructive===!0)}function Ws(R,t){let m=od(t),k=[`${R.operation} ${R.target}`];if(m.length===0)return k.push(" Nothing to do — every step is already satisfied."),k;for(let{step:L,state:z,reason:Y}of t){let X=z==="satisfied"?"ok ":z==="unknown"?"? ":"→ ",ne=[L.destructive?"DESTRUCTIVE":"",z==="satisfied"?"already done":""].filter(Boolean);if(k.push(` ${X}${L.title}${ne.length>0?` [${ne.join(", ")}]`:""}`),L.change)k.push(` ${L.change.from} → ${L.change.to}`);if(Y)k.push(` could not check: ${Y}`)}let D=m.filter((L)=>L.step.destructive).length;return k.push(` ${m.length} step(s) would run`+(D>0?`, ${D} irreversible — re-run with --confirm ${R.target}`:"")),k}async function Vs(R,t,m={}){let k={operation:R.operation,target:R.target,steps:[],success:!0};if(_m(t)&&m.confirm!==R.target)throw Error(`${R.operation} includes an irreversible step. Re-run with --confirm ${R.target} to authorize it.`);for(let{step:D,state:L}of t){if(L==="satisfied"){k.steps.push({id:D.id,title:D.title,state:"skipped"}),m.log?.(`skip ${D.title} (already done)`);continue}m.audit?.({operation:R.operation,target:R.target,step:D.id,state:"started"});try{await D.apply(),k.steps.push({id:D.id,title:D.title,state:"applied"}),m.log?.(`done ${D.title}`),m.audit?.({operation:R.operation,target:R.target,step:D.id,state:"applied"})}catch(z){let Y=z instanceof Error?z.message:String(z);return k.steps.push({id:D.id,title:D.title,state:"failed",error:Y}),k.success=!1,m.log?.(`FAIL ${D.title}: ${Y}`),m.audit?.({operation:R.operation,target:R.target,step:D.id,state:"failed",error:Y}),k}}return k}function No(R,t){return`/tmp/ts-cloud-move-${R}-${t}.tar.gz`}function sd(R,t){return`${R}-${t}*.service`}function Ut(R){return`'${R.split("'").join("'\\''")}'`}function Mm(R,t){return["set -eu",`for unit in $(ls /etc/systemd/system/ 2>/dev/null | grep -E ${Ut(ad(R,t))} || true); do`,' systemctl stop "$unit" 2>/dev/null || true',"done"].join(`
|
|
70
70
|
`)}function ad(R,t){return`^${R}-${t}-((queue|daemon)-.*|scheduler)\\.service$`}function zm(R,t){return cd(ad(R,t))}function cd(R){return[`ls /etc/systemd/system/ 2>/dev/null | grep -E ${Ut(R)} | while read -r unit; do`,' systemctl is-active "$unit" >/dev/null 2>&1 && echo "active:$unit" || true',"done"].join(`
|
|
71
71
|
`)}function Um(R){return!R.split(`
|
|
72
72
|
`).some((t)=>t.trim().startsWith("active:"))}function Wm(R,t,m){let k=No(R,t);return["set -eu",`test -d ${Ut(m)} || { echo "no site tree at ${m}" >&2; exit 1; }`,`rm -f ${Ut(k)}`,`tar czf ${Ut(k)} -C "$(dirname ${Ut(m)})" "$(basename ${Ut(m)})" -C /etc/systemd/system $(cd /etc/systemd/system && ls ${sd(R,t)} 2>/dev/null | tr '\\n' ' ')`,`sha256sum ${Ut(k)} | cut -d' ' -f1`].join(`
|
|
@@ -79,12 +79,12 @@ Next steps:
|
|
|
79
79
|
`)}function Ga(R){let t=new Map;for(let m of R.split(`
|
|
80
80
|
`)){let k=m.trim();if(!k.startsWith("cert:"))continue;let[,D,L]=k.split(":");if(D&&L)t.set(D,L)}return t}function md(R,t){for(let[m,k]of R){if(k==="absent")continue;if(t.get(m)!==k)return!1}return!0}function pd(R,t,m){let k=t.flatMap((D)=>[`${D}.crt`,`${D}.key`]);return["set -eu",`rm -f ${Ut(m)}`,`cd ${Ut(R)} 2>/dev/null || { echo "no certs dir at ${R}" >&2; exit 0; }`,`TS_CLOUD_CERTS="$(ls ${k.map(Ut).join(" ")} 2>/dev/null | tr '\\n' ' ' || true)"`,'[ -n "$TS_CLOUD_CERTS" ] || { echo "no certificates to carry"; exit 0; }',`tar czf ${Ut(m)} $TS_CLOUD_CERTS`].join(`
|
|
81
81
|
`)}function fd(R,t){return["set -eu",`[ -s ${Ut(t)} ] || { echo "no certificate archive staged"; exit 0; }`,`mkdir -p ${Ut(R)}`,`tar xzf ${Ut(t)} -C ${Ut(R)}`,`chmod 600 ${Ut(R)}/*.key 2>/dev/null || true`,`rm -f ${Ut(t)}`].join(`
|
|
82
|
-
`)}async function gd(R,t){let{slug:m,siteName:k,appBase:D,from:L,to:z,targetAddress:Y}=R;if(L===z)throw Error(`'${k}' is already on ${z}.`);if(R.database&&!t.database)throw Error(`'${k}' uses the on-box database '${R.database.name}', which lives in the engine's own data directory rather than in the site tree, and this move has no way to carry it. Moving the site without it would cut traffic over to an empty database.`);if(R.database&&t.database&&await t.database.targetHasData())throw Error(`${z} already has a database named '${R.database.name}' with tables in it. Restoring this site's dump over it would destroy that data. Rename or drop it on the target first, or point this project at a different database name.`);let X=[{id:"pause-workers",title:`Stop background work on ${L} so the snapshot is consistent`,satisfied:async()=>Um(await t.runOnSource(zm(m,k))),apply:async()=>{await t.runOnSource(Mm(m,k))}},...t.database?[{id:"database-dump",title:`Dump the '${R.database?.name}' database on ${L}`,satisfied:async()=>!1,apply:()=>t.database.dump()}]:[],{id:"snapshot",title:`Archive the site's tree and units on ${L}`,change:{from:`${L}:${D}`,to:No(m,k)},satisfied:async()=>!1,apply:async()=>{await t.runOnSource(Wm(m,k,D))}},{id:"transfer",title:`Carry the archive to ${z}`,change:{from:L,to:z},satisfied:()=>t.archiveStaged(),apply:()=>t.transferArchive()},...t.database?[{id:"database-transfer",title:`Carry the database dump to ${z}`,change:{from:L,to:z},satisfied:()=>t.database.dumpStaged(),apply:()=>t.database.transferDump()},{id:"database-restore",title:`Create and load '${R.database?.name}' on ${z}`,satisfied:async()=>!1,apply:()=>t.database.restore()}]:[],{id:"restore",title:`Unpack the site on ${z} and start it`,change:{from:No(m,k),to:`${z}:${D}`},satisfied:async()=>Hm(await t.runOnTarget(qm(m,k,D))),apply:async()=>{await t.runOnTarget(Vm(m,k,D))}}];if(R.port!=null){let{port:ne}=R;X.push({id:"health",title:`Wait for the site to answer on ${z} (127.0.0.1:${ne})`,satisfied:async()=>!1,apply:async()=>{await t.runOnTarget(Gm(ne,R.healthCheckPath??"/"))}})}return X.push(...t.certificates?[{id:"certificates",title:`Carry the site's TLS material to ${z}`,satisfied:()=>t.certificates.inPlace(),apply:()=>t.certificates.carry()}]:[],{id:"gateway",title:`Route the site on ${z}`,satisfied:()=>t.targetRoutesSite(),apply:()=>t.refreshTargetGateway()},{id:"dns",title:`Point DNS at ${z}`,change:{from:`${L}`,to:Y},satisfied:async()=>await t.publishedAddress()===Y,apply:async()=>{let ne=await t.cutoverDns();if(ne.length>0)throw Error(ne.join("; "))}},{id:"drain-source",title:`Stop the site on ${L}, leaving its files in place`,satisfied:async()=>Ym(await t.runOnSource(Jm(m,k))),apply:async()=>{await t.runOnSource(Km(m,k))}}),{operation:"site:move",target:k,steps:X}}async function go(R,t,m){let k=await R.exec(t,m);if(k.code!==0)throw Error(`${t.name}: ${k.stderr.trim()||k.stdout.trim()||`exited ${k.code}`}`);return k.stdout}async function Oo(R,t,m){let k=Bun.spawn(["scp","-P",String(R.sshPort),"-o","BatchMode=yes","-o","StrictHostKeyChecking=yes",t,m],{stdout:"pipe",stderr:"pipe"}),[D,L]=await Promise.all([k.exited,new Response(k.stderr).text()]);if(D!==0)throw Error(`scp failed: ${L.trim()||`exited ${D}`}`)}async function ep(R,t){if(!t.to)throw Error("Name the target server with --to <server>.");let m=await Ne(),k=m.sites?.[R];if(!k)throw Error(`Site '${R}' is not in cloud.config.ts.`);let D=m.project.slug,L=y(process.cwd(),m);try{let Y=new xe(L.store).list(L.project.id),X=(Ye)=>{let _t=Y.find((Wt)=>Wt.id===Ye||Wt.name===Ye);if(!_t)throw Error(`Server ${Ye} was not found. Run \`cloud server:list\`.`);if(_t.trustState!=="pinned")throw Error(`${_t.name} has no pinned host key. Run \`cloud server:validate ${_t.name}\` first.`);return _t},ne=X(t.to),re=Y.filter((Ye)=>Ye.id!==ne.id);if(!t.from&&re.length!==1)throw Error(`Name the source server with --from <server> — ${re.length} others are enrolled.`);let ie=X(t.from??re[0].name),ae=new
|
|
83
|
-
`))},publishedAddress:async()=>{if(!le||!ke)return ne.endpoint;let Ye=
|
|
84
|
-
`))},dumpStaged:async()=>(await ae.exec(ne,`test -s ${Le} && echo staged || true`)).stdout.includes("staged"),transferDump:async()=>{let Ye=`${process.cwd()}/.ts-cloud-move-${D}-${R}.sql.gz`;await Oo(ie,`${ie.sshUser}@${ie.endpoint}:${Le}`,Ye),await Oo(ne,Ye,`${ne.sshUser}@${ne.endpoint}:${Le}`),await Bun.file(Ye).delete().catch(()=>{})},restore:async()=>{await go(ae,ne,[...At(Pe,m.infrastructure?.compute?.managedServices??{}),...
|
|
82
|
+
`)}async function gd(R,t){let{slug:m,siteName:k,appBase:D,from:L,to:z,targetAddress:Y}=R;if(L===z)throw Error(`'${k}' is already on ${z}.`);if(R.database&&!t.database)throw Error(`'${k}' uses the on-box database '${R.database.name}', which lives in the engine's own data directory rather than in the site tree, and this move has no way to carry it. Moving the site without it would cut traffic over to an empty database.`);if(R.database&&t.database&&await t.database.targetHasData())throw Error(`${z} already has a database named '${R.database.name}' with tables in it. Restoring this site's dump over it would destroy that data. Rename or drop it on the target first, or point this project at a different database name.`);let X=[{id:"pause-workers",title:`Stop background work on ${L} so the snapshot is consistent`,satisfied:async()=>Um(await t.runOnSource(zm(m,k))),apply:async()=>{await t.runOnSource(Mm(m,k))}},...t.database?[{id:"database-dump",title:`Dump the '${R.database?.name}' database on ${L}`,satisfied:async()=>!1,apply:()=>t.database.dump()}]:[],{id:"snapshot",title:`Archive the site's tree and units on ${L}`,change:{from:`${L}:${D}`,to:No(m,k)},satisfied:async()=>!1,apply:async()=>{await t.runOnSource(Wm(m,k,D))}},{id:"transfer",title:`Carry the archive to ${z}`,change:{from:L,to:z},satisfied:()=>t.archiveStaged(),apply:()=>t.transferArchive()},...t.database?[{id:"database-transfer",title:`Carry the database dump to ${z}`,change:{from:L,to:z},satisfied:()=>t.database.dumpStaged(),apply:()=>t.database.transferDump()},{id:"database-restore",title:`Create and load '${R.database?.name}' on ${z}`,satisfied:async()=>!1,apply:()=>t.database.restore()}]:[],{id:"restore",title:`Unpack the site on ${z} and start it`,change:{from:No(m,k),to:`${z}:${D}`},satisfied:async()=>Hm(await t.runOnTarget(qm(m,k,D))),apply:async()=>{await t.runOnTarget(Vm(m,k,D))}}];if(R.port!=null){let{port:ne}=R;X.push({id:"health",title:`Wait for the site to answer on ${z} (127.0.0.1:${ne})`,satisfied:async()=>!1,apply:async()=>{await t.runOnTarget(Gm(ne,R.healthCheckPath??"/"))}})}return X.push(...t.certificates?[{id:"certificates",title:`Carry the site's TLS material to ${z}`,satisfied:()=>t.certificates.inPlace(),apply:()=>t.certificates.carry()}]:[],{id:"gateway",title:`Route the site on ${z}`,satisfied:()=>t.targetRoutesSite(),apply:()=>t.refreshTargetGateway()},{id:"dns",title:`Point DNS at ${z}`,change:{from:`${L}`,to:Y},satisfied:async()=>await t.publishedAddress()===Y,apply:async()=>{let ne=await t.cutoverDns();if(ne.length>0)throw Error(ne.join("; "))}},{id:"drain-source",title:`Stop the site on ${L}, leaving its files in place`,satisfied:async()=>Ym(await t.runOnSource(Jm(m,k))),apply:async()=>{await t.runOnSource(Km(m,k))}}),{operation:"site:move",target:k,steps:X}}async function go(R,t,m){let k=await R.exec(t,m);if(k.code!==0)throw Error(`${t.name}: ${k.stderr.trim()||k.stdout.trim()||`exited ${k.code}`}`);return k.stdout}async function Oo(R,t,m){let k=Bun.spawn(["scp","-P",String(R.sshPort),"-o","BatchMode=yes","-o","StrictHostKeyChecking=yes",t,m],{stdout:"pipe",stderr:"pipe"}),[D,L]=await Promise.all([k.exited,new Response(k.stderr).text()]);if(D!==0)throw Error(`scp failed: ${L.trim()||`exited ${D}`}`)}async function ep(R,t){if(!t.to)throw Error("Name the target server with --to <server>.");let m=await Ne(),k=m.sites?.[R];if(!k)throw Error(`Site '${R}' is not in cloud.config.ts.`);let D=m.project.slug,L=y(process.cwd(),m);try{let Y=new xe(L.store).list(L.project.id),X=(Ye)=>{let _t=Y.find((Wt)=>Wt.id===Ye||Wt.name===Ye);if(!_t)throw Error(`Server ${Ye} was not found. Run \`cloud server:list\`.`);if(_t.trustState!=="pinned")throw Error(`${_t.name} has no pinned host key. Run \`cloud server:validate ${_t.name}\` first.`);return _t},ne=X(t.to),re=Y.filter((Ye)=>Ye.id!==ne.id);if(!t.from&&re.length!==1)throw Error(`Name the source server with --from <server> — ${re.length} others are enrolled.`);let ie=X(t.from??re[0].name),ae=new qr,be=J(D,R),Ae=No(D,R),le=k.domain,ke=m.infrastructure?.dns?.provider,De=m.infrastructure?.compute?.proxy?.engine==="rpx"?m.infrastructure.compute.proxy:void 0,Se=m.infrastructure?.dns?.domain,Re=(Ye)=>Se&&Ye.endsWith(Se)?Se:Ye.split(".").slice(-2).join("."),Pe=A(m),Ve=Pe?.name&&_i(Pe)?{name:Pe.name}:void 0,Le=`/tmp/ts-cloud-move-${D}-${R}.sql.gz`,Fe=dd(D,R),He=De?.certsDir??rn,wt=[le,...k.aliases??[]].filter((Ye)=>!!Ye),Dt=Pe?.engine??"mysql",it={runOnSource:(Ye)=>go(ae,ie,Ye),runOnTarget:(Ye)=>go(ae,ne,Ye),archiveStaged:async()=>(await ae.exec(ne,`test -f ${Ae} && echo staged || true`)).stdout.includes("staged"),transferArchive:async()=>{let Ye=`${process.cwd()}/.ts-cloud-move-${D}-${R}.tar.gz`;await Oo(ie,`${ie.sshUser}@${ie.endpoint}:${Ae}`,Ye),await Oo(ne,Ye,`${ne.sshUser}@${ne.endpoint}:${Ae}`),await Bun.file(Ye).delete().catch(()=>{})},targetRoutesSite:async()=>{if(!De||!le)return!0;return(await ae.exec(ne,`grep -qF ${JSON.stringify(le)} /etc/rpx/sites.d/${D}.json 2>/dev/null && echo routed || true`)).stdout.includes("routed")},refreshTargetGateway:async()=>{if(!De)throw Error(`This project does not run the rpx gateway, so the move cannot publish a route. Point ${ne.name}'s web server at ${be}/current yourself, then re-run to continue.`);await go(ae,ne,Je({config:Te(m.sites??{},{proxy:De,slug:D,lan:io(m)}),slug:D,preserveManagementDashboardRoutes:!0}).join(`
|
|
83
|
+
`))},publishedAddress:async()=>{if(!le||!ke)return ne.endpoint;let Ye=bn(ke);if(!Ye)return;return await to(U(Ye),Re(le),le,ne.endpoint,"A")?ne.endpoint:void 0},...Ve?{database:{dump:async()=>{await go(ae,ie,["set -euo pipefail",'eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true',...ls(Dt,Ve.name,"/tmp",Pe),`mv -f "$(ls -1t /tmp/${Ve.name}-*.sql.gz | head -1)" ${Le}`].join(`
|
|
84
|
+
`))},dumpStaged:async()=>(await ae.exec(ne,`test -s ${Le} && echo staged || true`)).stdout.includes("staged"),transferDump:async()=>{let Ye=`${process.cwd()}/.ts-cloud-move-${D}-${R}.sql.gz`;await Oo(ie,`${ie.sshUser}@${ie.endpoint}:${Le}`,Ye),await Oo(ne,Ye,`${ne.sshUser}@${ne.endpoint}:${Le}`),await Bun.file(Ye).delete().catch(()=>{})},restore:async()=>{await go(ae,ne,[...At(Pe,m.infrastructure?.compute?.managedServices??{}),...Oi(Pe,{from:Le}),`rm -f ${Le}`].join(`
|
|
85
85
|
`))},targetHasData:async()=>{let Ye=Dt==="postgres"?`psql -tAc "select count(*) from information_schema.tables where table_schema='public'" -d ${Ve.name} 2>/dev/null || echo 0`:`mysql -N -B -e "select count(*) from information_schema.tables where table_schema='${Ve.name}'" 2>/dev/null || echo 0`,_t=await ae.exec(ne,`eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true
|
|
86
86
|
${Ye}`);return Number.parseInt(_t.stdout.trim().split(`
|
|
87
|
-
`).pop()??"0",10)>0}}}:{},...De?{certificates:{inPlace:async()=>{let Ye=ud(He,wt),[_t,Wt]=await Promise.all([ae.exec(ie,Ye),ae.exec(ne,Ye)]);return md(Ga(_t.stdout),Ga(Wt.stdout))},carry:async()=>{await go(ae,ie,pd(He,wt,Fe));let Ye=`${process.cwd()}/.ts-cloud-move-${D}-${R}-certs.tar.gz`;if(!(await ae.exec(ie,`test -s ${Fe} && echo staged || true`)).stdout.includes("staged"))return;await Oo(ie,`${ie.sshUser}@${ie.endpoint}:${Fe}`,Ye),await Oo(ne,Ye,`${ne.sshUser}@${ne.endpoint}:${Fe}`),await Bun.file(Ye).delete().catch(()=>{}),await go(ae,ne,fd(He,Fe)),await ae.exec(ie,`rm -f ${Fe}`)}}}:{},cutoverDns:async()=>{if(!le)return[];if(!ke)return[`No DNS provider configured — point ${le} at ${ne.endpoint} manually.`];let Ye=
|
|
87
|
+
`).pop()??"0",10)>0}}}:{},...De?{certificates:{inPlace:async()=>{let Ye=ud(He,wt),[_t,Wt]=await Promise.all([ae.exec(ie,Ye),ae.exec(ne,Ye)]);return md(Ga(_t.stdout),Ga(Wt.stdout))},carry:async()=>{await go(ae,ie,pd(He,wt,Fe));let Ye=`${process.cwd()}/.ts-cloud-move-${D}-${R}-certs.tar.gz`;if(!(await ae.exec(ie,`test -s ${Fe} && echo staged || true`)).stdout.includes("staged"))return;await Oo(ie,`${ie.sshUser}@${ie.endpoint}:${Fe}`,Ye),await Oo(ne,Ye,`${ne.sshUser}@${ne.endpoint}:${Fe}`),await Bun.file(Ye).delete().catch(()=>{}),await go(ae,ne,fd(He,Fe)),await ae.exec(ie,`rm -f ${Fe}`)}}}:{},cutoverDns:async()=>{if(!le)return[];if(!ke)return[`No DNS provider configured — point ${le} at ${ne.endpoint} manually.`];let Ye=bn(ke);if(!Ye)return[`DNS provider '${ke}' is not configured.`];return(await mi({provider:U(Ye),zone:Re(le),fqdn:le,ipv4:ne.endpoint,ipv6:me(void 0)})).warnings}},xt=await gd({slug:D,siteName:R,appBase:be,from:ie.name,to:ne.name,targetAddress:ne.endpoint,port:k.port,healthCheckPath:k.healthCheck?.path,database:Ve},it),Zt=await Us(xt);if(!t.apply){if(t.json){console.log(JSON.stringify({schemaVersion:1,operation:xt.operation,target:xt.target,move:{site:R,from:ie.name,to:ne.name,address:ne.endpoint},steps:Zt.map((Ye)=>({id:Ye.step.id,title:Ye.step.title,state:Ye.state,change:Ye.step.change,reason:Ye.reason}))},null,2));return}for(let Ye of Ws(xt,Zt))console.log(Ye);console.log(` The source keeps its files either way — reversible until ${ie.name} is destroyed.`),console.log(" Re-run with --apply to perform it.");return}let Nt=await Vs(xt,Zt,{log:(Ye)=>e(Ye),audit:(Ye)=>L.store.appendEvent({projectId:L.project.id,type:`${Ye.operation}.${Ye.step}.${Ye.state}`,level:Ye.state==="failed"?"error":"info",payload:{site:R,from:ie.name,to:ne.name,...Ye.error?{error:Ye.error}:{}}})});if(t.json)console.log(JSON.stringify({schemaVersion:1,outcome:Nt},null,2));if(!Nt.success){let Ye=Nt.steps.find((_t)=>_t.state==="failed");throw Error(`${xt.operation} stopped at '${Ye?.title}': ${Ye?.error}. ${ie.name} still has the site and its files; re-run the same command to continue from here.`)}if(!t.json)n(`Moved ${R} from ${ie.name} to ${ne.name}. ${ie.name} keeps its copy until you destroy it.`)}finally{L.store.close()}}function Ka(R){R.command("site:move <name>","Move a deployed site to another server").option("--to <server>","Target server (enrolled name or id)").option("--from <server>","Source server; inferred when only one other is enrolled").option("--apply","Perform the reviewed move plan").option("--json","Print structured JSON").action(async(t,m)=>{try{await ep(t,m)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("site:add <name>","Add a site entry to cloud.config.ts").option("--config <path>","Path to cloud config file").option("--root <path>","Directory to deploy").option("--domain <domain>","Domain for the site").option("--path <path>","Path prefix for the site").option("--deploy <target>","Deployment target: server or bucket").option("--build <command>","Build command to run before deployment").option("--start <command>","Start command for server apps").option("--port <port>","Port for server apps").option("--type <type>","Site type, e.g. static, laravel, php").option("--path-rewrite-style <style>","Static rewrite style: directory or flat").option("--dry-run","Print the updated config without writing").action(async(t,m)=>{let k=m?.config||"cloud.config.ts",D=m?.root;if(!D){r("Missing --root <path>");return}if(!Zm(k)){r(`Config file not found: ${k}`);return}try{let L=Ln({configText:await Qm(k,"utf8"),name:t,root:D,domain:m?.domain,path:m?.path,deploy:tp(m?.deploy),build:m?.build,start:m?.start,port:rp(m?.port),type:m?.type,pathRewriteStyle:np(m?.pathRewriteStyle)});if(m?.dryRun){console.log(L);return}await Xm(k,L),n(`Added site '${t}' to ${k}`)}catch(L){r(L instanceof Error?L.message:String(L))}})}function tp(R){if(!R)return;if(R==="bucket"||R==="server")return R;throw Error(`Invalid --deploy '${R}'. Expected 'server' or 'bucket'.`)}function np(R){if(!R)return;if(R==="directory"||R==="flat")return R;throw Error(`Invalid --path-rewrite-style '${R}'. Expected 'directory' or 'flat'.`)}function rp(R){if(!R)return;let t=Number.parseInt(R,10);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`Invalid --port '${R}'. Expected a TCP port between 1 and 65535.`);return t}import{existsSync as cp}from"node:fs";import{mkdir as lp,writeFile as dp}from"node:fs/promises";import{join as up}from"node:path";class Lo{builder;config;environment;mergedConfig;serverEipLogicalIds=new Map;storageFailoverReplicas=[];constructor(R){this.config=R.config,this.environment=R.environment,this.builder=new Jo(`${this.config.project.name} - ${this.environment}`),this.mergedConfig=this.mergeEnvironmentConfig()}mergeEnvironmentConfig(){let t=this.config.environments[this.environment]?.infrastructure;if(!t)return this.config;return{...this.config,infrastructure:{...this.config.infrastructure,...t,compute:{...this.config.infrastructure?.compute,...t.compute},storage:{...this.config.infrastructure?.storage,...t.storage},functions:{...this.config.infrastructure?.functions,...t.functions},servers:{...this.config.infrastructure?.servers,...t.servers},databases:{...this.config.infrastructure?.databases,...t.databases},cdn:{...this.config.infrastructure?.cdn,...t.cdn},sftp:t.sftp??this.config.infrastructure?.sftp,queues:{...this.config.infrastructure?.queues,...t.queues},redirects:{...this.config.infrastructure?.redirects,...t.redirects},realtime:{...this.config.infrastructure?.realtime,...t.realtime},cache:{...this.config.infrastructure?.cache,...t.cache},fileSystem:{...this.config.infrastructure?.fileSystem,...t.fileSystem},email:{...this.config.infrastructure?.email,...t.email},search:{...this.config.infrastructure?.search,...t.search},ai:{...this.config.infrastructure?.ai,...t.ai}}}}shouldDeploy(R){if(R.environments&&!R.environments.includes(this.environment))return!1;if(R.requiresFeatures){let t=this.config.features||{};if(!R.requiresFeatures.every((k)=>t[k]===!0))return!1}if(R.regions){let t=this.config.environments[this.environment]?.region||this.config.project.region;if(!R.regions.includes(t))return!1}if(R.condition&&typeof R.condition==="function")return R.condition(this.config,this.environment);return!0}resolveApiOriginPort(){let R=this.mergedConfig.infrastructure?.api?.port??this.mergedConfig.ports?.api,t=Number(R||3008);return Number.isFinite(t)&&t>0?t:3008}defaultComputeCachePathPatterns(){return["/api/*","/auth/*","/publisher/api/*","/publisher/*","/publish","/publish/*","/analytics/*","/packages/*","/npm/*","/zig/*","/php/*","/commits/*","/health","/dashboard/*","/search","/login","/logout","/signup","/account","/account/*"]}shouldRouteStorageBucketToCompute(R,t){return R==="public"||t.routeCompute===!0}storageBucketName(R,t,m){return this.mergedConfig.infrastructure?.storage?.[m]?.bucket??`${R}-${t}-${m}`}fileSystemLogicalId(R,t,m){return o(l({slug:`${R}-${m}`,environment:t,resourceType:"efs"}))}resolveSftpStorage(R,t,m){let k=R.storage;if(k?.type==="efs"&&k.fileSystem){if(!this.mergedConfig.infrastructure?.fileSystem?.[k.fileSystem])throw Error(`sftp: storage.fileSystem "${k.fileSystem}" is not defined in infrastructure.fileSystem`);return{type:"efs",fileSystemId:{Ref:this.fileSystemLogicalId(t,m,k.fileSystem)},posixProfile:k.posixProfile}}if(k?.type==="s3"&&k.storageBucket){if(!this.mergedConfig.infrastructure?.storage?.[k.storageBucket])throw Error(`sftp: storage.storageBucket "${k.storageBucket}" is not defined in infrastructure.storage`);return{type:"s3",bucket:this.storageBucketName(t,m,k.storageBucket)}}return}resolveComputeCachePathPatterns(R){if(R&&R.length>0)return R;return this.defaultComputeCachePathPatterns()}createComputeCacheBehavior(R,t){return{PathPattern:R,TargetOriginId:t,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD","OPTIONS","PUT","POST","PATCH","DELETE"],CachedMethods:["GET","HEAD"],Compress:!0,CachePolicyId:"4135ea2d-6df8-44a3-9df3-4b5a84be39ad",OriginRequestPolicyId:"b689b0a8-53d0-40ab-baf2-68738e2966ac"}}appendComputeAppOrigin(R,t,m,k,D,L){let z=this.serverEipLogicalIds.get("app"),Y=this.serverEipLogicalIds.get("appInstance");if(!z)return;let X=`EC2-${k}-${D}-api`,ne=this.mergedConfig.infrastructure?.servers?.app?.region||this.mergedConfig.project?.region||"us-east-1",re=ne==="us-east-1"?".compute-1.amazonaws.com":`.${ne}.compute.amazonaws.com`,ie={"Fn::Join":["",["ec2-",{"Fn::Join":["-",{"Fn::Split":[".",{Ref:z}]}]},re]]},ae=this.resolveApiOriginPort();if(R.push({Id:X,DomainName:ie,CustomOriginConfig:{HTTPPort:ae,HTTPSPort:443,OriginProtocolPolicy:"http-only",OriginSSLProtocols:["TLSv1.2"]}}),m.push(z),Y)m.push(Y);for(let be of L)t.push(this.createComputeCacheBehavior(be,X))}buildCaddyfile(R){let t=R.filter(([,D])=>typeof D.domain==="string"&&D.domain&&typeof D.port==="number");if(t.length===0)return;let m=new Map;for(let[,D]of t){let L=m.get(D.domain)??[];L.push({port:D.port,path:D.path}),m.set(D.domain,L)}let k=[];for(let[D,L]of m){let Y=[...L].sort((X,ne)=>{let re=!X.path||X.path==="/",ie=!ne.path||ne.path==="/";if(re&&!ie)return 1;if(!re&&ie)return-1;return(ne.path?.length??0)-(X.path?.length??0)}).map((X)=>{let ne=!X.path||X.path==="/",re=`reverse_proxy localhost:${X.port}`;return ne?` handle {
|
|
88
88
|
${re}
|
|
89
89
|
}`:` handle ${X.path} {
|
|
90
90
|
${re}
|
|
@@ -96,8 +96,8 @@ ${Y.join(`
|
|
|
96
96
|
`)}normalizeMountPath(R){let t=R?.mountPath||R?.path;if(!t||t==="/")return;let m=`/${t}`.replace(/\/+/g,"/").replace(/\/$/,"");return m==="/"?void 0:m}storageBucketLogicalId(R,t,m){return`${R}-${t}-s3-${m}`.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((k)=>k.charAt(0).toUpperCase()+k.slice(1)).join("")}pathMountRewriteFunctionCode(R,t="directory"){return`function handler(event) { var request = event.request; var prefix = ${JSON.stringify(R)}; var rewriteStyle = ${JSON.stringify(t)}; var uri = request.uri; if (uri === prefix) { uri = '/'; } else if (uri.indexOf(prefix + '/') === 0) { uri = uri.substring(prefix.length); } if (uri === '' || uri === '/') { request.uri = '/index.html'; return request; } if (uri.endsWith('/')) { request.uri = uri + 'index.html'; return request; } var lastSegment = uri.substring(uri.lastIndexOf('/') + 1); if (lastSegment.indexOf('.') === -1) { request.uri = rewriteStyle === 'flat' ? uri + '.html' : uri + '/index.html'; return request; } request.uri = uri; return request; }`}generate(){let R=this.mergedConfig.project.slug,t=this.environment,m=!!(this.mergedConfig.infrastructure?.functions&&Object.keys(this.mergedConfig.infrastructure.functions).length>0||this.mergedConfig.infrastructure?.api),k=!!(this.mergedConfig.infrastructure?.servers&&Object.keys(this.mergedConfig.infrastructure.servers).length>0),D=!!this.mergedConfig.infrastructure?.compute;if(!!((this.mergedConfig.mode||"server")==="serverless"&&this.mergedConfig.infrastructure?.containers&&Object.keys(this.mergedConfig.infrastructure.containers).length>0))this.generateNetworkInfrastructure(R,t),this.generateContainerInfrastructure(R,t);if(m)this.generateServerless(R,t);if(k)this.generateServer(R,t);if(D)this.generateComputeApp(R,t);if(this.mergedConfig.infrastructure?.jumpBox)this.generateJumpBox(R,t);if(this.storageFailoverReplicas=[],this.generateSharedInfrastructure(R,t),this.storageFailoverReplicas.length>0)this.builder.addMetadata(Yo,this.storageFailoverReplicas);if(this.config.tags)this.applyGlobalTags(this.config.tags);return this}getStorageFailoverReplicas(){return[...this.storageFailoverReplicas]}applyGlobalTags(R){}generateNetworkInfrastructure(R,t){if(this.builder.hasResource("VPC"))return;let k=this.mergedConfig.infrastructure?.network?.cidr||"10.0.0.0/16",{vpc:D,logicalId:L}=M.createVpc({slug:R,environment:t,cidr:k,enableDnsHostnames:!0,enableDnsSupport:!0});this.builder.addResource("VPC",D);let z=`${R}${t}IGW`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(z,{Type:"AWS::EC2::InternetGateway",Properties:{Tags:[{Key:"Name",Value:`${R}-${t}-igw`},{Key:"Environment",Value:t}]}});let Y=`${R}${t}IGWAttach`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Y,{Type:"AWS::EC2::VPCGatewayAttachment",Properties:{VpcId:{Ref:"VPC"},InternetGatewayId:{Ref:z}}});let X=`${R}${t}PublicRT`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(X,{Type:"AWS::EC2::RouteTable",Properties:{VpcId:{Ref:"VPC"},Tags:[{Key:"Name",Value:`${R}-${t}-public-rt`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${R}${t}PublicRoute`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::EC2::Route",Properties:{RouteTableId:{Ref:X},DestinationCidrBlock:"0.0.0.0/0",GatewayId:{Ref:z}},DependsOn:Y});let ne=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",re=["a","b"];for(let ie=0;ie<2;ie++){let ae=`PublicSubnet${ie+1}`;this.builder.addResource(ae,{Type:"AWS::EC2::Subnet",Properties:{VpcId:{Ref:"VPC"},CidrBlock:`10.0.${ie}.0/24`,AvailabilityZone:`${ne}${re[ie]}`,MapPublicIpOnLaunch:!0,Tags:[{Key:"Name",Value:`${R}-${t}-public-${re[ie]}`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${ae}RTAssoc`,{Type:"AWS::EC2::SubnetRouteTableAssociation",Properties:{SubnetId:{Ref:ae},RouteTableId:{Ref:X}}})}}generateContainerInfrastructure(R,t){let m=this.mergedConfig.infrastructure?.containers;if(!m)return;let k=this.mergedConfig.infrastructure?.loadBalancer,D=this.mergedConfig.infrastructure?.ssl,L=this.mergedConfig.infrastructure?.dns,z;if(D?.enabled&&D.domains?.length)if(D.certificateArn);else{let re=D.domains[0],ie=D.domains.slice(1).map((Ae)=>{if(Ae.includes(".")&&Ae.endsWith(re))return Ae.replace(`.${re}`,"");return Ae}),{certificate:ae,logicalId:be}=j.createCertificate({domain:re,subdomains:ie,slug:R,environment:t,validationMethod:"DNS",hostedZoneId:L?.hostedZoneId});z=be,this.builder.addResource(be,ae)}let Y="ECSCluster";this.builder.addResource(Y,{Type:"AWS::ECS::Cluster",Properties:{ClusterName:`${R}-${t}`,ClusterSettings:[{Name:"containerInsights",Value:"enabled"}],Tags:[{Key:"Name",Value:`${R}-${t}`},{Key:"Environment",Value:t}]}});let X=`${R}${t}ALBSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(X,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ALB security group for ${R}-${t}`,VpcId:{Ref:"VPC"},SecurityGroupIngress:[{IpProtocol:"tcp",FromPort:80,ToPort:80,CidrIp:"0.0.0.0/0",Description:"HTTP"},{IpProtocol:"tcp",FromPort:443,ToPort:443,CidrIp:"0.0.0.0/0",Description:"HTTPS"}],SecurityGroupEgress:[{IpProtocol:"-1",CidrIp:"0.0.0.0/0",Description:"Allow all outbound"}],Tags:[{Key:"Name",Value:`${R}-${t}-alb-sg`},{Key:"Environment",Value:t}]}});let ne=`${R}${t}ECSSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");for(let[re,ie]of Object.entries(m)){let ae=ie.port||3000;this.builder.addResource(ne,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ECS tasks security group for ${R}-${t}`,VpcId:{Ref:"VPC"},SecurityGroupIngress:[{IpProtocol:"tcp",FromPort:ae,ToPort:ae,SourceSecurityGroupId:{Ref:X},Description:"Allow traffic from ALB"}],SecurityGroupEgress:[{IpProtocol:"-1",CidrIp:"0.0.0.0/0",Description:"Allow all outbound"}],Tags:[{Key:"Name",Value:`${R}-${t}-ecs-sg`},{Key:"Environment",Value:t}]}});let be=`${R}${t}TaskExecRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(be,{Type:"AWS::IAM::Role",Properties:{RoleName:`${R}-${t}-ecs-exec-role`,AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"ecs-tasks.amazonaws.com"},Action:"sts:AssumeRole"}]},ManagedPolicyArns:["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"],Policies:[{PolicyName:"ECRPullPolicy",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["ecr:GetAuthorizationToken","ecr:BatchCheckLayerAvailability","ecr:GetDownloadUrlForLayer","ecr:BatchGetImage","logs:CreateLogStream","logs:PutLogEvents"],Resource:"*"}]}}]}});let Ae=`${R}${t}TaskRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ae,{Type:"AWS::IAM::Role",Properties:{RoleName:`${R}-${t}-ecs-task-role`,AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"ecs-tasks.amazonaws.com"},Action:"sts:AssumeRole"}]},Policies:[{PolicyName:"TaskPolicy",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["logs:CreateLogStream","logs:PutLogEvents"],Resource:"*"},{Effect:"Allow",Action:["s3:GetObject","s3:PutObject","s3:ListBucket"],Resource:"*"}]}}]}});let le=`${R}${t}${re}LogGroup`.replace(/[^a-zA-Z0-9]/g,""),ke=`/ecs/${R}-${t}-${re}`;this.builder.addResource(le,{Type:"AWS::Logs::LogGroup",Properties:{LogGroupName:ke,RetentionInDays:30}});let De=`${R}${t}${re}TaskDef`.replace(/[^a-zA-Z0-9]/g,""),Se=String(ie.cpu||512),Re=String(ie.memory||1024);this.builder.addResource(De,{Type:"AWS::ECS::TaskDefinition",Properties:{Family:`${R}-${t}-${re}`,NetworkMode:"awsvpc",RequiresCompatibilities:["FARGATE"],Cpu:Se,Memory:Re,ExecutionRoleArn:{"Fn::GetAtt":[be,"Arn"]},TaskRoleArn:{"Fn::GetAtt":[Ae,"Arn"]},ContainerDefinitions:[{Name:re,Image:{"Fn::Sub":`\${AWS::AccountId}.dkr.ecr.\${AWS::Region}.amazonaws.com/${R}:latest`},Essential:!0,PortMappings:[{ContainerPort:ae,Protocol:"tcp"}],LogConfiguration:{LogDriver:"awslogs",Options:{"awslogs-group":ke,"awslogs-region":{Ref:"AWS::Region"},"awslogs-stream-prefix":re}},HealthCheck:{Command:["CMD-SHELL",`curl -f http://localhost:${ae}${ie.healthCheck||"/health"} || exit 1`],Interval:30,Timeout:5,Retries:3,StartPeriod:60}}],Tags:[{Key:"Name",Value:`${R}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[be,Ae,le]});let Pe=`${R}${t}ALB`.replace(/[^a-zA-Z0-9]/g,"");if(k?.enabled!==!1){this.builder.addResource(Pe,{Type:"AWS::ElasticLoadBalancingV2::LoadBalancer",Properties:{Name:`${R}-${t}-alb`,Scheme:"internet-facing",Type:"application",Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],SecurityGroups:[{Ref:X}],Tags:[{Key:"Name",Value:`${R}-${t}-alb`},{Key:"Environment",Value:t}]}});let Ve=`${R}${t}TargetGroup`.replace(/[^a-zA-Z0-9]/g,""),Le=k?.healthCheck?.path||ie.healthCheck||"/health";this.builder.addResource(Ve,{Type:"AWS::ElasticLoadBalancingV2::TargetGroup",Properties:{Name:`${R}-${t}-tg`,Port:ae,Protocol:"HTTP",VpcId:{Ref:"VPC"},TargetType:"ip",HealthCheckPath:Le,HealthCheckIntervalSeconds:k?.healthCheck?.interval||30,HealthyThresholdCount:k?.healthCheck?.healthyThreshold||2,UnhealthyThresholdCount:k?.healthCheck?.unhealthyThreshold||5,HealthCheckTimeoutSeconds:10,Tags:[{Key:"Name",Value:`${R}-${t}-tg`},{Key:"Environment",Value:t}]}});let Fe=`${R}${t}HTTPListener`.replace(/[^a-zA-Z0-9]/g,"");if(D?.enabled&&D?.redirectHttp)this.builder.addResource(Fe,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Pe},Port:80,Protocol:"HTTP",DefaultActions:[{Type:"redirect",RedirectConfig:{Protocol:"HTTPS",Port:"443",StatusCode:"HTTP_301"}}]}});else this.builder.addResource(Fe,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Pe},Port:80,Protocol:"HTTP",DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Ve}}]}});if(D?.enabled){let it=D.certificateArn||(z?{Ref:z}:void 0);if(it){let xt=`${R}${t}HTTPSListener`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(xt,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Pe},Port:443,Protocol:"HTTPS",Certificates:[{CertificateArn:it}],DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Ve}}],SslPolicy:"ELBSecurityPolicy-TLS13-1-2-2021-06"}})}}let He=`${R}${t}${re}Service`.replace(/[^a-zA-Z0-9]/g,""),wt=ie.desiredCount||1;this.builder.addResource(He,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${R}-${t}-${re}`,Cluster:{Ref:Y},TaskDefinition:{Ref:De},DesiredCount:wt,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:ne}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},LoadBalancers:[{ContainerName:re,ContainerPort:ae,TargetGroupArn:{Ref:Ve}}],HealthCheckGracePeriodSeconds:120,Tags:[{Key:"Name",Value:`${R}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[De,Fe,Ve]});let Dt=ie.autoScaling;if(Dt){let it=`${R}${t}${re}ScalableTarget`.replace(/[^a-zA-Z0-9]/g,"");if(this.builder.addResource(it,{Type:"AWS::ApplicationAutoScaling::ScalableTarget",Properties:{MaxCapacity:Dt.max||10,MinCapacity:Dt.min||1,ResourceId:{"Fn::Sub":`service/\${${Y}}/${R}-${t}-${re}`},ScalableDimension:"ecs:service:DesiredCount",ServiceNamespace:"ecs",RoleARN:{"Fn::Sub":"arn:aws:iam::${AWS::AccountId}:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService"}},DependsOn:He}),Dt.targetCpuUtilization)this.builder.addResource(`${R}${t}${re}CPUScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${R}-${t}-${re}-cpu-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:it},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageCPUUtilization"},TargetValue:Dt.targetCpuUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}});if(Dt.targetMemoryUtilization)this.builder.addResource(`${R}${t}${re}MemoryScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${R}-${t}-${re}-memory-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:it},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageMemoryUtilization"},TargetValue:Dt.targetMemoryUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}})}if(L?.domain&&L?.hostedZoneId)this.builder.addResource(`${R}${t}ApiDnsRecord`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:L.hostedZoneId,Name:`api.${L.domain}`,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[Pe,"DNSName"]},HostedZoneId:{"Fn::GetAtt":[Pe,"CanonicalHostedZoneID"]}}}});this.builder.addOutput("ECSClusterArn",{Description:"ECS Cluster ARN",Value:{"Fn::GetAtt":[Y,"Arn"]},Export:{Name:{"Fn::Sub":"${AWS::StackName}-ecs-cluster-arn"}}}),this.builder.addOutput("ECSServiceName",{Description:"ECS Service Name",Value:`${R}-${t}-${re}`,Export:{Name:{"Fn::Sub":"${AWS::StackName}-ecs-service-name"}}}),this.builder.addOutput("LoadBalancerDNS",{Description:"Application Load Balancer DNS Name",Value:{"Fn::GetAtt":[Pe,"DNSName"]},Export:{Name:{"Fn::Sub":"${AWS::StackName}-alb-dns"}}})}else{let Ve=`${R}${t}${re}Service`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ve,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${R}-${t}-${re}`,Cluster:{Ref:Y},TaskDefinition:{Ref:De},DesiredCount:ie.desiredCount||1,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:ne}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},Tags:[{Key:"Name",Value:`${R}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[De]})}break}}generateServerless(R,t){if(this.mergedConfig.infrastructure?.functions)for(let[m,k]of Object.entries(this.mergedConfig.infrastructure.functions)){if(!this.shouldDeploy(k))continue;let{role:D,logicalId:L}=Ie.createRole({slug:R,environment:t,roleName:`${R}-${t}-${m}-role`,servicePrincipal:"lambda.amazonaws.com",managedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]});this.builder.addResource(L,D);let{lambdaFunction:z,logicalId:Y}=b.createLambdaFunction({slug:R,environment:t,functionName:`${R}-${t}-${m}`,handler:k.handler||"index.handler",runtime:k.runtime||"nodejs20.x",code:{zipFile:k.code||"export const handler = async () => ({ statusCode: 200 });"},role:L,timeout:k.timeout,memorySize:k.memorySize});this.builder.addResource(Y,z)}if(this.mergedConfig.infrastructure?.api){let{restApi:m,logicalId:k}=ts.createRestApi({slug:R,environment:t,apiName:`${R}-${t}-api`});this.builder.addResource(k,m)}}generateServer(R,t){if(!this.mergedConfig.infrastructure?.servers)return;let m=this.mergedConfig.infrastructure.servers,k=this.mergedConfig.infrastructure?.compute,D=this.mergedConfig.infrastructure?.ssl;if(Object.values(m).some((z)=>!z.privateNetwork||z.privateNetwork==="create"))this.generateNetworkInfrastructure(R,t);for(let[z,Y]of Object.entries(m)){let X=Y.instanceType||Y.size||k?.size,re=(X?b.InstanceSize.specs[X]:void 0)?.instanceType||X||"t3.micro",ie=Y.type||"app",ae=Y.userData||Y.startupScript;if(!ae)ae=b.UserData.generateAppServerScript({runtime:"bun",runtimeVersion:Y.bunVersion||"latest",webServer:"none",domain:Y.domain,enableSsl:!!D?.enabled,sslEmail:D?.letsEncrypt?.email,installRedis:ie==="cache",installDatabaseClients:!!Y.database});let be=Y.privateNetwork&&Y.privateNetwork!=="create"?Y.privateNetwork:{Ref:"VPC"},Ae=Y.subnet||{Ref:"PublicSubnet1"},ke=!!this.mergedConfig.infrastructure?.email?.server?.enabled,De=[22,80,443,this.resolveApiOriginPort()];if(ke)De.push(25,465,587,143,993);let Se=b.createServerModeStack({slug:`${R}-${z}`,environment:t,vpcId:be,subnetId:Ae,instanceType:re,keyName:Y.keyName||`${R}-${t}`,domain:Y.domain,userData:ae,volumeSize:Y.diskSize||20,imageId:Y.image,allowedPorts:De});for(let[Re,Pe]of Object.entries(Se.resources))this.builder.addResource(Re,Pe);this.serverEipLogicalIds.set(z,Se.outputs.eipLogicalId),this.serverEipLogicalIds.set(`${z}Instance`,Se.outputs.instanceLogicalId),this.builder.addOutput(`${z}InstanceId`,{Value:{Ref:Se.outputs.instanceLogicalId},Description:`Instance ID for ${z} server`}),this.builder.addOutput(`${z}PublicIp`,{Value:{Ref:Se.outputs.eipLogicalId},Description:`Public IP for ${z} server`})}}generateComputeApp(R,t){let m=this.mergedConfig.infrastructure?.compute;if(!m)return;let k=this.mergedConfig.sites||{},D=Object.entries(k),z=this.mergedConfig.infrastructure?.dns?.domain,Y=this.mergedConfig.infrastructure?.database,X=m.size,re=(X?b.InstanceSize.specs[X]:void 0)?.instanceType||X||"t3.micro";if(!this.builder.hasResource("VPC"))this.generateNetworkInfrastructure(R,t);let ie=this.buildCaddyfile(D),ae=b.UserData.generateBunAppScript({runtime:m.runtime||"bun",runtimeVersion:m.runtimeVersion||"latest",systemPackages:m.systemPackages,database:Y,caddyfile:ie}),be=ro(m.appUpdates),Ae=be.length>0?`${ae}
|
|
97
97
|
${be.join(`
|
|
98
98
|
`)}
|
|
99
|
-
`:ae,le=D.map(([,Le])=>Le.port).filter((Le)=>typeof Le==="number"&&![80,443].includes(Le)),ke=this.resolveApiOriginPort(),De=`${R}${t}DeployBucket`.replace(/[^a-zA-Z0-9]/g,""),Se=`${R}-${t}-deploy`;this.builder.addResource(De,{Type:"AWS::S3::Bucket",Properties:{BucketName:Se,PublicAccessBlockConfiguration:{BlockPublicAcls:!0,BlockPublicPolicy:!0,IgnorePublicAcls:!0,RestrictPublicBuckets:!0},LifecycleConfiguration:{Rules:[{Id:"expire-old-releases",Status:"Enabled",ExpirationInDays:7,Prefix:"releases/"}]},Tags:[{Key:"Project",Value:R},{Key:"Environment",Value:t},{Key:"ManagedBy",Value:"ts-cloud"}]}});let Re=b.createServerModeStack({slug:`${R}-app`,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},instanceType:re,keyName:m.allowSsh?`${R}-${t}`:void 0,domain:z,userData:Ae,volumeSize:m.disk?.size||20,imageId:m.image,allowedPorts:[...m.allowSsh?[22]:[],80,443,ke,...le]}),Pe=Re.resources[Re.outputs.instanceLogicalId];if(Pe?.Properties){let Le=Pe.Properties.Tags||[];Pe.Properties.Tags=[...Le,{Key:"Project",Value:R},{Key:"Environment",Value:t},{Key:"Role",Value:"app"},{Key:"ManagedBy",Value:"ts-cloud"}]}let Ve=Re.resources[Re.outputs.roleLogicalId];if(Ve?.Properties)Ve.Properties.Policies=Ve.Properties.Policies||[],Ve.Properties.Policies.push({PolicyName:"DeployBucketRead",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["s3:GetObject","s3:ListBucket"],Resource:[`arn:aws:s3:::${Se}`,`arn:aws:s3:::${Se}/*`]}]}});for(let[Le,Fe]of Object.entries(Re.resources))this.builder.addResource(Le,Fe);this.builder.addOutput("deployBucketName",{Value:{Ref:De},Description:"S3 bucket where release tarballs are uploaded"}),this.builder.addOutput("appInstanceId",{Value:{Ref:Re.outputs.instanceLogicalId},Description:"EC2 instance ID for the app server"}),this.builder.addOutput("appPublicIp",{Value:{Ref:Re.outputs.eipLogicalId},Description:"Public IP for the app server"}),this.serverEipLogicalIds.set("app",Re.outputs.eipLogicalId),this.serverEipLogicalIds.set("appInstance",Re.outputs.instanceLogicalId)}jumpBoxFileSystemId(R,t,m){let k=Object.keys(this.mergedConfig.infrastructure?.fileSystem??{});if(typeof m==="string")return k.includes(m)?{Ref:this.fileSystemLogicalId(R,t,m)}:m;if(k.length===1)return{Ref:this.fileSystemLogicalId(R,t,k[0])};throw Error(k.length===0?"infrastructure.jumpBox.mountEfs is true, but infrastructure.fileSystem declares no file system. Declare one, or set mountEfs to an EFS id.":`infrastructure.jumpBox.mountEfs is true, but infrastructure.fileSystem declares ${k.length} (${k.join(", ")}). Set mountEfs to the name of the one to mount.`)}generateJumpBox(R,t){let m=this.mergedConfig.infrastructure?.jumpBox;if(!m)return;let k=m===!0?{}:m;if(k.enabled===!1)return;this.generateNetworkInfrastructure(R,t);let D=k.size||"micro",z=b.InstanceSize.specs[D]?.instanceType||D||"t3.micro",Y=b.createJumpBox({slug:R,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:k.keyName,instanceType:z,allowedCidrs:k.allowedCidrs,mountEfs:k.mountEfs?{fileSystemId:this.jumpBoxFileSystemId(R,t,k.mountEfs),mountPath:k.mountPath||"/mnt/efs"}:void 0,databaseTools:k.databaseTools});for(let[X,ne]of Object.entries(Y.resources))this.builder.addResource(X,ne);this.builder.addOutput("JumpBoxInstanceId",{Value:{Ref:Y.instanceLogicalId},Description:"The ID of the JumpBox EC2 instance (use with SSM Session Manager or SSH)"})}generateSharedInfrastructure(R,t){let m=this.mergedConfig.infrastructure?.ssl,k=this.mergedConfig.infrastructure?.dns,D=k?.domain,L=k?.hostedZoneId,z=[],Y,X;if(m?.certificateArn)X=m.certificateArn;else if(m?.enabled&&D&&L){let le=m.domains||[D],ke=le[0],De=le.slice(1).map((Pe)=>{if(Pe.includes(".")&&Pe.endsWith(ke))return Pe.replace(`.${ke}`,"");return Pe}),{certificate:Se,logicalId:Re}=j.createCertificate({domain:ke,subdomains:De,slug:R,environment:t,validationMethod:"DNS",hostedZoneId:L});Y=Re,this.builder.addResource(Re,Se)}if(this.mergedConfig.infrastructure?.storage){let le=Object.entries(this.mergedConfig.infrastructure.storage).some(([,Se])=>Se.website),ke;if(le&&(Y||X))ke=`${R}${t}CloudFrontOAC`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(ke,{Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`${R}-${t}-s3-oac`,Description:`OAC for ${R} ${t} S3 website buckets`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}});let De=Object.entries(this.mergedConfig.infrastructure.storage).map(([Se,Re])=>({name:Se,config:Re,mountPath:this.normalizeMountPath(Re),rewriteStyle:Re.pathRewriteStyle||"directory",logicalId:this.storageBucketLogicalId(R,t,Se)})).filter((Se)=>Se.name!=="public"&&Se.config.website&&Se.mountPath);for(let[Se,Re]of Object.entries(this.mergedConfig.infrastructure.storage)){let Pe=!!(Re.website&&ke),Ve=this.storageBucketName(R,t,Se),{bucket:Le,logicalId:Fe}=ze.createBucket({slug:R,name:Se,environment:t,bucketName:Ve,versioning:Re.versioning,encryption:Re.encryption,public:Pe?!1:Re.public}),He=this.normalizeMountPath(Re),wt;if(Re.failover){let it=`infrastructure.storage.${Se}`;if(!Pe||!D||He&&Se!=="public")throw Error(`${it}.failover needs this bucket to be served by its own CloudFront distribution: a website bucket (website: true), not mounted under the path of another site, with infrastructure.dns.domain set and SSL configured (infrastructure.ssl.certificateArn, or ssl.enabled with dns.hostedZoneId).`);if(wt=va({failover:Re.failover,primaryBucket:Ve,primaryRegion:this.mergedConfig.project.region||"us-east-1",where:it}),wt.replicate&&Le.Properties){let xt=`${Fe}FailoverReplicationRole`;this.builder.addResource(xt,Ta({primaryBucket:Ve,replicaBucket:wt.replicaBucket}));let
|
|
100
|
-
else if (!uri.includes('.')) { request.uri += '.html'; } return request; }`}});let hn=[{Id:at,DomainName:{"Fn::Sub":`\${${Fe}}.s3.${Et}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}],tr=[],Mi=[];if(this.shouldRouteStorageBucketToCompute(Se,Re)){this.appendComputeAppOrigin(hn,tr,Mi,R,t,this.resolveComputeCachePathPatterns(Re.computeRoutes));for(let Kt of De){let Ns=`S3-${R}-${t}-${Kt.name}`,Na=`${R}${t}${Kt.name}PathMountRewrite`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Na,{Type:"AWS::CloudFront::Function",Properties:{Name:`${R}-${t}-${Kt.name}-path-mount-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`Path mount rewrite for ${R} ${t} ${Kt.name} at ${Kt.mountPath}`,Runtime:"cloudfront-js-2.0"},FunctionCode:this.pathMountRewriteFunctionCode(Kt.mountPath,Kt.rewriteStyle)}}),hn.push({Id:Ns,DomainName:{"Fn::Sub":`\${${Kt.logicalId}}.s3.${Et}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}),Mi.push(Kt.logicalId,Na);for(let Bu of[Kt.mountPath,`${Kt.mountPath}/*`])tr.push({PathPattern:Bu,TargetOriginId:Ns,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD","OPTIONS"],CachedMethods:["GET","HEAD","OPTIONS"],Compress:!0,CachePolicyId:"4135ea2d-6df8-44a3-9df3-4b5a84be39ad",FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":[Na,"FunctionARN"]}}]})}}let vn,Gt=at;if(wt){let Kt=`${at}-failover`;Object.assign(hn[0],wt.primaryConnection),hn.push({Id:Kt,DomainName:Ca(wt.replicaBucket,wt.replicaRegion),OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}),vn=Qo({primaryOriginId:at,secondaryOriginId:Kt,statusCodes:wt.statusCodes}),Gt=vn.Items[0].Id}let An={Type:"AWS::CloudFront::Distribution",DependsOn:[Fe,ke,...gn?[gn]:[],...Mi],Properties:{DistributionConfig:{Enabled:!0,Comment:`${R} ${t} ${Se} site`,DefaultRootObject:"index.html",Origins:hn,...vn?{OriginGroups:vn}:{},DefaultCacheBehavior:{TargetOriginId:Gt,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD","OPTIONS"],CachedMethods:["GET","HEAD","OPTIONS"],Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6",...gn?{FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":[gn,"FunctionARN"]}}]}:{}},...tr.length>0?{CacheBehaviors:tr}:{},...xt.length>0?{Aliases:xt}:{},ViewerCertificate:Wt,PriceClass:"PriceClass_100",HttpVersion:"http2and3",IPV6Enabled:!0,CustomErrorResponses:_t}}};if(Y)An.DependsOn.push(Y);if(wt)wn(An.Properties.DistributionConfig,`infrastructure.storage.${Se}`),this.storageFailoverReplicas.push({name:Se,primaryBucket:Ve,primaryRegion:Et,replicaBucket:wt.replicaBucket,replicaRegion:wt.replicaRegion,replicate:wt.replicate,distributionArnOutput:`${Se}CloudFrontDistributionArn`}),this.builder.addOutput(`${Se}CloudFrontDistributionArn`,{Value:{"Fn::Sub":`arn:\${AWS::Partition}:cloudfront::\${AWS::AccountId}:distribution/\${${it}}`},Description:`CloudFront distribution ARN for ${Se} (scopes the bucket policy of the failover replica)`}),this.builder.addOutput(`${Se}FailoverBucketName`,{Value:wt.replicaBucket,Description:`Failover replica bucket for ${Se} (${wt.replicaRegion}), outside this stack`});this.builder.addResource(it,An);let Rn=`${Fe}CloudFrontPolicy`;if(this.builder.addResource(Rn,{Type:"AWS::S3::BucketPolicy",DependsOn:[Fe,it],Properties:{Bucket:{Ref:Fe},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${Fe}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${it}}`}}}}]}}}),this.shouldRouteStorageBucketToCompute(Se,Re))for(let Kt of De){let Ns=`${Kt.logicalId}CloudFrontPolicy`;this.builder.addResource(Ns,{Type:"AWS::S3::BucketPolicy",DependsOn:[Kt.logicalId,it],Properties:{Bucket:{Ref:Kt.logicalId},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${Kt.logicalId}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${it}}`}}}}]}}})}if(xt.length>0)z.push({name:Se,bucketLogicalId:Fe,distLogicalId:it,oacLogicalId:ke,aliases:xt});this.builder.addOutput(`${Se}CloudFrontDomain`,{Value:{"Fn::GetAtt":[it,"DomainName"]},Description:`CloudFront domain for ${Se}`}),this.builder.addOutput(`${Se}CloudFrontDistributionId`,{Value:{Ref:it},Description:`CloudFront distribution ID for ${Se}`})}if(this.builder.addOutput(`${Se}BucketName`,{Value:{Ref:Fe},Description:`S3 bucket name for ${Se}`}),Se==="public")this.builder.addOutput("FrontendBucketName",{Value:{Ref:Fe},Description:"Frontend S3 bucket name"});if(Se==="docs")this.builder.addOutput("DocsBucketName",{Value:{Ref:Fe},Description:"Documentation S3 bucket name"});if(Se==="blog")this.builder.addOutput("BlogBucketName",{Value:{Ref:Fe},Description:"Blog S3 bucket name"})}}if(this.mergedConfig.infrastructure?.sftp){let le=this.mergedConfig.infrastructure.sftp,ke=es.create({...le,storage:this.resolveSftpStorage(le,R,t)??le.storage,slug:R,environment:t});for(let[De,Se]of Object.entries(ke.resources))this.builder.addResource(De,Se);this.builder.addOutput("SftpServerId",{Value:{Ref:ke.serverLogicalId},Description:"AWS Transfer Family SFTP server ID"}),this.builder.addOutput("SftpEndpoint",{Value:{"Fn::Sub":`\${${ke.serverLogicalId}}.server.transfer.\${AWS::Region}.amazonaws.com`},Description:"AWS Transfer Family SFTP endpoint"}),this.builder.addOutput("SftpStorageDomain",{Value:ke.domain,Description:"Storage backing the SFTP server (EFS or S3)"})}if(L&&z.length>0)for(let{name:le,distLogicalId:ke,aliases:De}of z)for(let Se of De){let Re=Se.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Re}ARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[ke],Properties:{HostedZoneId:L,Name:Se,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[ke,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}}),this.builder.addResource(`${Re}AAAARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[ke],Properties:{HostedZoneId:L,Name:Se,Type:"AAAA",AliasTarget:{DNSName:{"Fn::GetAtt":[ke,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}})}if(this.mergedConfig.infrastructure?.databases){for(let[le,ke]of Object.entries(this.mergedConfig.infrastructure.databases))if(ke.engine==="dynamodb"){let{table:De,logicalId:Se}=Xt.createTable({slug:R,environment:t,tableName:`${R}-${t}-${le}`,partitionKey:ke.partitionKey||{name:"id",type:"S"},sortKey:ke.sortKey});this.builder.addResource(Se,De)}else if(ke.engine==="postgres"){let{dbInstance:De,logicalId:Se}=Xt.createPostgres({slug:R,environment:t,dbInstanceIdentifier:`${R}-${t}-${le}`,masterUsername:ke.username||"admin",masterUserPassword:ke.password||"changeme123",allocatedStorage:ke.storage||20,dbInstanceClass:ke.instanceClass||"db.t3.micro"});this.builder.addResource(Se,De)}else if(ke.engine==="mysql"){let{dbInstance:De,logicalId:Se}=Xt.createMysql({slug:R,environment:t,dbInstanceIdentifier:`${R}-${t}-${le}`,masterUsername:ke.username||"admin",masterUserPassword:ke.password||"changeme123",allocatedStorage:ke.storage||20,dbInstanceClass:ke.instanceClass||"db.t3.micro"});this.builder.addResource(Se,De)}}if(this.mergedConfig.infrastructure?.cdn)for(let[le,ke]of Object.entries(this.mergedConfig.infrastructure.cdn)){if(!ke.origin)continue;let De=typeof ke.customDomain==="string"?ke.customDomain:ke.customDomain?.domain||ke.domain,Se=typeof ke.customDomain==="object"?ke.customDomain?.certificateArn:ke.certificateArn,Re=Se||X,Pe=`${R}${t}${le}CDN`.replace(/[^a-zA-Z0-9]/g,""),Ve=`S3-${R}-${t}-${le}-cdn`,Le=[{Id:Ve,DomainName:ke.origin,OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginShield:ke.originShield?{Enabled:!0,OriginShieldRegion:ke.originShieldRegion||this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1"}:{Enabled:!1},...ot(ke,`infrastructure.cdn.${le}`)}],Fe=[],He=[],wt=["GET","HEAD","OPTIONS"],Dt=Ve,it;if(ke.failoverOrigin){En(wt,`the default cache behavior of infrastructure.cdn.${le}`);let Ye=`${Ve}-failover`,_t=typeof ke.failoverOrigin==="string"?{domain:ke.failoverOrigin}:ke.failoverOrigin;if(!_t.domain)throw Error(`infrastructure.cdn.${le}.failoverOrigin.domain is required`);if(_t.domain===ke.origin)throw Error(`infrastructure.cdn.${le}: failoverOrigin must differ from origin (both are ${ke.origin})`);Le.push({Id:Ye,DomainName:_t.domain,OriginPath:_t.originPath||"",...Fi(_t.domain)?{S3OriginConfig:{OriginAccessIdentity:""}}:{CustomOriginConfig:{HTTPPort:80,HTTPSPort:443,OriginProtocolPolicy:"https-only"}},OriginShield:{Enabled:!1},...ot(_t,`infrastructure.cdn.${le}.failoverOrigin`)}),it=Qo({primaryOriginId:Ve,secondaryOriginId:Ye,statusCodes:ke.failoverStatusCodes}),Dt=it.Items[0].Id}else if(ke.failoverStatusCodes)throw Error(`infrastructure.cdn.${le}: failoverStatusCodes needs failoverOrigin to fail over to`);if(ke.routeCompute)this.appendComputeAppOrigin(Le,Fe,He,R,t,this.resolveComputeCachePathPatterns(ke.computeRoutes));let xt=Re&&De?{AcmCertificateArn:Y&&!Se?{Ref:Y}:Re,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},Yt=[...He];if(Y&&!Se&&De)Yt.push(Y);let Nt={Type:"AWS::CloudFront::Distribution",...Yt.length>0?{DependsOn:Yt}:{},Properties:{DistributionConfig:{Enabled:!0,Comment:`${R} ${t} ${le} CDN`,DefaultRootObject:"index.html",Origins:Le,...it?{OriginGroups:it}:{},DefaultCacheBehavior:{TargetOriginId:Dt,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:wt,CachedMethods:["GET","HEAD","OPTIONS"],Compress:ke.compress!==!1,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6"},...Fe.length>0?{CacheBehaviors:Fe}:{},...De&&Re?{Aliases:[De]}:{},ViewerCertificate:xt,PriceClass:"PriceClass_100",HttpVersion:ke.http3?"http2and3":"http2"}}};if(wn(Nt.Properties.DistributionConfig,`infrastructure.cdn.${le}`),this.builder.addResource(Pe,Nt),L&&De&&Re){let Ye=De.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Ye}CdnARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Pe,...Y&&!Se?[Y]:[]],Properties:{HostedZoneId:L,Name:De,Type:"A",AliasTarget:{HostedZoneId:"Z2FDTNDATAQYW2",DNSName:{"Fn::GetAtt":[Pe,"DomainName"]}}}})}}if(this.mergedConfig.infrastructure?.queues)for(let[le,ke]of Object.entries(this.mergedConfig.infrastructure.queues)){if(!this.shouldDeploy(ke))continue;let{queue:De,logicalId:Se}=ye.createQueue({slug:R,environment:t,name:`${R}-${t}-${le}`,fifo:ke.fifo,visibilityTimeout:ke.visibilityTimeout,messageRetentionPeriod:ke.messageRetentionPeriod,delaySeconds:ke.delaySeconds,maxMessageSize:ke.maxMessageSize,receiveMessageWaitTime:ke.receiveMessageWaitTime,contentBasedDeduplication:ke.contentBasedDeduplication,encrypted:ke.encrypted,kmsKeyId:ke.kmsKeyId});this.builder.addResource(Se,De);let Re;if(ke.deadLetterQueue){let{deadLetterQueue:Pe,updatedSourceQueue:Ve,deadLetterLogicalId:Le}=ye.createDeadLetterQueue(Se,{slug:R,environment:t,maxReceiveCount:ke.maxReceiveCount});Re=Le,this.builder.addResource(Le,Pe);let He=this.builder.getResources()[Se];if(He?.Properties)He.Properties.RedrivePolicy=Ve.Properties?.RedrivePolicy}if(ke.trigger){let Pe=ke.trigger,Ve=`${R}${t}${Pe.functionName}`.replace(/[^a-zA-Z0-9]/g,""),Le={Type:"AWS::Lambda::EventSourceMapping",Properties:{EventSourceArn:{"Fn::GetAtt":[Se,"Arn"]},FunctionName:{Ref:Ve},BatchSize:Pe.batchSize||10,MaximumBatchingWindowInSeconds:Pe.batchWindow||0,Enabled:!0,...Pe.reportBatchItemFailures!==!1&&{FunctionResponseTypes:["ReportBatchItemFailures"]},...Pe.maxConcurrency&&{ScalingConfig:{MaximumConcurrency:Pe.maxConcurrency}},...Pe.filterPattern&&{FilterCriteria:{Filters:[{Pattern:JSON.stringify(Pe.filterPattern)}]}}},DependsOn:[Se,Ve]};this.builder.addResource(`${Se}Trigger`,Le)}if(ke.alarms?.enabled){let Pe=ke.alarms,Ve=Pe.notificationTopicArn;if(!Ve&&Pe.notificationEmails?.length){let He=`${Se}AlarmTopic`;this.builder.addResource(He,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${R}-${t}-${le}-alarms`,DisplayName:`${le} Queue Alarms`}}),Pe.notificationEmails.forEach((wt,Dt)=>{this.builder.addResource(`${He}Sub${Dt}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:He},Protocol:"email",Endpoint:wt}})}),Ve={Ref:He}}let Le=Pe.queueDepthThreshold||1000;this.builder.addResource(`${Se}DepthAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-${le}-queue-depth`,AlarmDescription:`Queue ${le} depth exceeds ${Le} messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Average",Period:300,EvaluationPeriods:2,Threshold:Le,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Se,"QueueName"]}}],...Ve&&{AlarmActions:[Ve],OKActions:[Ve]}}});let Fe=Pe.messageAgeThreshold||3600;if(this.builder.addResource(`${Se}AgeAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-${le}-message-age`,AlarmDescription:`Queue ${le} oldest message exceeds ${Fe} seconds`,MetricName:"ApproximateAgeOfOldestMessage",Namespace:"AWS/SQS",Statistic:"Maximum",Period:300,EvaluationPeriods:2,Threshold:Fe,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Se,"QueueName"]}}],...Ve&&{AlarmActions:[Ve],OKActions:[Ve]}}}),Re&&Pe.dlqAlarm!==!1)this.builder.addResource(`${Re}Alarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-${le}-dlq-messages`,AlarmDescription:`Dead letter queue for ${le} has messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Sum",Period:300,EvaluationPeriods:1,Threshold:0,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Re,"QueueName"]}}],...Ve&&{AlarmActions:[Ve]}}})}if(ke.subscribe){let Pe=ke.subscribe,Ve=Pe.topicArn;if(!Ve&&Pe.topicName)Ve={Ref:Pe.topicName};if(Ve){this.builder.addResource(`${Se}SnsPolicy`,{Type:"AWS::SQS::QueuePolicy",Properties:{Queues:[{Ref:Se}],PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"sns.amazonaws.com"},Action:"sqs:SendMessage",Resource:{"Fn::GetAtt":[Se,"Arn"]},Condition:{ArnEquals:{"aws:SourceArn":Ve}}}]}}});let Le={TopicArn:Ve,Protocol:"sqs",Endpoint:{"Fn::GetAtt":[Se,"Arn"]},RawMessageDelivery:Pe.rawMessageDelivery||!1};if(Pe.filterPolicy)Le.FilterPolicy=Pe.filterPolicy,Le.FilterPolicyScope=Pe.filterPolicyScope||"MessageAttributes";this.builder.addResource(`${Se}SnsSub`,{Type:"AWS::SNS::Subscription",Properties:Le,DependsOn:`${Se}SnsPolicy`})}}}if(this.mergedConfig.infrastructure?.realtime?.enabled)if((this.mergedConfig.infrastructure.realtime.mode||"serverless")==="server")this.generateRealtimeServerResources(R,t);else this.generateRealtimeResources(R,t);let ne=this.mergedConfig.infrastructure?.redirects;if(ne){let le=ne.target||this.mergedConfig.infrastructure?.dns?.domain||"",ke=ne.protocol||"https";if(ne.domains?.length&&le)for(let De of ne.domains){let{bucket:Se,bucketPolicy:Re,logicalId:Pe,policyLogicalId:Ve}=kt.createDomainRedirectBucket({slug:R,environment:t,sourceDomain:De,targetDomain:le,protocol:ke});this.builder.addResource(Pe,Se),this.builder.addResource(Ve,Re)}if(ne.paths&&Object.keys(ne.paths).length>0){let De=kt.fromMapping(ne.paths,{statusCode:ne.statusCode||301}),{function:Se,logicalId:Re}=kt.createPathRedirectFunction({slug:R,environment:t,rules:De});this.builder.addResource(Re,Se)}}if(this.mergedConfig.infrastructure?.monitoring?.alarms)for(let[le,ke]of Object.entries(this.mergedConfig.infrastructure.monitoring.alarms)){let{alarm:De,logicalId:Se}=ji.createAlarm({slug:R,environment:t,alarmName:`${R}-${t}-${le}`,metricName:ke.metricName||"Errors",namespace:ke.namespace||"AWS/Lambda",threshold:ke.threshold||1,comparisonOperator:ke.comparisonOperator||"GreaterThanThreshold"});this.builder.addResource(Se,De)}let re=this.mergedConfig.infrastructure?.cache;if(re){let le=re.type||"redis";if(le==="redis"){this.generateNetworkInfrastructure(R,t);let ke=re.redis||{},{replicationGroup:De,subnetGroup:Se,logicalId:Re,subnetGroupId:Pe}=Bi.createRedis({slug:R,environment:t,nodeType:ke.nodeType||re.nodeType||"cache.t3.micro",engineVersion:ke.engineVersion||"7.1",port:ke.port||6379,numCacheClusters:ke.numCacheNodes||2,automaticFailover:ke.automaticFailoverEnabled!==!1,atRestEncryption:!0,transitEncryption:!0,snapshotRetentionDays:ke.snapshotRetentionLimit||7,snapshotWindow:ke.snapshotWindow,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Re,De),Se&&Pe)this.builder.addResource(Pe,Se);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Re,"PrimaryEndPoint.Address"]},Description:"Redis primary endpoint address"}),this.builder.addOutput("CachePort",{Value:{"Fn::GetAtt":[Re,"PrimaryEndPoint.Port"]},Description:"Redis primary endpoint port"})}else if(le==="memcached"){this.generateNetworkInfrastructure(R,t);let ke=re.elasticache||{},{cluster:De,subnetGroup:Se,logicalId:Re,subnetGroupId:Pe}=Bi.createMemcached({slug:R,environment:t,nodeType:ke.nodeType||re.nodeType||"cache.t3.micro",engineVersion:ke.engineVersion||"1.6.22",numCacheNodes:ke.numCacheNodes||2,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Re,De),Se&&Pe)this.builder.addResource(Pe,Se);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Re,"ConfigurationEndpoint.Address"]},Description:"Memcached configuration endpoint address"})}}let ie=this.mergedConfig.infrastructure?.email;if(ie){let le=ie.domain||this.mergedConfig.infrastructure?.dns?.domain;if(le){let{emailIdentity:ke,logicalId:De}=N.verifyDomain({domain:le,slug:R,environment:t,enableDkim:ie.enableDkim!==!1,dkimKeyLength:ie.dkimKeyLength||"RSA_2048_BIT"});if(this.builder.addResource(De,ke),ie.configurationSet!==!1){let{configurationSet:Pe,logicalId:Ve}=N.createConfigurationSet({slug:R,environment:t});this.builder.addResource(Ve,Pe)}let Se=ie.hostedZoneId||this.mergedConfig.infrastructure?.dns?.hostedZoneId;if(Se){if(ie.enableDkim!==!1)for(let He=1;He<=3;He++){let wt=`DkimRecord${He}${le.replace(/\./g,"")}`;this.builder.addResource(wt,{Type:"AWS::Route53::RecordSet",DependsOn:[De],Properties:{HostedZoneId:Se,Name:{"Fn::GetAtt":[De,`DkimDNSTokenName${He}`]},Type:"CNAME",TTL:1800,ResourceRecords:[{"Fn::GetAtt":[De,`DkimDNSTokenValue${He}`]}]}})}let{record:Pe,logicalId:Ve}=N.createSpfRecord(le,Se);this.builder.addResource(Ve,Pe);let{record:Le,logicalId:Fe}=N.createDmarcRecord(le,Se,{policy:"none",reportingEmail:ie.dmarcReportingEmail||`dmarc-reports@${le}`});this.builder.addResource(Fe,Le)}this.builder.addOutput("EmailDomain",{Value:le,Description:"SES verified email domain"});let Re=ie.server;if(Re?.enabled&&Se){let Pe=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",Ve=`${R}-${t}-email`,{role:Le,policy:Fe,roleLogicalId:He,policyLogicalId:wt}=N.createEmailLambdaRole({slug:R,environment:t,s3BucketArn:`arn:aws:s3:::${Ve}`,sesIdentityArn:`arn:aws:ses:${Pe}:*:identity/${le}`});this.builder.addResource(He,Le),this.builder.addResource(wt,Fe);let{function:Dt,permission:it,logicalId:xt,permissionLogicalId:Yt}=N.createInboundEmailLambda({slug:R,environment:t,roleArn:{"Fn::GetAtt":[He,"Arn"]},s3BucketName:Ve,organizedPrefix:"mailboxes/"});this.builder.addResource(xt,Dt),this.builder.addResource(Yt,it);let Nt=N.createInboundEmailSetup({slug:R,environment:t,domain:le,s3BucketName:Ve,s3KeyPrefix:"inbox/",region:Pe,hostedZoneId:Se,lambdaFunctionArn:{"Fn::GetAtt":[xt,"Arn"]}});for(let[_t,Wt]of Object.entries(Nt.resources)){if(Wt.Type==="AWS::SES::ReceiptRule")Wt.DependsOn=[Yt,xt];this.builder.addResource(_t,Wt)}this.builder.addOutput("InboundEmailLambda",{Value:{Ref:xt},Description:"Inbound email processing Lambda function"}),this.builder.addOutput("EmailBucket",{Value:Ve,Description:"S3 bucket for email storage"});let Ye=this.serverEipLogicalIds.get("app");if(Ye&&Se){let Wt=`${Re?.subdomain||"mail"}.${le}`,at=Wt.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${at}ARecord`,{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:Se,Name:Wt,Type:"A",TTL:"300",ResourceRecords:[{Ref:Ye}]}})}this.builder.addOutput("MailHost",{Value:`mail.${le}`,Description:"Mail server hostname for SMTP/IMAP clients"})}}}let ae=this.mergedConfig.infrastructure?.search;if(ae){let le=ae.vpc?{subnetIds:[{Ref:"PublicSubnet1"}],securityGroupIds:[]}:void 0;if(le)this.generateNetworkInfrastructure(R,t);let{domain:ke,logicalId:De}=Qt.createDomain({slug:R,environment:t,engineVersion:ae.engineVersion||"OpenSearch_2.11",instanceType:ae.instanceType||"t3.small.search",instanceCount:ae.instanceCount||1,volumeSize:ae.volumeSize||10,volumeType:ae.volumeType||"gp3",dedicatedMaster:ae.dedicatedMaster||!1,dedicatedMasterType:ae.dedicatedMasterType,dedicatedMasterCount:ae.dedicatedMasterCount||3,multiAz:ae.multiAz||!1,encryption:ae.encryption||{atRest:!0,nodeToNode:!0},advancedSecurity:ae.advancedSecurity,autoTune:ae.autoTune!==!1,vpc:le});this.builder.addResource(De,ke),this.builder.addOutput("SearchDomainEndpoint",{Value:{"Fn::GetAtt":[De,"DomainEndpoint"]},Description:"OpenSearch domain endpoint"}),this.builder.addOutput("SearchDomainArn",{Value:{"Fn::GetAtt":[De,"Arn"]},Description:"OpenSearch domain ARN"})}let be=this.mergedConfig.infrastructure?.fileSystem;if(be&&Object.keys(be).length>0){this.generateNetworkInfrastructure(R,t);for(let[le,ke]of Object.entries(be)){let{fileSystem:De,logicalId:Se}=st.createFileSystem({slug:`${R}-${le}`,environment:t,encrypted:ke.encrypted!==!1,performanceMode:ke.performanceMode||"generalPurpose",throughputMode:ke.throughputMode||"bursting",enableBackup:!0});this.builder.addResource(Se,De);let{securityGroup:Re,logicalId:Pe}=st.createEfsSecurityGroup({slug:`${R}-${le}`,environment:t,vpcId:{Ref:"VPC"},sourceCidrBlocks:["10.0.0.0/16"]});this.builder.addResource(Pe,Re);let{mountTargets:Ve,logicalIds:Le}=st.createMultiAzMountTargets(Se,{slug:`${R}-${le}`,environment:t,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],securityGroupId:{Ref:Pe}});for(let Fe=0;Fe<Ve.length;Fe++)this.builder.addResource(Le[Fe],Ve[Fe]);this.builder.addOutput(`${le}FileSystemId`,{Value:{Ref:Se},Description:`EFS file system ID for ${le}`})}}let Ae=this.mergedConfig.infrastructure?.ai;if(Ae){let le=Ae.service||"ecs",ke=Ae.models||["*"],De=Ae.allowStreaming!==!1,Se;if(le==="ecs")Se=Ge.enableBedrockForEcs({slug:R,environment:t,models:ke,allowStreaming:De});else if(le==="ec2")Se=Ge.enableBedrockForEc2({slug:R,environment:t,models:ke,allowStreaming:De});else if(le==="lambda")Se=Ge.enableBedrockForLambda({slug:R,environment:t,models:ke,allowStreaming:De});else Se=Ge.createBedrockRole(le,{slug:R,environment:t,models:ke,allowStreaming:De});if(this.builder.addResource(Se.logicalId,Se.role),Ae.allowAsync){let{policy:Re,logicalId:Pe}=Ge.createBedrockPolicy({slug:R,environment:t,models:ke,allowStreaming:De,allowAsync:!0});this.builder.addResource(Pe,Re)}this.builder.addOutput("BedrockRoleArn",{Value:{"Fn::GetAtt":[Se.logicalId,"Arn"]},Description:"IAM role ARN with Bedrock permissions"})}}generateRealtimeResources(R,t){let m=this.mergedConfig.infrastructure?.realtime;if(!m)return;let k=m.name||`${R}-${t}-realtime`,D=m.scaling||{},L=m.storage||{type:"dynamodb"},z=D.handlerMemory||256,Y=D.handlerTimeout||30,X=`${R}${t}RealtimeConnections`.replace(/[^a-zA-Z0-9]/g,""),ne=`${R}${t}RealtimeChannels`.replace(/[^a-zA-Z0-9]/g,"");if(L.type==="dynamodb"){let Le=L.dynamodb||{},Fe=Le.billingMode||"PAY_PER_REQUEST";this.builder.addResource(X,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${R}-${t}-realtime-connections`,BillingMode:Fe,AttributeDefinitions:[{AttributeName:"connectionId",AttributeType:"S"},{AttributeName:"userId",AttributeType:"S"}],KeySchema:[{AttributeName:"connectionId",KeyType:"HASH"}],GlobalSecondaryIndexes:[{IndexName:"userId-index",KeySchema:[{AttributeName:"userId",KeyType:"HASH"}],Projection:{ProjectionType:"ALL"},...Fe==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Le.readCapacity||5,WriteCapacityUnits:Le.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},...Fe==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Le.readCapacity||5,WriteCapacityUnits:Le.writeCapacity||5}},...Le.pointInTimeRecovery&&{PointInTimeRecoverySpecification:{PointInTimeRecoveryEnabled:!0}},Tags:[{Key:"Name",Value:`${R}-${t}-realtime-connections`},{Key:"Environment",Value:t}]}}),this.builder.addResource(ne,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${R}-${t}-realtime-channels`,BillingMode:Fe,AttributeDefinitions:[{AttributeName:"channel",AttributeType:"S"},{AttributeName:"connectionId",AttributeType:"S"}],KeySchema:[{AttributeName:"channel",KeyType:"HASH"},{AttributeName:"connectionId",KeyType:"RANGE"}],GlobalSecondaryIndexes:[{IndexName:"connectionId-index",KeySchema:[{AttributeName:"connectionId",KeyType:"HASH"}],Projection:{ProjectionType:"ALL"},...Fe==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Le.readCapacity||5,WriteCapacityUnits:Le.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},...Fe==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Le.readCapacity||5,WriteCapacityUnits:Le.writeCapacity||5}},Tags:[{Key:"Name",Value:`${R}-${t}-realtime-channels`},{Key:"Environment",Value:t}]}})}let re=`${R}${t}RealtimeRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(re,{Type:"AWS::IAM::Role",Properties:{RoleName:`${R}-${t}-realtime-handler-role`,AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]},ManagedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"],Policies:[{PolicyName:"RealtimeHandlerPolicy",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["dynamodb:GetItem","dynamodb:PutItem","dynamodb:DeleteItem","dynamodb:Query","dynamodb:Scan","dynamodb:UpdateItem"],Resource:[{"Fn::GetAtt":[X,"Arn"]},{"Fn::Sub":`\${${X}.Arn}/index/*`},{"Fn::GetAtt":[ne,"Arn"]},{"Fn::Sub":`\${${ne}.Arn}/index/*`}]},{Effect:"Allow",Action:"execute-api:ManageConnections",Resource:{"Fn::Sub":"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*"}}]}}]}});let ie=`${R}${t}RealtimeConnect`.replace(/[^a-zA-Z0-9]/g,""),ae=`${R}${t}RealtimeDisconnect`.replace(/[^a-zA-Z0-9]/g,""),be=`${R}${t}RealtimeMessage`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(ie,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${R}-${t}-realtime-connect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:z,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateConnectHandlerCode()}},DependsOn:[re,X]}),this.builder.addResource(ae,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${R}-${t}-realtime-disconnect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:z,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateDisconnectHandlerCode()}},DependsOn:[re,X,ne]}),this.builder.addResource(be,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${R}-${t}-realtime-message`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:z,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateMessageHandlerCode()}},DependsOn:[re,X,ne]});let Ae=`${R}${t}RealtimeApi`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ae,{Type:"AWS::ApiGatewayV2::Api",Properties:{Name:k,ProtocolType:"WEBSOCKET",RouteSelectionExpression:"$request.body.action",Tags:{Name:k,Environment:t}}});let le=`${ie}Permission`,ke=`${ae}Permission`,De=`${be}Permission`;this.builder.addResource(le,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:ie},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Ae}}/*/$connect`}}}),this.builder.addResource(ke,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:ae},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Ae}}/*/$disconnect`}}}),this.builder.addResource(De,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:be},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Ae}}/*/$default`}}});let Se=`${Ae}ConnectInteg`,Re=`${Ae}DisconnectInteg`,Pe=`${Ae}MessageInteg`;this.builder.addResource(Se,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Ae},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${ie}.Arn}/invocations`}}}),this.builder.addResource(Re,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Ae},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${ae}.Arn}/invocations`}}}),this.builder.addResource(Pe,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Ae},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${be}.Arn}/invocations`}}}),this.builder.addResource(`${Ae}ConnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Ae},RouteKey:"$connect",AuthorizationType:"NONE",Target:{"Fn::Sub":`integrations/\${${Se}}`}}}),this.builder.addResource(`${Ae}DisconnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Ae},RouteKey:"$disconnect",Target:{"Fn::Sub":`integrations/\${${Re}}`}}}),this.builder.addResource(`${Ae}DefaultRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Ae},RouteKey:"$default",Target:{"Fn::Sub":`integrations/\${${Pe}}`}}});let Ve=`${Ae}Stage`;if(this.builder.addResource(Ve,{Type:"AWS::ApiGatewayV2::Stage",Properties:{ApiId:{Ref:Ae},StageName:t,AutoDeploy:!0,DefaultRouteSettings:{ThrottlingBurstLimit:D.messagesPerSecond||1000,ThrottlingRateLimit:D.messagesPerSecond||1000}}}),m.monitoring?.enabled){let Le=m.monitoring,Fe=Le.notificationTopicArn;if(!Fe&&Le.notificationEmails?.length){let He=`${Ae}AlarmTopic`;this.builder.addResource(He,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${R}-${t}-realtime-alarms`,DisplayName:"Realtime WebSocket Alarms"}}),Le.notificationEmails.forEach((wt,Dt)=>{this.builder.addResource(`${He}Sub${Dt}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:He},Protocol:"email",Endpoint:wt}})}),Fe={Ref:He}}if(Le.connectionThreshold)this.builder.addResource(`${Ae}ConnectionAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-realtime-connections`,AlarmDescription:`WebSocket connections exceed ${Le.connectionThreshold}`,MetricName:"ConnectCount",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:300,EvaluationPeriods:2,Threshold:Le.connectionThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Ae}}],...Fe&&{AlarmActions:[Fe]}}});if(Le.errorThreshold)this.builder.addResource(`${Ae}ErrorAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-realtime-errors`,AlarmDescription:`WebSocket errors exceed ${Le.errorThreshold}/min`,MetricName:"ExecutionError",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:60,EvaluationPeriods:3,Threshold:Le.errorThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Ae}}],...Fe&&{AlarmActions:[Fe]}}})}this.builder.addOutput(`${Ae}Endpoint`,{Description:"WebSocket API endpoint URL",Value:{"Fn::Sub":`wss://\${${Ae}}.execute-api.\${AWS::Region}.amazonaws.com/${t}`},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-endpoint"}}}),this.builder.addOutput(`${Ae}Id`,{Description:"WebSocket API ID",Value:{Ref:Ae},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-api-id"}}})}generateConnectHandlerCode(){return`
|
|
99
|
+
`:ae,le=D.map(([,Le])=>Le.port).filter((Le)=>typeof Le==="number"&&![80,443].includes(Le)),ke=this.resolveApiOriginPort(),De=`${R}${t}DeployBucket`.replace(/[^a-zA-Z0-9]/g,""),Se=`${R}-${t}-deploy`;this.builder.addResource(De,{Type:"AWS::S3::Bucket",Properties:{BucketName:Se,PublicAccessBlockConfiguration:{BlockPublicAcls:!0,BlockPublicPolicy:!0,IgnorePublicAcls:!0,RestrictPublicBuckets:!0},LifecycleConfiguration:{Rules:[{Id:"expire-old-releases",Status:"Enabled",ExpirationInDays:7,Prefix:"releases/"}]},Tags:[{Key:"Project",Value:R},{Key:"Environment",Value:t},{Key:"ManagedBy",Value:"ts-cloud"}]}});let Re=b.createServerModeStack({slug:`${R}-app`,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},instanceType:re,keyName:m.allowSsh?`${R}-${t}`:void 0,domain:z,userData:Ae,volumeSize:m.disk?.size||20,imageId:m.image,allowedPorts:[...m.allowSsh?[22]:[],80,443,ke,...le]}),Pe=Re.resources[Re.outputs.instanceLogicalId];if(Pe?.Properties){let Le=Pe.Properties.Tags||[];Pe.Properties.Tags=[...Le,{Key:"Project",Value:R},{Key:"Environment",Value:t},{Key:"Role",Value:"app"},{Key:"ManagedBy",Value:"ts-cloud"}]}let Ve=Re.resources[Re.outputs.roleLogicalId];if(Ve?.Properties)Ve.Properties.Policies=Ve.Properties.Policies||[],Ve.Properties.Policies.push({PolicyName:"DeployBucketRead",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["s3:GetObject","s3:ListBucket"],Resource:[`arn:aws:s3:::${Se}`,`arn:aws:s3:::${Se}/*`]}]}});for(let[Le,Fe]of Object.entries(Re.resources))this.builder.addResource(Le,Fe);this.builder.addOutput("deployBucketName",{Value:{Ref:De},Description:"S3 bucket where release tarballs are uploaded"}),this.builder.addOutput("appInstanceId",{Value:{Ref:Re.outputs.instanceLogicalId},Description:"EC2 instance ID for the app server"}),this.builder.addOutput("appPublicIp",{Value:{Ref:Re.outputs.eipLogicalId},Description:"Public IP for the app server"}),this.serverEipLogicalIds.set("app",Re.outputs.eipLogicalId),this.serverEipLogicalIds.set("appInstance",Re.outputs.instanceLogicalId)}jumpBoxFileSystemId(R,t,m){let k=Object.keys(this.mergedConfig.infrastructure?.fileSystem??{});if(typeof m==="string")return k.includes(m)?{Ref:this.fileSystemLogicalId(R,t,m)}:m;if(k.length===1)return{Ref:this.fileSystemLogicalId(R,t,k[0])};throw Error(k.length===0?"infrastructure.jumpBox.mountEfs is true, but infrastructure.fileSystem declares no file system. Declare one, or set mountEfs to an EFS id.":`infrastructure.jumpBox.mountEfs is true, but infrastructure.fileSystem declares ${k.length} (${k.join(", ")}). Set mountEfs to the name of the one to mount.`)}generateJumpBox(R,t){let m=this.mergedConfig.infrastructure?.jumpBox;if(!m)return;let k=m===!0?{}:m;if(k.enabled===!1)return;this.generateNetworkInfrastructure(R,t);let D=k.size||"micro",z=b.InstanceSize.specs[D]?.instanceType||D||"t3.micro",Y=b.createJumpBox({slug:R,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:k.keyName,instanceType:z,allowedCidrs:k.allowedCidrs,mountEfs:k.mountEfs?{fileSystemId:this.jumpBoxFileSystemId(R,t,k.mountEfs),mountPath:k.mountPath||"/mnt/efs"}:void 0,databaseTools:k.databaseTools});for(let[X,ne]of Object.entries(Y.resources))this.builder.addResource(X,ne);this.builder.addOutput("JumpBoxInstanceId",{Value:{Ref:Y.instanceLogicalId},Description:"The ID of the JumpBox EC2 instance (use with SSM Session Manager or SSH)"})}generateSharedInfrastructure(R,t){let m=this.mergedConfig.infrastructure?.ssl,k=this.mergedConfig.infrastructure?.dns,D=k?.domain,L=k?.hostedZoneId,z=[],Y,X;if(m?.certificateArn)X=m.certificateArn;else if(m?.enabled&&D&&L){let le=m.domains||[D],ke=le[0],De=le.slice(1).map((Pe)=>{if(Pe.includes(".")&&Pe.endsWith(ke))return Pe.replace(`.${ke}`,"");return Pe}),{certificate:Se,logicalId:Re}=j.createCertificate({domain:ke,subdomains:De,slug:R,environment:t,validationMethod:"DNS",hostedZoneId:L});Y=Re,this.builder.addResource(Re,Se)}if(this.mergedConfig.infrastructure?.storage){let le=Object.entries(this.mergedConfig.infrastructure.storage).some(([,Se])=>Se.website),ke;if(le&&(Y||X))ke=`${R}${t}CloudFrontOAC`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(ke,{Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`${R}-${t}-s3-oac`,Description:`OAC for ${R} ${t} S3 website buckets`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}});let De=Object.entries(this.mergedConfig.infrastructure.storage).map(([Se,Re])=>({name:Se,config:Re,mountPath:this.normalizeMountPath(Re),rewriteStyle:Re.pathRewriteStyle||"directory",logicalId:this.storageBucketLogicalId(R,t,Se)})).filter((Se)=>Se.name!=="public"&&Se.config.website&&Se.mountPath);for(let[Se,Re]of Object.entries(this.mergedConfig.infrastructure.storage)){let Pe=!!(Re.website&&ke),Ve=this.storageBucketName(R,t,Se),{bucket:Le,logicalId:Fe}=ze.createBucket({slug:R,name:Se,environment:t,bucketName:Ve,versioning:Re.versioning,encryption:Re.encryption,public:Pe?!1:Re.public}),He=this.normalizeMountPath(Re),wt;if(Re.failover){let it=`infrastructure.storage.${Se}`;if(!Pe||!D||He&&Se!=="public")throw Error(`${it}.failover needs this bucket to be served by its own CloudFront distribution: a website bucket (website: true), not mounted under the path of another site, with infrastructure.dns.domain set and SSL configured (infrastructure.ssl.certificateArn, or ssl.enabled with dns.hostedZoneId).`);if(wt=va({failover:Re.failover,primaryBucket:Ve,primaryRegion:this.mergedConfig.project.region||"us-east-1",where:it}),wt.replicate&&Le.Properties){let xt=`${Fe}FailoverReplicationRole`;this.builder.addResource(xt,Ta({primaryBucket:Ve,replicaBucket:wt.replicaBucket}));let Zt=Le.Properties;Zt.VersioningConfiguration={Status:"Enabled"},Zt.ReplicationConfiguration=Ea({replicaBucket:wt.replicaBucket,roleArn:{"Fn::GetAtt":[xt,"Arn"]}})}}if(Pe&&Le.Properties)Le.Properties.PublicAccessBlockConfiguration={BlockPublicAcls:!0,IgnorePublicAcls:!0,BlockPublicPolicy:!1,RestrictPublicBuckets:!1};if(this.builder.addResource(Fe,Le),Re.website){let it=typeof Re.website==="object"?Re.website:{},xt=ze.enableWebsiteHosting(Le,it.indexDocument||"index.html",it.errorDocument);this.builder.addResource(Fe,xt)}let Dt=this.normalizeMountPath(Re);if(Pe&&ke&&D&&!(Dt&&Se!=="public")){let it=`${R}${t}${Se}CDN`.replace(/[^a-zA-Z0-9]/g,""),xt=[];if(Re.aliases&&Re.aliases.length>0)xt.push(...Re.aliases);else if(Se==="public"){if(xt.push(D),m?.domains?.includes(`www.${D}`))xt.push(`www.${D}`)}else if(Se==="docs")xt.push(`docs.${D}`);else if(Se==="blog")xt.push(`blog.${D}`);let Zt=typeof Re.website==="object"?Re.website:{},Nt=Re.spa===!0,Ye=Zt.errorDocument||(Nt?"index.html":"404.html"),_t=[];if(Nt)_t.push({ErrorCode:403,ResponseCode:200,ResponsePagePath:"/index.html",ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:200,ResponsePagePath:"/index.html",ErrorCachingMinTTL:300});else _t.push({ErrorCode:403,ResponseCode:404,ResponsePagePath:`/${Ye}`,ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:404,ResponsePagePath:`/${Ye}`,ErrorCachingMinTTL:300});let Wt=X&&xt.length>0?{AcmCertificateArn:X,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:Y&&xt.length>0?{AcmCertificateArn:{Ref:Y},SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},at=`S3-${R}-${t}-${Se}`,Et=this.mergedConfig.project.region||"us-east-1",gn;if(!Nt)gn=`${R}${t}${Se}UrlRewrite`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(gn,{Type:"AWS::CloudFront::Function",Properties:{Name:`${R}-${t}-${Se}-url-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`URL rewrite for ${R} ${t} ${Se} - appends .html to extensionless paths`,Runtime:"cloudfront-js-2.0"},FunctionCode:`function handler(event) { var request = event.request; var uri = request.uri; if (uri.endsWith('/')) { request.uri += 'index.html'; }
|
|
100
|
+
else if (!uri.includes('.')) { request.uri += '.html'; } return request; }`}});let yn=[{Id:at,DomainName:{"Fn::Sub":`\${${Fe}}.s3.${Et}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}],tr=[],Mi=[];if(this.shouldRouteStorageBucketToCompute(Se,Re)){this.appendComputeAppOrigin(yn,tr,Mi,R,t,this.resolveComputeCachePathPatterns(Re.computeRoutes));for(let Kt of De){let Ns=`S3-${R}-${t}-${Kt.name}`,Na=`${R}${t}${Kt.name}PathMountRewrite`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Na,{Type:"AWS::CloudFront::Function",Properties:{Name:`${R}-${t}-${Kt.name}-path-mount-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`Path mount rewrite for ${R} ${t} ${Kt.name} at ${Kt.mountPath}`,Runtime:"cloudfront-js-2.0"},FunctionCode:this.pathMountRewriteFunctionCode(Kt.mountPath,Kt.rewriteStyle)}}),yn.push({Id:Ns,DomainName:{"Fn::Sub":`\${${Kt.logicalId}}.s3.${Et}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}),Mi.push(Kt.logicalId,Na);for(let Bu of[Kt.mountPath,`${Kt.mountPath}/*`])tr.push({PathPattern:Bu,TargetOriginId:Ns,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD","OPTIONS"],CachedMethods:["GET","HEAD","OPTIONS"],Compress:!0,CachePolicyId:"4135ea2d-6df8-44a3-9df3-4b5a84be39ad",FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":[Na,"FunctionARN"]}}]})}}let Sn,Gt=at;if(wt){let Kt=`${at}-failover`;Object.assign(yn[0],wt.primaryConnection),yn.push({Id:Kt,DomainName:Ca(wt.replicaBucket,wt.replicaRegion),OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}),Sn=Qo({primaryOriginId:at,secondaryOriginId:Kt,statusCodes:wt.statusCodes}),Gt=Sn.Items[0].Id}let In={Type:"AWS::CloudFront::Distribution",DependsOn:[Fe,ke,...gn?[gn]:[],...Mi],Properties:{DistributionConfig:{Enabled:!0,Comment:`${R} ${t} ${Se} site`,DefaultRootObject:"index.html",Origins:yn,...Sn?{OriginGroups:Sn}:{},DefaultCacheBehavior:{TargetOriginId:Gt,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD","OPTIONS"],CachedMethods:["GET","HEAD","OPTIONS"],Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6",...gn?{FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":[gn,"FunctionARN"]}}]}:{}},...tr.length>0?{CacheBehaviors:tr}:{},...xt.length>0?{Aliases:xt}:{},ViewerCertificate:Wt,PriceClass:"PriceClass_100",HttpVersion:"http2and3",IPV6Enabled:!0,CustomErrorResponses:_t}}};if(Y)In.DependsOn.push(Y);if(wt)An(In.Properties.DistributionConfig,`infrastructure.storage.${Se}`),this.storageFailoverReplicas.push({name:Se,primaryBucket:Ve,primaryRegion:Et,replicaBucket:wt.replicaBucket,replicaRegion:wt.replicaRegion,replicate:wt.replicate,distributionArnOutput:`${Se}CloudFrontDistributionArn`}),this.builder.addOutput(`${Se}CloudFrontDistributionArn`,{Value:{"Fn::Sub":`arn:\${AWS::Partition}:cloudfront::\${AWS::AccountId}:distribution/\${${it}}`},Description:`CloudFront distribution ARN for ${Se} (scopes the bucket policy of the failover replica)`}),this.builder.addOutput(`${Se}FailoverBucketName`,{Value:wt.replicaBucket,Description:`Failover replica bucket for ${Se} (${wt.replicaRegion}), outside this stack`});this.builder.addResource(it,In);let Pn=`${Fe}CloudFrontPolicy`;if(this.builder.addResource(Pn,{Type:"AWS::S3::BucketPolicy",DependsOn:[Fe,it],Properties:{Bucket:{Ref:Fe},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${Fe}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${it}}`}}}}]}}}),this.shouldRouteStorageBucketToCompute(Se,Re))for(let Kt of De){let Ns=`${Kt.logicalId}CloudFrontPolicy`;this.builder.addResource(Ns,{Type:"AWS::S3::BucketPolicy",DependsOn:[Kt.logicalId,it],Properties:{Bucket:{Ref:Kt.logicalId},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${Kt.logicalId}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${it}}`}}}}]}}})}if(xt.length>0)z.push({name:Se,bucketLogicalId:Fe,distLogicalId:it,oacLogicalId:ke,aliases:xt});this.builder.addOutput(`${Se}CloudFrontDomain`,{Value:{"Fn::GetAtt":[it,"DomainName"]},Description:`CloudFront domain for ${Se}`}),this.builder.addOutput(`${Se}CloudFrontDistributionId`,{Value:{Ref:it},Description:`CloudFront distribution ID for ${Se}`})}if(this.builder.addOutput(`${Se}BucketName`,{Value:{Ref:Fe},Description:`S3 bucket name for ${Se}`}),Se==="public")this.builder.addOutput("FrontendBucketName",{Value:{Ref:Fe},Description:"Frontend S3 bucket name"});if(Se==="docs")this.builder.addOutput("DocsBucketName",{Value:{Ref:Fe},Description:"Documentation S3 bucket name"});if(Se==="blog")this.builder.addOutput("BlogBucketName",{Value:{Ref:Fe},Description:"Blog S3 bucket name"})}}if(this.mergedConfig.infrastructure?.sftp){let le=this.mergedConfig.infrastructure.sftp,ke=es.create({...le,storage:this.resolveSftpStorage(le,R,t)??le.storage,slug:R,environment:t});for(let[De,Se]of Object.entries(ke.resources))this.builder.addResource(De,Se);this.builder.addOutput("SftpServerId",{Value:{Ref:ke.serverLogicalId},Description:"AWS Transfer Family SFTP server ID"}),this.builder.addOutput("SftpEndpoint",{Value:{"Fn::Sub":`\${${ke.serverLogicalId}}.server.transfer.\${AWS::Region}.amazonaws.com`},Description:"AWS Transfer Family SFTP endpoint"}),this.builder.addOutput("SftpStorageDomain",{Value:ke.domain,Description:"Storage backing the SFTP server (EFS or S3)"})}if(L&&z.length>0)for(let{name:le,distLogicalId:ke,aliases:De}of z)for(let Se of De){let Re=Se.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Re}ARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[ke],Properties:{HostedZoneId:L,Name:Se,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[ke,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}}),this.builder.addResource(`${Re}AAAARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[ke],Properties:{HostedZoneId:L,Name:Se,Type:"AAAA",AliasTarget:{DNSName:{"Fn::GetAtt":[ke,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}})}if(this.mergedConfig.infrastructure?.databases){for(let[le,ke]of Object.entries(this.mergedConfig.infrastructure.databases))if(ke.engine==="dynamodb"){let{table:De,logicalId:Se}=Qt.createTable({slug:R,environment:t,tableName:`${R}-${t}-${le}`,partitionKey:ke.partitionKey||{name:"id",type:"S"},sortKey:ke.sortKey});this.builder.addResource(Se,De)}else if(ke.engine==="postgres"){let{dbInstance:De,logicalId:Se}=Qt.createPostgres({slug:R,environment:t,dbInstanceIdentifier:`${R}-${t}-${le}`,masterUsername:ke.username||"admin",masterUserPassword:ke.password||"changeme123",allocatedStorage:ke.storage||20,dbInstanceClass:ke.instanceClass||"db.t3.micro"});this.builder.addResource(Se,De)}else if(ke.engine==="mysql"){let{dbInstance:De,logicalId:Se}=Qt.createMysql({slug:R,environment:t,dbInstanceIdentifier:`${R}-${t}-${le}`,masterUsername:ke.username||"admin",masterUserPassword:ke.password||"changeme123",allocatedStorage:ke.storage||20,dbInstanceClass:ke.instanceClass||"db.t3.micro"});this.builder.addResource(Se,De)}}if(this.mergedConfig.infrastructure?.cdn)for(let[le,ke]of Object.entries(this.mergedConfig.infrastructure.cdn)){if(!ke.origin)continue;let De=typeof ke.customDomain==="string"?ke.customDomain:ke.customDomain?.domain||ke.domain,Se=typeof ke.customDomain==="object"?ke.customDomain?.certificateArn:ke.certificateArn,Re=Se||X,Pe=`${R}${t}${le}CDN`.replace(/[^a-zA-Z0-9]/g,""),Ve=`S3-${R}-${t}-${le}-cdn`,Le=[{Id:Ve,DomainName:ke.origin,OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginShield:ke.originShield?{Enabled:!0,OriginShieldRegion:ke.originShieldRegion||this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1"}:{Enabled:!1},...ot(ke,`infrastructure.cdn.${le}`)}],Fe=[],He=[],wt=["GET","HEAD","OPTIONS"],Dt=Ve,it;if(ke.failoverOrigin){wn(wt,`the default cache behavior of infrastructure.cdn.${le}`);let Ye=`${Ve}-failover`,_t=typeof ke.failoverOrigin==="string"?{domain:ke.failoverOrigin}:ke.failoverOrigin;if(!_t.domain)throw Error(`infrastructure.cdn.${le}.failoverOrigin.domain is required`);if(_t.domain===ke.origin)throw Error(`infrastructure.cdn.${le}: failoverOrigin must differ from origin (both are ${ke.origin})`);Le.push({Id:Ye,DomainName:_t.domain,OriginPath:_t.originPath||"",...Fi(_t.domain)?{S3OriginConfig:{OriginAccessIdentity:""}}:{CustomOriginConfig:{HTTPPort:80,HTTPSPort:443,OriginProtocolPolicy:"https-only"}},OriginShield:{Enabled:!1},...ot(_t,`infrastructure.cdn.${le}.failoverOrigin`)}),it=Qo({primaryOriginId:Ve,secondaryOriginId:Ye,statusCodes:ke.failoverStatusCodes}),Dt=it.Items[0].Id}else if(ke.failoverStatusCodes)throw Error(`infrastructure.cdn.${le}: failoverStatusCodes needs failoverOrigin to fail over to`);if(ke.routeCompute)this.appendComputeAppOrigin(Le,Fe,He,R,t,this.resolveComputeCachePathPatterns(ke.computeRoutes));let xt=Re&&De?{AcmCertificateArn:Y&&!Se?{Ref:Y}:Re,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},Zt=[...He];if(Y&&!Se&&De)Zt.push(Y);let Nt={Type:"AWS::CloudFront::Distribution",...Zt.length>0?{DependsOn:Zt}:{},Properties:{DistributionConfig:{Enabled:!0,Comment:`${R} ${t} ${le} CDN`,DefaultRootObject:"index.html",Origins:Le,...it?{OriginGroups:it}:{},DefaultCacheBehavior:{TargetOriginId:Dt,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:wt,CachedMethods:["GET","HEAD","OPTIONS"],Compress:ke.compress!==!1,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6"},...Fe.length>0?{CacheBehaviors:Fe}:{},...De&&Re?{Aliases:[De]}:{},ViewerCertificate:xt,PriceClass:"PriceClass_100",HttpVersion:ke.http3?"http2and3":"http2"}}};if(An(Nt.Properties.DistributionConfig,`infrastructure.cdn.${le}`),this.builder.addResource(Pe,Nt),L&&De&&Re){let Ye=De.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Ye}CdnARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Pe,...Y&&!Se?[Y]:[]],Properties:{HostedZoneId:L,Name:De,Type:"A",AliasTarget:{HostedZoneId:"Z2FDTNDATAQYW2",DNSName:{"Fn::GetAtt":[Pe,"DomainName"]}}}})}}if(this.mergedConfig.infrastructure?.queues)for(let[le,ke]of Object.entries(this.mergedConfig.infrastructure.queues)){if(!this.shouldDeploy(ke))continue;let{queue:De,logicalId:Se}=ye.createQueue({slug:R,environment:t,name:`${R}-${t}-${le}`,fifo:ke.fifo,visibilityTimeout:ke.visibilityTimeout,messageRetentionPeriod:ke.messageRetentionPeriod,delaySeconds:ke.delaySeconds,maxMessageSize:ke.maxMessageSize,receiveMessageWaitTime:ke.receiveMessageWaitTime,contentBasedDeduplication:ke.contentBasedDeduplication,encrypted:ke.encrypted,kmsKeyId:ke.kmsKeyId});this.builder.addResource(Se,De);let Re;if(ke.deadLetterQueue){let{deadLetterQueue:Pe,updatedSourceQueue:Ve,deadLetterLogicalId:Le}=ye.createDeadLetterQueue(Se,{slug:R,environment:t,maxReceiveCount:ke.maxReceiveCount});Re=Le,this.builder.addResource(Le,Pe);let He=this.builder.getResources()[Se];if(He?.Properties)He.Properties.RedrivePolicy=Ve.Properties?.RedrivePolicy}if(ke.trigger){let Pe=ke.trigger,Ve=`${R}${t}${Pe.functionName}`.replace(/[^a-zA-Z0-9]/g,""),Le={Type:"AWS::Lambda::EventSourceMapping",Properties:{EventSourceArn:{"Fn::GetAtt":[Se,"Arn"]},FunctionName:{Ref:Ve},BatchSize:Pe.batchSize||10,MaximumBatchingWindowInSeconds:Pe.batchWindow||0,Enabled:!0,...Pe.reportBatchItemFailures!==!1&&{FunctionResponseTypes:["ReportBatchItemFailures"]},...Pe.maxConcurrency&&{ScalingConfig:{MaximumConcurrency:Pe.maxConcurrency}},...Pe.filterPattern&&{FilterCriteria:{Filters:[{Pattern:JSON.stringify(Pe.filterPattern)}]}}},DependsOn:[Se,Ve]};this.builder.addResource(`${Se}Trigger`,Le)}if(ke.alarms?.enabled){let Pe=ke.alarms,Ve=Pe.notificationTopicArn;if(!Ve&&Pe.notificationEmails?.length){let He=`${Se}AlarmTopic`;this.builder.addResource(He,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${R}-${t}-${le}-alarms`,DisplayName:`${le} Queue Alarms`}}),Pe.notificationEmails.forEach((wt,Dt)=>{this.builder.addResource(`${He}Sub${Dt}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:He},Protocol:"email",Endpoint:wt}})}),Ve={Ref:He}}let Le=Pe.queueDepthThreshold||1000;this.builder.addResource(`${Se}DepthAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-${le}-queue-depth`,AlarmDescription:`Queue ${le} depth exceeds ${Le} messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Average",Period:300,EvaluationPeriods:2,Threshold:Le,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Se,"QueueName"]}}],...Ve&&{AlarmActions:[Ve],OKActions:[Ve]}}});let Fe=Pe.messageAgeThreshold||3600;if(this.builder.addResource(`${Se}AgeAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-${le}-message-age`,AlarmDescription:`Queue ${le} oldest message exceeds ${Fe} seconds`,MetricName:"ApproximateAgeOfOldestMessage",Namespace:"AWS/SQS",Statistic:"Maximum",Period:300,EvaluationPeriods:2,Threshold:Fe,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Se,"QueueName"]}}],...Ve&&{AlarmActions:[Ve],OKActions:[Ve]}}}),Re&&Pe.dlqAlarm!==!1)this.builder.addResource(`${Re}Alarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-${le}-dlq-messages`,AlarmDescription:`Dead letter queue for ${le} has messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Sum",Period:300,EvaluationPeriods:1,Threshold:0,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Re,"QueueName"]}}],...Ve&&{AlarmActions:[Ve]}}})}if(ke.subscribe){let Pe=ke.subscribe,Ve=Pe.topicArn;if(!Ve&&Pe.topicName)Ve={Ref:Pe.topicName};if(Ve){this.builder.addResource(`${Se}SnsPolicy`,{Type:"AWS::SQS::QueuePolicy",Properties:{Queues:[{Ref:Se}],PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"sns.amazonaws.com"},Action:"sqs:SendMessage",Resource:{"Fn::GetAtt":[Se,"Arn"]},Condition:{ArnEquals:{"aws:SourceArn":Ve}}}]}}});let Le={TopicArn:Ve,Protocol:"sqs",Endpoint:{"Fn::GetAtt":[Se,"Arn"]},RawMessageDelivery:Pe.rawMessageDelivery||!1};if(Pe.filterPolicy)Le.FilterPolicy=Pe.filterPolicy,Le.FilterPolicyScope=Pe.filterPolicyScope||"MessageAttributes";this.builder.addResource(`${Se}SnsSub`,{Type:"AWS::SNS::Subscription",Properties:Le,DependsOn:`${Se}SnsPolicy`})}}}if(this.mergedConfig.infrastructure?.realtime?.enabled)if((this.mergedConfig.infrastructure.realtime.mode||"serverless")==="server")this.generateRealtimeServerResources(R,t);else this.generateRealtimeResources(R,t);let ne=this.mergedConfig.infrastructure?.redirects;if(ne){let le=ne.target||this.mergedConfig.infrastructure?.dns?.domain||"",ke=ne.protocol||"https";if(ne.domains?.length&&le)for(let De of ne.domains){let{bucket:Se,bucketPolicy:Re,logicalId:Pe,policyLogicalId:Ve}=kt.createDomainRedirectBucket({slug:R,environment:t,sourceDomain:De,targetDomain:le,protocol:ke});this.builder.addResource(Pe,Se),this.builder.addResource(Ve,Re)}if(ne.paths&&Object.keys(ne.paths).length>0){let De=kt.fromMapping(ne.paths,{statusCode:ne.statusCode||301}),{function:Se,logicalId:Re}=kt.createPathRedirectFunction({slug:R,environment:t,rules:De});this.builder.addResource(Re,Se)}}if(this.mergedConfig.infrastructure?.monitoring?.alarms)for(let[le,ke]of Object.entries(this.mergedConfig.infrastructure.monitoring.alarms)){let{alarm:De,logicalId:Se}=ji.createAlarm({slug:R,environment:t,alarmName:`${R}-${t}-${le}`,metricName:ke.metricName||"Errors",namespace:ke.namespace||"AWS/Lambda",threshold:ke.threshold||1,comparisonOperator:ke.comparisonOperator||"GreaterThanThreshold"});this.builder.addResource(Se,De)}let re=this.mergedConfig.infrastructure?.cache;if(re){let le=re.type||"redis";if(le==="redis"){this.generateNetworkInfrastructure(R,t);let ke=re.redis||{},{replicationGroup:De,subnetGroup:Se,logicalId:Re,subnetGroupId:Pe}=Bi.createRedis({slug:R,environment:t,nodeType:ke.nodeType||re.nodeType||"cache.t3.micro",engineVersion:ke.engineVersion||"7.1",port:ke.port||6379,numCacheClusters:ke.numCacheNodes||2,automaticFailover:ke.automaticFailoverEnabled!==!1,atRestEncryption:!0,transitEncryption:!0,snapshotRetentionDays:ke.snapshotRetentionLimit||7,snapshotWindow:ke.snapshotWindow,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Re,De),Se&&Pe)this.builder.addResource(Pe,Se);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Re,"PrimaryEndPoint.Address"]},Description:"Redis primary endpoint address"}),this.builder.addOutput("CachePort",{Value:{"Fn::GetAtt":[Re,"PrimaryEndPoint.Port"]},Description:"Redis primary endpoint port"})}else if(le==="memcached"){this.generateNetworkInfrastructure(R,t);let ke=re.elasticache||{},{cluster:De,subnetGroup:Se,logicalId:Re,subnetGroupId:Pe}=Bi.createMemcached({slug:R,environment:t,nodeType:ke.nodeType||re.nodeType||"cache.t3.micro",engineVersion:ke.engineVersion||"1.6.22",numCacheNodes:ke.numCacheNodes||2,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Re,De),Se&&Pe)this.builder.addResource(Pe,Se);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Re,"ConfigurationEndpoint.Address"]},Description:"Memcached configuration endpoint address"})}}let ie=this.mergedConfig.infrastructure?.email;if(ie){let le=ie.domain||this.mergedConfig.infrastructure?.dns?.domain;if(le){let{emailIdentity:ke,logicalId:De}=N.verifyDomain({domain:le,slug:R,environment:t,enableDkim:ie.enableDkim!==!1,dkimKeyLength:ie.dkimKeyLength||"RSA_2048_BIT"});if(this.builder.addResource(De,ke),ie.configurationSet!==!1){let{configurationSet:Pe,logicalId:Ve}=N.createConfigurationSet({slug:R,environment:t});this.builder.addResource(Ve,Pe)}let Se=ie.hostedZoneId||this.mergedConfig.infrastructure?.dns?.hostedZoneId;if(Se){if(ie.enableDkim!==!1)for(let He=1;He<=3;He++){let wt=`DkimRecord${He}${le.replace(/\./g,"")}`;this.builder.addResource(wt,{Type:"AWS::Route53::RecordSet",DependsOn:[De],Properties:{HostedZoneId:Se,Name:{"Fn::GetAtt":[De,`DkimDNSTokenName${He}`]},Type:"CNAME",TTL:1800,ResourceRecords:[{"Fn::GetAtt":[De,`DkimDNSTokenValue${He}`]}]}})}let{record:Pe,logicalId:Ve}=N.createSpfRecord(le,Se);this.builder.addResource(Ve,Pe);let{record:Le,logicalId:Fe}=N.createDmarcRecord(le,Se,{policy:"none",reportingEmail:ie.dmarcReportingEmail||`dmarc-reports@${le}`});this.builder.addResource(Fe,Le)}this.builder.addOutput("EmailDomain",{Value:le,Description:"SES verified email domain"});let Re=ie.server;if(Re?.enabled&&Se){let Pe=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",Ve=`${R}-${t}-email`,{role:Le,policy:Fe,roleLogicalId:He,policyLogicalId:wt}=N.createEmailLambdaRole({slug:R,environment:t,s3BucketArn:`arn:aws:s3:::${Ve}`,sesIdentityArn:`arn:aws:ses:${Pe}:*:identity/${le}`});this.builder.addResource(He,Le),this.builder.addResource(wt,Fe);let{function:Dt,permission:it,logicalId:xt,permissionLogicalId:Zt}=N.createInboundEmailLambda({slug:R,environment:t,roleArn:{"Fn::GetAtt":[He,"Arn"]},s3BucketName:Ve,organizedPrefix:"mailboxes/"});this.builder.addResource(xt,Dt),this.builder.addResource(Zt,it);let Nt=N.createInboundEmailSetup({slug:R,environment:t,domain:le,s3BucketName:Ve,s3KeyPrefix:"inbox/",region:Pe,hostedZoneId:Se,lambdaFunctionArn:{"Fn::GetAtt":[xt,"Arn"]}});for(let[_t,Wt]of Object.entries(Nt.resources)){if(Wt.Type==="AWS::SES::ReceiptRule")Wt.DependsOn=[Zt,xt];this.builder.addResource(_t,Wt)}this.builder.addOutput("InboundEmailLambda",{Value:{Ref:xt},Description:"Inbound email processing Lambda function"}),this.builder.addOutput("EmailBucket",{Value:Ve,Description:"S3 bucket for email storage"});let Ye=this.serverEipLogicalIds.get("app");if(Ye&&Se){let Wt=`${Re?.subdomain||"mail"}.${le}`,at=Wt.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${at}ARecord`,{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:Se,Name:Wt,Type:"A",TTL:"300",ResourceRecords:[{Ref:Ye}]}})}this.builder.addOutput("MailHost",{Value:`mail.${le}`,Description:"Mail server hostname for SMTP/IMAP clients"})}}}let ae=this.mergedConfig.infrastructure?.search;if(ae){let le=ae.vpc?{subnetIds:[{Ref:"PublicSubnet1"}],securityGroupIds:[]}:void 0;if(le)this.generateNetworkInfrastructure(R,t);let{domain:ke,logicalId:De}=Yt.createDomain({slug:R,environment:t,engineVersion:ae.engineVersion||"OpenSearch_2.11",instanceType:ae.instanceType||"t3.small.search",instanceCount:ae.instanceCount||1,volumeSize:ae.volumeSize||10,volumeType:ae.volumeType||"gp3",dedicatedMaster:ae.dedicatedMaster||!1,dedicatedMasterType:ae.dedicatedMasterType,dedicatedMasterCount:ae.dedicatedMasterCount||3,multiAz:ae.multiAz||!1,encryption:ae.encryption||{atRest:!0,nodeToNode:!0},advancedSecurity:ae.advancedSecurity,autoTune:ae.autoTune!==!1,vpc:le});this.builder.addResource(De,ke),this.builder.addOutput("SearchDomainEndpoint",{Value:{"Fn::GetAtt":[De,"DomainEndpoint"]},Description:"OpenSearch domain endpoint"}),this.builder.addOutput("SearchDomainArn",{Value:{"Fn::GetAtt":[De,"Arn"]},Description:"OpenSearch domain ARN"})}let be=this.mergedConfig.infrastructure?.fileSystem;if(be&&Object.keys(be).length>0){this.generateNetworkInfrastructure(R,t);for(let[le,ke]of Object.entries(be)){let{fileSystem:De,logicalId:Se}=st.createFileSystem({slug:`${R}-${le}`,environment:t,encrypted:ke.encrypted!==!1,performanceMode:ke.performanceMode||"generalPurpose",throughputMode:ke.throughputMode||"bursting",enableBackup:!0});this.builder.addResource(Se,De);let{securityGroup:Re,logicalId:Pe}=st.createEfsSecurityGroup({slug:`${R}-${le}`,environment:t,vpcId:{Ref:"VPC"},sourceCidrBlocks:["10.0.0.0/16"]});this.builder.addResource(Pe,Re);let{mountTargets:Ve,logicalIds:Le}=st.createMultiAzMountTargets(Se,{slug:`${R}-${le}`,environment:t,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],securityGroupId:{Ref:Pe}});for(let Fe=0;Fe<Ve.length;Fe++)this.builder.addResource(Le[Fe],Ve[Fe]);this.builder.addOutput(`${le}FileSystemId`,{Value:{Ref:Se},Description:`EFS file system ID for ${le}`})}}let Ae=this.mergedConfig.infrastructure?.ai;if(Ae){let le=Ae.service||"ecs",ke=Ae.models||["*"],De=Ae.allowStreaming!==!1,Se;if(le==="ecs")Se=Ge.enableBedrockForEcs({slug:R,environment:t,models:ke,allowStreaming:De});else if(le==="ec2")Se=Ge.enableBedrockForEc2({slug:R,environment:t,models:ke,allowStreaming:De});else if(le==="lambda")Se=Ge.enableBedrockForLambda({slug:R,environment:t,models:ke,allowStreaming:De});else Se=Ge.createBedrockRole(le,{slug:R,environment:t,models:ke,allowStreaming:De});if(this.builder.addResource(Se.logicalId,Se.role),Ae.allowAsync){let{policy:Re,logicalId:Pe}=Ge.createBedrockPolicy({slug:R,environment:t,models:ke,allowStreaming:De,allowAsync:!0});this.builder.addResource(Pe,Re)}this.builder.addOutput("BedrockRoleArn",{Value:{"Fn::GetAtt":[Se.logicalId,"Arn"]},Description:"IAM role ARN with Bedrock permissions"})}}generateRealtimeResources(R,t){let m=this.mergedConfig.infrastructure?.realtime;if(!m)return;let k=m.name||`${R}-${t}-realtime`,D=m.scaling||{},L=m.storage||{type:"dynamodb"},z=D.handlerMemory||256,Y=D.handlerTimeout||30,X=`${R}${t}RealtimeConnections`.replace(/[^a-zA-Z0-9]/g,""),ne=`${R}${t}RealtimeChannels`.replace(/[^a-zA-Z0-9]/g,"");if(L.type==="dynamodb"){let Le=L.dynamodb||{},Fe=Le.billingMode||"PAY_PER_REQUEST";this.builder.addResource(X,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${R}-${t}-realtime-connections`,BillingMode:Fe,AttributeDefinitions:[{AttributeName:"connectionId",AttributeType:"S"},{AttributeName:"userId",AttributeType:"S"}],KeySchema:[{AttributeName:"connectionId",KeyType:"HASH"}],GlobalSecondaryIndexes:[{IndexName:"userId-index",KeySchema:[{AttributeName:"userId",KeyType:"HASH"}],Projection:{ProjectionType:"ALL"},...Fe==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Le.readCapacity||5,WriteCapacityUnits:Le.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},...Fe==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Le.readCapacity||5,WriteCapacityUnits:Le.writeCapacity||5}},...Le.pointInTimeRecovery&&{PointInTimeRecoverySpecification:{PointInTimeRecoveryEnabled:!0}},Tags:[{Key:"Name",Value:`${R}-${t}-realtime-connections`},{Key:"Environment",Value:t}]}}),this.builder.addResource(ne,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${R}-${t}-realtime-channels`,BillingMode:Fe,AttributeDefinitions:[{AttributeName:"channel",AttributeType:"S"},{AttributeName:"connectionId",AttributeType:"S"}],KeySchema:[{AttributeName:"channel",KeyType:"HASH"},{AttributeName:"connectionId",KeyType:"RANGE"}],GlobalSecondaryIndexes:[{IndexName:"connectionId-index",KeySchema:[{AttributeName:"connectionId",KeyType:"HASH"}],Projection:{ProjectionType:"ALL"},...Fe==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Le.readCapacity||5,WriteCapacityUnits:Le.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},...Fe==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Le.readCapacity||5,WriteCapacityUnits:Le.writeCapacity||5}},Tags:[{Key:"Name",Value:`${R}-${t}-realtime-channels`},{Key:"Environment",Value:t}]}})}let re=`${R}${t}RealtimeRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(re,{Type:"AWS::IAM::Role",Properties:{RoleName:`${R}-${t}-realtime-handler-role`,AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]},ManagedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"],Policies:[{PolicyName:"RealtimeHandlerPolicy",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["dynamodb:GetItem","dynamodb:PutItem","dynamodb:DeleteItem","dynamodb:Query","dynamodb:Scan","dynamodb:UpdateItem"],Resource:[{"Fn::GetAtt":[X,"Arn"]},{"Fn::Sub":`\${${X}.Arn}/index/*`},{"Fn::GetAtt":[ne,"Arn"]},{"Fn::Sub":`\${${ne}.Arn}/index/*`}]},{Effect:"Allow",Action:"execute-api:ManageConnections",Resource:{"Fn::Sub":"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*"}}]}}]}});let ie=`${R}${t}RealtimeConnect`.replace(/[^a-zA-Z0-9]/g,""),ae=`${R}${t}RealtimeDisconnect`.replace(/[^a-zA-Z0-9]/g,""),be=`${R}${t}RealtimeMessage`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(ie,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${R}-${t}-realtime-connect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:z,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateConnectHandlerCode()}},DependsOn:[re,X]}),this.builder.addResource(ae,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${R}-${t}-realtime-disconnect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:z,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateDisconnectHandlerCode()}},DependsOn:[re,X,ne]}),this.builder.addResource(be,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${R}-${t}-realtime-message`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:z,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateMessageHandlerCode()}},DependsOn:[re,X,ne]});let Ae=`${R}${t}RealtimeApi`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ae,{Type:"AWS::ApiGatewayV2::Api",Properties:{Name:k,ProtocolType:"WEBSOCKET",RouteSelectionExpression:"$request.body.action",Tags:{Name:k,Environment:t}}});let le=`${ie}Permission`,ke=`${ae}Permission`,De=`${be}Permission`;this.builder.addResource(le,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:ie},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Ae}}/*/$connect`}}}),this.builder.addResource(ke,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:ae},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Ae}}/*/$disconnect`}}}),this.builder.addResource(De,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:be},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Ae}}/*/$default`}}});let Se=`${Ae}ConnectInteg`,Re=`${Ae}DisconnectInteg`,Pe=`${Ae}MessageInteg`;this.builder.addResource(Se,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Ae},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${ie}.Arn}/invocations`}}}),this.builder.addResource(Re,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Ae},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${ae}.Arn}/invocations`}}}),this.builder.addResource(Pe,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Ae},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${be}.Arn}/invocations`}}}),this.builder.addResource(`${Ae}ConnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Ae},RouteKey:"$connect",AuthorizationType:"NONE",Target:{"Fn::Sub":`integrations/\${${Se}}`}}}),this.builder.addResource(`${Ae}DisconnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Ae},RouteKey:"$disconnect",Target:{"Fn::Sub":`integrations/\${${Re}}`}}}),this.builder.addResource(`${Ae}DefaultRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Ae},RouteKey:"$default",Target:{"Fn::Sub":`integrations/\${${Pe}}`}}});let Ve=`${Ae}Stage`;if(this.builder.addResource(Ve,{Type:"AWS::ApiGatewayV2::Stage",Properties:{ApiId:{Ref:Ae},StageName:t,AutoDeploy:!0,DefaultRouteSettings:{ThrottlingBurstLimit:D.messagesPerSecond||1000,ThrottlingRateLimit:D.messagesPerSecond||1000}}}),m.monitoring?.enabled){let Le=m.monitoring,Fe=Le.notificationTopicArn;if(!Fe&&Le.notificationEmails?.length){let He=`${Ae}AlarmTopic`;this.builder.addResource(He,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${R}-${t}-realtime-alarms`,DisplayName:"Realtime WebSocket Alarms"}}),Le.notificationEmails.forEach((wt,Dt)=>{this.builder.addResource(`${He}Sub${Dt}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:He},Protocol:"email",Endpoint:wt}})}),Fe={Ref:He}}if(Le.connectionThreshold)this.builder.addResource(`${Ae}ConnectionAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-realtime-connections`,AlarmDescription:`WebSocket connections exceed ${Le.connectionThreshold}`,MetricName:"ConnectCount",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:300,EvaluationPeriods:2,Threshold:Le.connectionThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Ae}}],...Fe&&{AlarmActions:[Fe]}}});if(Le.errorThreshold)this.builder.addResource(`${Ae}ErrorAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${R}-${t}-realtime-errors`,AlarmDescription:`WebSocket errors exceed ${Le.errorThreshold}/min`,MetricName:"ExecutionError",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:60,EvaluationPeriods:3,Threshold:Le.errorThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Ae}}],...Fe&&{AlarmActions:[Fe]}}})}this.builder.addOutput(`${Ae}Endpoint`,{Description:"WebSocket API endpoint URL",Value:{"Fn::Sub":`wss://\${${Ae}}.execute-api.\${AWS::Region}.amazonaws.com/${t}`},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-endpoint"}}}),this.builder.addOutput(`${Ae}Id`,{Description:"WebSocket API ID",Value:{Ref:Ae},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-api-id"}}})}generateConnectHandlerCode(){return`
|
|
101
101
|
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
|
|
102
102
|
const { DynamoDBDocumentClient, PutCommand } = require('@aws-sdk/lib-dynamodb');
|
|
103
103
|
|
|
@@ -305,86 +305,86 @@ ${t.loadManagement?.enabled?`
|
|
|
305
305
|
},
|
|
306
306
|
`:""}
|
|
307
307
|
} satisfies BroadcastConfig
|
|
308
|
-
`}toYAML(){return this.builder.toYAML()}toJSON(){return this.builder.toJSON()}getBuilder(){return this.builder}}function rs(R){let t=[],m=[];if(!R.AWSTemplateFormatVersion)t.push({path:"AWSTemplateFormatVersion",message:"AWSTemplateFormatVersion is required",severity:"error"});else if(R.AWSTemplateFormatVersion!=="2010-09-09")m.push({path:"AWSTemplateFormatVersion",message:'AWSTemplateFormatVersion should be "2010-09-09"',severity:"warning"});if(!R.Resources||Object.keys(R.Resources).length===0)t.push({path:"Resources",message:"At least one resource is required",severity:"error"});else for(let[k,D]of Object.entries(R.Resources))ip(k,D,t,m);if(R.Resources){let k=ap(R.Resources);if(k.length>0)t.push({path:"Resources",message:`Circular dependencies detected: ${k.join(" -> ")}`,severity:"error"})}if(R.Parameters)for(let[k,D]of Object.entries(R.Parameters))op(k,D,t,m);if(R.Outputs)for(let[k,D]of Object.entries(R.Outputs))sp(k,D,t,m);return{valid:t.length===0,errors:t,warnings:m}}function ip(R,t,m,k){if(!t.Type)m.push({path:`Resources.${R}.Type`,message:"Resource Type is required",severity:"error"});if(!/^[a-zA-Z0-9]+$/.test(R))k.push({path:`Resources.${R}`,message:"Logical ID should only contain alphanumeric characters",severity:"warning"});if(t.Type&&!t.Type.startsWith("AWS::")&&!t.Type.startsWith("Custom::"))m.push({path:`Resources.${R}.Type`,message:'Resource Type must start with "AWS::" or "Custom::"',severity:"error"});if(t.Properties){for(let[D,L]of Object.entries(t.Properties))if(L===void 0||L===null)k.push({path:`Resources.${R}.Properties.${D}`,message:"Property has undefined or null value",severity:"warning"})}if(t.DependsOn){if(typeof t.DependsOn==="string"){if(t.DependsOn===R)m.push({path:`Resources.${R}.DependsOn`,message:"Resource cannot depend on itself",severity:"error"})}else if(Array.isArray(t.DependsOn)){if(t.DependsOn.includes(R))m.push({path:`Resources.${R}.DependsOn`,message:"Resource cannot depend on itself",severity:"error"})}}}function op(R,t,m,k){if(!t.Type)m.push({path:`Parameters.${R}.Type`,message:"Parameter Type is required",severity:"error"});let D=["String","Number","List<Number>","CommaDelimitedList","AWS::SSM::Parameter::Value<String>"];if(t.Type&&!D.includes(t.Type)&&!t.Type.startsWith("AWS::"))k.push({path:`Parameters.${R}.Type`,message:`Uncommon parameter type: ${t.Type}`,severity:"warning"});if(t.NoEcho&&t.Default)k.push({path:`Parameters.${R}`,message:"NoEcho parameters should not have default values",severity:"warning"})}function sp(R,t,m,k){if(!t.Value)m.push({path:`Outputs.${R}.Value`,message:"Output Value is required",severity:"error"})}function ap(R){let t={};for(let[z,Y]of Object.entries(R)){if(t[z]=[],Y.DependsOn){if(typeof Y.DependsOn==="string")t[z].push(Y.DependsOn);else if(Array.isArray(Y.DependsOn))t[z].push(...Y.DependsOn)}let X=Ja(Y);t[z].push(...X)}let m=new Set,k=new Set,D=[];function L(z,Y){m.add(z),k.add(z),Y.push(z);let X=t[z]||[];for(let ne of X)if(!m.has(ne)){if(L(ne,Y))return!0}else if(k.has(ne)){let re=Y.indexOf(ne);return D.push(...Y.slice(re),ne),!0}return Y.pop(),k.delete(z),!1}for(let z of Object.keys(t))if(!m.has(z)){if(L(z,[]))return D}return[]}function Ja(R,t=[]){if(typeof R!=="object"||R===null)return t;if(Array.isArray(R)){for(let m of R)Ja(m,t);return t}if(R.Ref&&typeof R.Ref==="string"&&!R.Ref.startsWith("AWS::"))t.push(R.Ref);if(R["Fn::GetAtt"]){let m=R["Fn::GetAtt"];if(Array.isArray(m)&&m.length>0)t.push(m[0])}for(let m of Object.values(R))Ja(m,t);return t}function is(R){let t=[],m=[],k=new TextEncoder().encode(R).length,D=51200,L=460800;if(k>51200)if(k>460800)t.push({path:"template",message:`Template size (${k} bytes) exceeds maximum allowed size of 460800 bytes`,severity:"error"});else m.push({path:"template",message:`Template size (${k} bytes) exceeds direct upload limit (51200 bytes). You must upload to S3 first.`,severity:"warning"});return{valid:t.length===0,errors:t,warnings:m}}function os(R){let t=[],m=[],k=R.Resources?Object.keys(R.Resources).length:0,D=R.Parameters?Object.keys(R.Parameters).length:0,L=R.Outputs?Object.keys(R.Outputs).length:0;if(k>500)t.push({path:"Resources",message:`Template has ${k} resources, exceeding the limit of 500`,severity:"error"});else if(k>200)m.push({path:"Resources",message:`Template has ${k} resources. Consider using nested stacks for better organization.`,severity:"warning"});if(D>200)t.push({path:"Parameters",message:`Template has ${D} parameters, exceeding the limit of 200`,severity:"error"});if(L>200)t.push({path:"Outputs",message:`Template has ${L} outputs, exceeding the limit of 200`,severity:"error"});return{valid:t.length===0,errors:t,warnings:m}}function
|
|
308
|
+
`}toYAML(){return this.builder.toYAML()}toJSON(){return this.builder.toJSON()}getBuilder(){return this.builder}}function rs(R){let t=[],m=[];if(!R.AWSTemplateFormatVersion)t.push({path:"AWSTemplateFormatVersion",message:"AWSTemplateFormatVersion is required",severity:"error"});else if(R.AWSTemplateFormatVersion!=="2010-09-09")m.push({path:"AWSTemplateFormatVersion",message:'AWSTemplateFormatVersion should be "2010-09-09"',severity:"warning"});if(!R.Resources||Object.keys(R.Resources).length===0)t.push({path:"Resources",message:"At least one resource is required",severity:"error"});else for(let[k,D]of Object.entries(R.Resources))ip(k,D,t,m);if(R.Resources){let k=ap(R.Resources);if(k.length>0)t.push({path:"Resources",message:`Circular dependencies detected: ${k.join(" -> ")}`,severity:"error"})}if(R.Parameters)for(let[k,D]of Object.entries(R.Parameters))op(k,D,t,m);if(R.Outputs)for(let[k,D]of Object.entries(R.Outputs))sp(k,D,t,m);return{valid:t.length===0,errors:t,warnings:m}}function ip(R,t,m,k){if(!t.Type)m.push({path:`Resources.${R}.Type`,message:"Resource Type is required",severity:"error"});if(!/^[a-zA-Z0-9]+$/.test(R))k.push({path:`Resources.${R}`,message:"Logical ID should only contain alphanumeric characters",severity:"warning"});if(t.Type&&!t.Type.startsWith("AWS::")&&!t.Type.startsWith("Custom::"))m.push({path:`Resources.${R}.Type`,message:'Resource Type must start with "AWS::" or "Custom::"',severity:"error"});if(t.Properties){for(let[D,L]of Object.entries(t.Properties))if(L===void 0||L===null)k.push({path:`Resources.${R}.Properties.${D}`,message:"Property has undefined or null value",severity:"warning"})}if(t.DependsOn){if(typeof t.DependsOn==="string"){if(t.DependsOn===R)m.push({path:`Resources.${R}.DependsOn`,message:"Resource cannot depend on itself",severity:"error"})}else if(Array.isArray(t.DependsOn)){if(t.DependsOn.includes(R))m.push({path:`Resources.${R}.DependsOn`,message:"Resource cannot depend on itself",severity:"error"})}}}function op(R,t,m,k){if(!t.Type)m.push({path:`Parameters.${R}.Type`,message:"Parameter Type is required",severity:"error"});let D=["String","Number","List<Number>","CommaDelimitedList","AWS::SSM::Parameter::Value<String>"];if(t.Type&&!D.includes(t.Type)&&!t.Type.startsWith("AWS::"))k.push({path:`Parameters.${R}.Type`,message:`Uncommon parameter type: ${t.Type}`,severity:"warning"});if(t.NoEcho&&t.Default)k.push({path:`Parameters.${R}`,message:"NoEcho parameters should not have default values",severity:"warning"})}function sp(R,t,m,k){if(!t.Value)m.push({path:`Outputs.${R}.Value`,message:"Output Value is required",severity:"error"})}function ap(R){let t={};for(let[z,Y]of Object.entries(R)){if(t[z]=[],Y.DependsOn){if(typeof Y.DependsOn==="string")t[z].push(Y.DependsOn);else if(Array.isArray(Y.DependsOn))t[z].push(...Y.DependsOn)}let X=Ja(Y);t[z].push(...X)}let m=new Set,k=new Set,D=[];function L(z,Y){m.add(z),k.add(z),Y.push(z);let X=t[z]||[];for(let ne of X)if(!m.has(ne)){if(L(ne,Y))return!0}else if(k.has(ne)){let re=Y.indexOf(ne);return D.push(...Y.slice(re),ne),!0}return Y.pop(),k.delete(z),!1}for(let z of Object.keys(t))if(!m.has(z)){if(L(z,[]))return D}return[]}function Ja(R,t=[]){if(typeof R!=="object"||R===null)return t;if(Array.isArray(R)){for(let m of R)Ja(m,t);return t}if(R.Ref&&typeof R.Ref==="string"&&!R.Ref.startsWith("AWS::"))t.push(R.Ref);if(R["Fn::GetAtt"]){let m=R["Fn::GetAtt"];if(Array.isArray(m)&&m.length>0)t.push(m[0])}for(let m of Object.values(R))Ja(m,t);return t}function is(R){let t=[],m=[],k=new TextEncoder().encode(R).length,D=51200,L=460800;if(k>51200)if(k>460800)t.push({path:"template",message:`Template size (${k} bytes) exceeds maximum allowed size of 460800 bytes`,severity:"error"});else m.push({path:"template",message:`Template size (${k} bytes) exceeds direct upload limit (51200 bytes). You must upload to S3 first.`,severity:"warning"});return{valid:t.length===0,errors:t,warnings:m}}function os(R){let t=[],m=[],k=R.Resources?Object.keys(R.Resources).length:0,D=R.Parameters?Object.keys(R.Parameters).length:0,L=R.Outputs?Object.keys(R.Outputs).length:0;if(k>500)t.push({path:"Resources",message:`Template has ${k} resources, exceeding the limit of 500`,severity:"error"});else if(k>200)m.push({path:"Resources",message:`Template has ${k} resources. Consider using nested stacks for better organization.`,severity:"warning"});if(D>200)t.push({path:"Parameters",message:`Template has ${D} parameters, exceeding the limit of 200`,severity:"error"});if(L>200)t.push({path:"Outputs",message:`Template has ${L} outputs, exceeding the limit of 200`,severity:"error"});return{valid:t.length===0,errors:t,warnings:m}}function En(R,t={}){let m={ok:!1,code:"unsupported_capability",exitCode:2,command:R,provider:t.provider,target:t.target,retryable:!1,nextAction:t.nextAction??"Run `cloud capabilities` and choose a supported driver operation.",message:t.message??`${R} is not supported by the selected provider driver.`};if(r(`${m.message} ${m.nextAction}`),t.setProcessExitCode!==!1)process.exitCode=m.exitCode;return m}function Ya(R){R.command("generate","Generate CloudFormation templates").alias("gen").option("--output <path>","Output directory for templates",{default:"cloudformation"}).option("--format <format>","Output format: json or yaml",{default:"json"}).option("--module <module>","Generate specific module only").action(async(t)=>{i("Generating CloudFormation Templates");let m=new s("Loading configuration...");m.start();try{let k=await Ne();m.succeed("Configuration loaded");let D=t?.output||"cloudformation",L=t?.format||"json",z=t?.env||"production";if(!cp(D))await lp(D,{recursive:!0});u("Generating infrastructure...");let Y=new Lo({config:k,environment:z,modules:t?.module?[t.module]:void 0}),X=new s("Generating CloudFormation template...");X.start(),Y.generate();let ne=L==="yaml"?Y.toYAML():Y.toJSON();X.succeed("Template generated"),u("Validating template...");let re=JSON.parse(Y.toJSON()),ie=rs(re),ae=is(ne),be=os(re),Ae=[...ie.errors,...ae.errors,...be.errors];if(Ae.length>0){r("Template validation failed:");for(let Le of Ae)r(` - ${Le.path}: ${Le.message}`);if(k.project?.slug===$n.project?.slug)a("No project config was loaded — this is the built-in default ("+`project.slug "${$n.project?.slug}"). Check that cloud.config.ts is in ${process.cwd()} and is not shadowed by another config file.`);process.exitCode=1;return}n("Template validated successfully");let le=[...ie.warnings,...ae.warnings,...be.warnings];if(le.length>0)for(let Le of le)a(` - ${Le.path}: ${Le.message}`);let ke=up(D,`${z}.${L}`);await dp(ke,ne),n(`Generated ${ke}`);let De=Y.getBuilder(),Se=Object.keys(De.getResources()).length;e(`
|
|
309
309
|
Generated ${Se} resources:`);let Re=De.getResources(),Pe={};for(let Le of Object.values(Re)){let Fe=Le.Type;Pe[Fe]=(Pe[Fe]||0)+1}let Ve=Object.entries(Pe).sort((Le,Fe)=>Fe[1]-Le[1]);for(let[Le,Fe]of Ve)e(` - ${Le}: ${Fe}`);e(`
|
|
310
310
|
Next steps:
|
|
311
311
|
1. Review the generated templates in ${D}/
|
|
312
|
-
2. Run 'cloud deploy' to deploy your infrastructure`)}catch(k){m.fail("Failed to generate templates"),r(k instanceof Error?k.message:"Unknown error")}}),R.command("generate:preview","Preview what will be generated").action(async()=>{
|
|
312
|
+
2. Run 'cloud deploy' to deploy your infrastructure`)}catch(k){m.fail("Failed to generate templates"),r(k instanceof Error?k.message:"Unknown error")}}),R.command("generate:preview","Preview what will be generated").action(async()=>{En("generate:preview",{message:"The legacy preview renderer has no stable output contract.",nextAction:"Run `cloud generate` into a temporary output directory and inspect the generated templates."})}),R.command("diff","Show diff between local config and deployed stack").alias("generate:diff").option("--stack <name>","Stack name to compare against").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{i("Infrastructure Diff");try{let m=await Ne(),k=t?.env||"production",{resolveProjectStackName:D}=await import("./chunk-sn1zde06.js"),L=t?.stack||D(m,k),z=m.project.region||"us-east-1";e(`Stack: ${L}`),e(`Region: ${z}`),e(`Environment: ${k}`),u("Generating template from configuration...");let Y=new Lo({config:m,environment:k});Y.generate();let X=Y.toJSON(),ne=JSON.parse(X);u("Fetching deployed template...");let re=new v(z),ie=null;try{let Re=await re.getTemplate(L);if(Re.TemplateBody)ie=JSON.parse(Re.TemplateBody)}catch(Re){if(Re.message?.includes("does not exist")){a(`Stack "${L}" does not exist yet`),e(`
|
|
313
313
|
This will be a new deployment with the following resources:`);let Pe=Object.keys(ne.Resources||{}).length;e(`
|
|
314
314
|
Resources to create: ${Pe}`);let Ve={};for(let Le of Object.values(ne.Resources||{})){let Fe=Le.Type;Ve[Fe]=(Ve[Fe]||0)+1}for(let[Le,Fe]of Object.entries(Ve).sort((He,wt)=>wt[1]-He[1]))e(` + ${Le}: ${Fe}`);e("\nRun `cloud deploy` to create this stack");return}throw Re}u("Comparing templates...");let ae=ie.Resources||{},be=ne.Resources||{},Ae=new Set(Object.keys(ae)),le=new Set(Object.keys(be)),ke=[];for(let Re of le)if(!Ae.has(Re))ke.push(Re);let De=[];for(let Re of Ae)if(!le.has(Re))De.push(Re);let Se=[];for(let Re of le)if(Ae.has(Re)){let Pe=JSON.stringify(ae[Re]),Ve=JSON.stringify(be[Re]);if(Pe!==Ve)Se.push(Re)}if(ke.length===0&&De.length===0&&Se.length===0){n(`
|
|
315
315
|
No changes detected - infrastructure is up to date`);return}if(e(`
|
|
316
316
|
Changes detected:
|
|
317
317
|
`),ke.length>0){n(`Resources to add (${ke.length}):`);for(let Re of ke){let Pe=be[Re].Type;e(` + ${Re} (${Pe})`)}console.log()}if(De.length>0){r(`Resources to remove (${De.length}):`);for(let Re of De){let Pe=ae[Re].Type;e(` - ${Re} (${Pe})`)}console.log()}if(Se.length>0){a(`Resources to modify (${Se.length}):`);for(let Re of Se){let Pe=be[Re].Type;e(` ~ ${Re} (${Pe})`)}console.log()}e("Summary:"),e(` - Add: ${ke.length}`),e(` - Remove: ${De.length}`),e(` - Modify: ${Se.length}`),e("\nRun `cloud deploy` to apply these changes")}catch(m){r(`Diff failed: ${m.message}`)}})}function Wi(R,t=0){let m=Number(R);return Number.isFinite(m)?m:t}function Za(R){return Wi(R)*1024}function mp(R){let t=R?.date?.trim(),m=R?.time?.trim();if(!t||!m)return null;let k=new Date(`${t}T${m}Z`);return Number.isFinite(k.getTime())?k.toISOString():null}function pp(R,t,m){let k;try{k=JSON.parse(R)}catch{return[]}let D=(k?.sysstat?.hosts??[]).flatMap((z)=>z.statistics??[]),L=[];for(let z of D){let Y=mp(z.timestamp);if(!Y)continue;let X=new Date(Y).getTime();if(X<t.getTime()||X>m.getTime())continue;let ne=z["cpu-load"]?.find((ie)=>ie.cpu==="all")??z["cpu-load"]?.[0],re=Wi(ne?.idle,100);L.push({timestamp:Y,cpuUsedPercent:Math.max(0,Math.min(100,100-re)),cpuUserPercent:Wi(ne?.user),cpuSystemPercent:Wi(ne?.system),cpuIowaitPercent:Wi(ne?.iowait),memoryUsedPercent:Wi(z.memory?.["memused-percent"]),memoryUsedBytes:Za(z.memory?.memused),memoryAvailableBytes:Za(z.memory?.avail),swapUsedPercent:Wi(z.memory?.["swpused-percent"]),swapUsedBytes:Za(z.memory?.swpused)})}return L.sort((z,Y)=>z.timestamp.localeCompare(Y.timestamp))}function fp(R){return Object.fromEntries(Object.entries(R.time_series??{}).map(([t,m])=>[t,(m.values??[]).map(([k,D])=>({timestamp:new Date(Number(k)*1000).toISOString(),value:Wi(D,Number.NaN)})).filter((k)=>Number.isFinite(k.value))]))}function hd(R){return R.length?R.reduce((t,m)=>t+m,0)/R.length:null}function Qa(R){return R.length?Math.max(...R):null}function gp(R){return R.length?Math.min(...R):null}async function yd(R){let[t,m]=await Promise.all([R.client.getServerMetrics(R.serverId,{types:["cpu","disk","network"],start:R.from,end:R.to,step:R.step}),_e(R.remote.host,"LC_ALL=C sadf -j -- -u -r -S",R.remote).catch(()=>"")]),k=pp(m,R.from,R.to),D=fp(t),z=(D.cpu??[]).map((ie)=>Math.max(0,Math.min(100,ie.value/Math.max(1,R.cores)))),Y=k.map((ie)=>ie.cpuUsedPercent),X=z.length?z:Y,ne=k.map((ie)=>ie.memoryUsedPercent),re=k.map((ie)=>ie.swapUsedPercent);return{from:R.from.toISOString(),to:R.to.toISOString(),cores:R.cores,host:k,provider:D,summary:{cpuAveragePercent:hd(X),cpuPeakPercent:Qa(X),memoryAveragePercent:hd(ne),memoryPeakPercent:Qa(ne),memoryMinimumAvailableBytes:gp(k.map((ie)=>ie.memoryAvailableBytes)),swapPeakPercent:Qa(re)}}}function hp(R){if(R.length===0)throw Error("A server name cannot be empty.");if(R.length>253)throw Error(`'${R}' is longer than the 253 characters a hostname allows.`);for(let t of R.split(".")){if(t.length===0)throw Error(`'${R}' has an empty label — a hostname cannot contain '..'.`);if(t.length>63)throw Error(`'${t}' is longer than the 63 characters a hostname label allows.`);if(!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(t))throw Error(`'${t}' is not a valid hostname label. Use letters, digits and hyphens, not starting or ending with a hyphen.`)}}async function vd(R,t,m){if(hp(t),R===t)throw Error(`'${R}' is already named that.`);if((await m.takenNames()).some((L)=>L===t&&L!==R))throw Error(`'${t}' is already taken. Server names have to be unique within a provider project.`);let D=[];if(m.providerName&&m.renameProvider){let{providerName:L,renameProvider:z}=m;D.push({id:"provider",title:"Rename the server at the provider",change:{from:R,to:t},satisfied:async()=>await L()===t,apply:()=>z(t)})}if(m.stateName&&m.writeStateName){let{stateName:L,writeStateName:z}=m;D.push({id:"state-pin",title:"Update the recorded name in the local driver state",change:{from:R,to:t},satisfied:async()=>{let Y=await L();return Y===void 0||Y===t},apply:()=>z(t)})}if(m.remoteHostname&&m.setRemoteHostname){let{remoteHostname:L,setRemoteHostname:z}=m;D.push({id:"hostname",title:"Set the hostname on the box",change:{from:R,to:t},satisfied:async()=>await L()===t,apply:()=>z(t)})}return D.push({id:"inventory",title:"Rename the fleet inventory record",change:{from:R,to:t},satisfied:async()=>m.inventoryName()===t,apply:async()=>{await m.renameInventory(t)}}),{operation:"server:rename",target:R,steps:D}}function bd(R){let t=`'${R.replace(/'/g,`'"'"'`)}'`;return["set -eu",'TS_CLOUD_OLD="$(hostname)"',`hostnamectl set-hostname ${t} 2>/dev/null || { echo ${t} > /etc/hostname && hostname ${t}; }`,'if grep -q "^127.0.1.1" /etc/hosts; then',` TS_CLOUD_HOSTS="$(sed -E "s/^127\\.0\\.1\\.1.*/127.0.1.1\\t${R}/" /etc/hosts)"`,` printf '%s\\n' "$TS_CLOUD_HOSTS" > /etc/hosts`,"else",` printf '127.0.1.1\\t%s\\n' ${t} >> /etc/hosts`,"fi",'printf "%s -> %s\\n" "$TS_CLOUD_OLD" "$(hostname)"'].join(`
|
|
318
|
-
`)}async function yp(){let R=await Ne(),t=y(process.cwd(),R),m=new xe(t.store),k=new Ln(m,[new ct("aws"),new ct("hetzner"),new ct("ssh")]);return{controlPlane:t,store:m,service:k}}async function hi(R){let t=await yp();try{return await R(t)}finally{t.controlPlane.store.close()}}var Vi=(R,t)=>{let m=R.store.list(R.controlPlane.project.id).find((k)=>k.id===t||k.name===t);if(!m)throw Error(`Server ${t} was not found.`);return m},Cn=(R)=>{r(R instanceof Error?R.message:String(R)),process.exitCode=1};function Xa(R,t){let m=Number.parseInt(R??"",10);return Number.isFinite(m)&&m>=0?m:t}function vp(R="3h"){let t=R.match(/^(\d+)(m|h)$/);if(!t)throw Error("--range must use minutes or hours, for example 30m or 3h.");let m=Number(t[1])*(t[2]==="h"?3600000:60000);if(m<300000||m>86400000)throw Error("--range must be between 5 minutes and 24 hours.");return m}function ss(R,t="%"){return R==null?"unavailable":`${R.toFixed(1)}${t}`}async function bp(R,t){let m=await Ne();if(m.cloud?.provider!=="hetzner")throw Error("server:monitoring currently requires the Hetzner provider.");let k=t.env??process.env.CLOUD_ENV??"production",D=T(m,k),L=await ge(D);if(!L?.serverId)throw Error(`No pinned Hetzner server was found for ${D}.`);if(![L.serverName,String(L.serverId),L.stackName].includes(R))throw Error(`Refusing to read ${R}: project state is pinned to ${L.serverName}.`);let z=gt(m),Y=new Oe({apiToken:gr(z.apiToken,m)}),X=await Y.getServer(L.serverId),ne=L.publicIp??X.public_net.ipv4?.ip;if(!ne)throw Error(`Server ${X.name} has no public IPv4 address.`);let re=new Date,ie=new Date(re.getTime()-vp(t.range)),ae=Math.min(3600,Math.max(60,Xa(t.step,60))),be=await yd({client:Y,serverId:X.id,cores:Math.max(1,Number(X.server_type.cores)||1),from:ie,to:re,step:ae,remote:{host:ne,user:L.sshUser??z.sshUser,identityFile:z.sshPrivateKeyPath,connectTimeoutSec:15}});if(t.json){console.log(JSON.stringify({schemaVersion:1,server:X.name,result:be},null,2));return}c(["Server","Window","CPU average / peak","RAM average / peak","Minimum available","Swap peak"],[[X.name,`${be.from} to ${be.to}`,`${ss(be.summary.cpuAveragePercent)} / ${ss(be.summary.cpuPeakPercent)}`,`${ss(be.summary.memoryAveragePercent)} / ${ss(be.summary.memoryPeakPercent)}`,be.summary.memoryMinimumAvailableBytes==null?"unavailable":`${(be.summary.memoryMinimumAvailableBytes/1073741824).toFixed(2)} GB`,ss(be.summary.swapPeakPercent)]])}function wd(R){let t=R.targetType.prices?.find((m)=>m.location===R.location);return{serverId:R.serverId,serverName:R.serverName,location:R.location,from:R.currentType.name,to:R.targetType.name,capacityAvailable:R.capacityAvailable,alreadyComplete:R.alreadyComplete,upgradeDisk:R.upgradeDisk,target:{cores:R.targetType.cores,memoryGb:R.targetType.memory,diskGb:R.targetType.disk,architecture:R.targetType.architecture,cpuType:R.targetType.cpu_type},price:t?{hourlyNet:t.price_hourly.net,hourlyGross:t.price_hourly.gross,monthlyNet:t.price_monthly.net,monthlyGross:t.price_monthly.gross}:void 0}}function wp(R){let t=wd(R);c(["Server","Location","Resize","Capacity","CPU / RAM / disk","Monthly"],[[`${t.serverName} (${t.serverId})`,t.location,`${t.from} -> ${t.to}`,t.capacityAvailable?"available":"waiting",`${t.target.cores??0} / ${t.target.memoryGb??0} GB / ${t.target.diskGb??0} GB`,t.price?.monthlyGross??t.price?.monthlyNet??"not reported"]])}async function Sp(R,t,m){let k=await Ne();if(k.cloud?.provider!=="hetzner")throw Error("server:resize currently requires the Hetzner provider.");let D=m.env??process.env.CLOUD_ENV??"production",L=T(k,D),z=await ge(L),Y=gt(k),X=new Oe({apiToken:gr(Y.apiToken,k)}),re=z&&(R===z.serverName||R===String(z.serverId)||R===z.stackName)?await X.getServer(z.serverId):(await X.listServers()).find((Le)=>Le.name===R||String(Le.id)===R);if(!re)throw Error(`Hetzner server ${R} was not found.`);if(z?.serverId&&z.serverId!==re.id)throw Error(`Refusing to resize ${R}: project state is pinned to server ${z.serverId}.`);let ie=Xe(t),ae=m.upgradeDisk!==!1,be=await Qe(X,re.id,ie,ae);if(m.json)console.log(JSON.stringify({schemaVersion:1,plan:wd(be)},null,2));else wp(be);if(!m.apply){if(!m.json)e(`Preview only. Re-run with --apply --confirm ${re.name} to execute.`);return}if(m.confirm!==re.name)throw Error(`Resize requires --confirm ${re.name}.`);let Ae=z?.publicIp??re.public_net.ipv4?.ip;if(!Ae)throw Error(`Server ${re.name} has no public IPv4 address for verification.`);let le={host:Ae,user:z?.sshUser??Y.sshUser,identityFile:Y.sshPrivateKeyPath,connectTimeoutSec:15,sshTimeoutMs:600000,routeTimeoutMs:20000},ke=await fs(L),De=new Date().toISOString(),Se={schemaVersion:1,operationId:ke?.targetType===ie&&ke.serverId===re.id?ke.operationId:crypto.randomUUID(),stackName:L,serverId:re.id,serverName:re.name,sourceType:be.currentType.name,targetType:ie,upgradeDisk:ae,phase:"planning",status:"running",attempts:ke?.targetType===ie?ke.attempts:0,startedAt:ke?.targetType===ie?ke.startedAt:De,updatedAt:De,manifest:ke?.targetType===ie?ke.manifest:void 0};await ht(Se);let Re=Xa(m.pollSeconds,900)*1000,Pe=Xa(m.maxWaitMinutes,0)*60000,Ve=Date.now();for(;;){let Le=await Qe(X,re.id,ie,ae);if(!Le.capacityAvailable&&!Le.alreadyComplete){if(Se={...Se,phase:"waiting-capacity",status:"waiting-capacity",updatedAt:new Date().toISOString()},await ht(Se),!m.wait){if(!m.json)e(`${ie} has no capacity in ${Le.location}. The server remains running and unchanged. Use --wait to poll safely.`);return}if(Pe>0&&Date.now()-Ve>=Pe)throw Error(`Timed out waiting for ${ie} capacity in ${Le.location}.`);if(!m.json)e(`Waiting ${Math.round(Re/1000)} seconds for ${ie} capacity...`);await Bun.sleep(Re);continue}let Fe=await hs(L);try{let He=await(async()=>{try{return Se={...Se,status:"running",attempts:Se.attempts+1,updatedAt:new Date().toISOString()},await ht(Se),await fr({client:X,serverId:re.id,targetType:ie,upgradeDisk:ae,resumeFromOff:Se.phase==="shutting-down"||Se.phase==="resizing"||Se.phase==="powering-on"||Se.phase==="verifying",manifest:Se.manifest,hooks:{preflight:async()=>{let wt=await mi(le);return Se={...Se,manifest:wt,updatedAt:new Date().toISOString()},await ht(Se),wt},afterBoot:async(wt,Dt,it,xt)=>gi(le,Dt,it,xt),onPhase:async(wt,Dt)=>{if(Se={...Se,phase:wt,status:wt==="waiting-capacity"?"waiting-capacity":wt==="complete"?"completed":"running",updatedAt:new Date().toISOString()},await ht(Se),!m.json)e(`${wt}${Dt?` ${JSON.stringify(Dt)}`:""}`)}}})}finally{await Fe()}})();if(Se={...Se,status:He.status==="completed"||He.status==="already-complete"?"completed":He.status==="recovered"?"recovered":"waiting-capacity",lastError:He.status==="recovered"?He.error:void 0,updatedAt:new Date().toISOString()},await ht(Se),m.json)console.log(JSON.stringify({schemaVersion:1,result:He},null,2));else if(He.status==="completed"||He.status==="already-complete")n(`${re.name} is on ${ie} and passed full post-boot verification.`);else if(He.status==="recovered"&&He.retryable&&m.wait){e(`Capacity race recovered safely. Waiting ${Math.round(Re/1000)} seconds before retry.`),await Bun.sleep(Re);continue}else if(He.status==="recovered")throw Error(`Resize did not complete and the original server was recovered: ${He.error}`);return}catch(He){throw Se={...Se,status:"failed",lastError:He instanceof Error?He.message:String(He),updatedAt:new Date().toISOString()},await ht(Se),He}}}async function Cp(R,t){let m=await Ne();if(m.cloud?.provider!=="hetzner")throw Error("server:optimize currently requires the Hetzner provider.");let k=t.env??process.env.CLOUD_ENV??"production",D=T(m,k),L=await ge(D);if(!L?.serverId)throw Error(`No pinned Hetzner server was found for ${D}.`);if(![L.serverName,String(L.serverId),L.stackName].includes(R))throw Error(`Refusing to optimize ${R}: project state is pinned to ${L.serverName}.`);let z=gt(m),Y=new Oe({apiToken:gr(z.apiToken,m)}),X=await Y.getServer(L.serverId),ne=L.publicIp??X.public_net.ipv4?.ip;if(!ne)throw Error(`Server ${X.name} has no public IPv4 address.`);let re=co(m);if(t.json)console.log(JSON.stringify({schemaVersion:1,server:X.name,plan:re},null,2));else c(["Server","Firewall ports","Monitoring","Auto updates","Swap","SSH passwords"],[[X.name,re.firewallPorts.join(","),re.monitoring?"on":"off",re.autoUpdates?"on":"off",`${re.swapGb} GB`,"disabled"]]);if(!t.apply){if(!t.json)e(`Preview only. Re-run with --apply --confirm ${X.name} to execute.`);return}if(t.confirm!==X.name)throw Error(`Optimization requires --confirm ${X.name}.`);let ie={host:ne,user:L.sshUser??z.sshUser,identityFile:z.sshPrivateKeyPath,connectTimeoutSec:15},ae=await on(ie),be=ae.routeProbes.filter((it)=>!it.ok),Ae=m.infrastructure?.compute,le=Z(Ae)?new Set(["nginx.service","bun-gateway.service","ts-cloud-nginx.service"]):new Set,ke=ae.failedUnits.filter((it)=>!le.has(it));if(ke.length>0)throw Error(`Preflight found failed services: ${ke.join(", ")}`);if(be.length>0)throw Error(`Preflight found unhealthy routes: ${be.map((it)=>it.domain).join(", ")}`);let De=Z(Ae)?[]:Object.values(m.sites??{}).flatMap((it)=>typeof it?.port==="number"&&![80,443].includes(it.port)?[it.port]:[]),Se=nn({allowSsh:Ae?.allowSsh!==!1,sitePorts:De,allowedPorts:Ae?.firewall?.allowedPorts}),Re=(L.firewallId?(await Y.listFirewalls()).find((it)=>it.id===L.firewallId):void 0)??(await Y.listFirewalls()).find((it)=>it.name===`${m.project.slug}-${k}-app-fw`);if(!Re)throw Error(`Managed firewall for ${X.name} was not found.`);let Pe=await Y.setFirewallRules(Re.id,Se);await Promise.all(Pe.map((it)=>Y.waitForAction(it.id))),await ps(m,ie);let[Ve,Le]=await Promise.all([on(ie),ms(ie)]),Fe=gs(re,Le),He=yc(ae,Ve),wt=Ve.routeProbes.filter((it)=>!it.ok);if(He.stoppedServices.length>0)Fe.push(`services stopped: ${He.stoppedServices.join(", ")}`);if(He.changedRouteFragments.length>0)Fe.push(`route fragments changed: ${He.changedRouteFragments.join(", ")}`);if(He.missingRouteIds.length>0)Fe.push(`routes disappeared: ${He.missingRouteIds.join(", ")}`);if(He.changedReleaseLinks.length>0)Fe.push(`release links changed: ${He.changedReleaseLinks.join(", ")}`);if(He.missingData.length>0)Fe.push(`databases or volumes disappeared: ${He.missingData.join(", ")}`);if(wt.length>0)Fe.push(`unhealthy routes: ${wt.map((it)=>it.domain).join(", ")}`);if(Fe.length>0)throw Error(`Host optimization verification failed: ${Fe.join("; ")}`);let Dt={serverId:X.id,serverName:X.name,services:Ve.runningServices.length,routes:Ve.routeIds.length,healthyDomains:Ve.routeProbes.length,releaseLinks:Ve.releaseLinks.length,databasesAndVolumes:Ve.dataCatalog.length,report:Le};if(t.json)console.log(JSON.stringify({schemaVersion:1,result:Dt},null,2));else n(`${X.name} passed full host, rpx route, service, release, and data verification.`)}async function $p(R,t,m){let k=await Ne(),D=m.env??"production",L=T(k,D);await hi(async(z)=>{let Y=Vi(z,R),X=z.store.list(z.controlPlane.project.id,!0),ne=Y.provider==="hetzner"&&Y.providerId?(()=>{try{let Re=gt(k);return new Oe({apiToken:gr(Re.apiToken,k)})}catch{return null}})():null,re=Y.providerId?Number(Y.providerId):Number.NaN,ie=await ge(L),ae=ie!=null&&ie.serverId===re,be=new Gr,Ae=Y.trustState==="pinned",le={takenNames:async()=>{let Re=X.map((Pe)=>Pe.name);if(ne)Re.push(...(await ne.listServers()).map((Pe)=>Pe.name));return Re},inventoryName:()=>z.store.get(Y.id)?.name??Y.name,renameInventory:(Re)=>{z.store.update(Y.id,{name:Re})},...ne&&Number.isFinite(re)?{providerName:async()=>(await ne.getServer(re)).name,renameProvider:async(Re)=>{await ne.renameServer(re,Re)}}:{},...ae?{stateName:async()=>(await ge(L))?.serverName,writeStateName:async(Re)=>{let Pe=await ge(L);if(Pe)await ft(L,{...Pe,serverName:Re})}}:{},...Ae?{remoteHostname:async()=>{let Re=await be.exec(Y,"hostname");return Re.code===0?Re.stdout.trim():void 0},setRemoteHostname:async(Re)=>{let Pe=await be.exec(Y,bd(Re));if(Pe.code!==0)throw Error(Pe.stderr.trim()||`hostname exited ${Pe.code}`)}}:{}},ke=await vd(Y.name,t,le),De=await Us(ke);if(!m.apply){let Re=[ne?"":"provider record (no provider id or no API token)",ae?"":"local state pin (none points at this server)",Ae?"":"box hostname (host key is not pinned — run server:validate first)"].filter(Boolean);if(m.json){console.log(JSON.stringify({schemaVersion:1,operation:ke.operation,target:ke.target,rename:{from:Y.name,to:t},notCovered:Re,steps:De.map((Pe)=>({id:Pe.step.id,title:Pe.step.title,state:Pe.state,change:Pe.step.change,reason:Pe.reason}))},null,2));return}for(let Pe of Ws(ke,De))console.log(Pe);for(let Pe of Re)console.log(` not covered: ${Pe}`);console.log(" Re-run with --apply to perform it.");return}let Se=await Vs(ke,De,{log:(Re)=>e(Re),audit:(Re)=>z.controlPlane.store.appendEvent({projectId:z.controlPlane.project.id,resourceId:Y.resourceId,type:`${Re.operation}.${Re.step}.${Re.state}`,level:Re.state==="failed"?"error":"info",payload:{target:Re.target,renameTo:t,...Re.error?{error:Re.error}:{}}})});if(m.json)console.log(JSON.stringify({schemaVersion:1,outcome:Se},null,2));if(!Se.success){let Re=Se.steps.find((Pe)=>Pe.state==="failed");throw Error(`${ke.operation} stopped at '${Re?.title}': ${Re?.error}. Earlier steps stayed applied; re-run the same command to continue from here.`)}if(!m.json)n(`Renamed ${Y.name} to ${t}.`)})}function ec(R){R.command("capabilities [server]","Show provider and target operation support").option("--json","Print structured JSON").action(async(t,m)=>{try{await hi(async(k)=>{let D=t?[Vi(k,t)]:k.store.list(k.controlPlane.project.id),L=D.flatMap((z)=>Object.entries(z.capabilities).map(([Y,X])=>({server:z.name,provider:z.provider,action:Y,...X})));if(m.json)console.log(JSON.stringify({schemaVersion:1,targets:L},null,2));else c(["Server","Provider","Action","Support","Explanation"],L.map((z)=>[z.server,z.provider,z.action,z.supported?"supported":"unsupported",z.reason??"—"]))})}catch(k){Cn(k)}}),R.command("server:resize <name> <type>","Safely resize Hetzner compute with full host verification").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Execute the reviewed resize plan").option("--confirm <name>","Exact server name required for mutation").option("--upgrade-disk","Permanently expand the root disk to the target size",{default:!0}).option("--wait","Poll capacity without taking the server offline until placement is available").option("--poll-seconds <seconds>","Capacity retry interval",{default:"900"}).option("--max-wait-minutes <minutes>","Stop waiting after this many minutes; 0 waits indefinitely",{default:"0"}).option("--json","Print structured JSON").action(async(t,m,k)=>{try{await Sp(t,m,k)}catch(D){Cn(D)}}),R.command("server:optimize <name>","Reconcile and verify a production Hetzner host").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Apply the reviewed host optimization plan").option("--confirm <name>","Exact server name required for mutation").option("--json","Print structured JSON").action(async(t,m)=>{try{await Cp(t,m)}catch(k){Cn(k)}}),R.command("server:monitoring <name>","Read historical Hetzner CPU, RAM, swap, disk, and network metrics").option("--env <environment>","Deployment environment",{default:"production"}).option("--range <duration>","Historical window from 5m to 24h",{default:"3h"}).option("--step <seconds>","Hetzner provider sample interval",{default:"60"}).option("--json","Print structured JSON").action(async(t,m)=>{try{await bp(t,m)}catch(k){Cn(k)}}),R.command("server:list","List fleet servers").option("--json","Print structured JSON").action(async(t)=>{try{await hi(async(m)=>{let k=m.store.list(m.controlPlane.project.id);if(t.json)console.log(JSON.stringify(k,null,2));else c(["Name","Provider ID","Provider / region","Status / trust","Roles","CPU / memory","Heartbeat"],k.map((D)=>[D.name,D.providerId??"—",`${D.provider} / ${D.region??"external"}`,`${D.status} / ${D.trustState}`,D.roles.join(","),`${D.capacity.cpu??0} / ${D.capacity.memoryBytes??0}`,D.heartbeatAt??"never"]))})}catch(m){Cn(m)}}),R.command("server:create <name>","Enroll a provisioned or existing server").option("--provider <provider>","aws, hetzner, or ssh",{default:"ssh"}).option("--provider-id <id>","Stable provider identity").option("--endpoint <host>","SSH hostname or IP").option("--user <user>","Non-root SSH user",{default:"deploy"}).option("--credential-ref <ref>","Secret reference",{default:"secret://fleet/agent"}).option("--region <region>","Provider region").option("--roles <roles>","Comma-separated roles",{default:"application"}).option("--labels <labels>","Comma-separated key=value labels").action(async(t,m)=>{try{await hi(async(k)=>{let D=m.provider;if(!["aws","hetzner","ssh"].includes(D)||!m.endpoint)throw Error("A supported --provider and --endpoint are required; provisioning and enrollment are separate operations.");let L=k.service.enroll({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,name:t,provider:D,providerId:m.providerId,endpoint:m.endpoint,sshUser:m.user??"deploy",credentialRef:m.credentialRef??"secret://fleet/agent",region:m.region,roles:String(m.roles??"application").split(","),labels:Object.fromEntries(String(m.labels??"").split(",").map((z)=>z.split("=")).filter((z)=>z[0]&&z[1]))});n(`Enrolled ${L.name} as ${L.id}; no remote mutation was performed.`)})}catch(k){Cn(k)}}),R.command("server:validate <name>","Pin trust and validate a server").option("--accept-host-key <fingerprint>","Accept reviewed rotation").option("--json","Print JSON").action(async(t,m)=>{try{await hi(async(k)=>{let D=Vi(k,t);if(D=await k.service.test(D.id),D.trustState==="rotation_pending"){if(!m.acceptHostKey)throw Error(`Host key changed to ${D.pendingHostKey}; review it first.`);D=k.service.reviewHostKey(D.id,m.acceptHostKey)}let L=await k.service.validate(D.id);if(m.json)console.log(JSON.stringify(L.validation,null,2));else c(["Severity","Code","Finding","Remediation"],(L.validation?.findings??[]).map((z)=>[z.severity,z.code,z.message,z.remediation??"—"]));if(!L.validation?.valid)process.exitCode=1})}catch(k){Cn(k)}}),R.command("server:bootstrap <name>","Preview or queue bootstrap").option("--apply","Apply reviewed plan").action(async(t,m)=>{try{await hi(async(k)=>{let D=k.service.bootstrap(Vi(k,t).id,!!m.apply);if(D.preview)c(["Step"],D.steps.map((L)=>[L]));else n(`Bootstrap queued: ${D.operation?.id}`)})}catch(k){Cn(k)}}),R.command("server:rename <name> <new-name>","Rename a server in place, without recreating it").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Perform the reviewed rename plan").option("--json","Print structured JSON").action(async(t,m,k)=>{try{await $p(t,m,k)}catch(D){Cn(D)}}),R.command("server:drain <name>","Drain without terminating").option("--complete","Mark movement complete").action(async(t,m)=>{try{await hi(async(k)=>n(k.service.drain(Vi(k,t).id,!!m.complete).status))}catch(k){Cn(k)}}),R.command("server:uncordon <name>","Return a server to scheduling").action(async(t)=>{try{await hi(async(m)=>{m.service.uncordon(Vi(m,t).id),n(`Uncordoned ${t}.`)})}catch(m){Cn(m)}}),R.command("server:archive <name>","Archive inventory without termination").option("--confirm <name>","Exact name").action(async(t,m)=>{try{await hi(async(k)=>{k.service.archive(Vi(k,t).id,m.confirm??""),n(`Archived ${t}; provider infrastructure was not terminated.`)})}catch(k){Cn(k)}}),R.command("server:ssh <name>","Open strict SSH using the enrolled endpoint").action(async(t)=>{try{await hi(async(m)=>{let k=Vi(m,t);if(k.trustState!=="pinned")throw Error("Validate and pin the server host key first.");let D=Bun.spawn(["ssh","-p",String(k.sshPort),"-o","StrictHostKeyChecking=yes",`${k.sshUser}@${k.endpoint}`],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}),L=await D.exited;if(L)process.exitCode=L})}catch(m){Cn(m)}});for(let[t,m]of[["server:logs <name>","Use the runtime log service"],["server:deploy <name>","Use immutable release deployment"],["server:reboot <name>","Reboot provider compute"],["server:destroy <name>","Terminate provider compute"],["server:recipe <name> <recipe>","Apply a server recipe"],["server:firewall:add <name> <rule>","Add firewall rule"],["server:firewall:list <name>","List firewall rules"],["server:firewall:remove <name> <rule>","Remove firewall rule"],["server:ssl:install <domain>","Install TLS"],["server:ssl:renew <domain>","Renew TLS"],["server:snapshot <name>","Create provider snapshot"],["server:snapshot:restore <name> <snapshot-id>","Restore provider snapshot"],["server:update <name>","Update OS packages"],["server:secure <name>","Apply OS hardening"]])R.command(t,m).action(async(...k)=>Pn(t.split(" ")[0],{target:String(k[0]??""),nextAction:"Use the dashboard capability view or the corresponding runtime, release, firewall, backup, or maintenance service."}))}import{existsSync as Sd,mkdirSync as kp,readFileSync as Ap,writeFileSync as Rp}from"node:fs";import{join as Cd,resolve as Ip}from"node:path";var tc=(R)=>{r(R instanceof Error?R.message:String(R)),process.exitCode=1};async function Pp(){try{return await Ne()}catch{return}}function xp(R){if(R===void 0)return;let t=Number(R);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`--port must be a port number, not '${R}'.`);return t}function $d(R,t,m){let k=R?.ssh?.hosts?.find((D)=>D.host===t);return new vr({hosts:t?[k??{host:t}]:R?.ssh?.hosts,user:m.user,port:xp(m.port),privateKeyPath:m.key,hostKey:R?.ssh?.hostKey,sudo:R?.ssh?.sudo,profile:m.profile??R?.ssh?.profile,publicIp:R?.ssh?.publicIp,lan:R?.ssh?.lan})}var kd=1073741824;function nc(R){R.command("ssh:preflight [host]","Check a bring-your-own Linux host before adopting it").option("--user <user>","SSH user (default: ssh.hosts[].user, then root)").option("--port <port>","SSH port (default: ssh.hosts[].port, then 22)").option("--key <path>","SSH private key (default: ssh.hosts[].privateKeyPath, then ~/.ssh/id_ed25519)").option("--profile <profile>","raspberry-pi or generic (default: ssh.profile, then generic)").option("--json","Print the facts and findings as JSON").action(async(t,m)=>{try{let k=await Pp(),D=$d(k,t,m),L=t??D.targetHost(),z=await D.ensureHostKey(L),{facts:Y,findings:X}=await D.preflight(L);if(m.json)console.log(JSON.stringify({schemaVersion:1,host:L,hostKeyFingerprint:z.fingerprint,facts:Y,findings:X},null,2));else{if(i(`Preflight ${L}`),z.pinnedNow)e(`Pinned host key ${z.fingerprint}`);if(e(`${Y.os} on ${Y.arch}, ${Y.cpuCores} cores, ${(Y.memoryBytes/kd).toFixed(1)} GiB memory, ${(Y.diskFreeBytes/kd).toFixed(1)} GiB free`),Y.piModel)e(Y.piModel);if(Y.lanIp)e(`LAN address ${Y.lanIp}`);if(console.log(sn(X)),Sr(X))r("The host is not ready; fix the errors above and run the preflight again.");else n("The host is ready to adopt.")}if(Sr(X))process.exitCode=1}catch(k){tc(k)}}),R.command("ssh:bootstrap","Adopt the configured host: pin its key, run the preflight, then bootstrap it").option("--env <environment>","Deployment environment",{default:"production"}).option("--dry-run","Print the bootstrap script instead of running it").action(async(t)=>{try{let m=await Ne(),k=t.env??"production";if(t.dryRun){let z=br(m),[Y]=z.hosts;console.log(yr({config:m,environment:k,profile:z.profile,sudoUser:Y.user!=="root"?Y.user:void 0,lan:z.lan}));return}let D=$d(m,void 0,{});i(`Adopt ${D.targetHost()}`);let L=await D.provisionComputeInfrastructure({config:m,environment:k});n(`${L.appPublicIp} is bootstrapped; deploy with: cloud deploy --env ${k}`)}catch(m){tc(m)}}),R.command("ssh:first-boot","Write cloud-init first-boot files for a freshly flashed Raspberry Pi OS or Ubuntu image").option("--os <os>","raspberry-pi-os or ubuntu",{default:"raspberry-pi-os"}).option("--out <dir>","Directory to write user-data, network-config and meta-data into (never a mounted card; copy them yourself)").option("--hostname <name>","Hostname of the new host").option("--user <user>","Deploy user created on first boot (default: ssh.hosts[].user, then pi)").option("--key <path>","Public key authorized for the user",{default:"~/.ssh/id_ed25519.pub"}).option("--wifi-ssid <ssid>","Join this Wi-Fi network on first boot").option("--wifi-passphrase-env <name>","Environment variable holding the Wi-Fi passphrase",{default:"WIFI_PASSWORD"}).option("--wifi-country <cc>","Wi-Fi regulatory domain, e.g. DE").option("--timezone <tz>","IANA timezone, e.g. Europe/Berlin").option("--locale <locale>","System locale, e.g. en_US.UTF-8").option("--env <environment>","Deployment environment the embedded bootstrap is built for",{default:"production"}).option("--force","Overwrite files already in --out").action(async(t)=>{try{if(!t.out)throw Error("--out <dir> is required.");if(!t.hostname)throw Error("--hostname <name> is required.");let m=t.os??"raspberry-pi-os";if(!(m in so))throw Error(`--os must be raspberry-pi-os or ubuntu, not '${m}'.`);let k=await Ne(),D=t.env??"production",L=br(k,{hosts:k.ssh?.hosts?.length?void 0:[{host:t.hostname}],profile:m==="raspberry-pi-os"?"raspberry-pi":void 0}),z=t.user??(k.ssh?.hosts?.[0]?.user??"pi"),Y=rn(t.key??"~/.ssh/id_ed25519.pub");if(!Sd(Y))throw Error(`Public key not found at ${Y}; pass --key <path to a .pub file>.`);let X=Ap(Y,"utf8").trim(),ne;if(t.wifiSsid){let Ae=t.wifiPassphraseEnv??"WIFI_PASSWORD",le=process.env[Ae];if(!le)throw Error(`--wifi-ssid needs the passphrase in $${Ae} (set it in the environment, not on the command line).`);if(!t.wifiCountry)throw Error("--wifi-country <cc> is required with --wifi-ssid.");ne={ssid:t.wifiSsid,passphrase:le,country:t.wifiCountry}}let re=yr({config:k,environment:D,profile:L.profile,sudoUser:z!=="root"?z:void 0,lan:L.lan}),ie=ao({hostname:t.hostname,user:z,publicKey:X,timezone:t.timezone,locale:t.locale,wifi:ne},re,{os:m}),ae=Ip(t.out);kp(ae,{recursive:!0});let be=Object.keys(ie.files).filter((Ae)=>Sd(Cd(ae,Ae)));if(be.length>0&&!t.force)throw Error(`${be.join(", ")} already exist in ${ae}; pass --force to overwrite.`);for(let[Ae,le]of Object.entries(ie.files))Rp(Cd(ae,Ae),le,{mode:384});i(`First boot for ${t.hostname}`),n(`Wrote ${Object.keys(ie.files).join(", ")} to ${ae}`),console.log(ie.instructions)}catch(m){tc(m)}})}async function Dp(){let R=await Ne(),t=y(process.cwd(),R),m=new Mn(t.store),k=new Un(m,new xe(t.store));return{controlPlane:t,store:m,service:k}}async function as(R){let t=await Dp();try{await R(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var Ks=(R)=>Object.fromEntries(String(R??"").split(",").map((t)=>t.split("=")).filter((t)=>t[0]&&t[1])),Ad=(R)=>Object.fromEntries(Object.entries(Ks(R)).map(([t,m])=>[t,Number(m)]));function rc(R){R.command("pool:list","List capacity pools and reservations").option("--json","Print structured JSON").action(async(t)=>as((m)=>{let k=m.store.listPools(m.controlPlane.project.id);if(t.json)console.log(JSON.stringify(k,null,2));else c(["Name","Purpose","Backend","Region / arch","Status","Capacity","Concurrency"],k.map((D)=>[D.name,D.purpose,D.backend,`${D.region??"any"} / ${D.architecture??"any"}`,D.status,JSON.stringify(D.capacity),String(D.concurrency)]))})),R.command("pool:create <name>","Create a capacity pool").option("--purpose <purpose>","application, build, worker, monitoring, or backup",{default:"application"}).option("--backend <backend>","server, ecs, or asg",{default:"server"}).option("--capacity <values>","cpu=4,memoryBytes=8589934592,diskBytes=...").option("--region <region>","Required region").option("--arch <architecture>","Required architecture").option("--labels <labels>","Pool key=value labels").option("--selector <labels>","Required server labels").option("--max <count>","Maximum workloads",{default:"10"}).option("--concurrency <count>","Build concurrency",{default:"1"}).action(async(t,m)=>as((k)=>{let{purpose:D,backend:L}=m;if(!["application","build","worker","monitoring","backup"].includes(D)||!["server","ecs","asg"].includes(L))throw Error("Unsupported pool purpose or backend.");let z=k.store.createPool({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,name:t,purpose:D,backend:L,region:m.region,architecture:m.arch,labels:Ks(m.labels),requiredServerLabels:Ks(m.selector),toleratedTaints:[],capacity:{cpu:0,memoryBytes:0,diskBytes:0,gpu:0,...Ad(m.capacity)},maxWorkloads:Number(m.max),costWeight:1,concurrency:Number(m.concurrency),ephemeralWorkspaces:!0,allowProductionSecrets:!1,status:"active"});n(`Created ${z.name} (${z.id}).`)})),R.command("pool:add-server <pool> <server>","Attach an enrolled server to a pool").action(async(t,m)=>as((k)=>{let D=k.store.listPools(k.controlPlane.project.id).find((z)=>z.id===t||z.name===t),L=new xe(k.controlPlane.store).list(k.controlPlane.project.id).find((z)=>z.id===m||z.name===m);if(!D||!L)throw Error("Pool or server was not found.");k.store.addMember(D.id,L.id),n(`Attached ${L.name} to ${D.name}.`)})),R.command("placement:explain","Explain eligible and rejected placement targets").option("--purpose <purpose>","Workload purpose",{default:"application"}).option("--resources <values>","Requested resources").option("--region <region>","Required region").option("--arch <architecture>","Required architecture").option("--labels <labels>","Required pool labels").option("--json","Print structured JSON").action(async(t)=>as((m)=>{let k=m.service.explain(m.controlPlane.project.id,{purpose:t.purpose,resources:Ad(t.resources),region:t.region,architecture:t.arch,labels:Ks(t.labels)});if(t.json)console.log(JSON.stringify(k,null,2));else c(["Pool","Server","Eligible","Available","Fit / spread / cost","Reasons"],k.map((D)=>[D.poolName,D.serverId??"provider-managed",D.eligible?"yes":"no",JSON.stringify(D.available),`${D.score.fit.toFixed(2)} / ${D.score.spread} / ${D.score.cost}`,D.reasons.join("; ")||"eligible"]))})),R.command("pool:drain <pool>","Move stateless placements and block stateful placements").action(async(t)=>as((m)=>{let k=m.store.listPools(m.controlPlane.project.id).find((L)=>L.id===t||L.name===t);if(!k)throw Error("Pool was not found.");let D=m.service.drainPool(k.id);n(`Draining ${k.name}: moved ${D.moved.length}, blocked ${D.blocked.length} stateful placement(s).`)}))}async function Ep(){let R=await Ne(),t=y(process.cwd(),R),m=new Bn(t.store),k=new jn(m);return{controlPlane:t,store:m,service:k}}async function ho(R){let t=await Ep();try{await R(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var ys=(R,t)=>{let m=R.store.list(R.controlPlane.project.id).find((k)=>k.id===t||k.name===t);if(!m)throw Error("Regional topology was not found.");return m},Tp=(R)=>R.split(",").map((t,m)=>{let[k,D="aws"]=t.split(":");return{region:String(k),provider:D,role:m===0?"primary":"secondary"}}),Np=(R)=>{let t=new Set(["s3","dynamodb","secrets"]),m=R.split(",").map((D)=>D.trim()),k=m.find((D)=>!t.has(D));if(k)throw Error(`Unsupported replication kind: ${k}`);return m};function ic(R){R.command("region:list","List regional topology, traffic, and replication health").option("--json","Print structured JSON").action(async(t)=>ho((m)=>{let k=m.store.list(m.controlPlane.project.id).map((D)=>({topology:D,targets:m.store.targets(D.id),channels:m.store.channels(D.id),route:m.store.route(D.id),executions:m.store.executions(D.id)}));if(t.json)console.log(JSON.stringify(k,null,2));else c(["Name","Hostname","Active","Status","Revision","Replication"],k.map((D)=>[D.topology.name,D.topology.hostname,D.topology.activeRegion,D.topology.status,D.topology.revision??"—",`${D.channels.filter((L)=>L.status==="in_sync").length}/${D.channels.length} in sync`]))})),R.command("region:create <name>","Create a persistent multi-region topology").option("--hostname <hostname>","Traffic hostname").option("--regions <regions>","Comma-separated region[:provider], primary first").option("--replicate <kinds>","s3,dynamodb,secrets",{default:"s3,dynamodb,secrets"}).option("--max-lag <seconds>","Maximum failover lag",{default:"30"}).option("--cdn","Enable CloudFront edge state").option("--waf","Enable WAF edge state").action(async(t,m)=>ho((k)=>{if(!m.hostname||!m.regions)throw Error("--hostname and --regions are required.");let D=k.service.create({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,name:t,hostname:m.hostname,regions:Tp(m.regions),trafficPolicy:"active_passive",dataPolicy:{replicate:Np(String(m.replicate)),maxLagSeconds:Number(m.maxLag),retainOnDestroy:!0},dnsProvider:"route53",cdnEnabled:!!m.cdn,wafEnabled:!!m.waf});n(`Created ${D.name} (${D.id}); run region:rollout with an immutable manifest.`)})),R.command("region:plan <name> <operation>","Preview exact regional execution steps").option("--target <region>","Failover or failback region").action(async(t,m,k)=>ho((D)=>c(["Step"],D.service.plan(ys(D,t).id,m,k.target).map((L)=>[L])))),R.command("region:rollout <name> <revision>","Queue secondary-first regional stack rollout").option("--manifest <file>","Regional stack manifest JSON").action(async(t,m,k)=>ho(async(D)=>{let L=k.manifest?await Bun.file(k.manifest).json():{},z=D.service.enqueue({topologyId:ys(D,t).id,kind:"rollout",revision:m,manifest:L});n(`Regional rollout queued: ${z.operation.id}`)})),R.command("region:failover <name> <region>","Queue health and RPO-gated failover").action(async(t,m)=>ho((k)=>{let D=k.service.enqueue({topologyId:ys(k,t).id,kind:"failover",targetRegion:m});n(`Failover queued: ${D.operation.id}`)})),R.command("region:failback <name> <region>","Queue health and RPO-gated failback").action(async(t,m)=>ho((k)=>{let D=k.service.enqueue({topologyId:ys(k,t).id,kind:"failback",targetRegion:m});n(`Failback queued: ${D.operation.id}`)})),R.command("region:destroy <name>","Drain traffic and delete regional stacks while retaining data").option("--confirm <name>","Exact topology name").action(async(t,m)=>ho((k)=>{let D=k.service.enqueue({topologyId:ys(k,t).id,kind:"destroy",confirmation:m.confirm});n(`Regional teardown queued: ${D.operation.id}; replicated data remains retained.`)}))}var vs="0.16.67";async function Lp(){let R=await Ne(),t=y(process.cwd(),R),m=new Vn(t.store),k=new zn(m),D=["releases","artifacts","operations","logs","previews","snapshots","orphaned_resources"].map((L)=>new Hn(L,t.store,t.project.id,t.organization.id));return{controlPlane:t,store:m,service:k,cleanup:D}}async function ki(R){let t=await Lp();try{await R(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}function oc(R){R.command("maintenance:list","List updates, campaigns, cleanup plans, and DR drills").option("--json","Print structured JSON").action(async(t)=>ki((m)=>{let k={manifests:m.store.manifests(m.controlPlane.organization.id),windows:m.store.windows(m.controlPlane.project.id),campaigns:m.store.campaigns(m.controlPlane.project.id),cleanups:m.store.cleanups(m.controlPlane.project.id),drills:m.store.drills(m.controlPlane.project.id)},D=[...k.manifests.map((L)=>["update",L.version,L.compatibility.compatible?"compatible":"blocked",L.compatibility.reasons.join("; ")||L.channel]),...k.campaigns.map((L)=>["upgrade",L.id,L.status,L.currentStage??L.fromVersion]),...k.cleanups.map((L)=>["cleanup",L.kind,L.status,`${L.candidates.length} items / ${L.estimatedBytes} bytes`]),...k.drills.map((L)=>["DR drill",L.scenario,L.status,L.isolatedTarget])];if(t.json)console.log(JSON.stringify(k,null,2));else c(["Area","Name / version","Status","Detail"],D)})),R.command("maintenance:key:add <name> <pem>","Trust an Ed25519 update signing key").action(async(t,m)=>ki(async(k)=>{let D=k.store.addTrustedKey({organizationId:k.controlPlane.organization.id,name:t,publicKeyPem:await Bun.file(m).text()});n(`Trusted ${D.name}: ${D.fingerprint}`)})),R.command("maintenance:manifest <document> <signature> <key>","Verify and import a signed update manifest").action(async(t,m,k)=>ki(async(D)=>{let L=await Bun.file(t).json(),z=(await Bun.file(m).text()).trim(),Y=D.store.importManifest({organizationId:D.controlPlane.organization.id,keyId:k,document:L,signature:z,current:{schemaVersion:D.controlPlane.store.health().schemaVersion,currentVersion:vs,platform:process.platform,architecture:process.arch}});n(`Verified ${Y.version}; ${Y.compatibility.compatible?"compatible":"blocked: "+Y.compatibility.reasons.join("; ")}`)})),R.command("maintenance:window <name> <schedule>","Create an approved maintenance window").option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--minutes <minutes>","Duration",{default:"60"}).action(async(t,m,k)=>ki((D)=>{let L=D.store.createWindow({projectId:D.controlPlane.project.id,name:t,schedule:m,timezone:k.timezone,durationMinutes:Number(k.minutes),allowedOperations:["platform.upgrade"],requireApproval:!0});n(`Created ${L.name} (${L.id}).`)})),R.command("platform:upgrade <manifest> <backup>","Plan and queue a signed staged platform update").option("--strategy <strategy>","canary or rolling",{default:"canary"}).option("--batch <count>","Rolling batch size",{default:"1"}).action(async(t,m,k)=>ki((D)=>{let L=new xe(D.controlPlane.store).list(D.controlPlane.project.id).filter((X)=>X.status==="ready").map((X)=>({serverId:X.id,target:X.name,previousVersion:X.bootstrapVersion??vs}));if(!L.length)L.push({target:"control-plane",previousVersion:vs});let z=D.store.createCampaign({projectId:D.controlPlane.project.id,manifestId:t,fromVersion:vs,strategy:k.strategy==="rolling"?"rolling":"canary",batchSize:Number(k.batch),healthGate:{kind:"platform"},backupId:m,targets:L}),Y=D.service.enqueueCampaign(z.id);n(`Staged upgrade queued: ${Y.operation.id}`)})),R.command("cleanup:preview <kind>","Discover a bounded cleanup candidate set without deleting").option("--older-than <days>","Minimum age",{default:"30"}).action(async(t,m)=>ki(async(k)=>{let D=await k.service.previewCleanup({projectId:k.controlPlane.project.id,kind:t,criteria:{olderThanDays:Number(m.olderThan)}},k.cleanup);c(["ID","Candidate","Bytes"],D.candidates.map((L)=>[L.id,L.label,String(L.bytes)])),e(`Apply with: cloud cleanup:apply ${D.id} --confirm "DELETE ${D.candidates.length} ${D.kind}"`)})),R.command("cleanup:apply <plan>","Apply an unchanged cleanup preview").option("--confirm <text>","Exact preview confirmation").action(async(t,m)=>ki((k)=>{let D=k.service.enqueueCleanup(t,String(m.confirm??""));n(`Cleanup queued: ${D.operation.id}`)})),R.command("dr:plan <scenario> <target>","Plan an isolated disaster-recovery drill").option("--rpo <minutes>","RPO objective",{default:"60"}).option("--rto <minutes>","RTO objective",{default:"30"}).option("--recovery-point <id>","Recovery point").option("--topology <id>","Regional topology").action(async(t,m,k)=>ki((D)=>{let L=D.store.createDrill({projectId:D.controlPlane.project.id,scenario:t,isolatedTarget:m,expectedRpoMinutes:Number(k.rpo),expectedRtoMinutes:Number(k.rto),recoveryPointId:k.recoveryPoint,topologyId:k.topology});n(`Planned isolated drill ${L.id}.`)})),R.command("dr:run <drill>","Queue an isolated disaster-recovery drill").action(async(t)=>ki((m)=>{let k=m.service.enqueueDrill(t);n(`DR drill queued: ${k.operation.id}`)}))}async function sc(){try{return(await Ne()).project.region||process.env.AWS_REGION||"us-east-1"}catch{return process.env.AWS_REGION||"us-east-1"}}function ac(R){R.command("function:list","List all Lambda functions").option("--region <region>","AWS region").action(async(t)=>{i("Lambda Functions");let m=t?.region||await sc(),k=new P(m);try{let{Functions:D=[]}=await k.listFunctions({MaxItems:50});if(!D.length){e("No functions found");return}c(["Name","Runtime","Memory","Timeout","Last Modified"],D.map((L)=>[L.FunctionName??"-",L.Runtime??"-",L.MemorySize?`${L.MemorySize} MB`:"-",L.Timeout?`${L.Timeout}s`:"-",L.LastModified??"-"]))}catch(D){r(`Failed to list functions: ${D.message}`),process.exitCode=1}}),R.command("function:invoke <name>","Invoke a Lambda function").option("--payload <json>","Event payload as JSON",{default:"{}"}).option("--async","Fire-and-forget (Event invocation)").option("--region <region>","AWS region").action(async(t,m)=>{i(`Invoking ${t}`);let k=m?.region||await sc(),D=new P(k),L=new s("Invoking function...");L.start();try{let z=await D.invoke({FunctionName:t,InvocationType:m?.async?"Event":"RequestResponse",Payload:m?.payload||"{}",LogType:m?.async?"None":"Tail"});if(z.FunctionError){if(L.fail(`Function returned an error (${z.FunctionError})`),z.Payload)e(z.Payload);process.exitCode=1;return}if(L.succeed(`Invoked (status ${z.StatusCode})`),z.Payload)e(`
|
|
318
|
+
`)}async function yp(){let R=await Ne(),t=y(process.cwd(),R),m=new xe(t.store),k=new Mn(m,[new ct("aws"),new ct("hetzner"),new ct("ssh")]);return{controlPlane:t,store:m,service:k}}async function pi(R){let t=await yp();try{return await R(t)}finally{t.controlPlane.store.close()}}var Vi=(R,t)=>{let m=R.store.list(R.controlPlane.project.id).find((k)=>k.id===t||k.name===t);if(!m)throw Error(`Server ${t} was not found.`);return m},kn=(R)=>{r(R instanceof Error?R.message:String(R)),process.exitCode=1};function Xa(R,t){let m=Number.parseInt(R??"",10);return Number.isFinite(m)&&m>=0?m:t}function vp(R="3h"){let t=R.match(/^(\d+)(m|h)$/);if(!t)throw Error("--range must use minutes or hours, for example 30m or 3h.");let m=Number(t[1])*(t[2]==="h"?3600000:60000);if(m<300000||m>86400000)throw Error("--range must be between 5 minutes and 24 hours.");return m}function ss(R,t="%"){return R==null?"unavailable":`${R.toFixed(1)}${t}`}async function bp(R,t){let m=await Ne();if(m.cloud?.provider!=="hetzner")throw Error("server:monitoring currently requires the Hetzner provider.");let k=t.env??process.env.CLOUD_ENV??"production",D=T(m,k),L=await ge(D);if(!L?.serverId)throw Error(`No pinned Hetzner server was found for ${D}.`);if(![L.serverName,String(L.serverId),L.stackName].includes(R))throw Error(`Refusing to read ${R}: project state is pinned to ${L.serverName}.`);let z=gt(m),Y=new Oe({apiToken:fr(z.apiToken,m)}),X=await Y.getServer(L.serverId),ne=L.publicIp??X.public_net.ipv4?.ip;if(!ne)throw Error(`Server ${X.name} has no public IPv4 address.`);let re=new Date,ie=new Date(re.getTime()-vp(t.range)),ae=Math.min(3600,Math.max(60,Xa(t.step,60))),be=await yd({client:Y,serverId:X.id,cores:Math.max(1,Number(X.server_type.cores)||1),from:ie,to:re,step:ae,remote:{host:ne,user:L.sshUser??z.sshUser,identityFile:z.sshPrivateKeyPath,connectTimeoutSec:15}});if(t.json){console.log(JSON.stringify({schemaVersion:1,server:X.name,result:be},null,2));return}c(["Server","Window","CPU average / peak","RAM average / peak","Minimum available","Swap peak"],[[X.name,`${be.from} to ${be.to}`,`${ss(be.summary.cpuAveragePercent)} / ${ss(be.summary.cpuPeakPercent)}`,`${ss(be.summary.memoryAveragePercent)} / ${ss(be.summary.memoryPeakPercent)}`,be.summary.memoryMinimumAvailableBytes==null?"unavailable":`${(be.summary.memoryMinimumAvailableBytes/1073741824).toFixed(2)} GB`,ss(be.summary.swapPeakPercent)]])}function wd(R){let t=R.targetType.prices?.find((m)=>m.location===R.location);return{serverId:R.serverId,serverName:R.serverName,location:R.location,from:R.currentType.name,to:R.targetType.name,capacityAvailable:R.capacityAvailable,alreadyComplete:R.alreadyComplete,upgradeDisk:R.upgradeDisk,target:{cores:R.targetType.cores,memoryGb:R.targetType.memory,diskGb:R.targetType.disk,architecture:R.targetType.architecture,cpuType:R.targetType.cpu_type},price:t?{hourlyNet:t.price_hourly.net,hourlyGross:t.price_hourly.gross,monthlyNet:t.price_monthly.net,monthlyGross:t.price_monthly.gross}:void 0}}function wp(R){let t=wd(R);c(["Server","Location","Resize","Capacity","CPU / RAM / disk","Monthly"],[[`${t.serverName} (${t.serverId})`,t.location,`${t.from} -> ${t.to}`,t.capacityAvailable?"available":"waiting",`${t.target.cores??0} / ${t.target.memoryGb??0} GB / ${t.target.diskGb??0} GB`,t.price?.monthlyGross??t.price?.monthlyNet??"not reported"]])}async function Sp(R,t,m){let k=await Ne();if(k.cloud?.provider!=="hetzner")throw Error("server:resize currently requires the Hetzner provider.");let D=m.env??process.env.CLOUD_ENV??"production",L=T(k,D),z=await ge(L),Y=gt(k),X=new Oe({apiToken:fr(Y.apiToken,k)}),re=z&&(R===z.serverName||R===String(z.serverId)||R===z.stackName)?await X.getServer(z.serverId):(await X.listServers()).find((Le)=>Le.name===R||String(Le.id)===R);if(!re)throw Error(`Hetzner server ${R} was not found.`);if(z?.serverId&&z.serverId!==re.id)throw Error(`Refusing to resize ${R}: project state is pinned to server ${z.serverId}.`);let ie=Xe(t),ae=m.upgradeDisk!==!1,be=await Qe(X,re.id,ie,ae);if(m.json)console.log(JSON.stringify({schemaVersion:1,plan:wd(be)},null,2));else wp(be);if(!m.apply){if(!m.json)e(`Preview only. Re-run with --apply --confirm ${re.name} to execute.`);return}if(m.confirm!==re.name)throw Error(`Resize requires --confirm ${re.name}.`);let Ae=z?.publicIp??re.public_net.ipv4?.ip;if(!Ae)throw Error(`Server ${re.name} has no public IPv4 address for verification.`);let le={host:Ae,user:z?.sshUser??Y.sshUser,identityFile:Y.sshPrivateKeyPath,connectTimeoutSec:15,sshTimeoutMs:600000,routeTimeoutMs:20000},ke=await fs(L),De=new Date().toISOString(),Se={schemaVersion:1,operationId:ke?.targetType===ie&&ke.serverId===re.id?ke.operationId:crypto.randomUUID(),stackName:L,serverId:re.id,serverName:re.name,sourceType:be.currentType.name,targetType:ie,upgradeDisk:ae,phase:"planning",status:"running",attempts:ke?.targetType===ie?ke.attempts:0,startedAt:ke?.targetType===ie?ke.startedAt:De,updatedAt:De,manifest:ke?.targetType===ie?ke.manifest:void 0};await ht(Se);let Re=Xa(m.pollSeconds,900)*1000,Pe=Xa(m.maxWaitMinutes,0)*60000,Ve=Date.now();for(;;){let Le=await Qe(X,re.id,ie,ae);if(!Le.capacityAvailable&&!Le.alreadyComplete){if(Se={...Se,phase:"waiting-capacity",status:"waiting-capacity",updatedAt:new Date().toISOString()},await ht(Se),!m.wait){if(!m.json)e(`${ie} has no capacity in ${Le.location}. The server remains running and unchanged. Use --wait to poll safely.`);return}if(Pe>0&&Date.now()-Ve>=Pe)throw Error(`Timed out waiting for ${ie} capacity in ${Le.location}.`);if(!m.json)e(`Waiting ${Math.round(Re/1000)} seconds for ${ie} capacity...`);await Bun.sleep(Re);continue}let Fe=await hs(L);try{let He=await(async()=>{try{return Se={...Se,status:"running",attempts:Se.attempts+1,updatedAt:new Date().toISOString()},await ht(Se),await hr({client:X,serverId:re.id,targetType:ie,upgradeDisk:ae,resumeFromOff:Se.phase==="shutting-down"||Se.phase==="resizing"||Se.phase==="powering-on"||Se.phase==="verifying",manifest:Se.manifest,hooks:{preflight:async()=>{let wt=await gi(le);return Se={...Se,manifest:wt,updatedAt:new Date().toISOString()},await ht(Se),wt},afterBoot:async(wt,Dt,it,xt)=>fi(le,Dt,it,xt),onPhase:async(wt,Dt)=>{if(Se={...Se,phase:wt,status:wt==="waiting-capacity"?"waiting-capacity":wt==="complete"?"completed":"running",updatedAt:new Date().toISOString()},await ht(Se),!m.json)e(`${wt}${Dt?` ${JSON.stringify(Dt)}`:""}`)}}})}finally{await Fe()}})();if(Se={...Se,status:He.status==="completed"||He.status==="already-complete"?"completed":He.status==="recovered"?"recovered":"waiting-capacity",lastError:He.status==="recovered"?He.error:void 0,updatedAt:new Date().toISOString()},await ht(Se),m.json)console.log(JSON.stringify({schemaVersion:1,result:He},null,2));else if(He.status==="completed"||He.status==="already-complete")n(`${re.name} is on ${ie} and passed full post-boot verification.`);else if(He.status==="recovered"&&He.retryable&&m.wait){e(`Capacity race recovered safely. Waiting ${Math.round(Re/1000)} seconds before retry.`),await Bun.sleep(Re);continue}else if(He.status==="recovered")throw Error(`Resize did not complete and the original server was recovered: ${He.error}`);return}catch(He){throw Se={...Se,status:"failed",lastError:He instanceof Error?He.message:String(He),updatedAt:new Date().toISOString()},await ht(Se),He}}}async function Cp(R,t){let m=await Ne();if(m.cloud?.provider!=="hetzner")throw Error("server:optimize currently requires the Hetzner provider.");let k=t.env??process.env.CLOUD_ENV??"production",D=T(m,k),L=await ge(D);if(!L?.serverId)throw Error(`No pinned Hetzner server was found for ${D}.`);if(![L.serverName,String(L.serverId),L.stackName].includes(R))throw Error(`Refusing to optimize ${R}: project state is pinned to ${L.serverName}.`);let z=gt(m),Y=new Oe({apiToken:fr(z.apiToken,m)}),X=await Y.getServer(L.serverId),ne=L.publicIp??X.public_net.ipv4?.ip;if(!ne)throw Error(`Server ${X.name} has no public IPv4 address.`);let re=co(m);if(t.json)console.log(JSON.stringify({schemaVersion:1,server:X.name,plan:re},null,2));else c(["Server","Firewall ports","Monitoring","Auto updates","Swap","SSH passwords"],[[X.name,re.firewallPorts.join(","),re.monitoring?"on":"off",re.autoUpdates?"on":"off",`${re.swapGb} GB`,"disabled"]]);if(!t.apply){if(!t.json)e(`Preview only. Re-run with --apply --confirm ${X.name} to execute.`);return}if(t.confirm!==X.name)throw Error(`Optimization requires --confirm ${X.name}.`);let ie={host:ne,user:L.sshUser??z.sshUser,identityFile:z.sshPrivateKeyPath,connectTimeoutSec:15},ae=await sn(ie),be=ae.routeProbes.filter((it)=>!it.ok),Ae=m.infrastructure?.compute,le=Z(Ae)?new Set(["nginx.service","bun-gateway.service","ts-cloud-nginx.service"]):new Set,ke=ae.failedUnits.filter((it)=>!le.has(it));if(ke.length>0)throw Error(`Preflight found failed services: ${ke.join(", ")}`);if(be.length>0)throw Error(`Preflight found unhealthy routes: ${be.map((it)=>it.domain).join(", ")}`);let De=Z(Ae)?[]:Object.values(m.sites??{}).flatMap((it)=>typeof it?.port==="number"&&![80,443].includes(it.port)?[it.port]:[]),Se=on({allowSsh:Ae?.allowSsh!==!1,sitePorts:De,allowedPorts:Ae?.firewall?.allowedPorts}),Re=(L.firewallId?(await Y.listFirewalls()).find((it)=>it.id===L.firewallId):void 0)??(await Y.listFirewalls()).find((it)=>it.name===`${m.project.slug}-${k}-app-fw`);if(!Re)throw Error(`Managed firewall for ${X.name} was not found.`);let Pe=await Y.setFirewallRules(Re.id,Se);await Promise.all(Pe.map((it)=>Y.waitForAction(it.id))),await ps(m,ie);let[Ve,Le]=await Promise.all([sn(ie),ms(ie)]),Fe=gs(re,Le),He=yc(ae,Ve),wt=Ve.routeProbes.filter((it)=>!it.ok);if(He.stoppedServices.length>0)Fe.push(`services stopped: ${He.stoppedServices.join(", ")}`);if(He.changedRouteFragments.length>0)Fe.push(`route fragments changed: ${He.changedRouteFragments.join(", ")}`);if(He.missingRouteIds.length>0)Fe.push(`routes disappeared: ${He.missingRouteIds.join(", ")}`);if(He.changedReleaseLinks.length>0)Fe.push(`release links changed: ${He.changedReleaseLinks.join(", ")}`);if(He.missingData.length>0)Fe.push(`databases or volumes disappeared: ${He.missingData.join(", ")}`);if(wt.length>0)Fe.push(`unhealthy routes: ${wt.map((it)=>it.domain).join(", ")}`);if(Fe.length>0)throw Error(`Host optimization verification failed: ${Fe.join("; ")}`);let Dt={serverId:X.id,serverName:X.name,services:Ve.runningServices.length,routes:Ve.routeIds.length,healthyDomains:Ve.routeProbes.length,releaseLinks:Ve.releaseLinks.length,databasesAndVolumes:Ve.dataCatalog.length,report:Le};if(t.json)console.log(JSON.stringify({schemaVersion:1,result:Dt},null,2));else n(`${X.name} passed full host, rpx route, service, release, and data verification.`)}async function $p(R,t,m){let k=await Ne(),D=m.env??"production",L=T(k,D);await pi(async(z)=>{let Y=Vi(z,R),X=z.store.list(z.controlPlane.project.id,!0),ne=Y.provider==="hetzner"&&Y.providerId?(()=>{try{let Re=gt(k);return new Oe({apiToken:fr(Re.apiToken,k)})}catch{return null}})():null,re=Y.providerId?Number(Y.providerId):Number.NaN,ie=await ge(L),ae=ie!=null&&ie.serverId===re,be=new qr,Ae=Y.trustState==="pinned",le={takenNames:async()=>{let Re=X.map((Pe)=>Pe.name);if(ne)Re.push(...(await ne.listServers()).map((Pe)=>Pe.name));return Re},inventoryName:()=>z.store.get(Y.id)?.name??Y.name,renameInventory:(Re)=>{z.store.update(Y.id,{name:Re})},...ne&&Number.isFinite(re)?{providerName:async()=>(await ne.getServer(re)).name,renameProvider:async(Re)=>{await ne.renameServer(re,Re)}}:{},...ae?{stateName:async()=>(await ge(L))?.serverName,writeStateName:async(Re)=>{let Pe=await ge(L);if(Pe)await ft(L,{...Pe,serverName:Re})}}:{},...Ae?{remoteHostname:async()=>{let Re=await be.exec(Y,"hostname");return Re.code===0?Re.stdout.trim():void 0},setRemoteHostname:async(Re)=>{let Pe=await be.exec(Y,bd(Re));if(Pe.code!==0)throw Error(Pe.stderr.trim()||`hostname exited ${Pe.code}`)}}:{}},ke=await vd(Y.name,t,le),De=await Us(ke);if(!m.apply){let Re=[ne?"":"provider record (no provider id or no API token)",ae?"":"local state pin (none points at this server)",Ae?"":"box hostname (host key is not pinned — run server:validate first)"].filter(Boolean);if(m.json){console.log(JSON.stringify({schemaVersion:1,operation:ke.operation,target:ke.target,rename:{from:Y.name,to:t},notCovered:Re,steps:De.map((Pe)=>({id:Pe.step.id,title:Pe.step.title,state:Pe.state,change:Pe.step.change,reason:Pe.reason}))},null,2));return}for(let Pe of Ws(ke,De))console.log(Pe);for(let Pe of Re)console.log(` not covered: ${Pe}`);console.log(" Re-run with --apply to perform it.");return}let Se=await Vs(ke,De,{log:(Re)=>e(Re),audit:(Re)=>z.controlPlane.store.appendEvent({projectId:z.controlPlane.project.id,resourceId:Y.resourceId,type:`${Re.operation}.${Re.step}.${Re.state}`,level:Re.state==="failed"?"error":"info",payload:{target:Re.target,renameTo:t,...Re.error?{error:Re.error}:{}}})});if(m.json)console.log(JSON.stringify({schemaVersion:1,outcome:Se},null,2));if(!Se.success){let Re=Se.steps.find((Pe)=>Pe.state==="failed");throw Error(`${ke.operation} stopped at '${Re?.title}': ${Re?.error}. Earlier steps stayed applied; re-run the same command to continue from here.`)}if(!m.json)n(`Renamed ${Y.name} to ${t}.`)})}function ec(R){R.command("capabilities [server]","Show provider and target operation support").option("--json","Print structured JSON").action(async(t,m)=>{try{await pi(async(k)=>{let D=t?[Vi(k,t)]:k.store.list(k.controlPlane.project.id),L=D.flatMap((z)=>Object.entries(z.capabilities).map(([Y,X])=>({server:z.name,provider:z.provider,action:Y,...X})));if(m.json)console.log(JSON.stringify({schemaVersion:1,targets:L},null,2));else c(["Server","Provider","Action","Support","Explanation"],L.map((z)=>[z.server,z.provider,z.action,z.supported?"supported":"unsupported",z.reason??"—"]))})}catch(k){kn(k)}}),R.command("server:resize <name> <type>","Safely resize Hetzner compute with full host verification").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Execute the reviewed resize plan").option("--confirm <name>","Exact server name required for mutation").option("--upgrade-disk","Permanently expand the root disk to the target size",{default:!0}).option("--wait","Poll capacity without taking the server offline until placement is available").option("--poll-seconds <seconds>","Capacity retry interval",{default:"900"}).option("--max-wait-minutes <minutes>","Stop waiting after this many minutes; 0 waits indefinitely",{default:"0"}).option("--json","Print structured JSON").action(async(t,m,k)=>{try{await Sp(t,m,k)}catch(D){kn(D)}}),R.command("server:optimize <name>","Reconcile and verify a production Hetzner host").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Apply the reviewed host optimization plan").option("--confirm <name>","Exact server name required for mutation").option("--json","Print structured JSON").action(async(t,m)=>{try{await Cp(t,m)}catch(k){kn(k)}}),R.command("server:monitoring <name>","Read historical Hetzner CPU, RAM, swap, disk, and network metrics").option("--env <environment>","Deployment environment",{default:"production"}).option("--range <duration>","Historical window from 5m to 24h",{default:"3h"}).option("--step <seconds>","Hetzner provider sample interval",{default:"60"}).option("--json","Print structured JSON").action(async(t,m)=>{try{await bp(t,m)}catch(k){kn(k)}}),R.command("server:list","List fleet servers").option("--json","Print structured JSON").action(async(t)=>{try{await pi(async(m)=>{let k=m.store.list(m.controlPlane.project.id);if(t.json)console.log(JSON.stringify(k,null,2));else c(["Name","Provider ID","Provider / region","Status / trust","Roles","CPU / memory","Heartbeat"],k.map((D)=>[D.name,D.providerId??"—",`${D.provider} / ${D.region??"external"}`,`${D.status} / ${D.trustState}`,D.roles.join(","),`${D.capacity.cpu??0} / ${D.capacity.memoryBytes??0}`,D.heartbeatAt??"never"]))})}catch(m){kn(m)}}),R.command("server:create <name>","Enroll a provisioned or existing server").option("--provider <provider>","aws, hetzner, or ssh",{default:"ssh"}).option("--provider-id <id>","Stable provider identity").option("--endpoint <host>","SSH hostname or IP").option("--user <user>","Non-root SSH user",{default:"deploy"}).option("--credential-ref <ref>","Secret reference",{default:"secret://fleet/agent"}).option("--region <region>","Provider region").option("--roles <roles>","Comma-separated roles",{default:"application"}).option("--labels <labels>","Comma-separated key=value labels").action(async(t,m)=>{try{await pi(async(k)=>{let D=m.provider;if(!["aws","hetzner","ssh"].includes(D)||!m.endpoint)throw Error("A supported --provider and --endpoint are required; provisioning and enrollment are separate operations.");let L=k.service.enroll({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,name:t,provider:D,providerId:m.providerId,endpoint:m.endpoint,sshUser:m.user??"deploy",credentialRef:m.credentialRef??"secret://fleet/agent",region:m.region,roles:String(m.roles??"application").split(","),labels:Object.fromEntries(String(m.labels??"").split(",").map((z)=>z.split("=")).filter((z)=>z[0]&&z[1]))});n(`Enrolled ${L.name} as ${L.id}; no remote mutation was performed.`)})}catch(k){kn(k)}}),R.command("server:validate <name>","Pin trust and validate a server").option("--accept-host-key <fingerprint>","Accept reviewed rotation").option("--json","Print JSON").action(async(t,m)=>{try{await pi(async(k)=>{let D=Vi(k,t);if(D=await k.service.test(D.id),D.trustState==="rotation_pending"){if(!m.acceptHostKey)throw Error(`Host key changed to ${D.pendingHostKey}; review it first.`);D=k.service.reviewHostKey(D.id,m.acceptHostKey)}let L=await k.service.validate(D.id);if(m.json)console.log(JSON.stringify(L.validation,null,2));else c(["Severity","Code","Finding","Remediation"],(L.validation?.findings??[]).map((z)=>[z.severity,z.code,z.message,z.remediation??"—"]));if(!L.validation?.valid)process.exitCode=1})}catch(k){kn(k)}}),R.command("server:bootstrap <name>","Preview or queue bootstrap").option("--apply","Apply reviewed plan").action(async(t,m)=>{try{await pi(async(k)=>{let D=k.service.bootstrap(Vi(k,t).id,!!m.apply);if(D.preview)c(["Step"],D.steps.map((L)=>[L]));else n(`Bootstrap queued: ${D.operation?.id}`)})}catch(k){kn(k)}}),R.command("server:rename <name> <new-name>","Rename a server in place, without recreating it").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Perform the reviewed rename plan").option("--json","Print structured JSON").action(async(t,m,k)=>{try{await $p(t,m,k)}catch(D){kn(D)}}),R.command("server:drain <name>","Drain without terminating").option("--complete","Mark movement complete").action(async(t,m)=>{try{await pi(async(k)=>n(k.service.drain(Vi(k,t).id,!!m.complete).status))}catch(k){kn(k)}}),R.command("server:uncordon <name>","Return a server to scheduling").action(async(t)=>{try{await pi(async(m)=>{m.service.uncordon(Vi(m,t).id),n(`Uncordoned ${t}.`)})}catch(m){kn(m)}}),R.command("server:archive <name>","Archive inventory without termination").option("--confirm <name>","Exact name").action(async(t,m)=>{try{await pi(async(k)=>{k.service.archive(Vi(k,t).id,m.confirm??""),n(`Archived ${t}; provider infrastructure was not terminated.`)})}catch(k){kn(k)}}),R.command("server:ssh <name>","Open strict SSH using the enrolled endpoint").action(async(t)=>{try{await pi(async(m)=>{let k=Vi(m,t);if(k.trustState!=="pinned")throw Error("Validate and pin the server host key first.");let D=Bun.spawn(["ssh","-p",String(k.sshPort),"-o","StrictHostKeyChecking=yes",`${k.sshUser}@${k.endpoint}`],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}),L=await D.exited;if(L)process.exitCode=L})}catch(m){kn(m)}});for(let[t,m]of[["server:logs <name>","Use the runtime log service"],["server:deploy <name>","Use immutable release deployment"],["server:reboot <name>","Reboot provider compute"],["server:destroy <name>","Terminate provider compute"],["server:recipe <name> <recipe>","Apply a server recipe"],["server:firewall:add <name> <rule>","Add firewall rule"],["server:firewall:list <name>","List firewall rules"],["server:firewall:remove <name> <rule>","Remove firewall rule"],["server:ssl:install <domain>","Install TLS"],["server:ssl:renew <domain>","Renew TLS"],["server:snapshot <name>","Create provider snapshot"],["server:snapshot:restore <name> <snapshot-id>","Restore provider snapshot"],["server:update <name>","Update OS packages"],["server:secure <name>","Apply OS hardening"]])R.command(t,m).action(async(...k)=>En(t.split(" ")[0],{target:String(k[0]??""),nextAction:"Use the dashboard capability view or the corresponding runtime, release, firewall, backup, or maintenance service."}))}import{existsSync as Sd,mkdirSync as kp,readFileSync as Ap,writeFileSync as Rp}from"node:fs";import{join as Cd,resolve as Ip}from"node:path";var tc=(R)=>{r(R instanceof Error?R.message:String(R)),process.exitCode=1};async function Pp(){try{return await Ne()}catch{return}}function xp(R){if(R===void 0)return;let t=Number(R);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`--port must be a port number, not '${R}'.`);return t}function $d(R,t,m){let k=R?.ssh?.hosts?.find((D)=>D.host===t);return new Cr({hosts:t?[k??{host:t}]:R?.ssh?.hosts,user:m.user,port:xp(m.port),privateKeyPath:m.key,hostKey:R?.ssh?.hostKey,sudo:R?.ssh?.sudo,profile:m.profile??R?.ssh?.profile,publicIp:R?.ssh?.publicIp,lan:R?.ssh?.lan})}var kd=1073741824;function nc(R){R.command("ssh:preflight [host]","Check a bring-your-own Linux host before adopting it").option("--user <user>","SSH user (default: ssh.hosts[].user, then root)").option("--port <port>","SSH port (default: ssh.hosts[].port, then 22)").option("--key <path>","SSH private key (default: ssh.hosts[].privateKeyPath, then ~/.ssh/id_ed25519)").option("--profile <profile>","raspberry-pi or generic (default: ssh.profile, then generic)").option("--json","Print the facts and findings as JSON").action(async(t,m)=>{try{let k=await Pp(),D=$d(k,t,m),L=t??D.targetHost(),z=await D.ensureHostKey(L),{facts:Y,findings:X}=await D.preflight(L);if(m.json)console.log(JSON.stringify({schemaVersion:1,host:L,hostKeyFingerprint:z.fingerprint,facts:Y,findings:X},null,2));else{if(i(`Preflight ${L}`),z.pinnedNow)e(`Pinned host key ${z.fingerprint}`);if(e(`${Y.os} on ${Y.arch}, ${Y.cpuCores} cores, ${(Y.memoryBytes/kd).toFixed(1)} GiB memory, ${(Y.diskFreeBytes/kd).toFixed(1)} GiB free`),Y.piModel)e(Y.piModel);if(Y.lanIp)e(`LAN address ${Y.lanIp}`);if(console.log(an(X)),vr(X))r("The host is not ready; fix the errors above and run the preflight again.");else n("The host is ready to adopt.")}if(vr(X))process.exitCode=1}catch(k){tc(k)}}),R.command("ssh:bootstrap","Adopt the configured host: pin its key, run the preflight, then bootstrap it").option("--env <environment>","Deployment environment",{default:"production"}).option("--dry-run","Print the bootstrap script instead of running it").action(async(t)=>{try{let m=await Ne(),k=t.env??"production";if(t.dryRun){let z=Sr(m),[Y]=z.hosts;console.log(br({config:m,environment:k,profile:z.profile,sudoUser:Y.user!=="root"?Y.user:void 0,lan:z.lan}));return}let D=$d(m,void 0,{});i(`Adopt ${D.targetHost()}`);let L=await D.provisionComputeInfrastructure({config:m,environment:k});n(`${L.appPublicIp} is bootstrapped; deploy with: cloud deploy --env ${k}`)}catch(m){tc(m)}}),R.command("ssh:first-boot","Write cloud-init first-boot files for a freshly flashed Raspberry Pi OS or Ubuntu image").option("--os <os>","raspberry-pi-os or ubuntu",{default:"raspberry-pi-os"}).option("--out <dir>","Directory to write user-data, network-config and meta-data into (never a mounted card; copy them yourself)").option("--hostname <name>","Hostname of the new host").option("--user <user>","Deploy user created on first boot (default: ssh.hosts[].user, then pi)").option("--key <path>","Public key authorized for the user",{default:"~/.ssh/id_ed25519.pub"}).option("--wifi-ssid <ssid>","Join this Wi-Fi network on first boot").option("--wifi-passphrase-env <name>","Environment variable holding the Wi-Fi passphrase",{default:"WIFI_PASSWORD"}).option("--wifi-country <cc>","Wi-Fi regulatory domain, e.g. DE").option("--timezone <tz>","IANA timezone, e.g. Europe/Berlin").option("--locale <locale>","System locale, e.g. en_US.UTF-8").option("--env <environment>","Deployment environment the embedded bootstrap is built for",{default:"production"}).option("--force","Overwrite files already in --out").action(async(t)=>{try{if(!t.out)throw Error("--out <dir> is required.");if(!t.hostname)throw Error("--hostname <name> is required.");let m=t.os??"raspberry-pi-os";if(!(m in so))throw Error(`--os must be raspberry-pi-os or ubuntu, not '${m}'.`);let k=await Ne(),D=t.env??"production",L=Sr(k,{hosts:k.ssh?.hosts?.length?void 0:[{host:t.hostname}],profile:m==="raspberry-pi-os"?"raspberry-pi":void 0}),z=t.user??(k.ssh?.hosts?.[0]?.user??"pi"),Y=nn(t.key??"~/.ssh/id_ed25519.pub");if(!Sd(Y))throw Error(`Public key not found at ${Y}; pass --key <path to a .pub file>.`);let X=Ap(Y,"utf8").trim(),ne;if(t.wifiSsid){let Ae=t.wifiPassphraseEnv??"WIFI_PASSWORD",le=process.env[Ae];if(!le)throw Error(`--wifi-ssid needs the passphrase in $${Ae} (set it in the environment, not on the command line).`);if(!t.wifiCountry)throw Error("--wifi-country <cc> is required with --wifi-ssid.");ne={ssid:t.wifiSsid,passphrase:le,country:t.wifiCountry}}let re=br({config:k,environment:D,profile:L.profile,sudoUser:z!=="root"?z:void 0,lan:L.lan}),ie=ao({hostname:t.hostname,user:z,publicKey:X,timezone:t.timezone,locale:t.locale,wifi:ne},re,{os:m}),ae=Ip(t.out);kp(ae,{recursive:!0});let be=Object.keys(ie.files).filter((Ae)=>Sd(Cd(ae,Ae)));if(be.length>0&&!t.force)throw Error(`${be.join(", ")} already exist in ${ae}; pass --force to overwrite.`);for(let[Ae,le]of Object.entries(ie.files))Rp(Cd(ae,Ae),le,{mode:384});i(`First boot for ${t.hostname}`),n(`Wrote ${Object.keys(ie.files).join(", ")} to ${ae}`),console.log(ie.instructions)}catch(m){tc(m)}})}async function Dp(){let R=await Ne(),t=y(process.cwd(),R),m=new Un(t.store),k=new Bn(m,new xe(t.store));return{controlPlane:t,store:m,service:k}}async function as(R){let t=await Dp();try{await R(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var Ks=(R)=>Object.fromEntries(String(R??"").split(",").map((t)=>t.split("=")).filter((t)=>t[0]&&t[1])),Ad=(R)=>Object.fromEntries(Object.entries(Ks(R)).map(([t,m])=>[t,Number(m)]));function rc(R){R.command("pool:list","List capacity pools and reservations").option("--json","Print structured JSON").action(async(t)=>as((m)=>{let k=m.store.listPools(m.controlPlane.project.id);if(t.json)console.log(JSON.stringify(k,null,2));else c(["Name","Purpose","Backend","Region / arch","Status","Capacity","Concurrency"],k.map((D)=>[D.name,D.purpose,D.backend,`${D.region??"any"} / ${D.architecture??"any"}`,D.status,JSON.stringify(D.capacity),String(D.concurrency)]))})),R.command("pool:create <name>","Create a capacity pool").option("--purpose <purpose>","application, build, worker, monitoring, or backup",{default:"application"}).option("--backend <backend>","server, ecs, or asg",{default:"server"}).option("--capacity <values>","cpu=4,memoryBytes=8589934592,diskBytes=...").option("--region <region>","Required region").option("--arch <architecture>","Required architecture").option("--labels <labels>","Pool key=value labels").option("--selector <labels>","Required server labels").option("--max <count>","Maximum workloads",{default:"10"}).option("--concurrency <count>","Build concurrency",{default:"1"}).action(async(t,m)=>as((k)=>{let{purpose:D,backend:L}=m;if(!["application","build","worker","monitoring","backup"].includes(D)||!["server","ecs","asg"].includes(L))throw Error("Unsupported pool purpose or backend.");let z=k.store.createPool({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,name:t,purpose:D,backend:L,region:m.region,architecture:m.arch,labels:Ks(m.labels),requiredServerLabels:Ks(m.selector),toleratedTaints:[],capacity:{cpu:0,memoryBytes:0,diskBytes:0,gpu:0,...Ad(m.capacity)},maxWorkloads:Number(m.max),costWeight:1,concurrency:Number(m.concurrency),ephemeralWorkspaces:!0,allowProductionSecrets:!1,status:"active"});n(`Created ${z.name} (${z.id}).`)})),R.command("pool:add-server <pool> <server>","Attach an enrolled server to a pool").action(async(t,m)=>as((k)=>{let D=k.store.listPools(k.controlPlane.project.id).find((z)=>z.id===t||z.name===t),L=new xe(k.controlPlane.store).list(k.controlPlane.project.id).find((z)=>z.id===m||z.name===m);if(!D||!L)throw Error("Pool or server was not found.");k.store.addMember(D.id,L.id),n(`Attached ${L.name} to ${D.name}.`)})),R.command("placement:explain","Explain eligible and rejected placement targets").option("--purpose <purpose>","Workload purpose",{default:"application"}).option("--resources <values>","Requested resources").option("--region <region>","Required region").option("--arch <architecture>","Required architecture").option("--labels <labels>","Required pool labels").option("--json","Print structured JSON").action(async(t)=>as((m)=>{let k=m.service.explain(m.controlPlane.project.id,{purpose:t.purpose,resources:Ad(t.resources),region:t.region,architecture:t.arch,labels:Ks(t.labels)});if(t.json)console.log(JSON.stringify(k,null,2));else c(["Pool","Server","Eligible","Available","Fit / spread / cost","Reasons"],k.map((D)=>[D.poolName,D.serverId??"provider-managed",D.eligible?"yes":"no",JSON.stringify(D.available),`${D.score.fit.toFixed(2)} / ${D.score.spread} / ${D.score.cost}`,D.reasons.join("; ")||"eligible"]))})),R.command("pool:drain <pool>","Move stateless placements and block stateful placements").action(async(t)=>as((m)=>{let k=m.store.listPools(m.controlPlane.project.id).find((L)=>L.id===t||L.name===t);if(!k)throw Error("Pool was not found.");let D=m.service.drainPool(k.id);n(`Draining ${k.name}: moved ${D.moved.length}, blocked ${D.blocked.length} stateful placement(s).`)}))}async function Ep(){let R=await Ne(),t=y(process.cwd(),R),m=new jn(t.store),k=new Vn(m);return{controlPlane:t,store:m,service:k}}async function ho(R){let t=await Ep();try{await R(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var ys=(R,t)=>{let m=R.store.list(R.controlPlane.project.id).find((k)=>k.id===t||k.name===t);if(!m)throw Error("Regional topology was not found.");return m},Tp=(R)=>R.split(",").map((t,m)=>{let[k,D="aws"]=t.split(":");return{region:String(k),provider:D,role:m===0?"primary":"secondary"}}),Np=(R)=>{let t=new Set(["s3","dynamodb","secrets"]),m=R.split(",").map((D)=>D.trim()),k=m.find((D)=>!t.has(D));if(k)throw Error(`Unsupported replication kind: ${k}`);return m};function ic(R){R.command("region:list","List regional topology, traffic, and replication health").option("--json","Print structured JSON").action(async(t)=>ho((m)=>{let k=m.store.list(m.controlPlane.project.id).map((D)=>({topology:D,targets:m.store.targets(D.id),channels:m.store.channels(D.id),route:m.store.route(D.id),executions:m.store.executions(D.id)}));if(t.json)console.log(JSON.stringify(k,null,2));else c(["Name","Hostname","Active","Status","Revision","Replication"],k.map((D)=>[D.topology.name,D.topology.hostname,D.topology.activeRegion,D.topology.status,D.topology.revision??"—",`${D.channels.filter((L)=>L.status==="in_sync").length}/${D.channels.length} in sync`]))})),R.command("region:create <name>","Create a persistent multi-region topology").option("--hostname <hostname>","Traffic hostname").option("--regions <regions>","Comma-separated region[:provider], primary first").option("--replicate <kinds>","s3,dynamodb,secrets",{default:"s3,dynamodb,secrets"}).option("--max-lag <seconds>","Maximum failover lag",{default:"30"}).option("--cdn","Enable CloudFront edge state").option("--waf","Enable WAF edge state").action(async(t,m)=>ho((k)=>{if(!m.hostname||!m.regions)throw Error("--hostname and --regions are required.");let D=k.service.create({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,name:t,hostname:m.hostname,regions:Tp(m.regions),trafficPolicy:"active_passive",dataPolicy:{replicate:Np(String(m.replicate)),maxLagSeconds:Number(m.maxLag),retainOnDestroy:!0},dnsProvider:"route53",cdnEnabled:!!m.cdn,wafEnabled:!!m.waf});n(`Created ${D.name} (${D.id}); run region:rollout with an immutable manifest.`)})),R.command("region:plan <name> <operation>","Preview exact regional execution steps").option("--target <region>","Failover or failback region").action(async(t,m,k)=>ho((D)=>c(["Step"],D.service.plan(ys(D,t).id,m,k.target).map((L)=>[L])))),R.command("region:rollout <name> <revision>","Queue secondary-first regional stack rollout").option("--manifest <file>","Regional stack manifest JSON").action(async(t,m,k)=>ho(async(D)=>{let L=k.manifest?await Bun.file(k.manifest).json():{},z=D.service.enqueue({topologyId:ys(D,t).id,kind:"rollout",revision:m,manifest:L});n(`Regional rollout queued: ${z.operation.id}`)})),R.command("region:failover <name> <region>","Queue health and RPO-gated failover").action(async(t,m)=>ho((k)=>{let D=k.service.enqueue({topologyId:ys(k,t).id,kind:"failover",targetRegion:m});n(`Failover queued: ${D.operation.id}`)})),R.command("region:failback <name> <region>","Queue health and RPO-gated failback").action(async(t,m)=>ho((k)=>{let D=k.service.enqueue({topologyId:ys(k,t).id,kind:"failback",targetRegion:m});n(`Failback queued: ${D.operation.id}`)})),R.command("region:destroy <name>","Drain traffic and delete regional stacks while retaining data").option("--confirm <name>","Exact topology name").action(async(t,m)=>ho((k)=>{let D=k.service.enqueue({topologyId:ys(k,t).id,kind:"destroy",confirmation:m.confirm});n(`Regional teardown queued: ${D.operation.id}; replicated data remains retained.`)}))}var vs="0.16.69";async function Lp(){let R=await Ne(),t=y(process.cwd(),R),m=new zn(t.store),k=new Hn(m),D=["releases","artifacts","operations","logs","previews","snapshots","orphaned_resources"].map((L)=>new Gn(L,t.store,t.project.id,t.organization.id));return{controlPlane:t,store:m,service:k,cleanup:D}}async function Ai(R){let t=await Lp();try{await R(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}function oc(R){R.command("maintenance:list","List updates, campaigns, cleanup plans, and DR drills").option("--json","Print structured JSON").action(async(t)=>Ai((m)=>{let k={manifests:m.store.manifests(m.controlPlane.organization.id),windows:m.store.windows(m.controlPlane.project.id),campaigns:m.store.campaigns(m.controlPlane.project.id),cleanups:m.store.cleanups(m.controlPlane.project.id),drills:m.store.drills(m.controlPlane.project.id)},D=[...k.manifests.map((L)=>["update",L.version,L.compatibility.compatible?"compatible":"blocked",L.compatibility.reasons.join("; ")||L.channel]),...k.campaigns.map((L)=>["upgrade",L.id,L.status,L.currentStage??L.fromVersion]),...k.cleanups.map((L)=>["cleanup",L.kind,L.status,`${L.candidates.length} items / ${L.estimatedBytes} bytes`]),...k.drills.map((L)=>["DR drill",L.scenario,L.status,L.isolatedTarget])];if(t.json)console.log(JSON.stringify(k,null,2));else c(["Area","Name / version","Status","Detail"],D)})),R.command("maintenance:key:add <name> <pem>","Trust an Ed25519 update signing key").action(async(t,m)=>Ai(async(k)=>{let D=k.store.addTrustedKey({organizationId:k.controlPlane.organization.id,name:t,publicKeyPem:await Bun.file(m).text()});n(`Trusted ${D.name}: ${D.fingerprint}`)})),R.command("maintenance:manifest <document> <signature> <key>","Verify and import a signed update manifest").action(async(t,m,k)=>Ai(async(D)=>{let L=await Bun.file(t).json(),z=(await Bun.file(m).text()).trim(),Y=D.store.importManifest({organizationId:D.controlPlane.organization.id,keyId:k,document:L,signature:z,current:{schemaVersion:D.controlPlane.store.health().schemaVersion,currentVersion:vs,platform:process.platform,architecture:process.arch}});n(`Verified ${Y.version}; ${Y.compatibility.compatible?"compatible":"blocked: "+Y.compatibility.reasons.join("; ")}`)})),R.command("maintenance:window <name> <schedule>","Create an approved maintenance window").option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--minutes <minutes>","Duration",{default:"60"}).action(async(t,m,k)=>Ai((D)=>{let L=D.store.createWindow({projectId:D.controlPlane.project.id,name:t,schedule:m,timezone:k.timezone,durationMinutes:Number(k.minutes),allowedOperations:["platform.upgrade"],requireApproval:!0});n(`Created ${L.name} (${L.id}).`)})),R.command("platform:upgrade <manifest> <backup>","Plan and queue a signed staged platform update").option("--strategy <strategy>","canary or rolling",{default:"canary"}).option("--batch <count>","Rolling batch size",{default:"1"}).action(async(t,m,k)=>Ai((D)=>{let L=new xe(D.controlPlane.store).list(D.controlPlane.project.id).filter((X)=>X.status==="ready").map((X)=>({serverId:X.id,target:X.name,previousVersion:X.bootstrapVersion??vs}));if(!L.length)L.push({target:"control-plane",previousVersion:vs});let z=D.store.createCampaign({projectId:D.controlPlane.project.id,manifestId:t,fromVersion:vs,strategy:k.strategy==="rolling"?"rolling":"canary",batchSize:Number(k.batch),healthGate:{kind:"platform"},backupId:m,targets:L}),Y=D.service.enqueueCampaign(z.id);n(`Staged upgrade queued: ${Y.operation.id}`)})),R.command("cleanup:preview <kind>","Discover a bounded cleanup candidate set without deleting").option("--older-than <days>","Minimum age",{default:"30"}).action(async(t,m)=>Ai(async(k)=>{let D=await k.service.previewCleanup({projectId:k.controlPlane.project.id,kind:t,criteria:{olderThanDays:Number(m.olderThan)}},k.cleanup);c(["ID","Candidate","Bytes"],D.candidates.map((L)=>[L.id,L.label,String(L.bytes)])),e(`Apply with: cloud cleanup:apply ${D.id} --confirm "DELETE ${D.candidates.length} ${D.kind}"`)})),R.command("cleanup:apply <plan>","Apply an unchanged cleanup preview").option("--confirm <text>","Exact preview confirmation").action(async(t,m)=>Ai((k)=>{let D=k.service.enqueueCleanup(t,String(m.confirm??""));n(`Cleanup queued: ${D.operation.id}`)})),R.command("dr:plan <scenario> <target>","Plan an isolated disaster-recovery drill").option("--rpo <minutes>","RPO objective",{default:"60"}).option("--rto <minutes>","RTO objective",{default:"30"}).option("--recovery-point <id>","Recovery point").option("--topology <id>","Regional topology").action(async(t,m,k)=>Ai((D)=>{let L=D.store.createDrill({projectId:D.controlPlane.project.id,scenario:t,isolatedTarget:m,expectedRpoMinutes:Number(k.rpo),expectedRtoMinutes:Number(k.rto),recoveryPointId:k.recoveryPoint,topologyId:k.topology});n(`Planned isolated drill ${L.id}.`)})),R.command("dr:run <drill>","Queue an isolated disaster-recovery drill").action(async(t)=>Ai((m)=>{let k=m.service.enqueueDrill(t);n(`DR drill queued: ${k.operation.id}`)}))}async function sc(){try{return(await Ne()).project.region||process.env.AWS_REGION||"us-east-1"}catch{return process.env.AWS_REGION||"us-east-1"}}function ac(R){R.command("function:list","List all Lambda functions").option("--region <region>","AWS region").action(async(t)=>{i("Lambda Functions");let m=t?.region||await sc(),k=new P(m);try{let{Functions:D=[]}=await k.listFunctions({MaxItems:50});if(!D.length){e("No functions found");return}c(["Name","Runtime","Memory","Timeout","Last Modified"],D.map((L)=>[L.FunctionName??"-",L.Runtime??"-",L.MemorySize?`${L.MemorySize} MB`:"-",L.Timeout?`${L.Timeout}s`:"-",L.LastModified??"-"]))}catch(D){r(`Failed to list functions: ${D.message}`),process.exitCode=1}}),R.command("function:invoke <name>","Invoke a Lambda function").option("--payload <json>","Event payload as JSON",{default:"{}"}).option("--async","Fire-and-forget (Event invocation)").option("--region <region>","AWS region").action(async(t,m)=>{i(`Invoking ${t}`);let k=m?.region||await sc(),D=new P(k),L=new s("Invoking function...");L.start();try{let z=await D.invoke({FunctionName:t,InvocationType:m?.async?"Event":"RequestResponse",Payload:m?.payload||"{}",LogType:m?.async?"None":"Tail"});if(z.FunctionError){if(L.fail(`Function returned an error (${z.FunctionError})`),z.Payload)e(z.Payload);process.exitCode=1;return}if(L.succeed(`Invoked (status ${z.StatusCode})`),z.Payload)e(`
|
|
319
319
|
Response:`),e(z.Payload);if(z.LogResult)e(`
|
|
320
320
|
Logs:`),e(Buffer.from(z.LogResult,"base64").toString("utf-8"))}catch(z){L.fail(`Invocation failed: ${z.message}`),process.exitCode=1}}),R.command("function:logs <name>","View recent function logs").option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <minutes>","Look back this many minutes",{default:"15"}).option("--region <region>","AWS region").action(async(t,m)=>{i(`Logs for ${t}`);let k=m?.region||await sc(),D=new se(k),L=t.startsWith("/aws/lambda/")?t:`/aws/lambda/${t}`,z=Number(m?.since??15),Y=(X)=>{for(let ne of X){let re=ne.timestamp?new Date(ne.timestamp).toISOString():"";e(`${re} ${(ne.message??"").trimEnd()}`)}};try{let X=Date.now()-z*60000,{events:ne=[]}=await D.filterLogEvents({logGroupName:L,startTime:X,filterPattern:m?.filter,limit:200});if(Y(ne),ne.length)X=(ne[ne.length-1].timestamp??X)+1;if(m?.tail){e(`
|
|
321
|
-
(streaming — Ctrl+C to stop)`);for(;;){await new Promise((ie)=>setTimeout(ie,3000));let{events:re=[]}=await D.filterLogEvents({logGroupName:L,startTime:X,filterPattern:m?.filter,limit:200});if(re.length)Y(re),X=(re[re.length-1].timestamp??X)+1}}}catch(X){r(`Failed to read logs: ${X.message}`),process.exitCode=1}})}async function Rd(R){let t=Bun.spawn(["docker",...R],{stdin:"inherit",stdout:"inherit",stderr:"pipe"}),[m,k]=await Promise.all([t.exited,new Response(t.stderr).text()]);return{ok:m===0,exitCode:m,command:["docker",...R].join(" "),stderr:k.trim()}}function cc(R){R.command("container:build","Build a Docker image").option("--tag <tag>","Image tag",{default:"latest"}).option("--file <dockerfile>","Dockerfile",{default:"Dockerfile"}).option("--dry-run","Print the exact command").option("--json","Print result JSON").action(async(t)=>{let m=["build","--file",t.file??"Dockerfile","--tag",t.tag??"latest","."];if(t.dryRun){console.log(`docker ${m.join(" ")}`);return}let k=await Rd(m);if(t.json)console.log(JSON.stringify(k));if(!k.ok)r(k.stderr||`docker exited ${k.exitCode}`),process.exitCode=k.exitCode;else n(`Built ${t.tag??"latest"}.`)}),R.command("container:push","Push an already-tagged image").option("--image <image>","Full registry image reference").option("--dry-run","Print command").option("--json","Print JSON").action(async(t)=>{if(!t.image){r("--image with a full registry reference is required; credentials must come from docker credential helpers."),process.exitCode=2;return}if(t.dryRun){console.log(`docker push ${t.image}`);return}let m=await Rd(["push",t.image]);if(t.json)console.log(JSON.stringify(m));if(!m.ok)r(m.stderr),process.exitCode=m.exitCode;else n(`Pushed ${t.image}.`)}),R.command("container:deploy","Deploy a container image").action(async()=>
|
|
321
|
+
(streaming — Ctrl+C to stop)`);for(;;){await new Promise((ie)=>setTimeout(ie,3000));let{events:re=[]}=await D.filterLogEvents({logGroupName:L,startTime:X,filterPattern:m?.filter,limit:200});if(re.length)Y(re),X=(re[re.length-1].timestamp??X)+1}}}catch(X){r(`Failed to read logs: ${X.message}`),process.exitCode=1}})}async function Rd(R){let t=Bun.spawn(["docker",...R],{stdin:"inherit",stdout:"inherit",stderr:"pipe"}),[m,k]=await Promise.all([t.exited,new Response(t.stderr).text()]);return{ok:m===0,exitCode:m,command:["docker",...R].join(" "),stderr:k.trim()}}function cc(R){R.command("container:build","Build a Docker image").option("--tag <tag>","Image tag",{default:"latest"}).option("--file <dockerfile>","Dockerfile",{default:"Dockerfile"}).option("--dry-run","Print the exact command").option("--json","Print result JSON").action(async(t)=>{let m=["build","--file",t.file??"Dockerfile","--tag",t.tag??"latest","."];if(t.dryRun){console.log(`docker ${m.join(" ")}`);return}let k=await Rd(m);if(t.json)console.log(JSON.stringify(k));if(!k.ok)r(k.stderr||`docker exited ${k.exitCode}`),process.exitCode=k.exitCode;else n(`Built ${t.tag??"latest"}.`)}),R.command("container:push","Push an already-tagged image").option("--image <image>","Full registry image reference").option("--dry-run","Print command").option("--json","Print JSON").action(async(t)=>{if(!t.image){r("--image with a full registry reference is required; credentials must come from docker credential helpers."),process.exitCode=2;return}if(t.dryRun){console.log(`docker push ${t.image}`);return}let m=await Rd(["push",t.image]);if(t.json)console.log(JSON.stringify(m));if(!m.ok)r(m.stderr),process.exitCode=m.exitCode;else n(`Pushed ${t.image}.`)}),R.command("container:deploy","Deploy a container image").action(async()=>En("container:deploy",{message:"Direct ECS mutation is not registered as a shared release driver.",nextAction:"Import the image as an application and use the immutable release deployment workflow."}))}function lc(R){R.command("domain:list","List all domains").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").action(async(t)=>{i("Domains");try{let m=Vr(t?.provider),k=bn(t?.provider)?.provider||"unknown",D=new s(`Fetching domains from ${k}...`);D.start();let L=await m.listDomains();if(D.succeed(`Found ${L.length} domain(s)`),L.length===0){e("No domains found in this provider");return}let z=L.map((Y)=>[Y,"Active","-",k.charAt(0).toUpperCase()+k.slice(1)]);c(["Domain","Status","SSL","Provider"],z)}catch(m){r(`Failed to list domains: ${m instanceof Error?m.message:String(m)}`)}}),R.command("domain:add <domain>","Add a new domain").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").action(async(t,m)=>{i(`Adding Domain: ${t}`);try{let k=Vr(m?.provider),D=bn(m?.provider)?.provider||"unknown",L=new s(`Checking if ${D} can manage ${t}...`);if(L.start(),await k.canManageDomain(t))L.succeed(`Domain ${t} is already available in ${D}`),e(`
|
|
322
322
|
The domain is ready to use. You can now:`),e(` - Add DNS records: cloud dns:add ${t} A 192.168.1.1`),e(` - Generate SSL: cloud domain:ssl ${t}`);else if(L.warn(`Domain ${t} is not available in ${D}`),e(`
|
|
323
|
-
To add this domain:`),D==="route53")e(" - Create a hosted zone in Route53 for this domain"),e(" - Update nameservers at your registrar to point to Route53");else e(` - Ensure the domain is registered with ${D}`),e(" - Enable API access for the domain in your provider dashboard")}catch(k){r(`Failed to check domain: ${k instanceof Error?k.message:String(k)}`)}}),R.command("domain:ssl <domain>","Generate SSL certificate via ACM with DNS validation").option("--provider <provider>","DNS provider for validation: porkbun, godaddy, or route53").option("--region <region>","AWS region for ACM (default: us-east-1 for CloudFront compatibility)").option("--wait","Wait for certificate validation to complete").action(async(t,m)=>{i(`Generating SSL Certificate for ${t}`);try{let k=
|
|
323
|
+
To add this domain:`),D==="route53")e(" - Create a hosted zone in Route53 for this domain"),e(" - Update nameservers at your registrar to point to Route53");else e(` - Ensure the domain is registered with ${D}`),e(" - Enable API access for the domain in your provider dashboard")}catch(k){r(`Failed to check domain: ${k instanceof Error?k.message:String(k)}`)}}),R.command("domain:ssl <domain>","Generate SSL certificate via ACM with DNS validation").option("--provider <provider>","DNS provider for validation: porkbun, godaddy, or route53").option("--region <region>","AWS region for ACM (default: us-east-1 for CloudFront compatibility)").option("--wait","Wait for certificate validation to complete").action(async(t,m)=>{i(`Generating SSL Certificate for ${t}`);try{let k=bn(m?.provider);if(!k)throw Error("No DNS provider configured");let D=m?.region||"us-east-1",L=k.provider;e(`DNS Provider: ${L}`),e(`ACM Region: ${D}`);let z=new Ht(k,D),Y=new s("Requesting certificate and creating validation records...");Y.start();let X=await z.findOrCreateCertificate({domainName:t,subjectAlternativeNames:[`*.${t}`],waitForValidation:m?.wait??!0,maxWaitMinutes:10});if(X.isNew)Y.succeed("Certificate requested and validation records created");else Y.succeed("Found existing valid certificate");if(e(`Certificate ARN: ${X.certificateArn}`),e(`Status: ${X.status}`),X.status==="issued")n(`
|
|
324
324
|
SSL Certificate is ready!`),e(`
|
|
325
325
|
You can now use this certificate with:`),e(" - CloudFront distributions"),e(" - Application Load Balancers"),e(" - API Gateway custom domains");else if(X.status==="pending")e(`
|
|
326
326
|
DNS validation records have been created.`),e("Certificate validation may take a few more minutes."),e(`
|
|
327
327
|
Check status with: cloud domain:verify ${t}`);else r(`
|
|
328
|
-
Certificate validation failed. Check ACM console for details.`)}catch(k){r(`Failed to generate SSL: ${k instanceof Error?k.message:String(k)}`)}}),R.command("domain:verify <domain>","Verify domain ownership and SSL status").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").action(async(t,m)=>{i(`Verifying Domain: ${t}`);try{let k=Vr(m?.provider),D=
|
|
328
|
+
Certificate validation failed. Check ACM console for details.`)}catch(k){r(`Failed to generate SSL: ${k instanceof Error?k.message:String(k)}`)}}),R.command("domain:verify <domain>","Verify domain ownership and SSL status").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").action(async(t,m)=>{i(`Verifying Domain: ${t}`);try{let k=Vr(m?.provider),D=bn(m?.provider)?.provider||"unknown",L=new s("Checking domain ownership...");if(L.start(),!await k.canManageDomain(t)){L.fail("Domain not found in provider"),r(`Domain ${t} is not available in ${D}`);return}L.succeed("Domain ownership verified"),L.text="Fetching DNS records...",L.start();let X=(await k.listRecords(t)).records||[];L.succeed(`Found ${X.length} DNS record(s)`),L.text="Checking SSL certificate status...",L.start();let ne=new W("us-east-1"),re="Not found",ie="";try{let Ae=(await ne.listCertificates()).CertificateSummaryList.find((le)=>le.DomainName===t||le.DomainName===`*.${t}`);if(Ae)ie=Ae.CertificateArn||"",re=(await ne.describeCertificate({CertificateArn:ie})).Status||"Unknown"}catch{}if(L.succeed("SSL check complete"),e(`
|
|
329
329
|
Verification details:`),e(` - Provider: ${D}`),e(` - DNS records found: ${X.length}`),e(" - Domain managed: Yes"),e(` - SSL certificate: ${re}`),ie)e(` - Certificate ARN: ${ie}`);let ae=new Map;for(let be of X){let Ae=ae.get(be.type)||0;ae.set(be.type,Ae+1)}if(ae.size>0){e(`
|
|
330
|
-
Record summary:`);for(let[be,Ae]of ae)e(` - ${be}: ${Ae}`)}}catch(k){r(`Failed to verify domain: ${k instanceof Error?k.message:String(k)}`)}}),R.command("dns:migrate <domain>","Migrate a whole zone from Route53 to Cloudflare").option("--account <id>","Cloudflare account id (defaults to CLOUDFLARE_ACCOUNT_ID)").option("--hosted-zone <id>","Route53 hosted zone id (auto-discovered when omitted)").option("--region <region>","AWS region for Route53",{default:"us-east-1"}).option("--apply","Write the records. Without it this is a dry run.").action(async(t,m)=>{i(`Zone migration: ${t} (Route53 → Cloudflare)`);let k=process.env.CLOUDFLARE_API_TOKEN;if(m?.apply&&!k){r("CLOUDFLARE_API_TOKEN is not set — required for --apply.");return}let D=m?.account??process.env.CLOUDFLARE_ACCOUNT_ID;try{let L=new F(m?.region||"us-east-1"),z=m?.hostedZone;if(!z){let le=await L.findHostedZoneForDomain(t);if(!le){r(`No Route53 hosted zone found for ${t}`);return}z=le.Id.replace("/hostedzone/","")}let Y=new s(`Exporting ${t} from Route53...`);Y.start();let X=await Io(L,z);Y.succeed(`Exported ${X.length} record set(s) from ${z}`);let ne=Po(X,t);e(`Planned ${ne.records.length} record(s), skipped ${ne.skipped.length}`);for(let le of ne.skipped)e(` - ${le.type} ${le.name}: ${le.reason}`);for(let le of ne.warnings)a(` ⚠ ${le}`);for(let le of ne.records.filter((ke)=>ke.translatedFrom))e(` ~ ${le.name} ${le.translatedFrom} → CNAME ${le.content}`);if(!m?.apply){e(""),e("Dry run — nothing was written. Re-run with --apply to migrate.");return}let re=new he(k,{accountId:D}),ie=await re.createZone(t,{accountId:D});n(ie.created?`Created Cloudflare zone ${ie.name} (${ie.id})`:`Using existing Cloudflare zone ${ie.name} (${ie.id})`);let ae=new s(`Importing ${ne.records.length} record(s)...`);ae.start();let be=await Do(re,ne);ae.succeed(`Imported ${be.applied.length} record(s)`);for(let le of be.failed)r(` ✗ ${le.record.type} ${le.record.name}: ${le.message}`);let Ae=await ko(re,ne);if(Ae.ok)n(`Parity check passed — all ${Ae.matched.length} record(s) present at Cloudflare`);else{r(`Parity check FAILED — ${Ae.missing.length} record(s) missing or different:`);for(let le of Ae.missing)r(` ✗ ${le.record.type} ${le.record.name} → ${le.record.content}${le.found?` (found: ${le.found})`:""}`);a("Do NOT move the nameservers until this is clean.");return}e(""),e("Nameservers to set at the registrar:");for(let le of ie.nameServers)e(` ${le}`);e(""),e(` cloud dns:nameservers ${t} --set ${ie.nameServers.join(",")}`)}catch(L){r(`Migration failed: ${L instanceof Error?L.message:String(L)}`)}}),R.command("dns:nameservers <domain>","Show or set the nameservers a domain is delegated to").option("--registrar <name>","Registrar: godaddy or porkbun").option("--set <nameservers>","Comma-separated nameservers to delegate to").action(async(t,m)=>{i(`Nameservers for ${t}`);let k=(m?.registrar||Fp())?.toLowerCase();if(!k){r("No registrar credentials found. Set GODADDY_PAT (or GODADDY_API_KEY/GODADDY_API_SECRET) or PORKBUN_API_KEY/PORKBUN_SECRET_KEY.");return}let D=k==="porkbun"?new nt(process.env.PORKBUN_API_KEY,process.env.PORKBUN_SECRET_KEY):new zt(process.env.GODADDY_PAT||process.env.GODADDY_API_KEY,process.env.GODADDY_PAT?"":process.env.GODADDY_API_SECRET);try{let L=await D.getNameServers(t);e(`Current (${k}):`);for(let X of L)e(` ${X}`);if(!m?.set)return;let z=m.set.split(",").map((X)=>X.trim()).filter(Boolean);if(z.length<2){r("At least two nameservers are required.");return}if(a(""),a("Changing nameservers moves authority for the ENTIRE zone — every"),a("record on it, including mail. Make sure the destination zone is"),a("already populated and parity-checked (cloud dns:migrate)."),await D.updateNameServers(t,z)){n(`Delegated ${t} to:`);for(let X of z)n(` ${X}`);e("Propagation typically takes minutes to a few hours.")}else r("The registrar rejected the nameserver update.")}catch(L){r(`Failed: ${L instanceof Error?L.message:String(L)}`)}}),R.command("dns:records <domain>","List DNS records for a domain").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").option("--type <type>","Filter by record type (A, AAAA, CNAME, TXT, MX, etc.)").action(async(t,m)=>{i(`DNS Records for ${t}`);try{let k=Vr(m?.provider),D=
|
|
330
|
+
Record summary:`);for(let[be,Ae]of ae)e(` - ${be}: ${Ae}`)}}catch(k){r(`Failed to verify domain: ${k instanceof Error?k.message:String(k)}`)}}),R.command("dns:migrate <domain>","Migrate a whole zone from Route53 to Cloudflare").option("--account <id>","Cloudflare account id (defaults to CLOUDFLARE_ACCOUNT_ID)").option("--hosted-zone <id>","Route53 hosted zone id (auto-discovered when omitted)").option("--region <region>","AWS region for Route53",{default:"us-east-1"}).option("--apply","Write the records. Without it this is a dry run.").action(async(t,m)=>{i(`Zone migration: ${t} (Route53 → Cloudflare)`);let k=process.env.CLOUDFLARE_API_TOKEN;if(m?.apply&&!k){r("CLOUDFLARE_API_TOKEN is not set — required for --apply.");return}let D=m?.account??process.env.CLOUDFLARE_ACCOUNT_ID;try{let L=new F(m?.region||"us-east-1"),z=m?.hostedZone;if(!z){let le=await L.findHostedZoneForDomain(t);if(!le){r(`No Route53 hosted zone found for ${t}`);return}z=le.Id.replace("/hostedzone/","")}let Y=new s(`Exporting ${t} from Route53...`);Y.start();let X=await Io(L,z);Y.succeed(`Exported ${X.length} record set(s) from ${z}`);let ne=Po(X,t);e(`Planned ${ne.records.length} record(s), skipped ${ne.skipped.length}`);for(let le of ne.skipped)e(` - ${le.type} ${le.name}: ${le.reason}`);for(let le of ne.warnings)a(` ⚠ ${le}`);for(let le of ne.records.filter((ke)=>ke.translatedFrom))e(` ~ ${le.name} ${le.translatedFrom} → CNAME ${le.content}`);if(!m?.apply){e(""),e("Dry run — nothing was written. Re-run with --apply to migrate.");return}let re=new he(k,{accountId:D}),ie=await re.createZone(t,{accountId:D});n(ie.created?`Created Cloudflare zone ${ie.name} (${ie.id})`:`Using existing Cloudflare zone ${ie.name} (${ie.id})`);let ae=new s(`Importing ${ne.records.length} record(s)...`);ae.start();let be=await Do(re,ne);ae.succeed(`Imported ${be.applied.length} record(s)`);for(let le of be.failed)r(` ✗ ${le.record.type} ${le.record.name}: ${le.message}`);let Ae=await ko(re,ne);if(Ae.ok)n(`Parity check passed — all ${Ae.matched.length} record(s) present at Cloudflare`);else{r(`Parity check FAILED — ${Ae.missing.length} record(s) missing or different:`);for(let le of Ae.missing)r(` ✗ ${le.record.type} ${le.record.name} → ${le.record.content}${le.found?` (found: ${le.found})`:""}`);a("Do NOT move the nameservers until this is clean.");return}e(""),e("Nameservers to set at the registrar:");for(let le of ie.nameServers)e(` ${le}`);e(""),e(` cloud dns:nameservers ${t} --set ${ie.nameServers.join(",")}`)}catch(L){r(`Migration failed: ${L instanceof Error?L.message:String(L)}`)}}),R.command("dns:nameservers <domain>","Show or set the nameservers a domain is delegated to").option("--registrar <name>","Registrar: godaddy or porkbun").option("--set <nameservers>","Comma-separated nameservers to delegate to").action(async(t,m)=>{i(`Nameservers for ${t}`);let k=(m?.registrar||Fp())?.toLowerCase();if(!k){r("No registrar credentials found. Set GODADDY_PAT (or GODADDY_API_KEY/GODADDY_API_SECRET) or PORKBUN_API_KEY/PORKBUN_SECRET_KEY.");return}let D=k==="porkbun"?new nt(process.env.PORKBUN_API_KEY,process.env.PORKBUN_SECRET_KEY):new zt(process.env.GODADDY_PAT||process.env.GODADDY_API_KEY,process.env.GODADDY_PAT?"":process.env.GODADDY_API_SECRET);try{let L=await D.getNameServers(t);e(`Current (${k}):`);for(let X of L)e(` ${X}`);if(!m?.set)return;let z=m.set.split(",").map((X)=>X.trim()).filter(Boolean);if(z.length<2){r("At least two nameservers are required.");return}if(a(""),a("Changing nameservers moves authority for the ENTIRE zone — every"),a("record on it, including mail. Make sure the destination zone is"),a("already populated and parity-checked (cloud dns:migrate)."),await D.updateNameServers(t,z)){n(`Delegated ${t} to:`);for(let X of z)n(` ${X}`);e("Propagation typically takes minutes to a few hours.")}else r("The registrar rejected the nameserver update.")}catch(L){r(`Failed: ${L instanceof Error?L.message:String(L)}`)}}),R.command("dns:records <domain>","List DNS records for a domain").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").option("--type <type>","Filter by record type (A, AAAA, CNAME, TXT, MX, etc.)").action(async(t,m)=>{i(`DNS Records for ${t}`);try{let k=Vr(m?.provider),D=bn(m?.provider)?.provider||"unknown",L=new s(`Fetching records from ${D}...`);L.start();let Y=(await k.listRecords(t)).records||[];if(L.succeed(`Found ${Y.length} record(s)`),m?.type){let ne=m.type.toUpperCase();Y=Y.filter((re)=>re.type.toUpperCase()===ne),e(`Filtered to ${Y.length} ${ne} record(s)`)}if(Y.length===0){e("No records found");return}let X=Y.map((ne)=>[ne.type,ne.name||"@",ne.content.length>50?`${ne.content.substring(0,47)}...`:ne.content,String(ne.ttl||300)]);c(["Type","Name","Value","TTL"],X)}catch(k){r(`Failed to list records: ${k instanceof Error?k.message:String(k)}`)}}),R.command("dns:add <domain> <type> <value>","Add DNS record").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").option("--name <name>","Record name (subdomain)",{default:"@"}).option("--ttl <seconds>","Time to live in seconds",{default:"300"}).action(async(t,m,k,D)=>{i("Adding DNS Record");try{let L=Vr(D?.provider),z=bn(D?.provider)?.provider||"unknown",Y=D?.name||"@",X=Number.parseInt(D?.ttl||"300",10),ne=m.toUpperCase();e(`Provider: ${z}`),e(`Domain: ${t}`),e(`Type: ${ne}`),e(`Name: ${Y}`),e(`Value: ${k}`),e(`TTL: ${X}`);let re=new s(`Adding record via ${z}...`);re.start();let ie=await L.createRecord(t,{type:ne,name:Y==="@"?"":Y,content:k,ttl:X});if(!ie.success){re.fail("Failed to add DNS record"),r(ie.message||"DNS provider rejected the record");return}re.succeed("DNS record added successfully"),n(`
|
|
331
331
|
Record created!`),e(`
|
|
332
|
-
Note: DNS changes may take a few minutes to propagate`)}catch(L){r(`Failed to add record: ${L instanceof Error?L.message:String(L)}`)}}),R.command("dns:delete <domain> <type>","Delete DNS record").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").option("--name <name>","Record name (subdomain)",{default:"@"}).option("--value <value>","Record value (required for multi-value records)").action(async(t,m,k)=>{i("Deleting DNS Record");try{let D=Vr(k?.provider),L=
|
|
332
|
+
Note: DNS changes may take a few minutes to propagate`)}catch(L){r(`Failed to add record: ${L instanceof Error?L.message:String(L)}`)}}),R.command("dns:delete <domain> <type>","Delete DNS record").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").option("--name <name>","Record name (subdomain)",{default:"@"}).option("--value <value>","Record value (required for multi-value records)").action(async(t,m,k)=>{i("Deleting DNS Record");try{let D=Vr(k?.provider),L=bn(k?.provider)?.provider||"unknown",z=k?.name||"@",Y=m.toUpperCase();e(`Provider: ${L}`),e(`Domain: ${t}`),e(`Type: ${Y}`),e(`Name: ${z}`);let X=new s("Finding record...");X.start();let ie=((await D.listRecords(t)).records||[]).filter((Ae)=>Ae.type.toUpperCase()===Y&&(Ae.name===z||Ae.name===""&&z==="@"));if(ie.length===0){X.fail("No matching record found");return}if(ie.length>1&&!k?.value){X.warn("Multiple records found"),e(`
|
|
333
333
|
Please specify --value to identify which record to delete:`);for(let Ae of ie)e(` - ${Ae.content}`);return}let ae=k?.value?ie.find((Ae)=>Ae.content===k.value)||ie[0]:ie[0];if(e(`Value: ${ae.content}`),!await d("Delete this record?",!1)){e("Deletion cancelled");return}X.text=`Deleting record via ${L}...`,X.start(),await D.deleteRecord(t,ae),X.succeed("DNS record deleted successfully"),n(`
|
|
334
|
-
Record deleted!`)}catch(D){r(`Failed to delete record: ${D instanceof Error?D.message:String(D)}`)}})}function Fp(){if(process.env.GODADDY_PAT)return"godaddy";if(process.env.GODADDY_API_KEY&&process.env.GODADDY_API_SECRET)return"godaddy";if(process.env.PORKBUN_API_KEY&&process.env.PORKBUN_SECRET_KEY)return"porkbun";return null}var Id=["postgres","mysql","mariadb","redis","mongodb","libsql"],Pd=["aws_rds","aws_aurora","aws_elasticache","server","container","external"],xd=["observe","connect","backup","restore","restart","resize","version","rotate","expose","delete","logs","slow_queries","users","databases"];async function jp(R,t){let m=await Ne(),k=y(process.cwd(),m),D=new or(k.store),L=!R&&t?D.get(t)??D.list(k.project.id).find((le)=>le.name===t):void 0,z=L?.environmentId?k.store.listEnvironments(k.project.id).find((le)=>le.id===L.environmentId)?.slug:void 0,Y=R??z??Object.keys(m.environments??{})[0]??"production",X=k.environments.get(Y);if(!X)throw k.store.close(),Error(`Environment ${Y} was not found.`);let ne=new S(k.store,{workerId:`cli:${process.pid}`}),re=new sr(k.store,_(process.cwd())),ie=new qr(D,ne,re),ae=k.store.getActorByExternalId("system","cli")??k.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),be=m.project?.region??"us-east-1",Ae={aws_rds:new qn(new Qn(new fe(be))),aws_aurora:new Wn(new Yn(new fe(be))),aws_elasticache:new Kn(new Zn(new oe(be))),server:new Jn(new lt),container:new Xn(new lt)};return{config:m,controlPlane:k,environmentRecord:X,env:Y,store:D,queue:ne,secrets:re,lifecycle:ie,actor:ae,resolveAdapter:(le)=>Ae[le.provider]}}async function wr(R,t,m){let k=await jp(R,m);try{return await t(k)}finally{k.controlPlane.store.close()}}async function Dn(R){try{await R()}catch(t){r(t instanceof Error?t.message:String(t))}}function dc(R){if(!Id.includes(R))throw Error(`--engine must be ${Id.join(", ")}.`);return R}function uc(R){if(!Pd.includes(R))throw Error(`--provider must be ${Pd.join(", ")}.`);return R}function vo(R,t){let m=R.store.list(R.controlPlane.project.id,R.environmentRecord.id),k=R.store.get(t)??m.find((D)=>D.name===t);if(!k||k.environmentId!==R.environmentRecord.id)throw Error(`Data service ${t} was not found in ${R.env}.`);return k}function Dd(R){return R.map((t)=>[t.id,t.name,t.engine,t.provider,t.status,t.plan,t.publicExposure?"public allowlist":"private",t.managementEnabled?"managed":"read-only"])}function Bp(R,t){return R!==null&&typeof R==="object"&&!Array.isArray(R)&&R.serviceId===t}function _p(R){let t={};if(R.plan)t.plan=R.plan;if(R.storage!=null)t.storageGb=Number(R.storage);if(R.engineVersion)t.engineVersion=R.engineVersion;if(R.retention)t.retention=R.retention;if(R.confirm)t.confirm=R.confirm;if(R.backupId)t.backupId=R.backupId;if(R.compatibilityReviewed)t.compatibilityReviewed=!0;if(R.minCapacity!=null)t.minCapacity=Number(R.minCapacity);if(R.maxCapacity!=null)t.maxCapacity=Number(R.maxCapacity);if(R.public!=null)t.publicExposure=R.public;if(R.cidrs)t.allowedCidrs=R.cidrs.split(",").map((m)=>m.trim()).filter(Boolean);return t}function Ed(R){let t=typeof R.observedState.endpoint==="string"?R.observedState.endpoint:R.placement,m=Number(R.observedState.port)||(R.engine==="postgres"?5432:R.engine==="redis"?6379:R.engine==="mongodb"?27017:R.engine==="libsql"?8080:3306),k=typeof R.observedState.database==="string"?R.observedState.database:void 0;return[{type:R.publicExposure?"external":"internal",host:t,port:m,database:k,tls:typeof R.observedState.tls==="boolean"?R.observedState.tls:R.provider.startsWith("aws_")},{type:"tunnel",host:t,port:m,database:k,tls:typeof R.observedState.tls==="boolean"?R.observedState.tls:R.provider.startsWith("aws_")}]}function Td(R){R.option("--env <environment>","Target environment").option("--engine <engine>","postgres, mysql, mariadb, redis, mongodb, or libsql",{default:"postgres"}).option("--provider <provider>","aws_rds, aws_aurora, aws_elasticache, server, container, or external",{default:"aws_rds"}).option("--placement <id>","Provider identifier or runtime placement").option("--plan <plan>","Provider plan or instance type",{default:"db.t4g.micro"}).option("--engine-version <version>","Engine version").option("--storage <gb>","Allocated storage in GB",{default:"20"}).option("--username <name>","Generated credential username",{default:"app"}).option("--database <name>","Initial logical database").option("--subnet-group <name>","Private subnet group").option("--security-groups <ids>","Comma-separated security group IDs").option("--high-availability","Enable provider high availability").option("--public","Request a public endpoint; requires narrow --cidrs").option("--cidrs <ranges>","Comma-separated narrow public CIDRs")}function vc(R){R.command("data:list","List first-class databases and caches").option("--env <environment>","Target environment").option("--json","Print structured JSON").action((k)=>Dn(()=>wr(k.env,async(D)=>{let L=D.store.list(D.controlPlane.project.id,D.environmentRecord.id);if(k.json)e(JSON.stringify(L,null,2));else c(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],Dd(L))}))),R.command("data:capabilities <engine> <provider>","Explain lifecycle support before taking action").action((k,D)=>Dn(async()=>{e(JSON.stringify(ir(dc(k),uc(D)),null,2))}));let t=R.command("data:create <name>","Provision an encrypted, private-by-default data service");Td(t),t.action((k,D)=>Dn(()=>wr(D.env,async(L)=>{let z=dc(D.engine),Y=uc(D.provider),X=L.resolveAdapter({provider:Y});if(!X||!X.engines.includes(z))throw Error(`No ${Y}/${z} provisioning adapter is configured. Use data:adopt for read-only inventory.`);let ne={...D.database?{database:D.database}:{},...D.subnetGroup?{subnetGroup:D.subnetGroup}:{},...D.securityGroups?{securityGroupIds:D.securityGroups.split(",").map((ie)=>ie.trim()).filter(Boolean)}:{}},re=await L.lifecycle.create({organizationId:L.controlPlane.organization.id,projectId:L.controlPlane.project.id,environmentId:L.environmentRecord.id,name:k,engine:z,provider:Y,placement:D.placement??k,engineVersion:D.engineVersion,plan:D.plan??"db.t4g.micro",storageGb:Number(D.storage)||void 0,highAvailability:D.highAvailability===!0,publicExposure:D.public===!0,allowedCidrs:D.cidrs?D.cidrs.split(",").map((ie)=>ie.trim()).filter(Boolean):[],desiredState:ne,observedState:{},origin:"managed",managementEnabled:!0,ownerActorId:L.actor.id,username:D.username});if(n(`Queued ${re.service.name} as operation ${re.operationId}.`),re.credential)Nr("One-time credential — copy it now:"),e(`Username: ${re.credential.username}`),e(`Password: ${re.credential.password}`),e(`Reference: ${re.credential.secretRef}`)})));let m=R.command("data:adopt <name>","Adopt an existing provider resource as read-only inventory");Td(m),m.action((k,D)=>Dn(()=>wr(D.env,async(L)=>{let z=L.store.create({organizationId:L.controlPlane.organization.id,projectId:L.controlPlane.project.id,environmentId:L.environmentRecord.id,name:k,engine:dc(D.engine),provider:uc(D.provider),placement:D.placement??k,engineVersion:D.engineVersion,plan:D.plan??"external",storageGb:Number(D.storage)||void 0,highAvailability:D.highAvailability===!0,publicExposure:D.public===!0,allowedCidrs:D.cidrs?D.cidrs.split(",").map((Y)=>Y.trim()).filter(Boolean):[],desiredState:{},observedState:{},credentialRef:void 0,status:"adopted",origin:"adopted",managementEnabled:!1,ownerActorId:L.actor.id});n(`Adopted ${z.name} (${z.id}) read-only.`)}))),R.command("data:show <service>","Show safe service, capability, dependency, and operation metadata").option("--env <environment>","Target environment").option("--reveal","Reveal the encrypted credential with exact confirmation").option("--confirm <name>","Exact service name required with --reveal").action((k,D)=>Dn(()=>wr(D.env,async(L)=>{let z=vo(L,k),Y=L.store.credential(z.id),X={...z,credentialRef:void 0,credential:Y?{configured:!0,username:Y.username,version:Y.version,rotatedAt:Y.rotatedAt}:void 0,dependencies:L.store.dependencies(z.id).map((ne)=>({resourceId:ne.resourceId,requiresRedeploy:ne.requiresRedeploy})),operations:L.queue.list({projectId:z.projectId,limit:250}).filter((ne)=>Bp(ne.operation.input,z.id))};if(e(JSON.stringify(X,null,2)),D.reveal){if(D.confirm!==z.name)throw Error(`Credential reveal requires --confirm ${z.name}.`);if(!Y)throw Error("No managed credential is configured.");Nr("Credential value follows; it will not be logged or persisted by ts-cloud:"),e(`Username: ${Y.username}`),e(`Password: ${await L.secrets.resolve(Y.secretRef)}`)}},k))),R.command("data:connect <service>","Print secret-free connection and tunnel guidance").option("--env <environment>","Target environment").action((k,D)=>Dn(()=>wr(D.env,async(L)=>{let z=vo(L,k);c(["Type","Command","Credential reference"],Gn(z,Ed(z)).map((Y)=>[Y.type,Y.command,Y.secretRef??"—"]))},k))),R.command("data:action <service> <action>","Preview or enqueue a durable lifecycle action").option("--env <environment>","Target environment").option("--execute","Enqueue after validation; default is preview only").option("--plan <plan>","New provider plan").option("--storage <gb>","New storage allocation").option("--engine-version <version>","Target engine version").option("--min-capacity <acu>","Aurora minimum capacity").option("--max-capacity <acu>","Aurora maximum capacity").option("--public","Enable public exposure").option("--cidrs <ranges>","Comma-separated narrow public CIDRs").option("--retention <choice>","final_backup or retain for deletion").option("--confirm <name>","Exact service name for destructive actions").option("--backup-id <id>","Required backup for a major version change").option("--compatibility-reviewed","Confirm major-version compatibility review").action((k,D,L)=>Dn(()=>wr(L.env,async(z)=>{if(!xd.includes(D))throw Error(`Action must be ${xd.join(", ")}.`);let Y=vo(z,k),X=D,ne=_p(L),re=z.lifecycle.plan(Y,X,ne);if(e(JSON.stringify({...re,productionExecutionCreated:!1},null,2)),!L.execute)return;let ie=z.lifecycle.enqueue(Y,X,ne,z.actor.id);n(`Queued ${X} as ${ie}.`)},k))),R.command("data:management <service>","Enable reviewed management of an adopted service").option("--env <environment>","Target environment").option("--confirm <name>","Exact service name").action((k,D)=>Dn(()=>wr(D.env,async(L)=>{let z=vo(L,k);if(z.origin!=="adopted"||D.confirm!==z.name)throw Error(`Enabling adopted management requires --confirm ${z.name}.`);let Y=L.store.update(z.id,z.version,{managementEnabled:!0});n(`${Y.name} management is enabled.`)},k))),R.command("data:dependency <service> <resource>","Register or remove an application credential dependency").option("--env <environment>","Target environment").option("--dynamic","Dependency consumes a dynamic reference and does not need redeploy").option("--remove","Remove the dependency").action((k,D,L)=>Dn(()=>wr(L.env,async(z)=>{let Y=vo(z,k),X=z.controlPlane.store.listResources(z.controlPlane.project.id,z.environmentRecord.id).find((ne)=>ne.id===D||ne.slug===D);if(!X)throw Error(`Resource ${D} was not found in ${z.env}.`);if(L.remove){e(z.store.removeDependency(Y.id,X.id)?"Dependency removed.":"Dependency was not registered.");return}z.store.addDependency({serviceId:Y.id,resourceId:X.id,secretRef:Y.credentialRef??`secret://data-services/${Y.projectId}/${Y.name}`,requiresRedeploy:!L.dynamic}),n(`Registered dependency ${X.slug} -> ${Y.name}.`)},k))),R.command("db:list","Alias for data:list").option("--env <environment>","Target environment").action((k)=>Dn(()=>wr(k.env,async(D)=>c(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],Dd(D.store.list(D.controlPlane.project.id,D.environmentRecord.id)))))),R.command("db:connect <service>","Alias for data:connect").option("--env <environment>","Target environment").action((k,D)=>Dn(()=>wr(D.env,async(L)=>{let z=vo(L,k);c(["Type","Command","Credential reference"],Gn(z,Ed(z)).map((Y)=>[Y.type,Y.command,Y.secretRef??"—"]))},k)));for(let k of["backup","snapshot"])R.command(`db:${k} <service>`,`Alias for data:action ${k==="backup"?"backup":"backup"}`).option("--env <environment>","Target environment").option("--execute","Enqueue the backup; default previews").action((D,L)=>Dn(()=>wr(L.env,async(z)=>{let Y=vo(z,D),X=z.lifecycle.plan(Y,"backup");if(e(JSON.stringify({...X,productionExecutionCreated:!1},null,2)),L.execute)n(`Queued backup as ${z.lifecycle.enqueue(Y,"backup",{},z.actor.id)}.`)},D)))}function Nd(R,t=15){if(!R)return t*60000;let m=/^(\d+)\s*([smhd])?$/.exec(R.trim());if(!m)return t*60000;let k=Number(m[1]),D=m[2]??"m";return k*({s:1000,m:60000,h:3600000,d:86400000}[D]??60000)}function Mp(R,t){for(let m of R){let k=m.timestamp?new Date(m.timestamp).toISOString():"",D=t?`[${t}] `:"";e(`${k} ${D}${(m.message??"").trimEnd()}`)}}function bc(R){R.command("logs","Stream serverless application logs (http + queue + cli)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli | all",{default:"all"}).option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <duration>","Look back (e.g. 1h, 30m, 15)",{default:"15m"}).action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=new se(D),Y=(t?.function??"all").toLowerCase();if(Y!=="all"&&!["http","queue","cli"].includes(Y)){r("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let ne=(Y==="all"?["http","queue","cli"]:[Y]).map((ae)=>({mode:ae,group:`/aws/lambda/${L[ae]}`}));i(`Logs — ${m.project.slug} (${k})`);let re=Date.now()-Nd(t?.since),ie=async()=>{let ae=re;for(let{mode:be,group:Ae}of ne)try{let{events:le=[]}=await z.filterLogEvents({logGroupName:Ae,startTime:re,filterPattern:t?.filter,limit:200});Mp(le,ne.length>1?be:void 0);for(let ke of le)if((ke.timestamp??0)>ae)ae=ke.timestamp??ae}catch(le){if(!/ResourceNotFound/i.test(String(le?.message)))throw le}re=ae+1};if(await ie(),t?.tail){e(`
|
|
335
|
-
(streaming — Ctrl+C to stop)`);for(;;)await new Promise((ae)=>setTimeout(ae,3000)),await ie()}}catch(m){r(`Failed to read logs: ${m.message}`),process.exitCode=1}}),R.command("metrics","Show serverless function metrics (invocations, errors, duration, throttles)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--since <duration>","Window (e.g. 1h, 24h)",{default:"1h"}).action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=new q(D),Y=Nd(t?.since,60),X=new Date,ne=new Date(X.getTime()-Y),re=Math.max(60,Math.round(Y/1000));i(`Metrics — ${m.project.slug} (${k}), last ${t?.since??"1h"}`);let ie=[];for(let ae of["http","queue","cli"]){let be=[{Name:"FunctionName",Value:L[ae]}],Ae=async(Ve,Le)=>{let Fe=await z.getMetricStatistics({Namespace:"AWS/Lambda",MetricName:Ve,Dimensions:be,StartTime:ne,EndTime:X,Period:re,Statistics:[Le]});if(!Fe.length)return 0;if(Le==="Sum")return Fe.reduce((He,wt)=>He+(wt.Sum??0),0);if(Le==="Maximum")return Math.max(...Fe.map((He)=>He.Maximum??0));return Fe.reduce((He,wt)=>He+(wt.Average??0),0)/Fe.length},[le,ke,De,Se,Re]=await Promise.all([Ae("Invocations","Sum"),Ae("Errors","Sum"),Ae("Throttles","Sum"),Ae("Duration","Average"),Ae("Duration","Maximum")]),Pe=le?`${(ke/le*100).toFixed(2)}%`:"—";ie.push([ae,String(le),String(ke),Pe,String(De),`${Se.toFixed(0)}ms`,`${Re.toFixed(0)}ms`])}c(["Function","Invocations","Errors","Error rate","Throttles","Avg","Max"],ie)}catch(m){r(`Failed to read metrics: ${m.message}`),process.exitCode=1}}),R.command("metrics:dashboard","Print the CloudWatch console URL for this app").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=encodeURIComponent(JSON.stringify({metrics:["http","queue","cli"].flatMap((Y)=>[["AWS/Lambda","Invocations","FunctionName",L[Y]],["AWS/Lambda","Errors","FunctionName",L[Y]]]),view:"timeSeries",region:D}));i("CloudWatch dashboard"),e(`https://console.aws.amazon.com/cloudwatch/home?region=${D}#metricsV2:graph=${z}`)}catch(m){r(m.message),process.exitCode=1}}),R.command("alarms","List CloudWatch alarms").option("--prefix <prefix>","Filter by alarm name prefix").option("--region <region>","AWS region").action(async(t)=>{i("CloudWatch Alarms");let m=t?.region;if(!m)try{m=(await Ne()).project.region}catch{}try{let D=await new q(m||process.env.AWS_REGION||"us-east-1").describeAlarms({AlarmNamePrefix:t?.prefix,MaxRecords:100});if(!D.length){e("No alarms found");return}c(["Name","State","Metric","Condition"],D.map((L)=>[L.AlarmName??"-",L.StateValue??"-",L.MetricName??"-",`${L.ComparisonOperator??""} ${L.Threshold??""}`.trim()]))}catch(k){r(`Failed to list alarms: ${k.message}`),process.exitCode=1}}),R.command("alarms:create","Create a CloudWatch alarm on a Lambda metric").option("--name <name>","Alarm name").option("--env <environment>","Environment",{default:"production"}).option("--function <which>","Function: http | queue | cli",{default:"http"}).option("--metric <metric>","Lambda metric (Errors, Throttles, Duration, Invocations)",{default:"Errors"}).option("--threshold <value>","Threshold value").option("--comparison <op>","GreaterThanThreshold | GreaterThanOrEqualToThreshold | LessThanThreshold",{default:"GreaterThanThreshold"}).option("--statistic <stat>","Sum | Average | Maximum | Minimum",{default:"Sum"}).action(async(t)=>{if(i("Creating CloudWatch Alarm"),!t?.name||t?.threshold==null){r("Missing required options: --name and --threshold"),process.exitCode=1;return}try{let m=await Ne(),k=t.env||"production",{region:D,functions:L}=Tt(m,k),z=t.function??"http";await new q(D).putMetricAlarm({AlarmName:t.name,Namespace:"AWS/Lambda",MetricName:t.metric??"Errors",ComparisonOperator:t.comparison??"GreaterThanThreshold",Threshold:Number(t.threshold),EvaluationPeriods:1,Period:300,Statistic:t.statistic??"Sum",Dimensions:[{Name:"FunctionName",Value:L[z]}],AlarmDescription:`ts-cloud: ${L[z]} ${t.metric} ${t.comparison} ${t.threshold}`}),n(`Alarm '${t.name}' created on ${L[z]} ${t.metric}`)}catch(m){r(`Failed to create alarm: ${m.message}`),process.exitCode=1}})}async function Js(R){let t=await Ne(),m=R||"production";return{region:t.environments?.[m]?.region||t.project.region||"us-east-1",prefix:`${t.project.slug}/${m}`}}function Sc(R){R.command("secrets:list","List secrets for an environment").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{i("Secrets");try{let{region:m,prefix:k}=await Js(t?.env),D=new K(m),{SecretList:L=[]}=await D.listSecrets({Filters:[{Key:"name",Values:[`${k}/`]}],MaxResults:100});if(!L.length){e(`No secrets found under ${k}/`);return}c(["Key","Last Changed"],L.map((z)=>[(z.Name??"").replace(`${k}/`,""),z.LastChangedDate??z.LastAccessedDate??"-"]))}catch(m){r(`Failed to list secrets: ${m.message}`),process.exitCode=1}}),R.command("secrets:set <key> <value>","Set (create or update) a secret").option("--env <environment>","Environment (production, staging, development)").action(async(t,m,k)=>{i(`Setting secret ${t}`);let{region:D,prefix:L}=await Js(k?.env),z=new K(D),Y=`${L}/${t}`,X=new s("Storing secret...");X.start();try{try{await z.putSecretValue({SecretId:Y,SecretString:m})}catch{await z.createSecret({Name:Y,SecretString:m})}X.succeed(`Secret ${t} stored (${Y})`),a("Redeploy for the new value to take effect in running functions.")}catch(ne){X.fail(`Failed to store secret: ${ne.message}`),process.exitCode=1}}),R.command("secrets:get <key>","Reveal a secret value").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{try{let{region:k,prefix:D}=await Js(m?.env),z=await new K(k).getSecretValue({SecretId:`${D}/${t}`});e(z.SecretString??"")}catch(k){r(`Failed to read secret: ${k.message}`),process.exitCode=1}}),R.command("secrets:delete <key>","Delete a secret").option("--env <environment>","Environment (production, staging, development)").option("--force","Delete immediately without a recovery window").action(async(t,m)=>{try{let{region:k,prefix:D}=await Js(m?.env);await new K(k).deleteSecret({SecretId:`${D}/${t}`,ForceDeleteWithoutRecovery:m?.force}),n(`Secret ${t} deleted`)}catch(k){r(`Failed to delete secret: ${k.message}`),process.exitCode=1}})}function Cc(R){for(let[t,m]of[["firewall:rules","List provider firewall policy"],["firewall:block <ip>","Block an address"],["firewall:unblock <ip>","Unblock an address"],["firewall:countries","Manage geographic policy"]])R.command(t,m).action(async(...k)=>
|
|
334
|
+
Record deleted!`)}catch(D){r(`Failed to delete record: ${D instanceof Error?D.message:String(D)}`)}})}function Fp(){if(process.env.GODADDY_PAT)return"godaddy";if(process.env.GODADDY_API_KEY&&process.env.GODADDY_API_SECRET)return"godaddy";if(process.env.PORKBUN_API_KEY&&process.env.PORKBUN_SECRET_KEY)return"porkbun";return null}var Id=["postgres","mysql","mariadb","redis","mongodb","libsql"],Pd=["aws_rds","aws_aurora","aws_elasticache","server","container","external"],xd=["observe","connect","backup","restore","restart","resize","version","rotate","expose","delete","logs","slow_queries","users","databases"];async function jp(R,t){let m=await Ne(),k=y(process.cwd(),m),D=new sr(k.store),L=!R&&t?D.get(t)??D.list(k.project.id).find((le)=>le.name===t):void 0,z=L?.environmentId?k.store.listEnvironments(k.project.id).find((le)=>le.id===L.environmentId)?.slug:void 0,Y=R??z??Object.keys(m.environments??{})[0]??"production",X=k.environments.get(Y);if(!X)throw k.store.close(),Error(`Environment ${Y} was not found.`);let ne=new S(k.store,{workerId:`cli:${process.pid}`}),re=new ar(k.store,_(process.cwd())),ie=new Wr(D,ne,re),ae=k.store.getActorByExternalId("system","cli")??k.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),be=m.project?.region??"us-east-1",Ae={aws_rds:new Wn(new Yn(new fe(be))),aws_aurora:new Kn(new Zn(new fe(be))),aws_elasticache:new Jn(new ei(new oe(be))),server:new Xn(new lt),container:new Qn(new lt)};return{config:m,controlPlane:k,environmentRecord:X,env:Y,store:D,queue:ne,secrets:re,lifecycle:ie,actor:ae,resolveAdapter:(le)=>Ae[le.provider]}}async function yr(R,t,m){let k=await jp(R,m);try{return await t(k)}finally{k.controlPlane.store.close()}}async function Tn(R){try{await R()}catch(t){r(t instanceof Error?t.message:String(t))}}function dc(R){if(!Id.includes(R))throw Error(`--engine must be ${Id.join(", ")}.`);return R}function uc(R){if(!Pd.includes(R))throw Error(`--provider must be ${Pd.join(", ")}.`);return R}function vo(R,t){let m=R.store.list(R.controlPlane.project.id,R.environmentRecord.id),k=R.store.get(t)??m.find((D)=>D.name===t);if(!k||k.environmentId!==R.environmentRecord.id)throw Error(`Data service ${t} was not found in ${R.env}.`);return k}function Dd(R){return R.map((t)=>[t.id,t.name,t.engine,t.provider,t.status,t.plan,t.publicExposure?"public allowlist":"private",t.managementEnabled?"managed":"read-only"])}function Bp(R,t){return R!==null&&typeof R==="object"&&!Array.isArray(R)&&R.serviceId===t}function _p(R){let t={};if(R.plan)t.plan=R.plan;if(R.storage!=null)t.storageGb=Number(R.storage);if(R.engineVersion)t.engineVersion=R.engineVersion;if(R.retention)t.retention=R.retention;if(R.confirm)t.confirm=R.confirm;if(R.backupId)t.backupId=R.backupId;if(R.compatibilityReviewed)t.compatibilityReviewed=!0;if(R.minCapacity!=null)t.minCapacity=Number(R.minCapacity);if(R.maxCapacity!=null)t.maxCapacity=Number(R.maxCapacity);if(R.public!=null)t.publicExposure=R.public;if(R.cidrs)t.allowedCidrs=R.cidrs.split(",").map((m)=>m.trim()).filter(Boolean);return t}function Ed(R){let t=typeof R.observedState.endpoint==="string"?R.observedState.endpoint:R.placement,m=Number(R.observedState.port)||(R.engine==="postgres"?5432:R.engine==="redis"?6379:R.engine==="mongodb"?27017:R.engine==="libsql"?8080:3306),k=typeof R.observedState.database==="string"?R.observedState.database:void 0;return[{type:R.publicExposure?"external":"internal",host:t,port:m,database:k,tls:typeof R.observedState.tls==="boolean"?R.observedState.tls:R.provider.startsWith("aws_")},{type:"tunnel",host:t,port:m,database:k,tls:typeof R.observedState.tls==="boolean"?R.observedState.tls:R.provider.startsWith("aws_")}]}function Td(R){R.option("--env <environment>","Target environment").option("--engine <engine>","postgres, mysql, mariadb, redis, mongodb, or libsql",{default:"postgres"}).option("--provider <provider>","aws_rds, aws_aurora, aws_elasticache, server, container, or external",{default:"aws_rds"}).option("--placement <id>","Provider identifier or runtime placement").option("--plan <plan>","Provider plan or instance type",{default:"db.t4g.micro"}).option("--engine-version <version>","Engine version").option("--storage <gb>","Allocated storage in GB",{default:"20"}).option("--username <name>","Generated credential username",{default:"app"}).option("--database <name>","Initial logical database").option("--subnet-group <name>","Private subnet group").option("--security-groups <ids>","Comma-separated security group IDs").option("--high-availability","Enable provider high availability").option("--public","Request a public endpoint; requires narrow --cidrs").option("--cidrs <ranges>","Comma-separated narrow public CIDRs")}function vc(R){R.command("data:list","List first-class databases and caches").option("--env <environment>","Target environment").option("--json","Print structured JSON").action((k)=>Tn(()=>yr(k.env,async(D)=>{let L=D.store.list(D.controlPlane.project.id,D.environmentRecord.id);if(k.json)e(JSON.stringify(L,null,2));else c(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],Dd(L))}))),R.command("data:capabilities <engine> <provider>","Explain lifecycle support before taking action").action((k,D)=>Tn(async()=>{e(JSON.stringify(or(dc(k),uc(D)),null,2))}));let t=R.command("data:create <name>","Provision an encrypted, private-by-default data service");Td(t),t.action((k,D)=>Tn(()=>yr(D.env,async(L)=>{let z=dc(D.engine),Y=uc(D.provider),X=L.resolveAdapter({provider:Y});if(!X||!X.engines.includes(z))throw Error(`No ${Y}/${z} provisioning adapter is configured. Use data:adopt for read-only inventory.`);let ne={...D.database?{database:D.database}:{},...D.subnetGroup?{subnetGroup:D.subnetGroup}:{},...D.securityGroups?{securityGroupIds:D.securityGroups.split(",").map((ie)=>ie.trim()).filter(Boolean)}:{}},re=await L.lifecycle.create({organizationId:L.controlPlane.organization.id,projectId:L.controlPlane.project.id,environmentId:L.environmentRecord.id,name:k,engine:z,provider:Y,placement:D.placement??k,engineVersion:D.engineVersion,plan:D.plan??"db.t4g.micro",storageGb:Number(D.storage)||void 0,highAvailability:D.highAvailability===!0,publicExposure:D.public===!0,allowedCidrs:D.cidrs?D.cidrs.split(",").map((ie)=>ie.trim()).filter(Boolean):[],desiredState:ne,observedState:{},origin:"managed",managementEnabled:!0,ownerActorId:L.actor.id,username:D.username});if(n(`Queued ${re.service.name} as operation ${re.operationId}.`),re.credential)xr("One-time credential — copy it now:"),e(`Username: ${re.credential.username}`),e(`Password: ${re.credential.password}`),e(`Reference: ${re.credential.secretRef}`)})));let m=R.command("data:adopt <name>","Adopt an existing provider resource as read-only inventory");Td(m),m.action((k,D)=>Tn(()=>yr(D.env,async(L)=>{let z=L.store.create({organizationId:L.controlPlane.organization.id,projectId:L.controlPlane.project.id,environmentId:L.environmentRecord.id,name:k,engine:dc(D.engine),provider:uc(D.provider),placement:D.placement??k,engineVersion:D.engineVersion,plan:D.plan??"external",storageGb:Number(D.storage)||void 0,highAvailability:D.highAvailability===!0,publicExposure:D.public===!0,allowedCidrs:D.cidrs?D.cidrs.split(",").map((Y)=>Y.trim()).filter(Boolean):[],desiredState:{},observedState:{},credentialRef:void 0,status:"adopted",origin:"adopted",managementEnabled:!1,ownerActorId:L.actor.id});n(`Adopted ${z.name} (${z.id}) read-only.`)}))),R.command("data:show <service>","Show safe service, capability, dependency, and operation metadata").option("--env <environment>","Target environment").option("--reveal","Reveal the encrypted credential with exact confirmation").option("--confirm <name>","Exact service name required with --reveal").action((k,D)=>Tn(()=>yr(D.env,async(L)=>{let z=vo(L,k),Y=L.store.credential(z.id),X={...z,credentialRef:void 0,credential:Y?{configured:!0,username:Y.username,version:Y.version,rotatedAt:Y.rotatedAt}:void 0,dependencies:L.store.dependencies(z.id).map((ne)=>({resourceId:ne.resourceId,requiresRedeploy:ne.requiresRedeploy})),operations:L.queue.list({projectId:z.projectId,limit:250}).filter((ne)=>Bp(ne.operation.input,z.id))};if(e(JSON.stringify(X,null,2)),D.reveal){if(D.confirm!==z.name)throw Error(`Credential reveal requires --confirm ${z.name}.`);if(!Y)throw Error("No managed credential is configured.");xr("Credential value follows; it will not be logged or persisted by ts-cloud:"),e(`Username: ${Y.username}`),e(`Password: ${await L.secrets.resolve(Y.secretRef)}`)}},k))),R.command("data:connect <service>","Print secret-free connection and tunnel guidance").option("--env <environment>","Target environment").action((k,D)=>Tn(()=>yr(D.env,async(L)=>{let z=vo(L,k);c(["Type","Command","Credential reference"],qn(z,Ed(z)).map((Y)=>[Y.type,Y.command,Y.secretRef??"—"]))},k))),R.command("data:action <service> <action>","Preview or enqueue a durable lifecycle action").option("--env <environment>","Target environment").option("--execute","Enqueue after validation; default is preview only").option("--plan <plan>","New provider plan").option("--storage <gb>","New storage allocation").option("--engine-version <version>","Target engine version").option("--min-capacity <acu>","Aurora minimum capacity").option("--max-capacity <acu>","Aurora maximum capacity").option("--public","Enable public exposure").option("--cidrs <ranges>","Comma-separated narrow public CIDRs").option("--retention <choice>","final_backup or retain for deletion").option("--confirm <name>","Exact service name for destructive actions").option("--backup-id <id>","Required backup for a major version change").option("--compatibility-reviewed","Confirm major-version compatibility review").action((k,D,L)=>Tn(()=>yr(L.env,async(z)=>{if(!xd.includes(D))throw Error(`Action must be ${xd.join(", ")}.`);let Y=vo(z,k),X=D,ne=_p(L),re=z.lifecycle.plan(Y,X,ne);if(e(JSON.stringify({...re,productionExecutionCreated:!1},null,2)),!L.execute)return;let ie=z.lifecycle.enqueue(Y,X,ne,z.actor.id);n(`Queued ${X} as ${ie}.`)},k))),R.command("data:management <service>","Enable reviewed management of an adopted service").option("--env <environment>","Target environment").option("--confirm <name>","Exact service name").action((k,D)=>Tn(()=>yr(D.env,async(L)=>{let z=vo(L,k);if(z.origin!=="adopted"||D.confirm!==z.name)throw Error(`Enabling adopted management requires --confirm ${z.name}.`);let Y=L.store.update(z.id,z.version,{managementEnabled:!0});n(`${Y.name} management is enabled.`)},k))),R.command("data:dependency <service> <resource>","Register or remove an application credential dependency").option("--env <environment>","Target environment").option("--dynamic","Dependency consumes a dynamic reference and does not need redeploy").option("--remove","Remove the dependency").action((k,D,L)=>Tn(()=>yr(L.env,async(z)=>{let Y=vo(z,k),X=z.controlPlane.store.listResources(z.controlPlane.project.id,z.environmentRecord.id).find((ne)=>ne.id===D||ne.slug===D);if(!X)throw Error(`Resource ${D} was not found in ${z.env}.`);if(L.remove){e(z.store.removeDependency(Y.id,X.id)?"Dependency removed.":"Dependency was not registered.");return}z.store.addDependency({serviceId:Y.id,resourceId:X.id,secretRef:Y.credentialRef??`secret://data-services/${Y.projectId}/${Y.name}`,requiresRedeploy:!L.dynamic}),n(`Registered dependency ${X.slug} -> ${Y.name}.`)},k))),R.command("db:list","Alias for data:list").option("--env <environment>","Target environment").action((k)=>Tn(()=>yr(k.env,async(D)=>c(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],Dd(D.store.list(D.controlPlane.project.id,D.environmentRecord.id)))))),R.command("db:connect <service>","Alias for data:connect").option("--env <environment>","Target environment").action((k,D)=>Tn(()=>yr(D.env,async(L)=>{let z=vo(L,k);c(["Type","Command","Credential reference"],qn(z,Ed(z)).map((Y)=>[Y.type,Y.command,Y.secretRef??"—"]))},k)));for(let k of["backup","snapshot"])R.command(`db:${k} <service>`,`Alias for data:action ${k==="backup"?"backup":"backup"}`).option("--env <environment>","Target environment").option("--execute","Enqueue the backup; default previews").action((D,L)=>Tn(()=>yr(L.env,async(z)=>{let Y=vo(z,D),X=z.lifecycle.plan(Y,"backup");if(e(JSON.stringify({...X,productionExecutionCreated:!1},null,2)),L.execute)n(`Queued backup as ${z.lifecycle.enqueue(Y,"backup",{},z.actor.id)}.`)},D)))}function Nd(R,t=15){if(!R)return t*60000;let m=/^(\d+)\s*([smhd])?$/.exec(R.trim());if(!m)return t*60000;let k=Number(m[1]),D=m[2]??"m";return k*({s:1000,m:60000,h:3600000,d:86400000}[D]??60000)}function Mp(R,t){for(let m of R){let k=m.timestamp?new Date(m.timestamp).toISOString():"",D=t?`[${t}] `:"";e(`${k} ${D}${(m.message??"").trimEnd()}`)}}function bc(R){R.command("logs","Stream serverless application logs (http + queue + cli)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli | all",{default:"all"}).option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <duration>","Look back (e.g. 1h, 30m, 15)",{default:"15m"}).action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=new se(D),Y=(t?.function??"all").toLowerCase();if(Y!=="all"&&!["http","queue","cli"].includes(Y)){r("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let ne=(Y==="all"?["http","queue","cli"]:[Y]).map((ae)=>({mode:ae,group:`/aws/lambda/${L[ae]}`}));i(`Logs — ${m.project.slug} (${k})`);let re=Date.now()-Nd(t?.since),ie=async()=>{let ae=re;for(let{mode:be,group:Ae}of ne)try{let{events:le=[]}=await z.filterLogEvents({logGroupName:Ae,startTime:re,filterPattern:t?.filter,limit:200});Mp(le,ne.length>1?be:void 0);for(let ke of le)if((ke.timestamp??0)>ae)ae=ke.timestamp??ae}catch(le){if(!/ResourceNotFound/i.test(String(le?.message)))throw le}re=ae+1};if(await ie(),t?.tail){e(`
|
|
335
|
+
(streaming — Ctrl+C to stop)`);for(;;)await new Promise((ae)=>setTimeout(ae,3000)),await ie()}}catch(m){r(`Failed to read logs: ${m.message}`),process.exitCode=1}}),R.command("metrics","Show serverless function metrics (invocations, errors, duration, throttles)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--since <duration>","Window (e.g. 1h, 24h)",{default:"1h"}).action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=new q(D),Y=Nd(t?.since,60),X=new Date,ne=new Date(X.getTime()-Y),re=Math.max(60,Math.round(Y/1000));i(`Metrics — ${m.project.slug} (${k}), last ${t?.since??"1h"}`);let ie=[];for(let ae of["http","queue","cli"]){let be=[{Name:"FunctionName",Value:L[ae]}],Ae=async(Ve,Le)=>{let Fe=await z.getMetricStatistics({Namespace:"AWS/Lambda",MetricName:Ve,Dimensions:be,StartTime:ne,EndTime:X,Period:re,Statistics:[Le]});if(!Fe.length)return 0;if(Le==="Sum")return Fe.reduce((He,wt)=>He+(wt.Sum??0),0);if(Le==="Maximum")return Math.max(...Fe.map((He)=>He.Maximum??0));return Fe.reduce((He,wt)=>He+(wt.Average??0),0)/Fe.length},[le,ke,De,Se,Re]=await Promise.all([Ae("Invocations","Sum"),Ae("Errors","Sum"),Ae("Throttles","Sum"),Ae("Duration","Average"),Ae("Duration","Maximum")]),Pe=le?`${(ke/le*100).toFixed(2)}%`:"—";ie.push([ae,String(le),String(ke),Pe,String(De),`${Se.toFixed(0)}ms`,`${Re.toFixed(0)}ms`])}c(["Function","Invocations","Errors","Error rate","Throttles","Avg","Max"],ie)}catch(m){r(`Failed to read metrics: ${m.message}`),process.exitCode=1}}),R.command("metrics:dashboard","Print the CloudWatch console URL for this app").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=encodeURIComponent(JSON.stringify({metrics:["http","queue","cli"].flatMap((Y)=>[["AWS/Lambda","Invocations","FunctionName",L[Y]],["AWS/Lambda","Errors","FunctionName",L[Y]]]),view:"timeSeries",region:D}));i("CloudWatch dashboard"),e(`https://console.aws.amazon.com/cloudwatch/home?region=${D}#metricsV2:graph=${z}`)}catch(m){r(m.message),process.exitCode=1}}),R.command("alarms","List CloudWatch alarms").option("--prefix <prefix>","Filter by alarm name prefix").option("--region <region>","AWS region").action(async(t)=>{i("CloudWatch Alarms");let m=t?.region;if(!m)try{m=(await Ne()).project.region}catch{}try{let D=await new q(m||process.env.AWS_REGION||"us-east-1").describeAlarms({AlarmNamePrefix:t?.prefix,MaxRecords:100});if(!D.length){e("No alarms found");return}c(["Name","State","Metric","Condition"],D.map((L)=>[L.AlarmName??"-",L.StateValue??"-",L.MetricName??"-",`${L.ComparisonOperator??""} ${L.Threshold??""}`.trim()]))}catch(k){r(`Failed to list alarms: ${k.message}`),process.exitCode=1}}),R.command("alarms:create","Create a CloudWatch alarm on a Lambda metric").option("--name <name>","Alarm name").option("--env <environment>","Environment",{default:"production"}).option("--function <which>","Function: http | queue | cli",{default:"http"}).option("--metric <metric>","Lambda metric (Errors, Throttles, Duration, Invocations)",{default:"Errors"}).option("--threshold <value>","Threshold value").option("--comparison <op>","GreaterThanThreshold | GreaterThanOrEqualToThreshold | LessThanThreshold",{default:"GreaterThanThreshold"}).option("--statistic <stat>","Sum | Average | Maximum | Minimum",{default:"Sum"}).action(async(t)=>{if(i("Creating CloudWatch Alarm"),!t?.name||t?.threshold==null){r("Missing required options: --name and --threshold"),process.exitCode=1;return}try{let m=await Ne(),k=t.env||"production",{region:D,functions:L}=Tt(m,k),z=t.function??"http";await new q(D).putMetricAlarm({AlarmName:t.name,Namespace:"AWS/Lambda",MetricName:t.metric??"Errors",ComparisonOperator:t.comparison??"GreaterThanThreshold",Threshold:Number(t.threshold),EvaluationPeriods:1,Period:300,Statistic:t.statistic??"Sum",Dimensions:[{Name:"FunctionName",Value:L[z]}],AlarmDescription:`ts-cloud: ${L[z]} ${t.metric} ${t.comparison} ${t.threshold}`}),n(`Alarm '${t.name}' created on ${L[z]} ${t.metric}`)}catch(m){r(`Failed to create alarm: ${m.message}`),process.exitCode=1}})}async function Js(R){let t=await Ne(),m=R||"production";return{region:t.environments?.[m]?.region||t.project.region||"us-east-1",prefix:`${t.project.slug}/${m}`}}function Sc(R){R.command("secrets:list","List secrets for an environment").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{i("Secrets");try{let{region:m,prefix:k}=await Js(t?.env),D=new K(m),{SecretList:L=[]}=await D.listSecrets({Filters:[{Key:"name",Values:[`${k}/`]}],MaxResults:100});if(!L.length){e(`No secrets found under ${k}/`);return}c(["Key","Last Changed"],L.map((z)=>[(z.Name??"").replace(`${k}/`,""),z.LastChangedDate??z.LastAccessedDate??"-"]))}catch(m){r(`Failed to list secrets: ${m.message}`),process.exitCode=1}}),R.command("secrets:set <key> <value>","Set (create or update) a secret").option("--env <environment>","Environment (production, staging, development)").action(async(t,m,k)=>{i(`Setting secret ${t}`);let{region:D,prefix:L}=await Js(k?.env),z=new K(D),Y=`${L}/${t}`,X=new s("Storing secret...");X.start();try{try{await z.putSecretValue({SecretId:Y,SecretString:m})}catch{await z.createSecret({Name:Y,SecretString:m})}X.succeed(`Secret ${t} stored (${Y})`),a("Redeploy for the new value to take effect in running functions.")}catch(ne){X.fail(`Failed to store secret: ${ne.message}`),process.exitCode=1}}),R.command("secrets:get <key>","Reveal a secret value").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{try{let{region:k,prefix:D}=await Js(m?.env),z=await new K(k).getSecretValue({SecretId:`${D}/${t}`});e(z.SecretString??"")}catch(k){r(`Failed to read secret: ${k.message}`),process.exitCode=1}}),R.command("secrets:delete <key>","Delete a secret").option("--env <environment>","Environment (production, staging, development)").option("--force","Delete immediately without a recovery window").action(async(t,m)=>{try{let{region:k,prefix:D}=await Js(m?.env);await new K(k).deleteSecret({SecretId:`${D}/${t}`,ForceDeleteWithoutRecovery:m?.force}),n(`Secret ${t} deleted`)}catch(k){r(`Failed to delete secret: ${k.message}`),process.exitCode=1}})}function Cc(R){for(let[t,m]of[["firewall:rules","List provider firewall policy"],["firewall:block <ip>","Block an address"],["firewall:unblock <ip>","Unblock an address"],["firewall:countries","Manage geographic policy"]])R.command(t,m).action(async(...k)=>En(t.split(" ")[0],{target:String(k[0]??""),message:"No reviewed WAF/IP-set adapter is configured for this legacy command.",nextAction:"Use server firewall configuration or install a provider adapter; no rule was changed."}))}function $c(R){R.command("ssl:list","List all SSL certificates").option("--region <region>","AWS region (default: us-east-1)").action(async(t)=>{i("SSL Certificates");let m=t?.region||"us-east-1",k=new s("Fetching certificates from ACM...");k.start();try{let D=new W(m),L=await D.listCertificates();if(L.CertificateSummaryList.length===0){k.succeed("No certificates found"),e(`
|
|
336
336
|
No SSL certificates found in region ${m}`),e("Use 'cloud domain:ssl <domain>' to request a new certificate");return}let z=await Promise.all(L.CertificateSummaryList.map(async(ne)=>await D.describeCertificate({CertificateArn:ne.CertificateArn})));k.succeed(`Found ${z.length} certificate(s)`);let Y=(ne)=>{if(!ne)return"N/A";let re=typeof ne==="string"?Number.parseFloat(ne):ne,ie=re<1000000000000?re*1000:re;return new Date(ie).toISOString().split("T")[0]},X=z.map((ne)=>{let re=Y(ne.NotAfter),ie=ne.Type==="AMAZON_ISSUED"?"Amazon Issued":ne.Type||"Unknown",ae=ne.Status==="ISSUED"?"Available":ne.Status||"Unknown";return[ne.DomainName,ne.Status||"Unknown",re,ie,ae]});c(["Domain","Status","Expiry","Type","State"],X),e(`
|
|
337
337
|
ACM certificates are automatically renewed by AWS`),e(`Region: ${m}`)}catch(D){k.fail("Failed to fetch certificates"),r(D.message)}}),R.command("ssl:renew <domain>","Renew SSL certificate").option("--region <region>","AWS region (default: us-east-1)").action(async(t,m)=>{i(`Checking SSL Certificate for ${t}`);let k=m?.region||"us-east-1";e(`Domain: ${t}`),e(`Region: ${k}`);let D=new s("Checking certificate status...");D.start();try{let z=await new W(k).findCertificateByDomain(t);if(!z){D.fail("Certificate not found"),r(`No certificate found for domain: ${t}`),e("Use 'cloud domain:ssl <domain>' to request a new certificate");return}D.succeed("Certificate found"),e(`
|
|
338
338
|
Certificate is managed by AWS Certificate Manager`),e("ACM certificates are automatically renewed 60 days before expiry"),a(`
|
|
339
339
|
No manual renewal needed for ACM certificates`);let Y=(re)=>{if(!re)return"N/A";let ie=typeof re==="string"?Number.parseFloat(re):re,ae=ie<1000000000000?ie*1000:ie;return new Date(ae).toISOString().split("T")[0]},X=Y(z.NotAfter),ne=Y(z.IssuedAt);if(e(`
|
|
340
|
-
Certificate details:`),e(` - Domain: ${z.DomainName}`),e(` - Status: ${z.Status}`),e(` - Issued: ${ne}`),e(` - Expiry: ${X}`),e(` - Type: ${z.Type||"Unknown"}`),e(` - ARN: ${z.CertificateArn}`),e(` - Auto-renewal: ${z.Type==="AMAZON_ISSUED"?"Enabled":"N/A (imported)"}`),z.SubjectAlternativeNames&&z.SubjectAlternativeNames.length>1)e(` - SANs: ${z.SubjectAlternativeNames.join(", ")}`)}catch(L){D.fail("Failed to check certificate"),r(L.message)}})}function qi(R){return R.toISOString().slice(0,10)}function Od(R){return new Date(Date.UTC(R.getUTCFullYear(),R.getUTCMonth(),R.getUTCDate()))}function Ld(R=new Date){let t=Od(R),m=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),1)),k=new Date(t.getTime()+86400000),D=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()+1,1)),L=m,z=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()-1,1));return{start:qi(m),end:qi(k),label:m.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"}),daysElapsed:t.getUTCDate(),daysInMonth:Math.round((D.getTime()-m.getTime())/86400000),previous:{start:qi(z),end:qi(L),label:z.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"})}}}function Fo(R,t=new Date){if(!Number.isInteger(R)||R<1||R>366)throw Error("days must be an integer from 1 to 366");let m=new Date(Od(t).getTime()+86400000),k=new Date(m.getTime()-R*86400000),D=new Date(k.getTime()-R*86400000);return{current:{start:qi(k),end:qi(m),label:`last ${R} days`},previous:{start:qi(D),end:qi(k),label:`previous ${R} days`}}}function kc(R,t,m){return t>0?R/t*m:0}function bs(R,t){if(t===0)return R===0?0:null;return(R-t)/t*100}function Ac(R,t){let m=new Map(t.map((k)=>[k.service,k.amount]));return R.map((k)=>{let D=m.get(k.service)??0;return{...k,previousAmount:D,changePercent:bs(k.amount,D)}})}var zp=/(?:data.?transfer|natgateway.*bytes|aws-(?:in|out)-bytes|regional-bytes|inter.?az|internet)/i;function Ys(R){return R.filter((t)=>zp.test(t.key)).sort((t,m)=>m.amount-t.amount)}function jo(R){if(R==null)return[];return Array.isArray(R)?R:[R]}function bo(R,t={}){let[,,m="unknown",k="",D="",L=R]=R.split(":"),z=L.includes("/")?"/":":",[Y=m,...X]=L.split(z),ne=X.join(z)||L;return{arn:R,service:m,type:Y,id:ne,name:t.Name||t.name||ne,region:k||void 0,accountId:D||void 0,tags:t,metadata:{}}}function Up(R,t){if(!t)return!0;let m=t.toLowerCase().replace(/^aws[:/]/,"");return[R.service,R.type,`${R.service}:${R.type}`].some((k)=>k.toLowerCase()===m||k.toLowerCase().includes(m))}function Wp(R,t){let m=new Map;for(let k of R){let D=`${k.service}:${k.type}:${k.id}`,L=m.get(D);m.set(D,L?{...L,...k,arn:L.accountId?L.arn:k.arn,accountId:L.accountId??k.accountId,tags:{...L.tags,...k.tags},metadata:{...L.metadata,...k.metadata}}:k)}return[...m.values()].filter((k)=>Up(k,t)).sort((k,D)=>k.service.localeCompare(D.service)||k.name.localeCompare(D.name)||k.arn.localeCompare(D.arn))}class ws{client;profile;region;constructor(R,t=process.env.AWS_REGION||"us-east-1"){this.profile=R,this.region=t,this.client=new p(Rt(R))}async taggedResources(){let R=[],t;do{let m=await this.client.request({service:"tagging",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-amz-json-1.1","x-amz-target":"ResourceGroupsTaggingAPI_20170126.GetResources"},body:JSON.stringify(t?{PaginationToken:t}:{})});for(let k of m?.ResourceTagMappingList??[]){let D=Object.fromEntries((k.Tags??[]).map((L)=>[String(L.Key),String(L.Value)]));R.push(bo(String(k.ResourceARN),D))}t=m?.PaginationToken||void 0}while(t);return R}async ec2Resources(){let R=async(L)=>this.client.request({service:"ec2",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({Action:L,Version:"2016-11-15"}).toString()}),[t,m,k]=await Promise.all([R("DescribeInstances"),R("DescribeVolumes"),R("DescribeAddresses")]),D=[];for(let L of jo(t?.reservationSet?.item))for(let z of jo(L?.instancesSet?.item)){let Y=String(z.instanceId);D.push({...bo(`arn:aws:ec2:${this.region}::instance/${Y}`),state:z.instanceState?.name,metadata:{instanceType:z.instanceType}})}for(let L of jo(m?.volumeSet?.item)){let z=String(L.volumeId);D.push({...bo(`arn:aws:ec2:${this.region}::volume/${z}`),state:L.status,metadata:{sizeGiB:Number(L.size??0),attached:jo(L.attachmentSet?.item).length>0}})}for(let L of jo(k?.addressesSet?.item)){let z=String(L.allocationId??L.publicIp);D.push({...bo(`arn:aws:ec2:${this.region}::elastic-ip/${z}`),state:L.associationId?"associated":"available",metadata:{associated:Boolean(L.associationId),publicIp:L.publicIp}})}return D}async rdsResources(){let R=new URLSearchParams({Action:"DescribeDBInstances",Version:"2014-10-31"}).toString(),t=await this.client.request({service:"rds",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-www-form-urlencoded"},body:R}),m=t?.DescribeDBInstancesResult??t;return jo(m?.DBInstances?.DBInstance).map((k)=>({...bo(k.DBInstanceArn||`arn:aws:rds:${this.region}::db:${String(k.DBInstanceIdentifier)}`),state:k.DBInstanceStatus,metadata:{engine:k.Engine,instanceClass:k.DBInstanceClass}}))}async lambdaResources(){let R=[],t;do{let m=await this.client.request({service:"lambda",region:this.region,method:"GET",path:"/2015-03-31/functions",queryParams:t?{Marker:t}:void 0});for(let k of m?.Functions??[])R.push({...bo(k.FunctionArn),state:k.State,metadata:{runtime:k.Runtime,memoryMiB:Number(k.MemorySize??0)}});t=m?.NextMarker||void 0}while(t);return R}async s3Resources(){return(await new g("us-east-1",this.profile).listBuckets()).Buckets.map((t)=>({...bo(`arn:aws:s3:::${t.Name}`),metadata:{creationDate:t.CreationDate}}))}async discover(R){let t=[["tagging:GetResources",()=>this.taggedResources()],["ec2:Describe*",()=>this.ec2Resources()],["rds:DescribeDBInstances",()=>this.rdsResources()],["lambda:ListFunctions",()=>this.lambdaResources()],["s3:ListAllMyBuckets",()=>this.s3Resources()]],m=await Promise.allSettled(t.map(([,L])=>L())),k=[],D=[];return m.forEach((L,z)=>{if(L.status==="fulfilled")k.push(...L.value);else D.push(`${t[z][0]} unavailable: ${L.reason instanceof Error?L.reason.message:String(L.reason)}`)}),{resources:Wp(k,R?.type),warnings:D}}}function Ss(R,t){if(R==null)return{currentCost:null,projectedCost:null,monthlySavings:null};let m=R*t;return{currentCost:R,projectedCost:R-m,monthlySavings:m}}function Fd(R,t,m,k=!1){let D=Rc(R,t,m);if(D)return[{category:"unused",resource:R,recommendation:D.recommendation,sourceSignal:D.signal,...Ss(m,1)}];if((R.service==="ec2"&&R.type==="instance"||R.service==="rds"&&R.type==="db")&&t.cpuAverage!=null&&t.cpuAverage<20)return[{category:"right-size",resource:R,recommendation:"Validate memory, network, and peak demand, then test one smaller instance class.",sourceSignal:`${R.service.toUpperCase()} 30-day average CPU ${t.cpuAverage.toFixed(1)}% (<20%)`,...Ss(m,0.4)}];if(k&&(R.service==="ec2"&&R.type==="instance"&&R.state==="running"||R.service==="rds"&&R.type==="db"&&R.state==="available")&&t.cpuAverage!=null&&t.cpuAverage>=20)return[{category:"commitment",resource:R,recommendation:"Compare a one-year no-upfront commitment against this steady workload before purchasing.",sourceSignal:`${R.service.toUpperCase()} remained active with 30-day average CPU ${t.cpuAverage.toFixed(1)}%`,...Ss(m,0.3)}];if(R.service==="s3"&&t.storageBytes!=null&&t.storageBytes>=5368709120&&t.requests!=null&&t.requests<9000){let L=t.storageBytes/1073741824;return[{category:"storage",resource:R,recommendation:"Review object age and retrieval needs, then add Intelligent-Tiering or Glacier lifecycle rules.",sourceSignal:`${L.toFixed(1)} GiB stored with ${t.requests.toFixed(0)} requests over 90 days`,...Ss(m,0.25)}]}return[]}function Vp(R,t){if(R<=0||t.length===0)return null;return{category:"cdn",resource:{arn:"arn:aws:cloudfront::account:distribution/account-wide",service:"cloudfront",type:"distribution",id:"account-wide",name:"account-wide transfer",tags:{},metadata:{}},recommendation:"Enable compression and review cache policies, TTLs, and origin cache headers for high-transfer paths.",sourceSignal:`30-day Cost Explorer transfer line items: ${t.slice(0,3).join(", ")}`,...Ss(R,0.2)}}function qp(R){let t=R.map((m)=>m.Average).filter((m)=>m!=null);return t.length>0?t.reduce((m,k)=>m+k,0)/t.length:void 0}function Hp(R){let t=R.map((m)=>m.Sum).filter((m)=>m!=null);return t.length>0?t.reduce((m,k)=>m+k,0):void 0}function Rc(R,t,m=null){if(R.service==="ec2"&&R.type==="volume"&&R.metadata.attached===!1)return{resource:R,signal:"EBS volume is detached",recommendation:"Snapshot if needed, then delete the detached volume.",monthlySavings:m};if(R.service==="ec2"&&R.type==="elastic-ip"&&R.metadata.associated===!1)return{resource:R,signal:"Elastic IP is not associated",recommendation:"Release the unassociated Elastic IP.",monthlySavings:m};if(R.service==="ec2"&&R.type==="instance"&&t.cpuAverage!=null&&t.cpuAverage<5)return{resource:R,signal:`30-day average CPU ${t.cpuAverage.toFixed(1)}% (<5%)`,recommendation:"Stop/delete if unused, or right-size after validating memory and network demand.",monthlySavings:m};if(R.service==="rds"&&R.type==="db"&&t.connectionsAverage!=null&&t.iopsAverage!=null&&t.connectionsAverage===0&&t.iopsAverage<1)return{resource:R,signal:`14-day connections 0 and average IOPS ${t.iopsAverage.toFixed(2)} (<1)`,recommendation:"Take a final snapshot, validate ownership, then stop or delete the database.",monthlySavings:m};if(R.service==="lambda"&&R.type==="function"&&t.invocations===0)return{resource:R,signal:"30-day invocations 0",recommendation:"Confirm no event source depends on it, then remove the function.",monthlySavings:m};if(R.service==="s3"&&t.objectCount===0&&t.requests===0)return{resource:R,signal:"Bucket is empty with zero requests over 90 days",recommendation:"Confirm retention policy, then delete the empty bucket.",monthlySavings:m};if(R.service==="elasticache"&&t.connectionsAverage!=null&&t.cacheHitRate!=null&&t.connectionsAverage===0&&t.cacheHitRate<20)return{resource:R,signal:`14-day connections 0 and cache hit rate ${t.cacheHitRate.toFixed(1)}%`,recommendation:"Validate clients and snapshots, then delete the idle cache cluster.",monthlySavings:m};return null}class Zs{inventory;costs;profile;region;constructor(R,t=process.env.AWS_REGION||"us-east-1"){this.profile=R,this.region=t,this.inventory=new ws(R,t),this.costs=new We(R)}async metric(R,t,m,k,D,L,z,Y=[]){let X=new Date,ne=new Date(X.getTime()-L*24*60*60*1000),re=await R.getMetricStatistics({Namespace:m,MetricName:k,Dimensions:[{Name:D,Value:t.id},...Y],StartTime:ne,EndTime:X,Period:86400,Statistics:[z]});return z==="Sum"?Hp(re):qp(re)}async signals(R){let t=new q(R.region??this.region,this.profile);if(R.service==="ec2"&&R.type==="instance")return{cpuAverage:await this.metric(t,R,"AWS/EC2","CPUUtilization","InstanceId",30,"Average")};if(R.service==="rds"&&R.type==="db"){let[m,k,D,L]=await Promise.all([this.metric(t,R,"AWS/RDS","CPUUtilization","DBInstanceIdentifier",30,"Average"),this.metric(t,R,"AWS/RDS","DatabaseConnections","DBInstanceIdentifier",14,"Average"),this.metric(t,R,"AWS/RDS","ReadIOPS","DBInstanceIdentifier",14,"Average"),this.metric(t,R,"AWS/RDS","WriteIOPS","DBInstanceIdentifier",14,"Average")]);return{cpuAverage:m,connectionsAverage:k,iopsAverage:D!=null&&L!=null?D+L:void 0}}if(R.service==="lambda"&&R.type==="function")return{invocations:await this.metric(t,R,"AWS/Lambda","Invocations","FunctionName",30,"Sum")};if(R.service==="s3"){let[m,k,D]=await Promise.all([this.metric(t,R,"AWS/S3","NumberOfObjects","BucketName",90,"Average",[{Name:"StorageType",Value:"AllStorageTypes"}]),this.metric(t,R,"AWS/S3","AllRequests","BucketName",90,"Sum"),this.metric(t,R,"AWS/S3","BucketSizeBytes","BucketName",90,"Average",[{Name:"StorageType",Value:"StandardStorage"}])]);return{objectCount:m,requests:k,storageBytes:D}}if(R.service==="elasticache"&&R.type.includes("cluster")){let[m,k]=await Promise.all([this.metric(t,R,"AWS/ElastiCache","CurrConnections","CacheClusterId",14,"Average"),this.metric(t,R,"AWS/ElastiCache","CacheHitRate","CacheClusterId",14,"Average")]);return{connectionsAverage:m,cacheHitRate:k}}return{}}async resourceCosts(){try{let R=Fo(30).current,t=await this.costs.getCostByDimension({start:R.start,end:R.end,dimension:"RESOURCE_ID",granularity:"DAILY"});return new Map(t.map((m)=>[m.key,m.amount]))}catch{return new Map}}resourceCost(R,t){return R.get(t.id)??R.get(t.arn)??null}async cloudFrontTransfer(){try{let R=Fo(30).current,t=await this.costs.getCostByDimension({start:R.start,end:R.end,dimension:"USAGE_TYPE",granularity:"DAILY",filter:{Dimensions:{Key:"SERVICE",Values:["Amazon CloudFront"]}}}),m=Ys(t);return{cost:m.reduce((k,D)=>k+D.amount,0),usageTypes:m.map((k)=>k.key)}}catch{return{cost:0,usageTypes:[]}}}async unused(R){let[t,m]=await Promise.all([this.inventory.discover(R),this.resourceCosts()]),k=(await Promise.all(t.resources.map(async(D)=>{let L=this.resourceCost(m,D),z=Rc(D,{},L);if(z)return z;try{return Rc(D,await this.signals(D),L)}catch{return null}}))).filter((D)=>D!==null);return{findings:k,inventory:t,savingsAvailable:k.some((D)=>D.monthlySavings!=null)}}async optimize(R){let t=!R?.type||["cloudfront","cdn"].includes(R.type.toLowerCase()),[m,k,D]=await Promise.all([this.inventory.discover({type:R?.type}),this.resourceCosts(),t?this.cloudFrontTransfer():Promise.resolve({cost:0,usageTypes:[]})]),L=[],z=[...m.warnings];for(let X of m.resources){let ne=this.resourceCost(k,X),re=Fd(X,{},ne,R?.includeCommitments);if(re.length>0){L.push(...re);continue}try{L.push(...Fd(X,await this.signals(X),ne,R?.includeCommitments))}catch(ie){z.push(`CloudWatch metrics unavailable for ${X.service}:${X.type}/${X.name}: ${ie instanceof Error?ie.message:String(ie)}`)}}let Y=Vp(D.cost,D.usageTypes);if(Y)L.push(Y);return L.sort((X,ne)=>(ne.monthlySavings??-1)-(X.monthlySavings??-1)||X.category.localeCompare(ne.category)||X.resource.name.localeCompare(ne.resource.name)),{recommendations:L,inventory:m,savingsAvailable:L.some((X)=>X.monthlySavings!=null),warnings:z}}}var Ic="Amazon Simple Storage Service";function Gp(){let R=new Date,t=new Date(Date.UTC(R.getUTCFullYear(),R.getUTCMonth(),1)),m=new Date(t);m.setUTCMonth(m.getUTCMonth()-1);let k=(L)=>L.toISOString().slice(0,10),D=m.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"});return{start:k(m),end:k(t),label:D}}function mn(R){return`$${R.toFixed(2)}`}function ea(R){if(R===null)return"new";if(Math.abs(R)<0.05)return"→ 0.0%";return`${R>0?"↑":"↓"} ${Math.abs(R).toFixed(1)}%`}function Pc(R){r(`Cost Explorer request failed: ${R instanceof Error?R.message:String(R)}`),e(`
|
|
340
|
+
Certificate details:`),e(` - Domain: ${z.DomainName}`),e(` - Status: ${z.Status}`),e(` - Issued: ${ne}`),e(` - Expiry: ${X}`),e(` - Type: ${z.Type||"Unknown"}`),e(` - ARN: ${z.CertificateArn}`),e(` - Auto-renewal: ${z.Type==="AMAZON_ISSUED"?"Enabled":"N/A (imported)"}`),z.SubjectAlternativeNames&&z.SubjectAlternativeNames.length>1)e(` - SANs: ${z.SubjectAlternativeNames.join(", ")}`)}catch(L){D.fail("Failed to check certificate"),r(L.message)}})}function qi(R){return R.toISOString().slice(0,10)}function Od(R){return new Date(Date.UTC(R.getUTCFullYear(),R.getUTCMonth(),R.getUTCDate()))}function Ld(R=new Date){let t=Od(R),m=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),1)),k=new Date(t.getTime()+86400000),D=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()+1,1)),L=m,z=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()-1,1));return{start:qi(m),end:qi(k),label:m.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"}),daysElapsed:t.getUTCDate(),daysInMonth:Math.round((D.getTime()-m.getTime())/86400000),previous:{start:qi(z),end:qi(L),label:z.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"})}}}function Fo(R,t=new Date){if(!Number.isInteger(R)||R<1||R>366)throw Error("days must be an integer from 1 to 366");let m=new Date(Od(t).getTime()+86400000),k=new Date(m.getTime()-R*86400000),D=new Date(k.getTime()-R*86400000);return{current:{start:qi(k),end:qi(m),label:`last ${R} days`},previous:{start:qi(D),end:qi(k),label:`previous ${R} days`}}}function kc(R,t,m){return t>0?R/t*m:0}function bs(R,t){if(t===0)return R===0?0:null;return(R-t)/t*100}function Ac(R,t){let m=new Map(t.map((k)=>[k.service,k.amount]));return R.map((k)=>{let D=m.get(k.service)??0;return{...k,previousAmount:D,changePercent:bs(k.amount,D)}})}var zp=/(?:data.?transfer|natgateway.*bytes|aws-(?:in|out)-bytes|regional-bytes|inter.?az|internet)/i;function Ys(R){return R.filter((t)=>zp.test(t.key)).sort((t,m)=>m.amount-t.amount)}function jo(R){if(R==null)return[];return Array.isArray(R)?R:[R]}function bo(R,t={}){let[,,m="unknown",k="",D="",L=R]=R.split(":"),z=L.includes("/")?"/":":",[Y=m,...X]=L.split(z),ne=X.join(z)||L;return{arn:R,service:m,type:Y,id:ne,name:t.Name||t.name||ne,region:k||void 0,accountId:D||void 0,tags:t,metadata:{}}}function Up(R,t){if(!t)return!0;let m=t.toLowerCase().replace(/^aws[:/]/,"");return[R.service,R.type,`${R.service}:${R.type}`].some((k)=>k.toLowerCase()===m||k.toLowerCase().includes(m))}function Wp(R,t){let m=new Map;for(let k of R){let D=`${k.service}:${k.type}:${k.id}`,L=m.get(D);m.set(D,L?{...L,...k,arn:L.accountId?L.arn:k.arn,accountId:L.accountId??k.accountId,tags:{...L.tags,...k.tags},metadata:{...L.metadata,...k.metadata}}:k)}return[...m.values()].filter((k)=>Up(k,t)).sort((k,D)=>k.service.localeCompare(D.service)||k.name.localeCompare(D.name)||k.arn.localeCompare(D.arn))}class ws{client;profile;region;constructor(R,t=process.env.AWS_REGION||"us-east-1"){this.profile=R,this.region=t,this.client=new p(Rt(R))}async taggedResources(){let R=[],t;do{let m=await this.client.request({service:"tagging",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-amz-json-1.1","x-amz-target":"ResourceGroupsTaggingAPI_20170126.GetResources"},body:JSON.stringify(t?{PaginationToken:t}:{})});for(let k of m?.ResourceTagMappingList??[]){let D=Object.fromEntries((k.Tags??[]).map((L)=>[String(L.Key),String(L.Value)]));R.push(bo(String(k.ResourceARN),D))}t=m?.PaginationToken||void 0}while(t);return R}async ec2Resources(){let R=async(L)=>this.client.request({service:"ec2",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({Action:L,Version:"2016-11-15"}).toString()}),[t,m,k]=await Promise.all([R("DescribeInstances"),R("DescribeVolumes"),R("DescribeAddresses")]),D=[];for(let L of jo(t?.reservationSet?.item))for(let z of jo(L?.instancesSet?.item)){let Y=String(z.instanceId);D.push({...bo(`arn:aws:ec2:${this.region}::instance/${Y}`),state:z.instanceState?.name,metadata:{instanceType:z.instanceType}})}for(let L of jo(m?.volumeSet?.item)){let z=String(L.volumeId);D.push({...bo(`arn:aws:ec2:${this.region}::volume/${z}`),state:L.status,metadata:{sizeGiB:Number(L.size??0),attached:jo(L.attachmentSet?.item).length>0}})}for(let L of jo(k?.addressesSet?.item)){let z=String(L.allocationId??L.publicIp);D.push({...bo(`arn:aws:ec2:${this.region}::elastic-ip/${z}`),state:L.associationId?"associated":"available",metadata:{associated:Boolean(L.associationId),publicIp:L.publicIp}})}return D}async rdsResources(){let R=new URLSearchParams({Action:"DescribeDBInstances",Version:"2014-10-31"}).toString(),t=await this.client.request({service:"rds",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-www-form-urlencoded"},body:R}),m=t?.DescribeDBInstancesResult??t;return jo(m?.DBInstances?.DBInstance).map((k)=>({...bo(k.DBInstanceArn||`arn:aws:rds:${this.region}::db:${String(k.DBInstanceIdentifier)}`),state:k.DBInstanceStatus,metadata:{engine:k.Engine,instanceClass:k.DBInstanceClass}}))}async lambdaResources(){let R=[],t;do{let m=await this.client.request({service:"lambda",region:this.region,method:"GET",path:"/2015-03-31/functions",queryParams:t?{Marker:t}:void 0});for(let k of m?.Functions??[])R.push({...bo(k.FunctionArn),state:k.State,metadata:{runtime:k.Runtime,memoryMiB:Number(k.MemorySize??0)}});t=m?.NextMarker||void 0}while(t);return R}async s3Resources(){return(await new g("us-east-1",this.profile).listBuckets()).Buckets.map((t)=>({...bo(`arn:aws:s3:::${t.Name}`),metadata:{creationDate:t.CreationDate}}))}async discover(R){let t=[["tagging:GetResources",()=>this.taggedResources()],["ec2:Describe*",()=>this.ec2Resources()],["rds:DescribeDBInstances",()=>this.rdsResources()],["lambda:ListFunctions",()=>this.lambdaResources()],["s3:ListAllMyBuckets",()=>this.s3Resources()]],m=await Promise.allSettled(t.map(([,L])=>L())),k=[],D=[];return m.forEach((L,z)=>{if(L.status==="fulfilled")k.push(...L.value);else D.push(`${t[z][0]} unavailable: ${L.reason instanceof Error?L.reason.message:String(L.reason)}`)}),{resources:Wp(k,R?.type),warnings:D}}}function Ss(R,t){if(R==null)return{currentCost:null,projectedCost:null,monthlySavings:null};let m=R*t;return{currentCost:R,projectedCost:R-m,monthlySavings:m}}function Fd(R,t,m,k=!1){let D=Rc(R,t,m);if(D)return[{category:"unused",resource:R,recommendation:D.recommendation,sourceSignal:D.signal,...Ss(m,1)}];if((R.service==="ec2"&&R.type==="instance"||R.service==="rds"&&R.type==="db")&&t.cpuAverage!=null&&t.cpuAverage<20)return[{category:"right-size",resource:R,recommendation:"Validate memory, network, and peak demand, then test one smaller instance class.",sourceSignal:`${R.service.toUpperCase()} 30-day average CPU ${t.cpuAverage.toFixed(1)}% (<20%)`,...Ss(m,0.4)}];if(k&&(R.service==="ec2"&&R.type==="instance"&&R.state==="running"||R.service==="rds"&&R.type==="db"&&R.state==="available")&&t.cpuAverage!=null&&t.cpuAverage>=20)return[{category:"commitment",resource:R,recommendation:"Compare a one-year no-upfront commitment against this steady workload before purchasing.",sourceSignal:`${R.service.toUpperCase()} remained active with 30-day average CPU ${t.cpuAverage.toFixed(1)}%`,...Ss(m,0.3)}];if(R.service==="s3"&&t.storageBytes!=null&&t.storageBytes>=5368709120&&t.requests!=null&&t.requests<9000){let L=t.storageBytes/1073741824;return[{category:"storage",resource:R,recommendation:"Review object age and retrieval needs, then add Intelligent-Tiering or Glacier lifecycle rules.",sourceSignal:`${L.toFixed(1)} GiB stored with ${t.requests.toFixed(0)} requests over 90 days`,...Ss(m,0.25)}]}return[]}function Vp(R,t){if(R<=0||t.length===0)return null;return{category:"cdn",resource:{arn:"arn:aws:cloudfront::account:distribution/account-wide",service:"cloudfront",type:"distribution",id:"account-wide",name:"account-wide transfer",tags:{},metadata:{}},recommendation:"Enable compression and review cache policies, TTLs, and origin cache headers for high-transfer paths.",sourceSignal:`30-day Cost Explorer transfer line items: ${t.slice(0,3).join(", ")}`,...Ss(R,0.2)}}function qp(R){let t=R.map((m)=>m.Average).filter((m)=>m!=null);return t.length>0?t.reduce((m,k)=>m+k,0)/t.length:void 0}function Hp(R){let t=R.map((m)=>m.Sum).filter((m)=>m!=null);return t.length>0?t.reduce((m,k)=>m+k,0):void 0}function Rc(R,t,m=null){if(R.service==="ec2"&&R.type==="volume"&&R.metadata.attached===!1)return{resource:R,signal:"EBS volume is detached",recommendation:"Snapshot if needed, then delete the detached volume.",monthlySavings:m};if(R.service==="ec2"&&R.type==="elastic-ip"&&R.metadata.associated===!1)return{resource:R,signal:"Elastic IP is not associated",recommendation:"Release the unassociated Elastic IP.",monthlySavings:m};if(R.service==="ec2"&&R.type==="instance"&&t.cpuAverage!=null&&t.cpuAverage<5)return{resource:R,signal:`30-day average CPU ${t.cpuAverage.toFixed(1)}% (<5%)`,recommendation:"Stop/delete if unused, or right-size after validating memory and network demand.",monthlySavings:m};if(R.service==="rds"&&R.type==="db"&&t.connectionsAverage!=null&&t.iopsAverage!=null&&t.connectionsAverage===0&&t.iopsAverage<1)return{resource:R,signal:`14-day connections 0 and average IOPS ${t.iopsAverage.toFixed(2)} (<1)`,recommendation:"Take a final snapshot, validate ownership, then stop or delete the database.",monthlySavings:m};if(R.service==="lambda"&&R.type==="function"&&t.invocations===0)return{resource:R,signal:"30-day invocations 0",recommendation:"Confirm no event source depends on it, then remove the function.",monthlySavings:m};if(R.service==="s3"&&t.objectCount===0&&t.requests===0)return{resource:R,signal:"Bucket is empty with zero requests over 90 days",recommendation:"Confirm retention policy, then delete the empty bucket.",monthlySavings:m};if(R.service==="elasticache"&&t.connectionsAverage!=null&&t.cacheHitRate!=null&&t.connectionsAverage===0&&t.cacheHitRate<20)return{resource:R,signal:`14-day connections 0 and cache hit rate ${t.cacheHitRate.toFixed(1)}%`,recommendation:"Validate clients and snapshots, then delete the idle cache cluster.",monthlySavings:m};return null}class Zs{inventory;costs;profile;region;constructor(R,t=process.env.AWS_REGION||"us-east-1"){this.profile=R,this.region=t,this.inventory=new ws(R,t),this.costs=new We(R)}async metric(R,t,m,k,D,L,z,Y=[]){let X=new Date,ne=new Date(X.getTime()-L*24*60*60*1000),re=await R.getMetricStatistics({Namespace:m,MetricName:k,Dimensions:[{Name:D,Value:t.id},...Y],StartTime:ne,EndTime:X,Period:86400,Statistics:[z]});return z==="Sum"?Hp(re):qp(re)}async signals(R){let t=new q(R.region??this.region,this.profile);if(R.service==="ec2"&&R.type==="instance")return{cpuAverage:await this.metric(t,R,"AWS/EC2","CPUUtilization","InstanceId",30,"Average")};if(R.service==="rds"&&R.type==="db"){let[m,k,D,L]=await Promise.all([this.metric(t,R,"AWS/RDS","CPUUtilization","DBInstanceIdentifier",30,"Average"),this.metric(t,R,"AWS/RDS","DatabaseConnections","DBInstanceIdentifier",14,"Average"),this.metric(t,R,"AWS/RDS","ReadIOPS","DBInstanceIdentifier",14,"Average"),this.metric(t,R,"AWS/RDS","WriteIOPS","DBInstanceIdentifier",14,"Average")]);return{cpuAverage:m,connectionsAverage:k,iopsAverage:D!=null&&L!=null?D+L:void 0}}if(R.service==="lambda"&&R.type==="function")return{invocations:await this.metric(t,R,"AWS/Lambda","Invocations","FunctionName",30,"Sum")};if(R.service==="s3"){let[m,k,D]=await Promise.all([this.metric(t,R,"AWS/S3","NumberOfObjects","BucketName",90,"Average",[{Name:"StorageType",Value:"AllStorageTypes"}]),this.metric(t,R,"AWS/S3","AllRequests","BucketName",90,"Sum"),this.metric(t,R,"AWS/S3","BucketSizeBytes","BucketName",90,"Average",[{Name:"StorageType",Value:"StandardStorage"}])]);return{objectCount:m,requests:k,storageBytes:D}}if(R.service==="elasticache"&&R.type.includes("cluster")){let[m,k]=await Promise.all([this.metric(t,R,"AWS/ElastiCache","CurrConnections","CacheClusterId",14,"Average"),this.metric(t,R,"AWS/ElastiCache","CacheHitRate","CacheClusterId",14,"Average")]);return{connectionsAverage:m,cacheHitRate:k}}return{}}async resourceCosts(){try{let R=Fo(30).current,t=await this.costs.getCostByDimension({start:R.start,end:R.end,dimension:"RESOURCE_ID",granularity:"DAILY"});return new Map(t.map((m)=>[m.key,m.amount]))}catch{return new Map}}resourceCost(R,t){return R.get(t.id)??R.get(t.arn)??null}async cloudFrontTransfer(){try{let R=Fo(30).current,t=await this.costs.getCostByDimension({start:R.start,end:R.end,dimension:"USAGE_TYPE",granularity:"DAILY",filter:{Dimensions:{Key:"SERVICE",Values:["Amazon CloudFront"]}}}),m=Ys(t);return{cost:m.reduce((k,D)=>k+D.amount,0),usageTypes:m.map((k)=>k.key)}}catch{return{cost:0,usageTypes:[]}}}async unused(R){let[t,m]=await Promise.all([this.inventory.discover(R),this.resourceCosts()]),k=(await Promise.all(t.resources.map(async(D)=>{let L=this.resourceCost(m,D),z=Rc(D,{},L);if(z)return z;try{return Rc(D,await this.signals(D),L)}catch{return null}}))).filter((D)=>D!==null);return{findings:k,inventory:t,savingsAvailable:k.some((D)=>D.monthlySavings!=null)}}async optimize(R){let t=!R?.type||["cloudfront","cdn"].includes(R.type.toLowerCase()),[m,k,D]=await Promise.all([this.inventory.discover({type:R?.type}),this.resourceCosts(),t?this.cloudFrontTransfer():Promise.resolve({cost:0,usageTypes:[]})]),L=[],z=[...m.warnings];for(let X of m.resources){let ne=this.resourceCost(k,X),re=Fd(X,{},ne,R?.includeCommitments);if(re.length>0){L.push(...re);continue}try{L.push(...Fd(X,await this.signals(X),ne,R?.includeCommitments))}catch(ie){z.push(`CloudWatch metrics unavailable for ${X.service}:${X.type}/${X.name}: ${ie instanceof Error?ie.message:String(ie)}`)}}let Y=Vp(D.cost,D.usageTypes);if(Y)L.push(Y);return L.sort((X,ne)=>(ne.monthlySavings??-1)-(X.monthlySavings??-1)||X.category.localeCompare(ne.category)||X.resource.name.localeCompare(ne.resource.name)),{recommendations:L,inventory:m,savingsAvailable:L.some((X)=>X.monthlySavings!=null),warnings:z}}}var Ic="Amazon Simple Storage Service";function Gp(){let R=new Date,t=new Date(Date.UTC(R.getUTCFullYear(),R.getUTCMonth(),1)),m=new Date(t);m.setUTCMonth(m.getUTCMonth()-1);let k=(L)=>L.toISOString().slice(0,10),D=m.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"});return{start:k(m),end:k(t),label:D}}function fn(R){return`$${R.toFixed(2)}`}function ea(R){if(R===null)return"new";if(Math.abs(R)<0.05)return"→ 0.0%";return`${R>0?"↑":"↓"} ${Math.abs(R).toFixed(1)}%`}function Pc(R){r(`Cost Explorer request failed: ${R instanceof Error?R.message:String(R)}`),e(`
|
|
341
341
|
This command needs the ce:GetCostAndUsage IAM permission.`)}function Kp(R){let{label:t,profile:m,rows:k,total:D,count:L}=R,z=`# AWS Cost Analysis — ${t}`,Y=m?`
|
|
342
342
|
_Profile: \`${m}\`_
|
|
343
343
|
`:`
|
|
344
344
|
`,X=`| Service | Resources | Cost | % of Total |
|
|
345
345
|
|---|---|---|---|`,ne=k.map(([ie,ae,be,Ae])=>`| ${ie} | ${ae} | ${be} | ${Ae} |`).join(`
|
|
346
346
|
`),re=`
|
|
347
|
-
**Total: ${
|
|
347
|
+
**Total: ${fn(D)} across ${L} service${L===1?"":"s"}**`;return`${z}
|
|
348
348
|
${Y}
|
|
349
349
|
| Service | Resources | Cost | % of Total |
|
|
350
350
|
|---|---|---|---|
|
|
351
351
|
${ne}
|
|
352
352
|
${re}
|
|
353
353
|
`}function xc(R){R.command("cost:analyze","Rank AWS services by cost for the last full month").option("--output","Also write a markdown report to ./aws.md").option("--no-cache","Skip the local response cache (always hit AWS — costs $0.01)").action(async(t)=>{let m=t?.profile,k=t?.cache!==!1,{start:D,end:L,label:z}=Gp();i(`Cost Analysis — ${z}${m?` (profile: ${m})`:""}`);let Y=new s("Querying AWS Cost Explorer...");Y.start();let X=new We(m,{useCache:k}),ne;try{ne=await X.getCostByService({start:D,end:L})}catch(le){Y.stop(),r(`Cost Explorer request failed: ${le?.message??le}`),e(`
|
|
354
|
-
This command needs the ce:GetCostAndUsage IAM permission.`);return}let re=null,ie=!1;if(ne.some((le)=>le.service===Ic))try{re=(await new g("us-east-1",m).listBuckets()).Buckets?.length??0}catch{ie=!0}if(Y.stop(),ne.length===0){e("No billed services in this period.");return}let ae=ne.reduce((le,ke)=>le+ke.amount,0),be=ne.map((le)=>{let ke="-";if(le.service===Ic){if(ie)ke="unknown (no s3:ListAllMyBuckets)";else if(re!==null)ke=`${re} bucket${re===1?"":"s"}`}let De=ae>0?`${(le.amount/ae*100).toFixed(1)}%`:"—";return[le.service,ke,
|
|
355
|
-
Total: ${
|
|
354
|
+
This command needs the ce:GetCostAndUsage IAM permission.`);return}let re=null,ie=!1;if(ne.some((le)=>le.service===Ic))try{re=(await new g("us-east-1",m).listBuckets()).Buckets?.length??0}catch{ie=!0}if(Y.stop(),ne.length===0){e("No billed services in this period.");return}let ae=ne.reduce((le,ke)=>le+ke.amount,0),be=ne.map((le)=>{let ke="-";if(le.service===Ic){if(ie)ke="unknown (no s3:ListAllMyBuckets)";else if(re!==null)ke=`${re} bucket${re===1?"":"s"}`}let De=ae>0?`${(le.amount/ae*100).toFixed(1)}%`:"—";return[le.service,ke,fn(le.amount),De]});if(c(["Service","Resources","Cost","% of Total"],be),e(`
|
|
355
|
+
Total: ${fn(ae)} across ${ne.length} service${ne.length===1?"":"s"}`),X.lastCacheAgeSeconds!==null)e(`(cached, ${X.lastCacheAgeSeconds}s old — pass --no-cache to refresh)`);if((ne.find((le)=>le.service===Ic)?.amount??0)>1&&re===0)a(`
|
|
356
356
|
S3 has spend but listBuckets returned 0. The buckets are likely owned by a different account in your AWS Organization (consolidated billing rolls up to the payer account, but ListBuckets only shows buckets owned by the calling account).`);if(t?.output){let le=`${process.cwd()}/aws.md`;await Bun.write(le,Kp({label:z,profile:m,rows:be,total:ae,count:ne.length})),n(`
|
|
357
|
-
Wrote ${le}`)}}),R.command("cost:cache:clear","Wipe the local Cost Explorer response cache").option("--all","Wipe entries for every profile (default: just the current profile)").action(async(t)=>{let m=t?.all?void 0:t?.profile,k=hc(m),D=fc(m);e(`Cleared ${D.deletedFiles} cached response${D.deletedFiles===1?"":"s"} for ${D.scope}`),e(`(${k})`)}),R.command("cost","Show current month-to-date AWS spend and projected total").option("--env <environment>","Environment (production, staging, development)").option("--no-cache","Skip the local response cache").action(async(t)=>{let m=Ld(),k=new We(t?.profile,{useCache:t?.cache!==!1});i(`AWS Cost — ${m.label}${t?.profile?` (profile: ${t.profile})`:""}`);let D=new s("Querying month-to-date AWS spend...");D.start();try{let[L,z]=await Promise.all([k.getCostByService({start:m.start,end:m.end,granularity:"DAILY"}),k.getCostByService({start:m.previous.start,end:m.previous.end,granularity:"MONTHLY"})]);D.stop();let X=Ac(L,z).map((ae)=>{let be=kc(ae.amount,m.daysElapsed,m.daysInMonth);return[ae.service,
|
|
358
|
-
MTD: ${
|
|
359
|
-
Total: ${
|
|
360
|
-
Total ranked egress: ${
|
|
361
|
-
${D.resources.length} resource${D.resources.length===1?"":"s"} discovered.`);for(let L of D.warnings)a(L)}catch(D){m.stop(),r(`Resource discovery failed: ${D instanceof Error?D.message:String(D)}`)}}),R.command("resources:unused","Find unused resources").option("--env <environment>","Environment (production, staging, development)").option("--type <type>","Limit analysis to one resource type").option("--region <region>","AWS region for regional resources").action(async(t)=>{i(`Unused AWS Resources${t?.profile?` (profile: ${t.profile})`:""}`);let m=new s("Evaluating inventory and CloudWatch idle signals...");m.start();try{let k=await new Zs(t?.profile,t?.region).unused({type:t?.type});if(m.stop(),k.findings.length===0)e("No resources met the conservative unused-resource thresholds.");else{c(["Resource","Signal","Recommendation","Monthly savings"],k.findings.map((L)=>[`${L.resource.service}:${L.resource.type}/${L.resource.name}`,L.signal,L.recommendation,L.monthlySavings==null?"—":
|
|
362
|
-
Known monthly savings: ${k.savingsAvailable?
|
|
363
|
-
Estimated monthly savings: ${k.savingsAvailable?mn(D):"unavailable (enable CUR resource IDs)"}`)}for(let D of k.warnings)a(D);e("Estimates are directional; validate workload peaks and current AWS pricing before applying changes.")}catch(k){m.stop(),r(`Optimization analysis failed: ${k instanceof Error?k.message:String(k)}`)}})}async function Bo(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=y(process.cwd(),t);return{config:t,env:m,controlPlane:k,controls:new nr(k.store)}}function _o(R){r(R instanceof Error?R.message:String(R)),process.exitCode=1}function jd(R){let t=R?.infrastructure?.compute??{};return{ddos:t.ddos===!1?void 0:typeof t.ddos==="object"?t.ddos:{ports:[80,443]},waf:t.waf===!1?void 0:typeof t.waf==="object"?t.waf:{mode:"detection"}}}function Dc(R){R.command("protect:status","Show the current edge-protection posture").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Bo(t.env),k=m.controls.current(),D=Ji(k);if(t.json){e(JSON.stringify({posture:D,controls:m.controls.raw()},null,2));return}if(i("Edge protection"),D.mitigationPaused||D.attackMode)a(D.summary);else n(D.summary);let L=jd(m.config);if(e(`L3/L4 mitigation: ${L.ddos?"enabled":"disabled in config"}`),e(`WAF: ${L.waf?`enabled (${L.waf.mode??"detection"})`:"disabled in config"}`),e(`Allowlist: ${k.ipRules.allow.length} entry(s)`),e(`Blocklist: ${k.ipRules.block.length} entry(s)`),k.ipRules.allow.length>0||k.ipRules.block.length>0)c(["List","CIDR"],[...k.ipRules.allow.map((z)=>["allow",z]),...k.ipRules.block.map((z)=>["block",z])])}catch(m){_o(m)}}),R.command("protect:attack-mode <state>","Challenge every visitor for a bounded window (on|off)").option("--hours <hours>",`How long before it lifts itself (default ${Nn}, max ${rr})`).option("--reason <reason>","Why, for the record").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Bo(m.env);if(t==="off"){n(k.controls.disableAttackMode()?"Attack mode is off.":"Attack mode was not on.");return}if(t!=="on")throw Error("State must be 'on' or 'off'.");let D=k.controls.enableAttackMode({hours:m.hours==null?void 0:Number(m.hours),reason:m.reason??"Enabled from the CLI."});a("Attack mode is ON. Every visitor is challenged, including legitimate ones."),e(`It lifts itself at ${D.expiresAt} — you do not need to remember.`)}catch(k){_o(k)}}),R.command("protect:pause <state>","Pause or resume automatic mitigation (on|off)").option("--hours <hours>",`How long (max ${rr})`).option("--reason <reason>","Why — required when pausing").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Bo(m.env);if(t==="off"){n(k.controls.resumeMitigations()?"Automatic mitigation resumed.":"Mitigation was not paused.");return}if(t!=="on")throw Error("State must be 'on' or 'off'.");if(!m.reason)throw Error("--reason is required: you are liable for the traffic this admits.");let D=k.controls.pauseMitigations({hours:m.hours==null?void 0:Number(m.hours),reason:m.reason});a("Automatic mitigation is PAUSED. All traffic is served, and billed, including attacks."),e(`It resumes automatically at ${D.expiresAt}.`)}catch(k){_o(k)}}),R.command("protect:allow <cidr>","Never rate-limit or block this IP or CIDR").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Bo(m.env),D=m.remove?k.controls.removeIpRule("allow",t):k.controls.addIpRule("allow",t);n(`Allowlist now has ${D.allow.length} entry(s).`),e("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(k){_o(k)}}),R.command("protect:block <cidr>","Drop traffic from this IP or CIDR at the kernel").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Bo(m.env),D=m.remove?k.controls.removeIpRule("block",t):k.controls.addIpRule("block",t);n(`Blocklist now has ${D.block.length} entry(s).`),e("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(k){_o(k)}}),R.command("protect:apply","Print the ruleset the current controls produce").option("--env <environment>","Target environment").option("--waf","Print the WAF configuration instead of the firewall script").action(async(t)=>{try{let m=await Bo(t.env),k=jd(m.config);if(t.waf){if(!k.waf)throw Error("The WAF is disabled in this configuration.");let D=bi(k.waf);for(let L of D.warnings)a(L);e(D.config);return}if(!k.ddos)throw Error("L3/L4 mitigation is disabled in this configuration.");e(Rr(xn(k.ddos,m.controls.current())))}catch(m){_o(m)}})}async function Tn(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=y(process.cwd(),t),D=k.environments.get(m),L=new Ar(k.store),z=new St(k.store,{encryptionKey:_(process.cwd())});return{config:t,env:m,controlPlane:k,environmentRecord:D,store:L,alerts:z,service:new vt(L),gate:new Vt(k.store),organizationId:k.organization.id,projectId:k.project.id}}function ta(R){if(R==null)return;let t=String(R).trim().replace(/[$,\s]/g,"");if(!t)return;if(/^\d+c$/i.test(t))return Number.parseInt(t,10);let m=Number(t);if(!Number.isFinite(m)||m<0)throw Error(`Invalid amount: ${R}`);return Math.round(m*100)}function Bd(R){if(!R)return;let t=R.split(",").map((m)=>m.trim()).filter(Boolean);for(let m of t)if(!fo.includes(m))throw Error(`Unknown enforcement action '${m}'. Valid: ${fo.join(", ")}`);return t}function _d(R){if(R==null)return"monthly";if(R==="daily"||R==="weekly"||R==="monthly")return R;throw Error(`Unknown period '${R}'. Valid: daily, weekly, monthly`)}function Jp(R){return pn[R]?.label??R}function Yp(R,t=24){let m=Math.max(0,Math.min(t,Math.round(R/100*t)));return`${"█".repeat(m)}${"░".repeat(t-m)}`}function Fn(R){r(R instanceof Error?R.message:String(R)),process.exitCode=1}function Nc(R){R.command("usage","Show current usage, cost, and remaining budget headroom").option("--env <environment>","Target environment").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Tn(t.env),k=m.service.usageReport({organizationId:m.organizationId,projectId:m.projectId,period:_d(t.period),timezone:t.timezone});if(t.json){e(JSON.stringify(k,null,2));return}if(i(`Usage — ${k.window.label}`),e(`Total: ${ee(k.totalCents,k.currency)}`),k.byMeter.length===0)e("No metered usage in this window yet.");else c(["Meter","Provider","Quantity","Cost"],k.byMeter.map((D)=>[Jp(D.meter),D.provider||"-",D.quantity.toLocaleString(void 0,{maximumFractionDigits:3}),ee(D.costCents,k.currency)]));if(k.budgets.length===0){a("No budgets are configured. Nothing is capping this project."),e("Create one with: cloud budget:create --name 'Monthly' --hard 200");return}if(i("Budgets"),c(["Name","Period","Spent","Limit","Used","Projected","Time to cap","State"],k.budgets.map((D)=>[D.name+(D.dryRun?" (dry run)":""),D.period,ee(D.spentCents,k.currency),D.limitCents==null?"-":ee(D.limitCents,k.currency),`${Yp(D.usedPercent)} ${D.usedPercent.toFixed(0)}%`,`${ee(D.projectedCents,k.currency)} (${(D.projectionConfidence*100).toFixed(0)}% conf)`,D.timeToCap||"-",D.level])),k.enforcement.strongestAction)a(`Enforcement in force: ${k.enforcement.strongestAction}`)}catch(m){Fn(m)}}),R.command("budget:list","List budgets governing this project").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Tn(t.env),k=m.store.listBudgets({organizationId:m.organizationId,projectId:m.projectId});if(t.json){e(JSON.stringify(k,null,2));return}if(k.length===0){a("No budgets configured.");return}c(["ID","Name","Scope","Period","Soft","Hard","Mode","Enabled"],k.map((D)=>[D.id.slice(0,8),D.name,D.environmentId?"environment":D.projectId?"project":"organization",D.period,D.softLimitCents==null?"-":ee(D.softLimitCents,D.currency),D.hardLimitCents==null?"-":ee(D.hardLimitCents,D.currency),D.dryRun?"dry run":"enforcing",D.enabled?"yes":"no"]))}catch(m){Fn(m)}}),R.command("budget:create","Create a spend budget").option("--name <name>","Budget name").option("--soft <amount>","Soft limit in dollars (warn only)").option("--hard <amount>","Hard limit in dollars (enforces)").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--scope <scope>","organization, project, or environment (default project)").option("--actions <list>","Comma-separated actions to run at 100% (default notify,block_builds,block_deployments)").option("--grace <seconds>","Seconds a breach must persist before enforcing").option("--dry-run","Evaluate and report but never enforce").option("--env <environment>","Target environment").action(async(t)=>{try{if(!t.name)throw Error("--name is required.");let m=await Tn(t.env),k=t.scope??"project";if(!["organization","project","environment"].includes(k))throw Error(`Unknown scope '${k}'. Valid: organization, project, environment`);let D=Bd(t.actions),L=m.store.createBudget({organizationId:m.organizationId,projectId:k==="organization"?void 0:m.projectId,environmentId:k==="environment"?m.environmentRecord?.id:void 0,name:t.name,period:_d(t.period),timezone:t.timezone,softLimitCents:ta(t.soft),hardLimitCents:ta(t.hard),graceSeconds:t.grace==null?void 0:Number(t.grace),thresholds:D?[{atPercent:80,actions:["notify"]},{atPercent:100,actions:D}]:[...yo],dryRun:t.dryRun===!0});n(`Created budget '${L.name}' (${L.id}).`),e(`Mode: ${L.dryRun?"dry run — reports only, enforces nothing":"enforcing"}`),e(`Ladder: ${L.thresholds.map((z)=>`${z.atPercent}% → ${z.actions.join("+")}`).join(", ")}`)}catch(m){Fn(m)}}),R.command("budget:update <budgetId>","Update a budget").option("--soft <amount>","Soft limit in dollars").option("--hard <amount>","Hard limit in dollars").option("--enable","Enable the budget").option("--disable","Disable the budget").option("--enforce","Leave dry run and start enforcing").option("--dry-run","Return to dry run").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Tn(m.env),D=k.store.getBudget(t);if(!D)throw Error(`Budget '${t}' was not found.`);let L=k.store.updateBudget(t,{softLimitCents:ta(m.soft)??D.softLimitCents,hardLimitCents:ta(m.hard)??D.hardLimitCents,enabled:m.disable?!1:m.enable?!0:D.enabled,dryRun:m.enforce?!1:m.dryRun?!0:D.dryRun});n(`Updated budget '${L.name}'.`),e(`Mode: ${L.dryRun?"dry run":"enforcing"} · Enabled: ${L.enabled?"yes":"no"}`)}catch(k){Fn(k)}}),R.command("budget:delete <budgetId>","Delete a budget and lift anything it was enforcing").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Tn(m.env),D=k.store.getBudget(t);if(!D)throw Error(`Budget '${t}' was not found.`);let L=k.gate.closeBudget(t);if(k.store.deleteBudget(t),n(`Deleted budget '${D.name}'.`),L>0)e(`Lifted ${L} enforcement action(s) it had in force.`)}catch(k){Fn(k)}}),R.command("spend:check","Run one spend cycle now and report what it would do").option("--env <environment>","Target environment").option("--apply","Actually apply enforcement (default is a preview)").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Tn(t.env),k=new Ri({controlPlane:m.controlPlane.store,store:m.store,alerts:t.apply?m.alerts:void 0});if(!t.apply){let z=m.store.budgetsForScope(m.organizationId,m.projectId).map((Y)=>m.service.status(Y));if(t.json){e(JSON.stringify(z,null,2));return}if(i("Spend check (preview — nothing was applied)"),z.length===0){a("No budgets govern this project.");return}c(["Budget","Used","Projected","Level","Would apply"],z.map((Y)=>[Y.budget.name,`${Y.decision.usedPercent.toFixed(1)}%`,`${Y.decision.projectedPercent.toFixed(1)}%`,Y.decision.level,Y.decision.actions.join(", ")||"-"])),e(`
|
|
364
|
-
Run with --apply to enforce.`);return}let D=await k.run({organizationId:m.organizationId,projectId:m.projectId,environmentId:m.environmentRecord?.id,environmentKind:m.environmentRecord?.kind});if(t.json){e(JSON.stringify(D,null,2));return}if(i("Spend cycle"),e(`Evaluated ${D.decisions.length} budget(s) in ${D.durationMs}ms.`),D.applied.length>0)a(`Applied: ${D.applied.join(", ")}`);if(D.released.length>0)n(`Released: ${D.released.join(", ")}`);if(D.withheld.length>0)a(`Withheld pending approval: ${D.withheld.join(", ")}`);if(D.anomalies.length>0)a(`${D.anomalies.length} anomaly(ies) detected.`);e(`Notifications queued: ${D.notificationsSent}`);for(let L of D.warnings)a(L);if(D.applied.length===0&&D.released.length===0)n("Nothing to change.")}catch(m){Fn(m)}}),R.command("spend:work","Run the spend loop continuously: meter, evaluate, enforce, notify").option("--env <environment>","Target environment").option("--interval <seconds>",`Seconds between cycles (default ${
|
|
365
|
-
Spend worker stopped; the loop lease was released.`),process.exit(0)};process.on("SIGINT",X),process.on("SIGTERM",X),await new Promise(()=>{})}catch(m){Fn(m)}}),R.command("spend:status","Show enforcement currently in force").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Tn(t.env),k=m.gate.listUnder({organizationId:m.organizationId});if(t.json){e(JSON.stringify(k,null,2));return}if(k.length===0){n("No spend enforcement is in force.");return}c(["Action","Budget","Scope","Mode","Since","Reason"],k.map((D)=>[D.action,D.budgetId.slice(0,8),D.environmentId?"environment":D.projectId?"project":"organization",D.simulated?"dry run":"enforcing",D.appliedAt,D.reason]))}catch(m){Fn(m)}}),R.command("spend:release <budgetId> <action>","Manually lift one enforcement action").option("--env <environment>","Target environment").action(async(t,m,k)=>{try{let D=await Tn(k.env),L=Bd(m)?.[0];if(!L)throw Error("An action is required.");if(!D.gate.close(t,L))throw Error(`${L} was not in force for budget ${t}.`);n(`Lifted ${L} for budget ${t}.`),a("The next spend cycle will re-apply this if spend is still over the threshold.")}catch(D){Fn(D)}}),R.command("anomaly:config","Tune anomaly detection for a signal").option("--signal <signal>","Signal to configure (default cost)").option("--sensitivity <level>","low, medium, or high").option("--season <points>","Points per season: 24 for daily, 168 for weekly").option("--min-delta <amount>","Absolute change below which nothing is reported, in the signal's units").option("--severity <severity>","info, warning, or critical").option("--disable","Stop detecting this signal").option("--enable","Resume detecting this signal").option("--list","List configured signals instead of changing one").option("--signals","List every detectable signal and its data rules").option("--env <environment>","Target environment").action(async(t)=>{try{if(t.signals){c(["Signal","Source","Empty hour","Min samples","Unit"],jt.map((z)=>[z.key,z.source,z.gapPolicy==="zero"?"counts as zero":"ignored",String(z.minSamples),z.unit]));return}let m=await Tn(t.env),k=m.service.anomalyConfigs;if(t.list){let z=k.list({organizationId:m.organizationId,projectId:m.projectId});if(z.length===0){e("No signals are configured; every one uses its shipped defaults.");return}c(["Signal","Enabled","Sensitivity","Season","Min delta","Severity"],z.map((Y)=>[Y.signal,Y.enabled?"yes":"no",Y.sensitivity,String(Y.seasonLength),String(Y.minAbsoluteDelta),Y.severity]));return}let D=t.sensitivity;if(D&&!["low","medium","high"].includes(D))throw Error("--sensitivity must be low, medium, or high.");let L=k.upsert({organizationId:m.organizationId,projectId:m.projectId,signal:t.signal??"cost",sensitivity:D,seasonLength:t.season==null?void 0:Number(t.season),minAbsoluteDelta:t.minDelta==null?void 0:Number(t.minDelta),severity:t.severity,enabled:t.disable?!1:t.enable?!0:void 0});n(`Anomaly detection for '${L.signal}' updated.`),e(`${L.enabled?"Enabled":"Disabled"} · ${L.sensitivity} sensitivity · season ${L.seasonLength} · floor ${L.minAbsoluteDelta} · ${L.severity}`)}catch(m){Fn(m)}}),R.command("anomaly:silence","Skip detection for a signal, route, or status code").option("--signal <signal>","Signal to silence").option("--route <pattern>","Route glob, e.g. /webhooks/**").option("--status <code>","HTTP status code").option("--reason <reason>","Why — required").option("--until <timestamp>","ISO timestamp the silence expires").option("--list","List active silences").option("--remove <id>","Remove a silence").option("--env <environment>","Target environment").action(async(t)=>{try{let m=await Tn(t.env),k=m.service.anomalyConfigs;if(t.remove){n(k.removeSilence(t.remove)?"Silence removed.":"That silence was not found.");return}if(t.list){let L=k.listSilences({organizationId:m.organizationId,projectId:m.projectId});if(L.length===0){n("No active silences.");return}c(["ID","Signal","Route","Status","Expires","Reason"],L.map((z)=>[z.id.slice(0,8),z.signal??"-",z.routePattern??"-",z.statusCode==null?"-":String(z.statusCode),z.expiresAt??"never",z.reason]));return}if(!t.reason)throw Error("--reason is required: someone will ask why this stopped alerting.");let D=k.silence({organizationId:m.organizationId,projectId:m.projectId,signal:t.signal,routePattern:t.route,statusCode:t.status==null?void 0:Number(t.status),reason:t.reason,expiresAt:t.until});if(n(`Silenced (${D.id.slice(0,8)}).`),!D.expiresAt)a("This silence never expires. Pass --until to bound it, or it will outlive the reason for it.")}catch(m){Fn(m)}}),R.command("spend:anomalies","List detected spend anomalies").option("--env <environment>","Target environment").option("--unacknowledged","Only anomalies nobody has acknowledged").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Tn(t.env),k=m.store.listAnomalies({organizationId:m.organizationId,projectId:m.projectId,unacknowledgedOnly:t.unacknowledged===!0});if(t.json){e(JSON.stringify(k,null,2));return}if(k.length===0){n("No anomalies detected.");return}c(["Hour","Signal","Direction","Observed","Expected","Score","Severity"],k.map((D)=>[D.bucketStart,D.signal,D.direction,ee(D.observed),ee(D.expected),D.score.toFixed(1),D.severity]))}catch(m){Fn(m)}})}import{readFileSync as Zp}from"node:fs";var Qp={github:"GITHUB_TOKEN",gitlab:"GITLAB_TOKEN",bitbucket:"BITBUCKET_TOKEN",gitea:"GITEA_TOKEN",generic_https:"GIT_TOKEN"},Xp={github:"https://github.com",gitlab:"https://gitlab.com",bitbucket:"https://bitbucket.org",gitea:"https://gitea.com"};function Oc(R,t={}){if(t.provider&&!["github","gitlab","bitbucket","gitea","generic_https","generic_ssh"].includes(t.provider))throw Error(`Unsupported Git provider: ${t.provider}`);let m=t.cloneUrl??(/^(?:https?:\/\/|ssh:\/\/|git@)/.test(R)?R:void 0),k="",D=m??R;if(m?.startsWith("git@")){let Y=/^git@([^:]+):(.+)$/.exec(m);if(!Y)throw Error("SSH repository URL must use git@host:owner/name.git");k=Y[1],D=Y[2]}else if(m){let Y=new URL(m);k=Y.hostname,D=Y.pathname}if(D=D.replace(/^\/+|\/+$/g,"").replace(/\.git$/,""),!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(D))throw Error("Repository must use owner/name or a supported clone URL");let L=t.provider??(k.includes("github")?"github":k.includes("gitlab")?"gitlab":k.includes("bitbucket")?"bitbucket":k.includes("gitea")?"gitea":m?.startsWith("git@")||m?.startsWith("ssh://")?"generic_ssh":m?"generic_https":"github"),z=t.host??Xp[L]??(k?`https://${k}`:"");if(!z)throw Error("A provider host is required");return{provider:L,host:z,fullName:D,cloneUrl:m}}async function ef(){let R=await Ne(),t=y(process.cwd(),R);return{config:R,controlPlane:t,sources:new ar(t.store,{encryptionKey:_(process.cwd())})}}async function Ai(R){let t=await ef();try{return await R(t)}finally{t.controlPlane.store.close()}}function $s(R,t,m){let k=R.listConnections(t);if(!m)return k.length===1?k[0]:void 0;return k.find((D)=>D.id===m||D.name.toLowerCase()===m.toLowerCase())}function Md(R,t){if(!R)throw Error(`${t} file is required`);return Zp(R,"utf8")}async function tf(R,t,m,k){if(["github","gitlab","bitbucket","gitea"].includes(t.provider)){let L=R.listRepositories(t.id).find((X)=>X.fullName.toLowerCase()===m.fullName.toLowerCase());if(L)return L;let Y=(await Wr(R,t.id,{search:m.fullName,maxPages:20})).find((X)=>X.fullName.toLowerCase()===m.fullName.toLowerCase());if(!Y)throw Error(`Repository ${m.fullName} is not granted to this connection`);return Y}let D=k.url??m.cloneUrl;if(!D)throw Error("Generic Git connections require --url <clone-url>");return R.upsertRepository({connectionId:t.id,providerRepositoryId:`manual:${m.fullName}`,fullName:m.fullName,cloneUrl:D,defaultBranch:k.branch??"main",visibility:"unknown",archived:!1,metadata:{source:"cli"}})}async function zd(R,t,m){let k=Oc(t,{provider:m.provider,host:m.host,cloneUrl:m.url}),D=$s(R.sources,R.controlPlane.organization.id,m.connection);if(!D){if(m.connection)throw Error(`Source connection ${m.connection} was not found`);let z=m.tokenEnv??Qp[k.provider],Y=z?process.env[z]:void 0;if(["github","gitlab","bitbucket","gitea"].includes(k.provider)&&!Y)throw Error(`Set ${z} or pass --token-env <name>; secrets are never accepted as command arguments`);let X=k.provider==="generic_ssh"?{publicKey:Md(m.publicKey,"Public key"),privateKey:Md(m.privateKey,"Private key"),hostKey:m.hostKey??""}:void 0;R.controlPlane.store.database.transaction(()=>{if(D=R.sources.createConnection({organizationId:R.controlPlane.organization.id,provider:k.provider,name:m.name??`${k.provider.replace("_"," ")} · ${new URL(k.host).host}`,host:k.host,owner:k.fullName.split("/")[0],authKind:k.provider==="generic_ssh"?"deploy_key":Y?"access_token":"none",credential:Y?{token:Y}:void 0}),X)R.sources.createDeployKey({connectionId:D.id,name:m.deployKey??`${k.fullName} deploy key`,...X,host:m.sshHost??new URL(k.host).hostname,actorId:void 0})})()}if(!D)throw Error("Source connection could not be created");if(D.status==="disconnected")throw Error("The selected source connection is disconnected");let L=await tf(R.sources,D,k,m);if(m.site){let z=R.controlPlane.environments.get("production")??[...R.controlPlane.environments.values()][0],Y=R.controlPlane.store.listResources(R.controlPlane.project.id,z?.id).find((ne)=>ne.kind==="application"&&ne.slug===m.site);if(!Y)throw Error(`Application ${m.site} was not found`);let X=D.provider==="generic_ssh"?R.sources.listDeployKeys(D.id)[0]:void 0;R.sources.createBinding({projectId:R.controlPlane.project.id,environmentId:z?.id,resourceId:Y.id,connectionId:D.id,repositoryId:L.id,repositoryFullName:L.fullName,defaultBranch:m.branch??L.defaultBranch,branchRule:m.tag?void 0:m.branch??L.defaultBranch,tagRule:m.tag,monorepoRoot:m.root??".",includePaths:m.include?.split(",").map((ne)=>ne.trim()).filter(Boolean),excludePaths:m.exclude?.split(",").map((ne)=>ne.trim()).filter(Boolean),submodules:m.submodules,cloneDepth:m.fullClone?void 0:20,deployKeyId:X?.id})}return{connection:D,repository:L}}function Ni(R){r(R instanceof Error?R.message:"Git integration command failed")}function Lc(R){R.command("git:connections","List encrypted Git provider connections").action(async()=>{try{await Ai(async({sources:t,controlPlane:m})=>{let k=t.listConnections(m.organization.id);c(["ID","Name","Provider","Status","Credential","Last sync"],k.map((D)=>[D.id,D.name,D.provider,D.status,D.credentialConfigured?"encrypted":"none",D.lastSyncedAt??"-"]))})}catch(t){Ni(t)}}),R.command("git:repositories","List repositories granted to a connection").option("--connection <id>","Connection ID or name").action(async(t)=>{try{await Ai(async({sources:m,controlPlane:k})=>{let D=$s(m,k.organization.id,t.connection);if(!D)throw Error("Choose a connection with --connection <id>");let L=m.listRepositories(D.id);c(["ID","Repository","Default branch","Visibility","Archived"],L.map((z)=>[z.id,z.fullName,z.defaultBranch,z.visibility,z.archived?"yes":"no"]))})}catch(m){Ni(m)}}),R.command("git:add <repo>","Connect and optionally bind a Git repository").option("--branch <branch>","Default branch to deploy",{default:"main"}).option("--connection <id>","Reuse a connection ID or name").option("--provider <provider>","github, gitlab, bitbucket, gitea, generic_https, or generic_ssh").option("--host <url>","Provider API host").option("--name <name>","Connection name").option("--token-env <name>","Environment variable containing the access token").option("--url <clone-url>","Credential-free clone URL").option("--site <slug>","Bind the repository to an application").option("--root <path>","Monorepo root",{default:"."}).option("--include <globs>","Comma-separated watched paths").option("--exclude <globs>","Comma-separated ignored paths").option("--tag <glob>","Deploy matching tags instead of branch pushes").option("--full-clone","Use full history instead of a shallow clone").option("--submodules","Initialize repository submodules").option("--deploy-key <name>","Deploy-key display name").option("--public-key <path>","SSH public-key file").option("--private-key <path>","SSH private-key file").option("--host-key <key>","Pinned SSH host key").option("--ssh-host <host>","SSH clone host").option("--yes","Skip confirmation").action(async(t,m)=>{try{if(!m.yes&&!await d(`Connect ${t}?`,!0))return;let k=await Ai((D)=>zd(D,t,m));if(n(`Connected ${k.repository.fullName}`),e(`Connection: ${k.connection.name} (${k.connection.id})`),m.site)e(`Application binding: ${m.site}`)}catch(k){Ni(k)}}),R.command("git:import","Import site.repository entries from cloud.config.ts").option("--connection <id>","Reuse a connection ID or name").option("--token-env <name>","Environment variable containing provider token").option("--yes","Skip confirmation").action(async(t)=>{try{await Ai(async(m)=>{let k=Object.entries(m.config.sites??{}).filter(([,D])=>D.repository?.url);if(!k.length)throw Error("No site.repository entries were found");if(!t.yes&&!await d(`Import ${k.length} repository configuration${k.length===1?"":"s"}?`,!0))return;for(let[D,L]of k){let z=L.repository,Y=z.provider==="custom"?void 0:z.provider;await zd(m,z.url,{connection:t.connection,tokenEnv:t.tokenEnv,site:D,provider:Y,branch:z.branch??"main",tag:z.strategy==="tag"?z.tag??z.tagPattern??"v*":void 0,url:z.url}),n(`Imported ${D} · ${z.url}`)}})}catch(m){Ni(m)}}),R.command("git:branches <repo>","List branches or tags visible to a connection").option("--connection <id>","Connection ID or name").option("--tags","List tags instead of branches").option("--deploy-key <id>","SSH deploy-key ID").action(async(t,m)=>{try{await Ai(async({sources:k,controlPlane:D})=>{let L=$s(k,D.organization.id,m.connection);if(!L)throw Error("Choose a connection with --connection <id>");let z=k.listRepositories(L.id).find((X)=>X.id===t||X.fullName.toLowerCase()===t.toLowerCase());if(!z)throw Error(`Repository ${t} was not found`);let Y=await cr(k,{connectionId:L.id,repository:z.fullName,repositoryId:z.id,type:m.tags?"tags":"branches",deployKeyId:m.deployKey??k.listDeployKeys(L.id)[0]?.id});c([m.tags?"Tag":"Branch","Commit","Protected"],Y.refs.map((X)=>[X.name,X.commitSha,X.protected?"yes":"no"]))})}catch(k){Ni(k)}}),R.command("git:deploy <branch>","Queue an idempotent deployment from a Git branch").option("--binding <id>","Source binding ID").option("--repo <owner/name>","Bound repository").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{try{await Ai(async({sources:k,controlPlane:D})=>{let L=D.environments.get(m.env??"production");if(!L)throw Error(`Environment ${m.env} was not found`);let z=k.listBindings({projectId:D.project.id,status:"active"}).find((re)=>(!m.binding||re.id===m.binding)&&(!m.repo||re.repositoryFullName.toLowerCase()===m.repo.toLowerCase())&&(!re.environmentId||re.environmentId===L.id));if(!z)throw Error("No active source binding matched; use --binding or --repo");let X=(await cr(k,{connectionId:z.connectionId,repository:z.repositoryFullName,repositoryId:z.repositoryId,type:"branches",deployKeyId:z.deployKeyId})).refs.find((re)=>re.name===t);if(!X)throw Error(`Branch ${t} was not found`);let ne=D.store.createOperation({projectId:z.projectId,environmentId:L.id,resourceId:z.resourceId,kind:"deploy.source",idempotencyKey:`cli-source:${z.id}:${X.commitSha}`,correlationId:`cli-source:${z.id}:${X.commitSha}`,input:{source:{connectionId:z.connectionId,bindingId:z.id,repository:z.repositoryFullName,commitSha:X.commitSha,branch:t,monorepoRoot:z.monorepoRoot,submodules:z.submodules,cloneDepth:z.cloneDepth??null}}});n(`Deployment queued: ${ne.id}`),e(`${z.repositoryFullName}@${t} · ${X.commitSha}`)})}catch(k){Ni(k)}}),R.command("git:webhook:add <repo>","Create and reconcile a signed auto-deploy webhook").option("--connection <id>","Connection ID or name").option("--base-url <url>","Public HTTPS dashboard URL").action(async(t,m)=>{try{await Ai(async({sources:k,controlPlane:D})=>{let L=$s(k,D.organization.id,m.connection);if(!L)throw Error("Choose a connection with --connection <id>");let z=k.listRepositories(L.id).find((ie)=>ie.id===t||ie.fullName.toLowerCase()===t.toLowerCase());if(!z)throw Error(`Repository ${t} was not found`);let Y=m.baseUrl??process.env.TS_CLOUD_WEBHOOK_BASE_URL;if(!Y)throw Error("Pass --base-url or set TS_CLOUD_WEBHOOK_BASE_URL");let X=k.createWebhook({connectionId:L.id,repositoryId:z.id,repositoryFullName:z.fullName}),ne=Ot(Y,X.webhook),re=await lr(k,X.webhook.id,Y);n(`Webhook ${re.status}: ${ne}`),e("The signing secret is encrypted and is not printed.")})}catch(k){Ni(k)}}),R.command("git:webhook:remove <webhook>","Disable and remove a provider webhook").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Ai(async({sources:k,controlPlane:D})=>{let L=k.listConnections(D.organization.id).flatMap((Y)=>k.listWebhooks(Y.id)).find((Y)=>Y.id===t||Y.repositoryFullName.toLowerCase()===t.toLowerCase());if(!L)throw Error(`Webhook ${t} was not found`);if(!m.yes&&!await d(`Remove webhook for ${L.repositoryFullName}?`,!0))return;let z=await Yi(k,L.id);n(`Webhook disabled: ${z.id}`)})}catch(k){Ni(k)}}),R.command("git:disconnect <connection>","Preview and disconnect a Git provider connection").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Ai(async({sources:k,controlPlane:D})=>{let L=$s(k,D.organization.id,t);if(!L)throw Error(`Source connection ${t} was not found`);let z=k.listBindings({connectionId:L.id,status:"active"});e(`${z.length} active binding${z.length===1?"":"s"} will be disabled.`);for(let Y of z)e(` ${Y.repositoryFullName} · ${Y.branchRule??Y.defaultBranch} · ${Y.monorepoRoot}`);if(!m.yes&&!await d(`Disconnect ${L.name}?`,!1))return;k.disconnectConnection(L.id),n(`Disconnected ${L.name}`)})}catch(k){Ni(k)}})}import{readFileSync as Wd,writeFileSync as nf}from"node:fs";import{resolve as na}from"node:path";async function rf(){let R=await Ne(),t=y(process.cwd(),R),m=_(process.cwd());return{config:R,controlPlane:t,drafts:new ti(t.store),artifacts:new ni(t.store,{cwd:process.cwd()}),registries:new ri(t.store,{encryptionKey:m}),sources:new ar(t.store,{encryptionKey:m})}}async function kr(R){let t=await rf();try{return await R(t)}finally{t.controlPlane.store.close()}}function bn(R){r(R instanceof Error?R.message:"Application onboarding command failed")}function Ud(R){return JSON.parse(Wd(na(R),"utf8"))}function of(R){let t=R.site,m=["laravel","php","statamic","wordpress"].includes(t.type),k=["static","spa"].includes(t.type)||!t.start&&!m,D=k?{kind:"static",publishDirectory:t.root??t.webDirectory??"dist",buildCommand:t.build}:{kind:"server",runtime:m?"php":"bun",runtimeVersion:m?t.phpVersion:void 0,installCommand:m?"composer install --no-dev --prefer-dist --optimize-autoloader":void 0,buildCommand:t.build,startCommand:t.start??(m?"php -S 0.0.0.0:$PORT -t public":"bun run start")};return{schemaVersion:1,name:t.name??R.siteSlug,slug:R.siteSlug,projectId:R.projectId,environmentId:R.environmentId,source:R.source??{kind:"local",root:t.root??"."},build:D,runtime:{target:"server",architecture:t.architecture==="arm64"?"arm64":"x86_64",port:k?void 0:Number(t.port??3000),healthCheck:k?void 0:{protocol:"http",path:t.healthCheck?.path??"/health"}},domain:t.domain?{hostname:t.domain,tls:t.ssl?.provider!=="none"}:void 0}}function Fc(R){R.command("app:detect [root]","Detect application frameworks without executing project code").action(async(t=".")=>{try{let m=$t(ei(na(t)));c(["Framework","Strategy","Confidence","Evidence"],m.map((k)=>[k.framework,k.strategy,`${Math.round(k.confidence*100)}%`,k.evidence.map((D)=>D.path).join(", ")||"manual"]));for(let k of m)e(`${k.framework}: ${k.description}`)}catch(m){bn(m)}}),R.command("app:plan <draft>","Validate and preview a secret-free application draft").option("--secrets <names>","Comma-separated configured secret names").action(async(t,m)=>{try{let k=de(Ud(t),m.secrets?.split(",").map((D)=>D.trim()).filter(Boolean));if(e(JSON.stringify(k,null,2)),!k.valid)bn(Error("Plan is not deployable; resolve the reported issues and missing secrets."))}catch(k){bn(k)}}),R.command("app:draft:save <draft>","Persist or update a resumable application draft").option("--id <id>","Existing draft ID").option("--version <version>","Expected draft version").option("--step <step>","Wizard step",{default:"source"}).option("--secrets <names>","Comma-separated configured secret names").action(async(t,m)=>{try{await kr(async({drafts:k,controlPlane:D})=>{let L=Ud(t),z=m.secrets?.split(",").map((X)=>X.trim()).filter(Boolean)??[],Y=m.id?k.update(m.id,Number(m.version),{draft:L,step:m.step,suppliedSecretNames:z}):k.create({organizationId:D.organization.id,projectId:D.project.id,name:L.name,draft:{...L,projectId:D.project.id},step:m.step,suppliedSecretNames:z});n(`Draft saved: ${Y.id} · v${Y.version} · ${Y.status}`)})}catch(k){bn(k)}}),R.command("app:drafts","List resumable application drafts").action(async()=>{try{await kr(async({drafts:t,controlPlane:m})=>{let k=t.list(m.project.id);c(["ID","Application","Step","Status","Version","Updated"],k.map((D)=>[D.id,D.input.name,D.step,D.status,String(D.version),D.updatedAt]))})}catch(t){bn(t)}}),R.command("app:export <draft>","Export a deterministic application manifest").option("--output <path>","Output path",{default:"application.manifest.json"}).action(async(t,m)=>{try{await kr(async({drafts:k})=>{let D=k.get(t);if(!D)throw Error(`Draft ${t} was not found`);let L=de(D.input,D.suppliedSecretNames),z=na(m.output??"application.manifest.json");nf(z,L.serializedManifest,{mode:384}),n(`Manifest exported: ${z}`)})}catch(k){bn(k)}}),R.command("app:deploy <draft>","Confirm and queue a validated application plan").option("--confirm <environment>","Exact target environment slug").action(async(t,m)=>{try{await kr(async({drafts:k,controlPlane:D})=>{let L=k.get(t);if(!L)throw Error(`Draft ${t} was not found`);if(!m.confirm)throw Error("Pass --confirm <environment> to acknowledge the target and recurring cost drivers");let z=Kr({controlPlane:D.store,drafts:k,draftId:t,expectedVersion:L.version,confirmEnvironment:m.confirm});n(`Application queued: ${z.operation.id}`),e(`Cost drivers: ${z.plan.costDrivers.join(", ")}`)})}catch(k){bn(k)}}),R.command("app:import <site>","Import an existing cloud.config.ts site as a draft").option("--connection <id>","Git connection ID for repository-backed sites").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{try{await kr(async({config:k,controlPlane:D,sources:L,drafts:z})=>{let Y=k.sites?.[t];if(!Y)throw Error(`Site ${t} was not found`);let X=D.environments.get(m.env??"production");if(!X)throw Error(`Environment ${m.env} was not found`);let ne;if(Y.repository?.url){if(!m.connection)throw Error("Repository-backed imports require --connection <id>");let ae=L.getConnection(m.connection);if(!ae)throw Error("Git connection was not found");let be=Oc(Y.repository.url),Ae=L.listRepositories(ae.id).find((le)=>le.fullName.toLowerCase()===be.fullName.toLowerCase());if(!Ae)throw Error(`Repository ${be.fullName} is not synchronized`);ne={kind:"git",connectionId:ae.id,repositoryId:Ae.id,repositoryFullName:Ae.fullName,ref:Y.repository.branch??"main"}}let re=of({site:Y,siteSlug:t,projectId:D.project.id,environmentId:X.id,source:ne}),ie=z.create({organizationId:D.organization.id,projectId:D.project.id,name:`Import ${t}`,draft:re,step:"review"});n(`Imported draft: ${ie.id}`)})}catch(k){bn(k)}}),R.command("app:artifact:add <file>","Inspect and store a bounded ZIP or TAR artifact").action(async(t)=>{try{await kr(async({artifacts:m,controlPlane:k})=>{let D=na(t),L=m.create({organizationId:k.organization.id,projectId:k.project.id,filename:D.split("/").pop(),bytes:Wd(D)});n(`Artifact stored: ${L.id} · ${L.entryCount} entries · ${L.sha256}`)})}catch(m){bn(m)}}),R.command("app:registry:add <host>","Create an encrypted private-registry connection").option("--name <name>","Connection name",{default:"Container registry"}).option("--provider <provider>","docker_hub, ghcr, ecr, gcr, or generic",{default:"generic"}).option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).action(async(t,m)=>{try{await kr(async({registries:k,controlPlane:D})=>{let L={username:process.env[m.usernameEnv],password:process.env[m.passwordEnv],token:process.env[m.tokenEnv]},z=k.create({organizationId:D.organization.id,provider:m.provider,name:m.name,host:t,credential:Object.values(L).some(Boolean)?L:void 0});n(`Registry connection created: ${z.id}`)})}catch(k){bn(k)}}),R.command("app:registries","List safe registry connection metadata").action(async()=>{try{await kr(async({registries:t,controlPlane:m})=>c(["ID","Name","Provider","Host","Status"],t.list(m.organization.id).map((k)=>[k.id,k.name,k.provider,k.host,k.status])))}catch(t){bn(t)}}),R.command("app:registry:test <id>","Test registry and optional image pull access").option("--image <image>","Image reference").action(async(t,m)=>{try{await kr(async({registries:k})=>{let D=await k.test(t,{image:m.image});e(`${D.status}: ${D.healthMessage}`)})}catch(k){bn(k)}}),R.command("app:registry:rotate <id>","Rotate write-only registry credentials").option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).option("--expires <date>","Credential expiry timestamp").action(async(t,m)=>{try{await kr(async({registries:k})=>{let D={username:process.env[m.usernameEnv],password:process.env[m.passwordEnv],token:process.env[m.tokenEnv]};if(!Object.values(D).some(Boolean))throw Error("Set at least one configured registry credential environment variable");let L=k.rotate(t,D,{expiresAt:m.expires?new Date(m.expires).toISOString():void 0});n(`Registry credential rotated: ${L.id} · ${L.status}`)})}catch(k){bn(k)}}),R.command("app:registry:disconnect <id>","Disconnect and erase registry credentials").option("--yes","Skip confirmation").action(async(t,m)=>{try{await kr(async({registries:k,drafts:D,controlPlane:L})=>{let z=k.get(t);if(!z)throw Error(`Registry ${t} was not found`);let Y=D.list(L.project.id).filter((X)=>X.input.source.kind==="image"&&X.input.source.registryConnectionId===t||X.input.build.kind==="prebuilt_image"&&X.input.build.registryConnectionId===t);if(e(`${Y.length} application draft${Y.length===1?"":"s"} reference this registry.`),!m.yes&&!await d(`Disconnect ${z.name}?`,!1))return;k.disconnect(t),n(`Disconnected ${z.name}`)})}catch(k){bn(k)}})}import{existsSync as sf,readFileSync as af,writeFileSync as cf}from"node:fs";function jc(R,t){r(`'${R}' is not implemented yet.`),e(t),process.exitCode=1}function lf(R){let t={};for(let m of R.split(`
|
|
357
|
+
Wrote ${le}`)}}),R.command("cost:cache:clear","Wipe the local Cost Explorer response cache").option("--all","Wipe entries for every profile (default: just the current profile)").action(async(t)=>{let m=t?.all?void 0:t?.profile,k=hc(m),D=fc(m);e(`Cleared ${D.deletedFiles} cached response${D.deletedFiles===1?"":"s"} for ${D.scope}`),e(`(${k})`)}),R.command("cost","Show current month-to-date AWS spend and projected total").option("--env <environment>","Environment (production, staging, development)").option("--no-cache","Skip the local response cache").action(async(t)=>{let m=Ld(),k=new We(t?.profile,{useCache:t?.cache!==!1});i(`AWS Cost — ${m.label}${t?.profile?` (profile: ${t.profile})`:""}`);let D=new s("Querying month-to-date AWS spend...");D.start();try{let[L,z]=await Promise.all([k.getCostByService({start:m.start,end:m.end,granularity:"DAILY"}),k.getCostByService({start:m.previous.start,end:m.previous.end,granularity:"MONTHLY"})]);D.stop();let X=Ac(L,z).map((ae)=>{let be=kc(ae.amount,m.daysElapsed,m.daysInMonth);return[ae.service,fn(ae.amount),fn(be),fn(ae.previousAmount),ea(bs(be,ae.previousAmount))]});c(["Service","MTD","Projected",m.previous.label,"Change"],X);let ne=L.reduce((ae,be)=>ae+be.amount,0),re=z.reduce((ae,be)=>ae+be.amount,0),ie=kc(ne,m.daysElapsed,m.daysInMonth);e(`
|
|
358
|
+
MTD: ${fn(ne)} through day ${m.daysElapsed} of ${m.daysInMonth}`),e(`Projected: ${fn(ie)} (${ea(bs(ie,re))} vs ${m.previous.label})`)}catch(L){D.stop(),Pc(L)}}),R.command("cost:breakdown","Cost breakdown by service").option("--env <environment>","Environment (production, staging, development)").option("--days <days>","Number of days to analyze",{default:"30"}).option("--no-cache","Skip the local response cache").action(async(t)=>{let m=Number.parseInt(t?.days??"30",10),k;try{k=Fo(m)}catch(z){r(z instanceof Error?z.message:String(z));return}let D=new We(t?.profile,{useCache:t?.cache!==!1});i(`AWS Cost Breakdown — ${k.current.label}${t?.profile?` (profile: ${t.profile})`:""}`);let L=new s("Comparing AWS service spend...");L.start();try{let[z,Y]=await Promise.all([D.getCostByService({start:k.current.start,end:k.current.end,granularity:"DAILY"}),D.getCostByService({start:k.previous.start,end:k.previous.end,granularity:"DAILY"})]);L.stop();let X=Ac(z,Y);c(["Service",`Last ${m}d`,`Previous ${m}d`,"Trend"],X.map((ie)=>[ie.service,fn(ie.amount),fn(ie.previousAmount),ea(ie.changePercent)]));let ne=z.reduce((ie,ae)=>ie+ae.amount,0),re=Y.reduce((ie,ae)=>ie+ae.amount,0);e(`
|
|
359
|
+
Total: ${fn(ne)} (${ea(bs(ne,re))})`)}catch(z){L.stop(),Pc(z)}}),R.command("cost:egress","Rank AWS data-transfer usage types by cost").option("--days <days>","Number of days to analyze",{default:"30"}).option("--no-cache","Skip the local response cache").action(async(t)=>{let m=Number.parseInt(t?.days??"30",10),k;try{k=Fo(m).current}catch(z){r(z instanceof Error?z.message:String(z));return}let D=new We(t?.profile,{useCache:t?.cache!==!1});i(`AWS Egress Cost — ${k.label}${t?.profile?` (profile: ${t.profile})`:""}`);let L=new s("Querying data-transfer usage types...");L.start();try{let z=Ys(await D.getCostByDimension({start:k.start,end:k.end,dimension:"USAGE_TYPE",granularity:"DAILY"}));if(L.stop(),z.length===0){e("No billed data-transfer usage types were found in this period.");return}c(["Usage type","Cost"],z.map((Y)=>[Y.key,fn(Y.amount)])),e(`
|
|
360
|
+
Total ranked egress: ${fn(z.reduce((Y,X)=>Y+X.amount,0))}`),e("Usage types classify NAT, internet, inter-AZ, and inter-region transfer; destination correlation requires VPC Flow Logs.")}catch(z){L.stop(),Pc(z)}}),R.command("resources","List all resources").option("--env <environment>","Environment (production, staging, development)").option("--type <type>","Resource type (ec2, rds, s3, lambda, etc.)").option("--region <region>","AWS region for regional resources").action(async(t)=>{i(`AWS Resources${t?.profile?` (profile: ${t.profile})`:""}`);let m=new s("Discovering tagged and untagged resources...");m.start();let k=new ws(t?.profile,t?.region);try{let D=await k.discover({type:t?.type});if(m.stop(),D.resources.length===0)e("No matching resources were discovered.");else c(["Type","Name","Region","State","Monthly cost"],D.resources.map((L)=>[`${L.service}:${L.type}`,L.name,L.region??"global",L.state??"unknown","— (requires CUR resource IDs)"])),e(`
|
|
361
|
+
${D.resources.length} resource${D.resources.length===1?"":"s"} discovered.`);for(let L of D.warnings)a(L)}catch(D){m.stop(),r(`Resource discovery failed: ${D instanceof Error?D.message:String(D)}`)}}),R.command("resources:unused","Find unused resources").option("--env <environment>","Environment (production, staging, development)").option("--type <type>","Limit analysis to one resource type").option("--region <region>","AWS region for regional resources").action(async(t)=>{i(`Unused AWS Resources${t?.profile?` (profile: ${t.profile})`:""}`);let m=new s("Evaluating inventory and CloudWatch idle signals...");m.start();try{let k=await new Zs(t?.profile,t?.region).unused({type:t?.type});if(m.stop(),k.findings.length===0)e("No resources met the conservative unused-resource thresholds.");else{c(["Resource","Signal","Recommendation","Monthly savings"],k.findings.map((L)=>[`${L.resource.service}:${L.resource.type}/${L.resource.name}`,L.signal,L.recommendation,L.monthlySavings==null?"—":fn(L.monthlySavings)]));let D=k.findings.reduce((L,z)=>L+(z.monthlySavings??0),0);e(`
|
|
362
|
+
Known monthly savings: ${k.savingsAvailable?fn(D):"unavailable (enable CUR resource IDs)"}`)}for(let D of k.inventory.warnings)a(D)}catch(k){m.stop(),r(`Unused-resource analysis failed: ${k instanceof Error?k.message:String(k)}`)}}),R.command("optimize","Suggest cost optimizations").option("--env <environment>","Environment (production, staging, development)").option("--type <type>","Limit analysis to one resource type").option("--region <region>","AWS region for regional resources").option("--include-commitments","Include estimated RI and Savings Plan candidates").action(async(t)=>{i(`AWS Cost Optimizations${t?.profile?` (profile: ${t.profile})`:""}`);let m=new s("Combining inventory, CloudWatch signals, and Cost Explorer line items...");m.start();try{let k=await new Zs(t?.profile,t?.region).optimize({type:t?.type,includeCommitments:t?.includeCommitments});if(m.stop(),k.recommendations.length===0)e("No resources met the conservative optimization thresholds.");else{c(["Category","Resource","Current","Projected","Savings","Source signal","Recommendation"],k.recommendations.map((L)=>[L.category,`${L.resource.service}:${L.resource.type}/${L.resource.name}`,L.currentCost==null?"—":fn(L.currentCost),L.projectedCost==null?"—":fn(L.projectedCost),L.monthlySavings==null?"—":fn(L.monthlySavings),L.sourceSignal,L.recommendation]));let D=k.recommendations.reduce((L,z)=>L+(z.monthlySavings??0),0);e(`
|
|
363
|
+
Estimated monthly savings: ${k.savingsAvailable?fn(D):"unavailable (enable CUR resource IDs)"}`)}for(let D of k.warnings)a(D);e("Estimates are directional; validate workload peaks and current AWS pricing before applying changes.")}catch(k){m.stop(),r(`Optimization analysis failed: ${k instanceof Error?k.message:String(k)}`)}})}async function Bo(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=y(process.cwd(),t);return{config:t,env:m,controlPlane:k,controls:new ir(k.store)}}function _o(R){r(R instanceof Error?R.message:String(R)),process.exitCode=1}function jd(R){let t=R?.infrastructure?.compute??{};return{ddos:t.ddos===!1?void 0:typeof t.ddos==="object"?t.ddos:{ports:[80,443]},waf:t.waf===!1?void 0:typeof t.waf==="object"?t.waf:{mode:"detection"}}}function Dc(R){R.command("protect:status","Show the current edge-protection posture").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Bo(t.env),k=m.controls.current(),D=Ji(k);if(t.json){e(JSON.stringify({posture:D,controls:m.controls.raw()},null,2));return}if(i("Edge protection"),D.mitigationPaused||D.attackMode)a(D.summary);else n(D.summary);let L=jd(m.config);if(e(`L3/L4 mitigation: ${L.ddos?"enabled":"disabled in config"}`),e(`WAF: ${L.waf?`enabled (${L.waf.mode??"detection"})`:"disabled in config"}`),e(`Allowlist: ${k.ipRules.allow.length} entry(s)`),e(`Blocklist: ${k.ipRules.block.length} entry(s)`),k.ipRules.allow.length>0||k.ipRules.block.length>0)c(["List","CIDR"],[...k.ipRules.allow.map((z)=>["allow",z]),...k.ipRules.block.map((z)=>["block",z])])}catch(m){_o(m)}}),R.command("protect:attack-mode <state>","Challenge every visitor for a bounded window (on|off)").option("--hours <hours>",`How long before it lifts itself (default ${xn}, max ${nr})`).option("--reason <reason>","Why, for the record").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Bo(m.env);if(t==="off"){n(k.controls.disableAttackMode()?"Attack mode is off.":"Attack mode was not on.");return}if(t!=="on")throw Error("State must be 'on' or 'off'.");let D=k.controls.enableAttackMode({hours:m.hours==null?void 0:Number(m.hours),reason:m.reason??"Enabled from the CLI."});a("Attack mode is ON. Every visitor is challenged, including legitimate ones."),e(`It lifts itself at ${D.expiresAt} — you do not need to remember.`)}catch(k){_o(k)}}),R.command("protect:pause <state>","Pause or resume automatic mitigation (on|off)").option("--hours <hours>",`How long (max ${nr})`).option("--reason <reason>","Why — required when pausing").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Bo(m.env);if(t==="off"){n(k.controls.resumeMitigations()?"Automatic mitigation resumed.":"Mitigation was not paused.");return}if(t!=="on")throw Error("State must be 'on' or 'off'.");if(!m.reason)throw Error("--reason is required: you are liable for the traffic this admits.");let D=k.controls.pauseMitigations({hours:m.hours==null?void 0:Number(m.hours),reason:m.reason});a("Automatic mitigation is PAUSED. All traffic is served, and billed, including attacks."),e(`It resumes automatically at ${D.expiresAt}.`)}catch(k){_o(k)}}),R.command("protect:allow <cidr>","Never rate-limit or block this IP or CIDR").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Bo(m.env),D=m.remove?k.controls.removeIpRule("allow",t):k.controls.addIpRule("allow",t);n(`Allowlist now has ${D.allow.length} entry(s).`),e("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(k){_o(k)}}),R.command("protect:block <cidr>","Drop traffic from this IP or CIDR at the kernel").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Bo(m.env),D=m.remove?k.controls.removeIpRule("block",t):k.controls.addIpRule("block",t);n(`Blocklist now has ${D.block.length} entry(s).`),e("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(k){_o(k)}}),R.command("protect:apply","Print the ruleset the current controls produce").option("--env <environment>","Target environment").option("--waf","Print the WAF configuration instead of the firewall script").action(async(t)=>{try{let m=await Bo(t.env),k=jd(m.config);if(t.waf){if(!k.waf)throw Error("The WAF is disabled in this configuration.");let D=Si(k.waf);for(let L of D.warnings)a(L);e(D.config);return}if(!k.ddos)throw Error("L3/L4 mitigation is disabled in this configuration.");e(Ir(_n(k.ddos,m.controls.current())))}catch(m){_o(m)}})}async function Nn(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=y(process.cwd(),t),D=k.environments.get(m),L=new Rr(k.store),z=new St(k.store,{encryptionKey:_(process.cwd())});return{config:t,env:m,controlPlane:k,environmentRecord:D,store:L,alerts:z,service:new vt(L),gate:new Vt(k.store),organizationId:k.organization.id,projectId:k.project.id}}function ta(R){if(R==null)return;let t=String(R).trim().replace(/[$,\s]/g,"");if(!t)return;if(/^\d+c$/i.test(t))return Number.parseInt(t,10);let m=Number(t);if(!Number.isFinite(m)||m<0)throw Error(`Invalid amount: ${R}`);return Math.round(m*100)}function Bd(R){if(!R)return;let t=R.split(",").map((m)=>m.trim()).filter(Boolean);for(let m of t)if(!fo.includes(m))throw Error(`Unknown enforcement action '${m}'. Valid: ${fo.join(", ")}`);return t}function _d(R){if(R==null)return"monthly";if(R==="daily"||R==="weekly"||R==="monthly")return R;throw Error(`Unknown period '${R}'. Valid: daily, weekly, monthly`)}function Jp(R){return mn[R]?.label??R}function Yp(R,t=24){let m=Math.max(0,Math.min(t,Math.round(R/100*t)));return`${"█".repeat(m)}${"░".repeat(t-m)}`}function Fn(R){r(R instanceof Error?R.message:String(R)),process.exitCode=1}function Nc(R){R.command("usage","Show current usage, cost, and remaining budget headroom").option("--env <environment>","Target environment").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Nn(t.env),k=m.service.usageReport({organizationId:m.organizationId,projectId:m.projectId,period:_d(t.period),timezone:t.timezone});if(t.json){e(JSON.stringify(k,null,2));return}if(i(`Usage — ${k.window.label}`),e(`Total: ${ee(k.totalCents,k.currency)}`),k.byMeter.length===0)e("No metered usage in this window yet.");else c(["Meter","Provider","Quantity","Cost"],k.byMeter.map((D)=>[Jp(D.meter),D.provider||"-",D.quantity.toLocaleString(void 0,{maximumFractionDigits:3}),ee(D.costCents,k.currency)]));if(k.budgets.length===0){a("No budgets are configured. Nothing is capping this project."),e("Create one with: cloud budget:create --name 'Monthly' --hard 200");return}if(i("Budgets"),c(["Name","Period","Spent","Limit","Used","Projected","Time to cap","State"],k.budgets.map((D)=>[D.name+(D.dryRun?" (dry run)":""),D.period,ee(D.spentCents,k.currency),D.limitCents==null?"-":ee(D.limitCents,k.currency),`${Yp(D.usedPercent)} ${D.usedPercent.toFixed(0)}%`,`${ee(D.projectedCents,k.currency)} (${(D.projectionConfidence*100).toFixed(0)}% conf)`,D.timeToCap||"-",D.level])),k.enforcement.strongestAction)a(`Enforcement in force: ${k.enforcement.strongestAction}`)}catch(m){Fn(m)}}),R.command("budget:list","List budgets governing this project").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Nn(t.env),k=m.store.listBudgets({organizationId:m.organizationId,projectId:m.projectId});if(t.json){e(JSON.stringify(k,null,2));return}if(k.length===0){a("No budgets configured.");return}c(["ID","Name","Scope","Period","Soft","Hard","Mode","Enabled"],k.map((D)=>[D.id.slice(0,8),D.name,D.environmentId?"environment":D.projectId?"project":"organization",D.period,D.softLimitCents==null?"-":ee(D.softLimitCents,D.currency),D.hardLimitCents==null?"-":ee(D.hardLimitCents,D.currency),D.dryRun?"dry run":"enforcing",D.enabled?"yes":"no"]))}catch(m){Fn(m)}}),R.command("budget:create","Create a spend budget").option("--name <name>","Budget name").option("--soft <amount>","Soft limit in dollars (warn only)").option("--hard <amount>","Hard limit in dollars (enforces)").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--scope <scope>","organization, project, or environment (default project)").option("--actions <list>","Comma-separated actions to run at 100% (default notify,block_builds,block_deployments)").option("--grace <seconds>","Seconds a breach must persist before enforcing").option("--dry-run","Evaluate and report but never enforce").option("--env <environment>","Target environment").action(async(t)=>{try{if(!t.name)throw Error("--name is required.");let m=await Nn(t.env),k=t.scope??"project";if(!["organization","project","environment"].includes(k))throw Error(`Unknown scope '${k}'. Valid: organization, project, environment`);let D=Bd(t.actions),L=m.store.createBudget({organizationId:m.organizationId,projectId:k==="organization"?void 0:m.projectId,environmentId:k==="environment"?m.environmentRecord?.id:void 0,name:t.name,period:_d(t.period),timezone:t.timezone,softLimitCents:ta(t.soft),hardLimitCents:ta(t.hard),graceSeconds:t.grace==null?void 0:Number(t.grace),thresholds:D?[{atPercent:80,actions:["notify"]},{atPercent:100,actions:D}]:[...yo],dryRun:t.dryRun===!0});n(`Created budget '${L.name}' (${L.id}).`),e(`Mode: ${L.dryRun?"dry run — reports only, enforces nothing":"enforcing"}`),e(`Ladder: ${L.thresholds.map((z)=>`${z.atPercent}% → ${z.actions.join("+")}`).join(", ")}`)}catch(m){Fn(m)}}),R.command("budget:update <budgetId>","Update a budget").option("--soft <amount>","Soft limit in dollars").option("--hard <amount>","Hard limit in dollars").option("--enable","Enable the budget").option("--disable","Disable the budget").option("--enforce","Leave dry run and start enforcing").option("--dry-run","Return to dry run").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Nn(m.env),D=k.store.getBudget(t);if(!D)throw Error(`Budget '${t}' was not found.`);let L=k.store.updateBudget(t,{softLimitCents:ta(m.soft)??D.softLimitCents,hardLimitCents:ta(m.hard)??D.hardLimitCents,enabled:m.disable?!1:m.enable?!0:D.enabled,dryRun:m.enforce?!1:m.dryRun?!0:D.dryRun});n(`Updated budget '${L.name}'.`),e(`Mode: ${L.dryRun?"dry run":"enforcing"} · Enabled: ${L.enabled?"yes":"no"}`)}catch(k){Fn(k)}}),R.command("budget:delete <budgetId>","Delete a budget and lift anything it was enforcing").option("--env <environment>","Target environment").action(async(t,m)=>{try{let k=await Nn(m.env),D=k.store.getBudget(t);if(!D)throw Error(`Budget '${t}' was not found.`);let L=k.gate.closeBudget(t);if(k.store.deleteBudget(t),n(`Deleted budget '${D.name}'.`),L>0)e(`Lifted ${L} enforcement action(s) it had in force.`)}catch(k){Fn(k)}}),R.command("spend:check","Run one spend cycle now and report what it would do").option("--env <environment>","Target environment").option("--apply","Actually apply enforcement (default is a preview)").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Nn(t.env),k=new Ii({controlPlane:m.controlPlane.store,store:m.store,alerts:t.apply?m.alerts:void 0});if(!t.apply){let z=m.store.budgetsForScope(m.organizationId,m.projectId).map((Y)=>m.service.status(Y));if(t.json){e(JSON.stringify(z,null,2));return}if(i("Spend check (preview — nothing was applied)"),z.length===0){a("No budgets govern this project.");return}c(["Budget","Used","Projected","Level","Would apply"],z.map((Y)=>[Y.budget.name,`${Y.decision.usedPercent.toFixed(1)}%`,`${Y.decision.projectedPercent.toFixed(1)}%`,Y.decision.level,Y.decision.actions.join(", ")||"-"])),e(`
|
|
364
|
+
Run with --apply to enforce.`);return}let D=await k.run({organizationId:m.organizationId,projectId:m.projectId,environmentId:m.environmentRecord?.id,environmentKind:m.environmentRecord?.kind});if(t.json){e(JSON.stringify(D,null,2));return}if(i("Spend cycle"),e(`Evaluated ${D.decisions.length} budget(s) in ${D.durationMs}ms.`),D.applied.length>0)a(`Applied: ${D.applied.join(", ")}`);if(D.released.length>0)n(`Released: ${D.released.join(", ")}`);if(D.withheld.length>0)a(`Withheld pending approval: ${D.withheld.join(", ")}`);if(D.anomalies.length>0)a(`${D.anomalies.length} anomaly(ies) detected.`);e(`Notifications queued: ${D.notificationsSent}`);for(let L of D.warnings)a(L);if(D.applied.length===0&&D.released.length===0)n("Nothing to change.")}catch(m){Fn(m)}}),R.command("spend:work","Run the spend loop continuously: meter, evaluate, enforce, notify").option("--env <environment>","Target environment").option("--interval <seconds>",`Seconds between cycles (default ${Pi})`).option("--once","Run a single cycle and exit").action(async(t)=>{try{let m=await Nn(t.env),k=new Ii({controlPlane:m.controlPlane.store,store:m.store,alerts:m.alerts}),D={organizationId:m.organizationId,projectId:m.projectId,environmentId:m.environmentRecord?.id,environmentKind:m.environmentRecord?.kind};if(t.once){let ne=await k.run(D);n(`Cycle finished in ${ne.durationMs}ms.`);for(let re of ne.warnings)a(re);return}let L=Math.max(10,Number(t.interval??Pi)),z=new Co(m.controlPlane.store,{owner:`cli:${process.pid}`});i(`Spend worker (every ${L}s)`),e("Press Ctrl+C to stop.");let Y=_s(k,{intervalSeconds:L,lease:z,immediate:!0,onResult:(ne)=>{let re=ne.flatMap((be)=>be.applied),ie=ne.flatMap((be)=>be.released),ae=ne.flatMap((be)=>be.warnings);if(re.length>0)a(`Applied: ${re.join(", ")}`);if(ie.length>0)n(`Released: ${ie.join(", ")}`);for(let be of ae)a(be)},onSkip:(ne)=>e(`Skipped: ${ne} holds the loop lease.`),onError:(ne)=>r(ne instanceof Error?ne.message:String(ne))}),X=()=>{Y(),e(`
|
|
365
|
+
Spend worker stopped; the loop lease was released.`),process.exit(0)};process.on("SIGINT",X),process.on("SIGTERM",X),await new Promise(()=>{})}catch(m){Fn(m)}}),R.command("spend:status","Show enforcement currently in force").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Nn(t.env),k=m.gate.listUnder({organizationId:m.organizationId});if(t.json){e(JSON.stringify(k,null,2));return}if(k.length===0){n("No spend enforcement is in force.");return}c(["Action","Budget","Scope","Mode","Since","Reason"],k.map((D)=>[D.action,D.budgetId.slice(0,8),D.environmentId?"environment":D.projectId?"project":"organization",D.simulated?"dry run":"enforcing",D.appliedAt,D.reason]))}catch(m){Fn(m)}}),R.command("spend:release <budgetId> <action>","Manually lift one enforcement action").option("--env <environment>","Target environment").action(async(t,m,k)=>{try{let D=await Nn(k.env),L=Bd(m)?.[0];if(!L)throw Error("An action is required.");if(!D.gate.close(t,L))throw Error(`${L} was not in force for budget ${t}.`);n(`Lifted ${L} for budget ${t}.`),a("The next spend cycle will re-apply this if spend is still over the threshold.")}catch(D){Fn(D)}}),R.command("anomaly:config","Tune anomaly detection for a signal").option("--signal <signal>","Signal to configure (default cost)").option("--sensitivity <level>","low, medium, or high").option("--season <points>","Points per season: 24 for daily, 168 for weekly").option("--min-delta <amount>","Absolute change below which nothing is reported, in the signal's units").option("--severity <severity>","info, warning, or critical").option("--disable","Stop detecting this signal").option("--enable","Resume detecting this signal").option("--list","List configured signals instead of changing one").option("--signals","List every detectable signal and its data rules").option("--env <environment>","Target environment").action(async(t)=>{try{if(t.signals){c(["Signal","Source","Empty hour","Min samples","Unit"],jt.map((z)=>[z.key,z.source,z.gapPolicy==="zero"?"counts as zero":"ignored",String(z.minSamples),z.unit]));return}let m=await Nn(t.env),k=m.service.anomalyConfigs;if(t.list){let z=k.list({organizationId:m.organizationId,projectId:m.projectId});if(z.length===0){e("No signals are configured; every one uses its shipped defaults.");return}c(["Signal","Enabled","Sensitivity","Season","Min delta","Severity"],z.map((Y)=>[Y.signal,Y.enabled?"yes":"no",Y.sensitivity,String(Y.seasonLength),String(Y.minAbsoluteDelta),Y.severity]));return}let D=t.sensitivity;if(D&&!["low","medium","high"].includes(D))throw Error("--sensitivity must be low, medium, or high.");let L=k.upsert({organizationId:m.organizationId,projectId:m.projectId,signal:t.signal??"cost",sensitivity:D,seasonLength:t.season==null?void 0:Number(t.season),minAbsoluteDelta:t.minDelta==null?void 0:Number(t.minDelta),severity:t.severity,enabled:t.disable?!1:t.enable?!0:void 0});n(`Anomaly detection for '${L.signal}' updated.`),e(`${L.enabled?"Enabled":"Disabled"} · ${L.sensitivity} sensitivity · season ${L.seasonLength} · floor ${L.minAbsoluteDelta} · ${L.severity}`)}catch(m){Fn(m)}}),R.command("anomaly:silence","Skip detection for a signal, route, or status code").option("--signal <signal>","Signal to silence").option("--route <pattern>","Route glob, e.g. /webhooks/**").option("--status <code>","HTTP status code").option("--reason <reason>","Why — required").option("--until <timestamp>","ISO timestamp the silence expires").option("--list","List active silences").option("--remove <id>","Remove a silence").option("--env <environment>","Target environment").action(async(t)=>{try{let m=await Nn(t.env),k=m.service.anomalyConfigs;if(t.remove){n(k.removeSilence(t.remove)?"Silence removed.":"That silence was not found.");return}if(t.list){let L=k.listSilences({organizationId:m.organizationId,projectId:m.projectId});if(L.length===0){n("No active silences.");return}c(["ID","Signal","Route","Status","Expires","Reason"],L.map((z)=>[z.id.slice(0,8),z.signal??"-",z.routePattern??"-",z.statusCode==null?"-":String(z.statusCode),z.expiresAt??"never",z.reason]));return}if(!t.reason)throw Error("--reason is required: someone will ask why this stopped alerting.");let D=k.silence({organizationId:m.organizationId,projectId:m.projectId,signal:t.signal,routePattern:t.route,statusCode:t.status==null?void 0:Number(t.status),reason:t.reason,expiresAt:t.until});if(n(`Silenced (${D.id.slice(0,8)}).`),!D.expiresAt)a("This silence never expires. Pass --until to bound it, or it will outlive the reason for it.")}catch(m){Fn(m)}}),R.command("spend:anomalies","List detected spend anomalies").option("--env <environment>","Target environment").option("--unacknowledged","Only anomalies nobody has acknowledged").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Nn(t.env),k=m.store.listAnomalies({organizationId:m.organizationId,projectId:m.projectId,unacknowledgedOnly:t.unacknowledged===!0});if(t.json){e(JSON.stringify(k,null,2));return}if(k.length===0){n("No anomalies detected.");return}c(["Hour","Signal","Direction","Observed","Expected","Score","Severity"],k.map((D)=>[D.bucketStart,D.signal,D.direction,ee(D.observed),ee(D.expected),D.score.toFixed(1),D.severity]))}catch(m){Fn(m)}})}import{readFileSync as Zp}from"node:fs";var Qp={github:"GITHUB_TOKEN",gitlab:"GITLAB_TOKEN",bitbucket:"BITBUCKET_TOKEN",gitea:"GITEA_TOKEN",generic_https:"GIT_TOKEN"},Xp={github:"https://github.com",gitlab:"https://gitlab.com",bitbucket:"https://bitbucket.org",gitea:"https://gitea.com"};function Oc(R,t={}){if(t.provider&&!["github","gitlab","bitbucket","gitea","generic_https","generic_ssh"].includes(t.provider))throw Error(`Unsupported Git provider: ${t.provider}`);let m=t.cloneUrl??(/^(?:https?:\/\/|ssh:\/\/|git@)/.test(R)?R:void 0),k="",D=m??R;if(m?.startsWith("git@")){let Y=/^git@([^:]+):(.+)$/.exec(m);if(!Y)throw Error("SSH repository URL must use git@host:owner/name.git");k=Y[1],D=Y[2]}else if(m){let Y=new URL(m);k=Y.hostname,D=Y.pathname}if(D=D.replace(/^\/+|\/+$/g,"").replace(/\.git$/,""),!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(D))throw Error("Repository must use owner/name or a supported clone URL");let L=t.provider??(k.includes("github")?"github":k.includes("gitlab")?"gitlab":k.includes("bitbucket")?"bitbucket":k.includes("gitea")?"gitea":m?.startsWith("git@")||m?.startsWith("ssh://")?"generic_ssh":m?"generic_https":"github"),z=t.host??Xp[L]??(k?`https://${k}`:"");if(!z)throw Error("A provider host is required");return{provider:L,host:z,fullName:D,cloneUrl:m}}async function ef(){let R=await Ne(),t=y(process.cwd(),R);return{config:R,controlPlane:t,sources:new cr(t.store,{encryptionKey:_(process.cwd())})}}async function Ri(R){let t=await ef();try{return await R(t)}finally{t.controlPlane.store.close()}}function $s(R,t,m){let k=R.listConnections(t);if(!m)return k.length===1?k[0]:void 0;return k.find((D)=>D.id===m||D.name.toLowerCase()===m.toLowerCase())}function Md(R,t){if(!R)throw Error(`${t} file is required`);return Zp(R,"utf8")}async function tf(R,t,m,k){if(["github","gitlab","bitbucket","gitea"].includes(t.provider)){let L=R.listRepositories(t.id).find((X)=>X.fullName.toLowerCase()===m.fullName.toLowerCase());if(L)return L;let Y=(await Kr(R,t.id,{search:m.fullName,maxPages:20})).find((X)=>X.fullName.toLowerCase()===m.fullName.toLowerCase());if(!Y)throw Error(`Repository ${m.fullName} is not granted to this connection`);return Y}let D=k.url??m.cloneUrl;if(!D)throw Error("Generic Git connections require --url <clone-url>");return R.upsertRepository({connectionId:t.id,providerRepositoryId:`manual:${m.fullName}`,fullName:m.fullName,cloneUrl:D,defaultBranch:k.branch??"main",visibility:"unknown",archived:!1,metadata:{source:"cli"}})}async function zd(R,t,m){let k=Oc(t,{provider:m.provider,host:m.host,cloneUrl:m.url}),D=$s(R.sources,R.controlPlane.organization.id,m.connection);if(!D){if(m.connection)throw Error(`Source connection ${m.connection} was not found`);let z=m.tokenEnv??Qp[k.provider],Y=z?process.env[z]:void 0;if(["github","gitlab","bitbucket","gitea"].includes(k.provider)&&!Y)throw Error(`Set ${z} or pass --token-env <name>; secrets are never accepted as command arguments`);let X=k.provider==="generic_ssh"?{publicKey:Md(m.publicKey,"Public key"),privateKey:Md(m.privateKey,"Private key"),hostKey:m.hostKey??""}:void 0;R.controlPlane.store.database.transaction(()=>{if(D=R.sources.createConnection({organizationId:R.controlPlane.organization.id,provider:k.provider,name:m.name??`${k.provider.replace("_"," ")} · ${new URL(k.host).host}`,host:k.host,owner:k.fullName.split("/")[0],authKind:k.provider==="generic_ssh"?"deploy_key":Y?"access_token":"none",credential:Y?{token:Y}:void 0}),X)R.sources.createDeployKey({connectionId:D.id,name:m.deployKey??`${k.fullName} deploy key`,...X,host:m.sshHost??new URL(k.host).hostname,actorId:void 0})})()}if(!D)throw Error("Source connection could not be created");if(D.status==="disconnected")throw Error("The selected source connection is disconnected");let L=await tf(R.sources,D,k,m);if(m.site){let z=R.controlPlane.environments.get("production")??[...R.controlPlane.environments.values()][0],Y=R.controlPlane.store.listResources(R.controlPlane.project.id,z?.id).find((ne)=>ne.kind==="application"&&ne.slug===m.site);if(!Y)throw Error(`Application ${m.site} was not found`);let X=D.provider==="generic_ssh"?R.sources.listDeployKeys(D.id)[0]:void 0;R.sources.createBinding({projectId:R.controlPlane.project.id,environmentId:z?.id,resourceId:Y.id,connectionId:D.id,repositoryId:L.id,repositoryFullName:L.fullName,defaultBranch:m.branch??L.defaultBranch,branchRule:m.tag?void 0:m.branch??L.defaultBranch,tagRule:m.tag,monorepoRoot:m.root??".",includePaths:m.include?.split(",").map((ne)=>ne.trim()).filter(Boolean),excludePaths:m.exclude?.split(",").map((ne)=>ne.trim()).filter(Boolean),submodules:m.submodules,cloneDepth:m.fullClone?void 0:20,deployKeyId:X?.id})}return{connection:D,repository:L}}function xi(R){r(R instanceof Error?R.message:"Git integration command failed")}function Lc(R){R.command("git:connections","List encrypted Git provider connections").action(async()=>{try{await Ri(async({sources:t,controlPlane:m})=>{let k=t.listConnections(m.organization.id);c(["ID","Name","Provider","Status","Credential","Last sync"],k.map((D)=>[D.id,D.name,D.provider,D.status,D.credentialConfigured?"encrypted":"none",D.lastSyncedAt??"-"]))})}catch(t){xi(t)}}),R.command("git:repositories","List repositories granted to a connection").option("--connection <id>","Connection ID or name").action(async(t)=>{try{await Ri(async({sources:m,controlPlane:k})=>{let D=$s(m,k.organization.id,t.connection);if(!D)throw Error("Choose a connection with --connection <id>");let L=m.listRepositories(D.id);c(["ID","Repository","Default branch","Visibility","Archived"],L.map((z)=>[z.id,z.fullName,z.defaultBranch,z.visibility,z.archived?"yes":"no"]))})}catch(m){xi(m)}}),R.command("git:add <repo>","Connect and optionally bind a Git repository").option("--branch <branch>","Default branch to deploy",{default:"main"}).option("--connection <id>","Reuse a connection ID or name").option("--provider <provider>","github, gitlab, bitbucket, gitea, generic_https, or generic_ssh").option("--host <url>","Provider API host").option("--name <name>","Connection name").option("--token-env <name>","Environment variable containing the access token").option("--url <clone-url>","Credential-free clone URL").option("--site <slug>","Bind the repository to an application").option("--root <path>","Monorepo root",{default:"."}).option("--include <globs>","Comma-separated watched paths").option("--exclude <globs>","Comma-separated ignored paths").option("--tag <glob>","Deploy matching tags instead of branch pushes").option("--full-clone","Use full history instead of a shallow clone").option("--submodules","Initialize repository submodules").option("--deploy-key <name>","Deploy-key display name").option("--public-key <path>","SSH public-key file").option("--private-key <path>","SSH private-key file").option("--host-key <key>","Pinned SSH host key").option("--ssh-host <host>","SSH clone host").option("--yes","Skip confirmation").action(async(t,m)=>{try{if(!m.yes&&!await d(`Connect ${t}?`,!0))return;let k=await Ri((D)=>zd(D,t,m));if(n(`Connected ${k.repository.fullName}`),e(`Connection: ${k.connection.name} (${k.connection.id})`),m.site)e(`Application binding: ${m.site}`)}catch(k){xi(k)}}),R.command("git:import","Import site.repository entries from cloud.config.ts").option("--connection <id>","Reuse a connection ID or name").option("--token-env <name>","Environment variable containing provider token").option("--yes","Skip confirmation").action(async(t)=>{try{await Ri(async(m)=>{let k=Object.entries(m.config.sites??{}).filter(([,D])=>D.repository?.url);if(!k.length)throw Error("No site.repository entries were found");if(!t.yes&&!await d(`Import ${k.length} repository configuration${k.length===1?"":"s"}?`,!0))return;for(let[D,L]of k){let z=L.repository,Y=z.provider==="custom"?void 0:z.provider;await zd(m,z.url,{connection:t.connection,tokenEnv:t.tokenEnv,site:D,provider:Y,branch:z.branch??"main",tag:z.strategy==="tag"?z.tag??z.tagPattern??"v*":void 0,url:z.url}),n(`Imported ${D} · ${z.url}`)}})}catch(m){xi(m)}}),R.command("git:branches <repo>","List branches or tags visible to a connection").option("--connection <id>","Connection ID or name").option("--tags","List tags instead of branches").option("--deploy-key <id>","SSH deploy-key ID").action(async(t,m)=>{try{await Ri(async({sources:k,controlPlane:D})=>{let L=$s(k,D.organization.id,m.connection);if(!L)throw Error("Choose a connection with --connection <id>");let z=k.listRepositories(L.id).find((X)=>X.id===t||X.fullName.toLowerCase()===t.toLowerCase());if(!z)throw Error(`Repository ${t} was not found`);let Y=await lr(k,{connectionId:L.id,repository:z.fullName,repositoryId:z.id,type:m.tags?"tags":"branches",deployKeyId:m.deployKey??k.listDeployKeys(L.id)[0]?.id});c([m.tags?"Tag":"Branch","Commit","Protected"],Y.refs.map((X)=>[X.name,X.commitSha,X.protected?"yes":"no"]))})}catch(k){xi(k)}}),R.command("git:deploy <branch>","Queue an idempotent deployment from a Git branch").option("--binding <id>","Source binding ID").option("--repo <owner/name>","Bound repository").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{try{await Ri(async({sources:k,controlPlane:D})=>{let L=D.environments.get(m.env??"production");if(!L)throw Error(`Environment ${m.env} was not found`);let z=k.listBindings({projectId:D.project.id,status:"active"}).find((re)=>(!m.binding||re.id===m.binding)&&(!m.repo||re.repositoryFullName.toLowerCase()===m.repo.toLowerCase())&&(!re.environmentId||re.environmentId===L.id));if(!z)throw Error("No active source binding matched; use --binding or --repo");let X=(await lr(k,{connectionId:z.connectionId,repository:z.repositoryFullName,repositoryId:z.repositoryId,type:"branches",deployKeyId:z.deployKeyId})).refs.find((re)=>re.name===t);if(!X)throw Error(`Branch ${t} was not found`);let ne=D.store.createOperation({projectId:z.projectId,environmentId:L.id,resourceId:z.resourceId,kind:"deploy.source",idempotencyKey:`cli-source:${z.id}:${X.commitSha}`,correlationId:`cli-source:${z.id}:${X.commitSha}`,input:{source:{connectionId:z.connectionId,bindingId:z.id,repository:z.repositoryFullName,commitSha:X.commitSha,branch:t,monorepoRoot:z.monorepoRoot,submodules:z.submodules,cloneDepth:z.cloneDepth??null}}});n(`Deployment queued: ${ne.id}`),e(`${z.repositoryFullName}@${t} · ${X.commitSha}`)})}catch(k){xi(k)}}),R.command("git:webhook:add <repo>","Create and reconcile a signed auto-deploy webhook").option("--connection <id>","Connection ID or name").option("--base-url <url>","Public HTTPS dashboard URL").action(async(t,m)=>{try{await Ri(async({sources:k,controlPlane:D})=>{let L=$s(k,D.organization.id,m.connection);if(!L)throw Error("Choose a connection with --connection <id>");let z=k.listRepositories(L.id).find((ie)=>ie.id===t||ie.fullName.toLowerCase()===t.toLowerCase());if(!z)throw Error(`Repository ${t} was not found`);let Y=m.baseUrl??process.env.TS_CLOUD_WEBHOOK_BASE_URL;if(!Y)throw Error("Pass --base-url or set TS_CLOUD_WEBHOOK_BASE_URL");let X=k.createWebhook({connectionId:L.id,repositoryId:z.id,repositoryFullName:z.fullName}),ne=Ot(Y,X.webhook),re=await dr(k,X.webhook.id,Y);n(`Webhook ${re.status}: ${ne}`),e("The signing secret is encrypted and is not printed.")})}catch(k){xi(k)}}),R.command("git:webhook:remove <webhook>","Disable and remove a provider webhook").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Ri(async({sources:k,controlPlane:D})=>{let L=k.listConnections(D.organization.id).flatMap((Y)=>k.listWebhooks(Y.id)).find((Y)=>Y.id===t||Y.repositoryFullName.toLowerCase()===t.toLowerCase());if(!L)throw Error(`Webhook ${t} was not found`);if(!m.yes&&!await d(`Remove webhook for ${L.repositoryFullName}?`,!0))return;let z=await Yi(k,L.id);n(`Webhook disabled: ${z.id}`)})}catch(k){xi(k)}}),R.command("git:disconnect <connection>","Preview and disconnect a Git provider connection").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Ri(async({sources:k,controlPlane:D})=>{let L=$s(k,D.organization.id,t);if(!L)throw Error(`Source connection ${t} was not found`);let z=k.listBindings({connectionId:L.id,status:"active"});e(`${z.length} active binding${z.length===1?"":"s"} will be disabled.`);for(let Y of z)e(` ${Y.repositoryFullName} · ${Y.branchRule??Y.defaultBranch} · ${Y.monorepoRoot}`);if(!m.yes&&!await d(`Disconnect ${L.name}?`,!1))return;k.disconnectConnection(L.id),n(`Disconnected ${L.name}`)})}catch(k){xi(k)}})}import{readFileSync as Wd,writeFileSync as nf}from"node:fs";import{resolve as na}from"node:path";async function rf(){let R=await Ne(),t=y(process.cwd(),R),m=_(process.cwd());return{config:R,controlPlane:t,drafts:new ri(t.store),artifacts:new ii(t.store,{cwd:process.cwd()}),registries:new ni(t.store,{encryptionKey:m}),sources:new cr(t.store,{encryptionKey:m})}}async function wr(R){let t=await rf();try{return await R(t)}finally{t.controlPlane.store.close()}}function Cn(R){r(R instanceof Error?R.message:"Application onboarding command failed")}function Ud(R){return JSON.parse(Wd(na(R),"utf8"))}function of(R){let t=R.site,m=["laravel","php","statamic","wordpress"].includes(t.type),k=["static","spa"].includes(t.type)||!t.start&&!m,D=k?{kind:"static",publishDirectory:t.root??t.webDirectory??"dist",buildCommand:t.build}:{kind:"server",runtime:m?"php":"bun",runtimeVersion:m?t.phpVersion:void 0,installCommand:m?"composer install --no-dev --prefer-dist --optimize-autoloader":void 0,buildCommand:t.build,startCommand:t.start??(m?"php -S 0.0.0.0:$PORT -t public":"bun run start")};return{schemaVersion:1,name:t.name??R.siteSlug,slug:R.siteSlug,projectId:R.projectId,environmentId:R.environmentId,source:R.source??{kind:"local",root:t.root??"."},build:D,runtime:{target:"server",architecture:t.architecture==="arm64"?"arm64":"x86_64",port:k?void 0:Number(t.port??3000),healthCheck:k?void 0:{protocol:"http",path:t.healthCheck?.path??"/health"}},domain:t.domain?{hostname:t.domain,tls:t.ssl?.provider!=="none"}:void 0}}function Fc(R){R.command("app:detect [root]","Detect application frameworks without executing project code").action(async(t=".")=>{try{let m=$t(ti(na(t)));c(["Framework","Strategy","Confidence","Evidence"],m.map((k)=>[k.framework,k.strategy,`${Math.round(k.confidence*100)}%`,k.evidence.map((D)=>D.path).join(", ")||"manual"]));for(let k of m)e(`${k.framework}: ${k.description}`)}catch(m){Cn(m)}}),R.command("app:plan <draft>","Validate and preview a secret-free application draft").option("--secrets <names>","Comma-separated configured secret names").action(async(t,m)=>{try{let k=de(Ud(t),m.secrets?.split(",").map((D)=>D.trim()).filter(Boolean));if(e(JSON.stringify(k,null,2)),!k.valid)Cn(Error("Plan is not deployable; resolve the reported issues and missing secrets."))}catch(k){Cn(k)}}),R.command("app:draft:save <draft>","Persist or update a resumable application draft").option("--id <id>","Existing draft ID").option("--version <version>","Expected draft version").option("--step <step>","Wizard step",{default:"source"}).option("--secrets <names>","Comma-separated configured secret names").action(async(t,m)=>{try{await wr(async({drafts:k,controlPlane:D})=>{let L=Ud(t),z=m.secrets?.split(",").map((X)=>X.trim()).filter(Boolean)??[],Y=m.id?k.update(m.id,Number(m.version),{draft:L,step:m.step,suppliedSecretNames:z}):k.create({organizationId:D.organization.id,projectId:D.project.id,name:L.name,draft:{...L,projectId:D.project.id},step:m.step,suppliedSecretNames:z});n(`Draft saved: ${Y.id} · v${Y.version} · ${Y.status}`)})}catch(k){Cn(k)}}),R.command("app:drafts","List resumable application drafts").action(async()=>{try{await wr(async({drafts:t,controlPlane:m})=>{let k=t.list(m.project.id);c(["ID","Application","Step","Status","Version","Updated"],k.map((D)=>[D.id,D.input.name,D.step,D.status,String(D.version),D.updatedAt]))})}catch(t){Cn(t)}}),R.command("app:export <draft>","Export a deterministic application manifest").option("--output <path>","Output path",{default:"application.manifest.json"}).action(async(t,m)=>{try{await wr(async({drafts:k})=>{let D=k.get(t);if(!D)throw Error(`Draft ${t} was not found`);let L=de(D.input,D.suppliedSecretNames),z=na(m.output??"application.manifest.json");nf(z,L.serializedManifest,{mode:384}),n(`Manifest exported: ${z}`)})}catch(k){Cn(k)}}),R.command("app:deploy <draft>","Confirm and queue a validated application plan").option("--confirm <environment>","Exact target environment slug").action(async(t,m)=>{try{await wr(async({drafts:k,controlPlane:D})=>{let L=k.get(t);if(!L)throw Error(`Draft ${t} was not found`);if(!m.confirm)throw Error("Pass --confirm <environment> to acknowledge the target and recurring cost drivers");let z=Jr({controlPlane:D.store,drafts:k,draftId:t,expectedVersion:L.version,confirmEnvironment:m.confirm});n(`Application queued: ${z.operation.id}`),e(`Cost drivers: ${z.plan.costDrivers.join(", ")}`)})}catch(k){Cn(k)}}),R.command("app:import <site>","Import an existing cloud.config.ts site as a draft").option("--connection <id>","Git connection ID for repository-backed sites").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{try{await wr(async({config:k,controlPlane:D,sources:L,drafts:z})=>{let Y=k.sites?.[t];if(!Y)throw Error(`Site ${t} was not found`);let X=D.environments.get(m.env??"production");if(!X)throw Error(`Environment ${m.env} was not found`);let ne;if(Y.repository?.url){if(!m.connection)throw Error("Repository-backed imports require --connection <id>");let ae=L.getConnection(m.connection);if(!ae)throw Error("Git connection was not found");let be=Oc(Y.repository.url),Ae=L.listRepositories(ae.id).find((le)=>le.fullName.toLowerCase()===be.fullName.toLowerCase());if(!Ae)throw Error(`Repository ${be.fullName} is not synchronized`);ne={kind:"git",connectionId:ae.id,repositoryId:Ae.id,repositoryFullName:Ae.fullName,ref:Y.repository.branch??"main"}}let re=of({site:Y,siteSlug:t,projectId:D.project.id,environmentId:X.id,source:ne}),ie=z.create({organizationId:D.organization.id,projectId:D.project.id,name:`Import ${t}`,draft:re,step:"review"});n(`Imported draft: ${ie.id}`)})}catch(k){Cn(k)}}),R.command("app:artifact:add <file>","Inspect and store a bounded ZIP or TAR artifact").action(async(t)=>{try{await wr(async({artifacts:m,controlPlane:k})=>{let D=na(t),L=m.create({organizationId:k.organization.id,projectId:k.project.id,filename:D.split("/").pop(),bytes:Wd(D)});n(`Artifact stored: ${L.id} · ${L.entryCount} entries · ${L.sha256}`)})}catch(m){Cn(m)}}),R.command("app:registry:add <host>","Create an encrypted private-registry connection").option("--name <name>","Connection name",{default:"Container registry"}).option("--provider <provider>","docker_hub, ghcr, ecr, gcr, or generic",{default:"generic"}).option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).action(async(t,m)=>{try{await wr(async({registries:k,controlPlane:D})=>{let L={username:process.env[m.usernameEnv],password:process.env[m.passwordEnv],token:process.env[m.tokenEnv]},z=k.create({organizationId:D.organization.id,provider:m.provider,name:m.name,host:t,credential:Object.values(L).some(Boolean)?L:void 0});n(`Registry connection created: ${z.id}`)})}catch(k){Cn(k)}}),R.command("app:registries","List safe registry connection metadata").action(async()=>{try{await wr(async({registries:t,controlPlane:m})=>c(["ID","Name","Provider","Host","Status"],t.list(m.organization.id).map((k)=>[k.id,k.name,k.provider,k.host,k.status])))}catch(t){Cn(t)}}),R.command("app:registry:test <id>","Test registry and optional image pull access").option("--image <image>","Image reference").action(async(t,m)=>{try{await wr(async({registries:k})=>{let D=await k.test(t,{image:m.image});e(`${D.status}: ${D.healthMessage}`)})}catch(k){Cn(k)}}),R.command("app:registry:rotate <id>","Rotate write-only registry credentials").option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).option("--expires <date>","Credential expiry timestamp").action(async(t,m)=>{try{await wr(async({registries:k})=>{let D={username:process.env[m.usernameEnv],password:process.env[m.passwordEnv],token:process.env[m.tokenEnv]};if(!Object.values(D).some(Boolean))throw Error("Set at least one configured registry credential environment variable");let L=k.rotate(t,D,{expiresAt:m.expires?new Date(m.expires).toISOString():void 0});n(`Registry credential rotated: ${L.id} · ${L.status}`)})}catch(k){Cn(k)}}),R.command("app:registry:disconnect <id>","Disconnect and erase registry credentials").option("--yes","Skip confirmation").action(async(t,m)=>{try{await wr(async({registries:k,drafts:D,controlPlane:L})=>{let z=k.get(t);if(!z)throw Error(`Registry ${t} was not found`);let Y=D.list(L.project.id).filter((X)=>X.input.source.kind==="image"&&X.input.source.registryConnectionId===t||X.input.build.kind==="prebuilt_image"&&X.input.build.registryConnectionId===t);if(e(`${Y.length} application draft${Y.length===1?"":"s"} reference this registry.`),!m.yes&&!await d(`Disconnect ${z.name}?`,!1))return;k.disconnect(t),n(`Disconnected ${z.name}`)})}catch(k){Cn(k)}})}import{existsSync as sf,readFileSync as af,writeFileSync as cf}from"node:fs";function jc(R,t){r(`'${R}' is not implemented yet.`),e(t),process.exitCode=1}function lf(R){let t={};for(let m of R.split(`
|
|
366
366
|
`)){let k=m.trim();if(!k||k.startsWith("#"))continue;let D=k.indexOf("=");if(D===-1)continue;let L=k.slice(0,D).trim(),z=k.slice(D+1).trim();if(z.startsWith('"')&&z.endsWith('"')||z.startsWith("'")&&z.endsWith("'"))z=z.slice(1,-1);t[L]=z}return t}function df(R){return Object.entries(R).sort(([m],[k])=>m.localeCompare(k)).map(([m,k])=>{if(!/[\s#"']/.test(k))return`${m}=${k}`;let D='"'+k.replace(/"/g,"\\\"")+'"';return`${m}=${D}`}).join(`
|
|
367
367
|
`)+`
|
|
368
368
|
`}function Bc(R){R.command("env:list","List configured environments and their deployed stack status").action(async()=>{i("Environments");try{let t=await Ne(),m=Object.keys(t.environments??{});if(!m.length){e("No environments configured. Add environments.<env> to your cloud.config.");return}let k=[];for(let D of m){let L=t.environments[D],z=L?.region||t.project.region||"us-east-1",Y=L?.app?`serverless (${L.app.kind})`:"server/static",X="not deployed";if(L?.app)try{let ne=new v(z),{Stacks:re}=await ne.describeStacks({stackName:$e(t,D)});X=re?.[0]?.StackStatus??"not deployed"}catch{X="not deployed"}k.push([D,L?.type??D,Y,z,X])}c(["Environment","Type","Kind","Region","Stack status"],k)}catch(t){r(`Failed to list environments: ${t.message}`),process.exitCode=1}}),R.command("env:compare <env1> <env2>","Compare two environments' app configuration").action(async(t,m)=>{i(`Comparing ${t} ↔ ${m}`);try{let k=await Ne(),D=k.environments?.[t],L=k.environments?.[m];if(!D||!L){r(`Both environments must exist in config (have: ${Object.keys(k.environments??{}).join(", ")||"none"}).`),process.exitCode=1;return}let z=(ie,ae="")=>{let be={};for(let[Ae,le]of Object.entries(ie??{})){let ke=ae?`${ae}.${Ae}`:Ae;if(le&&typeof le==="object"&&!Array.isArray(le))Object.assign(be,z(le,ke));else be[ke]=Array.isArray(le)?JSON.stringify(le):String(le)}return be},Y=z(D),X=z(L),re=[...new Set([...Object.keys(Y),...Object.keys(X)])].sort().filter((ie)=>Y[ie]!==X[ie]).map((ie)=>[ie,Y[ie]??"—",X[ie]??"—"]);if(!re.length){n("Identical — no configuration differences.");return}c(["Setting",t,m],re),e(`
|
|
369
|
-
${re.length} difference(s)`)}catch(k){r(`Comparison failed: ${k.message}`),process.exitCode=1}}),R.command("env:pull","Download a serverless function's environment to a .env file").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli",{default:"http"}).option("--file <path>","Output file (default .env.<environment>)").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=t?.function??"http",ne=(await new P(D).getFunction(L[z])).Configuration?.Environment?.Variables??{},re=t?.file??`.env.${k}`;cf(re,df(ne)),n(`Wrote ${Object.keys(ne).length} variable(s) from ${L[z]} → ${re}`)}catch(m){r(`env:pull failed: ${m.message}`),process.exitCode=1}}),R.command("env:push","Upload a .env file to a serverless function's configuration").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Function: http | queue | cli | all",{default:"all"}).option("--file <path>","Input file (default .env.<environment>)").option("--replace","Replace the entire env instead of merging over the live config").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=t?.file??`.env.${k}`;if(!sf(z)){r(`File not found: ${z}`),process.exitCode=1;return}let Y=lf(af(z,"utf8")),X=(t?.function??"all").toLowerCase();if(X!=="all"&&!["http","queue","cli"].includes(X)){r("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let ne=X==="all"?["http","queue","cli"]:[X],re=(m.environments?.[k]?.app?.provisionedConcurrency??0)>0,ie=new P(D);for(let ae of ne){let be=L[ae],Ae=Y;if(!t?.replace)Ae={...(await ie.getFunction(be)).Configuration?.Environment?.Variables??{},...Y};if($o(be,Ae),await ie.updateFunctionConfiguration({FunctionName:be,Environment:{Variables:Ae}}),await ie.waitForFunctionActive(be,120),re){let le=await ie.publishVersion({FunctionName:be});if(le.Version)await ie.updateAlias({FunctionName:be,Name:"live",FunctionVersion:le.Version})}n(`Pushed ${Object.keys(Y).length} var(s) → ${be}${t?.replace?" (replaced)":" (merged)"}${re?" (published + flipped live alias)":""}`)}e("Note: a subsequent `cloud deploy:serverless` re-injects infra/secret env from config.")}catch(m){r(`env:push failed: ${m.message}`),process.exitCode=1}}),R.command("env:create <name>","Create new environment").action(async(t)=>{jc(`env:create ${t}`,"Add an `environments.<name>` block to your cloud.config and run `cloud deploy:serverless --env <name>`.")}),R.command("env:switch <name>","Switch active environment").action(async(t)=>{jc(`env:switch ${t}`,'ts-cloud has no persistent "active" environment — pass --env <name> to each command.')});for(let t of["clone","promote","sync"])R.command(`env:${t} <source> <target>`,`(${t}) environment configuration`).action(async(m,k)=>{jc(`env:${t} ${m} ${k}`,`Copy the \`environments.${m}\` block to \`environments.${k}\` in cloud.config, then \`cloud deploy:serverless --env ${k}\`.`)});R.command("env:preview <branch>","Create, inspect, rebuild, extend, or destroy an immutable preview").option("--sha <commit>","Exact 40-64 character source commit").option("--pr <number>","Pull request number").option("--site <name>","Application site (defaults to the first site)").option("--env <environment>","Base environment",{default:"production"}).option("--domain <pattern>","HTTPS URL pattern containing {name}").option("--ttl <hours>","Preview lifetime in hours").option("--destroy","Queue teardown for this preview").option("--rebuild","Rebuild the currently recorded commit").option("--extend <hours>","Extend expiry from now/current expiry").option("--get-url","Print the stable preview URL without mutation").option("--yes","Skip destructive confirmation").action(async(t,m)=>{let k;try{let D=await Ne(),L=m.env||"production",z=m.site||Object.keys(D.sites??{})[0];if(!z)throw Error("No application site is configured; pass --site after adding one");k=y(process.cwd(),D);let Y=k.environments.get(L),X=Y?k.store.listResources(k.project.id,Y.id).find((le)=>le.kind==="application"&&le.slug===z):void 0;if(!Y||!X)throw Error(`Application ${z} was not found in base environment ${L}`);let ne=new et(k.store),re=ne.previews.getDefinitionForResource(X.id);if(!re){let le=D.sites?.[z]?.domain,ke=m.domain||(le?`https://{name}.preview.${le}`:void 0);if(!ke)throw Error("Pass --domain with an HTTPS pattern containing {name} to configure previews");re=ne.previews.createDefinition({projectId:k.project.id,resourceId:X.id,baseEnvironmentId:Y.id,domainPattern:ke,ttlHours:Number(m.ttl)||void 0})}let ie=Number(m.pr)||void 0,ae=ie?ne.previews.findForPullRequest(re.id,"local",ie):ne.previews.findForBranch(re.id,"local",t);if(m.getUrl){if(!ae)throw Error("Preview was not found");e(ae.url??"URL unavailable");return}if(m.extend){if(!ae)throw Error("Preview was not found");let le=ne.previews.extend(ae.id,Number(m.extend));n(`Extended ${le.name} until ${le.expiresAt}`);return}if(m.destroy){if(!ae)throw Error("Preview was not found");if(!m.yes&&!await d(`Destroy ${ae.name} and only its tagged resources?`,!1))return;let le=ne.enqueueDestroy(ae,"manual");n(`Preview teardown queued: ${le.id}`);return}if(m.rebuild){if(!ae)throw Error("Preview was not found");let le=ne.enqueueDeploy(ae,{reason:"manual_rebuild"});n(`Preview rebuild queued: ${le.id}`);return}if(!m.sha)throw Error("Pass --sha with the exact immutable commit to deploy");let be=ne.previews.upsert({definitionId:re.id,repository:"local",branch:t,pullRequestNumber:ie,commitSha:m.sha}),Ae=ne.enqueueDeploy(be.preview,{created:be.created});n(`${be.created?"Preview creation":"Preview update"} queued: ${Ae.id}`),e(`${be.preview.url} · expires ${be.preview.expiresAt}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k?.store.close()}}),R.command("env:cleanup","Remove stale preview environments").option("--dry-run","List cleanup candidates without queuing teardown").option("--max-age <hours>","Also remove previews older than this age").option("--keep <count>","Keep this many newest previews per application").action(async(t)=>{let m;try{let k=await Ne();m=y(process.cwd(),k);let L=new et(m.store).cleanup({dryRun:t.dryRun,maxAgeHours:Number(t.maxAge)||void 0,keepCount:Number(t.keep)||void 0});c(["Preview","Status","Expires","Reason"],L.candidates.map((z)=>[z.preview.name,z.preview.status,z.preview.expiresAt,z.reasons.join(", ")])),n(t.dryRun?`${L.candidates.length} cleanup candidate(s)`:`${L.operations.length} teardown job(s) queued`)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}finally{m?.store.close()}}),R.command("env:previews","List persistent preview environments").option("--site <name>","Filter application site").action(async(t)=>{let m;try{let k=await Ne();m=y(process.cwd(),k);let D=new et(m.store),L=t.site?new Set(m.store.listResources(m.project.id).filter((Y)=>Y.slug===t.site).map((Y)=>Y.id)):void 0,z=D.previews.listInstances({projectId:m.project.id}).filter((Y)=>!L||L.has(Y.resourceId));c(["ID","Name","Status","Branch / PR","Commit","URL","Expires","Cost"],z.map((Y)=>[Y.id,Y.name,Y.status,Y.pullRequestNumber?`PR #${Y.pullRequestNumber}`:Y.branch,Y.commitSha.slice(0,12),Y.url??"—",Y.expiresAt,Y.costEstimate==null?"—":`$${Y.costEstimate.toFixed(2)}`]))}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}finally{m?.store.close()}})}import{readFileSync as ra}from"node:fs";async function xr(){let R=await Ne(),t=y(process.cwd(),R);return{config:R,controlPlane:t,applications:new bt(t.store)}}async function _c(R,t,m){let k=await xr();try{let D=k.applications.get(R);if(!D)throw Error("Compose application was not found");let L=t??k.controlPlane.store.listEnvironments(D.projectId).find((Y)=>Y.id===D.environmentId)?.slug;if(!L)throw Error("Compose environment was not found");let z=await dr(k.config,L,m(D));if(!z.ok)throw Error(z.stderr||z.error||"Remote Compose command failed");if(z.stdout)e(z.stdout)}finally{k.controlPlane.store.close()}}function Mc(R){R.command("compose:preview <file>","Parse Compose without persistence or provider mutation").option("--name <name>","Application name",{default:"Compose application"}).option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{let k=await xr();try{let D=k.controlPlane.environments.get(m.env??"production");if(!D)throw Error("Environment was not found");let L=k.applications.preview(ra(t,"utf8"),{name:m.name??"Compose application",projectId:k.controlPlane.project.id,environmentId:D.id});if(c(["Severity","Path","Code","Message"],L.diagnostics.map((z)=>[z.severity,z.path,z.code,z.message])),!L.valid)process.exitCode=1;else n(`${Object.keys(L.manifest.spec.services).length} services are valid`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:import <file>","Import and persist a validated Compose application").option("--name <name>","Application name").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{let k=await xr();try{let D=k.controlPlane.environments.get(m.env??"production");if(!D)throw Error("Environment was not found");let L=m.name??t.split("/").at(-2)??"Compose application",z=k.applications.import(ra(t,"utf8"),{name:L,projectId:k.controlPlane.project.id,environmentId:D.id});n(`Imported ${z.application.name} (${z.application.id})`),e(`${Object.keys(z.application.manifest.spec.services).length} services · ${z.parsed.diagnostics.length} diagnostic(s)`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:templates","List pinned verified Compose templates").action(()=>c(["ID","Version","Category","Architecture","Verified"],Cr().map((t)=>[t.id,t.version,t.category,t.architecture,t.lastVerifiedAt]))),R.command("compose:catalog <file>","Validate and inspect a local/custom template catalog").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{let k=await xr();try{let D=k.controlPlane.environments.get(m.env??"production");if(!D)throw Error("Environment was not found");let L=Tc(ra(t,"utf8"),{projectId:k.controlPlane.project.id,environmentId:D.id});if(c(["ID","Version","Category","Services","Checksum"],L.templates.map((z)=>[z.template.id,z.template.version,z.template.category,String(Object.keys(z.parsed.manifest.spec.services).length),z.template.checksum.slice(0,12)])),L.diagnostics.length)c(["Severity","Path","Message"],L.diagnostics.map((z)=>[z.severity,z.path,z.message]));if(!L.valid)process.exitCode=1}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:template <template>","Create an editable application from a template").option("--name <name>","Application name").option("--domain <domain>","Primary domain").option("--env <environment>","Target environment",{default:"production"}).option("--version <version>","Pinned template version").action(async(t,m)=>{let k=await xr();try{let D=k.controlPlane.environments.get(m.env??"production");if(!D)throw Error("Environment was not found");let L=k.applications.fromTemplate(t,{domain:m.domain??""},{name:m.name??t,projectId:k.controlPlane.project.id,environmentId:D.id,version:m.version});n(`Created ${L.application.name} from ${t}@${L.application.templateVersion}`),e(`Export with cloud compose:export ${L.application.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:list","List Compose applications").action(async()=>{let t=await xr();try{c(["ID","Name","Status","Services","Source","Updated"],t.applications.list({projectId:t.controlPlane.project.id}).map((m)=>[m.id,m.name,m.status,String(Object.keys(m.manifest.spec.services).length),m.templateId?`${m.templateId}@${m.templateVersion}`:"compose",m.updatedAt]))}finally{t.controlPlane.store.close()}}),R.command("compose:export <application>","Print a normalized editable Compose file").action(async(t)=>{let m=await xr();try{let k=m.applications.get(t);if(!k)throw Error("Compose application was not found");e(Si(k.manifest))}finally{m.controlPlane.store.close()}}),R.command("compose:diff <application> <file>","Compare persisted and proposed Compose manifests").action(async(t,m)=>{let k=await xr();try{let D=k.applications.get(t);if(!D)throw Error("Compose application was not found");let L=Ft(ra(m,"utf8"),D.manifest.metadata);c(["Path","Before","After"],Cs(D.manifest,L.manifest).map((z)=>[z.path,JSON.stringify(z.before),JSON.stringify(z.after)]))}finally{k.controlPlane.store.close()}});for(let t of["deploy","redeploy","start","stop"])R.command(`compose:${t} <application>`,`${t} a Compose application through the durable queue`).action(async(m)=>{let k=await xr();try{let D=k.applications.get(m);if(!D)throw Error("Compose application was not found");let L=new Ze(k.controlPlane.store).enqueue(D,t);n(`${t} queued: ${L.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}});R.command("compose:scale <application> <service> <replicas>","Scale one capable Compose service").action(async(t,m,k)=>{let D=await xr();try{let L=D.applications.get(t);if(!L)throw Error("Compose application was not found");let z=new Ze(D.controlPlane.store).enqueue(L,"scale",{service:m,replicas:Number(k)});n(`Scale queued: ${z.id}`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}finally{D.controlPlane.store.close()}}),R.command("compose:delete <application>","Delete a Compose stack; named volumes are preserved by default").option("--remove-volumes","Also permanently remove named volumes").option("--confirm <text>","Exact application confirmation").action(async(t,m)=>{let k=await xr();try{let D=k.applications.get(t);if(!D)throw Error("Compose application was not found");let L=new Ze(k.controlPlane.store).enqueue(D,"delete",{removeVolumes:m.removeVolumes,confirmation:m.confirm});n(`Deletion queued: ${L.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:logs <application> <service>","Read ordered service logs").option("--lines <count>","Tail line count",{default:"200"}).option("--env <environment>","Target environment").action((t,m,k)=>_c(t,k.env,(D)=>vi(D.manifest,m,Number(k.lines)||200))),R.command("compose:shell <application> <service> [command...]","Run a command in one service").option("--env <environment>","Target environment").action((t,m,k,D)=>_c(t,D.env,(L)=>Ci(L.manifest,m,k?.length?k:["sh"]))),R.command("compose:apply <application>","Internal durable Compose runtime action").option("--action <action>","Runtime action").option("--env <environment>","Target environment").option("--service <service>","Service name").option("--replicas <count>","Replica count").option("--remove-volumes","Remove named volumes").action((t,m)=>_c(t,m.env,(k)=>{if(m.action==="scale")return wc(k.manifest,m.service??"",Number(m.replicas));return Ec(k.manifest,m.action??"deploy",{service:m.service,removeVolumes:m.removeVolumes})}))}import{readFileSync as uf}from"node:fs";async function Er(){let R=await Ne(),t=y(process.cwd(),R);return{controlPlane:t,releases:new dt(t.store)}}function zc(R){if(!R)return{};return JSON.parse(uf(R,"utf8"))}function Uc(R){R.command("release:artifact <uri>","Register a verified content-addressed artifact").option("--digest <sha256>","Required sha256: digest").option("--kind <kind>","Deployable kind").option("--size <bytes>","Artifact size",{default:"0"}).option("--media-type <type>","Artifact media type").action(async(t,m)=>{let k=await Er();try{let D=k.releases.releases.registerArtifact({organizationId:k.controlPlane.organization.id,digest:String(m.digest??""),kind:m.kind??"compute",uri:t,size:Number(m.size),mediaType:m.mediaType});n(`Artifact registered: ${D.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("release:create <resource> <artifact>","Create an immutable release record").option("--env <environment>","Target environment",{default:"production"}).option("--kind <kind>","Deployable kind").option("--source-sha <sha>","Exact source commit").option("--strategy <strategy>","Activation strategy",{default:"atomic"}).option("--manifest <file>","Immutable manifest JSON").option("--config <file>","Resolved non-secret config JSON").option("--health-path <path>","HTTP health path").option("--approval","Require an environment approval").option("--trigger <trigger>","Build trigger",{default:"manual"}).action(async(t,m,k)=>{let D=await Er();try{let L=D.controlPlane.environments.get(k.env??"production"),z=L?D.controlPlane.store.listResources(D.controlPlane.project.id,L.id).find((X)=>X.slug===t):void 0;if(!L||!z)throw Error("Release resource was not found");let Y=D.releases.releases.create({organizationId:D.controlPlane.organization.id,projectId:D.controlPlane.project.id,environmentId:L.id,resourceId:z.id,artifactId:m,kind:k.kind??"compute",sourceSha:k.sourceSha,config:zc(k.config),manifest:zc(k.manifest),strategy:k.strategy??"atomic",healthGate:k.healthPath?{protocol:"http",path:k.healthPath,timeoutSeconds:5,intervalSeconds:5,healthyThreshold:2,unhealthyThreshold:2}:void 0,approvalRequired:k.approval,trigger:k.trigger});n(`Release created: ${Y.id} (${Y.status})`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}finally{D.controlPlane.store.close()}}),R.command("release:list","List unified release history").option("--env <environment>","Filter environment").action(async(t)=>{let m=await Er();try{let k=t.env?m.controlPlane.environments.get(t.env):void 0;c(["ID","Resource","Status","Kind","Artifact","Strategy","Source","Created"],m.releases.releases.list({projectId:m.controlPlane.project.id,environmentId:k?.id}).map((D)=>[D.id,m.controlPlane.store.getResource(D.resourceId)?.slug??D.resourceId,D.status,D.kind,D.artifactId.slice(0,12),D.strategy,D.sourceSha?.slice(0,12)??"—",D.createdAt]))}finally{m.controlPlane.store.close()}}),R.command("release:show <release>","Show provenance, health, approvals, and transitions").action(async(t)=>{let m=await Er();try{let k=m.releases.releases.get(t);if(!k)throw Error("Release was not found");e(JSON.stringify({release:k,artifact:m.releases.releases.getArtifact(k.artifactId),approvals:m.releases.releases.approvals(t),transitions:m.releases.releases.transitions(t)},null,2))}finally{m.controlPlane.store.close()}}),R.command("release:compare <left> <right>","Compare artifact, source, config, manifest, and data caveats").action(async(t,m)=>{let k=await Er();try{let D=k.releases.releases.compare(t,m);e(JSON.stringify(D,null,2))}finally{k.controlPlane.store.close()}}),R.command("release:capabilities <kind>","Explain supported deployment strategies").option("--health","Readiness gate is configured").option("--replicas <count>","Replica count",{default:"1"}).action((t,m)=>c(["Strategy","Supported","Capacity","Cost","Explanation","Rollback"],ur({kind:t,hasHealthGate:!!m.health,replicas:Number(m.replicas)}).map((k)=>[k.strategy,k.supported?"yes":"no",`${k.capacityMultiplier}x`,k.costImpact,k.explanation,k.rollback]))),R.command("release:promote <release> <resource>","Promote the same artifact without rebuilding").option("--env <environment>","Target environment").option("--config <file>","Target non-secret config JSON").option("--approval","Require approval").action(async(t,m,k)=>{let D=await Er();try{let L=D.controlPlane.environments.get(String(k.env??"")),z=L?D.controlPlane.store.listResources(D.controlPlane.project.id,L.id).find((X)=>X.slug===m):void 0;if(!L||!z)throw Error("Promotion target was not found");let Y=D.releases.releases.promote(t,{targetEnvironmentId:L.id,targetResourceId:z.id,config:zc(k.config),approvalRequired:k.approval});n(`Promoted without rebuild: ${Y.id} (${Y.status})`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}finally{D.controlPlane.store.close()}}),R.command("release:approve <release>","Approve or reject an environment gate").option("--actor <id>","Approving actor ID").option("--reject","Reject instead").option("--comment <text>","Approval comment").action(async(t,m)=>{let k=await Er();try{if(!m.actor)throw Error("--actor is required");let D=k.releases.releases.approve(t,{actorId:m.actor,decision:m.reject?"rejected":"approved",comment:m.comment});n(`Release is ${D.status}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("release:activate <release>","Queue immutable artifact activation").action(async(t)=>{let m=await Er();try{let k=m.releases.releases.get(t);if(!k)throw Error("Release was not found");let D=m.releases.enqueueActivation(k);n(`Activation queued: ${D.id}`)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}finally{m.controlPlane.store.close()}}),R.command("release:rollback <release>","Restore the preserved prior immutable release").option("--to <release>","Explicit preserved release ID").action(async(t,m)=>{let k=await Er();try{let D=k.releases.releases.get(t);if(!D)throw Error("Release was not found");let L=k.releases.enqueueRollback(D,{targetReleaseId:m.to});n(`Rollback queued: ${L.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("release:health <release>","Record activation health and trigger bounded rollback").option("--healthy","Health gate passed").option("--operation <id>","Activation operation ID").option("--message <text>","Health summary").action(async(t,m)=>{let k=await Er();try{let D=k.releases.completeHealthGate(t,{healthy:!!m.healthy,operationId:m.operation,message:m.message});n(D.rollbackOperation?`Health failed; rollback queued: ${D.rollbackOperation.id}`:`Release is ${D.release.status}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("release:pin <release>","Pin or unpin a release against retention").option("--remove","Remove pin").option("--reason <text>","Pin reason").action(async(t,m)=>{let k=await Er();try{let D=k.releases.releases.pin(t,!m.remove,m.reason);n(D.pinned?"Release pinned.":"Release unpinned.")}finally{k.controlPlane.store.close()}})}import{existsSync as mf}from"node:fs";function Vc(R){R.command("assets:build","Run the configured build hooks for an environment").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).action(async(t)=>{i("Building Assets");try{let m=await Ne(),k=t?.env||"production",L=m.environments?.[k]?.app?.build??[];if(!L.length){r(`No build hooks defined for '${k}'. Set environments.${k}.app.build = ['bun run build', …] in cloud.config.`),process.exitCode=1;return}let{execSync:z}=await import("node:child_process");for(let Y of L)u(`$ ${Y}`),z(Y,{stdio:"inherit"});n("Build hooks complete")}catch(m){r(`Build failed: ${m.message}`),process.exitCode=1}});for(let t of["assets:optimize:images","images:optimize"])R.command(t,"Optimize images (not implemented)").option("--dir <directory>","Directory to optimize").action(async()=>{r(`'${t}' is not implemented — ts-cloud has no built-in image optimizer.`),e("Optimize images in your own build step (e.g. sharp/squoosh), then `cloud assets:deploy`."),process.exitCode=1});R.command("assets:deploy","Deploy static assets to S3").option("--source <path>","Source directory",{default:"dist"}).option("--bucket <name>","S3 bucket name").option("--prefix <prefix>","S3 prefix/folder").option("--delete","Delete files not in source").option("--cache-control <value>","Cache-Control header",{default:"public, max-age=31536000"}).action(async(t)=>{i("Deploying Assets to S3");try{let k=(await Ne()).project.region||"us-east-1",D=t?.source||"dist",L=t?.bucket,z=t?.prefix,Y=t?.delete||!1,X=t?.cacheControl||"public, max-age=31536000";if(!L){r("--bucket is required");return}if(!mf(D)){r(`Source directory not found: ${D}`);return}if(e(`Source: ${D}`),e(`Bucket: s3://${L}${z?`/${z}`:""}`),e(`Cache-Control: ${X}`),Y)a("Delete mode enabled - files not in source will be removed");if(!await d(`
|
|
369
|
+
${re.length} difference(s)`)}catch(k){r(`Comparison failed: ${k.message}`),process.exitCode=1}}),R.command("env:pull","Download a serverless function's environment to a .env file").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli",{default:"http"}).option("--file <path>","Output file (default .env.<environment>)").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=t?.function??"http",ne=(await new P(D).getFunction(L[z])).Configuration?.Environment?.Variables??{},re=t?.file??`.env.${k}`;cf(re,df(ne)),n(`Wrote ${Object.keys(ne).length} variable(s) from ${L[z]} → ${re}`)}catch(m){r(`env:pull failed: ${m.message}`),process.exitCode=1}}),R.command("env:push","Upload a .env file to a serverless function's configuration").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Function: http | queue | cli | all",{default:"all"}).option("--file <path>","Input file (default .env.<environment>)").option("--replace","Replace the entire env instead of merging over the live config").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{region:D,functions:L}=Tt(m,k),z=t?.file??`.env.${k}`;if(!sf(z)){r(`File not found: ${z}`),process.exitCode=1;return}let Y=lf(af(z,"utf8")),X=(t?.function??"all").toLowerCase();if(X!=="all"&&!["http","queue","cli"].includes(X)){r("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let ne=X==="all"?["http","queue","cli"]:[X],re=(m.environments?.[k]?.app?.provisionedConcurrency??0)>0,ie=new P(D);for(let ae of ne){let be=L[ae],Ae=Y;if(!t?.replace)Ae={...(await ie.getFunction(be)).Configuration?.Environment?.Variables??{},...Y};if($o(be,Ae),await ie.updateFunctionConfiguration({FunctionName:be,Environment:{Variables:Ae}}),await ie.waitForFunctionActive(be,120),re){let le=await ie.publishVersion({FunctionName:be});if(le.Version)await ie.updateAlias({FunctionName:be,Name:"live",FunctionVersion:le.Version})}n(`Pushed ${Object.keys(Y).length} var(s) → ${be}${t?.replace?" (replaced)":" (merged)"}${re?" (published + flipped live alias)":""}`)}e("Note: a subsequent `cloud deploy:serverless` re-injects infra/secret env from config.")}catch(m){r(`env:push failed: ${m.message}`),process.exitCode=1}}),R.command("env:create <name>","Create new environment").action(async(t)=>{jc(`env:create ${t}`,"Add an `environments.<name>` block to your cloud.config and run `cloud deploy:serverless --env <name>`.")}),R.command("env:switch <name>","Switch active environment").action(async(t)=>{jc(`env:switch ${t}`,'ts-cloud has no persistent "active" environment — pass --env <name> to each command.')});for(let t of["clone","promote","sync"])R.command(`env:${t} <source> <target>`,`(${t}) environment configuration`).action(async(m,k)=>{jc(`env:${t} ${m} ${k}`,`Copy the \`environments.${m}\` block to \`environments.${k}\` in cloud.config, then \`cloud deploy:serverless --env ${k}\`.`)});R.command("env:preview <branch>","Create, inspect, rebuild, extend, or destroy an immutable preview").option("--sha <commit>","Exact 40-64 character source commit").option("--pr <number>","Pull request number").option("--site <name>","Application site (defaults to the first site)").option("--env <environment>","Base environment",{default:"production"}).option("--domain <pattern>","HTTPS URL pattern containing {name}").option("--ttl <hours>","Preview lifetime in hours").option("--destroy","Queue teardown for this preview").option("--rebuild","Rebuild the currently recorded commit").option("--extend <hours>","Extend expiry from now/current expiry").option("--get-url","Print the stable preview URL without mutation").option("--yes","Skip destructive confirmation").action(async(t,m)=>{let k;try{let D=await Ne(),L=m.env||"production",z=m.site||Object.keys(D.sites??{})[0];if(!z)throw Error("No application site is configured; pass --site after adding one");k=y(process.cwd(),D);let Y=k.environments.get(L),X=Y?k.store.listResources(k.project.id,Y.id).find((le)=>le.kind==="application"&&le.slug===z):void 0;if(!Y||!X)throw Error(`Application ${z} was not found in base environment ${L}`);let ne=new et(k.store),re=ne.previews.getDefinitionForResource(X.id);if(!re){let le=D.sites?.[z]?.domain,ke=m.domain||(le?`https://{name}.preview.${le}`:void 0);if(!ke)throw Error("Pass --domain with an HTTPS pattern containing {name} to configure previews");re=ne.previews.createDefinition({projectId:k.project.id,resourceId:X.id,baseEnvironmentId:Y.id,domainPattern:ke,ttlHours:Number(m.ttl)||void 0})}let ie=Number(m.pr)||void 0,ae=ie?ne.previews.findForPullRequest(re.id,"local",ie):ne.previews.findForBranch(re.id,"local",t);if(m.getUrl){if(!ae)throw Error("Preview was not found");e(ae.url??"URL unavailable");return}if(m.extend){if(!ae)throw Error("Preview was not found");let le=ne.previews.extend(ae.id,Number(m.extend));n(`Extended ${le.name} until ${le.expiresAt}`);return}if(m.destroy){if(!ae)throw Error("Preview was not found");if(!m.yes&&!await d(`Destroy ${ae.name} and only its tagged resources?`,!1))return;let le=ne.enqueueDestroy(ae,"manual");n(`Preview teardown queued: ${le.id}`);return}if(m.rebuild){if(!ae)throw Error("Preview was not found");let le=ne.enqueueDeploy(ae,{reason:"manual_rebuild"});n(`Preview rebuild queued: ${le.id}`);return}if(!m.sha)throw Error("Pass --sha with the exact immutable commit to deploy");let be=ne.previews.upsert({definitionId:re.id,repository:"local",branch:t,pullRequestNumber:ie,commitSha:m.sha}),Ae=ne.enqueueDeploy(be.preview,{created:be.created});n(`${be.created?"Preview creation":"Preview update"} queued: ${Ae.id}`),e(`${be.preview.url} · expires ${be.preview.expiresAt}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k?.store.close()}}),R.command("env:cleanup","Remove stale preview environments").option("--dry-run","List cleanup candidates without queuing teardown").option("--max-age <hours>","Also remove previews older than this age").option("--keep <count>","Keep this many newest previews per application").action(async(t)=>{let m;try{let k=await Ne();m=y(process.cwd(),k);let L=new et(m.store).cleanup({dryRun:t.dryRun,maxAgeHours:Number(t.maxAge)||void 0,keepCount:Number(t.keep)||void 0});c(["Preview","Status","Expires","Reason"],L.candidates.map((z)=>[z.preview.name,z.preview.status,z.preview.expiresAt,z.reasons.join(", ")])),n(t.dryRun?`${L.candidates.length} cleanup candidate(s)`:`${L.operations.length} teardown job(s) queued`)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}finally{m?.store.close()}}),R.command("env:previews","List persistent preview environments").option("--site <name>","Filter application site").action(async(t)=>{let m;try{let k=await Ne();m=y(process.cwd(),k);let D=new et(m.store),L=t.site?new Set(m.store.listResources(m.project.id).filter((Y)=>Y.slug===t.site).map((Y)=>Y.id)):void 0,z=D.previews.listInstances({projectId:m.project.id}).filter((Y)=>!L||L.has(Y.resourceId));c(["ID","Name","Status","Branch / PR","Commit","URL","Expires","Cost"],z.map((Y)=>[Y.id,Y.name,Y.status,Y.pullRequestNumber?`PR #${Y.pullRequestNumber}`:Y.branch,Y.commitSha.slice(0,12),Y.url??"—",Y.expiresAt,Y.costEstimate==null?"—":`$${Y.costEstimate.toFixed(2)}`]))}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}finally{m?.store.close()}})}import{readFileSync as ra}from"node:fs";async function Ar(){let R=await Ne(),t=y(process.cwd(),R);return{config:R,controlPlane:t,applications:new bt(t.store)}}async function _c(R,t,m){let k=await Ar();try{let D=k.applications.get(R);if(!D)throw Error("Compose application was not found");let L=t??k.controlPlane.store.listEnvironments(D.projectId).find((Y)=>Y.id===D.environmentId)?.slug;if(!L)throw Error("Compose environment was not found");let z=await ur(k.config,L,m(D));if(!z.ok)throw Error(z.stderr||z.error||"Remote Compose command failed");if(z.stdout)e(z.stdout)}finally{k.controlPlane.store.close()}}function Mc(R){R.command("compose:preview <file>","Parse Compose without persistence or provider mutation").option("--name <name>","Application name",{default:"Compose application"}).option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{let k=await Ar();try{let D=k.controlPlane.environments.get(m.env??"production");if(!D)throw Error("Environment was not found");let L=k.applications.preview(ra(t,"utf8"),{name:m.name??"Compose application",projectId:k.controlPlane.project.id,environmentId:D.id});if(c(["Severity","Path","Code","Message"],L.diagnostics.map((z)=>[z.severity,z.path,z.code,z.message])),!L.valid)process.exitCode=1;else n(`${Object.keys(L.manifest.spec.services).length} services are valid`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:import <file>","Import and persist a validated Compose application").option("--name <name>","Application name").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{let k=await Ar();try{let D=k.controlPlane.environments.get(m.env??"production");if(!D)throw Error("Environment was not found");let L=m.name??t.split("/").at(-2)??"Compose application",z=k.applications.import(ra(t,"utf8"),{name:L,projectId:k.controlPlane.project.id,environmentId:D.id});n(`Imported ${z.application.name} (${z.application.id})`),e(`${Object.keys(z.application.manifest.spec.services).length} services · ${z.parsed.diagnostics.length} diagnostic(s)`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:templates","List pinned verified Compose templates").action(()=>c(["ID","Version","Category","Architecture","Verified"],Tr().map((t)=>[t.id,t.version,t.category,t.architecture,t.lastVerifiedAt]))),R.command("compose:catalog <file>","Validate and inspect a local/custom template catalog").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{let k=await Ar();try{let D=k.controlPlane.environments.get(m.env??"production");if(!D)throw Error("Environment was not found");let L=Tc(ra(t,"utf8"),{projectId:k.controlPlane.project.id,environmentId:D.id});if(c(["ID","Version","Category","Services","Checksum"],L.templates.map((z)=>[z.template.id,z.template.version,z.template.category,String(Object.keys(z.parsed.manifest.spec.services).length),z.template.checksum.slice(0,12)])),L.diagnostics.length)c(["Severity","Path","Message"],L.diagnostics.map((z)=>[z.severity,z.path,z.message]));if(!L.valid)process.exitCode=1}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:template <template>","Create an editable application from a template").option("--name <name>","Application name").option("--domain <domain>","Primary domain").option("--env <environment>","Target environment",{default:"production"}).option("--version <version>","Pinned template version").action(async(t,m)=>{let k=await Ar();try{let D=k.controlPlane.environments.get(m.env??"production");if(!D)throw Error("Environment was not found");let L=k.applications.fromTemplate(t,{domain:m.domain??""},{name:m.name??t,projectId:k.controlPlane.project.id,environmentId:D.id,version:m.version});n(`Created ${L.application.name} from ${t}@${L.application.templateVersion}`),e(`Export with cloud compose:export ${L.application.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:list","List Compose applications").action(async()=>{let t=await Ar();try{c(["ID","Name","Status","Services","Source","Updated"],t.applications.list({projectId:t.controlPlane.project.id}).map((m)=>[m.id,m.name,m.status,String(Object.keys(m.manifest.spec.services).length),m.templateId?`${m.templateId}@${m.templateVersion}`:"compose",m.updatedAt]))}finally{t.controlPlane.store.close()}}),R.command("compose:export <application>","Print a normalized editable Compose file").action(async(t)=>{let m=await Ar();try{let k=m.applications.get(t);if(!k)throw Error("Compose application was not found");e(vi(k.manifest))}finally{m.controlPlane.store.close()}}),R.command("compose:diff <application> <file>","Compare persisted and proposed Compose manifests").action(async(t,m)=>{let k=await Ar();try{let D=k.applications.get(t);if(!D)throw Error("Compose application was not found");let L=Ft(ra(m,"utf8"),D.manifest.metadata);c(["Path","Before","After"],Cs(D.manifest,L.manifest).map((z)=>[z.path,JSON.stringify(z.before),JSON.stringify(z.after)]))}finally{k.controlPlane.store.close()}});for(let t of["deploy","redeploy","start","stop"])R.command(`compose:${t} <application>`,`${t} a Compose application through the durable queue`).action(async(m)=>{let k=await Ar();try{let D=k.applications.get(m);if(!D)throw Error("Compose application was not found");let L=new Ze(k.controlPlane.store).enqueue(D,t);n(`${t} queued: ${L.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}});R.command("compose:scale <application> <service> <replicas>","Scale one capable Compose service").action(async(t,m,k)=>{let D=await Ar();try{let L=D.applications.get(t);if(!L)throw Error("Compose application was not found");let z=new Ze(D.controlPlane.store).enqueue(L,"scale",{service:m,replicas:Number(k)});n(`Scale queued: ${z.id}`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}finally{D.controlPlane.store.close()}}),R.command("compose:delete <application>","Delete a Compose stack; named volumes are preserved by default").option("--remove-volumes","Also permanently remove named volumes").option("--confirm <text>","Exact application confirmation").action(async(t,m)=>{let k=await Ar();try{let D=k.applications.get(t);if(!D)throw Error("Compose application was not found");let L=new Ze(k.controlPlane.store).enqueue(D,"delete",{removeVolumes:m.removeVolumes,confirmation:m.confirm});n(`Deletion queued: ${L.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("compose:logs <application> <service>","Read ordered service logs").option("--lines <count>","Tail line count",{default:"200"}).option("--env <environment>","Target environment").action((t,m,k)=>_c(t,k.env,(D)=>Ci(D.manifest,m,Number(k.lines)||200))),R.command("compose:shell <application> <service> [command...]","Run a command in one service").option("--env <environment>","Target environment").action((t,m,k,D)=>_c(t,D.env,(L)=>Ti(L.manifest,m,k?.length?k:["sh"]))),R.command("compose:apply <application>","Internal durable Compose runtime action").option("--action <action>","Runtime action").option("--env <environment>","Target environment").option("--service <service>","Service name").option("--replicas <count>","Replica count").option("--remove-volumes","Remove named volumes").action((t,m)=>_c(t,m.env,(k)=>{if(m.action==="scale")return wc(k.manifest,m.service??"",Number(m.replicas));return Ec(k.manifest,m.action??"deploy",{service:m.service,removeVolumes:m.removeVolumes})}))}import{readFileSync as uf}from"node:fs";async function Nr(){let R=await Ne(),t=y(process.cwd(),R);return{controlPlane:t,releases:new dt(t.store)}}function zc(R){if(!R)return{};return JSON.parse(uf(R,"utf8"))}function Uc(R){R.command("release:artifact <uri>","Register a verified content-addressed artifact").option("--digest <sha256>","Required sha256: digest").option("--kind <kind>","Deployable kind").option("--size <bytes>","Artifact size",{default:"0"}).option("--media-type <type>","Artifact media type").action(async(t,m)=>{let k=await Nr();try{let D=k.releases.releases.registerArtifact({organizationId:k.controlPlane.organization.id,digest:String(m.digest??""),kind:m.kind??"compute",uri:t,size:Number(m.size),mediaType:m.mediaType});n(`Artifact registered: ${D.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("release:create <resource> <artifact>","Create an immutable release record").option("--env <environment>","Target environment",{default:"production"}).option("--kind <kind>","Deployable kind").option("--source-sha <sha>","Exact source commit").option("--strategy <strategy>","Activation strategy",{default:"atomic"}).option("--manifest <file>","Immutable manifest JSON").option("--config <file>","Resolved non-secret config JSON").option("--health-path <path>","HTTP health path").option("--approval","Require an environment approval").option("--trigger <trigger>","Build trigger",{default:"manual"}).action(async(t,m,k)=>{let D=await Nr();try{let L=D.controlPlane.environments.get(k.env??"production"),z=L?D.controlPlane.store.listResources(D.controlPlane.project.id,L.id).find((X)=>X.slug===t):void 0;if(!L||!z)throw Error("Release resource was not found");let Y=D.releases.releases.create({organizationId:D.controlPlane.organization.id,projectId:D.controlPlane.project.id,environmentId:L.id,resourceId:z.id,artifactId:m,kind:k.kind??"compute",sourceSha:k.sourceSha,config:zc(k.config),manifest:zc(k.manifest),strategy:k.strategy??"atomic",healthGate:k.healthPath?{protocol:"http",path:k.healthPath,timeoutSeconds:5,intervalSeconds:5,healthyThreshold:2,unhealthyThreshold:2}:void 0,approvalRequired:k.approval,trigger:k.trigger});n(`Release created: ${Y.id} (${Y.status})`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}finally{D.controlPlane.store.close()}}),R.command("release:list","List unified release history").option("--env <environment>","Filter environment").action(async(t)=>{let m=await Nr();try{let k=t.env?m.controlPlane.environments.get(t.env):void 0;c(["ID","Resource","Status","Kind","Artifact","Strategy","Source","Created"],m.releases.releases.list({projectId:m.controlPlane.project.id,environmentId:k?.id}).map((D)=>[D.id,m.controlPlane.store.getResource(D.resourceId)?.slug??D.resourceId,D.status,D.kind,D.artifactId.slice(0,12),D.strategy,D.sourceSha?.slice(0,12)??"—",D.createdAt]))}finally{m.controlPlane.store.close()}}),R.command("release:show <release>","Show provenance, health, approvals, and transitions").action(async(t)=>{let m=await Nr();try{let k=m.releases.releases.get(t);if(!k)throw Error("Release was not found");e(JSON.stringify({release:k,artifact:m.releases.releases.getArtifact(k.artifactId),approvals:m.releases.releases.approvals(t),transitions:m.releases.releases.transitions(t)},null,2))}finally{m.controlPlane.store.close()}}),R.command("release:compare <left> <right>","Compare artifact, source, config, manifest, and data caveats").action(async(t,m)=>{let k=await Nr();try{let D=k.releases.releases.compare(t,m);e(JSON.stringify(D,null,2))}finally{k.controlPlane.store.close()}}),R.command("release:capabilities <kind>","Explain supported deployment strategies").option("--health","Readiness gate is configured").option("--replicas <count>","Replica count",{default:"1"}).action((t,m)=>c(["Strategy","Supported","Capacity","Cost","Explanation","Rollback"],pr({kind:t,hasHealthGate:!!m.health,replicas:Number(m.replicas)}).map((k)=>[k.strategy,k.supported?"yes":"no",`${k.capacityMultiplier}x`,k.costImpact,k.explanation,k.rollback]))),R.command("release:promote <release> <resource>","Promote the same artifact without rebuilding").option("--env <environment>","Target environment").option("--config <file>","Target non-secret config JSON").option("--approval","Require approval").action(async(t,m,k)=>{let D=await Nr();try{let L=D.controlPlane.environments.get(String(k.env??"")),z=L?D.controlPlane.store.listResources(D.controlPlane.project.id,L.id).find((X)=>X.slug===m):void 0;if(!L||!z)throw Error("Promotion target was not found");let Y=D.releases.releases.promote(t,{targetEnvironmentId:L.id,targetResourceId:z.id,config:zc(k.config),approvalRequired:k.approval});n(`Promoted without rebuild: ${Y.id} (${Y.status})`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}finally{D.controlPlane.store.close()}}),R.command("release:approve <release>","Approve or reject an environment gate").option("--actor <id>","Approving actor ID").option("--reject","Reject instead").option("--comment <text>","Approval comment").action(async(t,m)=>{let k=await Nr();try{if(!m.actor)throw Error("--actor is required");let D=k.releases.releases.approve(t,{actorId:m.actor,decision:m.reject?"rejected":"approved",comment:m.comment});n(`Release is ${D.status}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("release:activate <release>","Queue immutable artifact activation").action(async(t)=>{let m=await Nr();try{let k=m.releases.releases.get(t);if(!k)throw Error("Release was not found");let D=m.releases.enqueueActivation(k);n(`Activation queued: ${D.id}`)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}finally{m.controlPlane.store.close()}}),R.command("release:rollback <release>","Restore the preserved prior immutable release").option("--to <release>","Explicit preserved release ID").action(async(t,m)=>{let k=await Nr();try{let D=k.releases.releases.get(t);if(!D)throw Error("Release was not found");let L=k.releases.enqueueRollback(D,{targetReleaseId:m.to});n(`Rollback queued: ${L.id}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("release:health <release>","Record activation health and trigger bounded rollback").option("--healthy","Health gate passed").option("--operation <id>","Activation operation ID").option("--message <text>","Health summary").action(async(t,m)=>{let k=await Nr();try{let D=k.releases.completeHealthGate(t,{healthy:!!m.healthy,operationId:m.operation,message:m.message});n(D.rollbackOperation?`Health failed; rollback queued: ${D.rollbackOperation.id}`:`Release is ${D.release.status}`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}finally{k.controlPlane.store.close()}}),R.command("release:pin <release>","Pin or unpin a release against retention").option("--remove","Remove pin").option("--reason <text>","Pin reason").action(async(t,m)=>{let k=await Nr();try{let D=k.releases.releases.pin(t,!m.remove,m.reason);n(D.pinned?"Release pinned.":"Release unpinned.")}finally{k.controlPlane.store.close()}})}import{existsSync as mf}from"node:fs";function Vc(R){R.command("assets:build","Run the configured build hooks for an environment").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).action(async(t)=>{i("Building Assets");try{let m=await Ne(),k=t?.env||"production",L=m.environments?.[k]?.app?.build??[];if(!L.length){r(`No build hooks defined for '${k}'. Set environments.${k}.app.build = ['bun run build', …] in cloud.config.`),process.exitCode=1;return}let{execSync:z}=await import("node:child_process");for(let Y of L)u(`$ ${Y}`),z(Y,{stdio:"inherit"});n("Build hooks complete")}catch(m){r(`Build failed: ${m.message}`),process.exitCode=1}});for(let t of["assets:optimize:images","images:optimize"])R.command(t,"Optimize images (not implemented)").option("--dir <directory>","Directory to optimize").action(async()=>{r(`'${t}' is not implemented — ts-cloud has no built-in image optimizer.`),e("Optimize images in your own build step (e.g. sharp/squoosh), then `cloud assets:deploy`."),process.exitCode=1});R.command("assets:deploy","Deploy static assets to S3").option("--source <path>","Source directory",{default:"dist"}).option("--bucket <name>","S3 bucket name").option("--prefix <prefix>","S3 prefix/folder").option("--delete","Delete files not in source").option("--cache-control <value>","Cache-Control header",{default:"public, max-age=31536000"}).action(async(t)=>{i("Deploying Assets to S3");try{let k=(await Ne()).project.region||"us-east-1",D=t?.source||"dist",L=t?.bucket,z=t?.prefix,Y=t?.delete||!1,X=t?.cacheControl||"public, max-age=31536000";if(!L){r("--bucket is required");return}if(!mf(D)){r(`Source directory not found: ${D}`);return}if(e(`Source: ${D}`),e(`Bucket: s3://${L}${z?`/${z}`:""}`),e(`Cache-Control: ${X}`),Y)a("Delete mode enabled - files not in source will be removed");if(!await d(`
|
|
370
370
|
Deploy assets now?`,!0)){e("Deployment cancelled");return}let re=new g(k),ie=new s("Uploading assets to S3...");ie.start(),await re.sync({source:D,bucket:L,prefix:z,delete:Y,cacheControl:X,acl:"public-read"}),ie.succeed("Assets deployed successfully!");let be=(await re.getBucketSize(L,z)/1024/1024).toFixed(2);n(`
|
|
371
371
|
Deployment complete!`),e(`Total size: ${be} MB`),e(`
|
|
372
372
|
Assets URL: https://${L}.s3.${k}.amazonaws.com${z?`/${z}`:""}`)}catch(m){r(`Deployment failed: ${m.message}`)}}),R.command("assets:invalidate","Invalidate CloudFront cache").option("--distribution <id>","CloudFront distribution ID").option("--paths <paths>","Paths to invalidate (comma-separated)",{default:"/*"}).option("--wait","Wait for invalidation to complete").action(async(t)=>{i("Invalidating CloudFront Cache");try{let m=t?.distribution;if(!m){r("--distribution is required");return}let D=(t?.paths||"/*").split(",").map((re)=>re.trim()),L=t?.wait||!1;if(e(`Distribution: ${m}`),e(`Paths: ${D.join(", ")}`),!await d(`
|
|
373
373
|
Invalidate cache now?`,!0)){e("Invalidation cancelled");return}let Y=new E,X=new s("Creating invalidation...");X.start();let ne=await Y.invalidatePaths(m,D);if(X.succeed("Invalidation created"),n(`
|
|
374
374
|
Invalidation ID: ${ne.Id}`),e(`Status: ${ne.Status}`),e(`Created: ${new Date(ne.CreateTime).toLocaleString()}`),L){let re=new s("Waiting for invalidation to complete...");re.start(),await Y.waitForInvalidation(m,ne.Id),re.succeed("Invalidation completed!")}else e(`
|
|
375
|
-
Invalidation is in progress. Use --wait to wait for completion.`)}catch(m){r(`Invalidation failed: ${m.message}`)}})}function qc(R){for(let[t,m]of[["team:add <email> <role>","Invite an organization member"],["team:list","List organization members"],["team:remove <email>","Revoke organization membership"]])R.command(t,m).action(async(...k)=>Pn(t.split(" ")[0],{target:String(k[0]??""),message:"Legacy IAM-backed team commands are deprecated and cannot safely represent organization-scoped access.",nextAction:"Use the Team dashboard or organization invitation API; this command exits without changing IAM."}))}import{execFileSync as Yf,execSync as dl}from"node:child_process";import{copyFileSync as Ds,existsSync as kn,mkdirSync as au,readFileSync as ll,writeFileSync as Zf}from"node:fs";import{tmpdir as cu}from"node:os";import{dirname as Qf,join as lu,resolve as Xf}from"node:path";import{existsSync as Hc,readdirSync as Vd,statSync as Gc}from"node:fs";import{join as ia}from"node:path";var pf=new Set([".DS_Store","Thumbs.db",".gitkeep",".keep"]);function qd(R){return pf.has(R)}function Hd(R,t=0){if(t>8)return!0;let m;try{m=Vd(R)}catch{return!1}for(let k of m){if(qd(k))continue;let D=ia(R,k),L;try{L=Gc(D)}catch{continue}if(L.isFile())return!0;if(L.isDirectory()&&Hd(D,t+1))return!0}return!1}function ff(R){let t;try{t=Vd(R)}catch{return[]}let m=[];for(let k of t){if(qd(k))continue;let D=ia(R,k);try{if(Gc(D).isDirectory()&&Hc(ia(D,"index.html")))m.push(k)}catch{}}return m.sort((k,D)=>k.localeCompare(D))}function Gd(R){let{root:t,siteName:m,expectIndex:k}=R,D=[];if(!Hc(t))return D.push({level:"error",message:`Build output not found at ${t} for site '${m}'`}),D;if(!Gc(t).isDirectory())return D.push({level:"error",message:`Site '${m}' has root ${t}, which is a file, not a directory`}),D;if(!Hd(t))return D.push({level:"error",message:`Site '${m}' would ship an empty release: ${t} contains no files. Check that the build ran and wrote where 'root' points.`}),D;if(!k)return D;if(Hc(ia(t,"index.html")))return D;let L=ff(t);if(L.length===1){let z=`${t.replace(/\/+$/,"")}/${L[0]}`;return D.push({level:"error",message:`Site '${m}' has no index.html at the top of ${t}, but ${z} has one. The generator wrote one level down, so this release would serve 404s at the domain root. Set root: '${z}'.`}),D}if(L.length>1)return D.push({level:"warning",message:`Site '${m}' has no index.html at the top of ${t}. These subdirectories have one: ${L.join(", ")}. The domain root will 404.`}),D;return D.push({level:"warning",message:`Site '${m}' has no index.html anywhere under ${t}. The domain root will 404.`}),D}function gf(R){if(!R)return[];if(Array.isArray(R))return R.map(String);let t=R,m=t.Items??t.Item;if(!m)return[];if(Array.isArray(m))return m.map(String);if(typeof m==="object"&&m!==null){let k=m;if("Item"in k){let D=k.Item;if(!D)return[];return Array.isArray(D)?D.map(String):[String(D)]}if("CNAME"in k){let D=k.CNAME;if(!D)return[];return Array.isArray(D)?D.map(String):[String(D)]}return Object.values(k).flatMap((D)=>{if(Array.isArray(D))return D.map(String);return typeof D==="string"?[D]:[]})}return[String(m)]}async function oa(R){if(R.length===0)return;let t=new E,m=await t.listDistributions(),k=[...new Set(R.map((D)=>D.toLowerCase()))];for(let D of k){let L=m.find((Y)=>gf(Y.Aliases).some((ne)=>ne.toLowerCase()===D));if(!L?.Id){a(`No CloudFront distribution found for ${D} — skipping dynamic HTTP method sync`);continue}if(await t.ensureDynamicHttpMethods(L.Id))n(`CloudFront ${L.Id} (${D}): enabled POST/PUT/PATCH/DELETE on dynamic paths`);else e(`CloudFront ${L.Id} (${D}): already allows dynamic HTTP methods`)}}import{execSync as hf}from"node:child_process";async function yf(R,t,m,k={},D=process.cwd()){if(!R)return;if(k.step?.(`Running ${m} hook`),typeof R==="string"){hf(R,{stdio:"inherit",cwd:D});return}await R(t)}async function ks(R,t,m={},k){try{return await yf(R.hooks?.[t],R,t,m,k),!0}catch(D){return m.error?.(`${t} hook failed: ${D?.message||D}`),!1}}var yi=(R)=>R===void 0?void 0:R?"on":"off";function Jc(R){let t={ssl:R.ssl,always_use_https:yi(R.alwaysUseHttps),automatic_https_rewrites:yi(R.automaticHttpsRewrites),min_tls_version:R.minTlsVersion,tls_1_3:yi(R.tls13),brotli:yi(R.brotli),http3:yi(R.http3),"0rtt":yi(R.zeroRtt),early_hints:yi(R.earlyHints),browser_cache_ttl:R.browserCacheTtl,always_online:yi(R.alwaysOnline),websockets:yi(R.websockets),email_obfuscation:yi(R.emailObfuscation)};if(R.hsts)t.security_header={strict_transport_security:{enabled:R.hsts.enabled,max_age:R.hsts.maxAge??31536000,include_subdomains:R.hsts.includeSubdomains??!1,preload:R.hsts.preload??!1,nosniff:R.hsts.noSniff??!0}};for(let m of Object.keys(t))if(t[m]===void 0)delete t[m];return{...t,...R.raw??{}}}var As={ssl:"strict",alwaysUseHttps:!0,automaticHttpsRewrites:!0,minTlsVersion:"1.2",tls13:!0,brotli:!0,http3:!0,earlyHints:!0,alwaysOnline:!0,emailObfuscation:!1,browserCacheTtl:0};import{connect as vf}from"node:tls";async function Yc(R){let{address:t,serverName:m,port:k=443,timeoutMs:D=5000}=R;return new Promise((L)=>{let z=!1,Y=(ne)=>{if(z)return;z=!0;try{X.destroy()}catch{}L(ne)},X=vf({host:t,port:k,servername:m,rejectUnauthorized:!1,timeout:D});X.once("secureConnect",()=>{if(X.authorized){Y({reachable:!0,trusted:!0});return}Y({reachable:!0,trusted:!1,reason:X.authorizationError?String(X.authorizationError):"origin certificate is not trusted for this hostname"})}),X.once("timeout",()=>{Y({reachable:!1,trusted:!1,reason:`no TLS handshake within ${D}ms`})}),X.once("error",(ne)=>{Y({reachable:!1,trusted:!1,reason:ne.message})})})}function bf(R,t){let m=t.replace(/\.$/,"").toLowerCase();return R.some((k)=>{let D=k.replace(/\.$/,"").toLowerCase();if(D===m)return!0;if(!D.startsWith("*."))return!1;let L=D.slice(2);if(!m.endsWith(`.${L}`))return!1;return!m.slice(0,m.length-L.length-1).includes(".")})}async function Zc(R){let{provider:t,zone:m,hosts:k,ipv4:D,ipv6:L,proxied:z=!0,settings:Y=As,cache:X,originGuard:ne,purge:re=!0,ttl:ie=300,skipOriginProbe:ae=!1,skipEdgeCertificateCheck:be=!1}=R,Ae={records:[],settingsChanged:[],cacheRules:0,originGuard:!1,purged:!1,deferredProxy:[],warnings:[]};if(k.length===0)return Ae;let le=[],ke=z&&!be?await t.listEdgeCertificateHosts(m):null;for(let Se of k){let Re=z;if(Re&&ke&&!bf(ke,Se)){Re=!1;let Ve=ke.length===0?"the zone has no active edge certificate yet":`no active edge certificate covers it (${ke.join(", ")})`;Ae.deferredProxy.push({host:Se,reason:Ve}),Ae.warnings.push(`${Se}: publishing DNS-only — ${Ve}. Proxying it would make Cloudflare terminate TLS with no certificate for the name, which fails the handshake outright rather than degrading. Universal SSL covers the apex and ONE level of subdomain, so a deeper name needs its own certificate; otherwise re-run the deploy once the certificate is active.`)}if(Re&&!ae){let Ve=await Yc({address:D,serverName:Se});if(!Ve.trusted){Re=!1;let Le=Ve.reason||"origin is not serving a trusted certificate";Ae.deferredProxy.push({host:Se,reason:Le}),Ae.warnings.push(`${Se}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${Le}). The origin issues certificates over HTTP-01, which needs the name to reach it directly; re-run the deploy once it has one and the record will be proxied.`)}}if(Re)le.push(Se);let Pe=[{type:"A",content:D}];if(L&&eo(Se))Pe.push({type:"AAAA",content:L});for(let{type:Ve,content:Le}of Pe){let Fe=await t.upsertRecord(m,{name:Se,type:Ve,content:Le,ttl:ie,proxied:Re});if(Fe.success)Ae.records.push({host:Se,type:Ve,content:Le,proxied:Re});else Ae.warnings.push(`${Se} ${Ve} → ${Le} failed: ${Fe.message||"unknown provider error"}`)}}if(le.length===0){if(z)Ae.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");return Ae}try{let Se=Jc(Y),Re=await t.applyZoneSettings(m,Se);Ae.settingsChanged=Re.changed;for(let Pe of Re.failed)Ae.warnings.push(`zone setting '${Pe.id}' could not be applied: ${Pe.error}`)}catch(Se){Ae.warnings.push(`zone settings could not be read: ${Se instanceof Error?Se.message:String(Se)}`)}let De=Ls(le,X);if(De.length>0){let Se=await t.putManagedPhaseRules(m,"http_request_cache_settings",De);if(Se.success)Ae.cacheRules=De.length;else Ae.warnings.push(`cache rules could not be written: ${Se.message}`)}if(ne){let Se=Ms(le,ne.header,ne.value);if(Se.length>0){let Re=await t.putManagedPhaseRules(m,"http_request_late_transform",Se);if(Re.success)Ae.originGuard=!0;else Ae.warnings.push(`origin guard header could not be written: ${Re.message}. `+"The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.")}}if(re){if(Ae.purged=await t.purgeCache(m,{hosts:le}),!Ae.purged)Ae.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.")}return Ae}var wf=(R)=>process.env[R];function Kd(R,t){let m=R.replace(/\.$/,"").toLowerCase();if(t){let k=t.replace(/\.$/,"").toLowerCase();if(m===k||m.endsWith(`.${k}`))return k}return m.split(".").slice(-2).join(".")}function Qc(R,t={}){let m=t.env??wf,k=R.infrastructure?.compute?.proxy?.cdn,D=[];if(!k||k.provider!=="cloudflare")return{plan:null,errors:D};if(k.originDomain)D.push(`compute.proxy.cdn.originDomain (${k.originDomain}) is ignored for Cloudflare: the proxy forwards to the address in the record it fronts, `+"so no separate origin hostname is needed — and publishing one gives clients a documented way around the edge.");let L=m("CLOUDFLARE_API_TOKEN");if(!L)return D.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled."),{plan:null,errors:D};let z=R.sites??{},Y=k.frontedHosts?.length?[...new Set(k.frontedHosts.map((ae)=>ae.toLowerCase()))]:no(z,{autoWww:R.infrastructure?.compute?.proxy?.autoWww!==!1});if(Y.length===0)return D.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain."),{plan:null,errors:D};let X=R.infrastructure?.dns?.domain,ne=new Set(Y.map((ae)=>Kd(ae,X)));if(ne.size>1)return D.push(`Fronted hosts span more than one zone (${[...ne].join(", ")}). A Cloudflare zone is configured as a unit, so run one CDN config per zone.`),{plan:null,errors:D};let re=k.cloudflare??{},ie=k.secret?{header:k.secretHeader??"X-Origin-Verify",value:k.secret}:void 0;return{plan:{zone:[...ne][0],hosts:Y,apiToken:L,zoneId:re.zoneId??m("CLOUDFLARE_ZONE_ID"),accountId:re.accountId??m("CLOUDFLARE_ACCOUNT_ID"),proxied:re.proxied!==!1,settings:re.settings??As,cache:re.cache,originGuard:ie,purge:re.purgeOnDeploy!==!1,skipOriginProbe:re.skipOriginProbe===!0},errors:D}}var Sf="https://api.cloudflare.com/client/v4",Yd=10042,Cf=10006,$f=10059;class wo extends Error{status;errors;constructor(R,t,m=[]){super(R);this.name="R2ApiError",this.status=t,this.errors=m}hasCode(R){return this.errors.some((t)=>t.code===R)}}class Hi extends wo{accountId;constructor(R,t,m=[]){super(`R2 is not enabled on Cloudflare account ${R}. Enable it once in the Cloudflare dashboard (R2 Object Storage, then follow the prompt to activate it), then re-run the deploy.`,t,m);this.name="R2NotEnabledError",this.accountId=R}}class zo{name="cloudflare-r2";apiToken;accountId;fetchImpl;constructor(R){if(!R.apiToken)throw Error("Cloudflare R2 needs an API token");if(!R.accountId)throw Error("Cloudflare R2 needs an account id");this.apiToken=R.apiToken,this.accountId=R.accountId,this.fetchImpl=R.fetch??((t,m)=>fetch(t,m))}async requestEnvelope(R,t,m,k={}){let D={Authorization:`Bearer ${this.apiToken}`};if(m!==void 0)D["Content-Type"]="application/json";if(k.jurisdiction&&k.jurisdiction!=="default")D["cf-r2-jurisdiction"]=k.jurisdiction;let L=await this.fetchImpl(`${Sf}/accounts/${this.accountId}/r2${t}`,{method:R,headers:D,body:m===void 0?void 0:JSON.stringify(m)}),z=await L.text(),Y;try{Y=JSON.parse(z)}catch{throw new wo(`Cloudflare R2 API returned ${L.status} with a non-JSON body: ${z.slice(0,200)}`,L.status)}if(!Y.success){let X=Y.errors??[];if(X.some((re)=>re.code===Yd))throw new Hi(this.accountId,L.status,X);let ne=X.map((re)=>`${re.code}: ${re.message}`).join(", ");throw new wo(`Cloudflare R2 API error (${L.status}) on ${R} ${t}: ${ne||z.slice(0,200)}`,L.status,X)}return Y}async request(R,t,m,k){return(await this.requestEnvelope(R,t,m,k)).result}bucketPath(R){return`/buckets/${encodeURIComponent(R)}`}async listBuckets(R={}){let t=[],m;do{let k=new URLSearchParams({per_page:"1000"});if(m)k.set("cursor",m);let D=await this.requestEnvelope("GET",`/buckets?${k}`,void 0,R),L=D.result?.buckets??[];t.push(...L),m=L.length>0?D.result_info?.cursor||void 0:void 0}while(m);return t}async getBucket(R,t={}){try{return await this.request("GET",this.bucketPath(R),void 0,t)}catch(m){if(m instanceof Hi)throw m;if(m instanceof wo&&(m.hasCode(Cf)||m.status===404))return null;throw m}}async ensureBucket(R,t={}){let m={jurisdiction:t.jurisdiction},k=await this.getBucket(R,m);if(k)return{bucket:k,created:!1};let D={name:R};if(t.locationHint)D.locationHint=t.locationHint;let L=await this.request("POST","/buckets",D,m);return{bucket:{...L,name:L?.name||R},created:!0}}async getCors(R,t={}){try{return(await this.request("GET",`${this.bucketPath(R)}/cors`,void 0,t))?.rules??[]}catch(m){if(m instanceof wo&&!(m instanceof Hi)&&(m.hasCode($f)||m.status===404))return[];throw m}}async putCors(R,t,m={}){if(t.length===0){await this.request("DELETE",`${this.bucketPath(R)}/cors`,void 0,m);return}await this.request("PUT",`${this.bucketPath(R)}/cors`,{rules:t},m)}async ensureCors(R,t,m={}){let k=await this.getCors(R,m);if(sa(k,t))return!1;return await this.putCors(R,t,m),!0}async getLifecycle(R,t={}){return(await this.request("GET",`${this.bucketPath(R)}/lifecycle`,void 0,t))?.rules??[]}async putLifecycle(R,t,m={}){await this.request("PUT",`${this.bucketPath(R)}/lifecycle`,{rules:t},m)}async ensureLifecycle(R,t,m={}){let k=await this.getLifecycle(R,m);if(ca(k,t))return!1;return await this.putLifecycle(R,t,m),!0}async listCustomDomains(R,t={}){return(await this.request("GET",`${this.bucketPath(R)}/domains/custom`,void 0,t))?.domains??[]}async attachCustomDomain(R,t,m={}){return this.request("POST",`${this.bucketPath(R)}/domains/custom`,{domain:t.domain,zoneId:t.zoneId,enabled:t.enabled??!0,minTLS:t.minTLS??"1.2"},m)}async updateCustomDomain(R,t,m,k={}){return this.request("PUT",`${this.bucketPath(R)}/domains/custom/${encodeURIComponent(t)}`,m,k)}async ensureCustomDomain(R,t,m={},k){let D=t.minTLS??"1.2",z=(k??await this.listCustomDomains(R,m)).find((ne)=>ne.domain.toLowerCase()===t.domain.toLowerCase());if(!z){let ne=await this.attachCustomDomain(R,{...t,minTLS:D,enabled:!0},m);return{action:"attached",domain:{...ne,domain:ne?.domain||t.domain}}}let Y=z.minTLS??"1.0";if(z.enabled&&Y===D)return{action:"unchanged",domain:z};let X=await this.updateCustomDomain(R,z.domain,{enabled:!0,minTLS:D},m);return{action:"updated",domain:{...z,...X}}}async getManagedDomain(R,t={}){return this.request("GET",`${this.bucketPath(R)}/domains/managed`,void 0,t)}async setManagedDomain(R,t,m={}){let k=await this.getManagedDomain(R,m);if(k?.enabled===t)return{changed:!1,domain:k};return{changed:!0,domain:await this.request("PUT",`${this.bucketPath(R)}/domains/managed`,{enabled:t},m)}}}function Rs(R){if(Array.isArray(R))return`[${R.map(Rs).join(",")}]`;if(R&&typeof R==="object")return`{${Object.entries(R).filter(([,m])=>m!==void 0).sort(([m],[k])=>m.localeCompare(k)).map(([m,k])=>`${JSON.stringify(m)}:${Rs(k)}`).join(",")}}`;return JSON.stringify(R)}function Jd(R,t){let m=(k)=>k&&k.length>0?[...k].map(String).sort():void 0;return{id:t?R.id:void 0,allowed:{origins:m(R.allowed?.origins),methods:m(R.allowed?.methods?.map((k)=>k.toUpperCase())),headers:m(R.allowed?.headers?.map((k)=>k.toLowerCase()))},exposeHeaders:m(R.exposeHeaders?.map((k)=>k.toLowerCase())),maxAgeSeconds:R.maxAgeSeconds}}function sa(R,t){if(R.length!==t.length)return!1;return t.every((m,k)=>{let D=m.id!==void 0;return Rs(Jd(R[k],D))===Rs(Jd(m,D))})}function ca(R,t){if(R.length!==t.length)return!1;let m=(k)=>Rs([...k].map((D)=>({...D,storageClassTransitions:D.storageClassTransitions?.length?D.storageClassTransitions:void 0})).sort((D,L)=>D.id.localeCompare(L.id)));return m(R)===m(t)}var kf="r2 cache",Af=/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;async function tl(R,t={}){let m=t.log??(()=>{}),k={buckets:[],warnings:[]},D=R.infrastructure?.r2??R.r2,L=Object.entries(D?.buckets??{});if(L.length===0)return m("R2: no buckets declared, skipping."),k;if(!t.apiToken||!t.accountId){let re=[!t.apiToken&&"CLOUDFLARE_API_TOKEN",!t.accountId&&"CLOUDFLARE_ACCOUNT_ID"].filter(Boolean).join(" and "),ie=`R2: ${L.length} bucket(s) declared but ${re} is not set, skipping.`;return m(ie),k.warnings.push(ie),k}let z=t.dryRun===!0,Y=new zo({apiToken:t.apiToken,accountId:t.accountId,fetch:t.fetch}),X=new he(t.apiToken,{zoneId:t.zoneId,accountId:t.accountId}),ne=new Map;for(let[re,ie]of L){let ae=ie?.name;if(!ae||!Af.test(ae)){k.warnings.push(`R2: bucket '${re}' has an invalid name '${ae??""}' (3-63 chars: lowercase letters, digits, hyphens), skipping.`);continue}m(`R2: ${z?"checking":"reconciling"} bucket ${ae}...`);let be={name:ae,changes:[],domains:[]};if(k.buckets.push(be),await Rf(ie,{r2:Y,cloudflare:X,dryRun:z,summary:be,warnings:k.warnings,claimed:ne,log:m}),be.changes.length===0)m(`R2: ${ae} is in sync.`);else for(let Ae of be.changes)m(`R2: ${ae}: ${Ae}`)}return k}async function Rf(R,t){let{r2:m,dryRun:k,summary:D,warnings:L}=t,z=R.name,Y={jurisdiction:R.jurisdiction},X=(be,Ae)=>{let le=k?Ae&&`would ${Ae}`:be;if(le)D.changes.push(le)},ne;if(k)ne=await m.getBucket(z,Y)!==null;else{let{created:be}=await m.ensureBucket(z,{locationHint:R.locationHint,jurisdiction:R.jurisdiction});if(ne=!0,be)X("created","")}if(!ne)X("","create bucket");let re=Pf(R.customDomains);await Is(t,`cors on ${z}`,async()=>{if(!R.cors)return;if(!ne){X("cors updated","set cors");return}let be=await m.getCors(z,Y);if(sa(be,R.cors))return;if(!k)await m.putCors(z,R.cors,Y);X(R.cors.length===0?"cors removed":"cors updated",R.cors.length===0?"remove cors":"update cors")}),await Is(t,`lifecycle on ${z}`,async()=>{if(!R.lifecycle)return;let be=R.lifecycle.map(Xd);if(ne){let Ae=await m.getLifecycle(z,Y);if(ca(Ae,be))return}if(!k)await m.putLifecycle(z,be,Y);X("lifecycle updated","update lifecycle")}),await Is(t,`r2.dev URL on ${z}`,async()=>{let be=R.publicDevUrl===!0;if(!ne){if(be)X("r2.dev URL enabled","enable r2.dev URL");return}if((await m.getManagedDomain(z,Y))?.enabled===be)return;if(!k)await m.setManagedDomain(z,be,Y);X(be?"r2.dev URL enabled":"r2.dev URL disabled",be?"enable r2.dev URL":"disable r2.dev URL")});let ie=[];if(re.length>0&&ne)try{ie=await m.listCustomDomains(z,Y)}catch(be){if(be instanceof Hi)throw be;L.push(`R2: could not list custom domains on ${z}: ${nl(be)}`)}let ae=new Map;for(let{domain:be,minTLS:Ae}of re){let le=t.claimed.get(be);if(le&&le!==z){L.push(`R2: ${be} is declared on both ${le} and ${z}; leaving it on ${le}.`);continue}t.claimed.set(be,z);let ke=ie.find((Se)=>Se.domain.toLowerCase()===be),De=Ae??"1.2";if(ke&&ke.enabled&&(ke.minTLS??"1.0")===De){if(D.domains.push({domain:be,status:el(ke)}),ke.zoneId)ae.set(be,ke.zoneId);continue}await Is(t,`custom domain ${be} on ${z}`,async()=>{if(ke){if(!k)await m.updateCustomDomain(z,ke.domain,{enabled:!0,minTLS:De},Y);if(X(`custom domain ${be} updated`,`update custom domain ${be}`),D.domains.push({domain:be,status:el({...ke,enabled:!0})}),ke.zoneId)ae.set(be,ke.zoneId);return}let Se=await t.cloudflare.zoneIdFor(be);if(ae.set(be,Se),!k)await m.ensureCustomDomain(z,{domain:be,zoneId:Se,minTLS:De},Y,ie);X(`custom domain ${be} attached`,`attach custom domain ${be}`),D.domains.push({domain:be,status:k?"not attached":"pending"})},()=>D.domains.push({domain:be,status:"error"}))}if(R.cache&&re.length>0)await If(R,re.map((be)=>be.domain).filter((be)=>t.claimed.get(be)===z),ae,t,X)}async function If(R,t,m,k,D){let L=Zd(R.cache);if(!L||t.length===0)return;let z=new Map;for(let Y of t){let X=m.get(Y);if(!X)try{X=await k.cloudflare.zoneIdFor(Y)}catch(ne){k.warnings.push(`R2: could not resolve the zone for ${Y}, cache rule skipped: ${nl(ne)}`);continue}z.set(X,[...z.get(X)??[],Y])}for(let Y of z.values()){let X={action:"set_cache_settings",description:`${Pr} ${kf}`,expression:`(${Ir(Y)})`,enabled:!0,action_parameters:L};await Is(k,`cache rule for ${Y.join(", ")}`,async()=>{let ne=await k.cloudflare.getPhaseRules(Y[0],"http_request_cache_settings");if(Qd(ne,X))return;if(!k.dryRun){let re=await k.cloudflare.putManagedPhaseRules(Y[0],"http_request_cache_settings",[X]);if(!re.success)throw Error(re.message||"cache rule write failed")}D(`cache rule updated for ${Y.join(", ")}`,`update cache rule for ${Y.join(", ")}`)})}}function Zd(R){if(R.edgeTtl===void 0&&R.browserTtl===void 0)return null;let t={cache:!0};if(R.edgeTtl!==void 0)t.edge_ttl={mode:"override_origin",default:R.edgeTtl};if(R.browserTtl!==void 0)t.browser_ttl={mode:"override_origin",default:R.browserTtl};return t}function Qd(R,t){let m=Dr(t.expression),k=Pi(t.expression),D=(Y)=>(Y.description||"").startsWith(Pr),L=(Y)=>Y.action===t.action&&Y.description===t.description&&(Y.enabled??!0)===(t.enabled??!0)&&Pi(Y.expression)===k&&la(Y.action_parameters)===la(t.action_parameters),z=!1;for(let Y of R){if(!D(Y))continue;let X=Dr(Y.expression);if(!m.some((re)=>X.includes(re)))continue;if(L(Y)&&m.every((re)=>X.includes(re))&&!z){z=!0;continue}return!1}return z}function Xd(R){let t={id:R.id,enabled:R.enabled??!0,conditions:{prefix:R.prefix??""}};if(R.expireAfterDays!==void 0)t.deleteObjectsTransition={condition:{type:"Age",maxAge:Xc(R.expireAfterDays)}};else if(R.expireOn!==void 0)t.deleteObjectsTransition={condition:{type:"Date",date:R.expireOn}};if(R.abortMultipartUploadsAfterDays!==void 0)t.abortMultipartUploadsTransition={condition:{type:"Age",maxAge:Xc(R.abortMultipartUploadsAfterDays)}};if(R.infrequentAccessAfterDays!==void 0)t.storageClassTransitions=[{storageClass:"InfrequentAccess",condition:{type:"Age",maxAge:Xc(R.infrequentAccessAfterDays)}}];return t}function Xc(R){return Math.round(R*86400)}function Pf(R){let t=new Set,m=[];for(let k of R??[]){let D=typeof k==="string"?{domain:k}:k,L=D.domain?.trim().replace(/\.$/,"").toLowerCase();if(!L||t.has(L))continue;t.add(L),m.push({domain:L,minTLS:D.minTLS})}return m}function el(R){if(!R.enabled)return"disabled";let t=R.status?.ownership,m=R.status?.ssl;if(t==="active"&&m==="active")return"active";for(let k of[t,m])if(k==="error"||k==="blocked"||k==="deactivated")return k;return"pending"}async function Is(R,t,m,k){try{await m()}catch(D){if(D instanceof Hi)throw D;R.warnings.push(`R2: ${t} failed: ${nl(D)}`),k?.()}}function nl(R){return R instanceof Error?R.message:String(R)}function la(R){if(Array.isArray(R))return`[${R.map(la).join(",")}]`;if(R&&typeof R==="object")return`{${Object.entries(R).filter(([,m])=>m!==void 0).sort(([m],[k])=>m.localeCompare(k)).map(([m,k])=>`${JSON.stringify(m)}:${la(k)}`).join(",")}}`;return JSON.stringify(R)}import{basename as Lf,extname as Ff}from"node:path";import{createHash as xf}from"node:crypto";var Df="https://api.cloudflare.com/client/v4",Gi="TS_CLOUD_CONTENT_HASH",Ps="2025-09-01",il="application/javascript+module",Ef=10007,Tf=[1e4,9109];class Wo extends Error{status;errors;constructor(R,t,m=[]){super(R);this.name="WorkersApiError",this.status=t,this.errors=m}hasCode(R){return this.errors.some((t)=>t.code===R)}}class da extends Wo{permission;constructor(R,t,m,k=[]){super(`The Cloudflare API token is missing the "${R}" permission (${t}). Edit the token in the Cloudflare dashboard (My Profile, API Tokens), add it, then re-run the deploy.`,m,k);this.name="WorkersPermissionError",this.permission=R}}class ua{name="cloudflare-workers";apiToken;accountId;fetchImpl;constructor(R){if(!R.apiToken)throw Error("Cloudflare Workers needs an API token");if(!R.accountId)throw Error("Cloudflare Workers needs an account id");this.apiToken=R.apiToken,this.accountId=R.accountId,this.fetchImpl=R.fetch??((t,m)=>fetch(t,m))}async request(R,t,m){let k={Authorization:`Bearer ${this.apiToken}`},D;if(m instanceof FormData)D=m;else if(m!==void 0)k["Content-Type"]="application/json",D=JSON.stringify(m);let L=await this.fetchImpl(`${Df}/accounts/${this.accountId}/workers${t}`,{method:R,headers:k,body:D}),z=await L.text(),Y;try{Y=JSON.parse(z)}catch{throw new Wo(`Cloudflare Workers API returned ${L.status} with a non-JSON body: ${z.slice(0,200)}`,L.status)}if(!Y.success){let X=Y.errors??[],ne=X.map((re)=>`${re.code}: ${re.message}`).join(", ");if(Nf(L.status,X))throw new da(Of(t),`${R} ${t} -> ${L.status}${ne?`, ${ne}`:""}`,L.status,X);throw new Wo(`Cloudflare Workers API error (${L.status}) on ${R} ${t}: ${ne||z.slice(0,200)}`,L.status,X)}return Y.result}scriptPath(R){return`/scripts/${encodeURIComponent(R)}`}async getSettings(R){try{return await this.request("GET",`${this.scriptPath(R)}/settings`)}catch(t){if(t instanceof da)throw t;if(t instanceof Wo&&(t.hasCode(Ef)||t.status===404))return null;throw t}}async getScript(R){return this.getSettings(R)}async uploadModuleScript(R,t){return this.request("PUT",this.scriptPath(R),tu(t))}async ensureModuleScript(R,t){let m=xs(t),k=await this.getSettings(R);if(k&&ma(k,t,m))return{action:"unchanged",hash:m};return await this.uploadModuleScript(R,nu(t,m)),{action:k?"updated":"created",hash:m}}async listCustomDomains(R={}){let t=new URLSearchParams;if(R.service)t.set("service",R.service);if(R.hostname)t.set("hostname",R.hostname);if(R.zoneId)t.set("zone_id",R.zoneId);let m=t.toString(),k=m?`?${m}`:"";return await this.request("GET",`/domains${k}`)??[]}async attachCustomDomain(R,t){let m={hostname:R,service:t.service,environment:t.environment??"production"};if(t.zoneId)m.zone_id=t.zoneId;return this.request("PUT","/domains",m)}async detachCustomDomain(R){await this.request("DELETE",`/domains/${encodeURIComponent(R)}`)}async ensureCustomDomain(R,t,m){let D=(m??await this.listCustomDomains({hostname:R})).find((z)=>z.hostname.toLowerCase()===R.toLowerCase());if(D&&D.service===t.service)return{action:"unchanged",domain:D};if(D)throw Error(`${R} is attached to the Worker '${D.service}'. Detach it there first (Workers & Pages, ${D.service}, Settings, Domains & Routes); ts-cloud does not move it.`);let L=await this.attachCustomDomain(R,t);return{action:"attached",domain:{...L,hostname:L?.hostname||R}}}}function Nf(R,t){if(t.some((m)=>Tf.includes(m.code)))return!0;return(R===401||R===403)&&t.length===0}function Of(R){if(R.startsWith("/domains"))return"Workers Routes: Edit (zone, for Workers custom domains) and Workers Scripts: Edit (account)";return"Workers Scripts: Edit (account)"}function tu(R){if(!R.modules.some((k)=>k.name===R.mainModule))throw Error(`Worker main module '${R.mainModule}' is not one of the uploaded modules`);let t={main_module:R.mainModule,bindings:R.bindings??[],compatibility_date:R.compatibilityDate??Ps,compatibility_flags:R.compatibilityFlags??[],keep_bindings:["secret_text"]},m=new FormData;m.append("metadata",JSON.stringify(t));for(let k of R.modules)m.append(k.name,new Blob([k.content],{type:k.type}),k.name);return m}function xs(R){let t={mainModule:R.mainModule,modules:[...R.modules].sort((m,k)=>m.name.localeCompare(k.name)).map((m)=>({name:m.name,type:m.type,sha256:eu(m.content)})),bindings:[...R.bindings??[]].filter((m)=>m.name!==Gi).sort((m,k)=>m.name.localeCompare(k.name)),compatibilityDate:R.compatibilityDate??Ps,compatibilityFlags:[...R.compatibilityFlags??[]].sort()};return eu(rl(t))}function nu(R,t=xs(R)){return{...R,bindings:[...(R.bindings??[]).filter((m)=>m.name!==Gi),{type:"plain_text",name:Gi,text:t}]}}function ma(R,t,m){let k=R.bindings??[],D=k.find((L)=>L.name===Gi&&L.type==="plain_text");if(!D||D.text!==m)return!1;return(t.bindings??[]).every((L)=>{let z=k.find((X)=>X.name===L.name);if(!z||z.type!==L.type)return!1;let Y=L.type==="r2_bucket"?"bucket_name":L.type==="plain_text"?"text":void 0;return!Y||z[Y]===L[Y]})}function eu(R){return xf("sha256").update(R).digest("hex")}function rl(R){if(Array.isArray(R))return`[${R.map(rl).join(",")}]`;if(R&&typeof R==="object")return`{${Object.entries(R).filter(([,m])=>m!==void 0).sort(([m],[k])=>m.localeCompare(k)).map(([m,k])=>`${JSON.stringify(m)}:${rl(k)}`).join(",")}}`;return JSON.stringify(R)}async function ol(R){if(!await Bun.file(R).exists())throw Error(`Worker entry not found: ${R}`);let t;try{t=await Bun.build({entrypoints:[R],target:"browser",format:"esm",minify:!0,external:["cloudflare:*"]})}catch(D){throw Error(`Bundling Worker ${R} failed: ${jf(D)}`)}if(!t.success)throw Error(`Bundling Worker ${R} failed: ${t.logs.map((D)=>String(D.message??D)).join("; ")}`);if(t.outputs.length!==1)throw Error(`Bundling Worker ${R} produced ${t.outputs.length} files (${t.outputs.map((D)=>D.path).join(", ")}); a Worker is uploaded as a single module, so remove the dynamic imports or asset imports that split it.`);let m=t.outputs[0];if(m.kind!=="entry-point")throw Error(`Bundling Worker ${R} produced a ${m.kind} instead of an entry module`);let k=await m.text();return{name:`${Lf(R,Ff(R))}.js`,content:k,type:il,size:new TextEncoder().encode(k).byteLength}}function jf(R){if(R instanceof AggregateError&&R.errors.length>0)return R.errors.map((t)=>t instanceof Error?t.message:String(t?.message??t)).join("; ");return R instanceof Error?R.message:String(R)}import{isAbsolute as Bf,resolve as _f}from"node:path";var Mf="https://api.cloudflare.com/client/v4",zf=/^[a-z0-9][a-z0-9_-]{0,62}$/;async function sl(R,t={}){let m=t.log??(()=>{}),k={workers:[],warnings:[]},D=Object.entries(R.infrastructure?.workers??R.workers??{});if(D.length===0)return m("Workers: none declared, skipping."),k;if(!t.apiToken||!t.accountId){let Y=[!t.apiToken&&"CLOUDFLARE_API_TOKEN",!t.accountId&&"CLOUDFLARE_ACCOUNT_ID"].filter(Boolean).join(" and "),X=`Workers: ${D.length} Worker(s) declared but ${Y} is not set, skipping.`;return m(X),k.warnings.push(X),k}let L=R.infrastructure?.r2??R.r2,z={workers:new ua({apiToken:t.apiToken,accountId:t.accountId,fetch:t.fetch}),r2:new zo({apiToken:t.apiToken,accountId:t.accountId,fetch:t.fetch}),cloudflare:new he(t.apiToken,{zoneId:t.zoneId,accountId:t.accountId}),apiToken:t.apiToken,fetch:t.fetch??((Y,X)=>fetch(Y,X)),projectRoot:t.projectRoot??process.cwd(),dryRun:t.dryRun===!0,warnings:k.warnings,log:m,r2ConfigDomains:Gf(L),r2Buckets:L,claimed:new Map};for(let[Y,X]of D){let ne=X?.name;if(!ne||!zf.test(ne)){k.warnings.push(`Workers: '${Y}' has an invalid name '${ne??""}' (1-63 chars: lowercase letters, digits, '-' and '_'), skipping.`);continue}if(!X.entry){k.warnings.push(`Workers: ${ne} has no entry, skipping.`);continue}m(`Workers: ${z.dryRun?"checking":"reconciling"} ${ne}...`);let re={name:ne,changes:[],domains:[]};k.workers.push(re),await Uf(X,re,z);for(let ie of re.changes)m(`Workers: ${ne}: ${ie}`)}return k}async function Uf(R,t,m){let k=R.name,D=Bf(R.entry)?R.entry:_f(m.projectRoot,R.entry),L;try{L=await ol(D)}catch(Y){throw Error(`Worker ${k}: ${Vo(Y)}`)}t.changes.push(`bundled ${Jf(L.size)}`);let z={mainModule:L.name,modules:[{name:L.name,content:L.content,type:L.type}],bindings:ru(R,m),compatibilityDate:R.compatibilityDate??Ps,compatibilityFlags:R.compatibilityFlags??[]};if(m.dryRun){let Y=await m.workers.getSettings(k);t.changes.push(Y&&ma(Y,z,xs(z))?"unchanged":"would upload")}else{let{action:Y}=await m.workers.ensureModuleScript(k,z);t.changes.push(Y==="unchanged"?"unchanged":"uploaded")}for(let Y of Kf(R.customDomains))await Wf(k,Y,t,m)}async function Wf(R,t,m,k){let D=(be)=>{m.domains.push({domain:t,status:be})},L=(be)=>{k.warnings.push(`Workers: ${be}`),D("conflict")},z=k.claimed.get(t);if(z&&z!==R){L(`${t} is declared on both ${z} and ${R}; leaving it on ${z}.`);return}k.claimed.set(t,R);let Y=k.r2ConfigDomains.get(t);if(Y){L(`${t} is also declared as a custom domain of the R2 bucket '${Y}' (r2.buckets). A hostname serves either a bucket or a Worker. Remove it from the bucket's customDomains and detach it in the dashboard (R2, ${Y}, Settings, Custom Domains) first; to serve the bucket through the Worker, bind it with bindings.r2Buckets instead.`);return}let X=await Vf(k);if(X===null){D("error");return}let ne=X.find((be)=>be.hostname.toLowerCase()===t);if(ne?.service===R){D("active");return}if(ne){L(`${t} is attached to the Worker '${ne.service}'. Detach it there first (Workers & Pages, ${ne.service}, Settings, Domains & Routes); it is not moved automatically.`);return}let re=(await qf(k))?.get(t);if(re){L(`${t} is attached to the R2 bucket '${re}' as a custom domain. Detach it first (R2, ${re}, Settings, Custom Domains, then re-run the deploy); it is not detached automatically. To serve the bucket through the Worker, bind it with bindings.r2Buckets.`);return}let ie;try{ie=await k.cloudflare.zoneIdFor(t)}catch(be){k.warnings.push(`Workers: could not resolve the zone for ${t}, not attached: ${Vo(be)}`),D("error");return}let ae=await Hf(k,ie,t);if(ae.length>0){let be=ae.map((Ae)=>`${Ae.type} ${Ae.content}`).join(", ");L(`${t} already has a DNS record (${be}). A Worker custom domain creates its own record, so Cloudflare will not attach over it. Delete the record first (DNS, Records), then re-run the deploy; it is not deleted automatically.`);return}if(k.dryRun){m.changes.push(`would attach custom domain ${t}`),D("not attached");return}try{await k.workers.ensureCustomDomain(t,{service:R,zoneId:ie},X),m.changes.push(`custom domain ${t} attached`),X.push({id:"",hostname:t,service:R,zone_id:ie}),D("pending")}catch(be){k.warnings.push(`Workers: custom domain ${t} on ${R} failed: ${Vo(be)}`),D("error")}}async function Vf(R){if(R.attached!==void 0)return R.attached;try{R.attached=await R.workers.listCustomDomains()}catch(t){R.warnings.push(`Workers: could not list Worker custom domains, none attached: ${Vo(t)}`),R.attached=null}return R.attached}async function qf(R){if(R.r2AccountDomains!==void 0)return R.r2AccountDomains;let t=new Map;try{let m=(await R.r2.listBuckets()).map((k)=>({name:k.name}));for(let k of Object.values(R.r2Buckets?.buckets??{}))if(k.jurisdiction&&k.jurisdiction!=="default")m.push({name:k.name,jurisdiction:k.jurisdiction});for(let k of m){let D=await R.r2.listCustomDomains(k.name,{jurisdiction:k.jurisdiction}).catch(()=>[]);for(let L of D)t.set(L.domain.toLowerCase(),k.name)}R.r2AccountDomains=t}catch(m){R.log(`Workers: could not check R2 custom domains (${Vo(m)}); relying on the DNS check.`),R.r2AccountDomains=null}return R.r2AccountDomains}async function Hf(R,t,m){try{let k=await R.fetch(`${Mf}/zones/${t}/dns_records?name=${encodeURIComponent(m)}&per_page=100`,{method:"GET",headers:{Authorization:`Bearer ${R.apiToken}`}}),D=await k.json();if(!D?.success)throw Error(`HTTP ${k.status}`);return(D.result??[]).filter((L)=>L.name?.toLowerCase()===m).map((L)=>({type:L.type??"?",content:L.content??""}))}catch(k){return R.log(`Workers: could not read DNS records for ${m} (${Vo(k)}); attaching anyway.`),[]}}function ru(R,t){let m=[],k=new Set,D=(z)=>{if(z===Gi)return t.warnings.push(`Workers: ${R.name}: binding name ${Gi} is reserved for ts-cloud, ignored.`),!1;if(k.has(z))return t.warnings.push(`Workers: ${R.name}: binding ${z} is declared twice; the first one wins.`),!1;return k.add(z),!0},L=new Map;for(let z of Object.values(t.r2Buckets?.buckets??{}))if(z.jurisdiction&&z.jurisdiction!=="default")L.set(z.name,z.jurisdiction);for(let[z,Y]of Object.entries(R.bindings?.r2Buckets??{})){if(!D(z))continue;let X=L.get(Y);m.push(X?{type:"r2_bucket",name:z,bucket_name:Y,jurisdiction:X}:{type:"r2_bucket",name:z,bucket_name:Y})}for(let[z,Y]of Object.entries(R.bindings?.vars??{}))if(D(z))m.push({type:"plain_text",name:z,text:String(Y)});return m}function Gf(R){let t=new Map;for(let m of Object.values(R?.buckets??{}))for(let k of m?.customDomains??[]){let D=iu(typeof k==="string"?k:k.domain);if(D)t.set(D,m.name)}return t}function Kf(R){return[...new Set((R??[]).map(iu).filter(Boolean))]}function iu(R){return(R??"").trim().replace(/\.$/,"").toLowerCase()}function Jf(R){if(R<1024)return`${R} B`;if(R<1048576)return`${(R/1024).toFixed(1)} KB`;return`${(R/1024/1024).toFixed(1)} MB`}function Vo(R){return R instanceof Error?R.message:String(R)}async function eg(R){let t=aa();if(t.emptyEnvKey||t.emptyEnvSecret)a("Empty AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY env var detected — likely a misconfigured .env file. Remove the empty lines or fill them in.");e(`AWS credentials: ${t.description}`);try{let k=await new mt(R).getCallerIdentity();if(k.Arn)e(`AWS identity: ${k.Arn}`)}catch(m){a(`Could not verify AWS identity: ${m.message}`)}}function tg(){try{return dl("git rev-parse --short HEAD",{stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return Date.now().toString(36)}}async function al(R,t,m,k){let{ensureManagementDashboard:D}=await import("./chunk-6stcmq7g.js");D(R,{logger:{info:e,warn:a}});let L=R.sites||{},z=Object.entries(L);if(z.length===0)return a("infrastructure.compute is set but no sites are configured — nothing to deploy. Add sites to `cloud.config.ts`."),!0;if(k&&!L[k])return r(`Site '${k}' was not found. Configured sites: ${z.map(([De])=>De).join(", ")}`),!1;let Y=z.filter(([De,Se])=>{if(!Se)return!1;if(k&&De!==k)return!1;if(w(Se)==="bucket")return a(`Site '${De}' targets a bucket — skipping compute deploy (handled by the static-site path).`),!1;return Qs(Se)});if(k)e(`Deploying only '${k}'. Other sites keep their current release; proxy routes are still regenerated in full.`);let X=R.project.slug,re=(R.infrastructure?.compute||{}).runtime||"bun",ie=O({config:R}),ae=tg(),be=new Map,Ae=new Map,le={step:(De)=>u(De),error:(De)=>r(De)};if(!await ks(R,"beforeDeploy",le))return!1;if(!await ks(R,"beforeBuild",le))return!1;for(let[De,Se]of Y){let Re=w(Se);if(i(`Preparing site: ${De}`),e(`Domain: ${Se.domain||"(none)"}`),e(`Source: ${Se.root}`),e(`Kind: ${Re==="server-static"?"static (shipped to /var/www)":"app (systemd service)"}`),Re==="server-app")e(`Start: ${Se.start}`),e(`Port: ${Se.port??"(unset)"}`);if(Se.build){u(`Running build: ${Se.build}`);try{dl(Se.build,{stdio:"inherit",cwd:process.cwd()})}catch(He){return r(`Build failed for site '${De}': ${He.message}`),!1}}let Pe=Gd({root:Se.root,siteName:De,expectIndex:Re==="server-static"});for(let He of Pe)if(He.level==="error")r(He.message);else a(He.message);if(Pe.some((He)=>He.level==="error"))return!1;let Ve=Se.build?void 0:JSON.stringify([Xf(Se.root),[...Se.exclude||[]].sort()]),Le=Ve?Ae.get(Ve):void 0;if(Le){u(`Reusing package for ${Se.root} → ${Le}`),be.set(De,Le);continue}let Fe=lu(cu(),`${X}-${De}-${ae}.tar.gz`);u(`Packaging ${Se.root} → ${Fe}`);try{let He=(Se.exclude||[]).map((wt)=>`--exclude=${wt}`);Yf("tar",["czf",Fe,"-C",Se.root,...He,"."],{stdio:"inherit",env:{...process.env,COPYFILE_DISABLE:"1"}})}catch(He){return r(`Failed to package '${De}': ${He.message}`),!1}if(be.set(De,Fe),Ve)Ae.set(Ve,Fe)}if(!await ks(R,"afterBuild",le))return!1;let ke=await uo({config:k?{...R,sites:Object.fromEntries(Y)}:R,rpxConfig:k?R:void 0,environment:t,driver:ie,sha:ae,runtime:re,tarballForSite:(De)=>{let Se=be.get(De);if(!Se)throw Error(`Missing tarball for site '${De}'`);return Se},logger:{info:(De)=>e(De),warn:(De)=>a(De),error:(De)=>r(De),step:(De)=>u(De),success:(De)=>n(De)}});if(ke&&!await ks(R,"afterDeploy",le))return!1;return ke}async function ya(R){let t=new Qr;if(u("Running pre-deployment security scan..."),R.exclude?.length)e(`Excluding from the scan: ${R.exclude.join(", ")}`);let m=await t.scan({directory:R.sourceDir,failOnSeverity:R.failOnSeverity||"critical",skipPatterns:R.skipPatterns,exclude:R.exclude});return{passed:m.passed,result:m}}function ng(){let R=lu(cu(),`ts-cloud-empty-scan-${process.pid}`);return au(R,{recursive:!0}),R}function ba(R){let{summary:t,findings:m,scannedFiles:k,duration:D}=R;if(e(`
|
|
375
|
+
Invalidation is in progress. Use --wait to wait for completion.`)}catch(m){r(`Invalidation failed: ${m.message}`)}})}function qc(R){for(let[t,m]of[["team:add <email> <role>","Invite an organization member"],["team:list","List organization members"],["team:remove <email>","Revoke organization membership"]])R.command(t,m).action(async(...k)=>En(t.split(" ")[0],{target:String(k[0]??""),message:"Legacy IAM-backed team commands are deprecated and cannot safely represent organization-scoped access.",nextAction:"Use the Team dashboard or organization invitation API; this command exits without changing IAM."}))}import{execFileSync as Yf,execSync as dl}from"node:child_process";import{copyFileSync as Ds,existsSync as Rn,mkdirSync as au,readFileSync as ll,writeFileSync as Zf}from"node:fs";import{tmpdir as cu}from"node:os";import{dirname as Qf,join as lu,resolve as Xf}from"node:path";import{existsSync as Hc,readdirSync as Vd,statSync as Gc}from"node:fs";import{join as ia}from"node:path";var pf=new Set([".DS_Store","Thumbs.db",".gitkeep",".keep"]);function qd(R){return pf.has(R)}function Hd(R,t=0){if(t>8)return!0;let m;try{m=Vd(R)}catch{return!1}for(let k of m){if(qd(k))continue;let D=ia(R,k),L;try{L=Gc(D)}catch{continue}if(L.isFile())return!0;if(L.isDirectory()&&Hd(D,t+1))return!0}return!1}function ff(R){let t;try{t=Vd(R)}catch{return[]}let m=[];for(let k of t){if(qd(k))continue;let D=ia(R,k);try{if(Gc(D).isDirectory()&&Hc(ia(D,"index.html")))m.push(k)}catch{}}return m.sort((k,D)=>k.localeCompare(D))}function Gd(R){let{root:t,siteName:m,expectIndex:k}=R,D=[];if(!Hc(t))return D.push({level:"error",message:`Build output not found at ${t} for site '${m}'`}),D;if(!Gc(t).isDirectory())return D.push({level:"error",message:`Site '${m}' has root ${t}, which is a file, not a directory`}),D;if(!Hd(t))return D.push({level:"error",message:`Site '${m}' would ship an empty release: ${t} contains no files. Check that the build ran and wrote where 'root' points.`}),D;if(!k)return D;if(Hc(ia(t,"index.html")))return D;let L=ff(t);if(L.length===1){let z=`${t.replace(/\/+$/,"")}/${L[0]}`;return D.push({level:"error",message:`Site '${m}' has no index.html at the top of ${t}, but ${z} has one. The generator wrote one level down, so this release would serve 404s at the domain root. Set root: '${z}'.`}),D}if(L.length>1)return D.push({level:"warning",message:`Site '${m}' has no index.html at the top of ${t}. These subdirectories have one: ${L.join(", ")}. The domain root will 404.`}),D;return D.push({level:"warning",message:`Site '${m}' has no index.html anywhere under ${t}. The domain root will 404.`}),D}function gf(R){if(!R)return[];if(Array.isArray(R))return R.map(String);let t=R,m=t.Items??t.Item;if(!m)return[];if(Array.isArray(m))return m.map(String);if(typeof m==="object"&&m!==null){let k=m;if("Item"in k){let D=k.Item;if(!D)return[];return Array.isArray(D)?D.map(String):[String(D)]}if("CNAME"in k){let D=k.CNAME;if(!D)return[];return Array.isArray(D)?D.map(String):[String(D)]}return Object.values(k).flatMap((D)=>{if(Array.isArray(D))return D.map(String);return typeof D==="string"?[D]:[]})}return[String(m)]}async function oa(R){if(R.length===0)return;let t=new E,m=await t.listDistributions(),k=[...new Set(R.map((D)=>D.toLowerCase()))];for(let D of k){let L=m.find((Y)=>gf(Y.Aliases).some((ne)=>ne.toLowerCase()===D));if(!L?.Id){a(`No CloudFront distribution found for ${D} — skipping dynamic HTTP method sync`);continue}if(await t.ensureDynamicHttpMethods(L.Id))n(`CloudFront ${L.Id} (${D}): enabled POST/PUT/PATCH/DELETE on dynamic paths`);else e(`CloudFront ${L.Id} (${D}): already allows dynamic HTTP methods`)}}import{execSync as hf}from"node:child_process";async function yf(R,t,m,k={},D=process.cwd()){if(!R)return;if(k.step?.(`Running ${m} hook`),typeof R==="string"){hf(R,{stdio:"inherit",cwd:D});return}await R(t)}async function ks(R,t,m={},k){try{return await yf(R.hooks?.[t],R,t,m,k),!0}catch(D){return m.error?.(`${t} hook failed: ${D?.message||D}`),!1}}var yi=(R)=>R===void 0?void 0:R?"on":"off";function Jc(R){let t={ssl:R.ssl,always_use_https:yi(R.alwaysUseHttps),automatic_https_rewrites:yi(R.automaticHttpsRewrites),min_tls_version:R.minTlsVersion,tls_1_3:yi(R.tls13),brotli:yi(R.brotli),http3:yi(R.http3),"0rtt":yi(R.zeroRtt),early_hints:yi(R.earlyHints),browser_cache_ttl:R.browserCacheTtl,always_online:yi(R.alwaysOnline),websockets:yi(R.websockets),email_obfuscation:yi(R.emailObfuscation)};if(R.hsts)t.security_header={strict_transport_security:{enabled:R.hsts.enabled,max_age:R.hsts.maxAge??31536000,include_subdomains:R.hsts.includeSubdomains??!1,preload:R.hsts.preload??!1,nosniff:R.hsts.noSniff??!0}};for(let m of Object.keys(t))if(t[m]===void 0)delete t[m];return{...t,...R.raw??{}}}var As={ssl:"strict",alwaysUseHttps:!0,automaticHttpsRewrites:!0,minTlsVersion:"1.2",tls13:!0,brotli:!0,http3:!0,earlyHints:!0,alwaysOnline:!0,emailObfuscation:!1,browserCacheTtl:0};import{connect as vf}from"node:tls";async function Yc(R){let{address:t,serverName:m,port:k=443,timeoutMs:D=5000}=R;return new Promise((L)=>{let z=!1,Y=(ne)=>{if(z)return;z=!0;try{X.destroy()}catch{}L(ne)},X=vf({host:t,port:k,servername:m,rejectUnauthorized:!1,timeout:D});X.once("secureConnect",()=>{if(X.authorized){Y({reachable:!0,trusted:!0});return}Y({reachable:!0,trusted:!1,reason:X.authorizationError?String(X.authorizationError):"origin certificate is not trusted for this hostname"})}),X.once("timeout",()=>{Y({reachable:!1,trusted:!1,reason:`no TLS handshake within ${D}ms`})}),X.once("error",(ne)=>{Y({reachable:!1,trusted:!1,reason:ne.message})})})}function bf(R,t){let m=t.replace(/\.$/,"").toLowerCase();return R.some((k)=>{let D=k.replace(/\.$/,"").toLowerCase();if(D===m)return!0;if(!D.startsWith("*."))return!1;let L=D.slice(2);if(!m.endsWith(`.${L}`))return!1;return!m.slice(0,m.length-L.length-1).includes(".")})}async function Zc(R){let{provider:t,zone:m,hosts:k,ipv4:D,ipv6:L,proxied:z=!0,settings:Y=As,cache:X,originGuard:ne,purge:re=!0,ttl:ie=300,skipOriginProbe:ae=!1,skipEdgeCertificateCheck:be=!1}=R,Ae={records:[],settingsChanged:[],cacheRules:0,originGuard:!1,purged:!1,deferredProxy:[],warnings:[]};if(k.length===0)return Ae;let le=[],ke=z&&!be?await t.listEdgeCertificateHosts(m):null;for(let Se of k){let Re=z;if(Re&&ke&&!bf(ke,Se)){Re=!1;let Ve=ke.length===0?"the zone has no active edge certificate yet":`no active edge certificate covers it (${ke.join(", ")})`;Ae.deferredProxy.push({host:Se,reason:Ve}),Ae.warnings.push(`${Se}: publishing DNS-only — ${Ve}. Proxying it would make Cloudflare terminate TLS with no certificate for the name, which fails the handshake outright rather than degrading. Universal SSL covers the apex and ONE level of subdomain, so a deeper name needs its own certificate; otherwise re-run the deploy once the certificate is active.`)}if(Re&&!ae){let Ve=await Yc({address:D,serverName:Se});if(!Ve.trusted){Re=!1;let Le=Ve.reason||"origin is not serving a trusted certificate";Ae.deferredProxy.push({host:Se,reason:Le}),Ae.warnings.push(`${Se}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${Le}). The origin issues certificates over HTTP-01, which needs the name to reach it directly; re-run the deploy once it has one and the record will be proxied.`)}}if(Re)le.push(Se);let Pe=[{type:"A",content:D}];if(L&&eo(Se))Pe.push({type:"AAAA",content:L});for(let{type:Ve,content:Le}of Pe){let Fe=await t.upsertRecord(m,{name:Se,type:Ve,content:Le,ttl:ie,proxied:Re});if(Fe.success)Ae.records.push({host:Se,type:Ve,content:Le,proxied:Re});else Ae.warnings.push(`${Se} ${Ve} → ${Le} failed: ${Fe.message||"unknown provider error"}`)}}if(le.length===0){if(z)Ae.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");return Ae}try{let Se=Jc(Y),Re=await t.applyZoneSettings(m,Se);Ae.settingsChanged=Re.changed;for(let Pe of Re.failed)Ae.warnings.push(`zone setting '${Pe.id}' could not be applied: ${Pe.error}`)}catch(Se){Ae.warnings.push(`zone settings could not be read: ${Se instanceof Error?Se.message:String(Se)}`)}let De=Ls(le,X);if(De.length>0){let Se=await t.putManagedPhaseRules(m,"http_request_cache_settings",De);if(Se.success)Ae.cacheRules=De.length;else Ae.warnings.push(`cache rules could not be written: ${Se.message}`)}if(ne){let Se=Ms(le,ne.header,ne.value);if(Se.length>0){let Re=await t.putManagedPhaseRules(m,"http_request_late_transform",Se);if(Re.success)Ae.originGuard=!0;else Ae.warnings.push(`origin guard header could not be written: ${Re.message}. `+"The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.")}}if(re){if(Ae.purged=await t.purgeCache(m,{hosts:le}),!Ae.purged)Ae.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.")}return Ae}var wf=(R)=>process.env[R];function Kd(R,t){let m=R.replace(/\.$/,"").toLowerCase();if(t){let k=t.replace(/\.$/,"").toLowerCase();if(m===k||m.endsWith(`.${k}`))return k}return m.split(".").slice(-2).join(".")}function Qc(R,t={}){let m=t.env??wf,k=R.infrastructure?.compute?.proxy?.cdn,D=[];if(!k||k.provider!=="cloudflare")return{plan:null,errors:D};if(k.originDomain)D.push(`compute.proxy.cdn.originDomain (${k.originDomain}) is ignored for Cloudflare: the proxy forwards to the address in the record it fronts, `+"so no separate origin hostname is needed — and publishing one gives clients a documented way around the edge.");let L=m("CLOUDFLARE_API_TOKEN");if(!L)return D.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled."),{plan:null,errors:D};let z=R.sites??{},Y=k.frontedHosts?.length?[...new Set(k.frontedHosts.map((ae)=>ae.toLowerCase()))]:no(z,{autoWww:R.infrastructure?.compute?.proxy?.autoWww!==!1});if(Y.length===0)return D.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain."),{plan:null,errors:D};let X=R.infrastructure?.dns?.domain,ne=new Set(Y.map((ae)=>Kd(ae,X)));if(ne.size>1)return D.push(`Fronted hosts span more than one zone (${[...ne].join(", ")}). A Cloudflare zone is configured as a unit, so run one CDN config per zone.`),{plan:null,errors:D};let re=k.cloudflare??{},ie=k.secret?{header:k.secretHeader??"X-Origin-Verify",value:k.secret}:void 0;return{plan:{zone:[...ne][0],hosts:Y,apiToken:L,zoneId:re.zoneId??m("CLOUDFLARE_ZONE_ID"),accountId:re.accountId??m("CLOUDFLARE_ACCOUNT_ID"),proxied:re.proxied!==!1,settings:re.settings??As,cache:re.cache,originGuard:ie,purge:re.purgeOnDeploy!==!1,skipOriginProbe:re.skipOriginProbe===!0},errors:D}}var Sf="https://api.cloudflare.com/client/v4",Yd=10042,Cf=10006,$f=10059;class wo extends Error{status;errors;constructor(R,t,m=[]){super(R);this.name="R2ApiError",this.status=t,this.errors=m}hasCode(R){return this.errors.some((t)=>t.code===R)}}class Hi extends wo{accountId;constructor(R,t,m=[]){super(`R2 is not enabled on Cloudflare account ${R}. Enable it once in the Cloudflare dashboard (R2 Object Storage, then follow the prompt to activate it), then re-run the deploy.`,t,m);this.name="R2NotEnabledError",this.accountId=R}}class zo{name="cloudflare-r2";apiToken;accountId;fetchImpl;constructor(R){if(!R.apiToken)throw Error("Cloudflare R2 needs an API token");if(!R.accountId)throw Error("Cloudflare R2 needs an account id");this.apiToken=R.apiToken,this.accountId=R.accountId,this.fetchImpl=R.fetch??((t,m)=>fetch(t,m))}async requestEnvelope(R,t,m,k={}){let D={Authorization:`Bearer ${this.apiToken}`};if(m!==void 0)D["Content-Type"]="application/json";if(k.jurisdiction&&k.jurisdiction!=="default")D["cf-r2-jurisdiction"]=k.jurisdiction;let L=await this.fetchImpl(`${Sf}/accounts/${this.accountId}/r2${t}`,{method:R,headers:D,body:m===void 0?void 0:JSON.stringify(m)}),z=await L.text(),Y;try{Y=JSON.parse(z)}catch{throw new wo(`Cloudflare R2 API returned ${L.status} with a non-JSON body: ${z.slice(0,200)}`,L.status)}if(!Y.success){let X=Y.errors??[];if(X.some((re)=>re.code===Yd))throw new Hi(this.accountId,L.status,X);let ne=X.map((re)=>`${re.code}: ${re.message}`).join(", ");throw new wo(`Cloudflare R2 API error (${L.status}) on ${R} ${t}: ${ne||z.slice(0,200)}`,L.status,X)}return Y}async request(R,t,m,k){return(await this.requestEnvelope(R,t,m,k)).result}bucketPath(R){return`/buckets/${encodeURIComponent(R)}`}async listBuckets(R={}){let t=[],m;do{let k=new URLSearchParams({per_page:"1000"});if(m)k.set("cursor",m);let D=await this.requestEnvelope("GET",`/buckets?${k}`,void 0,R),L=D.result?.buckets??[];t.push(...L),m=L.length>0?D.result_info?.cursor||void 0:void 0}while(m);return t}async getBucket(R,t={}){try{return await this.request("GET",this.bucketPath(R),void 0,t)}catch(m){if(m instanceof Hi)throw m;if(m instanceof wo&&(m.hasCode(Cf)||m.status===404))return null;throw m}}async ensureBucket(R,t={}){let m={jurisdiction:t.jurisdiction},k=await this.getBucket(R,m);if(k)return{bucket:k,created:!1};let D={name:R};if(t.locationHint)D.locationHint=t.locationHint;let L=await this.request("POST","/buckets",D,m);return{bucket:{...L,name:L?.name||R},created:!0}}async getCors(R,t={}){try{return(await this.request("GET",`${this.bucketPath(R)}/cors`,void 0,t))?.rules??[]}catch(m){if(m instanceof wo&&!(m instanceof Hi)&&(m.hasCode($f)||m.status===404))return[];throw m}}async putCors(R,t,m={}){if(t.length===0){await this.request("DELETE",`${this.bucketPath(R)}/cors`,void 0,m);return}await this.request("PUT",`${this.bucketPath(R)}/cors`,{rules:t},m)}async ensureCors(R,t,m={}){let k=await this.getCors(R,m);if(sa(k,t))return!1;return await this.putCors(R,t,m),!0}async getLifecycle(R,t={}){return(await this.request("GET",`${this.bucketPath(R)}/lifecycle`,void 0,t))?.rules??[]}async putLifecycle(R,t,m={}){await this.request("PUT",`${this.bucketPath(R)}/lifecycle`,{rules:t},m)}async ensureLifecycle(R,t,m={}){let k=await this.getLifecycle(R,m);if(ca(k,t))return!1;return await this.putLifecycle(R,t,m),!0}async listCustomDomains(R,t={}){return(await this.request("GET",`${this.bucketPath(R)}/domains/custom`,void 0,t))?.domains??[]}async attachCustomDomain(R,t,m={}){return this.request("POST",`${this.bucketPath(R)}/domains/custom`,{domain:t.domain,zoneId:t.zoneId,enabled:t.enabled??!0,minTLS:t.minTLS??"1.2"},m)}async updateCustomDomain(R,t,m,k={}){return this.request("PUT",`${this.bucketPath(R)}/domains/custom/${encodeURIComponent(t)}`,m,k)}async ensureCustomDomain(R,t,m={},k){let D=t.minTLS??"1.2",z=(k??await this.listCustomDomains(R,m)).find((ne)=>ne.domain.toLowerCase()===t.domain.toLowerCase());if(!z){let ne=await this.attachCustomDomain(R,{...t,minTLS:D,enabled:!0},m);return{action:"attached",domain:{...ne,domain:ne?.domain||t.domain}}}let Y=z.minTLS??"1.0";if(z.enabled&&Y===D)return{action:"unchanged",domain:z};let X=await this.updateCustomDomain(R,z.domain,{enabled:!0,minTLS:D},m);return{action:"updated",domain:{...z,...X}}}async getManagedDomain(R,t={}){return this.request("GET",`${this.bucketPath(R)}/domains/managed`,void 0,t)}async setManagedDomain(R,t,m={}){let k=await this.getManagedDomain(R,m);if(k?.enabled===t)return{changed:!1,domain:k};return{changed:!0,domain:await this.request("PUT",`${this.bucketPath(R)}/domains/managed`,{enabled:t},m)}}}function Rs(R){if(Array.isArray(R))return`[${R.map(Rs).join(",")}]`;if(R&&typeof R==="object")return`{${Object.entries(R).filter(([,m])=>m!==void 0).sort(([m],[k])=>m.localeCompare(k)).map(([m,k])=>`${JSON.stringify(m)}:${Rs(k)}`).join(",")}}`;return JSON.stringify(R)}function Jd(R,t){let m=(k)=>k&&k.length>0?[...k].map(String).sort():void 0;return{id:t?R.id:void 0,allowed:{origins:m(R.allowed?.origins),methods:m(R.allowed?.methods?.map((k)=>k.toUpperCase())),headers:m(R.allowed?.headers?.map((k)=>k.toLowerCase()))},exposeHeaders:m(R.exposeHeaders?.map((k)=>k.toLowerCase())),maxAgeSeconds:R.maxAgeSeconds}}function sa(R,t){if(R.length!==t.length)return!1;return t.every((m,k)=>{let D=m.id!==void 0;return Rs(Jd(R[k],D))===Rs(Jd(m,D))})}function ca(R,t){if(R.length!==t.length)return!1;let m=(k)=>Rs([...k].map((D)=>({...D,storageClassTransitions:D.storageClassTransitions?.length?D.storageClassTransitions:void 0})).sort((D,L)=>D.id.localeCompare(L.id)));return m(R)===m(t)}var kf="r2 cache",Af=/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;async function tl(R,t={}){let m=t.log??(()=>{}),k={buckets:[],warnings:[]},D=R.infrastructure?.r2??R.r2,L=Object.entries(D?.buckets??{});if(L.length===0)return m("R2: no buckets declared, skipping."),k;if(!t.apiToken||!t.accountId){let re=[!t.apiToken&&"CLOUDFLARE_API_TOKEN",!t.accountId&&"CLOUDFLARE_ACCOUNT_ID"].filter(Boolean).join(" and "),ie=`R2: ${L.length} bucket(s) declared but ${re} is not set, skipping.`;return m(ie),k.warnings.push(ie),k}let z=t.dryRun===!0,Y=new zo({apiToken:t.apiToken,accountId:t.accountId,fetch:t.fetch}),X=new he(t.apiToken,{zoneId:t.zoneId,accountId:t.accountId}),ne=new Map;for(let[re,ie]of L){let ae=ie?.name;if(!ae||!Af.test(ae)){k.warnings.push(`R2: bucket '${re}' has an invalid name '${ae??""}' (3-63 chars: lowercase letters, digits, hyphens), skipping.`);continue}m(`R2: ${z?"checking":"reconciling"} bucket ${ae}...`);let be={name:ae,changes:[],domains:[]};if(k.buckets.push(be),await Rf(ie,{r2:Y,cloudflare:X,dryRun:z,summary:be,warnings:k.warnings,claimed:ne,log:m}),be.changes.length===0)m(`R2: ${ae} is in sync.`);else for(let Ae of be.changes)m(`R2: ${ae}: ${Ae}`)}return k}async function Rf(R,t){let{r2:m,dryRun:k,summary:D,warnings:L}=t,z=R.name,Y={jurisdiction:R.jurisdiction},X=(be,Ae)=>{let le=k?Ae&&`would ${Ae}`:be;if(le)D.changes.push(le)},ne;if(k)ne=await m.getBucket(z,Y)!==null;else{let{created:be}=await m.ensureBucket(z,{locationHint:R.locationHint,jurisdiction:R.jurisdiction});if(ne=!0,be)X("created","")}if(!ne)X("","create bucket");let re=Pf(R.customDomains);await Is(t,`cors on ${z}`,async()=>{if(!R.cors)return;if(!ne){X("cors updated","set cors");return}let be=await m.getCors(z,Y);if(sa(be,R.cors))return;if(!k)await m.putCors(z,R.cors,Y);X(R.cors.length===0?"cors removed":"cors updated",R.cors.length===0?"remove cors":"update cors")}),await Is(t,`lifecycle on ${z}`,async()=>{if(!R.lifecycle)return;let be=R.lifecycle.map(Xd);if(ne){let Ae=await m.getLifecycle(z,Y);if(ca(Ae,be))return}if(!k)await m.putLifecycle(z,be,Y);X("lifecycle updated","update lifecycle")}),await Is(t,`r2.dev URL on ${z}`,async()=>{let be=R.publicDevUrl===!0;if(!ne){if(be)X("r2.dev URL enabled","enable r2.dev URL");return}if((await m.getManagedDomain(z,Y))?.enabled===be)return;if(!k)await m.setManagedDomain(z,be,Y);X(be?"r2.dev URL enabled":"r2.dev URL disabled",be?"enable r2.dev URL":"disable r2.dev URL")});let ie=[];if(re.length>0&&ne)try{ie=await m.listCustomDomains(z,Y)}catch(be){if(be instanceof Hi)throw be;L.push(`R2: could not list custom domains on ${z}: ${nl(be)}`)}let ae=new Map;for(let{domain:be,minTLS:Ae}of re){let le=t.claimed.get(be);if(le&&le!==z){L.push(`R2: ${be} is declared on both ${le} and ${z}; leaving it on ${le}.`);continue}t.claimed.set(be,z);let ke=ie.find((Se)=>Se.domain.toLowerCase()===be),De=Ae??"1.2";if(ke&&ke.enabled&&(ke.minTLS??"1.0")===De){if(D.domains.push({domain:be,status:el(ke)}),ke.zoneId)ae.set(be,ke.zoneId);continue}await Is(t,`custom domain ${be} on ${z}`,async()=>{if(ke){if(!k)await m.updateCustomDomain(z,ke.domain,{enabled:!0,minTLS:De},Y);if(X(`custom domain ${be} updated`,`update custom domain ${be}`),D.domains.push({domain:be,status:el({...ke,enabled:!0})}),ke.zoneId)ae.set(be,ke.zoneId);return}let Se=await t.cloudflare.zoneIdFor(be);if(ae.set(be,Se),!k)await m.ensureCustomDomain(z,{domain:be,zoneId:Se,minTLS:De},Y,ie);X(`custom domain ${be} attached`,`attach custom domain ${be}`),D.domains.push({domain:be,status:k?"not attached":"pending"})},()=>D.domains.push({domain:be,status:"error"}))}if(R.cache&&re.length>0)await If(R,re.map((be)=>be.domain).filter((be)=>t.claimed.get(be)===z),ae,t,X)}async function If(R,t,m,k,D){let L=Zd(R.cache);if(!L||t.length===0)return;let z=new Map;for(let Y of t){let X=m.get(Y);if(!X)try{X=await k.cloudflare.zoneIdFor(Y)}catch(ne){k.warnings.push(`R2: could not resolve the zone for ${Y}, cache rule skipped: ${nl(ne)}`);continue}z.set(X,[...z.get(X)??[],Y])}for(let Y of z.values()){let X={action:"set_cache_settings",description:`${Dr} ${kf}`,expression:`(${Pr(Y)})`,enabled:!0,action_parameters:L};await Is(k,`cache rule for ${Y.join(", ")}`,async()=>{let ne=await k.cloudflare.getPhaseRules(Y[0],"http_request_cache_settings");if(Qd(ne,X))return;if(!k.dryRun){let re=await k.cloudflare.putManagedPhaseRules(Y[0],"http_request_cache_settings",[X]);if(!re.success)throw Error(re.message||"cache rule write failed")}D(`cache rule updated for ${Y.join(", ")}`,`update cache rule for ${Y.join(", ")}`)})}}function Zd(R){if(R.edgeTtl===void 0&&R.browserTtl===void 0)return null;let t={cache:!0};if(R.edgeTtl!==void 0)t.edge_ttl={mode:"override_origin",default:R.edgeTtl};if(R.browserTtl!==void 0)t.browser_ttl={mode:"override_origin",default:R.browserTtl};return t}function Qd(R,t){let m=kr(t.expression),k=Di(t.expression),D=(Y)=>(Y.description||"").startsWith(Dr),L=(Y)=>Y.action===t.action&&Y.description===t.description&&(Y.enabled??!0)===(t.enabled??!0)&&Di(Y.expression)===k&&la(Y.action_parameters)===la(t.action_parameters),z=!1;for(let Y of R){if(!D(Y))continue;let X=kr(Y.expression);if(!m.some((re)=>X.includes(re)))continue;if(L(Y)&&m.every((re)=>X.includes(re))&&!z){z=!0;continue}return!1}return z}function Xd(R){let t={id:R.id,enabled:R.enabled??!0,conditions:{prefix:R.prefix??""}};if(R.expireAfterDays!==void 0)t.deleteObjectsTransition={condition:{type:"Age",maxAge:Xc(R.expireAfterDays)}};else if(R.expireOn!==void 0)t.deleteObjectsTransition={condition:{type:"Date",date:R.expireOn}};if(R.abortMultipartUploadsAfterDays!==void 0)t.abortMultipartUploadsTransition={condition:{type:"Age",maxAge:Xc(R.abortMultipartUploadsAfterDays)}};if(R.infrequentAccessAfterDays!==void 0)t.storageClassTransitions=[{storageClass:"InfrequentAccess",condition:{type:"Age",maxAge:Xc(R.infrequentAccessAfterDays)}}];return t}function Xc(R){return Math.round(R*86400)}function Pf(R){let t=new Set,m=[];for(let k of R??[]){let D=typeof k==="string"?{domain:k}:k,L=D.domain?.trim().replace(/\.$/,"").toLowerCase();if(!L||t.has(L))continue;t.add(L),m.push({domain:L,minTLS:D.minTLS})}return m}function el(R){if(!R.enabled)return"disabled";let t=R.status?.ownership,m=R.status?.ssl;if(t==="active"&&m==="active")return"active";for(let k of[t,m])if(k==="error"||k==="blocked"||k==="deactivated")return k;return"pending"}async function Is(R,t,m,k){try{await m()}catch(D){if(D instanceof Hi)throw D;R.warnings.push(`R2: ${t} failed: ${nl(D)}`),k?.()}}function nl(R){return R instanceof Error?R.message:String(R)}function la(R){if(Array.isArray(R))return`[${R.map(la).join(",")}]`;if(R&&typeof R==="object")return`{${Object.entries(R).filter(([,m])=>m!==void 0).sort(([m],[k])=>m.localeCompare(k)).map(([m,k])=>`${JSON.stringify(m)}:${la(k)}`).join(",")}}`;return JSON.stringify(R)}import{basename as Lf,extname as Ff}from"node:path";import{createHash as xf}from"node:crypto";var Df="https://api.cloudflare.com/client/v4",Gi="TS_CLOUD_CONTENT_HASH",Ps="2025-09-01",il="application/javascript+module",Ef=10007,Tf=[1e4,9109];class Wo extends Error{status;errors;constructor(R,t,m=[]){super(R);this.name="WorkersApiError",this.status=t,this.errors=m}hasCode(R){return this.errors.some((t)=>t.code===R)}}class da extends Wo{permission;constructor(R,t,m,k=[]){super(`The Cloudflare API token is missing the "${R}" permission (${t}). Edit the token in the Cloudflare dashboard (My Profile, API Tokens), add it, then re-run the deploy.`,m,k);this.name="WorkersPermissionError",this.permission=R}}class ua{name="cloudflare-workers";apiToken;accountId;fetchImpl;constructor(R){if(!R.apiToken)throw Error("Cloudflare Workers needs an API token");if(!R.accountId)throw Error("Cloudflare Workers needs an account id");this.apiToken=R.apiToken,this.accountId=R.accountId,this.fetchImpl=R.fetch??((t,m)=>fetch(t,m))}async request(R,t,m){let k={Authorization:`Bearer ${this.apiToken}`},D;if(m instanceof FormData)D=m;else if(m!==void 0)k["Content-Type"]="application/json",D=JSON.stringify(m);let L=await this.fetchImpl(`${Df}/accounts/${this.accountId}/workers${t}`,{method:R,headers:k,body:D}),z=await L.text(),Y;try{Y=JSON.parse(z)}catch{throw new Wo(`Cloudflare Workers API returned ${L.status} with a non-JSON body: ${z.slice(0,200)}`,L.status)}if(!Y.success){let X=Y.errors??[],ne=X.map((re)=>`${re.code}: ${re.message}`).join(", ");if(Nf(L.status,X))throw new da(Of(t),`${R} ${t} -> ${L.status}${ne?`, ${ne}`:""}`,L.status,X);throw new Wo(`Cloudflare Workers API error (${L.status}) on ${R} ${t}: ${ne||z.slice(0,200)}`,L.status,X)}return Y.result}scriptPath(R){return`/scripts/${encodeURIComponent(R)}`}async getSettings(R){try{return await this.request("GET",`${this.scriptPath(R)}/settings`)}catch(t){if(t instanceof da)throw t;if(t instanceof Wo&&(t.hasCode(Ef)||t.status===404))return null;throw t}}async getScript(R){return this.getSettings(R)}async uploadModuleScript(R,t){return this.request("PUT",this.scriptPath(R),tu(t))}async ensureModuleScript(R,t){let m=xs(t),k=await this.getSettings(R);if(k&&ma(k,t,m))return{action:"unchanged",hash:m};return await this.uploadModuleScript(R,nu(t,m)),{action:k?"updated":"created",hash:m}}async listCustomDomains(R={}){let t=new URLSearchParams;if(R.service)t.set("service",R.service);if(R.hostname)t.set("hostname",R.hostname);if(R.zoneId)t.set("zone_id",R.zoneId);let m=t.toString(),k=m?`?${m}`:"";return await this.request("GET",`/domains${k}`)??[]}async attachCustomDomain(R,t){let m={hostname:R,service:t.service,environment:t.environment??"production"};if(t.zoneId)m.zone_id=t.zoneId;return this.request("PUT","/domains",m)}async detachCustomDomain(R){await this.request("DELETE",`/domains/${encodeURIComponent(R)}`)}async ensureCustomDomain(R,t,m){let D=(m??await this.listCustomDomains({hostname:R})).find((z)=>z.hostname.toLowerCase()===R.toLowerCase());if(D&&D.service===t.service)return{action:"unchanged",domain:D};if(D)throw Error(`${R} is attached to the Worker '${D.service}'. Detach it there first (Workers & Pages, ${D.service}, Settings, Domains & Routes); ts-cloud does not move it.`);let L=await this.attachCustomDomain(R,t);return{action:"attached",domain:{...L,hostname:L?.hostname||R}}}}function Nf(R,t){if(t.some((m)=>Tf.includes(m.code)))return!0;return(R===401||R===403)&&t.length===0}function Of(R){if(R.startsWith("/domains"))return"Workers Routes: Edit (zone, for Workers custom domains) and Workers Scripts: Edit (account)";return"Workers Scripts: Edit (account)"}function tu(R){if(!R.modules.some((k)=>k.name===R.mainModule))throw Error(`Worker main module '${R.mainModule}' is not one of the uploaded modules`);let t={main_module:R.mainModule,bindings:R.bindings??[],compatibility_date:R.compatibilityDate??Ps,compatibility_flags:R.compatibilityFlags??[],keep_bindings:["secret_text"]},m=new FormData;m.append("metadata",JSON.stringify(t));for(let k of R.modules)m.append(k.name,new Blob([k.content],{type:k.type}),k.name);return m}function xs(R){let t={mainModule:R.mainModule,modules:[...R.modules].sort((m,k)=>m.name.localeCompare(k.name)).map((m)=>({name:m.name,type:m.type,sha256:eu(m.content)})),bindings:[...R.bindings??[]].filter((m)=>m.name!==Gi).sort((m,k)=>m.name.localeCompare(k.name)),compatibilityDate:R.compatibilityDate??Ps,compatibilityFlags:[...R.compatibilityFlags??[]].sort()};return eu(rl(t))}function nu(R,t=xs(R)){return{...R,bindings:[...(R.bindings??[]).filter((m)=>m.name!==Gi),{type:"plain_text",name:Gi,text:t}]}}function ma(R,t,m){let k=R.bindings??[],D=k.find((L)=>L.name===Gi&&L.type==="plain_text");if(!D||D.text!==m)return!1;return(t.bindings??[]).every((L)=>{let z=k.find((X)=>X.name===L.name);if(!z||z.type!==L.type)return!1;let Y=L.type==="r2_bucket"?"bucket_name":L.type==="plain_text"?"text":void 0;return!Y||z[Y]===L[Y]})}function eu(R){return xf("sha256").update(R).digest("hex")}function rl(R){if(Array.isArray(R))return`[${R.map(rl).join(",")}]`;if(R&&typeof R==="object")return`{${Object.entries(R).filter(([,m])=>m!==void 0).sort(([m],[k])=>m.localeCompare(k)).map(([m,k])=>`${JSON.stringify(m)}:${rl(k)}`).join(",")}}`;return JSON.stringify(R)}async function ol(R){if(!await Bun.file(R).exists())throw Error(`Worker entry not found: ${R}`);let t;try{t=await Bun.build({entrypoints:[R],target:"browser",format:"esm",minify:!0,external:["cloudflare:*"]})}catch(D){throw Error(`Bundling Worker ${R} failed: ${jf(D)}`)}if(!t.success)throw Error(`Bundling Worker ${R} failed: ${t.logs.map((D)=>String(D.message??D)).join("; ")}`);if(t.outputs.length!==1)throw Error(`Bundling Worker ${R} produced ${t.outputs.length} files (${t.outputs.map((D)=>D.path).join(", ")}); a Worker is uploaded as a single module, so remove the dynamic imports or asset imports that split it.`);let m=t.outputs[0];if(m.kind!=="entry-point")throw Error(`Bundling Worker ${R} produced a ${m.kind} instead of an entry module`);let k=await m.text();return{name:`${Lf(R,Ff(R))}.js`,content:k,type:il,size:new TextEncoder().encode(k).byteLength}}function jf(R){if(R instanceof AggregateError&&R.errors.length>0)return R.errors.map((t)=>t instanceof Error?t.message:String(t?.message??t)).join("; ");return R instanceof Error?R.message:String(R)}import{isAbsolute as Bf,resolve as _f}from"node:path";var Mf="https://api.cloudflare.com/client/v4",zf=/^[a-z0-9][a-z0-9_-]{0,62}$/;async function sl(R,t={}){let m=t.log??(()=>{}),k={workers:[],warnings:[]},D=Object.entries(R.infrastructure?.workers??R.workers??{});if(D.length===0)return m("Workers: none declared, skipping."),k;if(!t.apiToken||!t.accountId){let Y=[!t.apiToken&&"CLOUDFLARE_API_TOKEN",!t.accountId&&"CLOUDFLARE_ACCOUNT_ID"].filter(Boolean).join(" and "),X=`Workers: ${D.length} Worker(s) declared but ${Y} is not set, skipping.`;return m(X),k.warnings.push(X),k}let L=R.infrastructure?.r2??R.r2,z={workers:new ua({apiToken:t.apiToken,accountId:t.accountId,fetch:t.fetch}),r2:new zo({apiToken:t.apiToken,accountId:t.accountId,fetch:t.fetch}),cloudflare:new he(t.apiToken,{zoneId:t.zoneId,accountId:t.accountId}),apiToken:t.apiToken,fetch:t.fetch??((Y,X)=>fetch(Y,X)),projectRoot:t.projectRoot??process.cwd(),dryRun:t.dryRun===!0,warnings:k.warnings,log:m,r2ConfigDomains:Gf(L),r2Buckets:L,claimed:new Map};for(let[Y,X]of D){let ne=X?.name;if(!ne||!zf.test(ne)){k.warnings.push(`Workers: '${Y}' has an invalid name '${ne??""}' (1-63 chars: lowercase letters, digits, '-' and '_'), skipping.`);continue}if(!X.entry){k.warnings.push(`Workers: ${ne} has no entry, skipping.`);continue}m(`Workers: ${z.dryRun?"checking":"reconciling"} ${ne}...`);let re={name:ne,changes:[],domains:[]};k.workers.push(re),await Uf(X,re,z);for(let ie of re.changes)m(`Workers: ${ne}: ${ie}`)}return k}async function Uf(R,t,m){let k=R.name,D=Bf(R.entry)?R.entry:_f(m.projectRoot,R.entry),L;try{L=await ol(D)}catch(Y){throw Error(`Worker ${k}: ${Vo(Y)}`)}t.changes.push(`bundled ${Jf(L.size)}`);let z={mainModule:L.name,modules:[{name:L.name,content:L.content,type:L.type}],bindings:ru(R,m),compatibilityDate:R.compatibilityDate??Ps,compatibilityFlags:R.compatibilityFlags??[]};if(m.dryRun){let Y=await m.workers.getSettings(k);t.changes.push(Y&&ma(Y,z,xs(z))?"unchanged":"would upload")}else{let{action:Y}=await m.workers.ensureModuleScript(k,z);t.changes.push(Y==="unchanged"?"unchanged":"uploaded")}for(let Y of Kf(R.customDomains))await Wf(k,Y,t,m)}async function Wf(R,t,m,k){let D=(be)=>{m.domains.push({domain:t,status:be})},L=(be)=>{k.warnings.push(`Workers: ${be}`),D("conflict")},z=k.claimed.get(t);if(z&&z!==R){L(`${t} is declared on both ${z} and ${R}; leaving it on ${z}.`);return}k.claimed.set(t,R);let Y=k.r2ConfigDomains.get(t);if(Y){L(`${t} is also declared as a custom domain of the R2 bucket '${Y}' (r2.buckets). A hostname serves either a bucket or a Worker. Remove it from the bucket's customDomains and detach it in the dashboard (R2, ${Y}, Settings, Custom Domains) first; to serve the bucket through the Worker, bind it with bindings.r2Buckets instead.`);return}let X=await Vf(k);if(X===null){D("error");return}let ne=X.find((be)=>be.hostname.toLowerCase()===t);if(ne?.service===R){D("active");return}if(ne){L(`${t} is attached to the Worker '${ne.service}'. Detach it there first (Workers & Pages, ${ne.service}, Settings, Domains & Routes); it is not moved automatically.`);return}let re=(await qf(k))?.get(t);if(re){L(`${t} is attached to the R2 bucket '${re}' as a custom domain. Detach it first (R2, ${re}, Settings, Custom Domains, then re-run the deploy); it is not detached automatically. To serve the bucket through the Worker, bind it with bindings.r2Buckets.`);return}let ie;try{ie=await k.cloudflare.zoneIdFor(t)}catch(be){k.warnings.push(`Workers: could not resolve the zone for ${t}, not attached: ${Vo(be)}`),D("error");return}let ae=await Hf(k,ie,t);if(ae.length>0){let be=ae.map((Ae)=>`${Ae.type} ${Ae.content}`).join(", ");L(`${t} already has a DNS record (${be}). A Worker custom domain creates its own record, so Cloudflare will not attach over it. Delete the record first (DNS, Records), then re-run the deploy; it is not deleted automatically.`);return}if(k.dryRun){m.changes.push(`would attach custom domain ${t}`),D("not attached");return}try{await k.workers.ensureCustomDomain(t,{service:R,zoneId:ie},X),m.changes.push(`custom domain ${t} attached`),X.push({id:"",hostname:t,service:R,zone_id:ie}),D("pending")}catch(be){k.warnings.push(`Workers: custom domain ${t} on ${R} failed: ${Vo(be)}`),D("error")}}async function Vf(R){if(R.attached!==void 0)return R.attached;try{R.attached=await R.workers.listCustomDomains()}catch(t){R.warnings.push(`Workers: could not list Worker custom domains, none attached: ${Vo(t)}`),R.attached=null}return R.attached}async function qf(R){if(R.r2AccountDomains!==void 0)return R.r2AccountDomains;let t=new Map;try{let m=(await R.r2.listBuckets()).map((k)=>({name:k.name}));for(let k of Object.values(R.r2Buckets?.buckets??{}))if(k.jurisdiction&&k.jurisdiction!=="default")m.push({name:k.name,jurisdiction:k.jurisdiction});for(let k of m){let D=await R.r2.listCustomDomains(k.name,{jurisdiction:k.jurisdiction}).catch(()=>[]);for(let L of D)t.set(L.domain.toLowerCase(),k.name)}R.r2AccountDomains=t}catch(m){R.log(`Workers: could not check R2 custom domains (${Vo(m)}); relying on the DNS check.`),R.r2AccountDomains=null}return R.r2AccountDomains}async function Hf(R,t,m){try{let k=await R.fetch(`${Mf}/zones/${t}/dns_records?name=${encodeURIComponent(m)}&per_page=100`,{method:"GET",headers:{Authorization:`Bearer ${R.apiToken}`}}),D=await k.json();if(!D?.success)throw Error(`HTTP ${k.status}`);return(D.result??[]).filter((L)=>L.name?.toLowerCase()===m).map((L)=>({type:L.type??"?",content:L.content??""}))}catch(k){return R.log(`Workers: could not read DNS records for ${m} (${Vo(k)}); attaching anyway.`),[]}}function ru(R,t){let m=[],k=new Set,D=(z)=>{if(z===Gi)return t.warnings.push(`Workers: ${R.name}: binding name ${Gi} is reserved for ts-cloud, ignored.`),!1;if(k.has(z))return t.warnings.push(`Workers: ${R.name}: binding ${z} is declared twice; the first one wins.`),!1;return k.add(z),!0},L=new Map;for(let z of Object.values(t.r2Buckets?.buckets??{}))if(z.jurisdiction&&z.jurisdiction!=="default")L.set(z.name,z.jurisdiction);for(let[z,Y]of Object.entries(R.bindings?.r2Buckets??{})){if(!D(z))continue;let X=L.get(Y);m.push(X?{type:"r2_bucket",name:z,bucket_name:Y,jurisdiction:X}:{type:"r2_bucket",name:z,bucket_name:Y})}for(let[z,Y]of Object.entries(R.bindings?.vars??{}))if(D(z))m.push({type:"plain_text",name:z,text:String(Y)});return m}function Gf(R){let t=new Map;for(let m of Object.values(R?.buckets??{}))for(let k of m?.customDomains??[]){let D=iu(typeof k==="string"?k:k.domain);if(D)t.set(D,m.name)}return t}function Kf(R){return[...new Set((R??[]).map(iu).filter(Boolean))]}function iu(R){return(R??"").trim().replace(/\.$/,"").toLowerCase()}function Jf(R){if(R<1024)return`${R} B`;if(R<1048576)return`${(R/1024).toFixed(1)} KB`;return`${(R/1024/1024).toFixed(1)} MB`}function Vo(R){return R instanceof Error?R.message:String(R)}async function eg(R){let t=aa();if(t.emptyEnvKey||t.emptyEnvSecret)a("Empty AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY env var detected — likely a misconfigured .env file. Remove the empty lines or fill them in.");e(`AWS credentials: ${t.description}`);try{let k=await new mt(R).getCallerIdentity();if(k.Arn)e(`AWS identity: ${k.Arn}`)}catch(m){a(`Could not verify AWS identity: ${m.message}`)}}function tg(){try{return dl("git rev-parse --short HEAD",{stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return Date.now().toString(36)}}async function al(R,t,m,k){let{ensureManagementDashboard:D}=await import("./chunk-bkvxzenb.js");D(R,{logger:{info:e,warn:a}});let L=R.sites||{},z=Object.entries(L);if(z.length===0)return a("infrastructure.compute is set but no sites are configured — nothing to deploy. Add sites to `cloud.config.ts`."),!0;if(k&&!L[k])return r(`Site '${k}' was not found. Configured sites: ${z.map(([De])=>De).join(", ")}`),!1;let Y=z.filter(([De,Se])=>{if(!Se)return!1;if(k&&De!==k)return!1;if(w(Se)==="bucket")return a(`Site '${De}' targets a bucket — skipping compute deploy (handled by the static-site path).`),!1;return Qs(Se)});if(k)e(`Deploying only '${k}'. Other sites keep their current release; proxy routes are still regenerated in full.`);let X=R.project.slug,re=(R.infrastructure?.compute||{}).runtime||"bun",ie=O({config:R}),ae=tg(),be=new Map,Ae=new Map,le={step:(De)=>u(De),error:(De)=>r(De)};if(!await ks(R,"beforeDeploy",le))return!1;if(!await ks(R,"beforeBuild",le))return!1;for(let[De,Se]of Y){let Re=w(Se);if(i(`Preparing site: ${De}`),e(`Domain: ${Se.domain||"(none)"}`),e(`Source: ${Se.root}`),e(`Kind: ${Re==="server-static"?"static (shipped to /var/www)":"app (systemd service)"}`),Re==="server-app")e(`Start: ${Se.start}`),e(`Port: ${Se.port??"(unset)"}`);if(Se.build){u(`Running build: ${Se.build}`);try{dl(Se.build,{stdio:"inherit",cwd:process.cwd()})}catch(He){return r(`Build failed for site '${De}': ${He.message}`),!1}}let Pe=Gd({root:Se.root,siteName:De,expectIndex:Re==="server-static"});for(let He of Pe)if(He.level==="error")r(He.message);else a(He.message);if(Pe.some((He)=>He.level==="error"))return!1;let Ve=Se.build?void 0:JSON.stringify([Xf(Se.root),[...Se.exclude||[]].sort()]),Le=Ve?Ae.get(Ve):void 0;if(Le){u(`Reusing package for ${Se.root} → ${Le}`),be.set(De,Le);continue}let Fe=lu(cu(),`${X}-${De}-${ae}.tar.gz`);u(`Packaging ${Se.root} → ${Fe}`);try{let He=(Se.exclude||[]).map((wt)=>`--exclude=${wt}`);Yf("tar",["czf",Fe,"-C",Se.root,...He,"."],{stdio:"inherit",env:{...process.env,COPYFILE_DISABLE:"1"}})}catch(He){return r(`Failed to package '${De}': ${He.message}`),!1}if(be.set(De,Fe),Ve)Ae.set(Ve,Fe)}if(!await ks(R,"afterBuild",le))return!1;let ke=await uo({config:k?{...R,sites:Object.fromEntries(Y)}:R,rpxConfig:k?R:void 0,environment:t,driver:ie,sha:ae,runtime:re,tarballForSite:(De)=>{let Se=be.get(De);if(!Se)throw Error(`Missing tarball for site '${De}'`);return Se},logger:{info:(De)=>e(De),warn:(De)=>a(De),error:(De)=>r(De),step:(De)=>u(De),success:(De)=>n(De)}});if(ke&&!await ks(R,"afterDeploy",le))return!1;return ke}async function ya(R){let t=new Yr;if(u("Running pre-deployment security scan..."),R.exclude?.length)e(`Excluding from the scan: ${R.exclude.join(", ")}`);let m=await t.scan({directory:R.sourceDir,failOnSeverity:R.failOnSeverity||"critical",skipPatterns:R.skipPatterns,exclude:R.exclude});return{passed:m.passed,result:m}}function ng(){let R=lu(cu(),`ts-cloud-empty-scan-${process.pid}`);return au(R,{recursive:!0}),R}function ba(R){let{summary:t,findings:m,scannedFiles:k,duration:D}=R;if(e(`
|
|
376
376
|
Scanned ${k} files in ${D}ms`),t.critical>0)r(` Critical: ${t.critical}`);else e(` Critical: ${t.critical}`);if(t.high>0)a(` High: ${t.high}`);else e(` High: ${t.high}`);if(e(` Medium: ${t.medium}`),e(` Low: ${t.low}`),m.length>0){e(`
|
|
377
377
|
Findings:`);let L=m.filter((re)=>re.pattern.severity==="critical"),z=m.filter((re)=>re.pattern.severity==="high"),Y=m.filter((re)=>re.pattern.severity==="medium"),X=m.filter((re)=>re.pattern.severity==="low"),ne=(re,ie,ae)=>{if(re.length>0){console.log(`
|
|
378
|
-
${qt(`[${ie}]`,ae)}`);for(let be of re.slice(0,10))e(` ${be.pattern.name}`),e(` File: ${be.file}:${be.line}`),e(` Match: ${be.match}`);if(re.length>10)e(` ... and ${re.length-10} more ${ie.toLowerCase()} findings`)}};ne(L,"CRITICAL","red"),ne(z,"HIGH","yellow"),ne(Y,"MEDIUM","blue"),ne(X,"LOW","gray")}}function du(R,t){let m=Object.keys(R.environments??{});if(t&&m.includes(t))return t;if(m.includes("production"))return"production";if(m[0])return m[0];throw Error("A configured environment is required for security policy evaluation")}function cl(R,t,m){let k=du(R,t),D=y(process.cwd(),R);try{let L=new Mt(D.store);
|
|
378
|
+
${qt(`[${ie}]`,ae)}`);for(let be of re.slice(0,10))e(` ${be.pattern.name}`),e(` File: ${be.file}:${be.line}`),e(` Match: ${be.match}`);if(re.length>10)e(` ... and ${re.length-10} more ${ie.toLowerCase()} findings`)}};ne(L,"CRITICAL","red"),ne(z,"HIGH","yellow"),ne(Y,"MEDIUM","blue"),ne(X,"LOW","gray")}}function du(R,t){let m=Object.keys(R.environments??{});if(t&&m.includes(t))return t;if(m.includes("production"))return"production";if(m[0])return m[0];throw Error("A configured environment is required for security policy evaluation")}function cl(R,t,m){let k=du(R,t),D=y(process.cwd(),R);try{let L=new Mt(D.store);si(D);let z=ue(D,k);if(m)pc(L,z,m);else mc(L,z);let Y=mr(L,{scope:z,desiredConfigHash:D.project.desiredConfigHash});return e(`Security policy: ${Y.decision.policyId} v${Y.decision.policyVersion} — ${Y.decision.outcome.toUpperCase()}`),e(`Security decision: ${Y.decision.explanation}`),{allowed:Y.decision.outcome!=="block",explanation:Y.decision.explanation}}finally{D.store.close()}}async function rg(R,t){let m=du(R),k=y(process.cwd(),R);try{let D=new Mt(k.store);si(k);let L=ue(k,m),z=t.imageSha256?`${t.imageRef}@sha256:${t.imageSha256.replace(/^sha256:/,"")}`:t.imageRef;u("Scanning the built image and producing release security artifacts...");let Y=await gc(D,{scope:L,releaseId:z,imageRef:t.imageRef,imageSha256:t.imageSha256,artifactRoot:t.artifactRoot,invocationId:t.invocationId,startedAt:t.startedAt,externalParameters:{imageRef:t.imageRef,artifactRoot:t.artifactRoot}});e(`Image scanner: ${Y.scan.run.status} (${Y.scan.findings.length} findings)`),e(`Release SBOM: ${Y.sbomSource==="image"?"full image inventory":Y.sbomSource==="manifest"?"package-manifest fallback":"unavailable"}`);let X=mr(D,{scope:L,operationId:t.invocationId,desiredConfigHash:k.project.desiredConfigHash});return e(`Release policy: ${X.decision.policyId} v${X.decision.policyVersion} — ${X.decision.outcome.toUpperCase()}`),e(`Release decision: ${X.decision.explanation}`),{allowed:X.decision.outcome!=="block",explanation:X.decision.explanation}}finally{k.store.close()}}function ig(R){try{let t=y(process.cwd(),R),m=new ir(t.store).current();if(!m.attackMode&&!m.mitigationPause&&m.ipRules.allow.length===0&&m.ipRules.block.length===0)return R;let k=cs(R,m);return e(`Applying protection controls: ${m.ipRules.allow.length} allow, ${m.ipRules.block.length} block${m.attackMode?", attack mode ON":""}${m.mitigationPause?", mitigation PAUSED":""}`),k}catch{return R}}function ul(R){R.command("deploy:security-scan","Run pre-deployment security scan").option("--source <path>","Source directory to scan",{default:"."}).option("--fail-on <severity>","Fail on severity level (critical, high, medium, low)",{default:"critical"}).option("--skip-patterns <patterns>","Comma-separated list of pattern names to skip").action(async(t)=>{i("Pre-Deployment Security Scan");try{let m=t?.source||".",k=t?.failOn||"critical",D=t?.skipPatterns?.split(",").map((Y)=>Y.trim())||[];if(!Rn(m)){r(`Source directory not found: ${m}`);return}e(`Source: ${m}`),e(`Fail on: ${k} or higher severity`);let{passed:L,result:z}=await ya({sourceDir:m,failOnSeverity:k,skipPatterns:D});if(ba(z),L)n(`
|
|
379
379
|
✓ Security scan passed - no blocking issues found`);else r(`
|
|
380
380
|
✗ Security scan failed - blocking issues detected`),e(`
|
|
381
381
|
Recommendations:`),e(" 1. Remove any hardcoded credentials from your code"),e(" 2. Use environment variables or AWS Secrets Manager"),e(" 3. Add sensitive files to .gitignore"),e(" 4. Use --skip-patterns to ignore false positives"),process.exit(1)}catch(m){r(`Security scan failed: ${m.message}`),process.exit(1)}}),R.command("deploy","Deploy infrastructure").option("--stack <name>","Stack name").option("--env <environment>","Environment to deploy to").option("--site <name>","Deploy specific site only (routes are still regenerated in full)").option("--skip-security-scan","Skip pre-deployment security scan").option("--skip-dns-verification","Skip DNS provider verification and record creation (use when DNS is already configured)").option("--security-fail-on <severity>","Security scan fail threshold (critical, high, medium, low)",{default:"critical"}).option("--dry-run","Show the deployment plan without making changes").option("--yes","Skip confirmation prompts (non-interactive / CI)").action(async(t)=>{i("Deploying Infrastructure");let m=t?.yes===!0||process.env.CI==="true",k=t?.env||"staging",D=null;D=await og(k);try{let L=await Ne(),z=ig(L),{errors:Y,warnings:X}=Xs(z);for(let at of X)a(at);if(Y.length>0){r(`
|
|
382
|
-
✗ Deployment configuration is invalid:`);for(let at of Y)r(` • ${at}`);process.exitCode=1;return}if(t?.dryRun){if(t.site&&!z.sites?.[t.site]){r(`Site '${t.site}' was not found. Configured sites: ${Object.keys(z.sites||{}).join(", ")||"none"}`),process.exitCode=1;return}let at=H(z),Et=t.stack||T(z,k),gn=B(z),
|
|
382
|
+
✗ Deployment configuration is invalid:`);for(let at of Y)r(` • ${at}`);process.exitCode=1;return}if(t?.dryRun){if(t.site&&!z.sites?.[t.site]){r(`Site '${t.site}' was not found. Configured sites: ${Object.keys(z.sites||{}).join(", ")||"none"}`),process.exitCode=1;return}let at=H(z),Et=t.stack||T(z,k),gn=B(z),yn=z.project.region||"us-east-1",tr=t.site?[t.site]:Object.keys(z.sites||{});i("Deployment Plan"),e(`Cloud provider: ${gn}`),e(`Mode: ${at}`),e(`Stack: ${Et}`),e(`Region: ${yn}`),e(`Environment: ${k}`),e(`Sites: ${tr.length>0?tr.join(", "):"none"}`),await ou(z,{dryRun:!0}),await su(z,{dryRun:!0}),n("Dry run complete. No infrastructure, DNS, certificates, or application files were changed.");return}if(B(z)==="aws"){let at=z.project?.region||"us-east-1";await eg(at)}let ne;if(!t?.skipSecurityScan){let at=Kc(z,t?.site);if(!at)e("This deploy ships no files — every site in scope is a redirect or proxy route, so only gateway routes and certificates change. Scanning an empty artifact.");let{result:Et}=await ya({sourceDir:at?process.cwd():ng(),failOnSeverity:t?.securityFailOn||"critical",exclude:z.infrastructure?.security?.scan?.exclude});ba(Et),ne=Et}else a(`Security scan skipped (--skip-security-scan)
|
|
383
383
|
`);let re=cl(z,k,ne);if(!re.allowed){r(`
|
|
384
384
|
✗ Deployment blocked by security policy: ${re.explanation}`),e("Remediate the findings or create an authorized, time-limited waiver in the Security posture center."),process.exitCode=1;return}if(n(`✓ Security policy allows this deployment
|
|
385
|
-
`),!await ou(z)){process.exitCode=1;return}if(!await su(z)){process.exitCode=1;return}if(H(z)==="serverless"){u("Serverless project detected: deploying the Lambda application");let{deployServerlessApp:at}=await import("./chunk-
|
|
386
|
-
Resources to deploy: ${it}`);let xt={};for(let at of Object.values(Pe.Resources)){let Et=at.Type;xt[Et]=(xt[Et]||0)+1}for(let[at,Et]of Object.entries(xt).sort((gn,
|
|
387
|
-
Deploy now?`,!0))){e("Deployment cancelled");return}let Nt=new v(ae),Ye=Aa(Pe);if(Ye.length>0){let{ensureFailoverReplicaBuckets:at}=await import("./chunk-
|
|
385
|
+
`),!await ou(z)){process.exitCode=1;return}if(!await su(z)){process.exitCode=1;return}if(H(z)==="serverless"){u("Serverless project detected: deploying the Lambda application");let{deployServerlessApp:at}=await import("./chunk-6cyr6gcg.js");await at(z,k,{}),n("Serverless deployment complete");return}let ie=t?.stack||T(z,k),ae=z.project.region||"us-east-1",be=z.infrastructure?.deployStack!==!1,Ae=z.sites&&Object.keys(z.sites).length>0,le=z.infrastructure?.dns?.provider;if(Ae&&le){if(await lg(z,t?.site,le,ae,t?.skipDnsVerification,k,m),!be){if(z.infrastructure?.compute){let at=Object.values(z.sites).map((Et)=>Et.domain).filter((Et)=>!!Et);if(at.length>0)u("Syncing CloudFront dynamic HTTP methods for app domains..."),await oa(at)}return}}if(!be){e("Infrastructure stack deployment disabled (infrastructure.deployStack: false)");return}let ke=B(z);if(e(`Cloud provider: ${ke}`),e(`Stack: ${ie}`),e(`Region: ${ae}`),e(`Environment: ${k}`),(ke==="hetzner"||z.infrastructure?.compute?.mode==="server")&&Uo(z)){u(`Provisioning ${ke} compute infrastructure...`);let at=O({config:z,provider:ke});if(!at.provisionComputeInfrastructure){r(`${ke} driver does not support compute provisioning`),process.exitCode=1;return}let Et=await at.provisionComputeInfrastructure({config:z,environment:k});if(n(`${ke} compute infrastructure ready`),Et.appPublicIp)e(`App server: ${Et.appPublicIp}`);if(Et.appInstanceId)e(`Server ID: ${Et.appInstanceId}`);if(!await al(z,k,ae,t?.site))r(`App deploy to ${ke} compute reported a failure`),process.exitCode=1;else{if(n(`App deployed to ${ke} compute`),t?.skipDnsVerification)e("DNS verification and record reconciliation skipped (--skip-dns-verification)");else await sg(z,Et.appPublicIp,le,Et.appPublicIpv6),await ag(z,le);let yn=await po({config:z,environment:k,driver:at,logger:{info:e,warn:a,error:r,step:u,success:n}});if(!yn)r("App deployed, but rpx TLS certificate reconciliation failed");if(!yn)process.exitCode=1;if(!t?.skipDnsVerification)await cg(z,Et.appPublicIp,Et.appPublicIpv6)}return}u("Generating CloudFormation template...");let Se=new Lo({config:z,environment:k});Se.generate();let Re=Se.toJSON(),Pe=JSON.parse(Re);if(Object.keys(Pe.Resources||{}).length===0){e(`No infrastructure resources to deploy for environment '${k}' — skipping stack ${ie}.`);return}u("Validating template...");let Le=rs(Pe),Fe=is(Re),He=os(Pe),wt=[...Le.errors,...Fe.errors,...He.errors];if(wt.length>0){r("Template validation failed:");for(let at of wt)r(` - ${at.path}: ${at.message}`);return}let Dt=[...Le.warnings,...Fe.warnings,...He.warnings];if(Dt.length>0)for(let at of Dt)a(` - ${at.path}: ${at.message}`);n("Template validated successfully");let it=Object.keys(Pe.Resources).length;e(`
|
|
386
|
+
Resources to deploy: ${it}`);let xt={};for(let at of Object.values(Pe.Resources)){let Et=at.Type;xt[Et]=(xt[Et]||0)+1}for(let[at,Et]of Object.entries(xt).sort((gn,yn)=>yn[1]-gn[1]).slice(0,5))e(` - ${at}: ${Et}`);if(!(m||await d(`
|
|
387
|
+
Deploy now?`,!0))){e("Deployment cancelled");return}let Nt=new v(ae),Ye=Aa(Pe);if(Ye.length>0){let{ensureFailoverReplicaBuckets:at}=await import("./chunk-h9h9dn0r.js");u("Ensuring failover replica buckets..."),await at(Ye,(Et)=>e(` ${Et}`))}u("Checking stack status...");let _t=!1;try{let at=await Nt.describeStacks({stackName:ie});_t=at.Stacks&&at.Stacks.length>0}catch(at){_t=!1}if(_t){e("Stack exists, updating...");let at=new s("Updating CloudFormation stack...");at.start();try{await Nt.updateStack({stackName:ie,templateBody:Re,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:z.project.name},{Key:"Environment",Value:k},{Key:"ManagedBy",Value:"ts-cloud"}]}),at.succeed("Update initiated"),u("Waiting for stack update to complete..."),await Nt.waitForStack(ie,"stack-update-complete"),n("Stack updated successfully!")}catch(Et){if(Et.message.includes("No updates are to be performed"))at.succeed("No changes detected"),e("Stack is already up to date — continuing to app deploy");else throw Et}}else{e("Creating new stack...");let at=new s("Creating CloudFormation stack...");at.start(),await Nt.createStack({stackName:ie,templateBody:Re,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:z.project.name},{Key:"Environment",Value:k},{Key:"ManagedBy",Value:"ts-cloud"}]}),at.succeed("Stack creation initiated"),u("Waiting for stack creation to complete..."),await Nt.waitForStack(ie,"stack-create-complete"),n("Stack created successfully!")}let Wt=await Nt.getStackOutputs(ie);if(Ee(`Deployment Complete!
|
|
388
388
|
|
|
389
389
|
Stack: ${ie}
|
|
390
390
|
Region: ${ae}
|
|
@@ -393,23 +393,23 @@ Resources: ${it}
|
|
|
393
393
|
|
|
394
394
|
View in console:
|
|
395
395
|
https://console.aws.amazon.com/cloudformation/home?region=${ae}#/stacks/stackinfo?stackId=${encodeURIComponent(ie)}`,"green"),Object.keys(Wt).length>0){e(`
|
|
396
|
-
Stack Outputs:`);for(let[at,Et]of Object.entries(Wt))e(` - ${at}: ${Et}`)}if(Ye.length>0){let{grantFailoverReplicaAccess:at}=await import("./chunk-
|
|
397
|
-
Stack trace:`),console.error(L.stack)}finally{if(D)await D()}}),R.command("deploy:server","Deploy EC2 infrastructure").option("--env <environment>","Environment (production, staging, development)").option("--site <name>","Deploy specific site only (routes are still regenerated in full)").action(async(t)=>{i("Deploying Server Infrastructure");try{let m=await Ne(),k=t?.env||"production",D=m.project.region||"us-east-1",L=
|
|
398
|
-
Server deployment complete!`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),R.command("deploy:serverless","Deploy a serverless application (http/queue/cli Lambda functions)").option("--env <environment>","Environment (production, staging, development)").option("--skip-build","Skip local build hooks").option("--skip-hooks","Skip remote deploy hooks (e.g. migrations)").option("--redeploy","Re-activate the last build without rebuilding").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",D=
|
|
399
|
-
${X.instanceId}:`),e(X.output?.trimEnd()||"(no output)")}catch(k){r(`History lookup failed: ${k.message}`),process.exitCode=1}}),R.command("deploy:recipe <name> <script>","Run a reusable server recipe (a local bash file) across servers").option("--env <environment>","Environment (production, staging, development)").option("--user <user>","User to run the recipe as",{default:"root"}).action(async(t,m,k)=>{i(`Running Recipe: ${t}`);try{if(!
|
|
400
|
-
`),{runComputeRecipe:Y}=await import("./chunk-
|
|
401
|
-
${ne.instanceId}:`),e(ne.output?.trimEnd()||"(no output)");if(!X.success)process.exitCode=1}catch(D){r(`Recipe failed: ${D.message}`),process.exitCode=1}}),R.command("db:restore-backup [from]","Restore the app database from an on-box ts-backups dump (latest, or a given file)").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{i("Database Restore");try{let k=await Ne(),D=m?.env||"production",{restoreDatabaseBackup:L}=await import("./chunk-
|
|
402
|
-
(recorded as #${z} — re-run with \`cloud command:again ${z}\`)`)}catch(k){r(`Command failed: ${k.message}`),process.exitCode=1}}),R.command("command:history","List recorded CLI-function command invocations").option("--env <environment>","Environment (production, staging, development)").option("--limit <n>","Show at most this many (most recent)",{default:"20"}).action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{listCommandHistory:D}=await import("./chunk-
|
|
403
|
-
(recorded as #${X.id})`)}catch(k){r(`Command failed: ${k.message}`),process.exitCode=1}}),R.command("serverless:db-shell <sql>","Run a SQL statement against a private serverless database (via the CLI function)").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{try{let k=await Ne(),D=m?.env||"production",{runDbQuery:L}=await import("./chunk-
|
|
404
|
-
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),R.command("serverless:build-node-layer","Build + publish a ts-cloud Node custom runtime layer (any version, incl. 24)").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--node <version>","Node version (e.g. 24)",{default:"24"}).option("--name <name>","Layer name",{default:"tscloud-node"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(t)=>{i("Building Node runtime layer");try{let m=await Ne().catch(()=>null),k=t?.region||m?.project.region||"us-east-1",D=t?.arch||"x86_64",L=t?.node||"24",{buildNodeRuntimeLayerZip:z}=await import("./chunk-
|
|
405
|
-
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),R.command("serverless:build-bun-layer","Build + publish a ts-cloud Bun custom runtime layer").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--bun <version>",'Bun version (e.g. 1.3.13, or "latest")',{default:"latest"}).option("--name <name>","Layer name",{default:"tscloud-bun"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(t)=>{i("Building Bun runtime layer");try{let m=await Ne().catch(()=>null),k=t?.region||m?.project.region||"us-east-1",D=t?.arch||"x86_64",L=t?.bun||"latest",{buildBunRuntimeLayerZip:z}=await import("./chunk-
|
|
406
|
-
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),R.command("deploy:status","Check deployment status").option("--stack <name>","Stack name").option("--env <environment>","Environment").action(async(t)=>{i("Deployment Status");try{let m=await Ne(),k=t?.env||"production",D=t?.stack||T(m,k),L=m.project.region||"us-east-1";e(`Stack: ${D}`),e(`Region: ${L}`);let z=new s("Checking deployment status...");z.start();let X=await new v(L).describeStacks({stackName:D});if(X.Stacks.length===0){z.fail("Stack not found"),
|
|
396
|
+
Stack Outputs:`);for(let[at,Et]of Object.entries(Wt))e(` - ${at}: ${Et}`)}if(Ye.length>0){let{grantFailoverReplicaAccess:at}=await import("./chunk-h9h9dn0r.js");u("Granting the distribution read access to its failover replicas..."),await at(Ye,Wt,(Et)=>e(` ${Et}`))}if(z.infrastructure?.storage)for(let[at,Et]of Object.entries(z.infrastructure.storage)){if(!Et.website||!Et.root)continue;let gn=Wt[`${at}BucketName`]||Wt.FrontendBucketName,yn=Wt[`${at}CloudFrontDistributionId`];if(!gn){a(`Could not find bucket name for storage '${at}' — skipping file upload`);continue}if(!Rn(Et.root)){a(`Source directory not found: ${Et.root} — skipping file upload for '${at}'`);continue}u(`Uploading files from ${Et.root} to s3://${gn}...`);let{uploadStaticFiles:tr,invalidateCache:Mi}=await import("./chunk-6rwewb7j.js"),Sn=await tr({sourceDir:Et.root,bucket:gn,region:ae,onProgress:(Gt,In,Pn)=>{if(Gt%10===0||Gt===In)e(` ${Gt}/${In}: ${Pn}`)}});if(Sn.errors.length>0)a(`Upload completed with errors: ${Sn.errors.join(", ")}`);else{let Gt=Sn.skipped>0?`Uploaded ${Sn.uploaded} files (${Sn.skipped} unchanged)`:`Uploaded ${Sn.uploaded} files`;n(Gt)}if(yn&&Sn.uploaded>0){u("Invalidating CloudFront cache...");let{invalidationId:Gt}=await Mi(yn);n(`Cache invalidation created: ${Gt}`)}}if(Ye.length>0){let{seedFailoverReplicas:at}=await import("./chunk-h9h9dn0r.js");u("Syncing failover replicas..."),await at(Ye,(Et)=>e(` ${Et}`))}if(z.infrastructure?.compute)await al(z,k,ae,t?.site);if(z.infrastructure?.compute&&z.sites){let at=Object.values(z.sites).map((Et)=>Et.domain).filter((Et)=>!!Et);if(at.length>0)u("Syncing CloudFront dynamic HTTP methods for app domains..."),await oa(at)}}catch(L){if(r(`Deployment failed: ${L.message}`),process.exitCode=1,L.stack)e(`
|
|
397
|
+
Stack trace:`),console.error(L.stack)}finally{if(D)await D()}}),R.command("deploy:server","Deploy EC2 infrastructure").option("--env <environment>","Environment (production, staging, development)").option("--site <name>","Deploy specific site only (routes are still regenerated in full)").action(async(t)=>{i("Deploying Server Infrastructure");try{let m=await Ne(),k=t?.env||"production",D=m.project.region||"us-east-1",L=Br(m);if(L){r(L),process.exitCode=1;return}if(!Uo(m)){r("No `infrastructure.compute` configured — nothing to deploy as a server."),e("Add an EC2 compute block to cloud.config, or use `cloud deploy:serverless` for Lambda apps."),process.exitCode=1;return}if(e(`Region: ${D}`),e(`Environment: ${k}`),!await al(m,k,D,t?.site)){process.exitCode=1;return}if(m.sites){let Y=Object.values(m.sites).map((X)=>X.domain).filter((X)=>!!X);if(Y.length>0)u("Syncing CloudFront dynamic HTTP methods for app domains..."),await oa(Y)}n(`
|
|
398
|
+
Server deployment complete!`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),R.command("deploy:serverless","Deploy a serverless application (http/queue/cli Lambda functions)").option("--env <environment>","Environment (production, staging, development)").option("--skip-build","Skip local build hooks").option("--skip-hooks","Skip remote deploy hooks (e.g. migrations)").option("--redeploy","Re-activate the last build without rebuilding").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",D=Br(m);if(D){r(D),process.exitCode=1;return}let{deployServerlessApp:L,redeployServerlessApp:z}=await import("./chunk-6cyr6gcg.js");if(t?.redeploy){await z(m,k);return}await L(m,k,{skipBuild:t?.skipBuild,skipDeployHooks:t?.skipHooks})}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),R.command("serverless:rollback","Roll back a serverless app to the previous build").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{rollbackServerlessApp:D}=await import("./chunk-6cyr6gcg.js");await D(m,k)}catch(m){r(`Rollback failed: ${m.message}`),process.exitCode=1}}),R.command("deploy:rollback [site]","Roll a compute site back to a previous release").option("--env <environment>","Environment (production, staging, development)").option("--to <release>","Release id to roll back to (default: the previous release)").option("--dry-run","Say which release it would roll back to, and change nothing").option("--all","Every configured site, instead of one").option("--only-if-live <release>","Only roll back a site whose live release is this commit (what a failed deploy switched)").action(async(t,m)=>{i("Rolling Back Release");try{let k=await Ne(),D=m?.env||"production",L=m?.all?Object.keys(k.sites||{}):[t||Object.keys(k.sites||{})[0]].filter(Boolean);if(L.length===0){r("No site configured to roll back."),process.exitCode=1;return}let{rollbackComputeSite:z}=await import("./chunk-4vz5erjz.js"),Y=O({config:k});for(let X of L){let ne=await z({driver:Y,slug:k.project.slug,environment:D,logger:f},{siteName:X,to:m?.to,dryRun:m?.dryRun,onlyIfLive:m?.onlyIfLive,site:k.sites?.[X]});for(let re of ne.perInstance||[])e(` ${X} ${re.instanceId}: ${re.output?.trim()||re.status}`);if(!ne.success)r(`Rollback of ${X} failed: ${ne.error||"unknown error"}`),process.exitCode=1}}catch(k){r(`Rollback failed: ${k.message}`),process.exitCode=1}}),R.command("deploy:history [site]","Show a compute site's deployment history").option("--env <environment>","Environment (production, staging, development)").option("--limit <n>","Number of entries to show",{default:"20"}).action(async(t,m)=>{i("Deployment History");try{let k=await Ne(),D=m?.env||"production",L=t||Object.keys(k.sites||{})[0];if(!L){r("No site configured."),process.exitCode=1;return}let{getComputeDeployHistory:z}=await import("./chunk-4vz5erjz.js"),Y=await z({driver:O({config:k}),slug:k.project.slug,environment:D,logger:f},{siteName:L,limit:Number.parseInt(m?.limit||"20",10)||20});if(!Y.success){r(`Could not read history: ${Y.error||"unknown error"}`),process.exitCode=1;return}for(let X of Y.perInstance||[])e(`
|
|
399
|
+
${X.instanceId}:`),e(X.output?.trimEnd()||"(no output)")}catch(k){r(`History lookup failed: ${k.message}`),process.exitCode=1}}),R.command("deploy:recipe <name> <script>","Run a reusable server recipe (a local bash file) across servers").option("--env <environment>","Environment (production, staging, development)").option("--user <user>","User to run the recipe as",{default:"root"}).action(async(t,m,k)=>{i(`Running Recipe: ${t}`);try{if(!Rn(m)){r(`Recipe script not found: ${m}`),process.exitCode=1;return}let D=await Ne(),L=k?.env||"production",z=ll(m,"utf8").split(`
|
|
400
|
+
`),{runComputeRecipe:Y}=await import("./chunk-4vz5erjz.js"),X=await Y({driver:O({config:D}),slug:D.project.slug,environment:L,logger:f},{name:t,script:z,user:k?.user});for(let ne of X.perInstance||[])e(`
|
|
401
|
+
${ne.instanceId}:`),e(ne.output?.trimEnd()||"(no output)");if(!X.success)process.exitCode=1}catch(D){r(`Recipe failed: ${D.message}`),process.exitCode=1}}),R.command("db:restore-backup [from]","Restore the app database from an on-box ts-backups dump (latest, or a given file)").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{i("Database Restore");try{let k=await Ne(),D=m?.env||"production",{restoreDatabaseBackup:L}=await import("./chunk-4vz5erjz.js"),z=await L({driver:O({config:k}),slug:k.project.slug,environment:D,logger:f},{database:A(k),from:t});for(let Y of z.perInstance||[])e(` ${Y.instanceId}: ${Y.output?.trim()||Y.status}`);if(!z.success)r(`Restore failed: ${z.error||"unknown error"}`),process.exitCode=1}catch(k){r(`Restore failed: ${k.message}`),process.exitCode=1}}),R.command("quick-deploy","Generate a push-to-deploy CI pipeline for your git provider (Forge Quick Deploy)").option("--env <environment>","Environment to deploy on push",{default:"production"}).option("--provider <provider>","CI provider (github, gitlab, bitbucket); defaults to the origin remote").option("--site <site>","Deploy only this configured site").option("--skip-dns-verification","Generate a deploy that skips DNS verification and record creation").option("--ssh-key-secret <name>","CI secret/variable containing the Hetzner deployment SSH private key",{default:"SSH_PRIVATE_KEY"}).option("--force","Overwrite an existing pipeline file").action(async(t)=>{i("Quick Deploy (push-to-deploy)");try{let m=await Ne(),{buildQuickDeployCi:k,inferQuickDeployProvider:D}=await import("./chunk-d28q9ath.js"),L=t?.provider?.toLowerCase();if(L&&!["github","gitlab","bitbucket"].includes(L))throw Error(`Unsupported CI provider '${t?.provider}'. Use github, gitlab, or bitbucket.`);let z=L;if(!z)try{let X=dl("git remote get-url origin",{stdio:["ignore","pipe","ignore"]}).toString().trim();z=D(X)}catch{}let Y=k(m,t?.env||"production",{provider:z,site:t?.site,skipDnsVerification:t?.skipDnsVerification,setup:Rn("pantry.lock")?"pantry":"bun",sshPrivateKeySecret:t?.sshKeySecret});if(!Y){a("Could not resolve a GitHub, GitLab, or Bitbucket provider from --provider, the origin remote, or a configured site repository."),process.exitCode=1;return}if(Rn(Y.path)&&!t?.force){a(`${Y.path} already exists — re-run with --force to overwrite.`);return}au(Qf(Y.path),{recursive:!0}),Zf(Y.path,Y.content),n(`Wrote ${Y.path} (${Y.provider}, deploys on push to '${Y.branch}').`),e("Next: commit it and add your provider credentials as CI secrets/variables.")}catch(m){r(`Quick deploy setup failed: ${m.message}`),process.exitCode=1}}),R.command("down","Put the serverless app into maintenance mode (503)").option("--env <environment>","Environment (production, staging, development)").option("--secret <secret>","Bypass secret (send as x-maintenance-bypass header)").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{setMaintenance:D}=await import("./chunk-6cyr6gcg.js");await D(m,k,!0,t?.secret)}catch(m){r(`Failed to enable maintenance mode: ${m.message}`),process.exitCode=1}}),R.command("up","Bring the serverless app out of maintenance mode").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{setMaintenance:D}=await import("./chunk-6cyr6gcg.js");await D(m,k,!1)}catch(m){r(`Failed to disable maintenance mode: ${m.message}`),process.exitCode=1}}),R.command("command <cmd>","Run a command on the serverless CLI function (e.g. migrations)").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{try{let k=await Ne(),D=m?.env||"production",{runAndRecordCommand:L}=await import("./chunk-6cyr6gcg.js"),{id:z,output:Y}=await L(k,D,t);if(e(Y),z>0)e(`
|
|
402
|
+
(recorded as #${z} — re-run with \`cloud command:again ${z}\`)`)}catch(k){r(`Command failed: ${k.message}`),process.exitCode=1}}),R.command("command:history","List recorded CLI-function command invocations").option("--env <environment>","Environment (production, staging, development)").option("--limit <n>","Show at most this many (most recent)",{default:"20"}).action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{listCommandHistory:D}=await import("./chunk-6cyr6gcg.js"),L=await D(m,k);if(!L.length){e("No command history yet.");return}let z=Number(t?.limit??20);i(`Command history — ${m.project.slug} (${k})`),c(["#","When","Status","Command"],L.slice(-z).reverse().map((Y)=>[String(Y.id),Y.timestamp,Y.status,Y.command]))}catch(m){r(`Failed to read history: ${m.message}`),process.exitCode=1}}),R.command("command:again [id]","Re-run a recorded command (defaults to the most recent)").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{try{let k=await Ne(),D=m?.env||"production",{getCommandRecord:L,runAndRecordCommand:z}=await import("./chunk-6cyr6gcg.js"),Y=await L(k,D,t?Number(t):void 0);if(!Y){r(t?`No command #${t} in history.`:"No command history to re-run."),process.exitCode=1;return}e(`Re-running #${Y.id}: ${Y.command}`);let X=await z(k,D,Y.command);if(e(X.output),X.id>0)e(`
|
|
403
|
+
(recorded as #${X.id})`)}catch(k){r(`Command failed: ${k.message}`),process.exitCode=1}}),R.command("serverless:db-shell <sql>","Run a SQL statement against a private serverless database (via the CLI function)").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{try{let k=await Ne(),D=m?.env||"production",{runDbQuery:L}=await import("./chunk-6cyr6gcg.js"),z=await L(k,D,t);e(z)}catch(k){r(`Query failed: ${k.message}`),process.exitCode=1}}),R.command("serverless:db-scale <min> <max>","Rescale the serverless Aurora cluster (min/max ACUs)").option("--env <environment>","Environment (production, staging, development)").action(async(t,m,k)=>{i("Scaling serverless database");try{let D=await Ne(),L=k?.env||"production",z=Number(t),Y=Number(m);if(!(z>0)||!(Y>=z)){r("Provide valid capacities: min > 0 and max >= min (e.g. `serverless:db-scale 0.5 8`)."),process.exitCode=1;return}let{scaleServerlessDatabase:X}=await import("./chunk-6cyr6gcg.js");await X(D,L,z,Y)}catch(D){r(`Scale failed: ${D.message}`),process.exitCode=1}}),R.command("serverless:db-restore","Restore the serverless Aurora cluster to a point in time (as a new cluster)").option("--env <environment>","Environment (production, staging, development)").option("--to <timestamp>","Restore to this ISO-8601 time (UTC)").option("--latest","Restore to the latest restorable time").option("--target <id>","New cluster identifier (defaults to <cluster>-restore-<stamp>)").action(async(t)=>{i("Restoring serverless database");try{let m=await Ne(),k=t?.env||"production",D;if(t?.to){if(D=new Date(t.to),Number.isNaN(D.getTime())){r(`Invalid --to timestamp: ${t.to} (use ISO-8601, e.g. 2026-06-18T10:00:00Z).`),process.exitCode=1;return}}let{restoreServerlessDatabase:L}=await import("./chunk-6cyr6gcg.js");await L(m,k,{toTime:D,latest:t?.latest,target:t?.target})}catch(m){r(`Restore failed: ${m.message}`),process.exitCode=1}}),R.command("serverless:info","Show an operational summary of a deployed serverless app").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",{serverlessInfo:D}=await import("./chunk-6cyr6gcg.js"),L=await D(m,k);if(i(`${L.slug} (${L.environment})`),e(`Stack: ${L.stackStatus}`),e(`Region: ${L.region}`),L.endpoint)e(`URL: ${L.endpoint}`);if(L.assetUrl)e(`Assets: ${L.assetUrl}`);if(e(`Scheduler:${L.scheduler==="off"?" off":` ${L.scheduler}`}`),e(`Queues: ${L.queues.length?L.queues.join(", "):"none"}`),e(`Warming: ${L.provisionedConcurrency>0?`provisioned concurrency ×${L.provisionedConcurrency}`:"none / ping-warm"}`),L.lastRelease)e(`Release: ${L.lastRelease.sha.slice(0,12)} @ ${L.lastRelease.timestamp}`);c(["Function","Version","Provisioned"],L.functions.map((z)=>[z.name,z.version,z.provisioned?`${z.provisioned.status} ${z.provisioned.allocated}/${z.provisioned.requested}`:"—"]))}catch(m){r(`Failed to read info: ${m.message}`),process.exitCode=1}}),R.command("dashboard:build","Build the management dashboard with live data baked in").option("--env <environment>","Environment (production, staging, development)").option("--ui <dir>","UI directory (default ./ui)",{default:"ui"}).action(async(t)=>{i("Building management dashboard (live data)");try{let{existsSync:m}=await import("node:fs"),k=t?.ui||"ui";if(!m(`${k}/package.json`)){r(`No UI project at ${k}/ (expected ${k}/package.json). Pass --ui <dir>.`),process.exitCode=1;return}let D=await Ne(),L=t?.env||"production";u("Gathering live data from AWS");let z=null;if(D.environments?.[L]?.app){let{resolveDashboardData:re}=await import("./chunk-dycx66c7.js"),ie=await re(D,L).catch((ae)=>(a(`Serverless data unavailable (${ae.message}) — that view uses sample data.`),null));if(ie)z={...z??{},...ie}}if(D.infrastructure?.compute){let{resolveServerDashboardData:re}=await import("./chunk-fm6paqft.js"),ie=await re(D,L).catch(()=>null);if(ie){if(ie._serverReachable===!1)a("Server box not reachable over SSM — server view uses config + sample data.");z={...z??{},...ie}}}u("Building dashboard");let{encodeDashboardPayload:Y}=await import("./chunk-19dhvnjh.js"),{execSync:X}=await import("node:child_process"),{rmSync:ne}=await import("node:fs");for(let re of[".stx/ssg-cache",".stx/cache"])ne(`${k}/${re}`,{recursive:!0,force:!0});X("bun run build",{cwd:k,stdio:"inherit",env:{...process.env,...z?{TSCLOUD_DASHBOARD_DATA:Y(z)}:{}}}),n(`Dashboard built → ${k}/dist${z?" (live data)":" (sample data)"}`)}catch(m){r(`Dashboard build failed: ${m.message}`),process.exitCode=1}}),R.command("serverless:build-php-layer","Build + publish the ts-cloud PHP runtime layer (requires Docker)").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--php <version>","PHP version",{default:"8.3"}).option("--name <name>","Layer name",{default:"tscloud-php"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(t)=>{i("Building PHP runtime layer");try{let m=await Ne().catch(()=>null),k=t?.region||m?.project.region||"us-east-1",D=t?.arch||"x86_64",L=t?.php||"8.3",z=`${t?.name||"tscloud-php"}-${L.replace(".","")}-${D}`,Y=t?.bucket||`${m?.project.slug||"tscloud"}-layers`,{buildPhpRuntimeLayerZip:X}=await import("./chunk-sn1zde06.js"),{S3Client:ne}=await import("./chunk-h5ccvped.js"),{LambdaClient:re}=await import("./chunk-vrn0akcn.js"),ie=X({architecture:D,phpVersion:L,onStep:(ke)=>u(ke)});e(`Layer: ${ie.fileCount} files, ${(ie.zip.length/1024/1024).toFixed(1)} MB`);let ae=new ne(k);if(!await ae.bucketExists(Y))u(`Creating layer bucket ${Y}`),await ae.createBucket(Y);let be=`layers/${z}.zip`;u("Uploading layer zip"),await ae.putObject({bucket:Y,key:be,body:ie.zip,contentType:"application/zip"}),u("Publishing layer version");let le=await new re(k).publishLayerVersion({LayerName:z,Description:`ts-cloud PHP ${L} runtime (${D})`,Content:{S3Bucket:Y,S3Key:be},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[D]});Ee(["PHP runtime layer published","",`ARN: ${le.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'php', layers: ['${le.LayerVersionArn}'] }`,"or set TSCLOUD_PHP_LAYER_ARN before deploying."].join(`
|
|
404
|
+
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),R.command("serverless:build-node-layer","Build + publish a ts-cloud Node custom runtime layer (any version, incl. 24)").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--node <version>","Node version (e.g. 24)",{default:"24"}).option("--name <name>","Layer name",{default:"tscloud-node"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(t)=>{i("Building Node runtime layer");try{let m=await Ne().catch(()=>null),k=t?.region||m?.project.region||"us-east-1",D=t?.arch||"x86_64",L=t?.node||"24",{buildNodeRuntimeLayerZip:z}=await import("./chunk-sn1zde06.js"),{S3Client:Y}=await import("./chunk-h5ccvped.js"),{LambdaClient:X}=await import("./chunk-vrn0akcn.js"),ne=z({architecture:D,version:L,onStep:(ke)=>u(ke)}),re=`${t?.name||"tscloud-node"}-${ne.version.split(".")[0]}-${D}`,ie=t?.bucket||`${m?.project.slug||"tscloud"}-layers`;e(`Layer: Node ${ne.version}, ${(ne.zip.length/1024/1024).toFixed(1)} MB`);let ae=new Y(k);if(!await ae.bucketExists(ie))u(`Creating layer bucket ${ie}`),await ae.createBucket(ie);let be=`layers/${re}.zip`;u("Uploading layer zip"),await ae.putObject({bucket:ie,key:be,body:ne.zip,contentType:"application/zip"}),u("Publishing layer version");let le=await new X(k).publishLayerVersion({LayerName:re,Description:`ts-cloud Node ${ne.version} runtime (${D})`,Content:{S3Bucket:ie,S3Key:be},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[D]});Ee(["Node runtime layer published","",`ARN: ${le.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'node', runtimeVersion: '${ne.version.split(".")[0]}', layers: ['${le.LayerVersionArn}'] }`,"or set TSCLOUD_NODE_LAYER_ARN before deploying."].join(`
|
|
405
|
+
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),R.command("serverless:build-bun-layer","Build + publish a ts-cloud Bun custom runtime layer").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--bun <version>",'Bun version (e.g. 1.3.13, or "latest")',{default:"latest"}).option("--name <name>","Layer name",{default:"tscloud-bun"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(t)=>{i("Building Bun runtime layer");try{let m=await Ne().catch(()=>null),k=t?.region||m?.project.region||"us-east-1",D=t?.arch||"x86_64",L=t?.bun||"latest",{buildBunRuntimeLayerZip:z}=await import("./chunk-sn1zde06.js"),{S3Client:Y}=await import("./chunk-h5ccvped.js"),{LambdaClient:X}=await import("./chunk-vrn0akcn.js"),ne=z({architecture:D,version:L,onStep:(ke)=>u(ke)}),re=`${t?.name||"tscloud-bun"}-${ne.version.replace(/\./g,"")}-${D}`,ie=t?.bucket||`${m?.project.slug||"tscloud"}-layers`;e(`Layer: Bun ${ne.version}, ${(ne.zip.length/1024/1024).toFixed(1)} MB`);let ae=new Y(k);if(!await ae.bucketExists(ie))u(`Creating layer bucket ${ie}`),await ae.createBucket(ie);let be=`layers/${re}.zip`;u("Uploading layer zip"),await ae.putObject({bucket:ie,key:be,body:ne.zip,contentType:"application/zip"}),u("Publishing layer version");let le=await new X(k).publishLayerVersion({LayerName:re,Description:`ts-cloud Bun ${ne.version} runtime (${D})`,Content:{S3Bucket:ie,S3Key:be},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[D]});Ee(["Bun runtime layer published","",`ARN: ${le.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'bun', layers: ['${le.LayerVersionArn}'] }`,"or set TSCLOUD_BUN_LAYER_ARN before deploying."].join(`
|
|
406
|
+
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),R.command("deploy:status","Check deployment status").option("--stack <name>","Stack name").option("--env <environment>","Environment").action(async(t)=>{i("Deployment Status");try{let m=await Ne(),k=t?.env||"production",D=t?.stack||T(m,k),L=m.project.region||"us-east-1";e(`Stack: ${D}`),e(`Region: ${L}`);let z=new s("Checking deployment status...");z.start();let X=await new v(L).describeStacks({stackName:D});if(X.Stacks.length===0){z.fail("Stack not found"),xr("No deployment found for this environment");return}let ne=X.Stacks[0];if(z.succeed("Status retrieved"),e(`
|
|
407
407
|
Status: ${ne.StackStatus}`),e(`Created: ${ne.CreationTime}`),ne.LastUpdatedTime)e(`Last Updated: ${ne.LastUpdatedTime}`);if(ne.Outputs&&ne.Outputs.length>0){e(`
|
|
408
408
|
Outputs:`);for(let re of ne.Outputs)e(` ${re.OutputKey}: ${re.OutputValue}`)}}catch(m){r(`Failed to get status: ${m.message}`)}}),R.command("deploy:rollback","Rollback to previous version").option("--stack <name>","Stack name").option("--env <environment>","Environment").action(async(t)=>{i("Rolling Back Deployment");try{let m=await Ne(),k=t?.env||"production",D=t?.stack||T(m,k),L=m.project.region||"us-east-1";if(e(`Stack: ${D}`),e(`Region: ${L}`),!await d(`
|
|
409
409
|
Are you sure you want to rollback?`,!1)){e("Rollback cancelled");return}let Y=new s("Rolling back stack...");Y.start();let X=new v(L);await X.deleteStack(D),Y.succeed("Stack deletion initiated"),u("Waiting for stack deletion..."),await X.waitForStack(D,"stack-delete-complete"),n("Stack rolled back successfully!")}catch(m){r(`Rollback failed: ${m.message}`)}}),R.command("deploy:static","Deploy static site (S3 + CloudFront invalidation)").option("--source <path>","Source directory",{default:"dist"}).option("--bucket <name>","S3 bucket name").option("--distribution <id>","CloudFront distribution ID").option("--prefix <prefix>","S3 prefix/folder").option("--delete","Delete files not in source").option("--cache-control <value>","Cache-Control header",{default:"public, max-age=31536000"}).option("--no-invalidate","Skip CloudFront invalidation").option("--wait","Wait for invalidation to complete").option("--skip-security-scan","Skip pre-deployment security scan").option("--security-fail-on <severity>","Security scan fail threshold (critical, high, medium, low)",{default:"critical"}).action(async(t)=>{i("Deploying Static Site");try{let m=await Ne(),k=m.project.region||"us-east-1",D=t?.source||"dist",L;if(!t?.skipSecurityScan){u("Scanning source directory for leaked secrets...");let{result:Re}=await ya({sourceDir:D,failOnSeverity:t?.securityFailOn||"critical"});ba(Re),L=Re}else a(`Security scan skipped (--skip-security-scan)
|
|
410
410
|
`);let z=cl(m,void 0,L);if(!z.allowed){r(`
|
|
411
411
|
✗ Static deployment blocked by security policy: ${z.explanation}`),e("Remediate the findings or create an authorized, time-limited waiver in the Security posture center."),process.exitCode=1;return}n(`✓ Security policy allows this deployment
|
|
412
|
-
`);let Y=t?.bucket,X=t?.distribution,ne=t?.prefix,re=t?.delete||!1,ie=t?.cacheControl||"public, max-age=31536000",ae=t?.invalidate!==!1,be=t?.wait||!1;if(!Y){r("--bucket is required");return}if(!
|
|
412
|
+
`);let Y=t?.bucket,X=t?.distribution,ne=t?.prefix,re=t?.delete||!1,ie=t?.cacheControl||"public, max-age=31536000",ae=t?.invalidate!==!1,be=t?.wait||!1;if(!Y){r("--bucket is required");return}if(!Rn(D)){r(`Source directory not found: ${D}`);return}if(e(`Source: ${D}`),e(`Bucket: s3://${Y}${ne?`/${ne}`:""}`),e(`Cache-Control: ${ie}`),X)e(`CloudFront Distribution: ${X}`);if(re)a("Delete mode enabled - files not in source will be removed");if(!await d(`
|
|
413
413
|
Deploy static site now?`,!0)){e("Deployment cancelled");return}let le=new g(k),ke=new s("Uploading files to S3...");ke.start(),await le.sync({source:D,bucket:Y,prefix:ne,delete:re,cacheControl:ie,acl:"public-read"}),ke.succeed("Files uploaded successfully!");let Se=(await le.getBucketSize(Y,ne)/1024/1024).toFixed(2);if(e(`Total size: ${Se} MB`),ae&&X){let Re=new E,Pe=new s("Invalidating CloudFront cache...");Pe.start();let Ve=await Re.invalidateAll(X);if(Pe.succeed("Invalidation created"),e(`Invalidation ID: ${Ve.Id}`),be){let Le=new s("Waiting for invalidation to complete...");Le.start(),await Re.waitForInvalidation(X,Ve.Id),Le.succeed("Invalidation completed!")}}Ee(`Static Site Deployed!
|
|
414
414
|
|
|
415
415
|
Source: ${D}
|
|
@@ -418,21 +418,21 @@ Size: ${Se} MB
|
|
|
418
418
|
${X?`Distribution: ${X}`:""}
|
|
419
419
|
|
|
420
420
|
View your site:
|
|
421
|
-
https://${Y}.s3.${k}.amazonaws.com${ne?`/${ne}`:""}/index.html`,"green")}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),R.command("deploy:container","Deploy container (ECR push + ECS service update)").option("--cluster <name>","ECS cluster name").option("--service <name>","ECS service name").option("--repository <name>","ECR repository name").option("--image <tag>","Docker image tag",{default:"latest"}).option("--dockerfile <path>","Dockerfile path",{default:"Dockerfile"}).option("--context <path>","Docker build context",{default:"."}).option("--task-definition <name>","Task definition family name").option("--force","Force new deployment even if no changes").option("--wait","Wait for deployment to stabilize").option("--skip-security-scan","Skip pre-deployment security scan").option("--security-fail-on <severity>","Security scan fail threshold (critical, high, medium, low)",{default:"critical"}).action(async(t)=>{i("Deploying Container");try{let m=await Ne(),k=m.project.region||"us-east-1",D=t?.cluster,L=t?.service,z=t?.repository,Y=t?.image||"latest",X=t?.dockerfile||"Dockerfile",ne=t?.context||".",re=t?.force||!1,ie=t?.wait||!1,ae=new Date().toISOString(),be=crypto.randomUUID();if(!D||!L){r("--cluster and --service are required");return}if(!z){r("--repository is required");return}if(!
|
|
421
|
+
https://${Y}.s3.${k}.amazonaws.com${ne?`/${ne}`:""}/index.html`,"green")}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),R.command("deploy:container","Deploy container (ECR push + ECS service update)").option("--cluster <name>","ECS cluster name").option("--service <name>","ECS service name").option("--repository <name>","ECR repository name").option("--image <tag>","Docker image tag",{default:"latest"}).option("--dockerfile <path>","Dockerfile path",{default:"Dockerfile"}).option("--context <path>","Docker build context",{default:"."}).option("--task-definition <name>","Task definition family name").option("--force","Force new deployment even if no changes").option("--wait","Wait for deployment to stabilize").option("--skip-security-scan","Skip pre-deployment security scan").option("--security-fail-on <severity>","Security scan fail threshold (critical, high, medium, low)",{default:"critical"}).action(async(t)=>{i("Deploying Container");try{let m=await Ne(),k=m.project.region||"us-east-1",D=t?.cluster,L=t?.service,z=t?.repository,Y=t?.image||"latest",X=t?.dockerfile||"Dockerfile",ne=t?.context||".",re=t?.force||!1,ie=t?.wait||!1,ae=new Date().toISOString(),be=crypto.randomUUID();if(!D||!L){r("--cluster and --service are required");return}if(!z){r("--repository is required");return}if(!Rn(X)){r(`Dockerfile not found: ${X}`);return}let Ae;if(!t?.skipSecurityScan){u("Scanning build context for leaked secrets...");let{result:Gt}=await ya({sourceDir:ne,failOnSeverity:t?.securityFailOn||"critical"});ba(Gt),Ae=Gt}else a(`Security scan skipped (--skip-security-scan)
|
|
422
422
|
`);let le=cl(m,void 0,Ae);if(!le.allowed){r(`
|
|
423
423
|
✗ Container deployment blocked by security policy: ${le.explanation}`),e("Remediate the findings or create an authorized, time-limited waiver in the Security posture center."),process.exitCode=1;return}if(n(`✓ Security policy allows this deployment
|
|
424
424
|
`),e(`Cluster: ${D}`),e(`Service: ${L}`),e(`Repository: ${z}`),e(`Image Tag: ${Y}`),e(`Dockerfile: ${X}`),!await d(`
|
|
425
|
-
Deploy container now?`,!0)){e("Deployment cancelled");return}let De=new Ct(k),Se=new Lt(k),Re=new s("Getting ECR credentials...");Re.start();let Pe=await De.getAuthorizationToken();if(!Pe.authorizationData?.[0]){Re.fail("Failed to get ECR credentials");return}let Ve=Pe.authorizationData[0],Fe=(Ve.proxyEndpoint||"").replace("https://","");Re.succeed("ECR credentials obtained");let He=new s("Logging into ECR...");He.start();let wt=Ve.authorizationToken||"",it=Buffer.from(wt,"base64").toString("utf8").split(":")[1],{spawn:xt}=await import("child_process"),
|
|
425
|
+
Deploy container now?`,!0)){e("Deployment cancelled");return}let De=new Ct(k),Se=new Lt(k),Re=new s("Getting ECR credentials...");Re.start();let Pe=await De.getAuthorizationToken();if(!Pe.authorizationData?.[0]){Re.fail("Failed to get ECR credentials");return}let Ve=Pe.authorizationData[0],Fe=(Ve.proxyEndpoint||"").replace("https://","");Re.succeed("ECR credentials obtained");let He=new s("Logging into ECR...");He.start();let wt=Ve.authorizationToken||"",it=Buffer.from(wt,"base64").toString("utf8").split(":")[1],{spawn:xt}=await import("child_process"),Zt=xt("docker",["login","--username","AWS","--password-stdin",Fe],{stdio:["pipe","pipe","pipe"]});Zt.stdin.write(it),Zt.stdin.end(),await new Promise((Gt,In)=>{Zt.on("close",(Pn)=>{if(Pn===0)Gt();else In(Error(`Docker login failed with code ${Pn}`))})}),He.succeed("Logged into ECR");let Nt=new s("Building Docker image...");Nt.start();let Ye=`${Fe}/${z}:${Y}`,_t=xt("docker",["build","-t",Ye,"-f",X,ne],{stdio:["pipe","pipe","pipe"]});await new Promise((Gt,In)=>{let Pn="";_t.stderr.on("data",(Kt)=>{Pn+=Kt.toString()}),_t.on("close",(Kt)=>{if(Kt===0)Gt();else In(Error(`Docker build failed: ${Pn}`))})}),Nt.succeed("Docker image built");let Wt=new s("Pushing image to ECR...");Wt.start();let at=xt("docker",["push",Ye],{stdio:["pipe","pipe","pipe"]});await new Promise((Gt,In)=>{let Pn="";at.stderr.on("data",(Kt)=>{Pn+=Kt.toString()}),at.on("close",(Kt)=>{if(Kt===0)Gt();else In(Error(`Docker push failed: ${Pn}`))})}),Wt.succeed("Image pushed to ECR");let Et,gn=xt("docker",["image","inspect","--format={{.Id}}",Ye],{stdio:["ignore","pipe","pipe"]}),[yn,tr]=await Promise.all([new Promise((Gt)=>gn.on("close",Gt)),new Response(gn.stdout).text()]);if(yn===0){let Gt=tr.trim();if(/^sha256:[a-f0-9]{64}$/i.test(Gt))Et=Gt}let Mi=await rg(m,{imageRef:Ye,imageSha256:Et,artifactRoot:ne,invocationId:be,startedAt:ae});if(!Mi.allowed){r(`
|
|
426
426
|
✗ ECS service update blocked by release security policy: ${Mi.explanation}`),e("The image remains in ECR for investigation. Remediate and rebuild it, or create an authorized, time-limited waiver."),process.exitCode=1;return}n(`✓ Release security policy allows the ECS service update
|
|
427
|
-
`);let
|
|
427
|
+
`);let Sn=new s("Updating ECS service...");if(Sn.start(),await Se.updateService({cluster:D,service:L,forceNewDeployment:re}),Sn.succeed("ECS service updated"),ie){let Gt=new s("Waiting for deployment to stabilize...");Gt.start(),await Se.waitForServiceStable(D,L),Gt.succeed("Deployment stabilized")}n(`
|
|
428
428
|
Container deployment complete!`),e(`
|
|
429
|
-
Image: ${Ye}`),e(`Cluster: ${D}`),e(`Service: ${L}`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}})}async function og(R){let t=process.cwd(),m=`${t}/.env`,k=`${t}/.env.local`,z=({stage:["stage","staging"],staging:["staging","stage"]}[R]||[R]).map((ie)=>`${t}/.env.${ie}`).find((ie)=>
|
|
430
|
-
`)){let Ae=be.trim();if(!Ae||Ae.startsWith("#"))continue;let le=Ae.indexOf("=");if(le===-1)continue;let ke=Ae.slice(0,le).trim();delete process.env[ke]}let{unlinkSync:ae}=await import("node:fs");ae(k),e("Temporarily moved .env.local out of the way and purged its values from process.env")}if(
|
|
431
|
-
`)){let ae=ie.trim();if(!ae||ae.startsWith("#"))continue;let be=ae.indexOf("=");if(be===-1)continue;let Ae=ae.slice(0,be).trim(),le=ae.slice(be+1).trim();process.env[Ae]=le}return n(`Loaded ${Y}`),async()=>{let{unlinkSync:ie}=await import("node:fs");if(ne)Ds(ne,`${t}/.env`),ie(ne),e("Restored .env");if(X)Ds(X,`${t}/.env.local`),ie(X),e("Restored .env.local")}}async function sg(R,t,m,k){if(!m)return;let D=us(R.sites||{});if(D.size===0)return;if(!t){a("DNS: no app server IP resolved — skipping A-record reconciliation.");return}let L;try{L=
|
|
432
|
-
For Cloudflare: CLOUDFLARE_API_TOKEN`),e("For Porkbun: PORKBUN_API_KEY, PORKBUN_SECRET_KEY"),e("For GoDaddy: GODADDY_API_KEY, GODADDY_API_SECRET"),re=!0,null;if(ae.provider==="route53"){let be=R.infrastructure?.dns?.hostedZoneId;if(be&&!ae.hostedZoneId)ae.hostedZoneId=be}return ne={config:ae,provider:U(ae)},ne};for(let ae of X){let be=Y[ae];if(!be){r(`Site '${ae}' not found in configuration`);continue}if(!Wc(be)){let Fe=w(be);e(`Site '${ae}' is ${Fe} — handled outside the static bucket pipeline.`);continue}let Ae=ie();if(!Ae)return;let le=be.domain;if(!le){r(`Site '${ae}' has no domain configured`);continue}if(i(`Deploying Site: ${ae}`),e(`Domain: ${le}`),e(`Source: ${be.root}`),e(`DNS Provider: ${m}`),be.build){u(`Running build command: ${be.build}`);try{let{execSync:Fe}=await import("node:child_process");Fe(be.build,{stdio:"inherit",cwd:process.cwd()}),n("Build completed successfully")}catch(Fe){r(`Build failed: ${Fe.message}`);continue}}if(!
|
|
429
|
+
Image: ${Ye}`),e(`Cluster: ${D}`),e(`Service: ${L}`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}})}async function og(R){let t=process.cwd(),m=`${t}/.env`,k=`${t}/.env.local`,z=({stage:["stage","staging"],staging:["staging","stage"]}[R]||[R]).map((ie)=>`${t}/.env.${ie}`).find((ie)=>Rn(ie));if(!z){if(Rn(m))a(`No .env.${R} found, falling back to .env`);else e(`No .env.${R} or .env file found; using the existing process environment`);return null}let Y=z.split("/").pop();u(`Loading environment file: ${Y}`);let X=null,ne=null;if(Rn(k)){X=`${k}.bak`,Ds(k,X);let ie=ll(k,"utf-8");for(let be of ie.split(`
|
|
430
|
+
`)){let Ae=be.trim();if(!Ae||Ae.startsWith("#"))continue;let le=Ae.indexOf("=");if(le===-1)continue;let ke=Ae.slice(0,le).trim();delete process.env[ke]}let{unlinkSync:ae}=await import("node:fs");ae(k),e("Temporarily moved .env.local out of the way and purged its values from process.env")}if(Rn(m))ne=`${m}.bak`,Ds(m,ne);Ds(z,m);let re=ll(z,"utf-8");for(let ie of re.split(`
|
|
431
|
+
`)){let ae=ie.trim();if(!ae||ae.startsWith("#"))continue;let be=ae.indexOf("=");if(be===-1)continue;let Ae=ae.slice(0,be).trim(),le=ae.slice(be+1).trim();process.env[Ae]=le}return n(`Loaded ${Y}`),async()=>{let{unlinkSync:ie}=await import("node:fs");if(ne)Ds(ne,`${t}/.env`),ie(ne),e("Restored .env");if(X)Ds(X,`${t}/.env.local`),ie(X),e("Restored .env.local")}}async function sg(R,t,m,k){if(!m)return;let D=us(R.sites||{});if(D.size===0)return;if(!t){a("DNS: no app server IP resolved — skipping A-record reconciliation.");return}let L;try{L=bn(m)}catch(re){a(`DNS: ${re instanceof Error?re.message:String(re)}`),e(`Point each site domain at ${t} manually.`);return}if(!L){a(`DNS provider '${m}' is not configured — skipping A-record reconciliation.`),e(`Point each site domain at ${t} manually.`);return}if(L.provider==="route53"){let re=R.infrastructure?.dns?.hostedZoneId;if(re&&!L.hostedZoneId)L.hostedZoneId=re}let z=U(L),Y=R.infrastructure?.dns?.domain,X=me(k),ne=X?"A/AAAA":"A";u(`Reconciling DNS: ${D.size} ${ne} record(s) → ${t} via ${m}...`);for(let re of D){let ie=Y&&re.endsWith(Y)?Y:re.split(".").slice(-2).join(".");try{let ae=await mi({provider:z,zone:ie,fqdn:re,ipv4:t,ipv6:X});for(let be of ae.published)n(` ✓ ${be.fqdn} ${be.type} → ${be.content}`);for(let be of ae.warnings)a(` ⚠ ${re}: ${be}`)}catch(ae){a(` ⚠ ${re}: ${ae instanceof Error?ae.message:String(ae)} — set the address record manually.`)}}}async function ag(R,t){let m=R.infrastructure?.dns?.records;if(!t||!m?.length)return;let k=R.infrastructure?.dns?.domain;if(!k){a("DNS records: infrastructure.dns.domain is not set — skipping declared records.");return}let D;try{D=bn(t)}catch(Y){a(`DNS records: ${Y instanceof Error?Y.message:String(Y)}`);return}if(!D){a(`DNS records: provider '${t}' is not configured — skipping.`);return}let L=U(D),z=Ao(m,k);u(`Reconciling ${z.length} declared DNS record(s) on ${k}...`);try{let Y=await Ro({provider:L,zone:k,records:z});for(let X of Y.outcomes){let{record:ne,action:re}=X,ie=`${ne.type} ${ne.name} -> ${ne.content.slice(0,60)}`;if(re==="failed")a(` ⚠ ${ie}: ${X.message||"failed"}`);else if(re==="unchanged")e(` = ${ie}`);else n(` ✓ ${ie} (${re})`)}for(let X of Y.warnings)a(` ⚠ ${X}`)}catch(Y){a(`DNS records: ${Y instanceof Error?Y.message:String(Y)}`)}}async function cg(R,t,m){let{plan:k,errors:D}=Qc(R);for(let z of D)a(`Cloudflare CDN: ${z}`);if(!k)return;if(!t){a("Cloudflare CDN: no app server IP resolved — skipping.");return}u(`Cloudflare CDN: reconciling ${k.hosts.length} host(s) on ${k.zone}...`);let L=new he(k.apiToken,{zoneId:k.zoneId,accountId:k.accountId});try{let z=await Zc({provider:L,zone:k.zone,hosts:k.hosts,ipv4:t,ipv6:me(m),proxied:k.proxied,settings:k.settings,cache:k.cache,originGuard:k.originGuard,purge:k.purge,skipOriginProbe:k.skipOriginProbe});for(let Y of z.records)n(` ✓ ${Y.host} ${Y.type} → ${Y.content}${Y.proxied?" (proxied)":" (DNS-only)"}`);for(let Y of z.settingsChanged)e(` ✓ ${Y.id}: ${JSON.stringify(Y.from)} → ${JSON.stringify(Y.to)}`);if(z.cacheRules>0)n(` ✓ ${z.cacheRules} cache rule(s) applied`);if(z.originGuard)n(" ✓ origin guard header applied");if(z.purged)n(" ✓ edge cache purged");for(let Y of z.warnings)a(` ⚠ ${Y}`)}catch(z){a(`Cloudflare CDN: ${z instanceof Error?z.message:String(z)}`)}}async function ou(R,t={}){let m=Object.keys(R.infrastructure?.r2?.buckets??{}).length;if(m===0)return!0;u(`Cloudflare R2: ${t.dryRun?"planning":"reconciling"} ${m} bucket(s)...`);try{let k=await tl(R,{apiToken:process.env.CLOUDFLARE_API_TOKEN,accountId:process.env.CLOUDFLARE_ACCOUNT_ID,zoneId:process.env.CLOUDFLARE_ZONE_ID,dryRun:t.dryRun});for(let D of k.buckets){if(D.changes.length===0)e(` = ${D.name} (in sync)`);for(let L of D.changes)n(` ✓ ${D.name}: ${L}`);for(let L of D.domains)e(` ${L.domain}: ${L.status??"unknown"}`)}for(let D of k.warnings)a(` ⚠ ${D}`);return!0}catch(k){return r(`Cloudflare R2: ${k instanceof Error?k.message:String(k)}`),!1}}async function su(R,t={}){let m=Object.keys(R.infrastructure?.workers??{}).length;if(m===0)return!0;u(`Cloudflare Workers: ${t.dryRun?"planning":"reconciling"} ${m} Worker(s)...`);try{let k=await sl(R,{apiToken:process.env.CLOUDFLARE_API_TOKEN,accountId:process.env.CLOUDFLARE_ACCOUNT_ID,zoneId:process.env.CLOUDFLARE_ZONE_ID,projectRoot:process.cwd(),dryRun:t.dryRun});for(let D of k.workers){for(let L of D.changes)n(` ✓ ${D.name}: ${L}`);for(let L of D.domains)e(` ${L.domain}: ${L.status??"unknown"}`)}for(let D of k.warnings)a(` ⚠ ${D}`);return!0}catch(k){return r(`Cloudflare Workers: ${k instanceof Error?k.message:String(k)}`),!1}}async function lg(R,t,m,k,D,L,z=!1){let Y=R.sites||{},X=t?[t]:Object.keys(Y);if(X.length===0){a("No sites configured in cloud.config.ts");return}let ne=null,re=!1,ie=()=>{if(ne||re)return ne;let ae=null;try{ae=bn(m)}catch(be){r(be.message)}if(!ae)return r(`DNS provider '${m}' is not configured. Please set the required environment variables.`),e(`
|
|
432
|
+
For Cloudflare: CLOUDFLARE_API_TOKEN`),e("For Porkbun: PORKBUN_API_KEY, PORKBUN_SECRET_KEY"),e("For GoDaddy: GODADDY_API_KEY, GODADDY_API_SECRET"),re=!0,null;if(ae.provider==="route53"){let be=R.infrastructure?.dns?.hostedZoneId;if(be&&!ae.hostedZoneId)ae.hostedZoneId=be}return ne={config:ae,provider:U(ae)},ne};for(let ae of X){let be=Y[ae];if(!be){r(`Site '${ae}' not found in configuration`);continue}if(!Wc(be)){let Fe=w(be);e(`Site '${ae}' is ${Fe} — handled outside the static bucket pipeline.`);continue}let Ae=ie();if(!Ae)return;let le=be.domain;if(!le){r(`Site '${ae}' has no domain configured`);continue}if(i(`Deploying Site: ${ae}`),e(`Domain: ${le}`),e(`Source: ${be.root}`),e(`DNS Provider: ${m}`),be.build){u(`Running build command: ${be.build}`);try{let{execSync:Fe}=await import("node:child_process");Fe(be.build,{stdio:"inherit",cwd:process.cwd()}),n("Build completed successfully")}catch(Fe){r(`Build failed: ${Fe.message}`);continue}}if(!Rn(be.root)){if(r(`Source directory not found: ${be.root}`),!be.build)e('Run your build command first (e.g., bun run generate) or add a "build" option to your site config');continue}u("Checking existing DNS records...");let ke=await Ae.provider.listRecords(le);if(ke.success&&ke.records.length>0){let Fe=le.split("."),He=Fe.length>2?Fe[0]:"@",wt=Fe.slice(-2).join("."),Dt=ke.records.find((it)=>it.type==="CNAME"&&(it.name===le||it.name===He||it.name===`${He}.${wt}`));if(Dt){let it=Dt.content.includes("netlify"),xt=Dt.content.includes("vercel");if(Dt.content.includes("cloudfront.net"))e(`Domain already points to CloudFront: ${Dt.content}`),e("Proceeding with file upload...");else{let Nt=it?"Netlify":xt?"Vercel":"another provider";if(a(`
|
|
433
433
|
Existing CNAME record detected:`),e(` ${Dt.name} -> ${Dt.content}`),it||xt)e(`
|
|
434
434
|
This domain is currently pointing to ${Nt}.`),e("Deploying will update this record to point to AWS CloudFront.");if(!(z||await d(`
|
|
435
|
-
Update DNS record to point to AWS CloudFront?`,!0))){e("Deployment cancelled");continue}u(`Removing old ${Nt} CNAME record...`);let _t=await Ae.provider.deleteRecord(le,{name:Dt.name,type:"CNAME",content:Dt.content});if(_t.success)n(`Removed CNAME: ${Dt.name} -> ${Dt.content}`);else a(`Could not remove old record: ${_t.message}`),e("The deployment will attempt to update it instead.")}}}let De=be.root,Se,Re=!!be.installScript;if(Re){let{mkdtempSync:Fe,copyFileSync:He,readdirSync:wt,statSync:Dt}=await import("node:fs"),{tmpdir:it}=await import("node:os"),{join:xt,resolve:
|
|
435
|
+
Update DNS record to point to AWS CloudFront?`,!0))){e("Deployment cancelled");continue}u(`Removing old ${Nt} CNAME record...`);let _t=await Ae.provider.deleteRecord(le,{name:Dt.name,type:"CNAME",content:Dt.content});if(_t.success)n(`Removed CNAME: ${Dt.name} -> ${Dt.content}`);else a(`Could not remove old record: ${_t.message}`),e("The deployment will attempt to update it instead.")}}}let De=be.root,Se,Re=!!be.installScript;if(Re){let{mkdtempSync:Fe,copyFileSync:He,readdirSync:wt,statSync:Dt}=await import("node:fs"),{tmpdir:it}=await import("node:os"),{join:xt,resolve:Zt}=await import("node:path");Se=Fe(xt(it(),"cloud-install-"));let Nt=Zt(be.installScript);He(Nt,xt(Se,"index.html"));let Ye=Nt.split("/").pop();if(Ye!=="index.html")He(Nt,xt(Se,Ye));if(Rn(be.root)){let _t=wt(be.root);for(let Wt of _t){let at=xt(be.root,Wt);if(Dt(at).isFile()&&Wt!=="index.html")He(at,xt(Se,Wt))}}De=Se,e(`Install script: ${be.installScript}`),e(`Serving at: curl -fsSL https://${le} | bash`)}u("Deploying to AWS (S3 + CloudFront)...");let Pe=ga(R,ae,be,L),Ve=fa(R,ae);e(`Stack: ${Pe}`);let Le=await Xi({siteName:Ve,stackName:Pe,domain:le,region:k,bucket:ha(R.project.slug,L,ae,be.bucket),sourceDir:De,certificateArn:be.certificateArn,dnsProvider:Ae.config,skipDnsVerification:D,passthroughUrls:Re,dynamicApp:!!R.infrastructure?.compute?.cloudFrontOriginDomain,computeOriginDomain:R.infrastructure?.compute?.cloudFrontOriginDomain,computeOriginPort:R.infrastructure?.compute?.cloudFrontOriginPort??R.infrastructure?.api?.port??3008,computeOriginId:R.infrastructure?.compute?.cloudFrontOriginId??`${R.project.slug}-site-ec2`,onProgress:(Fe,He)=>{if(Fe==="infrastructure")u(He||"Setting up infrastructure...");else if(Fe==="upload"){if(He?.includes("/")&&!He.includes("1/")){let wt=He.match(/(\d+)\/(\d+)/);if(wt){let[,Dt,it]=wt;if(Number(Dt)%10===0||Dt===it)e(` Uploaded ${Dt}/${it} files`)}}}else if(Fe==="invalidate")u("Invalidating CDN cache...")}});if(Se){let{rmSync:Fe}=await import("node:fs");Fe(Se,{recursive:!0,force:!0})}if(Le.success){n(`
|
|
436
436
|
Deployment successful!`);let Fe=Le.filesSkipped>0?`${Le.filesUploaded} uploaded, ${Le.filesSkipped} unchanged`:`${Le.filesUploaded}`,He=Re?`
|
|
437
437
|
Install: curl -fsSL https://${Le.domain} | bash`:"";Ee(`Site Deployed!
|
|
438
438
|
|
|
@@ -447,7 +447,7 @@ Your site is now live at https://${Le.domain}`,"green")}else throw Error(Le.mess
|
|
|
447
447
|
${k}
|
|
448
448
|
`+"A site moved off this box keeps its files here on purpose — they are the rollback, and they are what makes "+`the move reversible until this server is destroyed. Destroying it now discards that.
|
|
449
449
|
Verify the site is serving from its new home first, then re-run with ${m} to destroy it anyway.`}var Tg="--discard-drained-sites";async function Ng(R,t,m,k){if(k)return!0;let D=t.project.slug,L;try{let Y=await R.findComputeTargets({slug:D,environment:m,role:"app",stackName:T(t,m)});if(Y.length===0)return!0;let X=await R.runRemoteDeploy({targets:Y,commands:vu(D),comment:`ts-cloud scan drained sites ${D}`,tags:{Project:D,Environment:m,Role:"app"}});if(!X.success)return a(`Could not check the server for moved-off site files: ${X.error||"unknown error"}`),!0;L=X.perInstance.map((ne)=>ne.output??"").join(`
|
|
450
|
-
`)}catch(Y){return a(`Could not check the server for moved-off site files: ${Y instanceof Error?Y.message:String(Y)}`),!0}let z=bu(L);if(z.length===0)return!0;return r(wu(z,D,Tg)),!1}function gl(R){R.command("apply","Apply configured server resize, persistent volumes and deployment storage").option("--env <env>","Environment",{default:"production"}).option("--dry-run","Show the provider plan without changing anything").option("--yes","Apply the displayed compute plan without prompting").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",D=O({config:m});if(!D.applyComputeChanges)throw Error("This provider does not support configured compute changes");let L=await D.applyComputeChanges({config:m,environment:k,dryRun:!0});if(console.log(JSON.stringify(L,null,2)),t?.dryRun)return;if(!t?.yes&&!await d("Apply these compute changes?",!1))return;let z=await D.applyComputeChanges({config:m,environment:k});if(z.waitingForCapacity)a(z.waitingForCapacity);n("Configured compute changes applied")}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}}),R.command("destroy","Destroy the single-server compute (instance + firewall)").option("--env <env>","Environment",{default:"production"}).option("--force","Skip the confirmation prompt").option("--discard-drained-sites","Destroy even though a moved site left its rollback files here").action(async(t)=>{i("Destroy Compute");let m=await Ne(),k=t?.env||"production",D=B(m),L=O({config:m,provider:D});if(!L.destroyCompute){r(`The ${D} driver does not support compute teardown`);return}if(a(`This terminates the ${D} server for ${m.project.slug}/${k} and deletes its firewall.`),!await Ng(L,m,k,!!t?.discardDrainedSites)){process.exitCode=1;return}if(!t?.force){if(!await d("This is irreversible. Continue?",!1)){e("Cancelled");return}}let z=new s("Destroying compute...");z.start();try{let{destroyed:Y}=await L.destroyCompute({config:m,environment:k});if(z.succeed("Compute destroyed"),Y.length>0)Y.forEach((X)=>e(` removed ${X}`));else e("Nothing to destroy (no matching resources found)")}catch(Y){z.fail("Teardown failed"),r(Y instanceof Error?Y.message:String(Y))}})}import{readFileSync as Og}from"node:fs";import{homedir as Lg}from"node:os";import Fg from"node:process";var Su=(R)=>R.replace(/^~(?=$|\/)/,Lg());function hl(R){R.command("boxes:apply [name]","Converge declared boxes: create, resize when asked, install packages and start services").option("--dry-run","Show what would change without touching any box").option("--yes","Apply without prompting").action(async(t,m)=>{try{let k=await Ne(),D=Object.entries(k.infrastructure?.boxes??{}),L=t?D.filter(([ne,re])=>ne===t||re.name===t):D;if(L.length===0)throw Error(t?`No box named ${t} in infrastructure.boxes`:"No boxes declared in infrastructure.boxes");let z=gt(k);if(!z.apiToken)throw Error("Hetzner API token required: hetzner.apiToken, HCLOUD_TOKEN or HETZNER_API_TOKEN");let Y=new Oe({apiToken:z.apiToken}),X={sshPublicKey:Og(Su(z.sshPublicKeyPath),"utf8").trim(),identityFile:Su(z.sshPrivateKeyPath),knownHostsFile:
|
|
450
|
+
`)}catch(Y){return a(`Could not check the server for moved-off site files: ${Y instanceof Error?Y.message:String(Y)}`),!0}let z=bu(L);if(z.length===0)return!0;return r(wu(z,D,Tg)),!1}function gl(R){R.command("apply","Apply configured server resize, persistent volumes and deployment storage").option("--env <env>","Environment",{default:"production"}).option("--dry-run","Show the provider plan without changing anything").option("--yes","Apply the displayed compute plan without prompting").action(async(t)=>{try{let m=await Ne(),k=t?.env||"production",D=O({config:m});if(!D.applyComputeChanges)throw Error("This provider does not support configured compute changes");let L=await D.applyComputeChanges({config:m,environment:k,dryRun:!0});if(console.log(JSON.stringify(L,null,2)),t?.dryRun)return;if(!t?.yes&&!await d("Apply these compute changes?",!1))return;let z=await D.applyComputeChanges({config:m,environment:k});if(z.waitingForCapacity)a(z.waitingForCapacity);n("Configured compute changes applied")}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}}),R.command("destroy","Destroy the single-server compute (instance + firewall)").option("--env <env>","Environment",{default:"production"}).option("--force","Skip the confirmation prompt").option("--discard-drained-sites","Destroy even though a moved site left its rollback files here").action(async(t)=>{i("Destroy Compute");let m=await Ne(),k=t?.env||"production",D=B(m),L=O({config:m,provider:D});if(!L.destroyCompute){r(`The ${D} driver does not support compute teardown`);return}if(a(`This terminates the ${D} server for ${m.project.slug}/${k} and deletes its firewall.`),!await Ng(L,m,k,!!t?.discardDrainedSites)){process.exitCode=1;return}if(!t?.force){if(!await d("This is irreversible. Continue?",!1)){e("Cancelled");return}}let z=new s("Destroying compute...");z.start();try{let{destroyed:Y}=await L.destroyCompute({config:m,environment:k});if(z.succeed("Compute destroyed"),Y.length>0)Y.forEach((X)=>e(` removed ${X}`));else e("Nothing to destroy (no matching resources found)")}catch(Y){z.fail("Teardown failed"),r(Y instanceof Error?Y.message:String(Y))}})}import{readFileSync as Og}from"node:fs";import{homedir as Lg}from"node:os";import Fg from"node:process";var Su=(R)=>R.replace(/^~(?=$|\/)/,Lg());function hl(R){R.command("boxes:apply [name]","Converge declared boxes: create, resize when asked, install packages and start services").option("--dry-run","Show what would change without touching any box").option("--yes","Apply without prompting").action(async(t,m)=>{try{let k=await Ne(),D=Object.entries(k.infrastructure?.boxes??{}),L=t?D.filter(([ne,re])=>ne===t||re.name===t):D;if(L.length===0)throw Error(t?`No box named ${t} in infrastructure.boxes`:"No boxes declared in infrastructure.boxes");let z=gt(k);if(!z.apiToken)throw Error("Hetzner API token required: hetzner.apiToken, HCLOUD_TOKEN or HETZNER_API_TOKEN");let Y=new Oe({apiToken:z.apiToken}),X={sshPublicKey:Og(Su(z.sshPublicKeyPath),"utf8").trim(),identityFile:Su(z.sshPrivateKeyPath),knownHostsFile:hi()};for(let[ne,re]of L){let ie=await lo({box:re,client:Y,...X,dryRun:!0});e(`${ne} (${re.name}):`);for(let ae of ie.report)e(` ${ae}`)}if(m?.dryRun)return;if(!m?.yes&&!await d("Apply these box changes?",!1))return;for(let[ne,re]of L){let ie=await lo({box:re,client:Y,...X,onPhase:(ae)=>e(` ${ne}: ${ae}`)});for(let ae of ie.report)e(` ${ne}: ${ae}`);n(`${ne} converged (${ie.box.publicIp})`)}}catch(k){r(k instanceof Error?k.message:String(k)),Fg.exitCode=1}})}import{statSync as jg,writeFileSync as Bg}from"node:fs";function yl(R){R.command("stack:list","List all CloudFormation stacks").action(async()=>{i("CloudFormation Stacks");try{let m=(await Ne()).project.region||"us-east-1",k=new v(m),D=new s("Loading stacks...");D.start();let L=await k.listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE","ROLLBACK_COMPLETE","UPDATE_ROLLBACK_COMPLETE","CREATE_IN_PROGRESS","UPDATE_IN_PROGRESS"]);if(D.succeed(`Found ${L.StackSummaries.length} stacks`),L.StackSummaries.length===0){e("No stacks found");return}let z=["Stack Name","Status","Created","Updated"],Y=L.StackSummaries.map((X)=>[X.StackName,X.StackStatus,new Date(X.CreationTime).toLocaleString(),X.LastUpdatedTime?new Date(X.LastUpdatedTime).toLocaleString():"Never"]);c(z,Y)}catch(t){r(`Failed to list stacks: ${t.message}`)}}),R.command("stack:describe STACK_NAME","Describe a CloudFormation stack").action(async(t)=>{i(`Stack: ${t}`);try{let k=(await Ne()).project.region||"us-east-1",D=new v(k),L=new s("Loading stack details...");L.start();let z=await D.describeStacks({stackName:t});if(!z.Stacks||z.Stacks.length===0){L.fail("Stack not found");return}let Y=z.Stacks[0];if(L.succeed("Stack details loaded"),e(`
|
|
451
451
|
Stack Information:`),e(` - Name: ${Y.StackName}`),e(` - Status: ${Y.StackStatus}`),e(` - Created: ${new Date(Y.CreationTime).toLocaleString()}`),Y.LastUpdatedTime)e(` - Updated: ${new Date(Y.LastUpdatedTime).toLocaleString()}`);if(Y.Parameters&&Y.Parameters.length>0){e(`
|
|
452
452
|
Parameters:`);for(let ne of Y.Parameters)e(` - ${ne.ParameterKey}: ${ne.ParameterValue}`)}if(Y.Outputs&&Y.Outputs.length>0){e(`
|
|
453
453
|
Outputs:`);for(let ne of Y.Outputs)if(e(` - ${ne.OutputKey}: ${ne.OutputValue}`),ne.Description)e(` ${ne.Description}`)}if(Y.Tags&&Y.Tags.length>0){e(`
|
|
@@ -457,12 +457,12 @@ Resources (${X.StackResourceSummaries.length}):`);let ne=["Logical ID","Type","S
|
|
|
457
457
|
... and ${X.StackResourceSummaries.length-10} more resources`)}}catch(m){r(`Failed to describe stack: ${m.message}`)}}),R.command("stack:delete STACK_NAME","Delete a CloudFormation stack").option("--yes","Skip the confirmation prompt").action(async(t,m)=>{i(`Delete Stack: ${t}`);try{let D=(await Ne()).project.region||"us-east-1";if(a("This will permanently delete the stack and all its resources!"),!(m?.yes||await d(`
|
|
458
458
|
Are you sure you want to delete this stack?`,!1))){e("Deletion cancelled");return}let z=new v(D),Y=new s("Deleting stack...");Y.start(),await z.deleteStack(t),Y.succeed("Stack deletion initiated"),u("Waiting for stack deletion..."),await z.waitForStack(t,"stack-delete-complete"),n("Stack deleted successfully!")}catch(k){r(`Failed to delete stack: ${k.message}`)}}),R.command("stack:events STACK_NAME","Show stack events").option("--limit <number>","Limit number of events",{default:"20"}).action(async(t,m)=>{i(`Stack Events: ${t}`);try{let D=(await Ne()).project.region||"us-east-1",L=new v(D),z=new s("Loading events...");z.start();let Y=await L.describeStackEvents(t);z.succeed(`Found ${Y.StackEvents.length} events`);let X=m?.limit?Number.parseInt(m.limit):20,ne=Y.StackEvents.slice(0,X);if(ne.length===0){e("No events found");return}let re=["Time","Resource","Status","Reason"],ie=ne.map((ae)=>[new Date(ae.Timestamp).toLocaleString(),ae.LogicalResourceId,ae.ResourceStatus,ae.ResourceStatusReason||""]);c(re,ie)}catch(k){r(`Failed to load events: ${k.message}`)}}),R.command("stack:outputs STACK_NAME","Show stack outputs").action(async(t)=>{i(`Stack Outputs: ${t}`);try{let k=(await Ne()).project.region||"us-east-1",D=new v(k),L=new s("Loading stack outputs...");L.start();let z=await D.describeStacks({stackName:t});if(!z.Stacks||z.Stacks.length===0){L.fail("Stack not found");return}let Y=z.Stacks[0];if(L.succeed("Stack outputs loaded"),!Y.Outputs||Y.Outputs.length===0){e("No outputs found for this stack");return}let X=["Key","Value","Description","Export Name"],ne=Y.Outputs.map((re)=>[re.OutputKey||"",re.OutputValue||"",re.Description||"",re.ExportName||""]);c(X,ne),e(`
|
|
459
459
|
Copy-friendly format:`);for(let re of Y.Outputs)e(`${re.OutputKey}=${re.OutputValue}`)}catch(m){r(`Failed to load outputs: ${m.message}`)}}),R.command("stack:export STACK_NAME","Export stack template").option("--output <file>","Output file path").option("--format <format>","Output format (json or yaml)",{default:"json"}).action(async(t,m)=>{i(`Export Stack: ${t}`);try{let D=(await Ne()).project.region||"us-east-1",L=new v(D),z=new s("Fetching stack template...");z.start();let Y=await L.getTemplate(t);if(!Y.TemplateBody){z.fail("Template not found");return}z.succeed("Template fetched");let X=m?.format||"json",ne=Y.TemplateBody;if(X==="json"){let re=JSON.parse(ne);ne=JSON.stringify(re,null,2)}if(m?.output){let re=m.output;Bg(re,ne,"utf-8"),n(`Template exported to: ${re}`);let ae=(jg(re).size/1024).toFixed(2);e(`File size: ${ae} KB`)}else e(`
|
|
460
|
-
Template:`),console.log(ne)}catch(k){r(`Failed to export template: ${k.message}`)}})}import{existsSync as _g}from"node:fs";function vl(R,t){R.command("upgrade","Upgrade CLI to latest version").action(async()=>{
|
|
460
|
+
Template:`),console.log(ne)}catch(k){r(`Failed to export template: ${k.message}`)}})}import{existsSync as _g}from"node:fs";function vl(R,t){R.command("upgrade","Upgrade CLI to latest version").action(async()=>{En("upgrade",{message:`Self-update is disabled for this ${t} build because no signed update-manifest source is configured.`,nextAction:"Install a verified release with your package manager or configure platform maintenance updates."})}),R.command("doctor","Check system requirements and AWS credentials").action(async()=>{if(i("System Diagnostics"),u("Checking Bun..."),n(`Bun ${process.versions.bun}`),u("Checking AWS CLI..."),await zs())n("AWS CLI is installed");else r("AWS CLI is not installed"),e("Install: https://aws.amazon.com/cli/");if(u("Checking AWS credentials..."),await Hs()){n("AWS credentials are configured");let L=await Gs();if(L)e(`Account ID: ${L}`)}else r("AWS credentials are not configured"),e("Run: aws configure");u("Checking CloudFront list access...");let D=!1;try{let{CloudFrontClient:L}=await import("./chunk-ncr1dwdt.js");await new L().listDistributions(),n("CloudFront list access is enabled"),D=!0}catch(L){a(`CloudFront list check failed: ${L.message}`)}if(D){u("Checking CloudFront create access...");try{let L=new v("us-east-1"),z=JSON.stringify({AWSTemplateFormatVersion:"2010-09-09",Description:"Test CloudFront access",Resources:{TestOAC:{Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:"test-oac-validation",OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}}}});await L.validateTemplate(z),n("CloudFront create access appears enabled")}catch(L){if(L.message?.includes("403")||L.message?.includes("AccessDenied")||L.message?.includes("must be verified"))r("CloudFront create access denied - account verification required"),e(""),e("Your AWS account needs to be verified for CloudFront."),e("This is required before you can create CloudFront distributions."),e(""),e("To verify your account:"),e(" 1. Go to: https://console.aws.amazon.com/support/home#/"),e(" 2. Create a support case"),e(" 3. Select: Service limit increase > CloudFront"),e(' 4. Request: "Please verify my account for CloudFront access"'),e(""),e("Verification usually takes 1-2 business days.");else n("CloudFront create access appears enabled (validation passed)")}}u("Checking S3 access...");try{let{S3Client:L}=await import("./chunk-h5ccvped.js");await new L("us-east-1").listBuckets(),n("S3 access is enabled")}catch(L){a(`S3 check failed: ${L.message}`)}u("Checking ACM (SSL certificates) access...");try{await new W("us-east-1").listCertificates(),n("ACM access is enabled")}catch(L){a(`ACM check failed: ${L.message}`)}if(u("Checking configuration..."),_g("cloud.config.ts"))n("cloud.config.ts found");else a("cloud.config.ts not found"),e("Run: cloud init")}),R.command("regions","List available AWS regions").action(async()=>{i("AWS Regions");let m=new s("Fetching regions...");m.start();let k=await qs();m.stop(),k.forEach((D)=>{console.log(` ${D}`)})}),R.command("version","Show the version of the CLI").alias("v").action(()=>{console.log(`ts-cloud v${t}`)})}function bl(R){R.command("analytics:sites:list","List all analytics sites").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).action(async(t)=>{i("Analytics Sites");try{let m=new G(t.region),k=new s("Fetching sites...");k.start();let D=await m.scan({TableName:t.table,FilterExpression:"begins_with(pk, :pk)",ExpressionAttributeValues:{":pk":{S:"SITE#"}}});if(k.succeed(`Found ${D.Items?.length||0} site(s)`),!D.Items||D.Items.length===0){e("No analytics sites found"),e("Use `cloud analytics:sites:create` to create a new site");return}let L=D.Items.map((z)=>G.unmarshal(z));c(["ID","Name","Domains","Active","Created"],L.map((z)=>[z.siteId||"N/A",z.name||"Unnamed",(z.domains||[]).join(", ")||"-",z.isActive?"Yes":"No",z.createdAt?new Date(z.createdAt).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list sites: ${m.message}`),process.exit(1)}}),R.command("analytics:sites:create","Create a new analytics site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--name <name>","Site name").option("--domain <domain>","Site domain(s) (can be specified multiple times)").action(async(t)=>{i("Create Analytics Site");try{let m=new G(t.region),k=t.name||await I("Site name","My Site"),D=[];if(t.domain)D=Array.isArray(t.domain)?t.domain:[t.domain];else D=(await I("Site domains (comma-separated)","example.com")).split(",").map((re)=>re.trim()).filter(Boolean);if(e(`
|
|
461
461
|
Creating site: ${k}`),e(`Domains: ${D.join(", ")}`),!await d(`
|
|
462
462
|
Create this site?`,!0)){e("Operation cancelled");return}let z=new s("Creating site...");z.start();let Y=`site_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,X=new Date().toISOString();await m.putItem({TableName:t.table,Item:{pk:{S:`SITE#${Y}`},sk:{S:`SITE#${Y}`},siteId:{S:Y},name:{S:k},domains:{L:D.map((ne)=>({S:ne}))},isActive:{BOOL:!0},createdAt:{S:X},updatedAt:{S:X}}}),z.succeed("Site created successfully"),n(`
|
|
463
463
|
Site ID: ${Y}`),e(`
|
|
464
|
-
Add the tracking script to your website:`),e(` <script src="https://analytics.stacksjs.com/track.js" data-site-id="${Y}"></script>`)}catch(m){r(`Failed to create site: ${m.message}`),process.exit(1)}}),R.command("analytics:sites:update <siteId>","Update an analytics site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--name <name>","New site name").option("--domain <domain>","Set domains (replaces existing)").option("--active <boolean>","Set site active/inactive status").action(async(t,m)=>{i("Update Analytics Site");try{let k=new G(m.region),D=await k.getItem({TableName:m.table,Key:{pk:{S:`SITE#${t}`},sk:{S:`SITE#${t}`}}});if(!D.Item)r(`Site not found: ${t}`),process.exit(1);let L=G.unmarshal(D.Item);e(`Updating site: ${L.name||"Unnamed"} (${t})`),e("");let z=[],Y={},X={};if(m.name)z.push("#n = :name"),Y["#n"]="name",X[":name"]={S:m.name},e(` Name: ${L.name} -> ${m.name}`);if(m.domain!==void 0){let ne=Array.isArray(m.domain)?m.domain:[m.domain];z.push("domains = :domains"),X[":domains"]={L:ne.map((re)=>({S:re}))},e(` Domains: ${JSON.stringify(L.domains||[])} -> ${JSON.stringify(ne)}`)}if(m.active!==void 0){let ne=m.active==="true"||m.active==="1";z.push("isActive = :active"),X[":active"]={BOOL:ne},e(` Active: ${L.isActive} -> ${ne}`)}if(z.length===0){a("No updates specified. Use --name, --domain, or --active options.");return}z.push("updatedAt = :updatedAt"),X[":updatedAt"]={S:new Date().toISOString()},await k.updateItem({TableName:m.table,Key:{pk:{S:`SITE#${t}`},sk:{S:`SITE#${t}`}},UpdateExpression:`SET ${z.join(", ")}`,ExpressionAttributeNames:Object.keys(Y).length>0?Y:void 0,ExpressionAttributeValues:X}),e(""),n("Site updated successfully")}catch(k){r(`Failed to update site: ${k.message}`),process.exit(1)}}),R.command("analytics:query <siteId>","Query analytics data for a site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--limit <n>","Max items to return",{default:"10"}).option("--type <type>","Filter by type (pageview, event, etc.)").action(async(t,m)=>{i(`Analytics Data: ${t}`);try{let k=new G(m.region),D=new s("Querying data...");D.start();let L=await k.query({TableName:m.table,KeyConditionExpression:"pk = :pk",ExpressionAttributeValues:{":pk":{S:`SITE#${t}`}},ScanIndexForward:!1,Limit:parseInt(m.limit,10)});if(D.succeed(`Found ${L.Items?.length||0} items`),!L.Items||L.Items.length===0){e("No data found for this site");return}let z=L.Items.map((Y)=>G.unmarshal(Y));for(let Y of z){if(e(""),e(`SK: ${Y.sk}`),Y.path)e(` Path: ${Y.path}`);if(Y.timestamp)e(` Time: ${Y.timestamp}`);if(Y.visitorId)e(` Visitor: ${Y.visitorId}`);if(Y.browser)e(` Browser: ${Y.browser}`);if(Y.country)e(` Country: ${Y.country}`)}}catch(k){r(`Failed to query data: ${k.message}`),process.exit(1)}}),R.command("analytics:realtime <siteId>","Check realtime visitors for a site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--minutes <n>","Minutes to look back",{default:"5"}).action(async(t,m)=>{i(`Realtime: ${t}`);try{let k=new G(m.region),D=new s("Checking realtime...");D.start();let L=parseInt(m.minutes,10),z=new Date(Date.now()-L*60*1000),Y=await k.query({TableName:m.table,KeyConditionExpression:"pk = :pk AND sk BETWEEN :start AND :end",ExpressionAttributeValues:{":pk":{S:`SITE#${t}`},":start":{S:`PAGEVIEW#${z.toISOString()}`},":end":{S:"PAGEVIEW#Z"}},ScanIndexForward:!1});if(D.succeed(`Found ${Y.Items?.length||0} pageviews in last ${L} minutes`),!Y.Items||Y.Items.length===0){a("No recent pageviews found"),e(""),e("Possible issues:"),e(" 1. Tracking script not installed correctly"),e(" 2. Site ID mismatch"),e(" 3. CORS or network issues");return}let X=Y.Items.map((ie)=>G.unmarshal(ie)),ne=new Set(X.map((ie)=>ie.visitorId)).size;e(""),n(`${ne} unique visitor(s) online`),e("");let re={};for(let ie of X){let ae=ie.path||"/";re[ae]=(re[ae]||0)+1}e("Active pages:");for(let[ie,ae]of Object.entries(re).sort((be,Ae)=>Ae[1]-be[1]).slice(0,5))e(` ${ie}: ${ae} view(s)`);e(""),e("Recent pageviews:");for(let ie of X.slice(0,5))e(` ${ie.timestamp} - ${ie.path} (${ie.browser||"Unknown"})`)}catch(k){r(`Failed to check realtime: ${k.message}`),process.exit(1)}})}import{chmodSync as Mg,mkdirSync as zg,readFileSync as Ug,writeFileSync as Wg}from"node:fs";import{dirname as Vg,resolve as ka}from"node:path";function Jt(R){return new
|
|
465
|
-
`,{mode:384}),Mg(k,384),n(`Control-plane snapshot exported to ${k}`)}finally{D.close()}}),R.command("control-plane:import <file>","Import a portable control-plane snapshot").option("--path <path>","Use a non-default control-plane database").option("--replace","Replace existing control-plane records").action((t,m)=>{let k=ka(t),D=JSON.parse(Ug(k,"utf8")),L=Jt(m?.path);try{let z=m?.replace?L.createBackup("pre-import"):void 0;if(L.importSnapshot(D,{replace:m?.replace}),n(`Imported control-plane snapshot from ${k}`),z)e(`Previous state backed up to ${z}`)}finally{L.close()}}),R.command("control-plane:compact","Apply history retention and compact control-plane storage").option("--path <path>","Use a non-default control-plane database").option("--event-days <days>","Retain event history for this many days",{default:"90"}).option("--operation-days <days>","Retain terminal operations for this many days",{default:"365"}).option("--no-vacuum","Delete expired records without reclaiming file space").action((t)=>{let m=Jt(t?.path);try{let k=m.compact({eventRetentionDays:Number(t?.eventDays??90),operationRetentionDays:Number(t?.operationDays??365),vacuum:t?.vacuum!==!1});if(n(`Removed ${k.deletedEvents} event(s) and ${k.deletedOperations} terminal operation(s).`),k.vacuumed)e("Database file compacted.")}finally{m.close()}}),R.command("organization:list","List organizations in the local control plane").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{i("Organizations");for(let k of m.listOrganizations())e(`${k.slug} ${k.name} ${k.id}`)}finally{m.close()}}),R.command("organization:members <organization>","List memberships and scoped grants").option("--path <path>","Use a non-default control-plane database").option("--all","Include revoked memberships").action((t,m)=>{let k=Jt(m?.path);try{let D=Oi(k,t);i(`${D.name} members`);for(let L of k.listMemberships(D.id,{includeRevoked:m?.all})){let z=k.getActor(L.actorId);e(`${z?.displayName??L.actorId} ${L.roleTemplate} ${L.scope.type}:${L.scope.id??D.slug} ${L.status} ${L.id}`);for(let Y of k.listGrants(L.id))e(` ${Y.effect} ${Y.capability} @ ${Y.scope.type}:${Y.scope.id??D.slug}`)}}finally{k.close()}}),R.command("organization:invite <organization> <email>","Create a hashed, expiring organization invitation").option("--path <path>","Use a non-default control-plane database").option("--role <role>","owner|admin|deployer|operator|viewer|auditor",{default:"viewer"}).option("--scope <scope>","organization|project|environment|resource",{default:"organization"}).option("--scope-id <id>","Project, environment, or resource ID").option("--days <days>","Invitation lifetime in days",{default:"7"}).option("--base-url <url>","Dashboard URL used to print the acceptance link",{default:"http://127.0.0.1:7676"}).action((t,m,k)=>{let D=Jt(k?.path);try{let L=Oi(D,t);if(!["owner","admin","deployer","operator","viewer","auditor"].includes(k?.role??"viewer"))throw Error("Unknown role template.");let z=D.createInvitation({organizationId:L.id,email:m,roleTemplate:k?.role??"viewer",scope:wa(k?.scope,k?.scopeId),expiresInMs:Number(k?.days??7)*86400000});n(`Invitation created for ${z.invitation.email}; expires ${z.invitation.expiresAt}`),e(`${String(k?.baseUrl??"http://127.0.0.1:7676").replace(/\/$/,"")}/accept-invitation?token=${encodeURIComponent(z.token)}`),e("This token is shown once. Resending revokes it and creates a new token.")}finally{D.close()}}),R.command("organization:invitations <organization>","List invitation states without exposing tokens").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=Oi(k,t);i(`${D.name} invitations`);for(let L of k.listInvitations(D.id))e(`${L.email} ${L.roleTemplate} ${L.scope.type}:${L.scope.id??D.slug} ${L.state} ${L.id}`)}finally{k.close()}}),R.command("organization:revoke-invitation <id>","Revoke a pending organization invitation").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=k.revokeInvitation(t);n(`Invitation ${D.id} is ${D.state}.`)}finally{k.close()}}),R.command("organization:grant <organization> <membership> <capability>","Add an explicit scoped allow or deny").option("--path <path>","Use a non-default control-plane database").option("--effect <effect>","allow|deny",{default:"allow"}).option("--scope <scope>","organization|project|environment|resource",{default:"organization"}).option("--scope-id <id>","Project, environment, or resource ID").action((t,m,k,D)=>{let L=Jt(D?.path);try{let z=Oi(L,t);if(!ce.includes(k))throw Error(`Unknown capability '${k}'.`);if(D?.effect!==void 0&&!["allow","deny"].includes(D.effect))throw Error("Effect must be allow or deny.");let Y=L.upsertGrant({organizationId:z.id,membershipId:m,effect:D?.effect??"allow",capability:k,scope:wa(D?.scope,D?.scopeId)});n(`${Y.effect} ${Y.capability} added at ${Y.scope.type}:${Y.scope.id??z.slug}.`)}finally{L.close()}}),R.command("organization:revoke-member <membership>","Revoke a membership and invalidate its sessions").option("--path <path>","Use a non-default control-plane database").option("--confirm <id>","Type the membership ID to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke this membership.`);let k=Jt(m?.path);try{let D=k.revokeMembership(t);n(`Membership ${D.id} revoked; session version is now ${D.sessionVersion}.`)}finally{k.close()}}),R.command("auth:identities","List local authentication identities without credential material").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{let k=new Me(m);i("Authentication identities");for(let D of k.listIdentities())e(`${D.username} ${D.email??"no-email"} ${D.disabledAt?"disabled":"active"} ${D.id}`)}finally{m.close()}}),R.command("auth:reset-link <identity>","Create a one-time password reset link for offline recovery").option("--path <path>","Use a non-default control-plane database").option("--base-url <url>","Dashboard URL used to print the reset link",{default:"http://127.0.0.1:7676"}).action((t,m)=>{let k=Jt(m?.path);try{let D=new Me(k),L=Sa(D,t);D.revokeActionTokens(L.id,"password_reset");let z=D.createActionToken(L.id,"password_reset");n(`Created a one-time reset link for ${L.username}; it expires ${z.actionToken.expiresAt}.`),e(`${String(m?.baseUrl??"http://127.0.0.1:7676").replace(/\/$/,"")}/reset-password?token=${encodeURIComponent(z.token)}`)}finally{k.close()}}),R.command("auth:sessions <identity>","List active and expired sessions for an identity").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=new Me(k),L=Sa(D,t);i(`${L.username} sessions`);for(let z of D.listSessions(L.id,{includeInactive:!0}))e(`${z.state} ${z.authMethod} ${z.userAgent??"unknown-device"} ${z.lastUsedAt} ${z.id}`)}finally{k.close()}}),R.command("auth:revoke-sessions <identity>","Revoke every dashboard session for an identity").option("--path <path>","Use a non-default control-plane database").option("--confirm <identity>","Type the username or email to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke these sessions.`);let k=Jt(m?.path);try{let D=new Me(k),L=Sa(D,t),z=D.listSessions(L.id);for(let Y of z)D.revokeSession(L.id,Y.id);n(`Revoked ${z.length} session(s) for ${L.username}.`)}finally{k.close()}}),R.command("auth:disable-mfa <identity>","Disable MFA through the offline administrative recovery path").option("--path <path>","Use a non-default control-plane database").option("--confirm <identity>","Type the username or email to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to disable MFA.`);let k=Jt(m?.path);try{let D=new Me(k),L=Sa(D,t);D.disableMfa(L.id);for(let z of D.listSessions(L.id))D.revokeSession(L.id,z.id);n(`Disabled MFA and revoked active sessions for ${L.username}.`)}finally{k.close()}}),R.command("auth:oidc:list <organization>","List OIDC providers without credential material").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=Oi(k,t),L=$a(k);i(`${D.name} OIDC providers`);let z=L.listOidcProviders(D.id,{includeDisabled:!0});if(z.length===0)e("No providers configured.");for(let Y of z)e(`${Y.enabled?"enabled":"disabled"} ${Y.slug} ${Y.issuer} role=${Y.defaultRole} enforce=${Y.enforceSso}`)}finally{k.close()}}),R.command("auth:oidc:configure <organization> <slug>","Create or update an encrypted OIDC provider configuration").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").option("--name <name>","Provider label shown on the sign-in page").option("--issuer <url>","Exact OIDC issuer URL").option("--client-id <id>","OIDC client ID").option("--secret-env <name>","Environment variable containing the client secret",{default:"TS_CLOUD_OIDC_CLIENT_SECRET"}).option("--domains <domains>","Comma-separated verified email domains").option("--scopes <scopes>","Comma-separated scopes (openid is always included)",{default:"email,profile"}).option("--default-role <role>","Role for provisioned users: admin, deployer, operator, viewer, or auditor",{default:"viewer"}).option("--enforce-sso","Require SSO for matching non-owner local accounts").option("--disabled","Save the provider disabled").action((t,m,k)=>{let D=Jt(k?.path);try{let L=Oi(D,t),z=$a(D,k?.authRoot),Y=z.getOidcProviderBySlug(m),X=k?.secretEnv??"TS_CLOUD_OIDC_CLIENT_SECRET",ne=process.env[X]?.trim();if(!Y&&!ne)throw Error(`Set ${X} to the client secret before creating this provider.`);let re=(k?.domains??Y?.allowedDomains.join(",")??"").split(",").map((be)=>be.trim()).filter(Boolean),ie=k?.defaultRole??Y?.defaultRole??"viewer";if(!["admin","deployer","operator","viewer","auditor"].includes(ie))throw Error("Default role must be admin, deployer, operator, viewer, or auditor.");let ae=z.upsertOidcProvider({id:Y?.id,organizationId:L.id,slug:m,name:k?.name??Y?.name??m,issuer:k?.issuer??Y?.issuer??"",clientId:k?.clientId??Y?.clientId??"",clientSecret:ne,allowedDomains:re,scopes:(k?.scopes??Y?.scopes.join(",")??"email,profile").split(",").map((be)=>be.trim()).filter(Boolean),defaultRole:ie,enforceSso:k?.enforceSso??Y?.enforceSso??!1,enabled:k?.disabled?!1:Y?.enabled??!0});if(n(`${ae.name} saved as ${ae.enabled?"enabled":"disabled"} with encrypted credentials.`),e(`Callback path: /auth/oidc/${ae.slug}/callback`),ae.enforceSso)e(`Offline recovery: cloud auth:oidc:disable ${ae.slug} --confirm ${ae.slug}`)}finally{D.close()}}),R.command("auth:oidc:test <slug>","Validate OIDC discovery for a configured provider").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").action(async(t,m)=>{let k=Jt(m?.path);try{let L=$a(k,m?.authRoot).getOidcProviderBySlug(t);if(!L)throw Error(`OIDC provider '${t}' was not found.`);let z=await _n(L);n(`${L.name} discovery is valid and issuer-bound.`),e(`Authorization: ${z.authorization_endpoint}`),e(`Token: ${z.token_endpoint}`),e(`JWKS: ${z.jwks_uri}`)}finally{k.close()}}),R.command("auth:oidc:disable <slug>","Disable an OIDC provider through the offline recovery path").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").option("--confirm <slug>","Type the provider slug to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to disable this provider.`);let k=Jt(m?.path);try{let D=$a(k,m?.authRoot),L=D.getOidcProviderBySlug(t);if(!L)throw Error(`OIDC provider '${t}' was not found.`);D.setOidcProviderEnabled(L.id,!1),n(`Disabled ${L.name}, cancelled pending sign-ins, and removed SSO enforcement.`)}finally{k.close()}}),R.command("api:service-accounts <organization>","List service accounts and safe token metadata").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=Oi(k,t),L=new qe(k);i(`${D.name} service accounts`);for(let z of L.listServiceAccounts(D.id,{includeDisabled:!0})){e(`${z.state} ${z.slug} ${z.name} ${z.id}`);for(let Y of L.listTokens(z.id,{includeInactive:!0}))e(` ${Y.state} ${Y.prefix} ${Y.name} expires=${Y.expiresAt} last-used=${Y.lastUsedAt??"never"}`)}}finally{k.close()}}),R.command("api:service-account:create <organization> <slug>","Create a scoped service account").option("--path <path>","Use a non-default control-plane database").option("--name <name>","Display name").option("--role <role>","admin, deployer, operator, viewer, or auditor",{default:"deployer"}).option("--scope <scope>","organization, project, environment, or resource",{default:"organization"}).option("--scope-id <id>","Required for non-organization scope").action((t,m,k)=>{let D=Jt(k?.path);try{let L=Oi(D,t),z=k?.role??"deployer";if(!["admin","deployer","operator","viewer","auditor"].includes(z))throw Error("Service-account role cannot be owner and must be a supported role.");let X=new qe(D).createServiceAccount({organizationId:L.id,slug:m,name:k?.name??m,roleTemplate:z,scope:wa(k?.scope,k?.scopeId)});n(`Created ${X.serviceAccount.name} (${X.serviceAccount.id}) with ${X.membership.roleTemplate} access.`)}finally{D.close()}}),R.command("api:token:create <organization> <account>","Create and reveal a scoped API token exactly once").option("--path <path>","Use a non-default control-plane database").option("--name <name>","Token label",{default:"CLI token"}).option("--capabilities <list>","Comma-separated capabilities").option("--scope <scope>","organization, project, environment, or resource",{default:"organization"}).option("--scope-id <id>","Required for non-organization scope").option("--expires-days <days>","Expiry in days, maximum 365",{default:"90"}).action((t,m,k)=>{let D=Jt(k?.path);try{let L=Oi(D,t),z=new qe(D),Y=z.getServiceAccount(m)??z.getServiceAccountBySlug(L.id,m);if(!Y||Y.organizationId!==L.id)throw Error(`Service account '${m}' was not found.`);let X=(k?.capabilities??"").split(",").map((ie)=>ie.trim()).filter((ie)=>ce.includes(ie));if(!X.length)throw Error(`Pass --capabilities with one or more of: ${ce.join(", ")}`);let ne=Math.min(365,Math.max(1,Number(k?.expiresDays??90))),re=z.createToken({serviceAccountId:Y.id,name:k?.name??"CLI token",capabilities:X,scope:wa(k?.scope,k?.scopeId),expiresAt:new Date(Date.now()+ne*24*60*60*1000).toISOString()});n(`Created ${re.token.prefix}; copy this secret now. It will not be shown again.`),e(re.secret)}finally{D.close()}}),R.command("api:token:rotate <token>","Create an overlapping replacement for an API token").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=new qe(k).rotateToken(t);n(`Created overlapping replacement ${D.token.prefix}; revoke the old token after rollout.`),e(D.secret)}finally{k.close()}}),R.command("api:token:revoke <token>","Immediately revoke an API token").option("--path <path>","Use a non-default control-plane database").option("--confirm <token>","Type the token ID to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke this token.`);let k=Jt(m?.path);try{let D=new qe(k).revokeToken(t);n(`Revoked ${D.prefix}; bearer requests stop immediately.`)}finally{k.close()}}),R.command("api:projects","List projects through the public v1 API").option("--base-url <url>","Dashboard base URL").option("--token-env <name>","Environment variable containing the API token",{default:"TS_CLOUD_API_TOKEN"}).action(async(t)=>{let m=await Cu(t?.baseUrl,t?.tokenEnv).listProjects();for(let k of m.data)e(`${k.slug} ${k.name} ${k.id}`)}),R.command("api:deploy <project> <environment>","Queue a deployment through the public v1 API").option("--base-url <url>","Dashboard base URL").option("--token-env <name>","Environment variable containing the API token",{default:"TS_CLOUD_API_TOKEN"}).option("--service <id>","Optional service resource ID").option("--revision <revision>","Revision or release identifier").option("--idempotency-key <key>","Stable retry key").action(async(t,m,k)=>{let D=k?.idempotencyKey??`cli-${Date.now()}-${crypto.randomUUID().slice(0,8)}`,L=await Cu(k?.baseUrl,k?.tokenEnv).createDeployment({projectId:t,environmentId:m,serviceId:k?.service,revision:k?.revision},D);n(`${L.idempotentReplay?"Replayed":"Queued"} operation ${L.operation.id} (${L.operation.state}).`)})}import{createHash as Hg}from"node:crypto";var Gg=`let coldStart = true
|
|
464
|
+
Add the tracking script to your website:`),e(` <script src="https://analytics.stacksjs.com/track.js" data-site-id="${Y}"></script>`)}catch(m){r(`Failed to create site: ${m.message}`),process.exit(1)}}),R.command("analytics:sites:update <siteId>","Update an analytics site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--name <name>","New site name").option("--domain <domain>","Set domains (replaces existing)").option("--active <boolean>","Set site active/inactive status").action(async(t,m)=>{i("Update Analytics Site");try{let k=new G(m.region),D=await k.getItem({TableName:m.table,Key:{pk:{S:`SITE#${t}`},sk:{S:`SITE#${t}`}}});if(!D.Item)r(`Site not found: ${t}`),process.exit(1);let L=G.unmarshal(D.Item);e(`Updating site: ${L.name||"Unnamed"} (${t})`),e("");let z=[],Y={},X={};if(m.name)z.push("#n = :name"),Y["#n"]="name",X[":name"]={S:m.name},e(` Name: ${L.name} -> ${m.name}`);if(m.domain!==void 0){let ne=Array.isArray(m.domain)?m.domain:[m.domain];z.push("domains = :domains"),X[":domains"]={L:ne.map((re)=>({S:re}))},e(` Domains: ${JSON.stringify(L.domains||[])} -> ${JSON.stringify(ne)}`)}if(m.active!==void 0){let ne=m.active==="true"||m.active==="1";z.push("isActive = :active"),X[":active"]={BOOL:ne},e(` Active: ${L.isActive} -> ${ne}`)}if(z.length===0){a("No updates specified. Use --name, --domain, or --active options.");return}z.push("updatedAt = :updatedAt"),X[":updatedAt"]={S:new Date().toISOString()},await k.updateItem({TableName:m.table,Key:{pk:{S:`SITE#${t}`},sk:{S:`SITE#${t}`}},UpdateExpression:`SET ${z.join(", ")}`,ExpressionAttributeNames:Object.keys(Y).length>0?Y:void 0,ExpressionAttributeValues:X}),e(""),n("Site updated successfully")}catch(k){r(`Failed to update site: ${k.message}`),process.exit(1)}}),R.command("analytics:query <siteId>","Query analytics data for a site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--limit <n>","Max items to return",{default:"10"}).option("--type <type>","Filter by type (pageview, event, etc.)").action(async(t,m)=>{i(`Analytics Data: ${t}`);try{let k=new G(m.region),D=new s("Querying data...");D.start();let L=await k.query({TableName:m.table,KeyConditionExpression:"pk = :pk",ExpressionAttributeValues:{":pk":{S:`SITE#${t}`}},ScanIndexForward:!1,Limit:parseInt(m.limit,10)});if(D.succeed(`Found ${L.Items?.length||0} items`),!L.Items||L.Items.length===0){e("No data found for this site");return}let z=L.Items.map((Y)=>G.unmarshal(Y));for(let Y of z){if(e(""),e(`SK: ${Y.sk}`),Y.path)e(` Path: ${Y.path}`);if(Y.timestamp)e(` Time: ${Y.timestamp}`);if(Y.visitorId)e(` Visitor: ${Y.visitorId}`);if(Y.browser)e(` Browser: ${Y.browser}`);if(Y.country)e(` Country: ${Y.country}`)}}catch(k){r(`Failed to query data: ${k.message}`),process.exit(1)}}),R.command("analytics:realtime <siteId>","Check realtime visitors for a site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--minutes <n>","Minutes to look back",{default:"5"}).action(async(t,m)=>{i(`Realtime: ${t}`);try{let k=new G(m.region),D=new s("Checking realtime...");D.start();let L=parseInt(m.minutes,10),z=new Date(Date.now()-L*60*1000),Y=await k.query({TableName:m.table,KeyConditionExpression:"pk = :pk AND sk BETWEEN :start AND :end",ExpressionAttributeValues:{":pk":{S:`SITE#${t}`},":start":{S:`PAGEVIEW#${z.toISOString()}`},":end":{S:"PAGEVIEW#Z"}},ScanIndexForward:!1});if(D.succeed(`Found ${Y.Items?.length||0} pageviews in last ${L} minutes`),!Y.Items||Y.Items.length===0){a("No recent pageviews found"),e(""),e("Possible issues:"),e(" 1. Tracking script not installed correctly"),e(" 2. Site ID mismatch"),e(" 3. CORS or network issues");return}let X=Y.Items.map((ie)=>G.unmarshal(ie)),ne=new Set(X.map((ie)=>ie.visitorId)).size;e(""),n(`${ne} unique visitor(s) online`),e("");let re={};for(let ie of X){let ae=ie.path||"/";re[ae]=(re[ae]||0)+1}e("Active pages:");for(let[ie,ae]of Object.entries(re).sort((be,Ae)=>Ae[1]-be[1]).slice(0,5))e(` ${ie}: ${ae} view(s)`);e(""),e("Recent pageviews:");for(let ie of X.slice(0,5))e(` ${ie.timestamp} - ${ie.path} (${ie.browser||"Unknown"})`)}catch(k){r(`Failed to check realtime: ${k.message}`),process.exit(1)}})}import{chmodSync as Mg,mkdirSync as zg,readFileSync as Ug,writeFileSync as Wg}from"node:fs";import{dirname as Vg,resolve as ka}from"node:path";function Jt(R){return new Fr(R?{path:ka(R)}:{})}function qg(R){let t=R.health();i("Control-plane diagnostics"),e(`Database: ${t.path}`),e(`Schema: ${t.schemaVersion}/${t.supportedSchemaVersion}`),e(`Integrity: ${t.integrity}`),e(`Journal: ${t.journalMode}`),e(`Size: ${t.databaseBytes.toLocaleString()} bytes`),e(`Last backup: ${t.lastBackupAt??"never"}`),e(`Operations: ${Object.entries(t.operations).map(([m,k])=>`${m}=${k}`).join(", ")}`),e(`Pending/retryable: ${t.pendingRetryableOperations}`)}function Ni(R,t){let m=R.getOrganization(t)??R.getOrganizationBySlug(t);if(!m)throw Error(`Organization '${t}' was not found.`);return m}function wa(R,t){let m=R??"organization";if(m==="organization")return{type:"organization"};if(!["project","environment","resource"].includes(m)||!t)throw Error("Scoped access requires --scope project|environment|resource and --scope-id <id>.");return{type:m,id:t}}function Sa(R,t){let m=t.includes("@")?R.getIdentityByEmail(t):R.getIdentityByUsername(t);if(!m)throw Error(`Authentication identity '${t}' was not found.`);return m}function $a(R,t){return new Me(R,{encryptionKey:_(ka(t??process.cwd()))})}function Cu(R,t="TS_CLOUD_API_TOKEN"){let m=process.env[t]?.trim();if(!m)throw Error(`Set ${t} to a one-time-issued API token.`);return new Zi({baseUrl:R??process.env.TS_CLOUD_API_URL??"http://127.0.0.1:7676",token:m})}function wl(R){R.command("dashboard:serve","Run the local Forge-style cloud management UI").option("--host <host>","Host to bind",{default:"127.0.0.1"}).option("--port <port>","Port to bind",{default:"7676"}).option("--env <environment>","Environment to manage").option("--box","Box mode: run on the provisioned server (operate on localhost)").option("--open","Print the URL for opening in a browser").option("--verbose","Print server errors").action(async(t)=>{let m=await gr({host:t?.host,port:Number(t?.port??7676),environment:t?.env,box:t?.box,verbose:t?.verbose});i("ts-cloud Local Dashboard"),n(`Serving ${m.url}`),e("Use Ctrl+C to stop."),await new Promise((k)=>{let D=()=>{m.server.stop(!0),k()};process.once("SIGINT",D),process.once("SIGTERM",D)})}),R.command("control-plane:status","Inspect local control-plane storage health").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{qg(m)}finally{m.close()}}),R.command("control-plane:backup","Create a consistent local control-plane backup").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{let k=m.createBackup("cli");n(`Backup written to ${k}`)}finally{m.close()}}),R.command("control-plane:export <file>","Export portable control-plane metadata and history").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=ka(t),D=Jt(m?.path);try{zg(Vg(k),{recursive:!0}),Wg(k,`${JSON.stringify(D.exportSnapshot(),null,2)}
|
|
465
|
+
`,{mode:384}),Mg(k,384),n(`Control-plane snapshot exported to ${k}`)}finally{D.close()}}),R.command("control-plane:import <file>","Import a portable control-plane snapshot").option("--path <path>","Use a non-default control-plane database").option("--replace","Replace existing control-plane records").action((t,m)=>{let k=ka(t),D=JSON.parse(Ug(k,"utf8")),L=Jt(m?.path);try{let z=m?.replace?L.createBackup("pre-import"):void 0;if(L.importSnapshot(D,{replace:m?.replace}),n(`Imported control-plane snapshot from ${k}`),z)e(`Previous state backed up to ${z}`)}finally{L.close()}}),R.command("control-plane:compact","Apply history retention and compact control-plane storage").option("--path <path>","Use a non-default control-plane database").option("--event-days <days>","Retain event history for this many days",{default:"90"}).option("--operation-days <days>","Retain terminal operations for this many days",{default:"365"}).option("--no-vacuum","Delete expired records without reclaiming file space").action((t)=>{let m=Jt(t?.path);try{let k=m.compact({eventRetentionDays:Number(t?.eventDays??90),operationRetentionDays:Number(t?.operationDays??365),vacuum:t?.vacuum!==!1});if(n(`Removed ${k.deletedEvents} event(s) and ${k.deletedOperations} terminal operation(s).`),k.vacuumed)e("Database file compacted.")}finally{m.close()}}),R.command("organization:list","List organizations in the local control plane").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{i("Organizations");for(let k of m.listOrganizations())e(`${k.slug} ${k.name} ${k.id}`)}finally{m.close()}}),R.command("organization:members <organization>","List memberships and scoped grants").option("--path <path>","Use a non-default control-plane database").option("--all","Include revoked memberships").action((t,m)=>{let k=Jt(m?.path);try{let D=Ni(k,t);i(`${D.name} members`);for(let L of k.listMemberships(D.id,{includeRevoked:m?.all})){let z=k.getActor(L.actorId);e(`${z?.displayName??L.actorId} ${L.roleTemplate} ${L.scope.type}:${L.scope.id??D.slug} ${L.status} ${L.id}`);for(let Y of k.listGrants(L.id))e(` ${Y.effect} ${Y.capability} @ ${Y.scope.type}:${Y.scope.id??D.slug}`)}}finally{k.close()}}),R.command("organization:invite <organization> <email>","Create a hashed, expiring organization invitation").option("--path <path>","Use a non-default control-plane database").option("--role <role>","owner|admin|deployer|operator|viewer|auditor",{default:"viewer"}).option("--scope <scope>","organization|project|environment|resource",{default:"organization"}).option("--scope-id <id>","Project, environment, or resource ID").option("--days <days>","Invitation lifetime in days",{default:"7"}).option("--base-url <url>","Dashboard URL used to print the acceptance link",{default:"http://127.0.0.1:7676"}).action((t,m,k)=>{let D=Jt(k?.path);try{let L=Ni(D,t);if(!["owner","admin","deployer","operator","viewer","auditor"].includes(k?.role??"viewer"))throw Error("Unknown role template.");let z=D.createInvitation({organizationId:L.id,email:m,roleTemplate:k?.role??"viewer",scope:wa(k?.scope,k?.scopeId),expiresInMs:Number(k?.days??7)*86400000});n(`Invitation created for ${z.invitation.email}; expires ${z.invitation.expiresAt}`),e(`${String(k?.baseUrl??"http://127.0.0.1:7676").replace(/\/$/,"")}/accept-invitation?token=${encodeURIComponent(z.token)}`),e("This token is shown once. Resending revokes it and creates a new token.")}finally{D.close()}}),R.command("organization:invitations <organization>","List invitation states without exposing tokens").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=Ni(k,t);i(`${D.name} invitations`);for(let L of k.listInvitations(D.id))e(`${L.email} ${L.roleTemplate} ${L.scope.type}:${L.scope.id??D.slug} ${L.state} ${L.id}`)}finally{k.close()}}),R.command("organization:revoke-invitation <id>","Revoke a pending organization invitation").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=k.revokeInvitation(t);n(`Invitation ${D.id} is ${D.state}.`)}finally{k.close()}}),R.command("organization:grant <organization> <membership> <capability>","Add an explicit scoped allow or deny").option("--path <path>","Use a non-default control-plane database").option("--effect <effect>","allow|deny",{default:"allow"}).option("--scope <scope>","organization|project|environment|resource",{default:"organization"}).option("--scope-id <id>","Project, environment, or resource ID").action((t,m,k,D)=>{let L=Jt(D?.path);try{let z=Ni(L,t);if(!ce.includes(k))throw Error(`Unknown capability '${k}'.`);if(D?.effect!==void 0&&!["allow","deny"].includes(D.effect))throw Error("Effect must be allow or deny.");let Y=L.upsertGrant({organizationId:z.id,membershipId:m,effect:D?.effect??"allow",capability:k,scope:wa(D?.scope,D?.scopeId)});n(`${Y.effect} ${Y.capability} added at ${Y.scope.type}:${Y.scope.id??z.slug}.`)}finally{L.close()}}),R.command("organization:revoke-member <membership>","Revoke a membership and invalidate its sessions").option("--path <path>","Use a non-default control-plane database").option("--confirm <id>","Type the membership ID to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke this membership.`);let k=Jt(m?.path);try{let D=k.revokeMembership(t);n(`Membership ${D.id} revoked; session version is now ${D.sessionVersion}.`)}finally{k.close()}}),R.command("auth:identities","List local authentication identities without credential material").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{let k=new Me(m);i("Authentication identities");for(let D of k.listIdentities())e(`${D.username} ${D.email??"no-email"} ${D.disabledAt?"disabled":"active"} ${D.id}`)}finally{m.close()}}),R.command("auth:reset-link <identity>","Create a one-time password reset link for offline recovery").option("--path <path>","Use a non-default control-plane database").option("--base-url <url>","Dashboard URL used to print the reset link",{default:"http://127.0.0.1:7676"}).action((t,m)=>{let k=Jt(m?.path);try{let D=new Me(k),L=Sa(D,t);D.revokeActionTokens(L.id,"password_reset");let z=D.createActionToken(L.id,"password_reset");n(`Created a one-time reset link for ${L.username}; it expires ${z.actionToken.expiresAt}.`),e(`${String(m?.baseUrl??"http://127.0.0.1:7676").replace(/\/$/,"")}/reset-password?token=${encodeURIComponent(z.token)}`)}finally{k.close()}}),R.command("auth:sessions <identity>","List active and expired sessions for an identity").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=new Me(k),L=Sa(D,t);i(`${L.username} sessions`);for(let z of D.listSessions(L.id,{includeInactive:!0}))e(`${z.state} ${z.authMethod} ${z.userAgent??"unknown-device"} ${z.lastUsedAt} ${z.id}`)}finally{k.close()}}),R.command("auth:revoke-sessions <identity>","Revoke every dashboard session for an identity").option("--path <path>","Use a non-default control-plane database").option("--confirm <identity>","Type the username or email to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke these sessions.`);let k=Jt(m?.path);try{let D=new Me(k),L=Sa(D,t),z=D.listSessions(L.id);for(let Y of z)D.revokeSession(L.id,Y.id);n(`Revoked ${z.length} session(s) for ${L.username}.`)}finally{k.close()}}),R.command("auth:disable-mfa <identity>","Disable MFA through the offline administrative recovery path").option("--path <path>","Use a non-default control-plane database").option("--confirm <identity>","Type the username or email to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to disable MFA.`);let k=Jt(m?.path);try{let D=new Me(k),L=Sa(D,t);D.disableMfa(L.id);for(let z of D.listSessions(L.id))D.revokeSession(L.id,z.id);n(`Disabled MFA and revoked active sessions for ${L.username}.`)}finally{k.close()}}),R.command("auth:oidc:list <organization>","List OIDC providers without credential material").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=Ni(k,t),L=$a(k);i(`${D.name} OIDC providers`);let z=L.listOidcProviders(D.id,{includeDisabled:!0});if(z.length===0)e("No providers configured.");for(let Y of z)e(`${Y.enabled?"enabled":"disabled"} ${Y.slug} ${Y.issuer} role=${Y.defaultRole} enforce=${Y.enforceSso}`)}finally{k.close()}}),R.command("auth:oidc:configure <organization> <slug>","Create or update an encrypted OIDC provider configuration").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").option("--name <name>","Provider label shown on the sign-in page").option("--issuer <url>","Exact OIDC issuer URL").option("--client-id <id>","OIDC client ID").option("--secret-env <name>","Environment variable containing the client secret",{default:"TS_CLOUD_OIDC_CLIENT_SECRET"}).option("--domains <domains>","Comma-separated verified email domains").option("--scopes <scopes>","Comma-separated scopes (openid is always included)",{default:"email,profile"}).option("--default-role <role>","Role for provisioned users: admin, deployer, operator, viewer, or auditor",{default:"viewer"}).option("--enforce-sso","Require SSO for matching non-owner local accounts").option("--disabled","Save the provider disabled").action((t,m,k)=>{let D=Jt(k?.path);try{let L=Ni(D,t),z=$a(D,k?.authRoot),Y=z.getOidcProviderBySlug(m),X=k?.secretEnv??"TS_CLOUD_OIDC_CLIENT_SECRET",ne=process.env[X]?.trim();if(!Y&&!ne)throw Error(`Set ${X} to the client secret before creating this provider.`);let re=(k?.domains??Y?.allowedDomains.join(",")??"").split(",").map((be)=>be.trim()).filter(Boolean),ie=k?.defaultRole??Y?.defaultRole??"viewer";if(!["admin","deployer","operator","viewer","auditor"].includes(ie))throw Error("Default role must be admin, deployer, operator, viewer, or auditor.");let ae=z.upsertOidcProvider({id:Y?.id,organizationId:L.id,slug:m,name:k?.name??Y?.name??m,issuer:k?.issuer??Y?.issuer??"",clientId:k?.clientId??Y?.clientId??"",clientSecret:ne,allowedDomains:re,scopes:(k?.scopes??Y?.scopes.join(",")??"email,profile").split(",").map((be)=>be.trim()).filter(Boolean),defaultRole:ie,enforceSso:k?.enforceSso??Y?.enforceSso??!1,enabled:k?.disabled?!1:Y?.enabled??!0});if(n(`${ae.name} saved as ${ae.enabled?"enabled":"disabled"} with encrypted credentials.`),e(`Callback path: /auth/oidc/${ae.slug}/callback`),ae.enforceSso)e(`Offline recovery: cloud auth:oidc:disable ${ae.slug} --confirm ${ae.slug}`)}finally{D.close()}}),R.command("auth:oidc:test <slug>","Validate OIDC discovery for a configured provider").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").action(async(t,m)=>{let k=Jt(m?.path);try{let L=$a(k,m?.authRoot).getOidcProviderBySlug(t);if(!L)throw Error(`OIDC provider '${t}' was not found.`);let z=await On(L);n(`${L.name} discovery is valid and issuer-bound.`),e(`Authorization: ${z.authorization_endpoint}`),e(`Token: ${z.token_endpoint}`),e(`JWKS: ${z.jwks_uri}`)}finally{k.close()}}),R.command("auth:oidc:disable <slug>","Disable an OIDC provider through the offline recovery path").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").option("--confirm <slug>","Type the provider slug to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to disable this provider.`);let k=Jt(m?.path);try{let D=$a(k,m?.authRoot),L=D.getOidcProviderBySlug(t);if(!L)throw Error(`OIDC provider '${t}' was not found.`);D.setOidcProviderEnabled(L.id,!1),n(`Disabled ${L.name}, cancelled pending sign-ins, and removed SSO enforcement.`)}finally{k.close()}}),R.command("api:service-accounts <organization>","List service accounts and safe token metadata").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=Ni(k,t),L=new qe(k);i(`${D.name} service accounts`);for(let z of L.listServiceAccounts(D.id,{includeDisabled:!0})){e(`${z.state} ${z.slug} ${z.name} ${z.id}`);for(let Y of L.listTokens(z.id,{includeInactive:!0}))e(` ${Y.state} ${Y.prefix} ${Y.name} expires=${Y.expiresAt} last-used=${Y.lastUsedAt??"never"}`)}}finally{k.close()}}),R.command("api:service-account:create <organization> <slug>","Create a scoped service account").option("--path <path>","Use a non-default control-plane database").option("--name <name>","Display name").option("--role <role>","admin, deployer, operator, viewer, or auditor",{default:"deployer"}).option("--scope <scope>","organization, project, environment, or resource",{default:"organization"}).option("--scope-id <id>","Required for non-organization scope").action((t,m,k)=>{let D=Jt(k?.path);try{let L=Ni(D,t),z=k?.role??"deployer";if(!["admin","deployer","operator","viewer","auditor"].includes(z))throw Error("Service-account role cannot be owner and must be a supported role.");let X=new qe(D).createServiceAccount({organizationId:L.id,slug:m,name:k?.name??m,roleTemplate:z,scope:wa(k?.scope,k?.scopeId)});n(`Created ${X.serviceAccount.name} (${X.serviceAccount.id}) with ${X.membership.roleTemplate} access.`)}finally{D.close()}}),R.command("api:token:create <organization> <account>","Create and reveal a scoped API token exactly once").option("--path <path>","Use a non-default control-plane database").option("--name <name>","Token label",{default:"CLI token"}).option("--capabilities <list>","Comma-separated capabilities").option("--scope <scope>","organization, project, environment, or resource",{default:"organization"}).option("--scope-id <id>","Required for non-organization scope").option("--expires-days <days>","Expiry in days, maximum 365",{default:"90"}).action((t,m,k)=>{let D=Jt(k?.path);try{let L=Ni(D,t),z=new qe(D),Y=z.getServiceAccount(m)??z.getServiceAccountBySlug(L.id,m);if(!Y||Y.organizationId!==L.id)throw Error(`Service account '${m}' was not found.`);let X=(k?.capabilities??"").split(",").map((ie)=>ie.trim()).filter((ie)=>ce.includes(ie));if(!X.length)throw Error(`Pass --capabilities with one or more of: ${ce.join(", ")}`);let ne=Math.min(365,Math.max(1,Number(k?.expiresDays??90))),re=z.createToken({serviceAccountId:Y.id,name:k?.name??"CLI token",capabilities:X,scope:wa(k?.scope,k?.scopeId),expiresAt:new Date(Date.now()+ne*24*60*60*1000).toISOString()});n(`Created ${re.token.prefix}; copy this secret now. It will not be shown again.`),e(re.secret)}finally{D.close()}}),R.command("api:token:rotate <token>","Create an overlapping replacement for an API token").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let k=Jt(m?.path);try{let D=new qe(k).rotateToken(t);n(`Created overlapping replacement ${D.token.prefix}; revoke the old token after rollout.`),e(D.secret)}finally{k.close()}}),R.command("api:token:revoke <token>","Immediately revoke an API token").option("--path <path>","Use a non-default control-plane database").option("--confirm <token>","Type the token ID to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke this token.`);let k=Jt(m?.path);try{let D=new qe(k).revokeToken(t);n(`Revoked ${D.prefix}; bearer requests stop immediately.`)}finally{k.close()}}),R.command("api:projects","List projects through the public v1 API").option("--base-url <url>","Dashboard base URL").option("--token-env <name>","Environment variable containing the API token",{default:"TS_CLOUD_API_TOKEN"}).action(async(t)=>{let m=await Cu(t?.baseUrl,t?.tokenEnv).listProjects();for(let k of m.data)e(`${k.slug} ${k.name} ${k.id}`)}),R.command("api:deploy <project> <environment>","Queue a deployment through the public v1 API").option("--base-url <url>","Dashboard base URL").option("--token-env <name>","Environment variable containing the API token",{default:"TS_CLOUD_API_TOKEN"}).option("--service <id>","Optional service resource ID").option("--revision <revision>","Revision or release identifier").option("--idempotency-key <key>","Stable retry key").action(async(t,m,k)=>{let D=k?.idempotencyKey??`cli-${Date.now()}-${crypto.randomUUID().slice(0,8)}`,L=await Cu(k?.baseUrl,k?.tokenEnv).createDeployment({projectId:t,environmentId:m,serviceId:k?.service,revision:k?.revision},D);n(`${L.idempotentReplay?"Replayed":"Queued"} operation ${L.operation.id} (${L.operation.state}).`)})}import{createHash as Hg}from"node:crypto";var Gg=`let coldStart = true
|
|
466
466
|
const initializedAt = new Date().toISOString()
|
|
467
467
|
export const handler = async (event) => {
|
|
468
468
|
const currentColdStart = coldStart
|
|
@@ -475,7 +475,7 @@ export const handler = async (event) => {
|
|
|
475
475
|
body: JSON.stringify({ ok: statusCode === 200, service: 'ts-cloud-static-api', path, coldStart: currentColdStart, initializedAt })
|
|
476
476
|
}
|
|
477
477
|
}
|
|
478
|
-
`;function Au(R){let t=R;return String(t?.code||t?.name||t?.statusCode||"")}function Kg(R){return["NoSuchEntity","ResourceNotFoundException","404"].includes(Au(R))}function $u(R){return["EntityAlreadyExists","ResourceAlreadyExistsException","ResourceConflictException","409"].includes(Au(R))}function ku(R,t){let m=R?.Items;if(!m)return[];if(Array.isArray(m))return m;let k=m[t];if(k===void 0)return[];return Array.isArray(k)?k:[k]}function Sl(R){let t=R?.Aliases?.Items,m=t?.CNAME??t;if(Array.isArray(m))return m.map(String);if(typeof m==="string")return[m];return[]}function Jg(R){if(!/^[A-Z0-9]{8,32}$/.test(R.distributionId))throw Error("CloudFront distribution ID is invalid");if(!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9-]{2,63}$/.test(R.expectedAlias))throw Error("Expected CloudFront alias is invalid");if(!/^[A-Za-z0-9-_]{1,64}$/.test(R.functionName))throw Error("Lambda function name is invalid");let t=R.pathPattern?.startsWith("/")?R.pathPattern:`/${R.pathPattern||"api/*"}`;if(t==="/"||t==="/*"||!t.endsWith("*"))throw Error("API path must be a non-default wildcard pattern such as /api/*");let m=R.originId||`${R.functionName}-url`,k=R.roleName||`${R.functionName}-execution`;if(!/^[A-Za-z0-9_.-]{1,64}$/.test(k))throw Error("IAM role name is invalid");let D=R.memorySize??256,L=R.timeout??10,z=R.logRetentionDays??14;if(D<128||D>10240)throw Error("Lambda memory must be between 128 and 10240 MB");if(L<1||L>900)throw Error("Lambda timeout must be between 1 and 900 seconds");return{region:R.region||"us-east-1",pathPattern:t,originId:m,roleName:k,memorySize:D,timeout:L,logRetentionDays:z}}function Yg(R){let t=R.region||"us-east-1";return{cloudfront:new E(R.profile),iam:new te(t,R.profile),lambda:new P(t,R.profile),logs:new se(t,R.profile),sts:new mt(t,R.profile),sleep:(m)=>new Promise((k)=>setTimeout(k,m))}}async function Ru(R,t){let m=Jg(R),k=t||Yg({profile:R.profile,region:m.region}),[D,L,z]=await Promise.all([k.sts.getCallerIdentity(),k.cloudfront.getDistribution(R.distributionId),k.cloudfront.getDistributionConfig(R.distributionId)]);if(!D.Account)throw Error("AWS identity did not return an account ID");if(!L.Enabled)throw Error(`CloudFront distribution ${R.distributionId} is disabled`);if(!(Sl(L).length?Sl(L):Sl(z.DistributionConfig)).includes(R.expectedAlias))throw Error(`Distribution ${R.distributionId} does not contain expected alias ${R.expectedAlias}`);let X,ne=!0;try{X=await k.iam.getRole({RoleName:m.roleName})}catch(Nt){if(!Kg(Nt))throw Nt;ne=!1}let re=X?.Arn||`arn:aws:iam::${D.Account}:role/${m.roleName}`,ie=await k.lambda.functionExists(R.functionName),ae=ie?await k.lambda.getFunctionUrl(R.functionName):null,be=await k.cloudfront.listOriginAccessControls(),Ae=`${R.functionName}-lambda-url`,le=be.some((Nt)=>Nt.Name===Ae&&Nt.OriginAccessControlOriginType==="lambda"),ke=ku(z.DistributionConfig.Origins,"Origin"),De=ku(z.DistributionConfig.CacheBehaviors,"CacheBehavior"),Se=ke.some((Nt)=>String(Nt.Id)===m.originId),Re=De.some((Nt)=>String(Nt.PathPattern)===m.pathPattern),Pe=[...!ne?[`create IAM execution role ${m.roleName}`]:[`reconcile IAM execution policy ${m.roleName}`],...ie?[`update Lambda ${R.functionName} code and configuration`]:[`create Lambda ${R.functionName}`],...!ae?["create private AWS_IAM function URL"]:[],...!le?[`create Lambda origin access control ${Ae}`]:[],"grant CloudFront function-URL and invocation permissions",`${Re?"reconcile":"add"} ${m.pathPattern} behavior without changing the default behavior`],Ve={command:`cloud cdn:origin:remove ${R.distributionId} FUNCTION_URL_HOST --id ${m.originId} --path '${m.pathPattern}' --apply --confirm 'remove:${R.distributionId}:${m.pathPattern}'`,removeFunction:!ie,removeRole:!ne},Le={mode:R.apply?"apply":"plan",accountId:D.Account,identityArn:D.Arn,distribution:{id:L.Id,arn:L.ARN,alias:R.expectedAlias,domainName:L.DomainName,status:L.Status},function:{name:R.functionName,existed:ie,runtime:"nodejs22.x",urlExisted:!!ae,url:ae?.FunctionUrl},role:{name:m.roleName,existed:ne,arn:re},origin:{id:m.originId,pathPattern:m.pathPattern,existed:Se,behaviorExisted:Re,accessControlExisted:le},changes:Pe,rollback:Ve,applied:!1};if(!R.apply)return Le;let Fe=`${R.distributionId}:${m.pathPattern}`;if(R.confirm!==Fe)throw Error(`Pass exact confirmation token ${Fe} to mutate the live distribution`);if(!X)X=await k.iam.createRole({RoleName:m.roleName,Description:`Execution role for ${R.functionName}`,AssumeRolePolicyDocument:JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]}),Tags:[{Key:"ManagedBy",Value:"ts-cloud"}]});await k.iam.putRolePolicy({RoleName:m.roleName,PolicyName:"lambda-logs",PolicyDocument:JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["logs:CreateLogStream","logs:PutLogEvents"],Resource:`arn:aws:logs:${m.region}:${D.Account}:log-group:/aws/lambda/${R.functionName}:*`}]})});try{await k.logs.createLogGroup(`/aws/lambda/${R.functionName}`,{ManagedBy:"ts-cloud"})}catch(Nt){if(!$u(Nt))throw Nt}await k.logs.putRetentionPolicy(`/aws/lambda/${R.functionName}`,m.logRetentionDays);let He=R.code||Gg;if(ie)await k.lambda.updateFunctionCodeInline(R.functionName,He,"index.mjs"),await k.lambda.waitForFunctionActive(R.functionName),await k.lambda.updateFunctionConfiguration({FunctionName:R.functionName,Runtime:"nodejs22.x",Role:X.Arn,Handler:"index.handler",Description:`Private API origin for ${R.expectedAlias}`,Timeout:m.timeout,MemorySize:m.memorySize});else{let Nt;for(let Ye=0;Ye<8;Ye++)try{await k.lambda.createFunctionWithCode({FunctionName:R.functionName,Runtime:"nodejs22.x",Role:X.Arn,Handler:"index.handler",Code:He,Filename:"index.mjs",Description:`Private API origin for ${R.expectedAlias}`,Timeout:m.timeout,MemorySize:m.memorySize}),Nt=void 0;break}catch(_t){if(Nt=_t,!String(_t.message).toLowerCase().includes("role"))throw _t;await k.sleep(1000)}if(Nt)throw Nt}await k.lambda.waitForFunctionActive(R.functionName);let wt=ae||await k.lambda.createFunctionUrl({FunctionName:R.functionName,AuthType:"AWS_IAM",InvokeMode:"BUFFERED"});if(!wt.FunctionUrl)throw Error("Lambda did not return a function URL");let Dt=new URL(wt.FunctionUrl).hostname,it=await k.cloudfront.findOrCreateOriginAccessControl(Ae,"lambda"),xt=L.ARN||`arn:aws:cloudfront::${D.Account}:distribution/${R.distributionId}`,Yt=[{FunctionName:R.functionName,StatementId:"AllowCloudFrontFunctionUrl",Action:"lambda:InvokeFunctionUrl",Principal:"cloudfront.amazonaws.com",SourceArn:xt,FunctionUrlAuthType:"AWS_IAM"},{FunctionName:R.functionName,StatementId:"AllowCloudFrontInvoke",Action:"lambda:InvokeFunction",Principal:"cloudfront.amazonaws.com",SourceArn:xt,InvokedViaFunctionUrl:!0}];for(let Nt of Yt)try{await k.lambda.addPermission(Nt)}catch(Ye){if(!$u(Ye))throw Ye}return await k.cloudfront.upsertExistingDistributionOrigin(R.distributionId,{id:m.originId,domainName:Dt,pathPattern:m.pathPattern,originAccessControlId:it.Id}),{...Le,function:{...Le.function,url:wt.FunctionUrl},applied:!0}}async function Iu(R){let t=`https://${R.alias}/`,m=`https://${R.alias}/api/health`,k=await fetch(t,{redirect:"follow",headers:{"cache-control":"no-cache"}}),D=new Uint8Array(await k.arrayBuffer()),L=Hg("sha256").update(D).digest("hex"),z=Date.now(),Y=await fetch(m,{redirect:"follow",headers:{"cache-control":"no-cache"}}),X=Date.now()-z,ne=await Y.text(),re=ne;try{re=JSON.parse(ne)}catch{}let ie;if(R.logs&&R.functionName){let ae=await R.logs.filterLogEvents({logGroupName:`/aws/lambda/${R.functionName}`,startTime:z-60000,filterPattern:'"Init Duration"',limit:25});for(let be of ae.events||[]){let Ae=be.message?.match(/Init Duration:\s*([0-9.]+)\s*ms/i);if(Ae)ie=Number(Ae[1])}}return{frontend:{url:t,status:k.status,sha256:L,unchanged:R.expectedFrontendSha256?L===R.expectedFrontendSha256:void 0,bytes:D.byteLength},api:{url:m,status:Y.status,latencyMs:X,healthy:Y.ok&&!!re?.ok,body:re},coldStart:{observed:ie!==void 0,initDurationMs:ie},verifiedAt:(R.now?.()||new Date).toISOString()}}function Pu(R){let t=(R.memoryMb||256)/1024,m=Math.max(0,R.requests-(R.includeFreeTier===!1?0:1e6)),k=Math.max(0,R.requests*(R.averageDurationMs/1000)*t-(R.includeFreeTier===!1?0:400000)),D=m/1e6*0.2+k*0.0000166667,L=9.010025,z=16.425;return{lambdaUsd:Number(D.toFixed(2)),alwaysOnFargateAndAlbUsd:Number(25.435025000000003 .toFixed(2)),assumptions:["us-east-1 public list rates snapshot, July 2026","Lambda x86 request and duration pricing with optional monthly free tier","Fargate comparison uses one always-on 0.25 vCPU/0.5 GB task plus one ALB before LCU charges","excludes CloudFront, data transfer, logs, taxes, and application data services"]}}function Cl(R){R.command("cdn:api:deploy <distributionId> <alias>","Plan or deploy a private Lambda API behind an existing static distribution").option("--function <name>","Lambda function name",{default:"ts-cloud-static-api"}).option("--path <pattern>","CloudFront API path",{default:"/api/*"}).option("--origin-id <id>","Stable CloudFront origin ID").option("--role <name>","Lambda execution role name").option("--region <region>","Lambda region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").option("--memory <mb>","Lambda memory in MB",{default:"256"}).option("--timeout <seconds>","Lambda timeout",{default:"10"}).option("--retention <days>","CloudWatch log retention",{default:"14"}).option("--apply","Create resources and patch the live distribution").option("--confirm <text>","Exact distribution:path confirmation").action(async(t,m,k)=>{try{let D=await Ru({distributionId:t,expectedAlias:m,functionName:k.function||"ts-cloud-static-api",pathPattern:k.path,originId:k.originId,roleName:k.role,region:k.region,profile:k.profile,memorySize:Number(k.memory),timeout:Number(k.timeout),logRetentionDays:Number(k.retention),apply:!!k.apply,confirm:k.confirm});if(e(JSON.stringify(D,null,2)),D.applied)n("Private API origin submitted to CloudFront. Run cdn:api:verify after propagation.");else e(`Plan only. Re-run with --apply --confirm '${t}:${D.origin.pathPattern}'.`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}}),R.command("cdn:api:verify <alias>","Verify static frontend integrity, API health, latency, and Lambda initialization").option("--function <name>","Lambda function name",{default:"ts-cloud-static-api"}).option("--frontend-sha256 <digest>","Expected SHA-256 of the frontend response").option("--region <region>","Lambda region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").action(async(t,m)=>{try{let k=new se(m.region||"us-east-1",m.profile),D=await Iu({alias:t,expectedFrontendSha256:m.frontendSha256,logs:k,functionName:m.function||"ts-cloud-static-api"});if(e(JSON.stringify(D,null,2)),!D.api.healthy||D.frontend.unchanged===!1)throw Error("Static API verification failed");n("Static frontend and Lambda API verification passed.")}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("cdn:api:cost <requests>","Compare a low-volume Lambda API with one always-on Fargate task and ALB").option("--duration <milliseconds>","Average Lambda duration",{default:"100"}).option("--memory <mb>","Lambda memory in MB",{default:"256"}).option("--no-free-tier","Exclude the Lambda free tier").action((t,m)=>{let k=Pu({requests:Number(t),averageDurationMs:Number(m.duration),memoryMb:Number(m.memory),includeFreeTier:m.freeTier!==!1});e(JSON.stringify(k,null,2))}),R.command("cdn:origin:add <distributionId> <domain>","Safely add a backend origin and path to an existing distribution").option("--id <origin>","Stable origin ID",{default:"api-backend"}).option("--path <pattern>","Path behavior",{default:"/api/*"}).option("--profile <name>","AWS credential profile").option("--replace","Replace a reviewed path/origin collision").option("--apply","Apply the ETag-protected patch").option("--confirm <text>","Exact distribution:path confirmation").action(async(t,m,k)=>{try{let D=new E(k.profile),L={id:k.id??"api-backend",domainName:m,pathPattern:k.path??"/api/*",replaceExisting:!!k.replace},z=await D.upsertExistingDistributionOrigin(t,{...L,dryRun:!0});if(e(JSON.stringify(z,null,2)),!k.apply){e("Dry run only. Re-run with --apply and the exact --confirm token.");return}let Y=`${t}:${z.pathPattern}`;if(k.confirm!==Y)throw Error(`Pass --confirm "${Y}" to apply this live distribution change`);let X=await D.upsertExistingDistributionOrigin(t,L);n(X.changed?"Backend origin patch submitted.":"Backend origin already matches."),e(JSON.stringify(X,null,2))}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}}),R.command("cdn:origin:remove <distributionId> <domain>","Remove one exact backend behavior and its unreferenced origin").option("--id <origin>","Origin ID",{default:"api-backend"}).option("--path <pattern>","Path behavior",{default:"/api/*"}).option("--profile <name>","AWS credential profile").option("--apply","Apply the ETag-protected rollback").option("--confirm <text>","Exact remove:distribution:path confirmation").action(async(t,m,k)=>{try{let D=new E(k.profile),L={id:k.id??"api-backend",domainName:m,pathPattern:k.path??"/api/*"},z=await D.removeExistingDistributionOrigin(t,{...L,dryRun:!0});if(e(JSON.stringify(z,null,2)),!k.apply){e("Dry run only. Re-run with --apply and the exact --confirm token.");return}let Y=`remove:${t}:${z.pathPattern}`;if(k.confirm!==Y)throw Error(`Pass --confirm "${Y}" to apply this live distribution rollback`);let X=await D.removeExistingDistributionOrigin(t,L);n(X.changed?"Backend behavior rollback submitted.":"Backend behavior was already absent."),e(JSON.stringify(X,null,2))}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}}),R.command("cdn:list","List all CloudFront distributions").action(async()=>{i("CloudFront Distributions");try{let t=await Ne(),m=new E,k=new s("Fetching distributions...");k.start();let D=await m.listDistributions();if(k.succeed(`Found ${D.length} distribution(s)`),D.length===0){e("No CloudFront distributions found"),e("Use `cloud cdn:create` to create a new distribution");return}c(["ID","Domain","Status","Enabled","Aliases"],D.map((L)=>[L.Id||"N/A",L.DomainName||"N/A",L.Status||"N/A",L.Enabled?"Yes":"No",(L.Aliases?.Items?.length||0).toString()]))}catch(t){r(`Failed to list distributions: ${t.message}`),process.exit(1)}}),R.command("cdn:status <distributionId>","Get CloudFront distribution status").action(async(t)=>{i(`CloudFront Distribution: ${t}`);try{let m=new E,k=new s("Fetching distribution details...");k.start();let D=await m.getDistribution(t);if(k.succeed("Distribution details loaded"),e(`
|
|
478
|
+
`;function Au(R){let t=R;return String(t?.code||t?.name||t?.statusCode||"")}function Kg(R){return["NoSuchEntity","ResourceNotFoundException","404"].includes(Au(R))}function $u(R){return["EntityAlreadyExists","ResourceAlreadyExistsException","ResourceConflictException","409"].includes(Au(R))}function ku(R,t){let m=R?.Items;if(!m)return[];if(Array.isArray(m))return m;let k=m[t];if(k===void 0)return[];return Array.isArray(k)?k:[k]}function Sl(R){let t=R?.Aliases?.Items,m=t?.CNAME??t;if(Array.isArray(m))return m.map(String);if(typeof m==="string")return[m];return[]}function Jg(R){if(!/^[A-Z0-9]{8,32}$/.test(R.distributionId))throw Error("CloudFront distribution ID is invalid");if(!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9-]{2,63}$/.test(R.expectedAlias))throw Error("Expected CloudFront alias is invalid");if(!/^[A-Za-z0-9-_]{1,64}$/.test(R.functionName))throw Error("Lambda function name is invalid");let t=R.pathPattern?.startsWith("/")?R.pathPattern:`/${R.pathPattern||"api/*"}`;if(t==="/"||t==="/*"||!t.endsWith("*"))throw Error("API path must be a non-default wildcard pattern such as /api/*");let m=R.originId||`${R.functionName}-url`,k=R.roleName||`${R.functionName}-execution`;if(!/^[A-Za-z0-9_.-]{1,64}$/.test(k))throw Error("IAM role name is invalid");let D=R.memorySize??256,L=R.timeout??10,z=R.logRetentionDays??14;if(D<128||D>10240)throw Error("Lambda memory must be between 128 and 10240 MB");if(L<1||L>900)throw Error("Lambda timeout must be between 1 and 900 seconds");return{region:R.region||"us-east-1",pathPattern:t,originId:m,roleName:k,memorySize:D,timeout:L,logRetentionDays:z}}function Yg(R){let t=R.region||"us-east-1";return{cloudfront:new E(R.profile),iam:new te(t,R.profile),lambda:new P(t,R.profile),logs:new se(t,R.profile),sts:new mt(t,R.profile),sleep:(m)=>new Promise((k)=>setTimeout(k,m))}}async function Ru(R,t){let m=Jg(R),k=t||Yg({profile:R.profile,region:m.region}),[D,L,z]=await Promise.all([k.sts.getCallerIdentity(),k.cloudfront.getDistribution(R.distributionId),k.cloudfront.getDistributionConfig(R.distributionId)]);if(!D.Account)throw Error("AWS identity did not return an account ID");if(!L.Enabled)throw Error(`CloudFront distribution ${R.distributionId} is disabled`);if(!(Sl(L).length?Sl(L):Sl(z.DistributionConfig)).includes(R.expectedAlias))throw Error(`Distribution ${R.distributionId} does not contain expected alias ${R.expectedAlias}`);let X,ne=!0;try{X=await k.iam.getRole({RoleName:m.roleName})}catch(Nt){if(!Kg(Nt))throw Nt;ne=!1}let re=X?.Arn||`arn:aws:iam::${D.Account}:role/${m.roleName}`,ie=await k.lambda.functionExists(R.functionName),ae=ie?await k.lambda.getFunctionUrl(R.functionName):null,be=await k.cloudfront.listOriginAccessControls(),Ae=`${R.functionName}-lambda-url`,le=be.some((Nt)=>Nt.Name===Ae&&Nt.OriginAccessControlOriginType==="lambda"),ke=ku(z.DistributionConfig.Origins,"Origin"),De=ku(z.DistributionConfig.CacheBehaviors,"CacheBehavior"),Se=ke.some((Nt)=>String(Nt.Id)===m.originId),Re=De.some((Nt)=>String(Nt.PathPattern)===m.pathPattern),Pe=[...!ne?[`create IAM execution role ${m.roleName}`]:[`reconcile IAM execution policy ${m.roleName}`],...ie?[`update Lambda ${R.functionName} code and configuration`]:[`create Lambda ${R.functionName}`],...!ae?["create private AWS_IAM function URL"]:[],...!le?[`create Lambda origin access control ${Ae}`]:[],"grant CloudFront function-URL and invocation permissions",`${Re?"reconcile":"add"} ${m.pathPattern} behavior without changing the default behavior`],Ve={command:`cloud cdn:origin:remove ${R.distributionId} FUNCTION_URL_HOST --id ${m.originId} --path '${m.pathPattern}' --apply --confirm 'remove:${R.distributionId}:${m.pathPattern}'`,removeFunction:!ie,removeRole:!ne},Le={mode:R.apply?"apply":"plan",accountId:D.Account,identityArn:D.Arn,distribution:{id:L.Id,arn:L.ARN,alias:R.expectedAlias,domainName:L.DomainName,status:L.Status},function:{name:R.functionName,existed:ie,runtime:"nodejs22.x",urlExisted:!!ae,url:ae?.FunctionUrl},role:{name:m.roleName,existed:ne,arn:re},origin:{id:m.originId,pathPattern:m.pathPattern,existed:Se,behaviorExisted:Re,accessControlExisted:le},changes:Pe,rollback:Ve,applied:!1};if(!R.apply)return Le;let Fe=`${R.distributionId}:${m.pathPattern}`;if(R.confirm!==Fe)throw Error(`Pass exact confirmation token ${Fe} to mutate the live distribution`);if(!X)X=await k.iam.createRole({RoleName:m.roleName,Description:`Execution role for ${R.functionName}`,AssumeRolePolicyDocument:JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]}),Tags:[{Key:"ManagedBy",Value:"ts-cloud"}]});await k.iam.putRolePolicy({RoleName:m.roleName,PolicyName:"lambda-logs",PolicyDocument:JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["logs:CreateLogStream","logs:PutLogEvents"],Resource:`arn:aws:logs:${m.region}:${D.Account}:log-group:/aws/lambda/${R.functionName}:*`}]})});try{await k.logs.createLogGroup(`/aws/lambda/${R.functionName}`,{ManagedBy:"ts-cloud"})}catch(Nt){if(!$u(Nt))throw Nt}await k.logs.putRetentionPolicy(`/aws/lambda/${R.functionName}`,m.logRetentionDays);let He=R.code||Gg;if(ie)await k.lambda.updateFunctionCodeInline(R.functionName,He,"index.mjs"),await k.lambda.waitForFunctionActive(R.functionName),await k.lambda.updateFunctionConfiguration({FunctionName:R.functionName,Runtime:"nodejs22.x",Role:X.Arn,Handler:"index.handler",Description:`Private API origin for ${R.expectedAlias}`,Timeout:m.timeout,MemorySize:m.memorySize});else{let Nt;for(let Ye=0;Ye<8;Ye++)try{await k.lambda.createFunctionWithCode({FunctionName:R.functionName,Runtime:"nodejs22.x",Role:X.Arn,Handler:"index.handler",Code:He,Filename:"index.mjs",Description:`Private API origin for ${R.expectedAlias}`,Timeout:m.timeout,MemorySize:m.memorySize}),Nt=void 0;break}catch(_t){if(Nt=_t,!String(_t.message).toLowerCase().includes("role"))throw _t;await k.sleep(1000)}if(Nt)throw Nt}await k.lambda.waitForFunctionActive(R.functionName);let wt=ae||await k.lambda.createFunctionUrl({FunctionName:R.functionName,AuthType:"AWS_IAM",InvokeMode:"BUFFERED"});if(!wt.FunctionUrl)throw Error("Lambda did not return a function URL");let Dt=new URL(wt.FunctionUrl).hostname,it=await k.cloudfront.findOrCreateOriginAccessControl(Ae,"lambda"),xt=L.ARN||`arn:aws:cloudfront::${D.Account}:distribution/${R.distributionId}`,Zt=[{FunctionName:R.functionName,StatementId:"AllowCloudFrontFunctionUrl",Action:"lambda:InvokeFunctionUrl",Principal:"cloudfront.amazonaws.com",SourceArn:xt,FunctionUrlAuthType:"AWS_IAM"},{FunctionName:R.functionName,StatementId:"AllowCloudFrontInvoke",Action:"lambda:InvokeFunction",Principal:"cloudfront.amazonaws.com",SourceArn:xt,InvokedViaFunctionUrl:!0}];for(let Nt of Zt)try{await k.lambda.addPermission(Nt)}catch(Ye){if(!$u(Ye))throw Ye}return await k.cloudfront.upsertExistingDistributionOrigin(R.distributionId,{id:m.originId,domainName:Dt,pathPattern:m.pathPattern,originAccessControlId:it.Id}),{...Le,function:{...Le.function,url:wt.FunctionUrl},applied:!0}}async function Iu(R){let t=`https://${R.alias}/`,m=`https://${R.alias}/api/health`,k=await fetch(t,{redirect:"follow",headers:{"cache-control":"no-cache"}}),D=new Uint8Array(await k.arrayBuffer()),L=Hg("sha256").update(D).digest("hex"),z=Date.now(),Y=await fetch(m,{redirect:"follow",headers:{"cache-control":"no-cache"}}),X=Date.now()-z,ne=await Y.text(),re=ne;try{re=JSON.parse(ne)}catch{}let ie;if(R.logs&&R.functionName){let ae=await R.logs.filterLogEvents({logGroupName:`/aws/lambda/${R.functionName}`,startTime:z-60000,filterPattern:'"Init Duration"',limit:25});for(let be of ae.events||[]){let Ae=be.message?.match(/Init Duration:\s*([0-9.]+)\s*ms/i);if(Ae)ie=Number(Ae[1])}}return{frontend:{url:t,status:k.status,sha256:L,unchanged:R.expectedFrontendSha256?L===R.expectedFrontendSha256:void 0,bytes:D.byteLength},api:{url:m,status:Y.status,latencyMs:X,healthy:Y.ok&&!!re?.ok,body:re},coldStart:{observed:ie!==void 0,initDurationMs:ie},verifiedAt:(R.now?.()||new Date).toISOString()}}function Pu(R){let t=(R.memoryMb||256)/1024,m=Math.max(0,R.requests-(R.includeFreeTier===!1?0:1e6)),k=Math.max(0,R.requests*(R.averageDurationMs/1000)*t-(R.includeFreeTier===!1?0:400000)),D=m/1e6*0.2+k*0.0000166667,L=9.010025,z=16.425;return{lambdaUsd:Number(D.toFixed(2)),alwaysOnFargateAndAlbUsd:Number(25.435025000000003 .toFixed(2)),assumptions:["us-east-1 public list rates snapshot, July 2026","Lambda x86 request and duration pricing with optional monthly free tier","Fargate comparison uses one always-on 0.25 vCPU/0.5 GB task plus one ALB before LCU charges","excludes CloudFront, data transfer, logs, taxes, and application data services"]}}function Cl(R){R.command("cdn:api:deploy <distributionId> <alias>","Plan or deploy a private Lambda API behind an existing static distribution").option("--function <name>","Lambda function name",{default:"ts-cloud-static-api"}).option("--path <pattern>","CloudFront API path",{default:"/api/*"}).option("--origin-id <id>","Stable CloudFront origin ID").option("--role <name>","Lambda execution role name").option("--region <region>","Lambda region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").option("--memory <mb>","Lambda memory in MB",{default:"256"}).option("--timeout <seconds>","Lambda timeout",{default:"10"}).option("--retention <days>","CloudWatch log retention",{default:"14"}).option("--apply","Create resources and patch the live distribution").option("--confirm <text>","Exact distribution:path confirmation").action(async(t,m,k)=>{try{let D=await Ru({distributionId:t,expectedAlias:m,functionName:k.function||"ts-cloud-static-api",pathPattern:k.path,originId:k.originId,roleName:k.role,region:k.region,profile:k.profile,memorySize:Number(k.memory),timeout:Number(k.timeout),logRetentionDays:Number(k.retention),apply:!!k.apply,confirm:k.confirm});if(e(JSON.stringify(D,null,2)),D.applied)n("Private API origin submitted to CloudFront. Run cdn:api:verify after propagation.");else e(`Plan only. Re-run with --apply --confirm '${t}:${D.origin.pathPattern}'.`)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}}),R.command("cdn:api:verify <alias>","Verify static frontend integrity, API health, latency, and Lambda initialization").option("--function <name>","Lambda function name",{default:"ts-cloud-static-api"}).option("--frontend-sha256 <digest>","Expected SHA-256 of the frontend response").option("--region <region>","Lambda region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").action(async(t,m)=>{try{let k=new se(m.region||"us-east-1",m.profile),D=await Iu({alias:t,expectedFrontendSha256:m.frontendSha256,logs:k,functionName:m.function||"ts-cloud-static-api"});if(e(JSON.stringify(D,null,2)),!D.api.healthy||D.frontend.unchanged===!1)throw Error("Static API verification failed");n("Static frontend and Lambda API verification passed.")}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("cdn:api:cost <requests>","Compare a low-volume Lambda API with one always-on Fargate task and ALB").option("--duration <milliseconds>","Average Lambda duration",{default:"100"}).option("--memory <mb>","Lambda memory in MB",{default:"256"}).option("--no-free-tier","Exclude the Lambda free tier").action((t,m)=>{let k=Pu({requests:Number(t),averageDurationMs:Number(m.duration),memoryMb:Number(m.memory),includeFreeTier:m.freeTier!==!1});e(JSON.stringify(k,null,2))}),R.command("cdn:origin:add <distributionId> <domain>","Safely add a backend origin and path to an existing distribution").option("--id <origin>","Stable origin ID",{default:"api-backend"}).option("--path <pattern>","Path behavior",{default:"/api/*"}).option("--profile <name>","AWS credential profile").option("--replace","Replace a reviewed path/origin collision").option("--apply","Apply the ETag-protected patch").option("--confirm <text>","Exact distribution:path confirmation").action(async(t,m,k)=>{try{let D=new E(k.profile),L={id:k.id??"api-backend",domainName:m,pathPattern:k.path??"/api/*",replaceExisting:!!k.replace},z=await D.upsertExistingDistributionOrigin(t,{...L,dryRun:!0});if(e(JSON.stringify(z,null,2)),!k.apply){e("Dry run only. Re-run with --apply and the exact --confirm token.");return}let Y=`${t}:${z.pathPattern}`;if(k.confirm!==Y)throw Error(`Pass --confirm "${Y}" to apply this live distribution change`);let X=await D.upsertExistingDistributionOrigin(t,L);n(X.changed?"Backend origin patch submitted.":"Backend origin already matches."),e(JSON.stringify(X,null,2))}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}}),R.command("cdn:origin:remove <distributionId> <domain>","Remove one exact backend behavior and its unreferenced origin").option("--id <origin>","Origin ID",{default:"api-backend"}).option("--path <pattern>","Path behavior",{default:"/api/*"}).option("--profile <name>","AWS credential profile").option("--apply","Apply the ETag-protected rollback").option("--confirm <text>","Exact remove:distribution:path confirmation").action(async(t,m,k)=>{try{let D=new E(k.profile),L={id:k.id??"api-backend",domainName:m,pathPattern:k.path??"/api/*"},z=await D.removeExistingDistributionOrigin(t,{...L,dryRun:!0});if(e(JSON.stringify(z,null,2)),!k.apply){e("Dry run only. Re-run with --apply and the exact --confirm token.");return}let Y=`remove:${t}:${z.pathPattern}`;if(k.confirm!==Y)throw Error(`Pass --confirm "${Y}" to apply this live distribution rollback`);let X=await D.removeExistingDistributionOrigin(t,L);n(X.changed?"Backend behavior rollback submitted.":"Backend behavior was already absent."),e(JSON.stringify(X,null,2))}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}}),R.command("cdn:list","List all CloudFront distributions").action(async()=>{i("CloudFront Distributions");try{let t=await Ne(),m=new E,k=new s("Fetching distributions...");k.start();let D=await m.listDistributions();if(k.succeed(`Found ${D.length} distribution(s)`),D.length===0){e("No CloudFront distributions found"),e("Use `cloud cdn:create` to create a new distribution");return}c(["ID","Domain","Status","Enabled","Aliases"],D.map((L)=>[L.Id||"N/A",L.DomainName||"N/A",L.Status||"N/A",L.Enabled?"Yes":"No",(L.Aliases?.Items?.length||0).toString()]))}catch(t){r(`Failed to list distributions: ${t.message}`),process.exit(1)}}),R.command("cdn:status <distributionId>","Get CloudFront distribution status").action(async(t)=>{i(`CloudFront Distribution: ${t}`);try{let m=new E,k=new s("Fetching distribution details...");k.start();let D=await m.getDistribution(t);if(k.succeed("Distribution details loaded"),e(`
|
|
479
479
|
Distribution Information:`),e(` ID: ${D.Id}`),e(` Domain: ${D.DomainName}`),e(` Status: ${D.Status}`),e(` ARN: ${D.ARN}`),e(` Enabled: ${D.Enabled?"Yes":"No"}`),D.Aliases?.Items?.length)e(` Aliases: ${D.Aliases.Items.join(", ")}`);let L=await m.getDistributionConfig(t);e(`
|
|
480
480
|
Origins:`);for(let z of x(L.DistributionConfig?.Origins?.Items?.Origin))e(` - ${z.Id}: ${z.DomainName}`);if(L.DistributionConfig?.DefaultCacheBehavior){let z=L.DistributionConfig.DefaultCacheBehavior;e(`
|
|
481
481
|
Default Cache Behavior:`),e(` Target Origin: ${z.TargetOriginId}`),e(` Viewer Protocol: ${z.ViewerProtocolPolicy}`)}}catch(m){r(`Failed to get distribution: ${m.message}`),process.exit(1)}}),R.command("cdn:create","Create a new CloudFront distribution for an S3 bucket").option("--bucket <name>","S3 bucket name").option("--region <region>","S3 bucket region",{default:"us-east-1"}).option("--alias <domain>","Custom domain alias (CNAME)").option("--certificate <arn>","ACM certificate ARN for custom domain").option("--comment <text>","Distribution comment/description").action(async(t)=>{i("Create CloudFront Distribution");try{if(!t.bucket)r("--bucket is required"),e("Example: cloud cdn:create --bucket my-bucket --region us-east-1"),process.exit(1);let m=new E;if(e(`Bucket: ${t.bucket}`),e(`Region: ${t.region}`),t.alias)e(`Alias: ${t.alias}`);if(!await d(`
|
|
@@ -488,7 +488,7 @@ Invalidation ID: ${Y.Id}`),e(`Status: ${Y.Status}`),e(`
|
|
|
488
488
|
Note: Invalidation typically completes in 5-10 minutes.`)}catch(k){r(`Failed to create invalidation: ${k.message}`),process.exit(1)}}),R.command("cdn:disable <distributionId>","Disable a CloudFront distribution").action(async(t)=>{i("Disable CloudFront Distribution");try{let m=new E;if(a(`This will disable distribution: ${t}`),e("The distribution will stop serving content."),!await d(`
|
|
489
489
|
Disable this distribution?`,!1)){e("Operation cancelled");return}let D=new s("Disabling distribution...");D.start(),await m.disableDistribution(t),D.succeed("Distribution disabled"),e(`
|
|
490
490
|
Note: Changes may take 15-30 minutes to propagate.`),e("To delete the distribution, wait for it to be fully disabled first.")}catch(m){r(`Failed to disable distribution: ${m.message}`),process.exit(1)}}),R.command("cdn:delete <distributionId>","Delete a CloudFront distribution").action(async(t)=>{i("Delete CloudFront Distribution");try{let m=new E;if(a(`This will permanently delete distribution: ${t}`),a("The distribution must be disabled first."),!await d(`
|
|
491
|
-
Delete this distribution?`,!1)){e("Operation cancelled");return}let D=new s("Checking distribution status...");D.start();let L=await m.getDistribution(t);if(L.Status!=="Deployed"||L.Enabled){D.fail("Distribution must be disabled and deployed before deletion"),e('\nRun `cloud cdn:disable` first and wait for status to be "Deployed"');return}D.text="Deleting distribution...",await m.deleteDistribution(t),D.succeed("Distribution deleted")}catch(m){r(`Failed to delete distribution: ${m.message}`),process.exit(1)}})}import{readdirSync as Zg,statSync as Qg}from"node:fs";import{join as xu,relative as Xg}from"node:path";async function jr(R){let t=await Ne(),m=y(process.cwd(),t),k=R??Object.keys(t.environments??{})[0]??"production",D=m.environments.get(k);if(!D)throw m.store.close(),Error(`Environment ${k} was not found.`);let L=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),z=new yt(m.store),Y=new
|
|
491
|
+
Delete this distribution?`,!1)){e("Operation cancelled");return}let D=new s("Checking distribution status...");D.start();let L=await m.getDistribution(t);if(L.Status!=="Deployed"||L.Enabled){D.fail("Distribution must be disabled and deployed before deletion"),e('\nRun `cloud cdn:disable` first and wait for status to be "Deployed"');return}D.text="Deleting distribution...",await m.deleteDistribution(t),D.succeed("Distribution deleted")}catch(m){r(`Failed to delete distribution: ${m.message}`),process.exit(1)}})}import{readdirSync as Zg,statSync as Qg}from"node:fs";import{join as xu,relative as Xg}from"node:path";async function jr(R){let t=await Ne(),m=y(process.cwd(),t),k=R??Object.keys(t.environments??{})[0]??"production",D=m.environments.get(k);if(!D)throw m.store.close(),Error(`Environment ${k} was not found.`);let L=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),z=new yt(m.store),Y=new dn(z,[new Bt,new un(xu(process.cwd(),".ts-cloud","volumes"))]);return{controlPlane:m,environmentRecord:D,actor:L,store:z,service:Y}}function eh(R){return R.map((t)=>[t.name,`${t.provider}/${t.type}`,t.status,t.capacityBytes==null?"—":Es(t.capacityBytes),t.usedBytes==null?"—":Es(t.usedBytes),String(t.attachments.filter((m)=>m.observedState==="attached").length),t.backupState,t.orphaned?"orphan":"managed"])}function $l(R){R.command("storage:list","List all S3 buckets").option("--region <region>","AWS region").action(async(m)=>{i("S3 Buckets");try{let k=await Ne(),D=m.region||k.project.region||"us-east-1",L=new g(D),z=new s("Fetching buckets...");z.start();let X=(await L.listBuckets()).Buckets||[];if(z.succeed(`Found ${X.length} bucket(s)`),X.length===0){e("No S3 buckets found"),e("Use `cloud storage:create` to create a new bucket");return}c(["Name","Created"],X.map((ne)=>[ne.Name||"N/A",ne.CreationDate?new Date(ne.CreationDate).toLocaleDateString():"N/A"]))}catch(k){r(`Failed to list buckets: ${k.message}`),process.exit(1)}}),R.command("storage:create <name>","Create a new S3 bucket").option("--region <region>","AWS region",{default:"us-east-1"}).option("--public","Enable public access").option("--versioning","Enable versioning").action(async(m,k)=>{i("Create S3 Bucket");try{let D=new g(k.region);if(e(`Bucket name: ${m}`),e(`Region: ${k.region}`),e(`Public access: ${k.public?"Yes":"No"}`),e(`Versioning: ${k.versioning?"Yes":"No"}`),!await d(`
|
|
492
492
|
Create this bucket?`,!0)){e("Operation cancelled");return}let z=new s("Creating bucket...");if(z.start(),await D.createBucket(m),!k.public)z.text="Configuring public access block...",await D.putPublicAccessBlock(m,{BlockPublicAcls:!0,IgnorePublicAcls:!0,BlockPublicPolicy:!0,RestrictPublicBuckets:!0});if(k.versioning)z.text="Enabling versioning...",await D.putBucketVersioning(m,"Enabled");z.succeed("Bucket created"),n(`
|
|
493
493
|
Bucket: ${m}`),e(`Region: ${k.region}`),e(`URL: s3://${m}`)}catch(D){r(`Failed to create bucket: ${D.message}`),process.exit(1)}}),R.command("storage:delete <name>","Delete an S3 bucket").option("--force","Delete all objects first").action(async(m,k)=>{i("Delete S3 Bucket");try{let D=new g("us-east-1");if(a(`This will permanently delete bucket: ${m}`),k.force)a("All objects in the bucket will be deleted!");if(!await d(`
|
|
494
494
|
Delete this bucket?`,!1)){e("Operation cancelled");return}let z=new s("Deleting bucket...");if(z.start(),k.force)z.text="Emptying bucket...",await D.emptyBucket(m);await D.deleteBucket(m),z.succeed("Bucket deleted")}catch(D){r(`Failed to delete bucket: ${D.message}`),process.exit(1)}}),R.command("storage:sync <source> <bucket>","Sync local directory to S3 bucket").option("--prefix <prefix>","S3 key prefix").option("--delete","Delete files in S3 that are not in source").option("--dry-run","Show what would be synced without making changes").action(async(m,k,D)=>{i("Sync to S3");try{let X=function(ae,be){let Ae=Zg(ae,{withFileTypes:!0});for(let le of Ae){let ke=xu(ae,le.name);if(le.isDirectory())X(ke,be);else{let De=Xg(be,ke),Se=D.prefix?`${D.prefix}/${De}`:De,Re=Qg(ke);Y.push({path:ke,key:Se,size:Re.size})}}},L=new g("us-east-1");if(e(`Source: ${m}`),e(`Destination: s3://${k}/${D.prefix||""}`),D.dryRun)e("Dry run mode - no changes will be made");let z=new s("Scanning files...");z.start();let Y=[];if(X(m,m),z.succeed(`Found ${Y.length} local file(s)`),Y.length===0){e("No files to sync");return}e(`
|
|
@@ -500,10 +500,10 @@ Delete bucket policy?`,!1)){e("Operation cancelled");return}let Y=new s("Deletin
|
|
|
500
500
|
Set public read policy?`,!1)){e("Operation cancelled");return}let Y=new s("Setting policy...");Y.start();let X={Version:"2012-10-17",Statement:[{Sid:"PublicReadGetObject",Effect:"Allow",Principal:"*",Action:"s3:GetObject",Resource:`arn:aws:s3:::${m}/*`}]};await D.putBucketPolicy(m,X),Y.succeed("Public read policy set");return}if(k.set){let z=new s("Setting policy...");z.start();let X=await Bun.file(k.set).text();await D.putBucketPolicy(m,X),z.succeed("Policy set");return}let L=new s("Fetching policy...");L.start();try{let z=await D.getBucketPolicy(m);L.succeed("Policy loaded"),e(`
|
|
501
501
|
Bucket Policy:`),console.log(JSON.stringify(z||{},null,2))}catch(z){if(z.message?.includes("NoSuchBucketPolicy"))L.succeed("No policy set"),e("This bucket has no policy configured.");else throw z}}catch(D){r(`Failed to manage policy: ${D.message}`),process.exit(1)}}),R.command("storage:ls <bucket>","List objects in a bucket").option("--prefix <prefix>","Filter by prefix").option("--limit <number>","Limit number of results",{default:"100"}).action(async(m,k)=>{i(`Objects in ${m}`);try{let D=new g("us-east-1"),L=new s("Listing objects...");L.start();let z=await D.listObjects({bucket:m,prefix:k.prefix,maxKeys:Number.parseInt(k.limit||"100")}),Y=z.objects;if(L.succeed(`Found ${Y.length} object(s)${z.nextContinuationToken?" (truncated)":""}`),Y.length===0){e("No objects found");return}if(c(["Key","Size","Last Modified"],Y.map((X)=>[X.Key||"N/A",Es(X.Size||0),X.LastModified?new Date(X.LastModified).toLocaleString():"N/A"])),z.nextContinuationToken)e(`
|
|
502
502
|
More objects available. Use --limit to see more.`)}catch(D){r(`Failed to list objects: ${D.message}`),process.exit(1)}});let t=(m,k)=>R.command(m,k).option("--env <environment>","Dashboard environment");t("volume:list","List persistent volumes, consumers, usage, and protection").option("--json","Print structured JSON").action(async(m)=>{let k=await jr(m.env);try{let D=k.store.inventory(k.controlPlane.project.id,k.environmentRecord.id);if(m.json)console.log(JSON.stringify(D,null,2));else c(["Name","Driver","Status","Capacity","Used","Consumers","Backup","Ownership"],eh(D))}catch(D){r(D instanceof Error?D.message:String(D))}finally{k.controlPlane.store.close()}}),t("volume:show <name>","Inspect a persistent volume and its dependency graph").option("--json","Print structured JSON").action(async(m,k)=>{let D=await jr(k.env);try{let L=D.store.inventory(D.controlPlane.project.id,D.environmentRecord.id).find((z)=>z.id===m||z.name===m);if(!L)throw Error(`Volume ${m} was not found.`);if(k.json)console.log(JSON.stringify(L,null,2));else c(["Property","Value"],[["ID",L.id],["Driver",`${L.provider}/${L.type}`],["Provider ID",L.providerId??"—"],["Status",L.status],["Capacity",L.capacityBytes==null?"—":Es(L.capacityBytes)],["Used",L.usedBytes==null?"—":Es(L.usedBytes)],["Backup",L.backupState]]),c(["Resource","Target","Mode","State"],L.attachments.map((z)=>[z.resourceId,z.targetPath,z.readOnly?"read only":"read/write",z.observedState]))}catch(L){r(L instanceof Error?L.message:String(L))}finally{D.controlPlane.store.close()}}),t("volume:create <name>","Create a stable named or managed-path volume").option("--driver <driver>","docker or server",{default:"docker"}).option("--capacity-bytes <bytes>","Advisory capacity in bytes").option("--encrypted","Request provider encryption").action(async(m,k)=>{let D=await jr(k.env);try{let L=k.driver==="server";if(!L&&k.driver!=="docker")throw Error("--driver must be docker or server.");let z=D.service.create({organizationId:D.controlPlane.organization.id,projectId:D.controlPlane.project.id,environmentId:D.environmentRecord.id,name:m,provider:L?"server":"docker",type:L?"server_path":"docker",capacityBytes:k.capacityBytes?Number(k.capacityBytes):void 0,encrypted:!!k.encrypted,actorId:D.actor.id});n(`Volume create queued: ${z.operation.id}`)}catch(L){r(L instanceof Error?L.message:String(L))}finally{D.controlPlane.store.close()}}),t("volume:attach <volume> <resource> <target>","Validate and attach a volume to a workload").option("--read-only","Mount read only").option("--uid <uid>","Filesystem UID").option("--gid <gid>","Filesystem GID").option("--mode <mode>","Octal mode").action(async(m,k,D,L)=>{let z=await jr(L.env);try{let Y=z.store.list({projectId:z.controlPlane.project.id,environmentId:z.environmentRecord.id}).find((re)=>re.id===m||re.name===m),X=z.controlPlane.store.listResources(z.controlPlane.project.id,z.environmentRecord.id).find((re)=>re.id===k||re.slug===k);if(!Y||!X)throw Error("Volume or resource was not found in the selected environment.");let ne=z.service.attach(Y.id,{resourceId:X.id,targetPath:D,readOnly:!!L.readOnly,uid:L.uid?Number(L.uid):void 0,gid:L.gid?Number(L.gid):void 0,mode:L.mode,actorId:z.actor.id});n(`Volume attachment queued: ${ne.operation.id}`)}catch(Y){r(Y instanceof Error?Y.message:String(Y))}finally{z.controlPlane.store.close()}}),t("volume:detach <volume> <attachment>","Drain, unmount, and detach a volume consumer").option("--force","Force after exact confirmation").option("--confirm <text>","Exact force-detach confirmation").action(async(m,k,D)=>{let L=await jr(D.env);try{let z=L.store.list({projectId:L.controlPlane.project.id,environmentId:L.environmentRecord.id}).find((X)=>X.id===m||X.name===m);if(!z)throw Error("Volume was not found.");let Y=L.service.detach(z.id,k,{drained:!0,unmounted:!0,force:!!D.force,confirmation:D.confirm,actorId:L.actor.id});n(`Volume detach queued: ${Y.operation.id}`)}catch(z){r(z instanceof Error?z.message:String(z))}finally{L.controlPlane.store.close()}}),t("volume:resize <volume> <bytes>","Grow a capable persistent volume").option("--drained","Confirm offline workload drain").action(async(m,k,D)=>{let L=await jr(D.env);try{let z=L.store.list({projectId:L.controlPlane.project.id,environmentId:L.environmentRecord.id}).find((X)=>X.id===m||X.name===m);if(!z)throw Error("Volume was not found.");let Y=L.service.resize(z.id,Number(k),{drained:!!D.drained,actorId:L.actor.id});n(`Volume resize queued: ${Y.operation.id}`)}catch(z){r(z instanceof Error?z.message:String(z))}finally{L.controlPlane.store.close()}}),t("volume:snapshot <volume>","Create a provider snapshot when supported").action(async(m,k)=>{let D=await jr(k.env);try{let L=D.store.list({projectId:D.controlPlane.project.id,environmentId:D.environmentRecord.id}).find((Y)=>Y.id===m||Y.name===m);if(!L)throw Error("Volume was not found.");let z=D.service.snapshot(L.id,{actorId:D.actor.id});n(`Volume snapshot queued: ${z.operation.id}`)}catch(L){r(L instanceof Error?L.message:String(L))}finally{D.controlPlane.store.close()}}),t("volume:restore <snapshot> <name>","Restore a snapshot into a replacement volume").action(async(m,k,D)=>{let L=await jr(D.env);try{let z=L.service.restore(m,{name:k,actorId:L.actor.id});n(`Volume restore queued: ${z.operation.id}`)}catch(z){r(z instanceof Error?z.message:String(z))}finally{L.controlPlane.store.close()}}),t("volume:discover","Discover provider volumes without adopting or deleting them").action(async(m)=>{let k=await jr(m.env);try{let D=await k.service.discover({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,environmentId:k.environmentRecord.id,actorId:k.actor.id});n(`Discovered ${D.filter((L)=>L.status==="orphaned").length} orphan volume(s).`)}catch(D){r(D instanceof Error?D.message:String(D))}finally{k.controlPlane.store.close()}}),t("volume:adopt <volume> <name>","Adopt a discovered orphan under a managed name").action(async(m,k,D)=>{let L=await jr(D.env);try{L.service.adopt(m,{name:k,actorId:L.actor.id}),n(`Adopted ${k}.`)}catch(z){r(z instanceof Error?z.message:String(z))}finally{L.controlPlane.store.close()}}),t("volume:delete <volume>","Permanently delete a detached volume").option("--confirm <name>","Exact volume name").option("--without-backup","Explicitly override the recent-backup gate").option("--backup-confirm <text>","Exact backup override confirmation").action(async(m,k)=>{let D=await jr(k.env);try{let L=D.store.list({projectId:D.controlPlane.project.id,environmentId:D.environmentRecord.id}).find((Y)=>Y.id===m||Y.name===m);if(!L)throw Error("Volume was not found.");let z=D.service.delete(L.id,{recentAuthAt:new Date().toISOString(),confirmation:k.confirm,backupOverride:!!k.withoutBackup,backupOverrideConfirmation:k.backupConfirm,actorId:D.actor.id});n(`Volume deletion queued: ${z.operation.id}`)}catch(L){r(L instanceof Error?L.message:String(L))}finally{D.controlPlane.store.close()}})}function th(R){let t=R.split(".").pop()?.toLowerCase();return{html:"text/html",css:"text/css",js:"application/javascript",json:"application/json",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",svg:"image/svg+xml",ico:"image/x-icon",woff:"font/woff",woff2:"font/woff2",ttf:"font/ttf",eot:"application/vnd.ms-fontobject",pdf:"application/pdf",zip:"application/zip",xml:"application/xml",txt:"text/plain",md:"text/markdown"}[t||""]||"application/octet-stream"}function Es(R){if(R===0)return"0 B";let t=1024,m=["B","KB","MB","GB","TB"],k=Math.floor(Math.log(R)/Math.log(t));return`${Number.parseFloat((R/t**k).toFixed(2))} ${m[k]}`}var Du=["aws","backblaze","hetzner"];function Eu(R,t){let m=R.indexOf(":");if(m===-1)throw Error(`--${t} must be in the form <provider:bucket> (e.g. aws:my-bucket), got "${R}"`);let k=R.slice(0,m),D=R.slice(m+1);if(!Du.includes(k))throw Error(`Unknown provider "${k}" for --${t}. Supported: ${Du.join(", ")}`);if(!D)throw Error(`--${t} is missing a bucket name (expected <provider:bucket>)`);return{provider:k,bucket:D}}function Tu(R){if(!R)return;let t=R.split(",").map((m)=>m.trim()).filter(Boolean);return t.length>0?t:void 0}function kl(R){R.command("migrate:storage","Migrate object-storage data between providers (AWS S3 ↔ Hetzner ↔ Backblaze)").option("--from <provider:bucket>","Source endpoint, e.g. aws:stacks-production-email").option("--to <provider:bucket>","Destination endpoint, e.g. hetzner:stacks-mail").option("--from-region <region>","Source region/location (default: provider default)").option("--to-region <region>","Destination region/location (default: provider default)").option("--from-endpoint <host>","Source endpoint host override (no scheme)").option("--to-endpoint <host>","Destination endpoint host override (no scheme)").option("--from-prefix <prefix>","Only read source keys under this prefix (stripped from dest key)").option("--to-prefix <prefix>","Prepend this prefix to every destination key").option("--include <csv>","Only copy keys under these comma-separated prefixes").option("--exclude <csv>","Skip keys under these comma-separated prefixes").option("--dry-run","Print the plan (what would copy / what is excluded) without writing").option("--force","Re-copy even if the destination already has an object of the same size").option("--delete-extraneous","Delete destination keys not present in the source (default OFF)").option("--concurrency <n>","Max concurrent copies",{default:"8"}).option("--verify","After copying, re-list the destination and assert object count + sizes match").action(async(t)=>{i("Migrate Object Storage");try{if(!t.from||!t.to)throw Error("Both --from <provider:bucket> and --to <provider:bucket> are required");let m=Eu(t.from,"from"),k=Eu(t.to,"to"),D=Tu(t.include),L=Tu(t.exclude),z=Number.parseInt(t.concurrency||"8",10),Y={provider:m.provider,bucket:m.bucket,region:t.fromRegion,endpoint:t.fromEndpoint,prefix:t.fromPrefix},X={provider:k.provider,bucket:k.bucket,region:t.toRegion,endpoint:t.toEndpoint,prefix:t.toPrefix};if(e(`Source: ${m.provider}:${m.bucket}${t.fromPrefix?`/${t.fromPrefix}`:""}`),e(`Destination: ${k.provider}:${k.bucket}${t.toPrefix?`/${t.toPrefix}`:""}`),D)e(`Include: ${D.join(", ")}`);if(L)e(`Exclude: ${L.join(", ")}`);if(e(`Concurrency: ${z}`),t.dryRun)a("Dry run — no objects will be written");if(t.deleteExtraneous&&!t.dryRun)a("Delete-extraneous ON — destination keys not in the source will be removed");let ne=new s("Listing source objects...");ne.start();let re=0,ie=await ds({from:Y,to:X,include:D,exclude:L,dryRun:t.dryRun,force:t.force,deleteExtraneous:t.deleteExtraneous,concurrency:z,verify:t.verify,onProgress:(ae)=>{if(ae.index-re>=1||ae.index===ae.total){re=ae.index;let be=ae.action==="planned"?"plan":ae.action;ne.text=`[${ae.index}/${ae.total}] ${be}: ${ae.key}`}}});if(ne.succeed(t.dryRun?"Plan ready":"Migration complete"),t.dryRun&&ie.plan){e(`
|
|
503
|
-
Would copy ${ie.plan.length} object(s):`);for(let ae of ie.plan.slice(0,50))e(` ${ae.key} -> ${ae.destKey} (${
|
|
503
|
+
Would copy ${ie.plan.length} object(s):`);for(let ae of ie.plan.slice(0,50))e(` ${ae.key} -> ${ae.destKey} (${hn(ae.size)})`);if(ie.plan.length>50)e(` ... and ${ie.plan.length-50} more`)}if(ie.excludedKeys.length>0){e(`
|
|
504
504
|
EXCLUDED ${ie.excludedKeys.length} key(s) (deliberately not migrated):`);for(let ae of ie.excludedKeys.slice(0,50))e(` ${ae}`);if(ie.excludedKeys.length>50)e(` ... and ${ie.excludedKeys.length-50} more`)}if(ie.deleted.length>0){a(`
|
|
505
505
|
Deleted ${ie.deleted.length} extraneous destination key(s):`);for(let ae of ie.deleted.slice(0,50))a(` ${ae}`)}if(e(`
|
|
506
|
-
Summary:`),t.dryRun)e(` would copy: ${ie.plan?.length??0}`);else n(` copied: ${ie.copied} (${
|
|
506
|
+
Summary:`),t.dryRun)e(` would copy: ${ie.plan?.length??0}`);else n(` copied: ${ie.copied} (${hn(ie.bytesCopied)})`),e(` skipped: ${ie.skipped} (already present)`);if(e(` excluded: ${ie.excluded}`),ie.deleted.length>0)e(` deleted: ${ie.deleted.length}`);if(ie.errors.length>0){r(` errors: ${ie.errors.length}`);for(let ae of ie.errors.slice(0,20))r(` ${ae.key}: ${ae.message}`)}if(ie.verification){let ae=ie.verification;if(ae.ok)n(`
|
|
507
507
|
Verification PASSED — ${ae.matched} object(s) present at destination with matching sizes`);else{r(`
|
|
508
508
|
Verification FAILED — ${ae.missing.length} missing, ${ae.sizeMismatches.length} size mismatch(es)`);for(let be of ae.missing.slice(0,20))r(` missing: ${be}`);for(let be of ae.sizeMismatches.slice(0,20))r(` size: ${be.key} expected ${be.expected} got ${be.actual}`)}}if(ie.errors.length>0||ie.verification?.ok===!1)process.exit(1)}catch(m){r(`Migration failed: ${m.message}`),process.exit(1)}})}function Al(R){R.command("cache:list","List all ElastiCache clusters").option("--region <region>","AWS region").action(async(t)=>{i("ElastiCache Clusters");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",D=new oe(k),L=new s("Fetching clusters...");L.start();let Y=(await D.describeCacheClusters()).CacheClusters||[];if(L.succeed(`Found ${Y.length} cluster(s)`),Y.length===0){e("No ElastiCache clusters found"),e("Use `cloud cache:create` to create a new cluster");return}c(["Cluster ID","Engine","Node Type","Nodes","Status"],Y.map((X)=>[X.CacheClusterId||"N/A",`${X.Engine||"N/A"} ${X.EngineVersion||""}`,X.CacheNodeType||"N/A",(X.NumCacheNodes||0).toString(),X.CacheClusterStatus||"N/A"]))}catch(m){r(`Failed to list clusters: ${m.message}`),process.exit(1)}}),R.command("cache:create <name>","Create a new ElastiCache cluster").option("--engine <engine>","Cache engine (redis or memcached)",{default:"redis"}).option("--node-type <type>","Node instance type",{default:"cache.t3.micro"}).option("--nodes <number>","Number of cache nodes",{default:"1"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--version <version>","Engine version").action(async(t,m)=>{i("Create ElastiCache Cluster");try{let k=new oe(m.region);if(e(`Cluster ID: ${t}`),e(`Engine: ${m.engine}`),e(`Node Type: ${m.nodeType}`),e(`Number of Nodes: ${m.nodes}`),e(`Region: ${m.region}`),!await d(`
|
|
509
509
|
Create this cluster?`,!0)){e("Operation cancelled");return}let L=new s("Creating cluster...");L.start(),await k.createCacheCluster({cacheClusterId:t,engine:m.engine,cacheNodeType:m.nodeType,numCacheNodes:Number.parseInt(m.nodes),engineVersion:m.version}),L.succeed("Cluster creation initiated"),n(`
|
|
@@ -614,9 +614,9 @@ Attached Policies:`);for(let X of z.AttachedPolicies)e(` - ${X.PolicyName}`)}if
|
|
|
614
614
|
Access Keys:`);for(let X of Y.AccessKeyMetadata)if(e(` - ${X.AccessKeyId}: ${X.Status}`),X.CreateDate)e(` Created: ${new Date(X.CreateDate).toLocaleString()}`)}if(D.Tags&&D.Tags.length>0){e(`
|
|
615
615
|
Tags:`);for(let X of D.Tags)e(` ${X.Key}: ${X.Value}`)}}catch(m){r(`Failed to get user: ${m.message}`),process.exit(1)}}),R.command("iam:groups","List IAM groups").option("--path <path>","Filter by path prefix",{default:"/"}).action(async(t)=>{i("IAM Groups");try{let m=new te,k=new s("Fetching groups...");k.start();let L=(await m.listGroups({PathPrefix:t.path})).Groups||[];if(k.succeed(`Found ${L.length} group(s)`),L.length===0){e("No IAM groups found");return}c(["Group Name","Group ID","Path","Created"],L.map((z)=>[z.GroupName||"N/A",z.GroupId||"N/A",z.Path||"/",z.CreateDate?new Date(z.CreateDate).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list groups: ${m.message}`),process.exit(1)}}),R.command("iam:simulate <policyArn>","Simulate IAM policy").option("--action <action>","Action to simulate (e.g., s3:GetObject)").option("--resource <arn>","Resource ARN to test against").action(async(t,m)=>{i("Simulate IAM Policy");try{let k=new te,D=m.action||await I("Action to simulate","s3:GetObject"),L=m.resource||await I("Resource ARN","*");e(`
|
|
616
616
|
Policy: ${t}`),e(`Action: ${D}`),e(`Resource: ${L}`);let z=new s("Running simulation...");z.start();let Y=await k.simulatePrincipalPolicy({PolicySourceArn:t,ActionNames:[D],ResourceArns:[L]});if(z.succeed("Simulation complete"),Y.EvaluationResults&&Y.EvaluationResults.length>0)for(let X of Y.EvaluationResults){let ne=X.EvalDecision;if(e(`
|
|
617
|
-
Result for ${X.EvalActionName}:`),ne==="allowed")n(" Decision: ALLOWED");else if(ne==="implicitDeny")a(" Decision: IMPLICIT DENY (no matching allow statement)");else r(" Decision: EXPLICIT DENY");if(X.MatchedStatements&&X.MatchedStatements.length>0){e(" Matched Statements:");for(let re of X.MatchedStatements)e(` - ${re.SourcePolicyId}: ${re.SourcePolicyType}`)}}}catch(k){r(`Failed to simulate policy: ${k.message}`),process.exit(1)}}),R.command("iam:whoami","Show current IAM identity").action(async()=>{i("Current IAM Identity");try{let t=new te,m=new s("Fetching identity...");m.start();let{STSClient:k}=await import("./chunk-
|
|
617
|
+
Result for ${X.EvalActionName}:`),ne==="allowed")n(" Decision: ALLOWED");else if(ne==="implicitDeny")a(" Decision: IMPLICIT DENY (no matching allow statement)");else r(" Decision: EXPLICIT DENY");if(X.MatchedStatements&&X.MatchedStatements.length>0){e(" Matched Statements:");for(let re of X.MatchedStatements)e(` - ${re.SourcePolicyId}: ${re.SourcePolicyType}`)}}}catch(k){r(`Failed to simulate policy: ${k.message}`),process.exit(1)}}),R.command("iam:whoami","Show current IAM identity").action(async()=>{i("Current IAM Identity");try{let t=new te,m=new s("Fetching identity...");m.start();let{STSClient:k}=await import("./chunk-t6zpvaqr.js"),L=await new k("us-east-1").getCallerIdentity();m.succeed("Identity loaded"),e(`
|
|
618
618
|
Caller Identity:`),e(` Account: ${L.Account}`),e(` ARN: ${L.Arn}`),e(` User ID: ${L.UserId}`);let z=L.Arn||"";if(z.includes(":user/"))e(" Type: IAM User");else if(z.includes(":assumed-role/"))e(" Type: Assumed Role");else if(z.includes(":root"))a(" Type: Root Account"),a(`
|
|
619
|
-
Warning: Using root credentials is not recommended!`)}catch(t){r(`Failed to get identity: ${t.message}`),process.exit(1)}})}async function Ki(R){let{AWSClient:t}=await import("./chunk-
|
|
619
|
+
Warning: Using root credentials is not recommended!`)}catch(t){r(`Failed to get identity: ${t.message}`),process.exit(1)}})}async function Ki(R){let{AWSClient:t}=await import("./chunk-qe8zh9kn.js");class m{client;region;constructor(k){this.region=k,this.client=new t}async jsonRpcRequest(k,D){return this.client.request({service:"cloudtrail",region:this.region,method:"POST",path:"/",headers:{"Content-Type":"application/x-amz-json-1.1","X-Amz-Target":`com.amazonaws.cloudtrail.v20131101.CloudTrail_20131101.${k}`},body:JSON.stringify(D)})}async describeTrails(){return this.jsonRpcRequest("DescribeTrails",{})}async getTrailStatus(k){return this.jsonRpcRequest("GetTrailStatus",{Name:k})}async lookupEvents(k){return this.jsonRpcRequest("LookupEvents",{...k,StartTime:k.StartTime?.toISOString(),EndTime:k.EndTime?.toISOString()})}async getEventSelectors(k){return this.jsonRpcRequest("GetEventSelectors",{TrailName:k})}async createTrail(k){return this.jsonRpcRequest("CreateTrail",k)}async startLogging(k){return this.jsonRpcRequest("StartLogging",{Name:k})}async stopLogging(k){return this.jsonRpcRequest("StopLogging",{Name:k})}async deleteTrail(k){return this.jsonRpcRequest("DeleteTrail",{Name:k})}}return new m(R)}function Nl(R){R.command("audit:trails","List CloudTrail trails").option("--region <region>","AWS region").action(async(t)=>{i("CloudTrail Trails");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",D=await Ki(k),L=new s("Fetching trails...");L.start();let Y=(await D.describeTrails()).trailList||[];if(L.succeed(`Found ${Y.length} trail(s)`),Y.length===0){e("No CloudTrail trails found"),e("Use `cloud audit:create` to create a new trail");return}c(["Name","Multi-Region","S3 Bucket","Log Validation","Home Region"],Y.map((X)=>[X.Name||"N/A",X.IsMultiRegionTrail?"Yes":"No",X.S3BucketName||"N/A",X.LogFileValidationEnabled?"Yes":"No",X.HomeRegion||"N/A"]))}catch(m){r(`Failed to list trails: ${m.message}`),process.exit(1)}}),R.command("audit:trail <trailName>","Show CloudTrail trail details").option("--region <region>","AWS region").action(async(t,m)=>{i(`CloudTrail: ${t}`);try{let k=await Ne(),D=m.region||k.project.region||"us-east-1",L=await Ki(D),z=new s("Fetching trail details...");z.start();let[Y,X]=await Promise.all([L.describeTrails(),L.getTrailStatus(t)]),ne=(Y.trailList||[]).find((re)=>re.Name===t);if(!ne){z.fail("Trail not found");return}if(z.succeed("Trail details loaded"),e(`
|
|
620
620
|
Trail Configuration:`),e(` Name: ${ne.Name}`),e(` ARN: ${ne.TrailARN}`),e(` Home Region: ${ne.HomeRegion}`),e(` Multi-Region: ${ne.IsMultiRegionTrail?"Yes":"No"}`),e(` Organization Trail: ${ne.IsOrganizationTrail?"Yes":"No"}`),e(`
|
|
621
621
|
Storage:`),e(` S3 Bucket: ${ne.S3BucketName}`),ne.S3KeyPrefix)e(` S3 Prefix: ${ne.S3KeyPrefix}`);if(e(` Log Validation: ${ne.LogFileValidationEnabled?"Enabled":"Disabled"}`),ne.CloudWatchLogsLogGroupArn)e(`
|
|
622
622
|
CloudWatch Logs:`),e(` Log Group: ${ne.CloudWatchLogsLogGroupArn}`);if(ne.KMSKeyId)e(`
|
|
@@ -629,30 +629,30 @@ Full Event Record:`);let ne=JSON.parse(X.CloudTrailEvent);console.log(JSON.strin
|
|
|
629
629
|
Create this trail?`,!0)){e("Operation cancelled");return}let L=new s("Creating trail...");L.start();let z=await k.createTrail({Name:t,S3BucketName:m.bucket,S3KeyPrefix:m.prefix,IsMultiRegionTrail:m.multiRegion,EnableLogFileValidation:m.validation,IncludeGlobalServiceEvents:m.globalEvents??!0});L.text="Starting logging...",await k.startLogging(t),L.succeed("Trail created and logging started"),n(`
|
|
630
630
|
Trail ARN: ${z.TrailARN}`),e(`S3 Bucket: ${z.S3BucketName}`)}catch(k){r(`Failed to create trail: ${k.message}`),process.exit(1)}}),R.command("audit:start <trailName>","Start CloudTrail logging").option("--region <region>","AWS region",{default:"us-east-1"}).action(async(t,m)=>{i("Start CloudTrail Logging");try{let k=await Ki(m.region),D=new s("Starting logging...");D.start(),await k.startLogging(t),D.succeed("Logging started")}catch(k){r(`Failed to start logging: ${k.message}`),process.exit(1)}}),R.command("audit:stop <trailName>","Stop CloudTrail logging").option("--region <region>","AWS region",{default:"us-east-1"}).action(async(t,m)=>{i("Stop CloudTrail Logging");try{let k=await Ki(m.region);if(a(`This will stop logging for trail: ${t}`),!await d(`
|
|
631
631
|
Stop logging?`,!1)){e("Operation cancelled");return}let L=new s("Stopping logging...");L.start(),await k.stopLogging(t),L.succeed("Logging stopped")}catch(k){r(`Failed to stop logging: ${k.message}`),process.exit(1)}}),R.command("audit:delete <trailName>","Delete a CloudTrail trail").option("--region <region>","AWS region",{default:"us-east-1"}).action(async(t,m)=>{i("Delete CloudTrail Trail");try{let k=await Ki(m.region);if(a(`This will permanently delete trail: ${t}`),!await d(`
|
|
632
|
-
Delete this trail?`,!1)){e("Operation cancelled");return}let L=new s("Deleting trail...");L.start(),await k.deleteTrail(t),L.succeed("Trail deleted")}catch(k){r(`Failed to delete trail: ${k.message}`),process.exit(1)}})}function Ol(R){R.command("status","Show overall infrastructure health dashboard").option("--region <region>","AWS region").action(async(t)=>{i("Infrastructure Status Dashboard");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1";e(`Region: ${k}`),e(`Project: ${m.project.name||"Unknown"}`),e("");let D=[],L=new s("Checking EC2 instances...");L.start();try{let{EC2Client:le}=await import("./chunk-
|
|
632
|
+
Delete this trail?`,!1)){e("Operation cancelled");return}let L=new s("Deleting trail...");L.start(),await k.deleteTrail(t),L.succeed("Trail deleted")}catch(k){r(`Failed to delete trail: ${k.message}`),process.exit(1)}})}function Ol(R){R.command("status","Show overall infrastructure health dashboard").option("--region <region>","AWS region").action(async(t)=>{i("Infrastructure Status Dashboard");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1";e(`Region: ${k}`),e(`Project: ${m.project.name||"Unknown"}`),e("");let D=[],L=new s("Checking EC2 instances...");L.start();try{let{EC2Client:le}=await import("./chunk-fjx816tt.js"),De=await new le(k).describeInstances({Filters:[{Name:"instance-state-name",Values:["running","pending","stopping","stopped"]}]}),Se=0,Re=0,Pe=0;for(let Ve of De.Reservations||[])for(let Le of Ve.Instances||[])if(Pe++,Le.State?.Name==="running")Se++;else if(Le.State?.Name==="stopped")Re++;L.succeed("EC2 instances checked"),D.push({name:"EC2 Instances",status:Se>0?"OK":Pe>0?"WARN":"INFO",details:`${Se} running, ${Re} stopped, ${Pe} total`})}catch(le){L.fail("EC2 check failed"),D.push({name:"EC2 Instances",status:"ERROR",details:le.message})}let z=new s("Checking RDS instances...");z.start();try{let{RDSClient:le}=await import("./chunk-nzhpva7z.js"),Se=(await new le(k).describeDBInstances()).DBInstances||[],Re=Se.filter((Pe)=>Pe.DBInstanceStatus==="available").length;z.succeed("RDS instances checked"),D.push({name:"RDS Databases",status:Se.length>0&&Re===Se.length?"OK":Se.length>0?"WARN":"INFO",details:`${Re} available, ${Se.length} total`})}catch(le){z.fail("RDS check failed"),D.push({name:"RDS Databases",status:"ERROR",details:le.message})}let Y=new s("Checking Lambda functions...");Y.start();try{let{LambdaClient:le}=await import("./chunk-vrn0akcn.js"),Se=(await new le(k).listFunctions()).Functions||[];Y.succeed("Lambda functions checked"),D.push({name:"Lambda Functions",status:"OK",details:`${Se.length} function(s)`})}catch(le){Y.fail("Lambda check failed"),D.push({name:"Lambda Functions",status:"ERROR",details:le.message})}let X=new s("Checking S3 buckets...");X.start();try{let{S3Client:le}=await import("./chunk-h5ccvped.js"),Se=(await new le(k).listBuckets()).Buckets||[];X.succeed("S3 buckets checked"),D.push({name:"S3 Buckets",status:"OK",details:`${Se.length} bucket(s)`})}catch(le){X.fail("S3 check failed"),D.push({name:"S3 Buckets",status:"ERROR",details:le.message})}let ne=new s("Checking CloudFront distributions...");ne.start();try{let{CloudFrontClient:le}=await import("./chunk-ncr1dwdt.js"),De=await new le().listDistributions(),Se=De.filter((Re)=>Re.Status==="Deployed").length;ne.succeed("CloudFront checked"),D.push({name:"CloudFront",status:De.length>0&&Se===De.length?"OK":De.length>0?"WARN":"INFO",details:`${Se} deployed, ${De.length} total`})}catch(le){ne.fail("CloudFront check failed"),D.push({name:"CloudFront",status:"ERROR",details:le.message})}let re=new s("Checking SQS queues...");re.start();try{let{SQSClient:le}=await import("./chunk-x00efaxm.js"),Se=(await new le(k).listQueues()).QueueUrls||[];re.succeed("SQS queues checked"),D.push({name:"SQS Queues",status:"OK",details:`${Se.length} queue(s)`})}catch(le){re.fail("SQS check failed"),D.push({name:"SQS Queues",status:"ERROR",details:le.message})}let ie=new s("Checking CloudFormation stacks...");ie.start();try{let{CloudFormationClient:le}=await import("./chunk-zred4827.js"),Se=(await new le(k).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE","CREATE_IN_PROGRESS","UPDATE_IN_PROGRESS","ROLLBACK_COMPLETE","UPDATE_ROLLBACK_COMPLETE"])).StackSummaries||[],Re=Se.filter((Le)=>Le.StackStatus==="CREATE_COMPLETE"||Le.StackStatus==="UPDATE_COMPLETE").length,Pe=Se.filter((Le)=>Le.StackStatus?.includes("IN_PROGRESS")).length,Ve=Se.filter((Le)=>Le.StackStatus?.includes("ROLLBACK")).length;ie.succeed("CloudFormation checked"),D.push({name:"CloudFormation",status:Ve>0?"WARN":Pe>0?"INFO":"OK",details:`${Re} healthy, ${Pe} in progress, ${Ve} rolled back`})}catch(le){ie.fail("CloudFormation check failed"),D.push({name:"CloudFormation",status:"ERROR",details:le.message})}let ae=new s("Checking SSL certificates...");ae.start();try{let{ACMClient:le}=await import("./chunk-ht6s2cwm.js"),ke=new le("us-east-1"),Se=(await ke.listCertificates()).CertificateSummaryList||[],Re=await Promise.all(Se.map((Fe)=>ke.describeCertificate({CertificateArn:Fe.CertificateArn}))),Pe=Re.filter((Fe)=>Fe.Status==="ISSUED").length,Ve=Re.filter((Fe)=>Fe.Status==="PENDING_VALIDATION").length,Le=Re.filter((Fe)=>{if(Fe.NotAfter)return(new Date(Fe.NotAfter).getTime()-Date.now())/86400000<30;return!1}).length;ae.succeed("SSL certificates checked"),D.push({name:"SSL Certificates",status:Le>0?"WARN":Ve>0?"INFO":"OK",details:`${Pe} issued, ${Ve} pending${Le>0?`, ${Le} expiring soon`:""}`})}catch(le){ae.fail("ACM check failed"),D.push({name:"SSL Certificates",status:"ERROR",details:le.message})}e(`
|
|
633
633
|
`+"=".repeat(60)),e("HEALTH SUMMARY"),e("=".repeat(60)+`
|
|
634
|
-
`);for(let le of D){let ke="";if(le.status==="OK")ke=`${C.green}[OK]${C.reset}`;else if(le.status==="WARN")ke=`${C.yellow}[WARN]${C.reset}`;else if(le.status==="ERROR")ke=`${C.red}[ERROR]${C.reset}`;else ke=`${C.blue}[INFO]${C.reset}`;console.log(`${ke} ${le.name.padEnd(20)} ${le.details}`)}let be=D.some((le)=>le.status==="ERROR"),Ae=D.some((le)=>le.status==="WARN");if(e(""),be)r("Some services have errors. Check the details above.");else if(Ae)a("Some services need attention. Check the warnings above.");else n("All services are healthy!")}catch(m){r(`Failed to get status: ${m.message}`),process.exit(1)}}),R.command("status:costs","Show current month cost summary").option("--region <region>","AWS region").action(async(t)=>{i("Cost Summary");try{e("Fetching cost data from AWS Cost Explorer..."),e("");let m=new Date,k=new Date(m.getFullYear(),m.getMonth(),1),D=new Date(m.getFullYear(),m.getMonth()+1,0);e(`Period: ${k.toISOString().split("T")[0]} to ${D.toISOString().split("T")[0]}`),e(""),e("Note: Cost data requires AWS Cost Explorer API access."),e("Run `cloud cost` for detailed cost analysis.")}catch(m){r(`Failed to get cost summary: ${m.message}`),process.exit(1)}}),R.command("status:alarms","Show CloudWatch alarm status").option("--region <region>","AWS region").action(async(t)=>{i("CloudWatch Alarms");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",{AWSClient:D}=await import("./chunk-
|
|
635
|
-
Alarms in ALARM state:`);for(let be of re)r(` - ${be.AlarmName}: ${be.AlarmDescription||"No description"}`)}}catch(m){r(`Failed to get alarms: ${m.message}`),process.exit(1)}})}async function
|
|
634
|
+
`);for(let le of D){let ke="";if(le.status==="OK")ke=`${C.green}[OK]${C.reset}`;else if(le.status==="WARN")ke=`${C.yellow}[WARN]${C.reset}`;else if(le.status==="ERROR")ke=`${C.red}[ERROR]${C.reset}`;else ke=`${C.blue}[INFO]${C.reset}`;console.log(`${ke} ${le.name.padEnd(20)} ${le.details}`)}let be=D.some((le)=>le.status==="ERROR"),Ae=D.some((le)=>le.status==="WARN");if(e(""),be)r("Some services have errors. Check the details above.");else if(Ae)a("Some services need attention. Check the warnings above.");else n("All services are healthy!")}catch(m){r(`Failed to get status: ${m.message}`),process.exit(1)}}),R.command("status:costs","Show current month cost summary").option("--region <region>","AWS region").action(async(t)=>{i("Cost Summary");try{e("Fetching cost data from AWS Cost Explorer..."),e("");let m=new Date,k=new Date(m.getFullYear(),m.getMonth(),1),D=new Date(m.getFullYear(),m.getMonth()+1,0);e(`Period: ${k.toISOString().split("T")[0]} to ${D.toISOString().split("T")[0]}`),e(""),e("Note: Cost data requires AWS Cost Explorer API access."),e("Run `cloud cost` for detailed cost analysis.")}catch(m){r(`Failed to get cost summary: ${m.message}`),process.exit(1)}}),R.command("status:alarms","Show CloudWatch alarm status").option("--region <region>","AWS region").action(async(t)=>{i("CloudWatch Alarms");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",{AWSClient:D}=await import("./chunk-qe8zh9kn.js");class L{client;region;constructor(be){this.region=be,this.client=new D}async describeAlarms(){return this.client.request({service:"monitoring",region:this.region,method:"POST",path:"/",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"Action=DescribeAlarms&Version=2010-08-01"})}}let z=new L(k),Y=new s("Fetching alarms...");Y.start();let ne=(await z.describeAlarms()).MetricAlarms||[];if(Y.succeed(`Found ${ne.length} alarm(s)`),ne.length===0){e("No CloudWatch alarms configured");return}let re=ne.filter((be)=>be.StateValue==="ALARM"),ie=ne.filter((be)=>be.StateValue==="OK"),ae=ne.filter((be)=>be.StateValue==="INSUFFICIENT_DATA");if(e(""),e(`Alarming: ${re.length}`),e(`OK: ${ie.length}`),e(`Insufficient Data: ${ae.length}`),re.length>0){e(`
|
|
635
|
+
Alarms in ALARM state:`);for(let be of re)r(` - ${be.AlarmName}: ${be.AlarmDescription||"No description"}`)}}catch(m){r(`Failed to get alarms: ${m.message}`),process.exit(1)}})}async function _r(R){let{AWSClient:t}=await import("./chunk-qe8zh9kn.js");class m{client;region;constructor(k){this.region=k,this.client=new t}async jsonRpcRequest(k,D){return this.client.request({service:"backup",region:this.region,method:"POST",path:"/",headers:{"Content-Type":"application/x-amz-json-1.1","X-Amz-Target":`CryoControllerFrontendService.${k}`},body:JSON.stringify(D)})}async listBackupVaults(){return this.jsonRpcRequest("ListBackupVaults",{})}async listBackupPlans(){return this.jsonRpcRequest("ListBackupPlans",{})}async listRecoveryPointsByBackupVault(k,D){return this.jsonRpcRequest("ListRecoveryPointsByBackupVault",{BackupVaultName:k,...D})}async describeBackupVault(k){return this.jsonRpcRequest("DescribeBackupVault",{BackupVaultName:k})}async getBackupPlan(k){return this.jsonRpcRequest("GetBackupPlan",{BackupPlanId:k})}async createBackupVault(k,D){return this.jsonRpcRequest("CreateBackupVault",{BackupVaultName:k,...D})}async deleteBackupVault(k){return this.jsonRpcRequest("DeleteBackupVault",{BackupVaultName:k})}async createBackupPlan(k){return this.jsonRpcRequest("CreateBackupPlan",{BackupPlan:k})}async deleteBackupPlan(k){return this.jsonRpcRequest("DeleteBackupPlan",{BackupPlanId:k})}async startBackupJob(k){return this.jsonRpcRequest("StartBackupJob",k)}async startRestoreJob(k){return this.jsonRpcRequest("StartRestoreJob",k)}async describeBackupJob(k){return this.jsonRpcRequest("DescribeBackupJob",{BackupJobId:k})}async describeRestoreJob(k){return this.jsonRpcRequest("DescribeRestoreJob",{RestoreJobId:k})}async listBackupJobs(k){return this.jsonRpcRequest("ListBackupJobs",k||{})}async listRestoreJobs(k){return this.jsonRpcRequest("ListRestoreJobs",k||{})}async createBackupSelection(k,D){return this.jsonRpcRequest("CreateBackupSelection",{BackupPlanId:k,BackupSelection:D})}async listBackupSelections(k){return this.jsonRpcRequest("ListBackupSelections",{BackupPlanId:k})}}return new m(R)}function Ll(R){R.command("backup:vaults","List backup vaults").option("--region <region>","AWS region").action(async(t)=>{i("Backup Vaults");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",D=await _r(k),L=new s("Fetching vaults...");L.start();let Y=(await D.listBackupVaults()).BackupVaultList||[];if(L.succeed(`Found ${Y.length} vault(s)`),Y.length===0){e("No backup vaults found"),e("Use `cloud backup:create-vault` to create a new vault");return}c(["Vault Name","Recovery Points","Created","Encrypted"],Y.map((X)=>[X.BackupVaultName||"N/A",(X.NumberOfRecoveryPoints||0).toString(),X.CreationDate?new Date(X.CreationDate).toLocaleDateString():"N/A",X.EncryptionKeyArn?"Yes":"Default"]))}catch(m){r(`Failed to list vaults: ${m.message}`),process.exit(1)}}),R.command("backup:list","List backup plans").option("--region <region>","AWS region").action(async(t)=>{i("Backup Plans");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",D=await _r(k),L=new s("Fetching backup plans...");L.start();let Y=(await D.listBackupPlans()).BackupPlansList||[];if(L.succeed(`Found ${Y.length} backup plan(s)`),Y.length===0){e("No backup plans found"),e("Use `cloud backup:create` to create a backup plan");return}c(["Plan Name","Plan ID","Version","Created"],Y.map((X)=>[X.BackupPlanName||"N/A",X.BackupPlanId||"N/A",X.VersionId?.substring(0,8)||"N/A",X.CreationDate?new Date(X.CreationDate).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list backup plans: ${m.message}`),process.exit(1)}}),R.command("backup:recovery-points <vaultName>","List recovery points in a vault").option("--region <region>","AWS region").option("--limit <number>","Maximum results",{default:"50"}).action(async(t,m)=>{i(`Recovery Points: ${t}`);try{let k=await Ne(),D=m.region||k.project.region||"us-east-1",L=await _r(D),z=new s("Fetching recovery points...");z.start();let X=(await L.listRecoveryPointsByBackupVault(t,{MaxResults:Number.parseInt(m.limit)})).RecoveryPoints||[];if(z.succeed(`Found ${X.length} recovery point(s)`),X.length===0){e("No recovery points found");return}c(["Resource","Status","Created","Size","Lifecycle"],X.map((ne)=>[ne.ResourceArn?.split(":").pop()||"N/A",ne.Status||"N/A",ne.CreationDate?new Date(ne.CreationDate).toLocaleString():"N/A",ne.BackupSizeInBytes?Nu(ne.BackupSizeInBytes):"N/A",ne.Lifecycle?.DeleteAfterDays?`${ne.Lifecycle.DeleteAfterDays} days`:"Indefinite"]))}catch(k){r(`Failed to list recovery points: ${k.message}`),process.exit(1)}}),R.command("backup:create-vault <vaultName>","Create a backup vault").option("--region <region>","AWS region",{default:"us-east-1"}).option("--kms-key <arn>","KMS key ARN for encryption").action(async(t,m)=>{i("Create Backup Vault");try{let k=await _r(m.region);if(e(`Vault Name: ${t}`),e(`Region: ${m.region}`),e(`Encryption: ${m.kmsKey?"Custom KMS":"AWS Managed"}`),!await d(`
|
|
636
636
|
Create this vault?`,!0)){e("Operation cancelled");return}let L=new s("Creating vault...");L.start(),await k.createBackupVault(t,{EncryptionKeyArn:m.kmsKey}),L.succeed("Vault created"),n(`
|
|
637
|
-
Vault: ${t}`)}catch(k){r(`Failed to create vault: ${k.message}`),process.exit(1)}}),R.command("backup:create <planName>","Create a backup plan").option("--region <region>","AWS region",{default:"us-east-1"}).option("--vault <name>","Target backup vault",{default:"Default"}).option("--schedule <cron>","Backup schedule (cron expression)",{default:"cron(0 5 ? * * *)"}).option("--retention <days>","Retention period in days",{default:"30"}).option("--lifecycle-cold <days>","Move to cold storage after days").action(async(t,m)=>{i("Create Backup Plan");try{let k=await
|
|
637
|
+
Vault: ${t}`)}catch(k){r(`Failed to create vault: ${k.message}`),process.exit(1)}}),R.command("backup:create <planName>","Create a backup plan").option("--region <region>","AWS region",{default:"us-east-1"}).option("--vault <name>","Target backup vault",{default:"Default"}).option("--schedule <cron>","Backup schedule (cron expression)",{default:"cron(0 5 ? * * *)"}).option("--retention <days>","Retention period in days",{default:"30"}).option("--lifecycle-cold <days>","Move to cold storage after days").action(async(t,m)=>{i("Create Backup Plan");try{let k=await _r(m.region);if(e(`Plan Name: ${t}`),e(`Vault: ${m.vault}`),e(`Schedule: ${m.schedule}`),e(`Retention: ${m.retention} days`),!await d(`
|
|
638
638
|
Create this backup plan?`,!0)){e("Operation cancelled");return}let L=new s("Creating backup plan...");L.start();let z={DeleteAfterDays:Number.parseInt(m.retention)};if(m.lifecycleCold)z.MoveToColdStorageAfterDays=Number.parseInt(m.lifecycleCold);let Y=await k.createBackupPlan({BackupPlanName:t,Rules:[{RuleName:`${t}-daily`,TargetBackupVaultName:m.vault,ScheduleExpression:m.schedule,StartWindowMinutes:60,CompletionWindowMinutes:180,Lifecycle:z}]});L.succeed("Backup plan created"),n(`
|
|
639
|
-
Plan ID: ${Y.BackupPlanId}`),e("\nNote: Add resource selections with `cloud backup:add-selection`")}catch(k){r(`Failed to create backup plan: ${k.message}`),process.exit(1)}}),R.command("backup:add-selection <planId>","Add resources to a backup plan").option("--region <region>","AWS region",{default:"us-east-1"}).option("--name <name>","Selection name").option("--role <arn>","IAM role ARN for backup").option("--resource <arn>","Resource ARN to backup").option("--tag-key <key>","Tag key for resource selection").option("--tag-value <value>","Tag value for resource selection").action(async(t,m)=>{i("Add Backup Selection");try{if(!m.role){r("--role is required (IAM role ARN for AWS Backup)");return}if(!m.resource&&!m.tagKey){r("Specify --resource or --tag-key/--tag-value");return}let k=await
|
|
639
|
+
Plan ID: ${Y.BackupPlanId}`),e("\nNote: Add resource selections with `cloud backup:add-selection`")}catch(k){r(`Failed to create backup plan: ${k.message}`),process.exit(1)}}),R.command("backup:add-selection <planId>","Add resources to a backup plan").option("--region <region>","AWS region",{default:"us-east-1"}).option("--name <name>","Selection name").option("--role <arn>","IAM role ARN for backup").option("--resource <arn>","Resource ARN to backup").option("--tag-key <key>","Tag key for resource selection").option("--tag-value <value>","Tag value for resource selection").action(async(t,m)=>{i("Add Backup Selection");try{if(!m.role){r("--role is required (IAM role ARN for AWS Backup)");return}if(!m.resource&&!m.tagKey){r("Specify --resource or --tag-key/--tag-value");return}let k=await _r(m.region),L={SelectionName:m.name||`selection-${Date.now()}`,IamRoleArn:m.role};if(m.resource)L.Resources=[m.resource],e(`Resource: ${m.resource}`);if(m.tagKey&&m.tagValue)L.ListOfTags=[{ConditionType:"STRINGEQUALS",ConditionKey:m.tagKey,ConditionValue:m.tagValue}],e(`Tag: ${m.tagKey}=${m.tagValue}`);if(!await d(`
|
|
640
640
|
Add this selection?`,!0)){e("Operation cancelled");return}let Y=new s("Adding selection...");Y.start();let X=await k.createBackupSelection(t,L);Y.succeed("Selection added"),n(`
|
|
641
|
-
Selection ID: ${X.SelectionId}`)}catch(k){r(`Failed to add selection: ${k.message}`),process.exit(1)}}),R.command("backup:start <resourceArn>","Start an on-demand backup").option("--region <region>","AWS region",{default:"us-east-1"}).option("--vault <name>","Backup vault name",{default:"Default"}).option("--role <arn>","IAM role ARN for backup").action(async(t,m)=>{i("Start Backup Job");try{if(!m.role){r("--role is required (IAM role ARN for AWS Backup)");return}let k=await
|
|
641
|
+
Selection ID: ${X.SelectionId}`)}catch(k){r(`Failed to add selection: ${k.message}`),process.exit(1)}}),R.command("backup:start <resourceArn>","Start an on-demand backup").option("--region <region>","AWS region",{default:"us-east-1"}).option("--vault <name>","Backup vault name",{default:"Default"}).option("--role <arn>","IAM role ARN for backup").action(async(t,m)=>{i("Start Backup Job");try{if(!m.role){r("--role is required (IAM role ARN for AWS Backup)");return}let k=await _r(m.region);if(e(`Resource: ${t}`),e(`Vault: ${m.vault}`),!await d(`
|
|
642
642
|
Start backup?`,!0)){e("Operation cancelled");return}let L=new s("Starting backup job...");L.start();let z=await k.startBackupJob({BackupVaultName:m.vault,ResourceArn:t,IamRoleArn:m.role,IdempotencyToken:`cli-${Date.now()}`});L.succeed("Backup job started"),n(`
|
|
643
|
-
Job ID: ${z.BackupJobId}`),e("Use `cloud backup:jobs` to check status")}catch(k){r(`Failed to start backup: ${k.message}`),process.exit(1)}}),R.command("backup:restore <recoveryPointArn>","Start a restore job").option("--region <region>","AWS region",{default:"us-east-1"}).option("--role <arn>","IAM role ARN for restore").option("--metadata <json>","Restore metadata JSON").action(async(t,m)=>{i("Start Restore Job");try{if(!m.role){r("--role is required (IAM role ARN for AWS Backup)");return}let k=await
|
|
643
|
+
Job ID: ${z.BackupJobId}`),e("Use `cloud backup:jobs` to check status")}catch(k){r(`Failed to start backup: ${k.message}`),process.exit(1)}}),R.command("backup:restore <recoveryPointArn>","Start a restore job").option("--region <region>","AWS region",{default:"us-east-1"}).option("--role <arn>","IAM role ARN for restore").option("--metadata <json>","Restore metadata JSON").action(async(t,m)=>{i("Start Restore Job");try{if(!m.role){r("--role is required (IAM role ARN for AWS Backup)");return}let k=await _r(m.region);e(`Recovery Point: ${t}`);let D={};if(m.metadata)D=JSON.parse(m.metadata);if(a(`
|
|
644
644
|
Restore will create new resources. Review carefully.`),!await d(`
|
|
645
645
|
Start restore?`,!1)){e("Operation cancelled");return}let z=new s("Starting restore job...");z.start();let Y=await k.startRestoreJob({RecoveryPointArn:t,IamRoleArn:m.role,Metadata:D,IdempotencyToken:`cli-${Date.now()}`});z.succeed("Restore job started"),n(`
|
|
646
|
-
Job ID: ${Y.RestoreJobId}`),e("Use `cloud backup:restore-jobs` to check status")}catch(k){r(`Failed to start restore: ${k.message}`),process.exit(1)}}),R.command("backup:jobs","List recent backup jobs").option("--region <region>","AWS region").option("--state <state>","Filter by state (CREATED, PENDING, RUNNING, COMPLETED, FAILED)").action(async(t)=>{i("Backup Jobs");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",D=await
|
|
647
|
-
${X.BackupPlanName}`),e("=".repeat(40));try{let re=(await D.getBackupPlan(X.BackupPlanId)).BackupPlan?.Rules||[];for(let ae of re)if(e(` Rule: ${ae.RuleName}`),e(` Schedule: ${ae.ScheduleExpression}`),e(` Vault: ${ae.TargetBackupVaultName}`),ae.Lifecycle?.DeleteAfterDays)e(` Retention: ${ae.Lifecycle.DeleteAfterDays} days`);let ie=await D.listBackupSelections(X.BackupPlanId);if(ie.BackupSelectionsList&&ie.BackupSelectionsList.length>0)e(` Selections: ${ie.BackupSelectionsList.length}`)}catch{e(" (Unable to load plan details)")}}}catch(m){r(`Failed to get schedule: ${m.message}`),process.exit(1)}})}function Nu(R){if(R===0)return"0 B";let t=1024,m=["B","KB","MB","GB","TB"],k=Math.floor(Math.log(R)/Math.log(t));return`${Number.parseFloat((R/t**k).toFixed(2))} ${m[k]}`}async function kh(R){let t=await Ne(),m=y(process.cwd(),t),k=R??Object.keys(t.environments??{})[0]??"production",D=m.environments.get(k);if(!D)throw m.store.close(),Error(`Environment ${k} was not found.`);let L=new S(m.store,{workerId:`cli:${process.pid}`}),z=new ci(m.store),Y=new en(z,L),X=new or(m.store),ne=new sr(m.store,_(process.cwd())),re=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:t,controlPlane:m,env:k,environmentRecord:D,queue:L,store:z,coordinator:Y,dataServices:X,secrets:ne,actor:re}}async function wi(R,t){let m=await kh(R);try{return await t(m)}finally{m.controlPlane.store.close()}}async function $i(R){try{await R()}catch(t){r(t instanceof Error?t.message:String(t))}}function Li(R,t,m,k){R.controlPlane.store.appendEvent({organizationId:R.controlPlane.organization.id,projectId:R.controlPlane.project.id,resourceId:k,actorId:R.actor.id,type:`backup.${t}`,payload:m})}function Ah(R,t){let m=R.store.getPolicy(t)??R.store.listPolicies(R.controlPlane.project.id,R.environmentRecord.id).find((k)=>k.name===t);if(!m||m.environmentId!==R.environmentRecord.id)throw Error(`Backup policy ${t} was not found in ${R.env}.`);return m}function Pa(R,t){let m=R.store.getRecoveryPoint(t),k=R.store.listRecoveryPoints(R.controlPlane.project.id).filter((L)=>L.id.startsWith(t)),D=m??(k.length===1?k[0]:void 0);if(!D)throw Error(k.length>1?`Recovery point prefix ${t} is ambiguous.`:`Recovery point ${t} was not found.`);return D}function Ou(R,t){let m=R.store.getDestination(t)??R.store.listDestinations(R.controlPlane.project.id).find((k)=>k.name===t);if(!m)throw Error(`Backup destination ${t} was not found.`);return m}function Rh(R,t){return R.kind==="volume"?{volumeName:t}:R.kind==="logical_database"?{targetId:t,dataServiceId:t}:{targetId:t}}function Ih(R,t=[]){let m=new Map(t.map((k)=>[k.id,k.name]));return R.map((k)=>[k.id,k.status,k.verificationState,k.kind,m.get(k.policyId??"")??"manual",k.pointInTime,k.sizeBytes?`${k.sizeBytes} B`:"provider",k.held?"hold":k.pinned?"pinned":"retention"])}function Fl(R){R.command("recovery:list","Show backup coverage, destination health, and recent recovery points").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>$i(async()=>wi(t.env,async(m)=>{let k=m.store.listPolicies(m.controlPlane.project.id,m.environmentRecord.id),D=new Set(k.map((Y)=>Y.id)),L=m.store.listRecoveryPoints(m.controlPlane.project.id).filter((Y)=>!Y.policyId||D.has(Y.policyId)),z={coverage:m.store.coverage(m.controlPlane.project.id).filter((Y)=>D.has(Y.policy.id)),destinations:m.store.listDestinations(m.controlPlane.project.id).map((Y)=>({...Y,credentialRef:void 0,encryptionKeyRef:void 0,credentialsConfigured:!!Y.credentialRef,clientEncryptionConfigured:!!Y.encryptionKeyRef})),policies:k,recoveryPoints:L,jobs:m.store.listJobs(m.controlPlane.project.id).filter((Y)=>!Y.policyId||D.has(Y.policyId))};if(t.json)e(JSON.stringify(z,null,2));else c(["Policy","RPO","Destination","Latest point","Verification"],z.coverage.map((Y)=>[Y.policy.name,Y.missedRpo?"MISSED":"protected",Y.destinationHealthy?"healthy":"attention",Y.lastRecoveryPoint?.pointInTime??"none",Y.lastRecoveryPoint?.verificationState??"none"])),c(["ID","State","Verification","Kind","Policy","Point in time","Size","Protection"],Ih(L,k))}))),R.command("recovery:jobs","List durable backup, verification, restore, drill, and cleanup jobs").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>$i(async()=>wi(t.env,async(m)=>{let k=new Set(m.store.listPolicies(m.controlPlane.project.id,m.environmentRecord.id).map((L)=>L.id)),D=m.store.listJobs(m.controlPlane.project.id).filter((L)=>!L.policyId||k.has(L.policyId));if(t.json)e(JSON.stringify(D,null,2));else c(["ID","Kind","State","Phase","Operation","Started","Finished","Error"],D.map((L)=>[L.id,L.kind,L.status,String(L.progress.phase??"queued"),L.operationId??"—",L.startedAt??"—",L.finishedAt??"—",L.error??"—"]))}))),R.command("recovery:destination:add <name>","Connect an S3, S3-compatible, or AWS Backup destination").option("--env <environment>","Target environment").option("--provider <provider>","aws_s3, s3_compatible, or aws_backup",{default:"aws_s3"}).option("--bucket <bucket>","Object bucket").option("--endpoint <url>","Origin-only HTTPS endpoint").option("--allow-private","Allow a private endpoint").option("--path-style","Force path-style S3 URLs").option("--prefix <prefix>","Object key prefix").option("--region <region>","Destination region").option("--credentials-env <name>","Environment variable containing credential JSON").option("--encryption <mode>","provider, client_side, or both",{default:"provider"}).option("--encryption-key-env <name>","Environment variable containing the client encryption key").option("--lock-days <days>","Default immutable retention days").action(async(t,m)=>$i(async()=>wi(m.env,async(k)=>{if(!["aws_s3","s3_compatible","aws_backup"].includes(m.provider??""))throw Error("--provider must be aws_s3, s3_compatible, or aws_backup.");if(!["provider","client_side","both"].includes(m.encryption??""))throw Error("--encryption must be provider, client_side, or both.");let D=`secret://data-services/backups/${k.controlPlane.project.id}/${t}`,L=[];try{let z,Y;if(m.credentialsEnv){let ne=process.env[m.credentialsEnv];if(!ne)throw Error(`${m.credentialsEnv} is empty.`);z=`${D}/credentials`,await k.secrets.put(z,ne),L.push(z)}if(m.encryption!=="provider"){if(!m.encryptionKeyEnv)throw Error("Client encryption requires --encryption-key-env.");let ne=process.env[m.encryptionKeyEnv];if(!ne||ne.length<32)throw Error(`${m.encryptionKeyEnv} must contain at least 32 characters.`);Y=`${D}/encryption-key`,await k.secrets.put(Y,ne),L.push(Y)}let X=k.store.createDestination({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,name:t,provider:m.provider,endpoint:m.endpoint,endpointPolicy:m.allowPrivate?"allow_private":"public_https",bucket:m.bucket,prefix:m.prefix??"",region:m.region??k.config.project.region,forcePathStyle:!!m.pathStyle,credentialRef:z,encryption:m.encryption,encryptionKeyRef:Y,immutability:{objectLock:!!m.lockDays,defaultRetentionDays:m.lockDays?Number(m.lockDays):void 0},status:"untested"});Li(k,"destination_created",{destinationId:X.id,provider:X.provider}),n(`Created destination ${X.name} (${X.id}); test it with cloud recovery:destination:test ${X.name}`)}catch(z){for(let Y of L)await k.secrets.remove(Y).catch(()=>{});throw z}}))),R.command("recovery:destination:test <destination>","Write, read, verify, and clean a destination health object").option("--env <environment>","Target environment").action(async(t,m)=>$i(async()=>wi(m.env,async(k)=>{let D=Ou(k,t);if(D.provider==="aws_backup")throw Error("AWS Backup is verified by creating a provider recovery point.");try{await new li(k.secrets).test(D),k.store.recordDestinationTest(D.id,{ok:!0}),Li(k,"destination_tested",{destinationId:D.id,ok:!0}),n(`${D.name} passed write, read, checksum, and cleanup checks.`)}catch(L){let z=L instanceof Error?L.message:String(L);throw k.store.recordDestinationTest(D.id,{ok:!1,error:z}),Li(k,"destination_tested",{destinationId:D.id,ok:!1}),L}}))),R.command("recovery:policy:add <name>","Create a scheduled recovery policy").option("--env <environment>","Target environment").option("--destination <id>","Destination ID or name").option("--kind <kind>","managed_database, logical_database, volume, files, control_plane, or infrastructure").option("--data-service <id>","Data service ID or name").option("--resource <id>","Resource ID or slug").option("--volume <name>","Named Docker volume").option("--include <path>","Project-relative include path or provider setting (repeatable)").option("--exclude <pattern>","Archive exclude pattern (repeatable)").option("--schedule <expression>","Cron, rate, or preset",{default:"daily"}).option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--rpo <minutes>","Recovery point objective",{default:"1440"}).option("--rto <minutes>","Recovery time objective",{default:"120"}).option("--keep <count>","Minimum latest points",{default:"7"}).option("--expire <days>","Expiry days",{default:"30"}).option("--compression <kind>","none, gzip, or zstd",{default:"gzip"}).option("--disabled","Create disabled").action(async(t,m)=>$i(async()=>wi(m.env,async(k)=>{if(!m.destination)throw Error("--destination is required.");if(!["managed_database","logical_database","volume","files","control_plane","infrastructure"].includes(m.kind??""))throw Error("--kind must be managed_database, logical_database, volume, files, control_plane, or infrastructure.");let D=Ou(k,m.destination),L=k.dataServices.list(k.controlPlane.project.id,k.environmentRecord.id),z=m.dataService?k.dataServices.get(m.dataService)??L.find((ae)=>ae.name===m.dataService):void 0,Y=k.controlPlane.store.listResources(k.controlPlane.project.id,k.environmentRecord.id),X=m.resource?Y.find((ae)=>ae.id===m.resource||ae.slug===m.resource):void 0,ne=m.include?Array.isArray(m.include)?m.include:[m.include]:[],re=m.exclude?Array.isArray(m.exclude)?m.exclude:[m.exclude]:[];if(["managed_database","logical_database"].includes(m.kind??"")&&!z)throw Error("--data-service must identify a service in this environment.");if(m.kind==="volume"&&(!X||!m.volume))throw Error("Volume policies require --resource and --volume.");if(m.kind==="files"&&(!X||!ne.length))throw Error("File policies require --resource and at least one --include path.");if(m.kind==="infrastructure"&&(!ne.some((ae)=>ae.startsWith("resource:arn:"))||!ne.some((ae)=>ae.startsWith("role:arn:"))))throw Error("Infrastructure policies require --include resource:<arn> and --include role:<arn>.");let ie=k.store.createPolicy({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,environmentId:k.environmentRecord.id,resourceId:X?.id,dataServiceId:z?.id,destinationId:D.id,name:t,resourceKind:m.kind,schedule:m.schedule??"daily",timezone:m.timezone??"UTC",retention:{keepLast:Number(m.keep)||7,expireAfterDays:Number(m.expire)||30},compression:m.compression,encryption:"destination",includePatterns:m.volume?[m.volume,...ne]:ne,excludePatterns:re,expectedRpoMinutes:Number(m.rpo)||1440,expectedRtoMinutes:Number(m.rto)||120,enabled:!m.disabled});Li(k,"policy_created",{policyId:ie.id,resourceKind:ie.resourceKind},ie.resourceId),n(`Created policy ${ie.name} (${ie.id}); next run ${ie.nextRunAt}`)}))),R.command("recovery:run <policy>","Queue an on-demand run of a recovery policy").option("--env <environment>","Target environment").action(async(t,m)=>$i(async()=>wi(m.env,async(k)=>{let D=Ah(k,t),L=k.coordinator.enqueueBackup(D,new Date().toISOString(),k.actor.id);Li(k,"run_queued",{policyId:D.id,backupJobId:L.id},D.resourceId),n(`Queued backup job ${L.id}; inspect it with cloud recovery:jobs`)}))),R.command("recovery:verify <point>","Queue independent verification of a recovery point").option("--env <environment>","Target environment").action(async(t,m)=>$i(async()=>wi(m.env,async(k)=>{let D=Pa(k,t),L=k.coordinator.enqueueVerification(D);Li(k,"verification_queued",{recoveryPointId:D.id,backupJobId:L.id},D.resourceId),n(`Queued verification job ${L.id}`)}))),R.command("recovery:restore <point> <target>","Validate or queue an isolated, in-place, or drill restore").option("--env <environment>","Target environment").option("--mode <mode>","isolated or in_place",{default:"isolated"}).option("--drill","Validate health and clean the isolated target").option("--execute","Queue the validated restore").option("--confirm <target>","Exact in-place target confirmation").option("--ack-downtime","Acknowledge downtime and data replacement").option("--safety <point>","Distinct verified safety recovery point").option("--metadata <json>","Provider restore metadata JSON").action(async(t,m,k)=>$i(async()=>wi(k.env,async(D)=>{if(!["isolated","in_place"].includes(k.mode??""))throw Error("--mode must be isolated or in_place.");if(k.drill&&k.mode==="in_place")throw Error("Recovery drills require --mode isolated.");let L=Pa(D,t),z={mode:k.mode,target:{...Rh(L,m),...k.metadata?{metadata:JSON.parse(k.metadata)}:{},inPlace:k.mode==="in_place"},targetName:m,confirm:k.confirm,recentAuth:!0,downtimeAcknowledged:!!k.ackDowntime,safetyBackupId:k.safety?Pa(D,k.safety).id:void 0},Y=D.coordinator.planRestore(L,z);if(!k.execute){e(JSON.stringify({...Y,productionExecutionCreated:!1},null,2));return}let X=D.coordinator.enqueueRestore(L,{...z,drill:!!k.drill,actorId:D.actor.id});Li(D,k.drill?"drill_queued":"restore_queued",{recoveryPointId:L.id,backupJobId:X.id,mode:z.mode,target:m},L.resourceId),n(`Queued ${k.drill?"drill":"restore"} job ${X.id}`)}))),R.command("recovery:protect <point>","Pin, hold, or release retention protection").option("--env <environment>","Target environment").option("--pin","Pin the point").option("--unpin","Remove its pin").option("--hold","Place a legal hold").option("--release-hold","Release its legal hold").action(async(t,m)=>$i(async()=>wi(m.env,async(k)=>{let D=Pa(k,t);if(![m.pin,m.unpin,m.hold,m.releaseHold].filter(Boolean).length)throw Error("Choose --pin, --unpin, --hold, or --release-hold.");let L=k.store.updateRecoveryPoint(D.id,{pinned:m.pin?!0:m.unpin?!1:D.pinned,held:m.hold?!0:m.releaseHold?!1:D.held});Li(k,"protection_updated",{recoveryPointId:D.id,pinned:L.pinned,held:L.held},D.resourceId),n(`${L.id}: pinned=${L.pinned} held=${L.held}`)}))),R.command("recovery:retention","Queue deletion of expired, unlocked, unheld recovery points").option("--env <environment>","Target environment").action(async(t)=>$i(async()=>wi(t.env,async(m)=>{let k=m.coordinator.enqueueRetention();Li(m,"retention_queued",{jobs:k.length}),n(`Queued ${k.length} retention cleanup job${k.length===1?"":"s"}.`)})))}function jl(R){R.command("api:list","List all API Gateway APIs").option("--region <region>","AWS region").action(async(t)=>{i("API Gateway APIs");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",{LambdaClient:D}=await import("./chunk-c6yrhzw5.js"),L=new D(k),z=new s("Fetching APIs...");z.start();let{CloudFormationClient:Y}=await import("./chunk-va3yvvs0.js"),re=(await new Y(k).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE"])).StackSummaries.filter((ie)=>ie.StackName?.includes("api")||ie.StackName?.includes("Api")||ie.StackName?.includes("API"));if(z.succeed("APIs listed"),e(`
|
|
646
|
+
Job ID: ${Y.RestoreJobId}`),e("Use `cloud backup:restore-jobs` to check status")}catch(k){r(`Failed to start restore: ${k.message}`),process.exit(1)}}),R.command("backup:jobs","List recent backup jobs").option("--region <region>","AWS region").option("--state <state>","Filter by state (CREATED, PENDING, RUNNING, COMPLETED, FAILED)").action(async(t)=>{i("Backup Jobs");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",D=await _r(k),L=new s("Fetching backup jobs...");L.start();let Y=(await D.listBackupJobs({ByState:t.state,MaxResults:50})).BackupJobs||[];if(L.succeed(`Found ${Y.length} job(s)`),Y.length===0){e("No backup jobs found");return}c(["Job ID","Resource","State","Started","Size"],Y.map((X)=>[X.BackupJobId?.substring(0,16)||"N/A",X.ResourceArn?.split(":").pop()||"N/A",X.State||"N/A",X.CreationDate?new Date(X.CreationDate).toLocaleString():"N/A",X.BackupSizeInBytes?Nu(X.BackupSizeInBytes):"N/A"]))}catch(m){r(`Failed to list backup jobs: ${m.message}`),process.exit(1)}}),R.command("backup:restore-jobs","List recent restore jobs").option("--region <region>","AWS region").option("--status <status>","Filter by status").action(async(t)=>{i("Restore Jobs");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",D=await _r(k),L=new s("Fetching restore jobs...");L.start();let Y=(await D.listRestoreJobs({ByStatus:t.status,MaxResults:50})).RestoreJobs||[];if(L.succeed(`Found ${Y.length} job(s)`),Y.length===0){e("No restore jobs found");return}c(["Job ID","Resource","Status","Started","Completed"],Y.map((X)=>[X.RestoreJobId?.substring(0,16)||"N/A",X.CreatedResourceArn?.split(":").pop()||"Pending",X.Status||"N/A",X.CreationDate?new Date(X.CreationDate).toLocaleString():"N/A",X.CompletionDate?new Date(X.CompletionDate).toLocaleString():"-"]))}catch(m){r(`Failed to list restore jobs: ${m.message}`),process.exit(1)}}),R.command("backup:schedule","Show backup schedule overview").option("--region <region>","AWS region").action(async(t)=>{i("Backup Schedule Overview");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",D=await _r(k),L=new s("Fetching backup plans...");L.start();let Y=(await D.listBackupPlans()).BackupPlansList||[];if(L.succeed(`Found ${Y.length} backup plan(s)`),Y.length===0){e("No backup plans configured");return}for(let X of Y){e(`
|
|
647
|
+
${X.BackupPlanName}`),e("=".repeat(40));try{let re=(await D.getBackupPlan(X.BackupPlanId)).BackupPlan?.Rules||[];for(let ae of re)if(e(` Rule: ${ae.RuleName}`),e(` Schedule: ${ae.ScheduleExpression}`),e(` Vault: ${ae.TargetBackupVaultName}`),ae.Lifecycle?.DeleteAfterDays)e(` Retention: ${ae.Lifecycle.DeleteAfterDays} days`);let ie=await D.listBackupSelections(X.BackupPlanId);if(ie.BackupSelectionsList&&ie.BackupSelectionsList.length>0)e(` Selections: ${ie.BackupSelectionsList.length}`)}catch{e(" (Unable to load plan details)")}}}catch(m){r(`Failed to get schedule: ${m.message}`),process.exit(1)}})}function Nu(R){if(R===0)return"0 B";let t=1024,m=["B","KB","MB","GB","TB"],k=Math.floor(Math.log(R)/Math.log(t));return`${Number.parseFloat((R/t**k).toFixed(2))} ${m[k]}`}async function kh(R){let t=await Ne(),m=y(process.cwd(),t),k=R??Object.keys(t.environments??{})[0]??"production",D=m.environments.get(k);if(!D)throw m.store.close(),Error(`Environment ${k} was not found.`);let L=new S(m.store,{workerId:`cli:${process.pid}`}),z=new li(m.store),Y=new tn(z,L),X=new sr(m.store),ne=new ar(m.store,_(process.cwd())),re=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:t,controlPlane:m,env:k,environmentRecord:D,queue:L,store:z,coordinator:Y,dataServices:X,secrets:ne,actor:re}}async function bi(R,t){let m=await kh(R);try{return await t(m)}finally{m.controlPlane.store.close()}}async function $i(R){try{await R()}catch(t){r(t instanceof Error?t.message:String(t))}}function Li(R,t,m,k){R.controlPlane.store.appendEvent({organizationId:R.controlPlane.organization.id,projectId:R.controlPlane.project.id,resourceId:k,actorId:R.actor.id,type:`backup.${t}`,payload:m})}function Ah(R,t){let m=R.store.getPolicy(t)??R.store.listPolicies(R.controlPlane.project.id,R.environmentRecord.id).find((k)=>k.name===t);if(!m||m.environmentId!==R.environmentRecord.id)throw Error(`Backup policy ${t} was not found in ${R.env}.`);return m}function Pa(R,t){let m=R.store.getRecoveryPoint(t),k=R.store.listRecoveryPoints(R.controlPlane.project.id).filter((L)=>L.id.startsWith(t)),D=m??(k.length===1?k[0]:void 0);if(!D)throw Error(k.length>1?`Recovery point prefix ${t} is ambiguous.`:`Recovery point ${t} was not found.`);return D}function Ou(R,t){let m=R.store.getDestination(t)??R.store.listDestinations(R.controlPlane.project.id).find((k)=>k.name===t);if(!m)throw Error(`Backup destination ${t} was not found.`);return m}function Rh(R,t){return R.kind==="volume"?{volumeName:t}:R.kind==="logical_database"?{targetId:t,dataServiceId:t}:{targetId:t}}function Ih(R,t=[]){let m=new Map(t.map((k)=>[k.id,k.name]));return R.map((k)=>[k.id,k.status,k.verificationState,k.kind,m.get(k.policyId??"")??"manual",k.pointInTime,k.sizeBytes?`${k.sizeBytes} B`:"provider",k.held?"hold":k.pinned?"pinned":"retention"])}function Fl(R){R.command("recovery:list","Show backup coverage, destination health, and recent recovery points").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>$i(async()=>bi(t.env,async(m)=>{let k=m.store.listPolicies(m.controlPlane.project.id,m.environmentRecord.id),D=new Set(k.map((Y)=>Y.id)),L=m.store.listRecoveryPoints(m.controlPlane.project.id).filter((Y)=>!Y.policyId||D.has(Y.policyId)),z={coverage:m.store.coverage(m.controlPlane.project.id).filter((Y)=>D.has(Y.policy.id)),destinations:m.store.listDestinations(m.controlPlane.project.id).map((Y)=>({...Y,credentialRef:void 0,encryptionKeyRef:void 0,credentialsConfigured:!!Y.credentialRef,clientEncryptionConfigured:!!Y.encryptionKeyRef})),policies:k,recoveryPoints:L,jobs:m.store.listJobs(m.controlPlane.project.id).filter((Y)=>!Y.policyId||D.has(Y.policyId))};if(t.json)e(JSON.stringify(z,null,2));else c(["Policy","RPO","Destination","Latest point","Verification"],z.coverage.map((Y)=>[Y.policy.name,Y.missedRpo?"MISSED":"protected",Y.destinationHealthy?"healthy":"attention",Y.lastRecoveryPoint?.pointInTime??"none",Y.lastRecoveryPoint?.verificationState??"none"])),c(["ID","State","Verification","Kind","Policy","Point in time","Size","Protection"],Ih(L,k))}))),R.command("recovery:jobs","List durable backup, verification, restore, drill, and cleanup jobs").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>$i(async()=>bi(t.env,async(m)=>{let k=new Set(m.store.listPolicies(m.controlPlane.project.id,m.environmentRecord.id).map((L)=>L.id)),D=m.store.listJobs(m.controlPlane.project.id).filter((L)=>!L.policyId||k.has(L.policyId));if(t.json)e(JSON.stringify(D,null,2));else c(["ID","Kind","State","Phase","Operation","Started","Finished","Error"],D.map((L)=>[L.id,L.kind,L.status,String(L.progress.phase??"queued"),L.operationId??"—",L.startedAt??"—",L.finishedAt??"—",L.error??"—"]))}))),R.command("recovery:destination:add <name>","Connect an S3, S3-compatible, or AWS Backup destination").option("--env <environment>","Target environment").option("--provider <provider>","aws_s3, s3_compatible, or aws_backup",{default:"aws_s3"}).option("--bucket <bucket>","Object bucket").option("--endpoint <url>","Origin-only HTTPS endpoint").option("--allow-private","Allow a private endpoint").option("--path-style","Force path-style S3 URLs").option("--prefix <prefix>","Object key prefix").option("--region <region>","Destination region").option("--credentials-env <name>","Environment variable containing credential JSON").option("--encryption <mode>","provider, client_side, or both",{default:"provider"}).option("--encryption-key-env <name>","Environment variable containing the client encryption key").option("--lock-days <days>","Default immutable retention days").action(async(t,m)=>$i(async()=>bi(m.env,async(k)=>{if(!["aws_s3","s3_compatible","aws_backup"].includes(m.provider??""))throw Error("--provider must be aws_s3, s3_compatible, or aws_backup.");if(!["provider","client_side","both"].includes(m.encryption??""))throw Error("--encryption must be provider, client_side, or both.");let D=`secret://data-services/backups/${k.controlPlane.project.id}/${t}`,L=[];try{let z,Y;if(m.credentialsEnv){let ne=process.env[m.credentialsEnv];if(!ne)throw Error(`${m.credentialsEnv} is empty.`);z=`${D}/credentials`,await k.secrets.put(z,ne),L.push(z)}if(m.encryption!=="provider"){if(!m.encryptionKeyEnv)throw Error("Client encryption requires --encryption-key-env.");let ne=process.env[m.encryptionKeyEnv];if(!ne||ne.length<32)throw Error(`${m.encryptionKeyEnv} must contain at least 32 characters.`);Y=`${D}/encryption-key`,await k.secrets.put(Y,ne),L.push(Y)}let X=k.store.createDestination({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,name:t,provider:m.provider,endpoint:m.endpoint,endpointPolicy:m.allowPrivate?"allow_private":"public_https",bucket:m.bucket,prefix:m.prefix??"",region:m.region??k.config.project.region,forcePathStyle:!!m.pathStyle,credentialRef:z,encryption:m.encryption,encryptionKeyRef:Y,immutability:{objectLock:!!m.lockDays,defaultRetentionDays:m.lockDays?Number(m.lockDays):void 0},status:"untested"});Li(k,"destination_created",{destinationId:X.id,provider:X.provider}),n(`Created destination ${X.name} (${X.id}); test it with cloud recovery:destination:test ${X.name}`)}catch(z){for(let Y of L)await k.secrets.remove(Y).catch(()=>{});throw z}}))),R.command("recovery:destination:test <destination>","Write, read, verify, and clean a destination health object").option("--env <environment>","Target environment").action(async(t,m)=>$i(async()=>bi(m.env,async(k)=>{let D=Ou(k,t);if(D.provider==="aws_backup")throw Error("AWS Backup is verified by creating a provider recovery point.");try{await new di(k.secrets).test(D),k.store.recordDestinationTest(D.id,{ok:!0}),Li(k,"destination_tested",{destinationId:D.id,ok:!0}),n(`${D.name} passed write, read, checksum, and cleanup checks.`)}catch(L){let z=L instanceof Error?L.message:String(L);throw k.store.recordDestinationTest(D.id,{ok:!1,error:z}),Li(k,"destination_tested",{destinationId:D.id,ok:!1}),L}}))),R.command("recovery:policy:add <name>","Create a scheduled recovery policy").option("--env <environment>","Target environment").option("--destination <id>","Destination ID or name").option("--kind <kind>","managed_database, logical_database, volume, files, control_plane, or infrastructure").option("--data-service <id>","Data service ID or name").option("--resource <id>","Resource ID or slug").option("--volume <name>","Named Docker volume").option("--include <path>","Project-relative include path or provider setting (repeatable)").option("--exclude <pattern>","Archive exclude pattern (repeatable)").option("--schedule <expression>","Cron, rate, or preset",{default:"daily"}).option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--rpo <minutes>","Recovery point objective",{default:"1440"}).option("--rto <minutes>","Recovery time objective",{default:"120"}).option("--keep <count>","Minimum latest points",{default:"7"}).option("--expire <days>","Expiry days",{default:"30"}).option("--compression <kind>","none, gzip, or zstd",{default:"gzip"}).option("--disabled","Create disabled").action(async(t,m)=>$i(async()=>bi(m.env,async(k)=>{if(!m.destination)throw Error("--destination is required.");if(!["managed_database","logical_database","volume","files","control_plane","infrastructure"].includes(m.kind??""))throw Error("--kind must be managed_database, logical_database, volume, files, control_plane, or infrastructure.");let D=Ou(k,m.destination),L=k.dataServices.list(k.controlPlane.project.id,k.environmentRecord.id),z=m.dataService?k.dataServices.get(m.dataService)??L.find((ae)=>ae.name===m.dataService):void 0,Y=k.controlPlane.store.listResources(k.controlPlane.project.id,k.environmentRecord.id),X=m.resource?Y.find((ae)=>ae.id===m.resource||ae.slug===m.resource):void 0,ne=m.include?Array.isArray(m.include)?m.include:[m.include]:[],re=m.exclude?Array.isArray(m.exclude)?m.exclude:[m.exclude]:[];if(["managed_database","logical_database"].includes(m.kind??"")&&!z)throw Error("--data-service must identify a service in this environment.");if(m.kind==="volume"&&(!X||!m.volume))throw Error("Volume policies require --resource and --volume.");if(m.kind==="files"&&(!X||!ne.length))throw Error("File policies require --resource and at least one --include path.");if(m.kind==="infrastructure"&&(!ne.some((ae)=>ae.startsWith("resource:arn:"))||!ne.some((ae)=>ae.startsWith("role:arn:"))))throw Error("Infrastructure policies require --include resource:<arn> and --include role:<arn>.");let ie=k.store.createPolicy({organizationId:k.controlPlane.organization.id,projectId:k.controlPlane.project.id,environmentId:k.environmentRecord.id,resourceId:X?.id,dataServiceId:z?.id,destinationId:D.id,name:t,resourceKind:m.kind,schedule:m.schedule??"daily",timezone:m.timezone??"UTC",retention:{keepLast:Number(m.keep)||7,expireAfterDays:Number(m.expire)||30},compression:m.compression,encryption:"destination",includePatterns:m.volume?[m.volume,...ne]:ne,excludePatterns:re,expectedRpoMinutes:Number(m.rpo)||1440,expectedRtoMinutes:Number(m.rto)||120,enabled:!m.disabled});Li(k,"policy_created",{policyId:ie.id,resourceKind:ie.resourceKind},ie.resourceId),n(`Created policy ${ie.name} (${ie.id}); next run ${ie.nextRunAt}`)}))),R.command("recovery:run <policy>","Queue an on-demand run of a recovery policy").option("--env <environment>","Target environment").action(async(t,m)=>$i(async()=>bi(m.env,async(k)=>{let D=Ah(k,t),L=k.coordinator.enqueueBackup(D,new Date().toISOString(),k.actor.id);Li(k,"run_queued",{policyId:D.id,backupJobId:L.id},D.resourceId),n(`Queued backup job ${L.id}; inspect it with cloud recovery:jobs`)}))),R.command("recovery:verify <point>","Queue independent verification of a recovery point").option("--env <environment>","Target environment").action(async(t,m)=>$i(async()=>bi(m.env,async(k)=>{let D=Pa(k,t),L=k.coordinator.enqueueVerification(D);Li(k,"verification_queued",{recoveryPointId:D.id,backupJobId:L.id},D.resourceId),n(`Queued verification job ${L.id}`)}))),R.command("recovery:restore <point> <target>","Validate or queue an isolated, in-place, or drill restore").option("--env <environment>","Target environment").option("--mode <mode>","isolated or in_place",{default:"isolated"}).option("--drill","Validate health and clean the isolated target").option("--execute","Queue the validated restore").option("--confirm <target>","Exact in-place target confirmation").option("--ack-downtime","Acknowledge downtime and data replacement").option("--safety <point>","Distinct verified safety recovery point").option("--metadata <json>","Provider restore metadata JSON").action(async(t,m,k)=>$i(async()=>bi(k.env,async(D)=>{if(!["isolated","in_place"].includes(k.mode??""))throw Error("--mode must be isolated or in_place.");if(k.drill&&k.mode==="in_place")throw Error("Recovery drills require --mode isolated.");let L=Pa(D,t),z={mode:k.mode,target:{...Rh(L,m),...k.metadata?{metadata:JSON.parse(k.metadata)}:{},inPlace:k.mode==="in_place"},targetName:m,confirm:k.confirm,recentAuth:!0,downtimeAcknowledged:!!k.ackDowntime,safetyBackupId:k.safety?Pa(D,k.safety).id:void 0},Y=D.coordinator.planRestore(L,z);if(!k.execute){e(JSON.stringify({...Y,productionExecutionCreated:!1},null,2));return}let X=D.coordinator.enqueueRestore(L,{...z,drill:!!k.drill,actorId:D.actor.id});Li(D,k.drill?"drill_queued":"restore_queued",{recoveryPointId:L.id,backupJobId:X.id,mode:z.mode,target:m},L.resourceId),n(`Queued ${k.drill?"drill":"restore"} job ${X.id}`)}))),R.command("recovery:protect <point>","Pin, hold, or release retention protection").option("--env <environment>","Target environment").option("--pin","Pin the point").option("--unpin","Remove its pin").option("--hold","Place a legal hold").option("--release-hold","Release its legal hold").action(async(t,m)=>$i(async()=>bi(m.env,async(k)=>{let D=Pa(k,t);if(![m.pin,m.unpin,m.hold,m.releaseHold].filter(Boolean).length)throw Error("Choose --pin, --unpin, --hold, or --release-hold.");let L=k.store.updateRecoveryPoint(D.id,{pinned:m.pin?!0:m.unpin?!1:D.pinned,held:m.hold?!0:m.releaseHold?!1:D.held});Li(k,"protection_updated",{recoveryPointId:D.id,pinned:L.pinned,held:L.held},D.resourceId),n(`${L.id}: pinned=${L.pinned} held=${L.held}`)}))),R.command("recovery:retention","Queue deletion of expired, unlocked, unheld recovery points").option("--env <environment>","Target environment").action(async(t)=>$i(async()=>bi(t.env,async(m)=>{let k=m.coordinator.enqueueRetention();Li(m,"retention_queued",{jobs:k.length}),n(`Queued ${k.length} retention cleanup job${k.length===1?"":"s"}.`)})))}function jl(R){R.command("api:list","List all API Gateway APIs").option("--region <region>","AWS region").action(async(t)=>{i("API Gateway APIs");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1",{LambdaClient:D}=await import("./chunk-vrn0akcn.js"),L=new D(k),z=new s("Fetching APIs...");z.start();let{CloudFormationClient:Y}=await import("./chunk-zred4827.js"),re=(await new Y(k).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE"])).StackSummaries.filter((ie)=>ie.StackName?.includes("api")||ie.StackName?.includes("Api")||ie.StackName?.includes("API"));if(z.succeed("APIs listed"),e(`
|
|
648
648
|
API-related CloudFormation stacks:`),re.length===0)e("No API Gateway stacks found"),e(`
|
|
649
649
|
To create an API, you can:`),e(" 1. Use cloud.config.ts to define your API"),e(" 2. Deploy with `cloud deploy`");else c(["Stack Name","Status","Created"],re.map((ie)=>[ie.StackName||"N/A",ie.StackStatus||"N/A",ie.CreationTime?new Date(ie.CreationTime).toLocaleDateString():"N/A"]));e("\nTip: Use AWS Console or `aws apigateway get-rest-apis` for detailed API listing")}catch(m){r(`Failed to list APIs: ${m.message}`),process.exit(1)}}),R.command("api:describe <apiId>","Show API Gateway API details").option("--region <region>","AWS region").action(async(t,m)=>{i(`API: ${t}`);try{let k=await Ne(),D=m.region||k.project.region||"us-east-1";e(`API ID: ${t}`),e(`Region: ${D}`),e(""),e("For detailed API information, use AWS CLI:"),e(` aws apigateway get-rest-api --rest-api-id ${t} --region ${D}`),e(` aws apigateway get-resources --rest-api-id ${t} --region ${D}`),e(` aws apigateway get-stages --rest-api-id ${t} --region ${D}`)}catch(k){r(`Failed to describe API: ${k.message}`),process.exit(1)}}),R.command("api:stages <apiId>","List API stages").option("--region <region>","AWS region").action(async(t,m)=>{i(`API Stages: ${t}`);try{let k=await Ne(),D=m.region||k.project.region||"us-east-1";e(`API ID: ${t}`),e(`Region: ${D}`),e(""),e("Common stages:"),e(" - prod (production)"),e(" - staging"),e(" - dev (development)"),e(""),e("For detailed stage information, use AWS CLI:"),e(` aws apigateway get-stages --rest-api-id ${t} --region ${D}`)}catch(k){r(`Failed to list stages: ${k.message}`),process.exit(1)}}),R.command("api:deploy <apiId>","Deploy API to a stage").option("--region <region>","AWS region",{default:"us-east-1"}).option("--stage <name>","Stage name",{default:"prod"}).option("--description <text>","Deployment description").action(async(t,m)=>{i("Deploy API");try{if(e(`API ID: ${t}`),e(`Stage: ${m.stage}`),!await d(`
|
|
650
650
|
Deploy to this stage?`,!0)){e("Operation cancelled");return}if(e(""),e("To deploy an API Gateway API:"),e(" aws apigateway create-deployment \\"),e(` --rest-api-id ${t} \\`),e(` --stage-name ${m.stage} \\`),e(` --region ${m.region}`),m.description)e(` --description "${m.description}"`)}catch(k){r(`Failed to deploy API: ${k.message}`),process.exit(1)}}),R.command("api:domains","List custom domain names").option("--region <region>","AWS region").action(async(t)=>{i("API Gateway Custom Domains");try{let m=await Ne(),k=t.region||m.project.region||"us-east-1";e(`Region: ${k}`),e(""),e("To list custom domains, use AWS CLI:"),e(` aws apigateway get-domain-names --region ${k}`),e(""),e("To create a custom domain:"),e(" 1. Request or import an SSL certificate in ACM"),e(" 2. Create a custom domain in API Gateway"),e(" 3. Create a base path mapping to your API"),e(" 4. Add a DNS record pointing to the distribution")}catch(m){r(`Failed to list domains: ${m.message}`),process.exit(1)}}),R.command("api:usage <apiId>","Show API usage statistics").option("--region <region>","AWS region").option("--stage <name>","Stage name",{default:"prod"}).option("--days <number>","Number of days to show",{default:"7"}).action(async(t,m)=>{i("API Usage Statistics");try{let k=await Ne(),D=m.region||k.project.region||"us-east-1";e(`API ID: ${t}`),e(`Stage: ${m.stage}`),e(`Period: Last ${m.days} days`),e("");let L=new Date,z=new Date;z.setDate(z.getDate()-Number.parseInt(m.days)),e("To view API metrics in CloudWatch:"),e(" aws cloudwatch get-metric-statistics \\"),e(" --namespace AWS/ApiGateway \\"),e(" --metric-name Count \\"),e(` --dimensions Name=ApiName,Value=${t} Name=Stage,Value=${m.stage} \\`),e(` --start-time ${z.toISOString()} \\`),e(` --end-time ${L.toISOString()} \\`),e(" --period 86400 \\"),e(" --statistics Sum \\"),e(` --region ${D}`),e(""),e("Available metrics:"),e(" - Count: Total API calls"),e(" - Latency: Response latency"),e(" - 4XXError: Client errors"),e(" - 5XXError: Server errors"),e(" - IntegrationLatency: Backend latency")}catch(k){r(`Failed to get usage: ${k.message}`),process.exit(1)}}),R.command("api:export <apiId>","Export API specification").option("--region <region>","AWS region",{default:"us-east-1"}).option("--stage <name>","Stage name",{default:"prod"}).option("--format <format>","Export format (oas30, swagger)",{default:"oas30"}).option("--output <file>","Output file path").action(async(t,m)=>{i("Export API Specification");try{e(`API ID: ${t}`),e(`Stage: ${m.stage}`),e(`Format: ${m.format==="oas30"?"OpenAPI 3.0":"Swagger 2.0"}`),e("");let k=m.format==="oas30"?"oas30":"swagger";e("To export the API specification:"),e(" aws apigateway get-export \\"),e(` --rest-api-id ${t} \\`),e(` --stage-name ${m.stage} \\`),e(` --export-type ${k} \\`),e(" --accepts application/json \\"),e(` --region ${m.region} \\`),e(` ${m.output||"api-spec.json"}`)}catch(k){r(`Failed to export API: ${k.message}`),process.exit(1)}}),R.command("api:logs <apiId>","View API Gateway logs").option("--region <region>","AWS region").option("--stage <name>","Stage name",{default:"prod"}).option("--tail","Tail the logs").action(async(t,m)=>{i("API Gateway Logs");try{let k=await Ne(),D=m.region||k.project.region||"us-east-1",L=`API-Gateway-Execution-Logs_${t}/${m.stage}`;if(e(`Log Group: ${L}`),e(""),e("To view logs:"),e(" aws logs filter-log-events \\"),e(` --log-group-name "${L}" \\`),e(` --region ${D}`),m.tail)e(""),e("For real-time log tailing, use:"),e(` aws logs tail "${L}" --follow --region ${D}`);e(""),e("Note: Ensure logging is enabled for the API stage."),e("You can enable it in the stage settings.")}catch(k){r(`Failed to get logs: ${k.message}`),process.exit(1)}}),R.command("api:test <apiId> <path>","Test an API endpoint").option("--region <region>","AWS region").option("--stage <name>","Stage name",{default:"prod"}).option("--method <method>","HTTP method",{default:"GET"}).option("--body <json>","Request body (JSON)").option("--header <header>","Request header (can be specified multiple times)").action(async(t,m,k)=>{i("Test API Endpoint");try{let D=await Ne(),L=k.region||D.project.region||"us-east-1",z=`https://${t}.execute-api.${L}.amazonaws.com/${k.stage}${m.startsWith("/")?m:`/${m}`}`;e(`URL: ${z}`),e(`Method: ${k.method}`);let Y={"Content-Type":"application/json"};if(k.header){let Ae=Array.isArray(k.header)?k.header:[k.header];for(let le of Ae){let[ke,...De]=le.split(":");Y[ke.trim()]=De.join(":").trim()}}e("Headers:");for(let[Ae,le]of Object.entries(Y))e(` ${Ae}: ${le}`);if(k.body)e(`Body: ${k.body}`);if(!await d(`
|
|
651
651
|
Send request?`,!0)){e("Operation cancelled");return}let ne=new s("Sending request...");ne.start();let re=Date.now(),ie=await fetch(z,{method:k.method,headers:Y,body:k.body}),ae=Date.now()-re,be=await ie.text();ne.succeed(`Response received (${ae}ms)`),e(`
|
|
652
652
|
Status: ${ie.status} ${ie.statusText}`),e(`
|
|
653
653
|
Response Headers:`),ie.headers.forEach((Ae,le)=>{e(` ${le}: ${Ae}`)}),e(`
|
|
654
|
-
Response Body:`);try{let Ae=JSON.parse(be);console.log(JSON.stringify(Ae,null,2))}catch{console.log(be)}}catch(D){r(`Failed to test API: ${D.message}`),process.exit(1)}})}async function So(R){let t=await Ne(),m=y(process.cwd(),t),k=new S(m.store,{workerId:`cli:${process.pid}`});try{return await R(k,m.project.id,m.organization.id)}finally{m.store.close()}}function Ph(R){if(!R)return;if(!["queued","running","succeeded","failed","cancelled","timed_out"].includes(R))throw Error("State must be queued, running, succeeded, failed, cancelled, or timed_out");return R}function xh(R,t){if(!R)return"—";let m=Math.max(0,new Date(t??Date.now()).getTime()-new Date(R).getTime());return m<1000?`${m}ms`:m<60000?`${Math.round(m/1000)}s`:`${Math.round(m/60000)}m`}function Bl(R){R.command("ops:list","List durable deployment and mutation jobs").option("--state <state>","Filter queue state").option("--all-projects","Show all locally authorized projects").action(async(t)=>{try{await So(async(m,k)=>{let D=m.list({projectId:t.allProjects?void 0:k,state:Ph(t.state),limit:500});c(["ID","State","Operation","Target","Attempt","Step / blocked","Queued","Duration"],D.map(({operation:L,job:z,approximatePosition:Y})=>[L.id,L.state,L.kind,L.resourceId??L.environmentId??L.projectId??"global",`${L.attempt}/${z.maxAttempts}`,z.blockedReason??z.currentStep??(Y?`${Y.ahead} ahead`:"—"),L.createdAt,xh(L.startedAt,L.finishedAt)]))})}catch(m){r(m instanceof Error?m.message:String(m))}}),R.command("ops:show <id>","Show a durable operation and its latest sanitized logs").option("--after <cursor>","Show logs after this sequence",{default:"0"}).action(async(t,m)=>{try{await So(async(k)=>{let D=k.view(t);if(!D)throw Error(`Operation ${t} was not found`);e(JSON.stringify(D,null,2));for(let L of k.logs(t,{after:Number(m.after)||0,limit:500}))e(`${L.sequence} ${L.stream.toUpperCase()}${L.step?` [${L.step}]`:""} ${L.message}`)})}catch(k){r(k instanceof Error?k.message:String(k))}}),R.command("ops:cancel <id>","Cancel queued work or request running cancellation").option("--yes","Skip confirmation").action(async(t,m)=>{try{await So(async(k)=>{let D=k.view(t);if(!D)throw Error(`Operation ${t} was not found`);if(!m.yes&&!await d(`Cancel ${D.operation.kind} (${t})?`,!1))return;let L=k.requestCancellation(t);n(`${L.state==="cancelled"?"Cancelled":"Cancellation requested for"} ${t}`)})}catch(k){r(k instanceof Error?k.message:String(k))}}),R.command("ops:retry <id>","Retry a terminal job using an allow-listed error class").option("--class <name>","Retryable error class").option("--delay <milliseconds>","Delay before the next attempt",{default:"0"}).action(async(t,m)=>{try{if(!m.class)throw Error("Pass --class with one of the retry classes shown by ops:show");await So(async(k)=>{let D=k.retry(t,m.class,{delayMs:Number(m.delay)||0});n(`Retry queued: ${D.id} · attempt ${D.attempt+1}`)})}catch(k){r(k instanceof Error?k.message:String(k))}}),R.command("ops:concurrency","Show effective durable queue concurrency limits").action(async()=>{try{await So(async(t)=>e(JSON.stringify(t.limits,null,2)))}catch(t){r(t instanceof Error?t.message:String(t))}}),R.command("ops:concurrency:set","Confirm and audit durable queue concurrency limits").option("--project <count>","Concurrent jobs per project").option("--environment <count>","Concurrent jobs per environment").option("--provider <count>","Concurrent jobs per provider").option("--builds <count>","Concurrent build slots").option("--confirm <text>",'Must equal "update queue limits"').action(async(t)=>{try{if(t.confirm!=="update queue limits")throw Error('Pass --confirm "update queue limits" to change production concurrency');let m={};for(let k of["project","environment","provider","builds"])if(t[k]!==void 0)m[k]=Number(t[k]);await So(async(k,D,L)=>n(`Queue concurrency updated: ${JSON.stringify(k.configureConcurrency(m,{organizationId:L}))}`))}catch(m){r(m instanceof Error?m.message:String(m))}}),R.command("ops:history:clear","Clear terminal jobs after their retention policy expires").option("--before <date>","Also require completion before this timestamp").option("--yes","Skip confirmation").action(async(t)=>{try{await So(async(m,k)=>{if(!t.yes&&!await d("Clear completed queue history whose retention has elapsed?",!1))return;let D=m.clearCompleted({projectId:k,before:t.before});n(`Cleared ${D} retained operation${D===1?"":"s"}.`)})}catch(m){r(m instanceof Error?m.message:String(m))}})}import{readFile as Dh,writeFile as Eh}from"node:fs/promises";function Th(R){if(R==null)return"—";if(R<60)return`${R}s`;if(R<3600)return`${Math.floor(R/60)}m`;if(R<86400)return`${Math.floor(R/3600)}h`;return`${Math.floor(R/86400)}d`}function Nh(R){return R==null?"—":fn(R)}function Oh(R){return R.map((t)=>[t.id,t.name,t.provider,t.kind,t.status,t.desiredReplicas==null?"—":`${t.runningReplicas??0}/${t.desiredReplicas}`,t.image??t.runtime??"—",Nh(t.resources?.memoryBytes??t.resources?.memoryLimitBytes),Th(t.ageSeconds)])}function Lh(R,t){let m=t.search?.toLowerCase();return{...R,workloads:R.workloads.filter((k)=>(!t.provider||k.provider===t.provider)&&(!t.status||k.status===t.status)&&(!m||`${k.name} ${k.id} ${k.links.service??""} ${Object.values(k.tags).join(" ")}`.toLowerCase().includes(m)))}}async function xo(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=new Ce(t,m);return{config:t,env:m,service:k}}async function xa(R,t){if(t!==void 0)return t;return I(`Type ${R.name} to confirm`,"")}async function Fh(R,t,m,k,D,L){return Da(R,`runtime.${t}`,{workloadId:R.id,provider:R.provider,action:t,replicas:m??null},D,()=>L.run({workloadId:R.id,action:t,replicas:m,confirm:k,recentAuth:!0}))}async function Da(R,t,m,k,D){let L=await Ne(),z=y(process.cwd(),L),Y=new S(z.store,{workerId:`cli:${process.pid}`});try{let X=z.environments.get(k),ne=z.store.listResources(z.project.id,X?.id).find((ae)=>ae.slug===R.links.service),re=z.store.getActorByExternalId("system","cli")??z.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI",metadata:{source:"cli"}}),ie=Y.enqueue({projectId:z.project.id,environmentId:X?.id,resourceId:ne?.id,actorId:re.id,kind:t,input:h(m),lockKey:ne?`resource:${ne.id}`:`runtime:${R.id}`,providerKey:R.provider,maxAttempts:1,timeoutSeconds:3600,resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:90}).operation;if(!Y.claim(ie.id))throw Error(`Workload is locked by another operation. Inspect ${ie.id} with cloud ops:show.`);try{let ae=await D();if(ae.stdout)Y.appendLog(ie.id,ae.stdout,{stream:"stdout"});if(ae.stderr)Y.appendLog(ie.id,ae.stderr,{stream:"stderr"});if(ae.ok)Y.complete(ie.id,{ok:!0,command:ae.command??null});else Y.fail(ie.id,ae.error??ae.stderr??"Runtime operation failed",{ok:!1});return{result:ae,operationId:ie.id}}catch(ae){throw Y.fail(ie.id,ae instanceof Error?ae.message:String(ae),{ok:!1,threw:!0}),ae}}finally{z.store.close()}}function _l(R){R.command("runtime:list","List services, containers, ECS tasks, and Lambda functions").option("--env <environment>","Target environment").option("--provider <provider>","Filter systemd, docker, ecs, or lambda").option("--status <status>","Filter normalized runtime status").option("--search <query>","Search names, IDs, services, and tags").option("--json","Print normalized JSON").action(async(t)=>{try{let{service:m}=await xo(t.env),k=Lh(await m.inventory(),t);if(t.json)e(JSON.stringify(k,null,2));else{c(["ID","Name","Provider","Kind","Status","Replicas","Image / runtime","Memory","Age"],Oh(k.workloads));for(let D of k.sources.filter((L)=>L.status!=="fresh"))a(`${D.id}: ${D.status}${D.message?` — ${D.message}`:""}`)}}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}}),R.command("runtime:inspect <workload>","Inspect normalized config, replicas, networks, mounts, links, and capabilities").option("--env <environment>","Target environment").action(async(t,m)=>{try{let{service:k}=await xo(m.env),D=await k.workload(t);if(!D)throw Error("Workload was not found in this project and environment");e(JSON.stringify(D,null,2))}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("runtime:logs <workload>","Read bounded logs for one authorized runtime target").option("--env <environment>","Target environment").option("--lines <count>","Maximum log lines",{default:"200"}).option("--json","Print structured JSON").action(async(t,m)=>{try{let{service:k}=await xo(m.env),D=await k.logs(t,{limit:Number(m.lines)||200});if(m.json)e(JSON.stringify(D,null,2));else for(let L of D.lines)console.log(`${L.timestamp??"—"} ${L.message}`)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("runtime:action <workload> <action>","Start, stop, restart, redeploy, or scale one workload").option("--env <environment>","Target environment").option("--replicas <count>","Desired replicas for scale").option("--confirm <name>","Exact workload name confirmation").action(async(t,m,k)=>{try{let D=m;if(!["start","stop","restart","redeploy","scale"].includes(D))throw Error("Action must be start, stop, restart, redeploy, or scale");let L=k.replicas==null?void 0:Number(k.replicas);if(D==="scale"&&(!Number.isInteger(L)||L<0))throw Error("Scale requires a non-negative integer --replicas value");let{env:z,service:Y}=await xo(k.env),X=await Y.inventory(),ne=X.workloads.find((ae)=>ae.id===t);if(!ne)throw Error("Workload was not found in this project and environment");let re=new Ce(await Ne(),z,{inventory:async()=>X}),ie=await Fh(ne,D,L,await xa(ne,k.confirm),z,re);if(!ie.result.ok)throw Error(ie.result.error??ie.result.stderr??"Runtime operation failed");if(n(`${D} completed for ${ne.name} · operation ${ie.operationId}`),ie.result.stdout)e(ie.result.stdout)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}}),R.command("runtime:exec <workload>","Run a scoped diagnostic preset or container command").option("--env <environment>","Target environment").option("--preset <preset>","Read-only preset: process, sockets, or filesystem").option("--command <command>","Free-form command (Docker containers only)").option("--confirm <name>","Exact workload name confirmation").action(async(t,m)=>{try{let{env:k,service:D}=await xo(m.env),z=(await D.inventory()).workloads.find((re)=>re.id===t);if(!z)throw Error("Workload was not found in this project and environment");let Y=["process","sockets","filesystem"].includes(String(m.preset))?m.preset:void 0;if(m.preset&&!Y)throw Error("Preset must be process, sockets, or filesystem");if(!Y&&!m.command)throw Error("Pass --preset or --command");let X=await xa(z,m.confirm),ne=await Da(z,"runtime.exec",{workloadId:t,provider:z.provider,preset:Y??null,freeForm:!!m.command},k,()=>D.exec({workloadId:t,preset:Y,command:m.command,confirm:X,recentAuth:!0}));if(!ne.result.ok)throw Error(ne.result.error??ne.result.stderr??"Exec failed");if(ne.result.stdout)e(ne.result.stdout);if(ne.result.stderr)a(ne.result.stderr);n(`Exec completed for ${z.name} · operation ${ne.operationId}`)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("runtime:download <workload> <remote> <local>","Download a bounded file from a service-owned path").option("--env <environment>","Target environment").option("--confirm <name>","Exact workload name confirmation").action(async(t,m,k,D)=>{try{let{env:L,service:z}=await xo(D.env),X=(await z.inventory()).workloads.find((ie)=>ie.id===t);if(!X)throw Error("Workload was not found in this project and environment");let ne=await xa(X,D.confirm),re=await Da(X,"runtime.file.read",{workloadId:t,provider:X.provider,path:m},L,()=>z.readFile({workloadId:t,path:m,confirm:ne,recentAuth:!0}));if(!re.result.ok||!re.result.contentBase64)throw Error(re.result.error??"File download failed");await Eh(k,Buffer.from(re.result.contentBase64,"base64")),n(`Downloaded ${re.result.size??0} bytes to ${k} · operation ${re.operationId}${re.result.truncated?" (truncated)":""}`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}}),R.command("runtime:upload <workload> <local> <remote>","Upload and atomically replace a file in a service-owned path").option("--env <environment>","Target environment").option("--confirm <name>","Exact workload name confirmation").action(async(t,m,k,D)=>{try{let{env:L,service:z}=await xo(D.env),X=(await z.inventory()).workloads.find((ae)=>ae.id===t);if(!X)throw Error("Workload was not found in this project and environment");let ne=await xa(X,D.confirm),re=(await Dh(m)).toString("base64"),ie=await Da(X,"runtime.file.write",{workloadId:t,provider:X.provider,path:k,bytes:Buffer.from(re,"base64").byteLength},L,()=>z.writeFile({workloadId:t,path:k,contentBase64:re,confirm:ne,recentAuth:!0}));if(!ie.result.ok)throw Error(ie.result.error??"File upload failed");n(`Uploaded ${ie.result.size??0} bytes to ${k} · operation ${ie.operationId}`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}})}function jh(R,t=new Date){let m=R.to?new Date(R.to):t;if(!Number.isFinite(m.getTime()))throw Error("--to must be a valid ISO-8601 instant");if(R.from){let L=new Date(R.from);if(!Number.isFinite(L.getTime()))throw Error("--from must be a valid ISO-8601 instant");return{from:L.toISOString(),to:m.toISOString()}}let k=String(R.range??"1h").match(/^(\d+)(m|h|d)$/);if(!k)throw Error("--range must use minutes, hours, or days (for example 30m, 6h, or 7d)");let D=k[2]==="m"?60000:k[2]==="h"?3600000:86400000;return{from:new Date(m.getTime()-Number(k[1])*D).toISOString(),to:m.toISOString()}}async function Ts(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=y(process.cwd(),t),D=k.environments.get(m),L=new rt(k.store);return{config:t,env:m,controlPlane:k,environmentRecord:D,telemetry:L}}function Lu(R,t,m){let k=(L)=>L?.split(",").map((z)=>z.trim()).filter(Boolean)||void 0,D=k(R.kind)?.filter((L)=>["metric","log","trace","request","event"].includes(L));return{projectId:t,environmentId:m,...jh(R),kinds:D,names:k(R.name),sources:k(R.source),text:R.text,limit:Math.min(5000,Math.max(1,Number(R.limit)||200))}}function Bh(R){let t=["timestamp","kind","source","name","level","value","unit","message","traceId","requestId","deploymentId","releaseId","workloadId"];return[t.join(","),...R.map((m)=>t.map((k)=>`"${String(m[k]??"").replaceAll('"','""')}"`).join(","))].join(`
|
|
655
|
-
`)}function Ml(R){R.command("telemetry:collect","Collect normalized host/runtime or AWS telemetry into the local control plane").option("--env <environment>","Target environment").option("--force","Bypass the provider-query cache").option("--json","Print structured JSON").action(async(t)=>{let m=await Ts(t.env);try{let k=await di({controlPlane:m.controlPlane.store,projectId:m.controlPlane.project.id,environmentId:m.environmentRecord?.id,config:m.config,environment:m.env,force:t.force});if(t.json)e(JSON.stringify(k,null,2));else n(`Collected ${k.collected} normalized records${k.cached?" (cached provider result)":""}.`),c(["Source","Freshness","Lag","Sampling","Retention"],k.statuses.map((D)=>[D.source,D.freshness,D.lagSeconds==null?"—":`${D.lagSeconds}s`,`${Math.round(D.samplingRate*100)}%`,`${D.retentionDays}d`]))}finally{m.controlPlane.store.close()}}),R.command("telemetry:query","Query bounded normalized metrics, logs, traces, requests, and events").option("--env <environment>","Target environment").option("--from <instant>","ISO-8601 start").option("--to <instant>","ISO-8601 end").option("--range <duration>","Relative window such as 1h or 7d",{default:"1h"}).option("--kind <kinds>","Comma-separated kinds").option("--name <names>","Comma-separated signal names").option("--source <sources>","Comma-separated sources").option("--text <query>","Search name/message").option("--limit <count>","Maximum records",{default:"200"}).option("--json","Print structured JSON").action(async(t)=>{let m=await Ts(t.env);try{let k=m.telemetry.query(Lu(t,m.controlPlane.project.id,m.environmentRecord?.id));if(t.json)e(JSON.stringify(k,null,2));else c(["Time","Kind","Source","Signal","Value / message","Correlation"],k.records.map((D)=>[D.timestamp,D.kind,D.source,D.name,D.message??(D.value==null?"—":`${D.value} ${D.unit??""}`),D.requestId??D.traceId??D.releaseId??D.deploymentId??"—"]));if(k.truncated)a("More records are available; narrow the window or use the dashboard cursor explorer.")}finally{m.controlPlane.store.close()}}),R.command("telemetry:export <file>","Export a bounded telemetry query as JSON or CSV").option("--env <environment>","Target environment").option("--from <instant>","ISO-8601 start").option("--to <instant>","ISO-8601 end").option("--range <duration>","Relative window",{default:"1h"}).option("--kind <kinds>","Comma-separated kinds").option("--name <names>","Comma-separated signal names").option("--source <sources>","Comma-separated sources").option("--text <query>","Search name/message").option("--limit <count>","Maximum records",{default:"5000"}).action(async(t,m)=>{let k=await Ts(m.env);try{let D=Lu(m,k.controlPlane.project.id,k.environmentRecord?.id),L=k.telemetry.query(D);await Bun.write(t,t.toLowerCase().endsWith(".csv")?Bh(L.records):JSON.stringify({exportedAt:new Date().toISOString(),query:D,...L},null,2)),n(`Exported ${L.records.length} records to ${t}${L.truncated?" (bounded result)":""}.`)}finally{k.controlPlane.store.close()}}),R.command("telemetry:compact","Downsample old metrics and enforce configured retention").option("--env <environment>","Target environment").action(async(t)=>{let m=await Ts(t.env);try{let k=tt(m.controlPlane.store,m.controlPlane.project.id);e(JSON.stringify(m.telemetry.enforceRetention(k,m.controlPlane.project.id),null,2))}finally{m.controlPlane.store.close()}}),R.command("telemetry:policy","Show or update sampling, retention, and local storage cost assumptions").option("--env <environment>","Target environment").option("--raw-days <days>","Raw retention days").option("--downsample-days <days>","Downsample after days").option("--sampling <rate>","Sampling rate from 0.01 to 1").option("--storage-usd-gb-month <amount>","Local storage cost assumption").option("--json","Print structured JSON").action(async(t)=>{let m=await Ts(t.env);try{let k={};if(t.rawDays!=null)k.rawDays=Number(t.rawDays);if(t.downsampleDays!=null)k.downsampleAfterDays=Number(t.downsampleDays);if(t.sampling!=null)k.samplingRate=Number(t.sampling);if(t.storageUsdGbMonth!=null)k.estimatedStorageUsdPerGbMonth=Number(t.storageUsdGbMonth);let D=Object.keys(k).length?Ti(m.controlPlane.store,m.controlPlane.project.id,k):tt(m.controlPlane.store,m.controlPlane.project.id),z=m.telemetry.status(m.controlPlane.project.id,m.environmentRecord?.id,D.rawDays,D.samplingRate).reduce((X,ne)=>X+ne.estimatedDailyBytes*30,0),Y={policy:D,estimatedMonthlyBytes:z,estimatedMonthlyCostUsd:Ei(z,D),costEstimateConfigured:D.estimatedStorageUsdPerGbMonth>0};if(t.json)e(JSON.stringify(Y,null,2));else e(JSON.stringify(D,null,2)),e(Y.costEstimateConfigured?`Estimated local storage: $${Y.estimatedMonthlyCostUsd}/month`:"Storage cost remains unavailable until --storage-usd-gb-month is set.")}finally{m.controlPlane.store.close()}})}async function oo(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=y(process.cwd(),t),D=k.environments.get(m),L=new St(k.store,{encryptionKey:_(process.cwd())});return{config:t,env:m,controlPlane:k,environmentRecord:D,alerts:L}}function Fu(R){return new Tr(R,{emailImpl:async(t)=>{let{email:m}=await import("./chunk-bphzq12c.js");return m.send(t)}})}function zl(R){R.command("health:list","List configured health checks and latest results").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let k=m.alerts.listHealthChecks(m.controlPlane.project.id,m.environmentRecord?.id).map((D)=>({...D,latest:m.alerts.listHealthResults(D.id,1)[0]}));if(t.json)e(JSON.stringify(k,null,2));else c(["ID","Name","Kind","Target","Status","Interval","Enabled"],k.map((D)=>[D.id,D.name,D.kind,D.target,D.latest?.status??"no data",`${D.intervalSeconds}s`,D.enabled?"yes":"no"]))}finally{m.controlPlane.store.close()}}),R.command("health:run <check>","Run one health check and evaluate linked alert rules").option("--env <environment>","Target environment").option("--test","Preview without persisting results or incidents").action(async(t,m)=>{let k=await oo(m.env);try{let D=k.alerts.getHealthCheck(t)??k.alerts.listHealthChecks(k.controlPlane.project.id,k.environmentRecord?.id).find((Y)=>Y.name===t);if(!D)throw Error("Health check was not found");let L=new je(k.alerts);if(m.test){let Y=await L.probe(D);e(JSON.stringify({...Y,productionIncidentCreated:!1},null,2));return}let z=await L.runAndEvaluate(D);if(e(JSON.stringify(z,null,2)),z.result.status==="healthy")n(`${D.name} is healthy.`);else a(`${D.name} is ${z.result.status}.`)}finally{k.controlPlane.store.close()}}),R.command("alerts:list","List pending, firing, silenced, and resolved alerts").option("--env <environment>","Target environment").option("--state <states>","Comma-separated states").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let k=m.alerts.listAlerts(m.controlPlane.project.id,{environmentId:m.environmentRecord?.id,states:t.state?.split(",").filter(Boolean)});if(t.json)e(JSON.stringify(k,null,2));else c(["ID","State","Severity","Title","Owner","First seen","Last seen"],k.map((D)=>[D.id,D.state,D.severity,D.title,D.ownerActorId??"unassigned",D.firstSeenAt,D.lastSeenAt]))}finally{m.controlPlane.store.close()}}),R.command("alerts:evaluate","Evaluate persisted telemetry rules now").option("--env <environment>","Target environment").action(async(t)=>{let m=await oo(t.env);try{let k=un(m.alerts,m.controlPlane.project.id,m.environmentRecord?.id);e(JSON.stringify(k,null,2))}finally{m.controlPlane.store.close()}}),R.command("alerts:ack <alert>","Acknowledge an alert as the CLI actor").option("--env <environment>","Target environment").action(async(t,m)=>{let k=await oo(m.env);try{let D=k.controlPlane.store.getActorByExternalId("system","cli")??k.controlPlane.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});n(`Acknowledged ${k.alerts.acknowledge(t,D.id).title}.`)}finally{k.controlPlane.store.close()}}),R.command("notifications:list","List notification channels, routes, and recent delivery diagnostics").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let k={channels:m.alerts.listChannels(m.controlPlane.organization.id),routes:m.alerts.listRoutes(m.controlPlane.organization.id),deliveries:m.alerts.listDeliveries({limit:100})};if(t.json)e(JSON.stringify(k,null,2));else c(["Channel","Type","Status","Tested","Error"],k.channels.map((D)=>[D.name,D.kind,D.status,D.lastTestedAt??"never",D.lastError??"—"])),c(["Event","State","Attempts","Status","Error"],k.deliveries.map((D)=>[D.eventType,D.state,`${D.attempt}/${D.maxAttempts}`,String(D.responseStatus??"—"),D.error??"—"]))}finally{m.controlPlane.store.close()}}),R.command("notifications:test <channel>","Send a channel test without creating a production incident").option("--env <environment>","Target environment").action(async(t,m)=>{let k=await oo(m.env);try{let D=k.alerts.getChannel(t)??k.alerts.listChannels(k.controlPlane.organization.id).find((z)=>z.name===t);if(!D)throw Error("Notification channel was not found");let L=await Fu(k.alerts).testChannel(D.id);if(L.ok)n("Test delivered; no production incident was created.");else throw Error(L.error)}finally{k.controlPlane.store.close()}}),R.command("notifications:retry <delivery>","Retry one failed notification delivery").option("--env <environment>","Target environment").action(async(t,m)=>{let k=await oo(m.env);try{let D=k.alerts.getDelivery(t);if(!D)throw Error("Notification delivery was not found");k.alerts.updateDelivery(t,{state:"retrying",attempt:Math.max(0,D.attempt-1),nextAttemptAt:new Date().toISOString()}),e(JSON.stringify(await Fu(k.alerts).deliver(t),null,2))}finally{k.controlPlane.store.close()}})}async function _h(R,t){let m=await Ne(),k=y(process.cwd(),m),D=!R&&t?new Zr(k.store).get(t):void 0,L=D?.environmentId?k.store.listEnvironments(k.project.id).find((be)=>be.id===D.environmentId)?.slug:void 0,z=R??L??Object.keys(m.environments??{})[0]??"production";if(!Object.hasOwn(m.environments??{},z))throw k.store.close(),Error(`Environment ${z} was not found`);let Y=k.environments.get(z);if(!Y)throw k.store.close(),Error(`Environment ${z} was not found in the control plane`);let X=new Zr(k.store),ne=new S(k.store,{workerId:`cli:${process.pid}`}),re=new ai(X,{queue:ne}),ie=k.store.listResources(k.project.id,Y.id);Yr(X,m,{organization:k.organization,project:k.project,environment:Y,resources:ie});let ae=k.store.getActorByExternalId("system","cli")??k.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:m,env:z,controlPlane:k,environmentRecord:Y,resources:ie,store:X,queue:ne,service:re,actor:ae}}async function zr(R,t,m){let k=await _h(R,m);try{return await t(k)}finally{k.controlPlane.store.close()}}async function er(R){try{await R()}catch(t){r(t instanceof Error?t.message:String(t))}}function Ho(R,t){let m=R.store.list(R.controlPlane.project.id,{environmentId:R.environmentRecord.id}),k=R.store.get(t)??m.find((D)=>D.name===t);if(!k||k.environmentId!==R.environmentRecord.id)throw Error(`Scheduled job ${t} was not found in ${R.env}`);return k}function Eo(R,t,m,k){R.controlPlane.store.appendEvent({organizationId:R.controlPlane.organization.id,projectId:R.controlPlane.project.id,resourceId:k,actorId:R.actor.id,type:`job.${t}`,payload:m})}function ju(R,t,m){if(R.environmentRecord.kind==="production"&&m!==t)throw Error(`Production ${t} requires --confirm ${t}`)}function Mh(R){if(!R)return{};if(!/^(?:secret|ssm|arn|env):\/\//.test(R))throw Error("Payloads must use a secret://, ssm://, arn://, or env:// reference");return{input:R}}function zh(R){return R.map((t)=>[t.id,t.enabled?"enabled":"disabled",t.provider,t.name,t.expression,t.timezone,t.nextRunAt??"—",t.reconciliationStatus])}function Ul(R){R.command("jobs:list","List server cron, EventBridge, and recurring control-plane jobs").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>er(async()=>zr(t.env,async(m)=>{let k=m.store.list(m.controlPlane.project.id,{environmentId:m.environmentRecord.id});if(t.json)e(JSON.stringify(k,null,2));else c(["ID","State","Provider","Name","Expression","Timezone","Next run","Reconciliation"],zh(k))}))),R.command("jobs:preview <expression>","Preview the next runs without creating production work").option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--from <date>","Preview from an ISO timestamp").option("--count <count>","Number of runs",{default:"5"}).action(async(t,m)=>er(async()=>{let k=pt(t,m.timezone??"UTC",m.from?new Date(m.from):new Date,Number(m.count)||5);e(JSON.stringify({...k,productionExecutionCreated:!1},null,2))})),R.command("jobs:add <name> <expression>","Create a managed recurring job").option("--env <environment>","Target environment").option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--operation <id>","Safe server operation target (scheduler, backup, or worker)").option("--resource <id>","Resource ID or slug").option("--starts-at <date>","Schedule window start").option("--ends-at <date>","Schedule window end").option("--flexible-minutes <count>","Provider flexible window",{default:"0"}).option("--missed <policy>","skip or catch_up",{default:"skip"}).option("--overlap <policy>","allow, forbid, or replace",{default:"forbid"}).option("--attempts <count>","Maximum attempts",{default:"3"}).option("--backoff <seconds>","Retry backoff seconds",{default:"30"}).option("--timeout <seconds>","Execution timeout",{default:"900"}).option("--dead-letter <reference>","Dead-letter ARN or secret reference").option("--payload-ref <reference>","Referenced payload; inline values are forbidden").option("--disabled","Create disabled").action(async(t,m,k)=>er(async()=>zr(k.env,async(D)=>{if(!["skip","catch_up"].includes(k.missed??"skip"))throw Error("--missed must be skip or catch_up");if(!["allow","forbid","replace"].includes(k.overlap??"forbid"))throw Error("--overlap must be allow, forbid, or replace");let L=H(D.config),Y={workers:D.store.listWorkers(D.controlPlane.project.id,D.environmentRecord.id).map((be)=>({name:be.name}))},X;if(L==="server"){let be=Jr(D.config,Y).filter((le)=>!le.danger&&["scheduler","backup","worker"].includes(le.group)),Ae=k.operation?be.find((le)=>le.id===k.operation):be.length===1?be[0]:void 0;if(!Ae)throw Error(`Choose a safe target with --operation. Available: ${be.map((le)=>le.id).join(", ")||"none"}`);X={kind:"dashboard_operation",operationId:Ae.id}}else X={kind:"serverless_scheduler",action:"run"};let ne=L==="server"?"server":"eventbridge",re=si({provider:ne,expression:m,flexibleMinutes:Number(k.flexibleMinutes)||0,missedRunPolicy:k.missed??"skip",overlapPolicy:k.overlap??"forbid"});if(!re.supported)throw Error(re.notes.join(" "));let ie=k.resource?D.resources.find((be)=>be.id===k.resource||be.slug===k.resource):void 0;if(k.resource&&!ie)throw Error(`Resource ${k.resource} was not found in ${D.env}`);let ae=D.store.create({organizationId:D.controlPlane.organization.id,projectId:D.controlPlane.project.id,environmentId:D.environmentRecord.id,resourceId:ie?.id,name:t,provider:ne,expression:m,timezone:k.timezone??"UTC",startsAt:k.startsAt,endsAt:k.endsAt,flexibleMinutes:Number(k.flexibleMinutes)||0,target:X,payloadRefs:Mh(k.payloadRef),missedRunPolicy:k.missed??"skip",overlapPolicy:k.overlap??"forbid",retryPolicy:{maxAttempts:Number(k.attempts)||3,backoffSeconds:Number(k.backoff)||30,deadLetterRef:k.deadLetter},timeoutSeconds:Number(k.timeout)||900,enabled:!k.disabled,origin:"managed",ownerActorId:D.actor.id,observedState:{},reconciliationStatus:"pending"});Eo(D,"created",{jobId:ae.id,provider:ae.provider,expression:ae.normalizedExpression},ae.resourceId),n(`Created ${ae.name} (${ae.id}); next run ${ae.nextRunAt??"outside the active window"}`)}))),R.command("jobs:update <job>","Update a managed job schedule and policies").option("--env <environment>","Target environment").option("--expression <expression>","Cron, preset, or rate expression").option("--timezone <timezone>","IANA timezone").option("--missed <policy>","skip or catch_up").option("--overlap <policy>","allow, forbid, or replace").option("--timeout <seconds>","Execution timeout").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t);if(D.origin==="config")throw Error("Config-defined schedules must be edited in cloud.config.ts and reconciled");if(m.missed&&!["skip","catch_up"].includes(m.missed))throw Error("--missed must be skip or catch_up");if(m.overlap&&!["allow","forbid","replace"].includes(m.overlap))throw Error("--overlap must be allow, forbid, or replace");let L=k.store.updateSchedule(D.id,{expression:m.expression,timezone:m.timezone,missedRunPolicy:m.missed,overlapPolicy:m.overlap,timeoutSeconds:m.timeout==null?void 0:Number(m.timeout)});Eo(k,"updated",{jobId:L.id,expression:L.normalizedExpression,version:L.version},L.resourceId),n(`Updated ${L.name}; next run ${L.nextRunAt??"outside the active window"}`)}))),R.command("jobs:run <job>","Queue a manual run through the durable operation worker").option("--env <environment>","Target environment").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t),L=k.service.enqueue(D,{trigger:"manual",actorId:k.actor.id});Eo(k,"run_queued",{jobId:D.id,executionId:L.id},D.resourceId),n(`Queued execution ${L.id}; inspect it with cloud jobs:history "${D.name}"`)}))),R.command("jobs:dispatch <job>","Dispatch an externally triggered schedule into the durable queue").option("--env <environment>","Target environment").option("--scheduled-for <date>","Provider scheduled timestamp").option("--scheduled","Mark this as a provider schedule trigger").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t);if(!D.enabled)throw Error(`${D.name} is disabled`);let L=m.scheduledFor?new Date(m.scheduledFor):new Date;if(!m.scheduledFor)L.setUTCSeconds(0,0);let z=L.toISOString(),Y=k.service.enqueue(D,{trigger:m.scheduled?"scheduled":"external",scheduledFor:z,actorId:k.actor.id}),X=ut(D.normalizedExpression,D.timezone,L,1)[0];k.store.markScheduled(D.id,z,X),n(`Accepted ${D.name} for ${z} as ${Y.id}`)},t)));for(let t of["enable","disable"])R.command(`jobs:${t} <job>`,`${t==="enable"?"Enable":"Disable"} a scheduled job`).option("--env <environment>","Target environment").option("--confirm <text>",`Production ${t} confirmation`).action(async(m,k)=>er(async()=>zr(k.env,async(D)=>{let L=Ho(D,m);if(t==="disable")ju(D,t,k.confirm);let z=D.store.setEnabled(L.id,t==="enable");Eo(D,t==="enable"?"enabled":"disabled",{jobId:z.id},z.resourceId),n(`${z.name} is ${z.enabled?"enabled":"disabled"}`)})));R.command("jobs:delete <job>","Delete a managed schedule").option("--env <environment>","Target environment").option("--confirm <text>","Must be delete in production").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t);ju(k,"delete",m.confirm),k.store.remove(D.id),Eo(k,"deleted",{jobId:D.id,name:D.name},D.resourceId),n(`Deleted ${D.name}`)}))),R.command("jobs:history <job>","Show execution attempts, output, errors, and redacted logs").option("--env <environment>","Target environment").option("--limit <count>","Maximum executions",{default:"100"}).option("--logs","Print execution logs").option("--json","Print structured JSON").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t),L=k.store.listExecutions(D.id,Number(m.limit)||100).map((z)=>({...z,logs:m.logs&&z.operationId?k.queue.logs(z.operationId,{limit:500}):[]}));if(m.json)e(JSON.stringify(L,null,2));else if(c(["ID","Status","Trigger","Scheduled for","Attempt","Started","Finished","Error"],L.map((z)=>[z.id,z.status,z.trigger,z.scheduledFor,String(z.attempt),z.startedAt??"—",z.finishedAt??"—",z.error??"—"])),m.logs)for(let z of L)for(let Y of z.logs)e(`${z.id} ${Y.sequence} ${Y.stream.toUpperCase()} ${Y.message}`)}))),R.command("jobs:reconcile","Import declarative schedules and workers without destructive deletion").option("--env <environment>","Target environment").action(async(t)=>er(async()=>zr(t.env,async(m)=>{let k=Yr(m.store,m.config,{organization:m.controlPlane.organization,project:m.controlPlane.project,environment:m.environmentRecord,resources:m.resources});Eo(m,"reconciled",k),n(`Reconciled ${k.jobs} schedules and ${k.workers} workers; retained ${k.drifted} missing definitions as drifted`)}))),R.command("workers:list","List configured systemd, ECS, and Lambda workers").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>er(async()=>zr(t.env,async(m)=>{let k=m.store.listWorkers(m.controlPlane.project.id,m.environmentRecord.id);if(t.json)e(JSON.stringify(k,null,2));else c(["ID","Provider","Name","Queue","Processes","Timeout","Restart","Reconciliation"],k.map((D)=>[D.id,D.provider,D.name,D.queue,String(D.processes),`${D.timeoutSeconds}s`,D.restartPolicy,D.reconciliationStatus]))}))),R.command("workers:restart <worker>","Gracefully restart an allowlisted systemd worker").option("--env <environment>","Target environment").option("--confirm <name>","Exact worker name confirmation").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=k.store.listWorkers(k.controlPlane.project.id,k.environmentRecord.id),L=k.store.getWorker(t)??D.find((X)=>X.name===t);if(!L||L.environmentId!==k.environmentRecord.id)throw Error(`Worker ${t} was not found in ${k.env}`);if(m.confirm!==L.name)throw Error(`Restart requires --confirm "${L.name}"`);if(L.provider!=="systemd")throw Error(`${L.provider} workers are provider/config managed and do not expose a safe restart adapter`);let z=ii(String(L.target.operationId??""),k.config,{workers:D.map((X)=>({name:X.name}))});if(!z||z.danger||z.group!=="worker")throw Error("The allowlisted worker restart operation is unavailable");let Y=await Xr(k.config,k.env,z);if(k.store.reconcileWorker(L.id,Y.ok?"in_sync":"unavailable",{...L.observedState,lastRestartAt:new Date().toISOString(),command:Y.command??null,error:Y.error??Y.stderr??null}),Eo(k,"worker_restarted",{workerId:L.id,ok:Y.ok},L.resourceId),!Y.ok)throw Error(Y.error??Y.stderr??"Worker restart failed");n(`Restarted ${L.name}`)})))}function Wl(R){R.command("tunnel","Start a local tunnel to expose your local server").option("--port <number>","Local port to expose",{default:"3000"}).option("--subdomain <name>","Custom subdomain (if available)").option("--host <hostname>","Local hostname",{default:"localhost"}).option("--server <url>","Tunnel server URL",{default:"localtunnel.dev"}).option("--verbose","Enable verbose logging").action(async(t)=>{i("Local Tunnel");let m=Number.parseInt(t.port);if(e(`Local server: ${t.host}:${m}`),e(`Tunnel server: ${t.server}`),t.subdomain)e(`Requested subdomain: ${t.subdomain}`);let k=new s("Connecting to tunnel server...");k.start();try{let D;try{let ne=await import("localtunnels");D=ne.TunnelClient}catch{k.fail("localtunnels package not found"),e(`
|
|
654
|
+
Response Body:`);try{let Ae=JSON.parse(be);console.log(JSON.stringify(Ae,null,2))}catch{console.log(be)}}catch(D){r(`Failed to test API: ${D.message}`),process.exit(1)}})}async function So(R){let t=await Ne(),m=y(process.cwd(),t),k=new S(m.store,{workerId:`cli:${process.pid}`});try{return await R(k,m.project.id,m.organization.id)}finally{m.store.close()}}function Ph(R){if(!R)return;if(!["queued","running","succeeded","failed","cancelled","timed_out"].includes(R))throw Error("State must be queued, running, succeeded, failed, cancelled, or timed_out");return R}function xh(R,t){if(!R)return"—";let m=Math.max(0,new Date(t??Date.now()).getTime()-new Date(R).getTime());return m<1000?`${m}ms`:m<60000?`${Math.round(m/1000)}s`:`${Math.round(m/60000)}m`}function Bl(R){R.command("ops:list","List durable deployment and mutation jobs").option("--state <state>","Filter queue state").option("--all-projects","Show all locally authorized projects").action(async(t)=>{try{await So(async(m,k)=>{let D=m.list({projectId:t.allProjects?void 0:k,state:Ph(t.state),limit:500});c(["ID","State","Operation","Target","Attempt","Step / blocked","Queued","Duration"],D.map(({operation:L,job:z,approximatePosition:Y})=>[L.id,L.state,L.kind,L.resourceId??L.environmentId??L.projectId??"global",`${L.attempt}/${z.maxAttempts}`,z.blockedReason??z.currentStep??(Y?`${Y.ahead} ahead`:"—"),L.createdAt,xh(L.startedAt,L.finishedAt)]))})}catch(m){r(m instanceof Error?m.message:String(m))}}),R.command("ops:show <id>","Show a durable operation and its latest sanitized logs").option("--after <cursor>","Show logs after this sequence",{default:"0"}).action(async(t,m)=>{try{await So(async(k)=>{let D=k.view(t);if(!D)throw Error(`Operation ${t} was not found`);e(JSON.stringify(D,null,2));for(let L of k.logs(t,{after:Number(m.after)||0,limit:500}))e(`${L.sequence} ${L.stream.toUpperCase()}${L.step?` [${L.step}]`:""} ${L.message}`)})}catch(k){r(k instanceof Error?k.message:String(k))}}),R.command("ops:cancel <id>","Cancel queued work or request running cancellation").option("--yes","Skip confirmation").action(async(t,m)=>{try{await So(async(k)=>{let D=k.view(t);if(!D)throw Error(`Operation ${t} was not found`);if(!m.yes&&!await d(`Cancel ${D.operation.kind} (${t})?`,!1))return;let L=k.requestCancellation(t);n(`${L.state==="cancelled"?"Cancelled":"Cancellation requested for"} ${t}`)})}catch(k){r(k instanceof Error?k.message:String(k))}}),R.command("ops:retry <id>","Retry a terminal job using an allow-listed error class").option("--class <name>","Retryable error class").option("--delay <milliseconds>","Delay before the next attempt",{default:"0"}).action(async(t,m)=>{try{if(!m.class)throw Error("Pass --class with one of the retry classes shown by ops:show");await So(async(k)=>{let D=k.retry(t,m.class,{delayMs:Number(m.delay)||0});n(`Retry queued: ${D.id} · attempt ${D.attempt+1}`)})}catch(k){r(k instanceof Error?k.message:String(k))}}),R.command("ops:concurrency","Show effective durable queue concurrency limits").action(async()=>{try{await So(async(t)=>e(JSON.stringify(t.limits,null,2)))}catch(t){r(t instanceof Error?t.message:String(t))}}),R.command("ops:concurrency:set","Confirm and audit durable queue concurrency limits").option("--project <count>","Concurrent jobs per project").option("--environment <count>","Concurrent jobs per environment").option("--provider <count>","Concurrent jobs per provider").option("--builds <count>","Concurrent build slots").option("--confirm <text>",'Must equal "update queue limits"').action(async(t)=>{try{if(t.confirm!=="update queue limits")throw Error('Pass --confirm "update queue limits" to change production concurrency');let m={};for(let k of["project","environment","provider","builds"])if(t[k]!==void 0)m[k]=Number(t[k]);await So(async(k,D,L)=>n(`Queue concurrency updated: ${JSON.stringify(k.configureConcurrency(m,{organizationId:L}))}`))}catch(m){r(m instanceof Error?m.message:String(m))}}),R.command("ops:history:clear","Clear terminal jobs after their retention policy expires").option("--before <date>","Also require completion before this timestamp").option("--yes","Skip confirmation").action(async(t)=>{try{await So(async(m,k)=>{if(!t.yes&&!await d("Clear completed queue history whose retention has elapsed?",!1))return;let D=m.clearCompleted({projectId:k,before:t.before});n(`Cleared ${D} retained operation${D===1?"":"s"}.`)})}catch(m){r(m instanceof Error?m.message:String(m))}})}import{readFile as Dh,writeFile as Eh}from"node:fs/promises";function Th(R){if(R==null)return"—";if(R<60)return`${R}s`;if(R<3600)return`${Math.floor(R/60)}m`;if(R<86400)return`${Math.floor(R/3600)}h`;return`${Math.floor(R/86400)}d`}function Nh(R){return R==null?"—":hn(R)}function Oh(R){return R.map((t)=>[t.id,t.name,t.provider,t.kind,t.status,t.desiredReplicas==null?"—":`${t.runningReplicas??0}/${t.desiredReplicas}`,t.image??t.runtime??"—",Nh(t.resources?.memoryBytes??t.resources?.memoryLimitBytes),Th(t.ageSeconds)])}function Lh(R,t){let m=t.search?.toLowerCase();return{...R,workloads:R.workloads.filter((k)=>(!t.provider||k.provider===t.provider)&&(!t.status||k.status===t.status)&&(!m||`${k.name} ${k.id} ${k.links.service??""} ${Object.values(k.tags).join(" ")}`.toLowerCase().includes(m)))}}async function xo(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=new Ce(t,m);return{config:t,env:m,service:k}}async function xa(R,t){if(t!==void 0)return t;return I(`Type ${R.name} to confirm`,"")}async function Fh(R,t,m,k,D,L){return Da(R,`runtime.${t}`,{workloadId:R.id,provider:R.provider,action:t,replicas:m??null},D,()=>L.run({workloadId:R.id,action:t,replicas:m,confirm:k,recentAuth:!0}))}async function Da(R,t,m,k,D){let L=await Ne(),z=y(process.cwd(),L),Y=new S(z.store,{workerId:`cli:${process.pid}`});try{let X=z.environments.get(k),ne=z.store.listResources(z.project.id,X?.id).find((ae)=>ae.slug===R.links.service),re=z.store.getActorByExternalId("system","cli")??z.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI",metadata:{source:"cli"}}),ie=Y.enqueue({projectId:z.project.id,environmentId:X?.id,resourceId:ne?.id,actorId:re.id,kind:t,input:h(m),lockKey:ne?`resource:${ne.id}`:`runtime:${R.id}`,providerKey:R.provider,maxAttempts:1,timeoutSeconds:3600,resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:90}).operation;if(!Y.claim(ie.id))throw Error(`Workload is locked by another operation. Inspect ${ie.id} with cloud ops:show.`);try{let ae=await D();if(ae.stdout)Y.appendLog(ie.id,ae.stdout,{stream:"stdout"});if(ae.stderr)Y.appendLog(ie.id,ae.stderr,{stream:"stderr"});if(ae.ok)Y.complete(ie.id,{ok:!0,command:ae.command??null});else Y.fail(ie.id,ae.error??ae.stderr??"Runtime operation failed",{ok:!1});return{result:ae,operationId:ie.id}}catch(ae){throw Y.fail(ie.id,ae instanceof Error?ae.message:String(ae),{ok:!1,threw:!0}),ae}}finally{z.store.close()}}function _l(R){R.command("runtime:list","List services, containers, ECS tasks, and Lambda functions").option("--env <environment>","Target environment").option("--provider <provider>","Filter systemd, docker, ecs, or lambda").option("--status <status>","Filter normalized runtime status").option("--search <query>","Search names, IDs, services, and tags").option("--json","Print normalized JSON").action(async(t)=>{try{let{service:m}=await xo(t.env),k=Lh(await m.inventory(),t);if(t.json)e(JSON.stringify(k,null,2));else{c(["ID","Name","Provider","Kind","Status","Replicas","Image / runtime","Memory","Age"],Oh(k.workloads));for(let D of k.sources.filter((L)=>L.status!=="fresh"))a(`${D.id}: ${D.status}${D.message?` — ${D.message}`:""}`)}}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}}),R.command("runtime:inspect <workload>","Inspect normalized config, replicas, networks, mounts, links, and capabilities").option("--env <environment>","Target environment").action(async(t,m)=>{try{let{service:k}=await xo(m.env),D=await k.workload(t);if(!D)throw Error("Workload was not found in this project and environment");e(JSON.stringify(D,null,2))}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("runtime:logs <workload>","Read bounded logs for one authorized runtime target").option("--env <environment>","Target environment").option("--lines <count>","Maximum log lines",{default:"200"}).option("--json","Print structured JSON").action(async(t,m)=>{try{let{service:k}=await xo(m.env),D=await k.logs(t,{limit:Number(m.lines)||200});if(m.json)e(JSON.stringify(D,null,2));else for(let L of D.lines)console.log(`${L.timestamp??"—"} ${L.message}`)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("runtime:action <workload> <action>","Start, stop, restart, redeploy, or scale one workload").option("--env <environment>","Target environment").option("--replicas <count>","Desired replicas for scale").option("--confirm <name>","Exact workload name confirmation").action(async(t,m,k)=>{try{let D=m;if(!["start","stop","restart","redeploy","scale"].includes(D))throw Error("Action must be start, stop, restart, redeploy, or scale");let L=k.replicas==null?void 0:Number(k.replicas);if(D==="scale"&&(!Number.isInteger(L)||L<0))throw Error("Scale requires a non-negative integer --replicas value");let{env:z,service:Y}=await xo(k.env),X=await Y.inventory(),ne=X.workloads.find((ae)=>ae.id===t);if(!ne)throw Error("Workload was not found in this project and environment");let re=new Ce(await Ne(),z,{inventory:async()=>X}),ie=await Fh(ne,D,L,await xa(ne,k.confirm),z,re);if(!ie.result.ok)throw Error(ie.result.error??ie.result.stderr??"Runtime operation failed");if(n(`${D} completed for ${ne.name} · operation ${ie.operationId}`),ie.result.stdout)e(ie.result.stdout)}catch(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}}),R.command("runtime:exec <workload>","Run a scoped diagnostic preset or container command").option("--env <environment>","Target environment").option("--preset <preset>","Read-only preset: process, sockets, or filesystem").option("--command <command>","Free-form command (Docker containers only)").option("--confirm <name>","Exact workload name confirmation").action(async(t,m)=>{try{let{env:k,service:D}=await xo(m.env),z=(await D.inventory()).workloads.find((re)=>re.id===t);if(!z)throw Error("Workload was not found in this project and environment");let Y=["process","sockets","filesystem"].includes(String(m.preset))?m.preset:void 0;if(m.preset&&!Y)throw Error("Preset must be process, sockets, or filesystem");if(!Y&&!m.command)throw Error("Pass --preset or --command");let X=await xa(z,m.confirm),ne=await Da(z,"runtime.exec",{workloadId:t,provider:z.provider,preset:Y??null,freeForm:!!m.command},k,()=>D.exec({workloadId:t,preset:Y,command:m.command,confirm:X,recentAuth:!0}));if(!ne.result.ok)throw Error(ne.result.error??ne.result.stderr??"Exec failed");if(ne.result.stdout)e(ne.result.stdout);if(ne.result.stderr)a(ne.result.stderr);n(`Exec completed for ${z.name} · operation ${ne.operationId}`)}catch(k){r(k instanceof Error?k.message:String(k)),process.exitCode=1}}),R.command("runtime:download <workload> <remote> <local>","Download a bounded file from a service-owned path").option("--env <environment>","Target environment").option("--confirm <name>","Exact workload name confirmation").action(async(t,m,k,D)=>{try{let{env:L,service:z}=await xo(D.env),X=(await z.inventory()).workloads.find((ie)=>ie.id===t);if(!X)throw Error("Workload was not found in this project and environment");let ne=await xa(X,D.confirm),re=await Da(X,"runtime.file.read",{workloadId:t,provider:X.provider,path:m},L,()=>z.readFile({workloadId:t,path:m,confirm:ne,recentAuth:!0}));if(!re.result.ok||!re.result.contentBase64)throw Error(re.result.error??"File download failed");await Eh(k,Buffer.from(re.result.contentBase64,"base64")),n(`Downloaded ${re.result.size??0} bytes to ${k} · operation ${re.operationId}${re.result.truncated?" (truncated)":""}`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}}),R.command("runtime:upload <workload> <local> <remote>","Upload and atomically replace a file in a service-owned path").option("--env <environment>","Target environment").option("--confirm <name>","Exact workload name confirmation").action(async(t,m,k,D)=>{try{let{env:L,service:z}=await xo(D.env),X=(await z.inventory()).workloads.find((ae)=>ae.id===t);if(!X)throw Error("Workload was not found in this project and environment");let ne=await xa(X,D.confirm),re=(await Dh(m)).toString("base64"),ie=await Da(X,"runtime.file.write",{workloadId:t,provider:X.provider,path:k,bytes:Buffer.from(re,"base64").byteLength},L,()=>z.writeFile({workloadId:t,path:k,contentBase64:re,confirm:ne,recentAuth:!0}));if(!ie.result.ok)throw Error(ie.result.error??"File upload failed");n(`Uploaded ${ie.result.size??0} bytes to ${k} · operation ${ie.operationId}`)}catch(L){r(L instanceof Error?L.message:String(L)),process.exitCode=1}})}function jh(R,t=new Date){let m=R.to?new Date(R.to):t;if(!Number.isFinite(m.getTime()))throw Error("--to must be a valid ISO-8601 instant");if(R.from){let L=new Date(R.from);if(!Number.isFinite(L.getTime()))throw Error("--from must be a valid ISO-8601 instant");return{from:L.toISOString(),to:m.toISOString()}}let k=String(R.range??"1h").match(/^(\d+)(m|h|d)$/);if(!k)throw Error("--range must use minutes, hours, or days (for example 30m, 6h, or 7d)");let D=k[2]==="m"?60000:k[2]==="h"?3600000:86400000;return{from:new Date(m.getTime()-Number(k[1])*D).toISOString(),to:m.toISOString()}}async function Ts(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=y(process.cwd(),t),D=k.environments.get(m),L=new rt(k.store);return{config:t,env:m,controlPlane:k,environmentRecord:D,telemetry:L}}function Lu(R,t,m){let k=(L)=>L?.split(",").map((z)=>z.trim()).filter(Boolean)||void 0,D=k(R.kind)?.filter((L)=>["metric","log","trace","request","event"].includes(L));return{projectId:t,environmentId:m,...jh(R),kinds:D,names:k(R.name),sources:k(R.source),text:R.text,limit:Math.min(5000,Math.max(1,Number(R.limit)||200))}}function Bh(R){let t=["timestamp","kind","source","name","level","value","unit","message","traceId","requestId","deploymentId","releaseId","workloadId"];return[t.join(","),...R.map((m)=>t.map((k)=>`"${String(m[k]??"").replaceAll('"','""')}"`).join(","))].join(`
|
|
655
|
+
`)}function Ml(R){R.command("telemetry:collect","Collect normalized host/runtime or AWS telemetry into the local control plane").option("--env <environment>","Target environment").option("--force","Bypass the provider-query cache").option("--json","Print structured JSON").action(async(t)=>{let m=await Ts(t.env);try{let k=await ui({controlPlane:m.controlPlane.store,projectId:m.controlPlane.project.id,environmentId:m.environmentRecord?.id,config:m.config,environment:m.env,force:t.force});if(t.json)e(JSON.stringify(k,null,2));else n(`Collected ${k.collected} normalized records${k.cached?" (cached provider result)":""}.`),c(["Source","Freshness","Lag","Sampling","Retention"],k.statuses.map((D)=>[D.source,D.freshness,D.lagSeconds==null?"—":`${D.lagSeconds}s`,`${Math.round(D.samplingRate*100)}%`,`${D.retentionDays}d`]))}finally{m.controlPlane.store.close()}}),R.command("telemetry:query","Query bounded normalized metrics, logs, traces, requests, and events").option("--env <environment>","Target environment").option("--from <instant>","ISO-8601 start").option("--to <instant>","ISO-8601 end").option("--range <duration>","Relative window such as 1h or 7d",{default:"1h"}).option("--kind <kinds>","Comma-separated kinds").option("--name <names>","Comma-separated signal names").option("--source <sources>","Comma-separated sources").option("--text <query>","Search name/message").option("--limit <count>","Maximum records",{default:"200"}).option("--json","Print structured JSON").action(async(t)=>{let m=await Ts(t.env);try{let k=m.telemetry.query(Lu(t,m.controlPlane.project.id,m.environmentRecord?.id));if(t.json)e(JSON.stringify(k,null,2));else c(["Time","Kind","Source","Signal","Value / message","Correlation"],k.records.map((D)=>[D.timestamp,D.kind,D.source,D.name,D.message??(D.value==null?"—":`${D.value} ${D.unit??""}`),D.requestId??D.traceId??D.releaseId??D.deploymentId??"—"]));if(k.truncated)a("More records are available; narrow the window or use the dashboard cursor explorer.")}finally{m.controlPlane.store.close()}}),R.command("telemetry:export <file>","Export a bounded telemetry query as JSON or CSV").option("--env <environment>","Target environment").option("--from <instant>","ISO-8601 start").option("--to <instant>","ISO-8601 end").option("--range <duration>","Relative window",{default:"1h"}).option("--kind <kinds>","Comma-separated kinds").option("--name <names>","Comma-separated signal names").option("--source <sources>","Comma-separated sources").option("--text <query>","Search name/message").option("--limit <count>","Maximum records",{default:"5000"}).action(async(t,m)=>{let k=await Ts(m.env);try{let D=Lu(m,k.controlPlane.project.id,k.environmentRecord?.id),L=k.telemetry.query(D);await Bun.write(t,t.toLowerCase().endsWith(".csv")?Bh(L.records):JSON.stringify({exportedAt:new Date().toISOString(),query:D,...L},null,2)),n(`Exported ${L.records.length} records to ${t}${L.truncated?" (bounded result)":""}.`)}finally{k.controlPlane.store.close()}}),R.command("telemetry:compact","Downsample old metrics and enforce configured retention").option("--env <environment>","Target environment").action(async(t)=>{let m=await Ts(t.env);try{let k=tt(m.controlPlane.store,m.controlPlane.project.id);e(JSON.stringify(m.telemetry.enforceRetention(k,m.controlPlane.project.id),null,2))}finally{m.controlPlane.store.close()}}),R.command("telemetry:policy","Show or update sampling, retention, and local storage cost assumptions").option("--env <environment>","Target environment").option("--raw-days <days>","Raw retention days").option("--downsample-days <days>","Downsample after days").option("--sampling <rate>","Sampling rate from 0.01 to 1").option("--storage-usd-gb-month <amount>","Local storage cost assumption").option("--json","Print structured JSON").action(async(t)=>{let m=await Ts(t.env);try{let k={};if(t.rawDays!=null)k.rawDays=Number(t.rawDays);if(t.downsampleDays!=null)k.downsampleAfterDays=Number(t.downsampleDays);if(t.sampling!=null)k.samplingRate=Number(t.sampling);if(t.storageUsdGbMonth!=null)k.estimatedStorageUsdPerGbMonth=Number(t.storageUsdGbMonth);let D=Object.keys(k).length?Ei(m.controlPlane.store,m.controlPlane.project.id,k):tt(m.controlPlane.store,m.controlPlane.project.id),z=m.telemetry.status(m.controlPlane.project.id,m.environmentRecord?.id,D.rawDays,D.samplingRate).reduce((X,ne)=>X+ne.estimatedDailyBytes*30,0),Y={policy:D,estimatedMonthlyBytes:z,estimatedMonthlyCostUsd:wi(z,D),costEstimateConfigured:D.estimatedStorageUsdPerGbMonth>0};if(t.json)e(JSON.stringify(Y,null,2));else e(JSON.stringify(D,null,2)),e(Y.costEstimateConfigured?`Estimated local storage: $${Y.estimatedMonthlyCostUsd}/month`:"Storage cost remains unavailable until --storage-usd-gb-month is set.")}finally{m.controlPlane.store.close()}})}async function oo(R){let t=await Ne(),m=R??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let k=y(process.cwd(),t),D=k.environments.get(m),L=new St(k.store,{encryptionKey:_(process.cwd())});return{config:t,env:m,controlPlane:k,environmentRecord:D,alerts:L}}function Fu(R){return new Er(R,{emailImpl:async(t)=>{let{email:m}=await import("./chunk-dmm1p4bs.js");return m.send(t)}})}function zl(R){R.command("health:list","List configured health checks and latest results").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let k=m.alerts.listHealthChecks(m.controlPlane.project.id,m.environmentRecord?.id).map((D)=>({...D,latest:m.alerts.listHealthResults(D.id,1)[0]}));if(t.json)e(JSON.stringify(k,null,2));else c(["ID","Name","Kind","Target","Status","Interval","Enabled"],k.map((D)=>[D.id,D.name,D.kind,D.target,D.latest?.status??"no data",`${D.intervalSeconds}s`,D.enabled?"yes":"no"]))}finally{m.controlPlane.store.close()}}),R.command("health:run <check>","Run one health check and evaluate linked alert rules").option("--env <environment>","Target environment").option("--test","Preview without persisting results or incidents").action(async(t,m)=>{let k=await oo(m.env);try{let D=k.alerts.getHealthCheck(t)??k.alerts.listHealthChecks(k.controlPlane.project.id,k.environmentRecord?.id).find((Y)=>Y.name===t);if(!D)throw Error("Health check was not found");let L=new je(k.alerts);if(m.test){let Y=await L.probe(D);e(JSON.stringify({...Y,productionIncidentCreated:!1},null,2));return}let z=await L.runAndEvaluate(D);if(e(JSON.stringify(z,null,2)),z.result.status==="healthy")n(`${D.name} is healthy.`);else a(`${D.name} is ${z.result.status}.`)}finally{k.controlPlane.store.close()}}),R.command("alerts:list","List pending, firing, silenced, and resolved alerts").option("--env <environment>","Target environment").option("--state <states>","Comma-separated states").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let k=m.alerts.listAlerts(m.controlPlane.project.id,{environmentId:m.environmentRecord?.id,states:t.state?.split(",").filter(Boolean)});if(t.json)e(JSON.stringify(k,null,2));else c(["ID","State","Severity","Title","Owner","First seen","Last seen"],k.map((D)=>[D.id,D.state,D.severity,D.title,D.ownerActorId??"unassigned",D.firstSeenAt,D.lastSeenAt]))}finally{m.controlPlane.store.close()}}),R.command("alerts:evaluate","Evaluate persisted telemetry rules now").option("--env <environment>","Target environment").action(async(t)=>{let m=await oo(t.env);try{let k=pn(m.alerts,m.controlPlane.project.id,m.environmentRecord?.id);e(JSON.stringify(k,null,2))}finally{m.controlPlane.store.close()}}),R.command("alerts:ack <alert>","Acknowledge an alert as the CLI actor").option("--env <environment>","Target environment").action(async(t,m)=>{let k=await oo(m.env);try{let D=k.controlPlane.store.getActorByExternalId("system","cli")??k.controlPlane.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});n(`Acknowledged ${k.alerts.acknowledge(t,D.id).title}.`)}finally{k.controlPlane.store.close()}}),R.command("notifications:list","List notification channels, routes, and recent delivery diagnostics").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let k={channels:m.alerts.listChannels(m.controlPlane.organization.id),routes:m.alerts.listRoutes(m.controlPlane.organization.id),deliveries:m.alerts.listDeliveries({limit:100})};if(t.json)e(JSON.stringify(k,null,2));else c(["Channel","Type","Status","Tested","Error"],k.channels.map((D)=>[D.name,D.kind,D.status,D.lastTestedAt??"never",D.lastError??"—"])),c(["Event","State","Attempts","Status","Error"],k.deliveries.map((D)=>[D.eventType,D.state,`${D.attempt}/${D.maxAttempts}`,String(D.responseStatus??"—"),D.error??"—"]))}finally{m.controlPlane.store.close()}}),R.command("notifications:test <channel>","Send a channel test without creating a production incident").option("--env <environment>","Target environment").action(async(t,m)=>{let k=await oo(m.env);try{let D=k.alerts.getChannel(t)??k.alerts.listChannels(k.controlPlane.organization.id).find((z)=>z.name===t);if(!D)throw Error("Notification channel was not found");let L=await Fu(k.alerts).testChannel(D.id);if(L.ok)n("Test delivered; no production incident was created.");else throw Error(L.error)}finally{k.controlPlane.store.close()}}),R.command("notifications:retry <delivery>","Retry one failed notification delivery").option("--env <environment>","Target environment").action(async(t,m)=>{let k=await oo(m.env);try{let D=k.alerts.getDelivery(t);if(!D)throw Error("Notification delivery was not found");k.alerts.updateDelivery(t,{state:"retrying",attempt:Math.max(0,D.attempt-1),nextAttemptAt:new Date().toISOString()}),e(JSON.stringify(await Fu(k.alerts).deliver(t),null,2))}finally{k.controlPlane.store.close()}})}async function _h(R,t){let m=await Ne(),k=y(process.cwd(),m),D=!R&&t?new en(k.store).get(t):void 0,L=D?.environmentId?k.store.listEnvironments(k.project.id).find((be)=>be.id===D.environmentId)?.slug:void 0,z=R??L??Object.keys(m.environments??{})[0]??"production";if(!Object.hasOwn(m.environments??{},z))throw k.store.close(),Error(`Environment ${z} was not found`);let Y=k.environments.get(z);if(!Y)throw k.store.close(),Error(`Environment ${z} was not found in the control plane`);let X=new en(k.store),ne=new S(k.store,{workerId:`cli:${process.pid}`}),re=new ci(X,{queue:ne}),ie=k.store.listResources(k.project.id,Y.id);Zr(X,m,{organization:k.organization,project:k.project,environment:Y,resources:ie});let ae=k.store.getActorByExternalId("system","cli")??k.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:m,env:z,controlPlane:k,environmentRecord:Y,resources:ie,store:X,queue:ne,service:re,actor:ae}}async function zr(R,t,m){let k=await _h(R,m);try{return await t(k)}finally{k.controlPlane.store.close()}}async function er(R){try{await R()}catch(t){r(t instanceof Error?t.message:String(t))}}function Ho(R,t){let m=R.store.list(R.controlPlane.project.id,{environmentId:R.environmentRecord.id}),k=R.store.get(t)??m.find((D)=>D.name===t);if(!k||k.environmentId!==R.environmentRecord.id)throw Error(`Scheduled job ${t} was not found in ${R.env}`);return k}function Eo(R,t,m,k){R.controlPlane.store.appendEvent({organizationId:R.controlPlane.organization.id,projectId:R.controlPlane.project.id,resourceId:k,actorId:R.actor.id,type:`job.${t}`,payload:m})}function ju(R,t,m){if(R.environmentRecord.kind==="production"&&m!==t)throw Error(`Production ${t} requires --confirm ${t}`)}function Mh(R){if(!R)return{};if(!/^(?:secret|ssm|arn|env):\/\//.test(R))throw Error("Payloads must use a secret://, ssm://, arn://, or env:// reference");return{input:R}}function zh(R){return R.map((t)=>[t.id,t.enabled?"enabled":"disabled",t.provider,t.name,t.expression,t.timezone,t.nextRunAt??"—",t.reconciliationStatus])}function Ul(R){R.command("jobs:list","List server cron, EventBridge, and recurring control-plane jobs").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>er(async()=>zr(t.env,async(m)=>{let k=m.store.list(m.controlPlane.project.id,{environmentId:m.environmentRecord.id});if(t.json)e(JSON.stringify(k,null,2));else c(["ID","State","Provider","Name","Expression","Timezone","Next run","Reconciliation"],zh(k))}))),R.command("jobs:preview <expression>","Preview the next runs without creating production work").option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--from <date>","Preview from an ISO timestamp").option("--count <count>","Number of runs",{default:"5"}).action(async(t,m)=>er(async()=>{let k=pt(t,m.timezone??"UTC",m.from?new Date(m.from):new Date,Number(m.count)||5);e(JSON.stringify({...k,productionExecutionCreated:!1},null,2))})),R.command("jobs:add <name> <expression>","Create a managed recurring job").option("--env <environment>","Target environment").option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--operation <id>","Safe server operation target (scheduler, backup, or worker)").option("--resource <id>","Resource ID or slug").option("--starts-at <date>","Schedule window start").option("--ends-at <date>","Schedule window end").option("--flexible-minutes <count>","Provider flexible window",{default:"0"}).option("--missed <policy>","skip or catch_up",{default:"skip"}).option("--overlap <policy>","allow, forbid, or replace",{default:"forbid"}).option("--attempts <count>","Maximum attempts",{default:"3"}).option("--backoff <seconds>","Retry backoff seconds",{default:"30"}).option("--timeout <seconds>","Execution timeout",{default:"900"}).option("--dead-letter <reference>","Dead-letter ARN or secret reference").option("--payload-ref <reference>","Referenced payload; inline values are forbidden").option("--disabled","Create disabled").action(async(t,m,k)=>er(async()=>zr(k.env,async(D)=>{if(!["skip","catch_up"].includes(k.missed??"skip"))throw Error("--missed must be skip or catch_up");if(!["allow","forbid","replace"].includes(k.overlap??"forbid"))throw Error("--overlap must be allow, forbid, or replace");let L=H(D.config),Y={workers:D.store.listWorkers(D.controlPlane.project.id,D.environmentRecord.id).map((be)=>({name:be.name}))},X;if(L==="server"){let be=Xr(D.config,Y).filter((le)=>!le.danger&&["scheduler","backup","worker"].includes(le.group)),Ae=k.operation?be.find((le)=>le.id===k.operation):be.length===1?be[0]:void 0;if(!Ae)throw Error(`Choose a safe target with --operation. Available: ${be.map((le)=>le.id).join(", ")||"none"}`);X={kind:"dashboard_operation",operationId:Ae.id}}else X={kind:"serverless_scheduler",action:"run"};let ne=L==="server"?"server":"eventbridge",re=ai({provider:ne,expression:m,flexibleMinutes:Number(k.flexibleMinutes)||0,missedRunPolicy:k.missed??"skip",overlapPolicy:k.overlap??"forbid"});if(!re.supported)throw Error(re.notes.join(" "));let ie=k.resource?D.resources.find((be)=>be.id===k.resource||be.slug===k.resource):void 0;if(k.resource&&!ie)throw Error(`Resource ${k.resource} was not found in ${D.env}`);let ae=D.store.create({organizationId:D.controlPlane.organization.id,projectId:D.controlPlane.project.id,environmentId:D.environmentRecord.id,resourceId:ie?.id,name:t,provider:ne,expression:m,timezone:k.timezone??"UTC",startsAt:k.startsAt,endsAt:k.endsAt,flexibleMinutes:Number(k.flexibleMinutes)||0,target:X,payloadRefs:Mh(k.payloadRef),missedRunPolicy:k.missed??"skip",overlapPolicy:k.overlap??"forbid",retryPolicy:{maxAttempts:Number(k.attempts)||3,backoffSeconds:Number(k.backoff)||30,deadLetterRef:k.deadLetter},timeoutSeconds:Number(k.timeout)||900,enabled:!k.disabled,origin:"managed",ownerActorId:D.actor.id,observedState:{},reconciliationStatus:"pending"});Eo(D,"created",{jobId:ae.id,provider:ae.provider,expression:ae.normalizedExpression},ae.resourceId),n(`Created ${ae.name} (${ae.id}); next run ${ae.nextRunAt??"outside the active window"}`)}))),R.command("jobs:update <job>","Update a managed job schedule and policies").option("--env <environment>","Target environment").option("--expression <expression>","Cron, preset, or rate expression").option("--timezone <timezone>","IANA timezone").option("--missed <policy>","skip or catch_up").option("--overlap <policy>","allow, forbid, or replace").option("--timeout <seconds>","Execution timeout").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t);if(D.origin==="config")throw Error("Config-defined schedules must be edited in cloud.config.ts and reconciled");if(m.missed&&!["skip","catch_up"].includes(m.missed))throw Error("--missed must be skip or catch_up");if(m.overlap&&!["allow","forbid","replace"].includes(m.overlap))throw Error("--overlap must be allow, forbid, or replace");let L=k.store.updateSchedule(D.id,{expression:m.expression,timezone:m.timezone,missedRunPolicy:m.missed,overlapPolicy:m.overlap,timeoutSeconds:m.timeout==null?void 0:Number(m.timeout)});Eo(k,"updated",{jobId:L.id,expression:L.normalizedExpression,version:L.version},L.resourceId),n(`Updated ${L.name}; next run ${L.nextRunAt??"outside the active window"}`)}))),R.command("jobs:run <job>","Queue a manual run through the durable operation worker").option("--env <environment>","Target environment").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t),L=k.service.enqueue(D,{trigger:"manual",actorId:k.actor.id});Eo(k,"run_queued",{jobId:D.id,executionId:L.id},D.resourceId),n(`Queued execution ${L.id}; inspect it with cloud jobs:history "${D.name}"`)}))),R.command("jobs:dispatch <job>","Dispatch an externally triggered schedule into the durable queue").option("--env <environment>","Target environment").option("--scheduled-for <date>","Provider scheduled timestamp").option("--scheduled","Mark this as a provider schedule trigger").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t);if(!D.enabled)throw Error(`${D.name} is disabled`);let L=m.scheduledFor?new Date(m.scheduledFor):new Date;if(!m.scheduledFor)L.setUTCSeconds(0,0);let z=L.toISOString(),Y=k.service.enqueue(D,{trigger:m.scheduled?"scheduled":"external",scheduledFor:z,actorId:k.actor.id}),X=ut(D.normalizedExpression,D.timezone,L,1)[0];k.store.markScheduled(D.id,z,X),n(`Accepted ${D.name} for ${z} as ${Y.id}`)},t)));for(let t of["enable","disable"])R.command(`jobs:${t} <job>`,`${t==="enable"?"Enable":"Disable"} a scheduled job`).option("--env <environment>","Target environment").option("--confirm <text>",`Production ${t} confirmation`).action(async(m,k)=>er(async()=>zr(k.env,async(D)=>{let L=Ho(D,m);if(t==="disable")ju(D,t,k.confirm);let z=D.store.setEnabled(L.id,t==="enable");Eo(D,t==="enable"?"enabled":"disabled",{jobId:z.id},z.resourceId),n(`${z.name} is ${z.enabled?"enabled":"disabled"}`)})));R.command("jobs:delete <job>","Delete a managed schedule").option("--env <environment>","Target environment").option("--confirm <text>","Must be delete in production").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t);ju(k,"delete",m.confirm),k.store.remove(D.id),Eo(k,"deleted",{jobId:D.id,name:D.name},D.resourceId),n(`Deleted ${D.name}`)}))),R.command("jobs:history <job>","Show execution attempts, output, errors, and redacted logs").option("--env <environment>","Target environment").option("--limit <count>","Maximum executions",{default:"100"}).option("--logs","Print execution logs").option("--json","Print structured JSON").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=Ho(k,t),L=k.store.listExecutions(D.id,Number(m.limit)||100).map((z)=>({...z,logs:m.logs&&z.operationId?k.queue.logs(z.operationId,{limit:500}):[]}));if(m.json)e(JSON.stringify(L,null,2));else if(c(["ID","Status","Trigger","Scheduled for","Attempt","Started","Finished","Error"],L.map((z)=>[z.id,z.status,z.trigger,z.scheduledFor,String(z.attempt),z.startedAt??"—",z.finishedAt??"—",z.error??"—"])),m.logs)for(let z of L)for(let Y of z.logs)e(`${z.id} ${Y.sequence} ${Y.stream.toUpperCase()} ${Y.message}`)}))),R.command("jobs:reconcile","Import declarative schedules and workers without destructive deletion").option("--env <environment>","Target environment").action(async(t)=>er(async()=>zr(t.env,async(m)=>{let k=Zr(m.store,m.config,{organization:m.controlPlane.organization,project:m.controlPlane.project,environment:m.environmentRecord,resources:m.resources});Eo(m,"reconciled",k),n(`Reconciled ${k.jobs} schedules and ${k.workers} workers; retained ${k.drifted} missing definitions as drifted`)}))),R.command("workers:list","List configured systemd, ECS, and Lambda workers").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>er(async()=>zr(t.env,async(m)=>{let k=m.store.listWorkers(m.controlPlane.project.id,m.environmentRecord.id);if(t.json)e(JSON.stringify(k,null,2));else c(["ID","Provider","Name","Queue","Processes","Timeout","Restart","Reconciliation"],k.map((D)=>[D.id,D.provider,D.name,D.queue,String(D.processes),`${D.timeoutSeconds}s`,D.restartPolicy,D.reconciliationStatus]))}))),R.command("workers:restart <worker>","Gracefully restart an allowlisted systemd worker").option("--env <environment>","Target environment").option("--confirm <name>","Exact worker name confirmation").action(async(t,m)=>er(async()=>zr(m.env,async(k)=>{let D=k.store.listWorkers(k.controlPlane.project.id,k.environmentRecord.id),L=k.store.getWorker(t)??D.find((X)=>X.name===t);if(!L||L.environmentId!==k.environmentRecord.id)throw Error(`Worker ${t} was not found in ${k.env}`);if(m.confirm!==L.name)throw Error(`Restart requires --confirm "${L.name}"`);if(L.provider!=="systemd")throw Error(`${L.provider} workers are provider/config managed and do not expose a safe restart adapter`);let z=oi(String(L.target.operationId??""),k.config,{workers:D.map((X)=>({name:X.name}))});if(!z||z.danger||z.group!=="worker")throw Error("The allowlisted worker restart operation is unavailable");let Y=await Qr(k.config,k.env,z);if(k.store.reconcileWorker(L.id,Y.ok?"in_sync":"unavailable",{...L.observedState,lastRestartAt:new Date().toISOString(),command:Y.command??null,error:Y.error??Y.stderr??null}),Eo(k,"worker_restarted",{workerId:L.id,ok:Y.ok},L.resourceId),!Y.ok)throw Error(Y.error??Y.stderr??"Worker restart failed");n(`Restarted ${L.name}`)})))}function Wl(R){R.command("tunnel","Start a local tunnel to expose your local server").option("--port <number>","Local port to expose",{default:"3000"}).option("--subdomain <name>","Custom subdomain (if available)").option("--host <hostname>","Local hostname",{default:"localhost"}).option("--server <url>","Tunnel server URL",{default:"localtunnel.dev"}).option("--verbose","Enable verbose logging").action(async(t)=>{i("Local Tunnel");let m=Number.parseInt(t.port);if(e(`Local server: ${t.host}:${m}`),e(`Tunnel server: ${t.server}`),t.subdomain)e(`Requested subdomain: ${t.subdomain}`);let k=new s("Connecting to tunnel server...");k.start();try{let D;try{let ne=await import("localtunnels");D=ne.TunnelClient}catch{k.fail("localtunnels package not found"),e(`
|
|
656
656
|
To use tunnels, install the localtunnels package:`),e(" bun add localtunnels"),e(`
|
|
657
657
|
Or use the standalone CLI:`),e(" bunx localtunnels --port 3000");return}let L=t.server.replace(/^(wss?|https?):\/\//,""),z=t.server.startsWith("wss://")||t.server.startsWith("https://")||L==="localtunnel.dev",Y=new D({localPort:m,localHost:t.host,subdomain:t.subdomain,host:L,port:z?443:80,secure:z,verbose:t.verbose});Y.on("connected",(ne)=>{k.succeed("Connected!"),n(`
|
|
658
658
|
Your tunnel URL: ${ne.url}`),e(`Subdomain: ${ne.subdomain}`),e(`
|