@stacksjs/ts-cloud 0.16.30 → 0.16.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/aws/index.js +1 -1
  2. package/dist/bin/{chunk-5nf3fxgw.js → chunk-12j17qm5.js} +1 -1
  3. package/dist/bin/{chunk-0n0jscsr.js → chunk-24473jks.js} +1 -1
  4. package/dist/bin/chunk-3932p1z3.js +1 -0
  5. package/dist/bin/chunk-3eztnxgw.js +1 -0
  6. package/dist/bin/{chunk-10sypz59.js → chunk-4182gadf.js} +1 -1
  7. package/dist/bin/chunk-4er4ktcs.js +1 -0
  8. package/dist/bin/{chunk-7577ykbj.js → chunk-6wz04wnk.js} +1 -1
  9. package/dist/bin/{chunk-remzfbhd.js → chunk-6yr12f64.js} +3 -3
  10. package/dist/bin/{chunk-8c4fnga8.js → chunk-79sd00d1.js} +1 -1
  11. package/dist/bin/{chunk-bkzt5ntz.js → chunk-81mt497w.js} +1 -1
  12. package/dist/bin/{chunk-dmyr6etw.js → chunk-8mgwqpmx.js} +1 -1
  13. package/dist/bin/{chunk-e6xn8g28.js → chunk-985ev3ns.js} +1 -1
  14. package/dist/bin/{chunk-bvtpvstn.js → chunk-9e62z9je.js} +11 -11
  15. package/dist/bin/{chunk-g1krw34t.js → chunk-9eq18gds.js} +1 -1
  16. package/dist/bin/chunk-9zhp2z0s.js +1 -0
  17. package/dist/bin/{chunk-g00a221j.js → chunk-b9psp74n.js} +14 -14
  18. package/dist/bin/chunk-bmmr5mq6.js +1 -0
  19. package/dist/bin/{chunk-je602ntp.js → chunk-cr56pxpt.js} +1 -1
  20. package/dist/bin/chunk-dvv2z19r.js +1 -0
  21. package/dist/bin/{chunk-y9cdqk8c.js → chunk-efbmb8rw.js} +1 -1
  22. package/dist/bin/chunk-eyrmv6c4.js +1 -0
  23. package/dist/bin/{chunk-9nnfyqme.js → chunk-f7dtn45y.js} +3 -3
  24. package/dist/bin/chunk-fnnv0t66.js +1 -0
  25. package/dist/bin/{chunk-1chq0c2k.js → chunk-fpxb97q5.js} +1 -1
  26. package/dist/bin/chunk-fwmje99n.js +1 -0
  27. package/dist/bin/{chunk-k8r5zfnf.js → chunk-gcm9n6ww.js} +1 -1
  28. package/dist/bin/{chunk-6hrycgbh.js → chunk-gzxpkk0c.js} +1 -1
  29. package/dist/bin/{chunk-s3mv4z3f.js → chunk-jhp53qsd.js} +1 -1
  30. package/dist/bin/{chunk-jeg6vw5v.js → chunk-jzby2kzy.js} +1 -1
  31. package/dist/bin/{chunk-xtqx4v3f.js → chunk-kgtzvh2k.js} +1 -1
  32. package/dist/bin/{chunk-a9yp82q5.js → chunk-kqf19j9m.js} +1 -1
  33. package/dist/bin/{chunk-nb5vjsdg.js → chunk-mpq5ct10.js} +4 -4
  34. package/dist/bin/chunk-n775dk3f.js +1 -0
  35. package/dist/bin/{chunk-kne9dmwr.js → chunk-nnawqw34.js} +32 -32
  36. package/dist/bin/chunk-ppj604c9.js +1 -0
  37. package/dist/bin/chunk-prq5gw4j.js +1 -0
  38. package/dist/bin/chunk-pt71fyem.js +1 -0
  39. package/dist/bin/{chunk-1wtkv65w.js → chunk-py8cvqvx.js} +1 -1
  40. package/dist/bin/{chunk-3ykx59eg.js → chunk-q6z8wvvb.js} +1 -1
  41. package/dist/bin/{chunk-q9tvzcr7.js → chunk-qe56mnjc.js} +3 -3
  42. package/dist/bin/chunk-qvj454fy.js +1 -0
  43. package/dist/bin/{chunk-j8dkatkk.js → chunk-sdy79wz7.js} +4 -4
  44. package/dist/bin/chunk-smj97rs5.js +1 -0
  45. package/dist/bin/{chunk-yrq5bkxe.js → chunk-tg45thhb.js} +1 -1
  46. package/dist/bin/{chunk-e4p6hwct.js → chunk-tgwx5haz.js} +1 -1
  47. package/dist/bin/{chunk-9p7g25kx.js → chunk-vhz2wmb8.js} +81 -81
  48. package/dist/bin/{chunk-a1kpwbjg.js → chunk-wt7wjgcr.js} +3 -3
  49. package/dist/bin/{chunk-j2yy3tth.js → chunk-x56s9b7f.js} +1 -1
  50. package/dist/bin/chunk-yf3zys06.js +1 -0
  51. package/dist/bin/{chunk-ee6ssn26.js → chunk-zhkbdsfs.js} +1 -1
  52. package/dist/bin/{chunk-jf8jp6f5.js → chunk-zk3nm0td.js} +1 -1
  53. package/dist/bin/chunk-zmjm7bmm.js +1 -0
  54. package/dist/bin/cli.js +46 -46
  55. package/dist/bin/dashboard-server.js +1 -1
  56. package/dist/chunk-05bmq0z1.js +1 -0
  57. package/dist/chunk-0qdgw73b.js +1 -0
  58. package/dist/{chunk-75bga11p.js → chunk-2mw7bjhn.js} +1 -1
  59. package/dist/{chunk-92w1g5d9.js → chunk-4hdp9ms2.js} +1 -1
  60. package/dist/{chunk-m3a4brpj.js → chunk-4jgy95bv.js} +5 -5
  61. package/dist/{chunk-z8qz33yk.js → chunk-57fhsqzb.js} +2 -2
  62. package/dist/{chunk-3kpnn06d.js → chunk-983txhkg.js} +1 -1
  63. package/dist/{chunk-b6ar7eqc.js → chunk-efssa93f.js} +1 -1
  64. package/dist/{chunk-26cq0zjv.js → chunk-jh38v9zp.js} +2 -2
  65. package/dist/{chunk-f733qy2d.js → chunk-kavwrxm8.js} +2 -2
  66. package/dist/{chunk-9n0q2cxj.js → chunk-nq51dyxz.js} +4 -4
  67. package/dist/{chunk-h3kkh31v.js → chunk-peeqkk92.js} +81 -81
  68. package/dist/chunk-pf6sanwe.js +1 -0
  69. package/dist/{chunk-2c8pcbq8.js → chunk-r45340yc.js} +34 -34
  70. package/dist/{chunk-mqm1hxaw.js → chunk-rqe0sfqw.js} +2 -2
  71. package/dist/{chunk-1erk1r1m.js → chunk-s909414m.js} +1 -1
  72. package/dist/{chunk-t3yzfbe7.js → chunk-t1gts5j9.js} +4 -4
  73. package/dist/{chunk-yc2rm4v9.js → chunk-wbabf3hg.js} +1 -1
  74. package/dist/{chunk-aeqm2v91.js → chunk-xbh2vr95.js} +34 -34
  75. package/dist/deploy/index.js +1 -1
  76. package/dist/drivers/index.js +1 -1
  77. package/dist/drivers/shared/deploy-script.d.ts +67 -0
  78. package/dist/index.js +3 -3
  79. package/dist/operations/index.js +1 -1
  80. package/dist/protection/index.js +1 -1
  81. package/dist/spend/index.js +1 -1
  82. package/dist/ui/404.html +1 -1
  83. package/dist/ui/accept-invitation.html +1 -1
  84. package/dist/ui/access-denied.html +1 -1
  85. package/dist/ui/account/automation.html +6 -6
  86. package/dist/ui/account/security.html +2 -2
  87. package/dist/ui/applications/compose.html +6 -6
  88. package/dist/ui/applications/new.html +2 -2
  89. package/dist/ui/data/backups.html +6 -6
  90. package/dist/ui/data/services.html +6 -6
  91. package/dist/ui/data/volumes.html +6 -6
  92. package/dist/ui/index.html +6 -6
  93. package/dist/ui/infrastructure/topology.html +6 -6
  94. package/dist/ui/integrations.html +1 -1
  95. package/dist/ui/operations/alerts.html +6 -6
  96. package/dist/ui/operations/configuration.html +6 -6
  97. package/dist/ui/operations/jobs.html +6 -6
  98. package/dist/ui/operations/maintenance.html +6 -6
  99. package/dist/ui/operations/observability.html +6 -6
  100. package/dist/ui/operations/previews.html +5 -5
  101. package/dist/ui/operations/queue.html +6 -6
  102. package/dist/ui/operations/regions.html +6 -6
  103. package/dist/ui/operations/releases.html +6 -6
  104. package/dist/ui/operations/spend.html +6 -6
  105. package/dist/ui/operations/workloads.html +6 -6
  106. package/dist/ui/security.html +1 -1
  107. package/dist/ui/server/actions.html +6 -6
  108. package/dist/ui/server/activity.html +1 -1
  109. package/dist/ui/server/capacity.html +6 -6
  110. package/dist/ui/server/database.html +6 -6
  111. package/dist/ui/server/deployments.html +6 -6
  112. package/dist/ui/server/diagnostics.html +2 -2
  113. package/dist/ui/server/firewall.html +6 -6
  114. package/dist/ui/server/fleet.html +6 -6
  115. package/dist/ui/server/logs.html +6 -6
  116. package/dist/ui/server/metrics.html +6 -6
  117. package/dist/ui/server/security.html +2 -2
  118. package/dist/ui/server/services.html +2 -2
  119. package/dist/ui/server/sites.html +6 -6
  120. package/dist/ui/server/ssh-keys.html +6 -6
  121. package/dist/ui/server/team.html +6 -6
  122. package/dist/ui/server/terminal.html +2 -2
  123. package/dist/ui/serverless/alarms.html +6 -6
  124. package/dist/ui/serverless/assets.html +1 -1
  125. package/dist/ui/serverless/cost.html +1 -1
  126. package/dist/ui/serverless/data.html +5 -5
  127. package/dist/ui/serverless/deployments.html +2 -2
  128. package/dist/ui/serverless/firewall.html +2 -2
  129. package/dist/ui/serverless/functions.html +6 -6
  130. package/dist/ui/serverless/logs.html +6 -6
  131. package/dist/ui/serverless/metrics.html +2 -2
  132. package/dist/ui/serverless/queues.html +6 -6
  133. package/dist/ui/serverless/secrets.html +6 -6
  134. package/dist/ui/serverless/traces.html +6 -6
  135. package/dist/ui/serverless.html +5 -5
  136. package/package.json +3 -3
  137. package/dist/bin/chunk-052sk11z.js +0 -1
  138. package/dist/bin/chunk-1d4663e1.js +0 -1
  139. package/dist/bin/chunk-223yq2z0.js +0 -1
  140. package/dist/bin/chunk-675txngy.js +0 -1
  141. package/dist/bin/chunk-98e48n5j.js +0 -1
  142. package/dist/bin/chunk-ckmgwd81.js +0 -1
  143. package/dist/bin/chunk-ecxchy9q.js +0 -1
  144. package/dist/bin/chunk-gcazbfsv.js +0 -1
  145. package/dist/bin/chunk-gg6vwdnn.js +0 -1
  146. package/dist/bin/chunk-gz0gahzh.js +0 -1
  147. package/dist/bin/chunk-hdkxj8p9.js +0 -1
  148. package/dist/bin/chunk-hg60j47r.js +0 -1
  149. package/dist/bin/chunk-jxh78anm.js +0 -1
  150. package/dist/bin/chunk-mv0t7j76.js +0 -1
  151. package/dist/bin/chunk-p8f1qgz2.js +0 -1
  152. package/dist/bin/chunk-pa106cr4.js +0 -1
  153. package/dist/bin/chunk-w3815pxd.js +0 -1
  154. package/dist/chunk-43pe4shw.js +0 -1
  155. package/dist/chunk-94tb2msg.js +0 -1
  156. package/dist/chunk-d8wcqgpx.js +0 -1
package/dist/bin/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import{T,Ut,n,r,a,br,e,p,i,o,I,d,go,c,en,Se,Rs,Ps,Ns,Ds,f}from"./chunk-e4p6hwct.js";import{Js,H,Pr,C,F,Io,l,s,E,Ys,Zs,ea,De,Ue,jt,Po,Si,No,bi,Do,ia,oa,sa,aa,la,j,S,L,tt,D,ge,_o,Fe,zt,vi,Ee,xo,Ti,Ht,Ct,da,ua}from"./chunk-kne9dmwr.js";import{xe,_,gn,y,fn,Ni,hn,Ho,kr,nt,Pe,yn,Sn,bn,vn,Tn,En,wn,Cn,ne,Jt,Xt,An,_r,In,Rn,Pn,Nn,Dn,kn,_n,xn,it,Qt,Yt,It,xr,Zt,er,Di,Rt,$n,ce,On,Ln,Mn,$r,Or,Un,Lr,tr,rr,ot,Pt,Nt,Mr,le,Bn,Ra,Pa,nr,Na,q,ki,je,st,at,jn,Ur,Br,Vn,zn,Hn,Fr,qo,de,fe,$e,he,qn,ir}from"./chunk-g00a221j.js";import{u,Hs}from"./chunk-q9tvzcr7.js";import{se,h,Rr}from"./chunk-jeg6vw5v.js";import{Er,Tt,wr,Cr,Ar,Ir,on}from"./chunk-remzfbhd.js";import{b,kt,Qn,ns,pr,La,Ma,Ua,Yn,Zn,pt,Kr,_t,Jr,mt,We,Le,Ge,gt,mr,Me,Ke,ei,ti,Je,Xr,Qr,qi,Gi,hr,xt,ft,$t,Z,Qi,ii,oi,fs}from"./chunk-k8r5zfnf.js";import{Qe,Ot,vs,Ts,Ye,to,ro,no,io,oo,so,G,Lt,M,ai}from"./chunk-1wtkv65w.js";import{J,P,fo,$s,Os,cc,lc,di,bt,ui}from"./chunk-a1kpwbjg.js";import{v}from"./chunk-j2yy3tth.js";import{Q}from"./chunk-bkzt5ntz.js";import{vt,g}from"./chunk-a9yp82q5.js";import{ze,Wo,ue,_i,Gn,xi,$i,jr,Oi,Li,ye,Y,He,Ne,Vr,or,qe,sr,zr,lt,pe,dt,cr,lr,dr,Hr,ur,O,Ui,Bi,Kn,Go,qr,Ko,Jo,_a,Fi,Xo,Qo,Yo,Zo,ut,es,ji,Vi}from"./chunk-9p7g25kx.js";import"./chunk-j8dkatkk.js";import{R}from"./chunk-1chq0c2k.js";import{me}from"./chunk-7577ykbj.js";import{K,yt,po}from"./chunk-9nnfyqme.js";import{ie,W,Da,ka,Ve}from"./chunk-jf8jp6f5.js";import{Gt,pn,Kt,Ri,mn,zo}from"./chunk-yccdr4be.js";import"./chunk-6hrycgbh.js";import{w}from"./chunk-bvtpvstn.js";import{ct}from"./chunk-xtqx4v3f.js";import{ee,be}from"./chunk-3ykx59eg.js";import{V}from"./chunk-yrq5bkxe.js";import"./chunk-a3vraghd.js";import{Pi}from"./chunk-nb5vjsdg.js";import"./chunk-dmyr6etw.js";import"./chunk-s3mv4z3f.js";import{si}from"./chunk-8d8tdhtx.js";import{yr}from"./chunk-573red0g.js";import{ht}from"./chunk-e6xn8g28.js";import{B}from"./chunk-y9cdqk8c.js";import{k}from"./chunk-yqkdyf0w.js";import{existsSync as Jd}from"node:fs";import pa from"node:fs/promises";import ml from"node:os";import ma from"node:path";import lo from"node:process";import{EventEmitter as Yd}from"node:events";import Ji from"node:process";import fl from"node:process";import ha from"node:process";import Ss from"node:process";import Bo from"node:process";import vl from"node:tty";import Bu,{stdin as Bg,stdout as Mg}from"node:process";class bl{configPath;config=null;events=[];retryCount=0;maxRetries=3;retryDelayMs=1000;constructor(){let x=ml.homedir(),t=ma.join(x,".config","clapp");this.configPath=ma.join(t,"telemetry.json")}async isEnabled(){if(lo.env.DO_NOT_TRACK==="1"||lo.env.DO_NOT_TRACK==="true")return!1;if(lo.env.NO_TELEMETRY==="1"||lo.env.NO_TELEMETRY==="true")return!1;return(await this.loadConfig()).enabled}async enable(){let x=await this.loadConfig();if(x.enabled=!0,!x.userId)x.userId=this.generateUserId();await this.saveConfig(x)}async disable(){let x=await this.loadConfig();x.enabled=!1,await this.saveConfig(x)}async track(x,t){if(!await this.isEnabled())return;let A={event:x,...t,timestamp:Date.now(),platform:ml.platform(),nodeVersion:lo.version};if(this.events.push(A),this.events.length>=10)await this.send()}async trackCommand(x,t){await this.track("command",{command:x,duration:t})}async trackError(x,t){await this.track("error",{error:x,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(x){return new Promise((t)=>setTimeout(t,x))}async status(){let x=await this.loadConfig();return{enabled:x.enabled,doNotTrack:lo.env.DO_NOT_TRACK==="1"||lo.env.DO_NOT_TRACK==="true",eventsQueued:this.events.length,lastSent:x.lastSent}}async loadConfig(){if(this.config)return this.config;try{if(Jd(this.configPath)){let x=await pa.readFile(this.configPath,"utf-8");return this.config=JSON.parse(x),this.config}}catch{}return this.config={enabled:!1},this.config}async saveConfig(x){this.config=x;try{let t=ma.dirname(this.configPath);await pa.mkdir(t,{recursive:!0}),await pa.writeFile(this.configPath,JSON.stringify(x,null,2),"utf-8")}catch{}}generateUserId(){let x=Math.random().toString(36).substring(2,15),t=Date.now().toString(36);return`${x}-${t}`}}var kg=new bl;function ga(x){return x.replace(/[<[].+/,"").trim()}function Zd(x){let t=/<([^>]+)>/g,m=/\[([^\]]+)\]/g,A=[],N=(X)=>{let te=!1,re=X[1];if(re.startsWith("..."))re=re.slice(3),te=!0;return{required:X[0].startsWith("<"),value:re,variadic:te}},z;while(z=t.exec(x))A.push(N(z));let U;while(U=m.exec(x))A.push(N(U));return A}function Qd(x){let t={alias:{},boolean:[]};for(let[m,A]of x.entries()){if(A.names.length>1)t.alias[A.names[0]]=A.names.slice(1);if(A.isBoolean)if(A.negated){if(!x.some((z,U)=>U!==m&&z.names.some((X)=>A.names.includes(X))&&typeof z.required==="boolean"))t.boolean.push(A.names[0])}else t.boolean.push(A.names[0])}return t}function gl(x){return x.reduce((t,m)=>t.length>=m.length?t:m,"")}function fa(x,t){return x.length>=t?x:`${x}${" ".repeat(t-x.length)}`}function ws(x){return x.replace(/([a-z])-([a-z])/g,(t,m,A)=>m+A.toUpperCase())}function Xd(x,t,m){let A=0,N=t.length,z=x,U;for(;A<N;++A)U=z[t[A]],z=z[t[A]]=A===N-1?m:U!=null?U:!!~t[A+1].indexOf(".")||!(+t[A+1]>-1)?{}:[]}function eu(x,t){for(let m of Object.keys(t)){let A=t[m];if(A.shouldTransform){if(x[m]=Array.prototype.concat.call([],x[m]),typeof A.transformFunction==="function")x[m]=x[m].map(A.transformFunction)}}}function tu(x){let t=/([^\\/]+)$/.exec(x);return t?t[1]:""}function Mo(x){return x.split(".").map((t,m)=>m===0?ws(t):t).join(".")}class Uo extends Error{exitCode=2;isUsageError=!0;usage;constructor(x,t){super(x);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(x).stack}format(x=!1){if(x&&this.stack)return`${this.message}
2
+ import{T,Ut,n,r,a,br,e,p,i,o,I,d,go,c,en,Se,Rs,Ps,Ns,Ds,f}from"./chunk-tgwx5haz.js";import{Js,H,Pr,w,F,Io,l,s,E,Ys,Zs,ea,De,Ue,jt,Po,Si,No,bi,Do,ia,oa,sa,aa,la,j,S,L,tt,D,ge,_o,Fe,zt,vi,Ee,xo,Ti,Ht,wt,da,ua}from"./chunk-nnawqw34.js";import{xe,_,gn,y,fn,Ni,hn,Ho,kr,nt,Pe,yn,Sn,bn,vn,Tn,En,Cn,wn,ne,Jt,Xt,An,_r,In,Rn,Pn,Nn,Dn,kn,_n,xn,it,Qt,Yt,It,xr,Zt,er,Di,Rt,$n,ce,On,Ln,Mn,$r,Or,Un,Lr,tr,rr,ot,Pt,Nt,Mr,le,Bn,Ra,Pa,nr,Na,q,ki,je,st,at,jn,Ur,Br,Vn,zn,Hn,Fr,qo,de,fe,$e,he,qn,ir}from"./chunk-b9psp74n.js";import{u,Hs}from"./chunk-qe56mnjc.js";import{se,h,Rr}from"./chunk-jzby2kzy.js";import{Er,Tt,Cr,wr,Ar,Ir,on}from"./chunk-6yr12f64.js";import{b,kt,Qn,ns,pr,La,Ma,Ua,Yn,Zn,pt,Kr,_t,Jr,mt,We,Le,Ge,gt,mr,Me,Ke,ei,ti,Je,Xr,Qr,qi,Gi,hr,xt,ft,$t,Z,Qi,ii,oi,fs}from"./chunk-gcm9n6ww.js";import{Qe,Ot,vs,Ts,Ye,to,ro,no,io,oo,so,G,Lt,M,ai}from"./chunk-py8cvqvx.js";import{J,P,fo,$s,Os,cc,lc,di,bt,ui}from"./chunk-wt7wjgcr.js";import{v}from"./chunk-x56s9b7f.js";import{Q}from"./chunk-81mt497w.js";import{vt,g}from"./chunk-kqf19j9m.js";import{ze,Wo,ue,_i,Gn,xi,$i,jr,Oi,Li,ye,Y,He,Ne,Vr,or,qe,sr,zr,lt,pe,dt,cr,lr,dr,Hr,ur,O,Ui,Bi,Kn,Go,qr,Ko,Jo,_a,Fi,Xo,Qo,Yo,Zo,ut,es,ji,Vi}from"./chunk-vhz2wmb8.js";import"./chunk-sdy79wz7.js";import{R}from"./chunk-fpxb97q5.js";import{me}from"./chunk-6wz04wnk.js";import{K,yt,po}from"./chunk-f7dtn45y.js";import{ie,W,Da,ka,Ve}from"./chunk-zk3nm0td.js";import{Gt,pn,Kt,Ri,mn,zo}from"./chunk-yccdr4be.js";import"./chunk-gzxpkk0c.js";import{C}from"./chunk-9e62z9je.js";import{ct}from"./chunk-kgtzvh2k.js";import{ee,be}from"./chunk-q6z8wvvb.js";import{V}from"./chunk-tg45thhb.js";import"./chunk-a3vraghd.js";import{Pi}from"./chunk-mpq5ct10.js";import"./chunk-8mgwqpmx.js";import"./chunk-jhp53qsd.js";import{si}from"./chunk-8d8tdhtx.js";import{yr}from"./chunk-573red0g.js";import{ht}from"./chunk-985ev3ns.js";import{B}from"./chunk-efbmb8rw.js";import{k}from"./chunk-yqkdyf0w.js";import{existsSync as Jd}from"node:fs";import pa from"node:fs/promises";import ml from"node:os";import ma from"node:path";import lo from"node:process";import{EventEmitter as Yd}from"node:events";import Ji from"node:process";import fl from"node:process";import ha from"node:process";import Ss from"node:process";import Bo from"node:process";import vl from"node:tty";import Bu,{stdin as Bg,stdout as Mg}from"node:process";class bl{configPath;config=null;events=[];retryCount=0;maxRetries=3;retryDelayMs=1000;constructor(){let x=ml.homedir(),t=ma.join(x,".config","clapp");this.configPath=ma.join(t,"telemetry.json")}async isEnabled(){if(lo.env.DO_NOT_TRACK==="1"||lo.env.DO_NOT_TRACK==="true")return!1;if(lo.env.NO_TELEMETRY==="1"||lo.env.NO_TELEMETRY==="true")return!1;return(await this.loadConfig()).enabled}async enable(){let x=await this.loadConfig();if(x.enabled=!0,!x.userId)x.userId=this.generateUserId();await this.saveConfig(x)}async disable(){let x=await this.loadConfig();x.enabled=!1,await this.saveConfig(x)}async track(x,t){if(!await this.isEnabled())return;let A={event:x,...t,timestamp:Date.now(),platform:ml.platform(),nodeVersion:lo.version};if(this.events.push(A),this.events.length>=10)await this.send()}async trackCommand(x,t){await this.track("command",{command:x,duration:t})}async trackError(x,t){await this.track("error",{error:x,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(x){return new Promise((t)=>setTimeout(t,x))}async status(){let x=await this.loadConfig();return{enabled:x.enabled,doNotTrack:lo.env.DO_NOT_TRACK==="1"||lo.env.DO_NOT_TRACK==="true",eventsQueued:this.events.length,lastSent:x.lastSent}}async loadConfig(){if(this.config)return this.config;try{if(Jd(this.configPath)){let x=await pa.readFile(this.configPath,"utf-8");return this.config=JSON.parse(x),this.config}}catch{}return this.config={enabled:!1},this.config}async saveConfig(x){this.config=x;try{let t=ma.dirname(this.configPath);await pa.mkdir(t,{recursive:!0}),await pa.writeFile(this.configPath,JSON.stringify(x,null,2),"utf-8")}catch{}}generateUserId(){let x=Math.random().toString(36).substring(2,15),t=Date.now().toString(36);return`${x}-${t}`}}var kg=new bl;function ga(x){return x.replace(/[<[].+/,"").trim()}function Zd(x){let t=/<([^>]+)>/g,m=/\[([^\]]+)\]/g,A=[],N=(X)=>{let te=!1,re=X[1];if(re.startsWith("..."))re=re.slice(3),te=!0;return{required:X[0].startsWith("<"),value:re,variadic:te}},z;while(z=t.exec(x))A.push(N(z));let U;while(U=m.exec(x))A.push(N(U));return A}function Qd(x){let t={alias:{},boolean:[]};for(let[m,A]of x.entries()){if(A.names.length>1)t.alias[A.names[0]]=A.names.slice(1);if(A.isBoolean)if(A.negated){if(!x.some((z,U)=>U!==m&&z.names.some((X)=>A.names.includes(X))&&typeof z.required==="boolean"))t.boolean.push(A.names[0])}else t.boolean.push(A.names[0])}return t}function gl(x){return x.reduce((t,m)=>t.length>=m.length?t:m,"")}function fa(x,t){return x.length>=t?x:`${x}${" ".repeat(t-x.length)}`}function ws(x){return x.replace(/([a-z])-([a-z])/g,(t,m,A)=>m+A.toUpperCase())}function Xd(x,t,m){let A=0,N=t.length,z=x,U;for(;A<N;++A)U=z[t[A]],z=z[t[A]]=A===N-1?m:U!=null?U:!!~t[A+1].indexOf(".")||!(+t[A+1]>-1)?{}:[]}function eu(x,t){for(let m of Object.keys(t)){let A=t[m];if(A.shouldTransform){if(x[m]=Array.prototype.concat.call([],x[m]),typeof A.transformFunction==="function")x[m]=x[m].map(A.transformFunction)}}}function tu(x){let t=/([^\\/]+)$/.exec(x);return t?t[1]:""}function Mo(x){return x.split(".").map((t,m)=>m===0?ws(t):t).join(".")}class Uo extends Error{exitCode=2;isUsageError=!0;usage;constructor(x,t){super(x);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(x).stack}format(x=!1){if(x&&this.stack)return`${this.message}
3
3
 
4
4
  Stack trace:
5
5
  ${this.stack}`;return this.message}}function nu(x){if(x instanceof Uo)return!0;return!!x&&typeof x==="object"&&x.name==="ClappError"&&typeof x.message==="string"}function ru(){let{env:x}=fl,{TERM:t,TERM_PROGRAM:m}=x;if(fl.platform!=="win32")return t!=="linux";return Boolean(x.WT_SESSION)||Boolean(x.TERMINUS_SUBLIME)||x.ConEmuTask==="{cmd::Cmder}"||m==="Terminus-Sublime"||m==="vscode"||t==="xterm-256color"||t==="alacritty"||t==="rxvt-unicode"||t==="rxvt-unicode-256color"||x.TERMINAL_EMULATOR==="JetBrains-JediTerm"}var Cs="\x1B",ln=`${Cs}[`;var hl={to(x,t){if(!t)return`${ln}${x+1}G`;return`${ln}${t+1};${x+1}H`},move(x,t){let m="";if(x<0)m+=`${ln}${-x}D`;else if(x>0)m+=`${ln}${x}C`;if(t<0)m+=`${ln}${-t}A`;else if(t>0)m+=`${ln}${t}B`;return m},up:(x=1)=>`${ln}${x}A`,down:(x=1)=>`${ln}${x}B`,forward:(x=1)=>`${ln}${x}C`,backward:(x=1)=>`${ln}${x}D`,nextLine:(x=1)=>`${ln}E`.repeat(x),prevLine:(x=1)=>`${ln}F`.repeat(x),left:`${ln}G`,hide:`${ln}?25l`,show:`${ln}?25h`,save:`${Cs}7`,restore:`${Cs}8`};var xg={screen:`${ln}2J`,up:(x=1)=>`${ln}1J`.repeat(x),down:(x=1)=>`${ln}J`.repeat(x),line:`${ln}2K`,lineEnd:`${ln}K`,lineStart:`${ln}1K`,lines(x){let t="";for(let m=0;m<x;m++)t+=this.line+(m<x-1?hl.up():"");if(x)t+=hl.left;return t}},Dg={screen:`${Cs}c`};function iu(x,t){let m=[];for(let A=0;A<=t.length;A++)m[A]=[A];for(let A=0;A<=x.length;A++)m[0][A]=A;for(let A=1;A<=t.length;A++)for(let N=1;N<=x.length;N++)if(t.charAt(A-1)===x.charAt(N-1))m[A][N]=m[A-1][N-1];else m[A][N]=Math.min(m[A-1][N-1]+1,m[A][N-1]+1,m[A-1][N]+1);return m[t.length][x.length]}function wl(x,t,m=2,A=3){return t.map((N)=>({cmd:N,distance:iu(x,N)})).filter(({distance:N})=>N<=m).sort((N,z)=>N.distance-z.distance).slice(0,A).map(({cmd:N})=>N)}function ou(x,t={}){let m={_:[]},A=t.alias||{},N=new Set(t.boolean||[]),z={};for(let te of Object.keys(A))for(let re of A[te])z[re]=te;for(let te of N)if(A[te])for(let re of A[te])N.add(re);function U(te,re){if(te===void 0)return re;if(typeof re==="boolean")return re;return Array.isArray(te)?[...te,re]:[te,re]}function X(te,re){let oe=z[te]||te,ae=U(m[oe],re);if(m[oe]=ae,A[oe])for(let ve of A[oe])m[ve]=ae;if(z[te]&&A[z[te]])for(let ve of A[z[te]])m[ve]=ae;m[te]=ae}for(let te=0;te<x.length;te++){let re=x[te];if(re==="--"){m._.push(...x.slice(te+1));break}if(re.startsWith("--")){let oe=re.indexOf("=");if(oe!==-1){let ae=ws(re.slice(2,oe)),ve=re.slice(oe+1);X(ae,ve)}else{let ae=re.slice(2);if(ae.startsWith("no-")){let Re=ws(ae.slice(3));X(Re,!1);continue}let ve=ws(ae),Ce=z[ve]||ve;if(N.has(Ce)||N.has(ve))X(ve,!0);else{let Re=x[te+1];if(Re!==void 0&&!Re.startsWith("-"))X(ve,Re),te++;else X(ve,!0)}}}else if(re.startsWith("-")&&re.length>1){let oe=re.slice(1);for(let ae=0;ae<oe.length;ae++){let ve=oe[ae],Ce=z[ve]||ve;if(ae===oe.length-1&&!N.has(Ce)&&!N.has(ve)){let Re=x[te+1];if(Re!==void 0&&!Re.startsWith("-"))X(ve,Re),te++;else X(ve,!0)}else X(ve,!0)}}else m._.push(re)}return m}class Sl{rawName;description;name;names;isBoolean;required;config;negated;constructor(x,t,m){if(this.rawName=x,this.description=t,this.config=Object.assign({},m),x=x.replace(/\.\*/g,""),this.negated=!1,this.names=ga(x).split(",").map((A)=>{let N=A.trim().replace(/^-{1,2}/,"");if(N.startsWith("no-"))this.negated=!0,N=N.replace(/^no-/,"");return Mo(N)}).sort((A,N)=>A.length>N.length?1:-1),this.name=this.names[this.names.length-1],this.negated&&this.config.default==null)this.config.default=!0;if(x.includes("<"))this.required=!0;else if(x.includes("["))this.required=!1;else this.isBoolean=!0}}var Tg=ha.argv,su=`${ha.platform}-${ha.arch} bun-v${typeof Bun<"u"?Bun.version:"unknown"}`,yl=Ss.argv,au=`${Ss.platform}-${Ss.arch} node-${Ss.version}`;class ya{rawName;description;config;cli;options;aliasNames;name;namespace;args;commandAction;usageText;versionNumber;examples;helpCallback;globalCommand;beforeHooks;afterHooks;middleware;constructor(x,t,m,A){this.rawName=x,this.description=t,this.config=m,this.cli=A,this.options=[],this.aliasNames=[],this.name=ga(x);let N=ga(x),z=N.indexOf(":");if(z>0)this.namespace=N.substring(0,z),this.name=N.substring(z+1);if(this.args=Zd(x),this.examples=[],this.beforeHooks=[],this.afterHooks=[],this.middleware=[],!m)this.config={}}usage(x){return this.usageText=x,this}allowUnknownOptions(){return this.config.allowUnknownOptions=!0,this}ignoreOptionDefaultValue(){return this.config.ignoreOptionDefaultValue=!0,this}version(x,t="-v, --version"){return this.versionNumber=x,this.option(t,"Display version number"),this}example(x){return this.examples.push(x),this}option(x,t,m){let A=new Sl(x,t,m);return this.options.push(A),this}alias(x){return this.aliasNames.push(x),this}action(x){return this.commandAction=x,this}before(x){return this.beforeHooks.push(x),this}after(x){return this.afterHooks.push(x),this}use(x){return this.middleware.push(x),this}isMatched(x){if(this.aliasNames.includes(x))return!0;if(this.namespace)return`${this.namespace}:${this.name}`===x;return this.name===x}get isDefaultCommand(){return this.name===""||this.aliasNames.includes("!")}get isGlobalCommand(){return this instanceof va}get displayName(){return this.namespace?`${this.namespace}:${this.name}`:this.name}hasOption(x){let t=Mo(x.split(".")[0]);return!!this.options.find((m)=>m.names.some((A)=>Mo(A)===t))}outputHelp(){let{name:x,commands:t}=this.cli,{versionNumber:m,options:A,helpCallback:N}=this.cli.globalCommand,z=[{body:`${x}${m?`/${m}`:""}`}];if(z.push({title:"Usage",body:` $ ${x} ${this.usageText||this.rawName}`}),(this.isGlobalCommand||this.isDefaultCommand)&&t.length>0){let re=gl(t.map((Re)=>Re.rawName)),oe=new Map,ae=[];for(let Re of t)if(Re.namespace){if(!oe.has(Re.namespace))oe.set(Re.namespace,[]);oe.get(Re.namespace).push(Re)}else ae.push(Re);let ve="";if(ae.sort((Re,we)=>Re.rawName<we.rawName?-1:Re.rawName>we.rawName?1:0),ae.length>0)ve+=ae.map((Re)=>` ${fa(Re.rawName,re.length)} ${Re.description}`).join(`
@@ -28,7 +28,7 @@ Received ${m}, cleaning up...`),x)try{await x()}catch(A){console.error("Error du
28
28
  `)),this.enableDidYouMean){let t=[];for(let A of this.commands){if(A.displayName)t.push(A.displayName);if(A.aliasNames)t.push(...A.aliasNames)}let m=wl(x,t);if(m.length>0)console.log(Fo.yellow("Did you mean one of these?")),m.forEach((A)=>console.log(` ${Fo.dim("•")} ${this.name} ${A}`)),console.log("")}console.log(Fo.dim("Run"),`${this.name} --help`,Fo.dim("to see all available commands")),Ji.exit(1)}async parse(x=yl,t={}){let{run:m=!0,exitOnError:A=!1}=t;if(A)try{return await this.parse(x,{run:m})}catch(te){throw this.handleUsageError(te),te}if(this.rawArgs=x,!this.name)this.name=x[1]?tu(x[1]):"cli";let N=!0,z=x.slice(2),U=z[0];if(U&&!U.startsWith("-")){for(let te of this.commands)if(te.isMatched(U)){let re=this.mri(z,te);N=!1;let oe={...re,args:re.args.slice(1)};this.setParsedInfo(oe,te,U),this.emit(`command:${U}`,te);break}if(N){let te=z[1];if(te&&!te.startsWith("-")){let re=`${U}:${te}`;for(let oe of this.commands)if(oe.isMatched(re)){let ae=this.mri(z,oe);N=!1;let ve={...ae,args:ae.args.slice(2)};this.setParsedInfo(ve,oe,re),this.emit(`command:${re}`,oe);break}}}}if(N){for(let te of this.commands)if(te.name===""){N=!1;let re=this.mri(z,te);this.setParsedInfo(re,te),this.emit("command:!",te);break}}if(N){let te=this.mri(x.slice(2));this.setParsedInfo(te)}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 X={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 X}mri(x,t){let m=[...this.globalCommand.options,...t?t.options:[]],A=Qd(m),N=[],z=x.indexOf("--");if(z>-1)N=x.slice(z+1),x=x.slice(0,z);let U=ou(x,A),X={_:U._};for(let ve of Object.keys(U))if(ve!=="_")X[Mo(ve)]=U[ve];let te=X._,re={"--":N},oe=t&&t.config.ignoreOptionDefaultValue?t.config.ignoreOptionDefaultValue:this.globalCommand.config.ignoreOptionDefaultValue,ae=Object.create(null);for(let ve of m){if(!oe&&ve.config.default!==void 0)for(let Ce of ve.names)re[Ce]=ve.config.default;if(Array.isArray(ve.config.type)){if(ae[ve.name]===void 0)ae[ve.name]={shouldTransform:!0,transformFunction:ve.config.type[0]}}}for(let ve of Object.keys(X))if(ve!=="_"){let Ce=ve.split(".");Xd(re,Ce,X[ve]),eu(re,ae)}return{args:te,options:re}}async run(x=yl){return this.parse(x,{run:!0,exitOnError:!0})}handleUsageError(x){if(!nu(x)||x.isUsageError===!1)return;let t=x.message||"command-line error",m=this.name?`${this.name}: `:"",A=/--help/.test(t)?"":x.usage?`
29
29
  Usage: ${x.usage}`:`
30
30
  Run \`${this.name??"cli"} --help\` for usage.`;Ji.stderr.write(`${m}${t}${A}
31
- `),Ji.exit(x.exitCode??2)}async runMatchedCommand(){let{args:x,options:t,matchedCommand:m}=this;if(!m||!m.commandAction)return;m.checkUnknownOptions(),m.checkOptionValue(),m.checkRequiredArgs();let A=[];m.args.forEach((X,te)=>{if(X.variadic)A.push(x.slice(te));else A.push(x[te])}),A.push(t);let N={command:m,args:A,options:t};for(let X of m.beforeHooks)await X(N);let z,U=async()=>{let X=m.commandAction.apply(this,A);if(X instanceof Promise)z=await X;else z=X;return z};if(m.middleware.length>0){let X=0,te=async()=>{if(X<m.middleware.length){let re=m.middleware[X++];await re({...N,next:te})}else await U()};await te()}else await U();for(let X of m.afterHooks)await X(N);return z}removeSignalHandlers(){if(!this.signalHandlersSet)return this;return Ji.removeAllListeners("SIGINT"),Ji.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 $l{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(x){if(!this.enabled){this.misses++;return}let t=this.cache.get(x);if(!t){this.misses++;return}if(Date.now()-t.timestamp>t.ttl){this.cache.delete(x),this.misses++;return}return this.hits++,t.value}set(x,t,m=5000){if(!this.enabled)return;this.cache.set(x,{value:t,timestamp:Date.now(),ttl:m})}has(x){if(!this.enabled)return!1;let t=this.cache.get(x);if(!t)return!1;if(Date.now()-t.timestamp>t.ttl)return this.cache.delete(x),!1;return!0}delete(x){this.cache.delete(x)}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 x=Date.now();for(let[t,m]of this.cache.entries())if(x-m.timestamp>m.ttl)this.cache.delete(t)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var Og=new $l;function uu(){if("FORCE_COLOR"in Bo.env)return Bo.env.FORCE_COLOR!=="0";if("NO_COLOR"in Bo.env||Bo.env.TERM==="dumb")return!1;if(Bo.platform==="win32")return!0;return vl.isatty(1)&&vl.isatty(2)}var Cl=uu();function cn(x,t){if(!Cl)return(m)=>m;return(m)=>x+m+t}var pu=cn("\x1B[0m","\x1B[0m"),mu=cn("\x1B[31m","\x1B[39m"),fu=cn("\x1B[32m","\x1B[39m"),gu=cn("\x1B[33m","\x1B[39m"),hu=cn("\x1B[34m","\x1B[39m"),yu=cn("\x1B[35m","\x1B[39m"),vu=cn("\x1B[36m","\x1B[39m"),bu=cn("\x1B[37m","\x1B[39m"),wu=cn("\x1B[90m","\x1B[39m"),Su=cn("\x1B[1m","\x1B[22m"),$u=cn("\x1B[3m","\x1B[23m"),Cu=cn("\x1B[4m","\x1B[24m"),Au=cn("\x1B[2m","\x1B[22m"),ku=cn("\x1B[7m","\x1B[27m"),Ru=cn("\x1B[8m","\x1B[28m"),Iu=cn("\x1B[9m","\x1B[29m"),Pu=cn("\x1B[41m","\x1B[49m"),xu=cn("\x1B[42m","\x1B[49m"),Du=cn("\x1B[43m","\x1B[49m"),Eu=cn("\x1B[44m","\x1B[49m"),Tu=cn("\x1B[45m","\x1B[49m"),Nu=cn("\x1B[46m","\x1B[49m"),Ou=cn("\x1B[47m","\x1B[49m"),Lu=Cl,ju={reset:pu,red:mu,green:fu,yellow:gu,blue:hu,magenta:yu,cyan:vu,white:bu,gray:wu,bold:Su,italic:$u,underline:Cu,dim:Au,inverse:ku,hidden:Ru,strikethrough:Iu,bgRed:Pu,bgGreen:xu,bgYellow:Du,bgBlue:Eu,bgMagenta:Tu,bgCyan:Nu,bgWhite:Ou,isColorSupported:Lu},Fu=ju,Mu=["up","down","left","right","space","enter","cancel"],_g={actions:new Set(Mu),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};var zg=Bu.platform.startsWith("win"),Ug=Symbol("clapp:cancel");var _u=ru();var an=(x,t)=>_u?x:t,Vg=an("◆","*"),Wg=an("■","x"),qg=an("▲","x"),Gg=an("◇","o"),Hg=an("┌","T"),zu=an("│","|"),Kg=an("└","—"),Jg=an("●",">"),Yg=an("○"," "),Zg=an("◻","[•]"),Qg=an("◼","[+]"),Xg=an("◻","[ ]"),eh=an("▪","•"),th=an("─","-"),nh=an("╮","+"),rh=an("├","+"),ih=an("╯","+"),oh=an("●","•"),sh=an("◆","*"),ah=an("▲","!"),ch=an("■","x");var lh={light:an("─","-"),heavy:an("━","="),block:an("█","#")};function Uu(){return`${Fu.gray(zu)} `}var dh=Uu();var wa="0.16.30";function Al(x,t,m=process.env){let A=t?.options.find((U)=>U.name==="profile");if((!A||/\baws\b/i.test(A.description))&&typeof x.profile==="string"&&x.profile)m.AWS_PROFILE=x.profile,delete m.AWS_ACCESS_KEY_ID,delete m.AWS_SECRET_ACCESS_KEY,delete m.AWS_SESSION_TOKEN;if(!t?.options.some((U)=>U.name==="region")&&typeof x.region==="string"&&x.region)m.AWS_REGION=x.region,m.AWS_DEFAULT_REGION=x.region}import{existsSync as Sa}from"node:fs";import{mkdir as Wu,writeFile as kl}from"node:fs/promises";function $a(x){x.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"),Sa("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"),A=t?.mode||await go("Select deployment mode",["serverless","server"]),N=t?.region||await go("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),z=new o("Creating configuration file...");z.start();let U=`import { defineConfig } from '@ts-cloud/core'
31
+ `),Ji.exit(x.exitCode??2)}async runMatchedCommand(){let{args:x,options:t,matchedCommand:m}=this;if(!m||!m.commandAction)return;m.checkUnknownOptions(),m.checkOptionValue(),m.checkRequiredArgs();let A=[];m.args.forEach((X,te)=>{if(X.variadic)A.push(x.slice(te));else A.push(x[te])}),A.push(t);let N={command:m,args:A,options:t};for(let X of m.beforeHooks)await X(N);let z,U=async()=>{let X=m.commandAction.apply(this,A);if(X instanceof Promise)z=await X;else z=X;return z};if(m.middleware.length>0){let X=0,te=async()=>{if(X<m.middleware.length){let re=m.middleware[X++];await re({...N,next:te})}else await U()};await te()}else await U();for(let X of m.afterHooks)await X(N);return z}removeSignalHandlers(){if(!this.signalHandlersSet)return this;return Ji.removeAllListeners("SIGINT"),Ji.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 $l{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(x){if(!this.enabled){this.misses++;return}let t=this.cache.get(x);if(!t){this.misses++;return}if(Date.now()-t.timestamp>t.ttl){this.cache.delete(x),this.misses++;return}return this.hits++,t.value}set(x,t,m=5000){if(!this.enabled)return;this.cache.set(x,{value:t,timestamp:Date.now(),ttl:m})}has(x){if(!this.enabled)return!1;let t=this.cache.get(x);if(!t)return!1;if(Date.now()-t.timestamp>t.ttl)return this.cache.delete(x),!1;return!0}delete(x){this.cache.delete(x)}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 x=Date.now();for(let[t,m]of this.cache.entries())if(x-m.timestamp>m.ttl)this.cache.delete(t)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var Og=new $l;function uu(){if("FORCE_COLOR"in Bo.env)return Bo.env.FORCE_COLOR!=="0";if("NO_COLOR"in Bo.env||Bo.env.TERM==="dumb")return!1;if(Bo.platform==="win32")return!0;return vl.isatty(1)&&vl.isatty(2)}var Cl=uu();function cn(x,t){if(!Cl)return(m)=>m;return(m)=>x+m+t}var pu=cn("\x1B[0m","\x1B[0m"),mu=cn("\x1B[31m","\x1B[39m"),fu=cn("\x1B[32m","\x1B[39m"),gu=cn("\x1B[33m","\x1B[39m"),hu=cn("\x1B[34m","\x1B[39m"),yu=cn("\x1B[35m","\x1B[39m"),vu=cn("\x1B[36m","\x1B[39m"),bu=cn("\x1B[37m","\x1B[39m"),wu=cn("\x1B[90m","\x1B[39m"),Su=cn("\x1B[1m","\x1B[22m"),$u=cn("\x1B[3m","\x1B[23m"),Cu=cn("\x1B[4m","\x1B[24m"),Au=cn("\x1B[2m","\x1B[22m"),ku=cn("\x1B[7m","\x1B[27m"),Ru=cn("\x1B[8m","\x1B[28m"),Iu=cn("\x1B[9m","\x1B[29m"),Pu=cn("\x1B[41m","\x1B[49m"),xu=cn("\x1B[42m","\x1B[49m"),Du=cn("\x1B[43m","\x1B[49m"),Eu=cn("\x1B[44m","\x1B[49m"),Tu=cn("\x1B[45m","\x1B[49m"),Nu=cn("\x1B[46m","\x1B[49m"),Ou=cn("\x1B[47m","\x1B[49m"),Lu=Cl,ju={reset:pu,red:mu,green:fu,yellow:gu,blue:hu,magenta:yu,cyan:vu,white:bu,gray:wu,bold:Su,italic:$u,underline:Cu,dim:Au,inverse:ku,hidden:Ru,strikethrough:Iu,bgRed:Pu,bgGreen:xu,bgYellow:Du,bgBlue:Eu,bgMagenta:Tu,bgCyan:Nu,bgWhite:Ou,isColorSupported:Lu},Fu=ju,Mu=["up","down","left","right","space","enter","cancel"],_g={actions:new Set(Mu),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};var zg=Bu.platform.startsWith("win"),Ug=Symbol("clapp:cancel");var _u=ru();var an=(x,t)=>_u?x:t,Vg=an("◆","*"),Wg=an("■","x"),qg=an("▲","x"),Gg=an("◇","o"),Hg=an("┌","T"),zu=an("│","|"),Kg=an("└","—"),Jg=an("●",">"),Yg=an("○"," "),Zg=an("◻","[•]"),Qg=an("◼","[+]"),Xg=an("◻","[ ]"),eh=an("▪","•"),th=an("─","-"),nh=an("╮","+"),rh=an("├","+"),ih=an("╯","+"),oh=an("●","•"),sh=an("◆","*"),ah=an("▲","!"),ch=an("■","x");var lh={light:an("─","-"),heavy:an("━","="),block:an("█","#")};function Uu(){return`${Fu.gray(zu)} `}var dh=Uu();var wa="0.16.32";function Al(x,t,m=process.env){let A=t?.options.find((U)=>U.name==="profile");if((!A||/\baws\b/i.test(A.description))&&typeof x.profile==="string"&&x.profile)m.AWS_PROFILE=x.profile,delete m.AWS_ACCESS_KEY_ID,delete m.AWS_SECRET_ACCESS_KEY,delete m.AWS_SESSION_TOKEN;if(!t?.options.some((U)=>U.name==="region")&&typeof x.region==="string"&&x.region)m.AWS_REGION=x.region,m.AWS_DEFAULT_REGION=x.region}import{existsSync as Sa}from"node:fs";import{mkdir as Wu,writeFile as kl}from"node:fs/promises";function $a(x){x.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"),Sa("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"),A=t?.mode||await go("Select deployment mode",["serverless","server"]),N=t?.region||await go("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),z=new o("Creating configuration file...");z.start();let U=`import { defineConfig } from '@ts-cloud/core'
32
32
 
33
33
  export default defineConfig({
34
34
  project: {
@@ -64,8 +64,8 @@ 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")}),x.command("init:server","Initialize server-based (EC2) project").action(async()=>{i("Initializing Server-Based Project"),await x.parse(["init","--mode","server"])}),x.command("init:serverless","Initialize serverless (Fargate/Lambda) project").action(async()=>{i("Initializing Serverless Project"),await x.parse(["init","--mode","serverless"])}),x.command("init:hybrid","Initialize hybrid project").action(async()=>{i("Initializing Hybrid Project"),await x.parse(["init","--mode","hybrid"])})}import{readFile as Rl,writeFile as qu}from"node:fs/promises";import{resolve as As}from"node:path";async function Oe(){let x=await Ni();if(!x.project)throw Error("Missing required project configuration in cloud.config.ts");return x}function Wn(x){if(x)switch(x.toLowerCase()){case"porkbun":{let A=process.env.PORKBUN_API_KEY,N=process.env.PORKBUN_SECRET_KEY;if(!A||!N)throw Error("PORKBUN_API_KEY and PORKBUN_SECRET_KEY environment variables are required for Porkbun provider");return{provider:"porkbun",apiKey:A,secretKey:N}}case"godaddy":{let A=process.env.GODADDY_API_KEY,N=process.env.GODADDY_API_SECRET;if(!A||!N)throw Error("GODADDY_API_KEY and GODADDY_API_SECRET environment variables are required for GoDaddy provider");let z=process.env.GODADDY_ENVIRONMENT||"production";return{provider:"godaddy",apiKey:A,apiSecret:N,environment:z}}case"route53":{let A=process.env.AWS_REGION||"us-east-1",N=process.env.AWS_HOSTED_ZONE_ID;return{provider:"route53",region:A,hostedZoneId:N}}case"cloudflare":{let A=process.env.CLOUDFLARE_API_TOKEN;if(!A)throw Error("CLOUDFLARE_API_TOKEN environment variable is required for Cloudflare provider");return{provider:"cloudflare",apiToken:A,zoneId:process.env.CLOUDFLARE_ZONE_ID,accountId:process.env.CLOUDFLARE_ACCOUNT_ID}}default:throw Error(`Unknown DNS provider: ${x}. Supported: porkbun, godaddy, route53, cloudflare`)}if(new ai().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 gi(x){let t=Wn(x);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 M(t)}async function Gu(x){let t=await Oe(),m=y(process.cwd(),t),A=x??Object.keys(t.environments??{})[0]??"production",N=m.environments.get(A);if(!N)throw m.store.close(),Error(`Environment ${A} was not found.`);let z=t.project?.region??"us-east-1",U=_(process.cwd()),X=new Er(m.store),te=new Ir(X,{encryptionKey:U,backends:[new Tt(m.store,U),new wr(new K(z),z),new Cr(new ze(z),z),new Ar]}),re=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return await on(te,m,t,re.id),{config:t,controlPlane:m,env:A,environmentRecord:N,store:X,service:te,actor:re}}async function hi(x,t){let m=await Gu(x);try{return await t(m)}finally{m.controlPlane.store.close()}}async function Sr(x){try{await x()}catch(t){r(t instanceof Error?t.message:String(t))}}function Hu(x){return x.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 Il(x,t=process.env){if(x.value!=null)throw Error("Secret values are not accepted in command arguments; use --from-env or the interactive prompt.");if(!x.fromEnv)return;let m=t[x.fromEnv];if(!m)throw Error(`${x.fromEnv} is empty or unavailable.`);return m}function Zr(x,t){let m=t.scope??"environment";if(m==="project")return{type:m,id:x.controlPlane.project.id};if(m==="environment"){let A=t.target?x.controlPlane.store.listEnvironments(x.controlPlane.project.id).find((N)=>N.id===t.target||N.slug===t.target):x.environmentRecord;if(!A)throw Error(`Environment ${t.target} was not found.`);return{type:m,id:A.id,environmentId:A.id}}if(m==="service"){let A=x.controlPlane.store.listResources(x.controlPlane.project.id),N=A.find((z)=>z.id===t.target||z.slug===t.target);if(!N)throw Error("Service scope requires --target with a resource ID or slug.");return{type:m,id:N.id,environmentId:N.environmentId,resourceId:N.id}}if(m==="function"){let A=x.controlPlane.store.listResources(x.controlPlane.project.id),N=A.find((z)=>z.kind==="function"&&(z.id===t.target||z.slug===t.target));if(!N)throw Error("Function scope requires --target with a function resource ID or slug.");return{type:m,id:N.id,environmentId:N.environmentId,resourceId:N.id}}if(m==="preview"){let A=new Le(x.controlPlane.store).listInstances({projectId:x.controlPlane.project.id}).find((N)=>N.id===t.target||N.name===t.target);if(!A)throw Error("Preview scope requires --target with a preview ID or name.");return{type:m,id:A.id,environmentId:A.baseEnvironmentId,resourceId:A.resourceId,previewId:A.id}}throw Error("--scope must be project, environment, service, function, or preview.")}function Ca(x){let t=x??"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 So(x,t,m){let A=x.store.find(x.controlPlane.project.id,m,t);if(!A)throw Error(`${t} was not found in the selected scope.`);return A}async function Ku(x,t){if(x==="secret")return Il(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 yi(x){return x.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 Aa(x){x.command("config","Show validated file configuration").action(async()=>Sr(async()=>{let t=await Oe();console.log(JSON.stringify(t,null,2))})),x.command("config:validate","Validate configuration file").action(async()=>Sr(async()=>{let t=await Oe();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"}`)})),yi(x.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)=>Sr(async()=>hi(t.env,async(m)=>{let A=Zr(m,t),N=["variable","secret"].includes(t.kind??"")?t.kind:void 0,z=m.service.list({projectId:m.controlPlane.project.id,scope:A,kind:N,search:t.search,canReadSecretMetadata:!0});if(t.json)console.log(JSON.stringify(z,null,2));else c(["Key","Kind","Scope","Precedence","Value/backend","Backend version","Requirement","Updated"],Hu(z))}))),yi(x.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)=>Sr(async()=>hi(m.env,async(A)=>{let N=Zr(A,m),z=m.secret?"secret":"variable",U=z==="secret"?Ca(m.backend):void 0,X=U==="external"?void 0:await Ku(z,m),te=await A.service.set({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,scope:N,key:t,kind:z,value:X,reference:m.reference,backend:U,required:!!m.required,confirmed:m.confirm===t,actorId:A.actor.id,idempotencyKey:`cli:${crypto.randomUUID()}`});if(m.json)console.log(JSON.stringify({entry:A.service.list({projectId:A.controlPlane.project.id,scope:N,canReadSecretMetadata:!0}).find((re)=>re.id===te.entry.id),mutation:te.mutation,warnings:te.warnings},null,2));else n(`${t}: ${te.mutation.added.length?"created":te.mutation.changed.length?"updated":"unchanged"}; ${te.mutation.affectedResourceIds.length} affected service(s).`)}))),yi(x.command("config:delete <key>","Delete a scoped variable or secret")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>Sr(async()=>hi(m.env,async(A)=>{let N=So(A,t,Zr(A,m));await A.service.remove({entryId:N.id,expectedVersion:N.version,confirmed:m.confirm===t,actorId:A.actor.id}),n(`Deleted ${t}.`)}))),yi(x.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)=>Sr(async()=>hi(m.env,async(A)=>{let N=await A.service.importDotenv({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,scope:Zr(A,m),source:await Rl(As(t),"utf8"),secretKeys:m.secretKeys?.split(",").map((z)=>z.trim()).filter(Boolean),backend:Ca(m.backend),confirmed:m.confirm==="import",actorId:A.actor.id,idempotencyKey:`cli-import:${crypto.randomUUID()}`});if(!N.document.valid)throw Error(N.document.diagnostics.map((z)=>`line ${z.line}: ${z.message}`).join(`
68
- `));if(m.json)console.log(JSON.stringify({mutation:N.mutation,diagnostics:N.document.diagnostics,warnings:N.warnings},null,2));else n(`Imported ${N.mutation?.added.length??0} new, ${N.mutation?.changed.length??0} changed, and ${N.mutation?.unchanged.length??0} unchanged entries.`)}))),yi(x.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)=>Sr(async()=>hi(m.env,async(A)=>{let{parseDotenv:N}=await import("./chunk-ee6ssn26.js"),z=N(await Rl(As(t),"utf8"));if(!z.valid)throw Error(z.diagnostics.map((X)=>`line ${X.line}: ${X.message}`).join(`
67
+ 3. Run 'cloud deploy' to deploy your infrastructure`,"green")}),x.command("init:server","Initialize server-based (EC2) project").action(async()=>{i("Initializing Server-Based Project"),await x.parse(["init","--mode","server"])}),x.command("init:serverless","Initialize serverless (Fargate/Lambda) project").action(async()=>{i("Initializing Serverless Project"),await x.parse(["init","--mode","serverless"])}),x.command("init:hybrid","Initialize hybrid project").action(async()=>{i("Initializing Hybrid Project"),await x.parse(["init","--mode","hybrid"])})}import{readFile as Rl,writeFile as qu}from"node:fs/promises";import{resolve as As}from"node:path";async function Oe(){let x=await Ni();if(!x.project)throw Error("Missing required project configuration in cloud.config.ts");return x}function Wn(x){if(x)switch(x.toLowerCase()){case"porkbun":{let A=process.env.PORKBUN_API_KEY,N=process.env.PORKBUN_SECRET_KEY;if(!A||!N)throw Error("PORKBUN_API_KEY and PORKBUN_SECRET_KEY environment variables are required for Porkbun provider");return{provider:"porkbun",apiKey:A,secretKey:N}}case"godaddy":{let A=process.env.GODADDY_API_KEY,N=process.env.GODADDY_API_SECRET;if(!A||!N)throw Error("GODADDY_API_KEY and GODADDY_API_SECRET environment variables are required for GoDaddy provider");let z=process.env.GODADDY_ENVIRONMENT||"production";return{provider:"godaddy",apiKey:A,apiSecret:N,environment:z}}case"route53":{let A=process.env.AWS_REGION||"us-east-1",N=process.env.AWS_HOSTED_ZONE_ID;return{provider:"route53",region:A,hostedZoneId:N}}case"cloudflare":{let A=process.env.CLOUDFLARE_API_TOKEN;if(!A)throw Error("CLOUDFLARE_API_TOKEN environment variable is required for Cloudflare provider");return{provider:"cloudflare",apiToken:A,zoneId:process.env.CLOUDFLARE_ZONE_ID,accountId:process.env.CLOUDFLARE_ACCOUNT_ID}}default:throw Error(`Unknown DNS provider: ${x}. Supported: porkbun, godaddy, route53, cloudflare`)}if(new ai().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 gi(x){let t=Wn(x);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 M(t)}async function Gu(x){let t=await Oe(),m=y(process.cwd(),t),A=x??Object.keys(t.environments??{})[0]??"production",N=m.environments.get(A);if(!N)throw m.store.close(),Error(`Environment ${A} was not found.`);let z=t.project?.region??"us-east-1",U=_(process.cwd()),X=new Er(m.store),te=new Ir(X,{encryptionKey:U,backends:[new Tt(m.store,U),new Cr(new K(z),z),new wr(new ze(z),z),new Ar]}),re=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return await on(te,m,t,re.id),{config:t,controlPlane:m,env:A,environmentRecord:N,store:X,service:te,actor:re}}async function hi(x,t){let m=await Gu(x);try{return await t(m)}finally{m.controlPlane.store.close()}}async function Sr(x){try{await x()}catch(t){r(t instanceof Error?t.message:String(t))}}function Hu(x){return x.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 Il(x,t=process.env){if(x.value!=null)throw Error("Secret values are not accepted in command arguments; use --from-env or the interactive prompt.");if(!x.fromEnv)return;let m=t[x.fromEnv];if(!m)throw Error(`${x.fromEnv} is empty or unavailable.`);return m}function Zr(x,t){let m=t.scope??"environment";if(m==="project")return{type:m,id:x.controlPlane.project.id};if(m==="environment"){let A=t.target?x.controlPlane.store.listEnvironments(x.controlPlane.project.id).find((N)=>N.id===t.target||N.slug===t.target):x.environmentRecord;if(!A)throw Error(`Environment ${t.target} was not found.`);return{type:m,id:A.id,environmentId:A.id}}if(m==="service"){let A=x.controlPlane.store.listResources(x.controlPlane.project.id),N=A.find((z)=>z.id===t.target||z.slug===t.target);if(!N)throw Error("Service scope requires --target with a resource ID or slug.");return{type:m,id:N.id,environmentId:N.environmentId,resourceId:N.id}}if(m==="function"){let A=x.controlPlane.store.listResources(x.controlPlane.project.id),N=A.find((z)=>z.kind==="function"&&(z.id===t.target||z.slug===t.target));if(!N)throw Error("Function scope requires --target with a function resource ID or slug.");return{type:m,id:N.id,environmentId:N.environmentId,resourceId:N.id}}if(m==="preview"){let A=new Le(x.controlPlane.store).listInstances({projectId:x.controlPlane.project.id}).find((N)=>N.id===t.target||N.name===t.target);if(!A)throw Error("Preview scope requires --target with a preview ID or name.");return{type:m,id:A.id,environmentId:A.baseEnvironmentId,resourceId:A.resourceId,previewId:A.id}}throw Error("--scope must be project, environment, service, function, or preview.")}function Ca(x){let t=x??"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 So(x,t,m){let A=x.store.find(x.controlPlane.project.id,m,t);if(!A)throw Error(`${t} was not found in the selected scope.`);return A}async function Ku(x,t){if(x==="secret")return Il(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 yi(x){return x.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 Aa(x){x.command("config","Show validated file configuration").action(async()=>Sr(async()=>{let t=await Oe();console.log(JSON.stringify(t,null,2))})),x.command("config:validate","Validate configuration file").action(async()=>Sr(async()=>{let t=await Oe();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"}`)})),yi(x.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)=>Sr(async()=>hi(t.env,async(m)=>{let A=Zr(m,t),N=["variable","secret"].includes(t.kind??"")?t.kind:void 0,z=m.service.list({projectId:m.controlPlane.project.id,scope:A,kind:N,search:t.search,canReadSecretMetadata:!0});if(t.json)console.log(JSON.stringify(z,null,2));else c(["Key","Kind","Scope","Precedence","Value/backend","Backend version","Requirement","Updated"],Hu(z))}))),yi(x.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)=>Sr(async()=>hi(m.env,async(A)=>{let N=Zr(A,m),z=m.secret?"secret":"variable",U=z==="secret"?Ca(m.backend):void 0,X=U==="external"?void 0:await Ku(z,m),te=await A.service.set({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,scope:N,key:t,kind:z,value:X,reference:m.reference,backend:U,required:!!m.required,confirmed:m.confirm===t,actorId:A.actor.id,idempotencyKey:`cli:${crypto.randomUUID()}`});if(m.json)console.log(JSON.stringify({entry:A.service.list({projectId:A.controlPlane.project.id,scope:N,canReadSecretMetadata:!0}).find((re)=>re.id===te.entry.id),mutation:te.mutation,warnings:te.warnings},null,2));else n(`${t}: ${te.mutation.added.length?"created":te.mutation.changed.length?"updated":"unchanged"}; ${te.mutation.affectedResourceIds.length} affected service(s).`)}))),yi(x.command("config:delete <key>","Delete a scoped variable or secret")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>Sr(async()=>hi(m.env,async(A)=>{let N=So(A,t,Zr(A,m));await A.service.remove({entryId:N.id,expectedVersion:N.version,confirmed:m.confirm===t,actorId:A.actor.id}),n(`Deleted ${t}.`)}))),yi(x.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)=>Sr(async()=>hi(m.env,async(A)=>{let N=await A.service.importDotenv({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,scope:Zr(A,m),source:await Rl(As(t),"utf8"),secretKeys:m.secretKeys?.split(",").map((z)=>z.trim()).filter(Boolean),backend:Ca(m.backend),confirmed:m.confirm==="import",actorId:A.actor.id,idempotencyKey:`cli-import:${crypto.randomUUID()}`});if(!N.document.valid)throw Error(N.document.diagnostics.map((z)=>`line ${z.line}: ${z.message}`).join(`
68
+ `));if(m.json)console.log(JSON.stringify({mutation:N.mutation,diagnostics:N.document.diagnostics,warnings:N.warnings},null,2));else n(`Imported ${N.mutation?.added.length??0} new, ${N.mutation?.changed.length??0} changed, and ${N.mutation?.unchanged.length??0} unchanged entries.`)}))),yi(x.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)=>Sr(async()=>hi(m.env,async(A)=>{let{parseDotenv:N}=await import("./chunk-zhkbdsfs.js"),z=N(await Rl(As(t),"utf8"));if(!z.valid)throw Error(z.diagnostics.map((X)=>`line ${X.line}: ${X.message}`).join(`
69
69
  `));let U=A.service.plan({projectId:A.controlPlane.project.id,scope:Zr(A,m),values:z.values,removeMissing:!!m.removeMissing});if(m.json)console.log(JSON.stringify(U,null,2));else c(["Action","Keys"],[["Add",U.added.join(", ")||"—"],["Change",U.changed.join(", ")||"—"],["Remove",U.removed.join(", ")||"—"],["Unchanged",U.unchanged.join(", ")||"—"]]),e(`${U.affectedResourceIds.length} affected service(s); no secret values were printed.`)}))),yi(x.command("config:export [file]","Export plaintext variables only as dotenv")).action(async(t,m)=>Sr(async()=>hi(m.env,async(A)=>{let N=A.service.exportVariables({projectId:A.controlPlane.project.id,scope:Zr(A,m)});if(t)await qu(As(t),N,{mode:384}),n(`Exported variables to ${As(t)}; secrets were excluded.`);else process.stdout.write(N)}))),yi(x.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)=>Sr(async()=>hi(m.env,async(A)=>{let N=Zr(A,m),z=So(A,t,N),U=Zr(A,{env:m.env,scope:m.toScope,target:m.toTarget}),X=await A.service.transfer({entryId:z.id,targetScope:U,mode:m.move?"move":"copy",confirmed:m.confirm===t,actorId:A.actor.id,idempotencyKey:`cli-transfer:${crypto.randomUUID()}`});n(`${t} ${m.move?"moved":"copied"}; ${X.mutation.affectedResourceIds.length} affected service(s).`)}))),yi(x.command("config:reveal <key>","Reveal a secret after exact confirmation")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>Sr(async()=>hi(m.env,async(A)=>{if(m.confirm!==t)throw Error(`Use --confirm ${t} to reveal this secret.`);let N=So(A,t,Zr(A,m));console.log(await A.service.reveal({entryId:N.id,canRevealSecrets:!0,recentlyAuthenticated:!0,actorId:A.actor.id}))}))),yi(x.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)=>Sr(async()=>hi(t.environment??t.env,async(m)=>{let A=Zr(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:A,kind:"variable"}).map((N)=>[N.key,N.value??"",N.updatedAt]));else if(t.set){let N=t.set.indexOf("=");if(N<1)throw Error("Use --set KEY=VALUE.");let z=t.set.slice(0,N),U=t.set.slice(N+1);await m.service.set({organizationId:m.controlPlane.organization.id,projectId:m.controlPlane.project.id,scope:A,key:z,kind:"variable",value:U,confirmed:!1,actorId:m.actor.id}),n(`Set ${z}.`)}else if(t.unset){let N=So(m,t.unset,A);await m.service.remove({entryId:N.id,expectedVersion:N.version,confirmed:await d(`Delete ${N.key}?`,!1),actorId:m.actor.id}),n(`Deleted ${N.key}.`)}else e("Use --list, --set KEY=VALUE, or --unset KEY.")}))),yi(x.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)=>Sr(async()=>hi(t.env,async(m)=>{let A=Zr(m,t);if(t.list)c(["Key","Backend","Version","Rotated"],m.service.list({projectId:m.controlPlane.project.id,scope:A,kind:"secret",canReadSecretMetadata:!0}).map((N)=>[N.key,N.backend??"restricted",N.backendVersion??"—",N.rotatedAt??"never"]));else if(t.create||t.update){let N=t.create??t.update,z=Il(t)??await I("Enter secret value (write-only)","");await m.service.set({organizationId:m.controlPlane.organization.id,projectId:m.controlPlane.project.id,scope:A,key:N,kind:"secret",value:z,backend:Ca(t.backend),confirmed:t.confirm===N,actorId:m.actor.id}),n(`${N} ${t.update?"rotated":"created"}.`)}else if(t.get){if(t.confirm!==t.get)throw Error(`Use --confirm ${t.get} to reveal.`);let N=So(m,t.get,A);console.log(await m.service.reveal({entryId:N.id,canRevealSecrets:!0,recentlyAuthenticated:!0,actorId:m.actor.id}))}else if(t.delete){let N=So(m,t.delete,A);await m.service.remove({entryId:N.id,expectedVersion:N.version,confirmed:t.confirm===N.key,actorId:m.actor.id}),n(`Deleted ${N.key}.`)}else e("Use --list, --create, --update, --get, or --delete.")})))}import{existsSync as ap}from"node:fs";import{readFile as cp,writeFile as lp}from"node:fs/promises";async function ks(x){let t=[];for(let m of x.steps)try{t.push({step:m,state:await m.satisfied()?"satisfied":"pending"})}catch(A){t.push({step:m,state:"unknown",reason:A instanceof Error?A.message:String(A)})}return t}function Pl(x){return x.filter((t)=>t.state!=="satisfied")}function Ju(x){return Pl(x).some((t)=>t.step.destructive===!0)}function Is(x,t){let m=Pl(t),A=[`${x.operation} ${x.target}`];if(m.length===0)return A.push(" Nothing to do — every step is already satisfied."),A;for(let{step:z,state:U,reason:X}of t){let te=U==="satisfied"?"ok ":U==="unknown"?"? ":"→ ",re=[z.destructive?"DESTRUCTIVE":"",U==="satisfied"?"already done":""].filter(Boolean);if(A.push(` ${te}${z.title}${re.length>0?` [${re.join(", ")}]`:""}`),z.change)A.push(` ${z.change.from} → ${z.change.to}`);if(X)A.push(` could not check: ${X}`)}let N=m.filter((z)=>z.step.destructive).length;return A.push(` ${m.length} step(s) would run`+(N>0?`, ${N} irreversible — re-run with --confirm ${x.target}`:"")),A}async function xs(x,t,m={}){let A={operation:x.operation,target:x.target,steps:[],success:!0};if(Ju(t)&&m.confirm!==x.target)throw Error(`${x.operation} includes an irreversible step. Re-run with --confirm ${x.target} to authorize it.`);for(let{step:N,state:z}of t){if(z==="satisfied"){A.steps.push({id:N.id,title:N.title,state:"skipped"}),m.log?.(`skip ${N.title} (already done)`);continue}m.audit?.({operation:x.operation,target:x.target,step:N.id,state:"started"});try{await N.apply(),A.steps.push({id:N.id,title:N.title,state:"applied"}),m.log?.(`done ${N.title}`),m.audit?.({operation:x.operation,target:x.target,step:N.id,state:"applied"})}catch(U){let X=U instanceof Error?U.message:String(U);return A.steps.push({id:N.id,title:N.title,state:"failed",error:X}),A.success=!1,m.log?.(`FAIL ${N.title}: ${X}`),m.audit?.({operation:x.operation,target:x.target,step:N.id,state:"failed",error:X}),A}}return A}function $o(x,t){return`/tmp/ts-cloud-move-${x}-${t}.tar.gz`}function xl(x,t){return`${x}-${t}*.service`}function Wt(x){return`'${x.split("'").join("'\\''")}'`}function Yu(x,t){return["set -eu",`for unit in $(ls /etc/systemd/system/ 2>/dev/null | grep -E ${Wt(Dl(x,t))} || true); do`,' systemctl stop "$unit" 2>/dev/null || true',"done"].join(`
70
70
  `)}function Dl(x,t){return`^${x}-${t}-((queue|daemon)-.*|scheduler)\\.service$`}function Zu(x,t){return El(Dl(x,t))}function El(x){return[`ls /etc/systemd/system/ 2>/dev/null | grep -E ${Wt(x)} | while read -r unit; do`,' systemctl is-active "$unit" >/dev/null 2>&1 && echo "active:$unit" || true',"done"].join(`
71
71
  `)}function Qu(x){return!x.split(`
@@ -79,12 +79,12 @@ Next steps:
79
79
  `)}function Ia(x){let t=new Map;for(let m of x.split(`
80
80
  `)){let A=m.trim();if(!A.startsWith("cert:"))continue;let[,N,z]=A.split(":");if(N&&z)t.set(N,z)}return t}function Ll(x,t){for(let[m,A]of x){if(A==="absent")continue;if(t.get(m)!==A)return!1}return!0}function jl(x,t,m){let A=t.flatMap((N)=>[`${N}.crt`,`${N}.key`]);return["set -eu",`rm -f ${Wt(m)}`,`cd ${Wt(x)} 2>/dev/null || { echo "no certs dir at ${x}" >&2; exit 0; }`,`TS_CLOUD_CERTS="$(ls ${A.map(Wt).join(" ")} 2>/dev/null | tr '\\n' ' ' || true)"`,'[ -n "$TS_CLOUD_CERTS" ] || { echo "no certificates to carry"; exit 0; }',`tar czf ${Wt(m)} $TS_CLOUD_CERTS`].join(`
81
81
  `)}function Fl(x,t){return["set -eu",`[ -s ${Wt(t)} ] || { echo "no certificate archive staged"; exit 0; }`,`mkdir -p ${Wt(x)}`,`tar xzf ${Wt(t)} -C ${Wt(x)}`,`chmod 600 ${Wt(x)}/*.key 2>/dev/null || true`,`rm -f ${Wt(t)}`].join(`
82
- `)}async function Bl(x,t){let{slug:m,siteName:A,appBase:N,from:z,to:U,targetAddress:X}=x;if(z===U)throw Error(`'${A}' is already on ${U}.`);if(x.database&&!t.database)throw Error(`'${A}' uses the on-box database '${x.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(x.database&&t.database&&await t.database.targetHasData())throw Error(`${U} already has a database named '${x.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 te=[{id:"pause-workers",title:`Stop background work on ${z} so the snapshot is consistent`,satisfied:async()=>Qu(await t.runOnSource(Zu(m,A))),apply:async()=>{await t.runOnSource(Yu(m,A))}},...t.database?[{id:"database-dump",title:`Dump the '${x.database?.name}' database on ${z}`,satisfied:async()=>!1,apply:()=>t.database.dump()}]:[],{id:"snapshot",title:`Archive the site's tree and units on ${z}`,change:{from:`${z}:${N}`,to:$o(m,A)},satisfied:async()=>!1,apply:async()=>{await t.runOnSource(Xu(m,A,N))}},{id:"transfer",title:`Carry the archive to ${U}`,change:{from:z,to:U},satisfied:()=>t.archiveStaged(),apply:()=>t.transferArchive()},...t.database?[{id:"database-transfer",title:`Carry the database dump to ${U}`,change:{from:z,to:U},satisfied:()=>t.database.dumpStaged(),apply:()=>t.database.transferDump()},{id:"database-restore",title:`Create and load '${x.database?.name}' on ${U}`,satisfied:async()=>!1,apply:()=>t.database.restore()}]:[],{id:"restore",title:`Unpack the site on ${U} and start it`,change:{from:$o(m,A),to:`${U}:${N}`},satisfied:async()=>np(await t.runOnTarget(tp(m,A,N))),apply:async()=>{await t.runOnTarget(ep(m,A,N))}}];if(x.port!=null){let{port:re}=x;te.push({id:"health",title:`Wait for the site to answer on ${U} (127.0.0.1:${re})`,satisfied:async()=>!1,apply:async()=>{await t.runOnTarget(rp(re,x.healthCheckPath??"/"))}})}return te.push(...t.certificates?[{id:"certificates",title:`Carry the site's TLS material to ${U}`,satisfied:()=>t.certificates.inPlace(),apply:()=>t.certificates.carry()}]:[],{id:"gateway",title:`Route the site on ${U}`,satisfied:()=>t.targetRoutesSite(),apply:()=>t.refreshTargetGateway()},{id:"dns",title:`Point DNS at ${U}`,change:{from:`${z}`,to:X},satisfied:async()=>await t.publishedAddress()===X,apply:async()=>{let re=await t.cutoverDns();if(re.length>0)throw Error(re.join("; "))}},{id:"drain-source",title:`Stop the site on ${z}, leaving its files in place`,satisfied:async()=>sp(await t.runOnSource(op(m,A))),apply:async()=>{await t.runOnSource(ip(m,A))}}),{operation:"site:move",target:A,steps:te}}async function uo(x,t,m){let A=await x.exec(t,m);if(A.code!==0)throw Error(`${t.name}: ${A.stderr.trim()||A.stdout.trim()||`exited ${A.code}`}`);return A.stdout}async function Co(x,t,m){let A=Bun.spawn(["scp","-P",String(x.sshPort),"-o","BatchMode=yes","-o","StrictHostKeyChecking=yes",t,m],{stdout:"pipe",stderr:"pipe"}),[N,z]=await Promise.all([A.exited,new Response(A.stderr).text()]);if(N!==0)throw Error(`scp failed: ${z.trim()||`exited ${N}`}`)}async function dp(x,t){if(!t.to)throw Error("Name the target server with --to <server>.");let m=await Oe(),A=m.sites?.[x];if(!A)throw Error(`Site '${x}' is not in cloud.config.ts.`);let N=m.project.slug,z=y(process.cwd(),m);try{let X=new Pe(z.store).list(z.project.id),te=(rt)=>{let Vt=X.find((qt)=>qt.id===rt||qt.name===rt);if(!Vt)throw Error(`Server ${rt} was not found. Run \`cloud server:list\`.`);if(Vt.trustState!=="pinned")throw Error(`${Vt.name} has no pinned host key. Run \`cloud server:validate ${Vt.name}\` first.`);return Vt},re=te(t.to),oe=X.filter((rt)=>rt.id!==re.id);if(!t.from&&oe.length!==1)throw Error(`Name the source server with --from <server> — ${oe.length} others are enrolled.`);let ae=te(t.from??oe[0].name),ve=new kr,Ce=J(N,x),Re=$o(N,x),we=A.domain,ke=m.infrastructure?.dns?.provider,Be=m.infrastructure?.compute?.proxy?.engine==="rpx"?m.infrastructure.compute.proxy:void 0,Ae=m.infrastructure?.dns?.domain,Ie=(rt)=>Ae&&rt.endsWith(Ae)?Ae:rt.split(".").slice(-2).join("."),Te=C(m),Xe=Te?.name&&di(Te)?{name:Te.name}:void 0,_e=`/tmp/ts-cloud-move-${N}-${x}.sql.gz`,Ze=Nl(N,x),et=Be?.certsDir??jr,At=[we,...A.aliases??[]].filter((rt)=>!!rt),Ft=Te?.engine??"mysql",wt={runOnSource:(rt)=>uo(ve,ae,rt),runOnTarget:(rt)=>uo(ve,re,rt),archiveStaged:async()=>(await ve.exec(re,`test -f ${Re} && echo staged || true`)).stdout.includes("staged"),transferArchive:async()=>{let rt=`${process.cwd()}/.ts-cloud-move-${N}-${x}.tar.gz`;await Co(ae,`${ae.sshUser}@${ae.endpoint}:${Re}`,rt),await Co(re,rt,`${re.sshUser}@${re.endpoint}:${Re}`),await Bun.file(rt).delete().catch(()=>{})},targetRoutesSite:async()=>{if(!Be||!we)return!0;return(await ve.exec(re,`grep -qF ${JSON.stringify(we)} /etc/rpx/sites.d/${N}.json 2>/dev/null && echo routed || true`)).stdout.includes("routed")},refreshTargetGateway:async()=>{if(!Be)throw Error(`This project does not run the rpx gateway, so the move cannot publish a route. Point ${re.name}'s web server at ${Ce}/current yourself, then re-run to continue.`);await uo(ve,re,He({config:ye(m.sites??{},{proxy:Be,slug:N,lan:Li(m)}),slug:N,preserveManagementDashboardRoutes:!0}).join(`
82
+ `)}async function Bl(x,t){let{slug:m,siteName:A,appBase:N,from:z,to:U,targetAddress:X}=x;if(z===U)throw Error(`'${A}' is already on ${U}.`);if(x.database&&!t.database)throw Error(`'${A}' uses the on-box database '${x.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(x.database&&t.database&&await t.database.targetHasData())throw Error(`${U} already has a database named '${x.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 te=[{id:"pause-workers",title:`Stop background work on ${z} so the snapshot is consistent`,satisfied:async()=>Qu(await t.runOnSource(Zu(m,A))),apply:async()=>{await t.runOnSource(Yu(m,A))}},...t.database?[{id:"database-dump",title:`Dump the '${x.database?.name}' database on ${z}`,satisfied:async()=>!1,apply:()=>t.database.dump()}]:[],{id:"snapshot",title:`Archive the site's tree and units on ${z}`,change:{from:`${z}:${N}`,to:$o(m,A)},satisfied:async()=>!1,apply:async()=>{await t.runOnSource(Xu(m,A,N))}},{id:"transfer",title:`Carry the archive to ${U}`,change:{from:z,to:U},satisfied:()=>t.archiveStaged(),apply:()=>t.transferArchive()},...t.database?[{id:"database-transfer",title:`Carry the database dump to ${U}`,change:{from:z,to:U},satisfied:()=>t.database.dumpStaged(),apply:()=>t.database.transferDump()},{id:"database-restore",title:`Create and load '${x.database?.name}' on ${U}`,satisfied:async()=>!1,apply:()=>t.database.restore()}]:[],{id:"restore",title:`Unpack the site on ${U} and start it`,change:{from:$o(m,A),to:`${U}:${N}`},satisfied:async()=>np(await t.runOnTarget(tp(m,A,N))),apply:async()=>{await t.runOnTarget(ep(m,A,N))}}];if(x.port!=null){let{port:re}=x;te.push({id:"health",title:`Wait for the site to answer on ${U} (127.0.0.1:${re})`,satisfied:async()=>!1,apply:async()=>{await t.runOnTarget(rp(re,x.healthCheckPath??"/"))}})}return te.push(...t.certificates?[{id:"certificates",title:`Carry the site's TLS material to ${U}`,satisfied:()=>t.certificates.inPlace(),apply:()=>t.certificates.carry()}]:[],{id:"gateway",title:`Route the site on ${U}`,satisfied:()=>t.targetRoutesSite(),apply:()=>t.refreshTargetGateway()},{id:"dns",title:`Point DNS at ${U}`,change:{from:`${z}`,to:X},satisfied:async()=>await t.publishedAddress()===X,apply:async()=>{let re=await t.cutoverDns();if(re.length>0)throw Error(re.join("; "))}},{id:"drain-source",title:`Stop the site on ${z}, leaving its files in place`,satisfied:async()=>sp(await t.runOnSource(op(m,A))),apply:async()=>{await t.runOnSource(ip(m,A))}}),{operation:"site:move",target:A,steps:te}}async function uo(x,t,m){let A=await x.exec(t,m);if(A.code!==0)throw Error(`${t.name}: ${A.stderr.trim()||A.stdout.trim()||`exited ${A.code}`}`);return A.stdout}async function Co(x,t,m){let A=Bun.spawn(["scp","-P",String(x.sshPort),"-o","BatchMode=yes","-o","StrictHostKeyChecking=yes",t,m],{stdout:"pipe",stderr:"pipe"}),[N,z]=await Promise.all([A.exited,new Response(A.stderr).text()]);if(N!==0)throw Error(`scp failed: ${z.trim()||`exited ${N}`}`)}async function dp(x,t){if(!t.to)throw Error("Name the target server with --to <server>.");let m=await Oe(),A=m.sites?.[x];if(!A)throw Error(`Site '${x}' is not in cloud.config.ts.`);let N=m.project.slug,z=y(process.cwd(),m);try{let X=new Pe(z.store).list(z.project.id),te=(rt)=>{let Vt=X.find((qt)=>qt.id===rt||qt.name===rt);if(!Vt)throw Error(`Server ${rt} was not found. Run \`cloud server:list\`.`);if(Vt.trustState!=="pinned")throw Error(`${Vt.name} has no pinned host key. Run \`cloud server:validate ${Vt.name}\` first.`);return Vt},re=te(t.to),oe=X.filter((rt)=>rt.id!==re.id);if(!t.from&&oe.length!==1)throw Error(`Name the source server with --from <server> — ${oe.length} others are enrolled.`);let ae=te(t.from??oe[0].name),ve=new kr,Ce=J(N,x),Re=$o(N,x),we=A.domain,ke=m.infrastructure?.dns?.provider,Be=m.infrastructure?.compute?.proxy?.engine==="rpx"?m.infrastructure.compute.proxy:void 0,Ae=m.infrastructure?.dns?.domain,Ie=(rt)=>Ae&&rt.endsWith(Ae)?Ae:rt.split(".").slice(-2).join("."),Te=w(m),Xe=Te?.name&&di(Te)?{name:Te.name}:void 0,_e=`/tmp/ts-cloud-move-${N}-${x}.sql.gz`,Ze=Nl(N,x),et=Be?.certsDir??jr,At=[we,...A.aliases??[]].filter((rt)=>!!rt),Ft=Te?.engine??"mysql",St={runOnSource:(rt)=>uo(ve,ae,rt),runOnTarget:(rt)=>uo(ve,re,rt),archiveStaged:async()=>(await ve.exec(re,`test -f ${Re} && echo staged || true`)).stdout.includes("staged"),transferArchive:async()=>{let rt=`${process.cwd()}/.ts-cloud-move-${N}-${x}.tar.gz`;await Co(ae,`${ae.sshUser}@${ae.endpoint}:${Re}`,rt),await Co(re,rt,`${re.sshUser}@${re.endpoint}:${Re}`),await Bun.file(rt).delete().catch(()=>{})},targetRoutesSite:async()=>{if(!Be||!we)return!0;return(await ve.exec(re,`grep -qF ${JSON.stringify(we)} /etc/rpx/sites.d/${N}.json 2>/dev/null && echo routed || true`)).stdout.includes("routed")},refreshTargetGateway:async()=>{if(!Be)throw Error(`This project does not run the rpx gateway, so the move cannot publish a route. Point ${re.name}'s web server at ${Ce}/current yourself, then re-run to continue.`);await uo(ve,re,He({config:ye(m.sites??{},{proxy:Be,slug:N,lan:Li(m)}),slug:N,preserveManagementDashboardRoutes:!0}).join(`
83
83
  `))},publishedAddress:async()=>{if(!we||!ke)return re.endpoint;let rt=Wn(ke);if(!rt)return;return await xi(M(rt),Ie(we),we,re.endpoint,"A")?re.endpoint:void 0},...Xe?{database:{dump:async()=>{await uo(ve,ae,["set -euo pipefail",'eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true',...Ho(Ft,Xe.name,"/tmp",Te),`mv -f "$(ls -1t /tmp/${Xe.name}-*.sql.gz | head -1)" ${_e}`].join(`
84
84
  `))},dumpStaged:async()=>(await ve.exec(re,`test -s ${_e} && echo staged || true`)).stdout.includes("staged"),transferDump:async()=>{let rt=`${process.cwd()}/.ts-cloud-move-${N}-${x}.sql.gz`;await Co(ae,`${ae.sshUser}@${ae.endpoint}:${_e}`,rt),await Co(re,rt,`${re.sshUser}@${re.endpoint}:${_e}`),await Bun.file(rt).delete().catch(()=>{})},restore:async()=>{await uo(ve,re,[...bt(Te,m.infrastructure?.compute?.managedServices??{}),...ui(Te,{from:_e}),`rm -f ${_e}`].join(`
85
85
  `))},targetHasData:async()=>{let rt=Ft==="postgres"?`psql -tAc "select count(*) from information_schema.tables where table_schema='public'" -d ${Xe.name} 2>/dev/null || echo 0`:`mysql -N -B -e "select count(*) from information_schema.tables where table_schema='${Xe.name}'" 2>/dev/null || echo 0`,Vt=await ve.exec(re,`eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true
86
86
  ${rt}`);return Number.parseInt(Vt.stdout.trim().split(`
87
- `).pop()??"0",10)>0}}}:{},...Be?{certificates:{inPlace:async()=>{let rt=Ol(et,At),[Vt,qt]=await Promise.all([ve.exec(ae,rt),ve.exec(re,rt)]);return Ll(Ia(Vt.stdout),Ia(qt.stdout))},carry:async()=>{await uo(ve,ae,jl(et,At,Ze));let rt=`${process.cwd()}/.ts-cloud-move-${N}-${x}-certs.tar.gz`;if(!(await ve.exec(ae,`test -s ${Ze} && echo staged || true`)).stdout.includes("staged"))return;await Co(ae,`${ae.sshUser}@${ae.endpoint}:${Ze}`,rt),await Co(re,rt,`${re.sshUser}@${re.endpoint}:${Ze}`),await Bun.file(rt).delete().catch(()=>{}),await uo(ve,re,Fl(et,Ze)),await ve.exec(ae,`rm -f ${Ze}`)}}}:{},cutoverDns:async()=>{if(!we)return[];if(!ke)return[`No DNS provider configured — point ${we} at ${re.endpoint} manually.`];let rt=Wn(ke);if(!rt)return[`DNS provider '${ke}' is not configured.`];return(await Gn({provider:M(rt),zone:Ie(we),fqdn:we,ipv4:re.endpoint,ipv6:ue(void 0)})).warnings}},Et=await Bl({slug:N,siteName:x,appBase:Ce,from:ae.name,to:re.name,targetAddress:re.endpoint,port:A.port,healthCheckPath:A.healthCheck?.path,database:Xe},wt),sn=await ks(Et);if(!t.apply){if(t.json){console.log(JSON.stringify({schemaVersion:1,operation:Et.operation,target:Et.target,move:{site:x,from:ae.name,to:re.name,address:re.endpoint},steps:sn.map((rt)=>({id:rt.step.id,title:rt.step.title,state:rt.state,change:rt.step.change,reason:rt.reason}))},null,2));return}for(let rt of Is(Et,sn))console.log(rt);console.log(` The source keeps its files either way — reversible until ${ae.name} is destroyed.`),console.log(" Re-run with --apply to perform it.");return}let Mt=await xs(Et,sn,{log:(rt)=>e(rt),audit:(rt)=>z.store.appendEvent({projectId:z.project.id,type:`${rt.operation}.${rt.step}.${rt.state}`,level:rt.state==="failed"?"error":"info",payload:{site:x,from:ae.name,to:re.name,...rt.error?{error:rt.error}:{}}})});if(t.json)console.log(JSON.stringify({schemaVersion:1,outcome:Mt},null,2));if(!Mt.success){let rt=Mt.steps.find((Vt)=>Vt.state==="failed");throw Error(`${Et.operation} stopped at '${rt?.title}': ${rt?.error}. ${ae.name} still has the site and its files; re-run the same command to continue from here.`)}if(!t.json)n(`Moved ${x} from ${ae.name} to ${re.name}. ${ae.name} keeps its copy until you destroy it.`)}finally{z.store.close()}}function xa(x){x.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 dp(t,m)}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.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 A=m?.config||"cloud.config.ts",N=m?.root;if(!N){r("Missing --root <path>");return}if(!ap(A)){r(`Config file not found: ${A}`);return}try{let z=hn({configText:await cp(A,"utf8"),name:t,root:N,domain:m?.domain,path:m?.path,deploy:up(m?.deploy),build:m?.build,start:m?.start,port:mp(m?.port),type:m?.type,pathRewriteStyle:pp(m?.pathRewriteStyle)});if(m?.dryRun){console.log(z);return}await lp(A,z),n(`Added site '${t}' to ${A}`)}catch(z){r(z instanceof Error?z.message:String(z))}})}function up(x){if(!x)return;if(x==="bucket"||x==="server")return x;throw Error(`Invalid --deploy '${x}'. Expected 'server' or 'bucket'.`)}function pp(x){if(!x)return;if(x==="directory"||x==="flat")return x;throw Error(`Invalid --path-rewrite-style '${x}'. Expected 'directory' or 'flat'.`)}function mp(x){if(!x)return;let t=Number.parseInt(x,10);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`Invalid --port '${x}'. Expected a TCP port between 1 and 65535.`);return t}import{existsSync as vp}from"node:fs";import{mkdir as bp,writeFile as wp}from"node:fs/promises";import{join as Sp}from"node:path";class Ao{builder;config;environment;mergedConfig;serverEipLogicalIds=new Map;storageFailoverReplicas=[];constructor(x){this.config=x.config,this.environment=x.environment,this.builder=new Io(`${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(x){if(x.environments&&!x.environments.includes(this.environment))return!1;if(x.requiresFeatures){let t=this.config.features||{};if(!x.requiresFeatures.every((A)=>t[A]===!0))return!1}if(x.regions){let t=this.config.environments[this.environment]?.region||this.config.project.region;if(!x.regions.includes(t))return!1}if(x.condition&&typeof x.condition==="function")return x.condition(this.config,this.environment);return!0}resolveApiOriginPort(){let x=this.mergedConfig.infrastructure?.api?.port??this.mergedConfig.ports?.api,t=Number(x||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(x,t){return x==="public"||t.routeCompute===!0}storageBucketName(x,t,m){return this.mergedConfig.infrastructure?.storage?.[m]?.bucket??`${x}-${t}-${m}`}fileSystemLogicalId(x,t,m){return s(l({slug:`${x}-${m}`,environment:t,resourceType:"efs"}))}resolveSftpStorage(x,t,m){let A=x.storage;if(A?.type==="efs"&&A.fileSystem){if(!this.mergedConfig.infrastructure?.fileSystem?.[A.fileSystem])throw Error(`sftp: storage.fileSystem "${A.fileSystem}" is not defined in infrastructure.fileSystem`);return{type:"efs",fileSystemId:{Ref:this.fileSystemLogicalId(t,m,A.fileSystem)},posixProfile:A.posixProfile}}if(A?.type==="s3"&&A.storageBucket){if(!this.mergedConfig.infrastructure?.storage?.[A.storageBucket])throw Error(`sftp: storage.storageBucket "${A.storageBucket}" is not defined in infrastructure.storage`);return{type:"s3",bucket:this.storageBucketName(t,m,A.storageBucket)}}return}resolveComputeCachePathPatterns(x){if(x&&x.length>0)return x;return this.defaultComputeCachePathPatterns()}createComputeCacheBehavior(x,t){return{PathPattern:x,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(x,t,m,A,N,z){let U=this.serverEipLogicalIds.get("app"),X=this.serverEipLogicalIds.get("appInstance");if(!U)return;let te=`EC2-${A}-${N}-api`,re=this.mergedConfig.infrastructure?.servers?.app?.region||this.mergedConfig.project?.region||"us-east-1",oe=re==="us-east-1"?".compute-1.amazonaws.com":`.${re}.compute.amazonaws.com`,ae={"Fn::Join":["",["ec2-",{"Fn::Join":["-",{"Fn::Split":[".",{Ref:U}]}]},oe]]},ve=this.resolveApiOriginPort();if(x.push({Id:te,DomainName:ae,CustomOriginConfig:{HTTPPort:ve,HTTPSPort:443,OriginProtocolPolicy:"http-only",OriginSSLProtocols:["TLSv1.2"]}}),m.push(U),X)m.push(X);for(let Ce of z)t.push(this.createComputeCacheBehavior(Ce,te))}buildCaddyfile(x){let t=x.filter(([,N])=>typeof N.domain==="string"&&N.domain&&typeof N.port==="number");if(t.length===0)return;let m=new Map;for(let[,N]of t){let z=m.get(N.domain)??[];z.push({port:N.port,path:N.path}),m.set(N.domain,z)}let A=[];for(let[N,z]of m){let X=[...z].sort((te,re)=>{let oe=!te.path||te.path==="/",ae=!re.path||re.path==="/";if(oe&&!ae)return 1;if(!oe&&ae)return-1;return(re.path?.length??0)-(te.path?.length??0)}).map((te)=>{let re=!te.path||te.path==="/",oe=`reverse_proxy localhost:${te.port}`;return re?` handle {
87
+ `).pop()??"0",10)>0}}}:{},...Be?{certificates:{inPlace:async()=>{let rt=Ol(et,At),[Vt,qt]=await Promise.all([ve.exec(ae,rt),ve.exec(re,rt)]);return Ll(Ia(Vt.stdout),Ia(qt.stdout))},carry:async()=>{await uo(ve,ae,jl(et,At,Ze));let rt=`${process.cwd()}/.ts-cloud-move-${N}-${x}-certs.tar.gz`;if(!(await ve.exec(ae,`test -s ${Ze} && echo staged || true`)).stdout.includes("staged"))return;await Co(ae,`${ae.sshUser}@${ae.endpoint}:${Ze}`,rt),await Co(re,rt,`${re.sshUser}@${re.endpoint}:${Ze}`),await Bun.file(rt).delete().catch(()=>{}),await uo(ve,re,Fl(et,Ze)),await ve.exec(ae,`rm -f ${Ze}`)}}}:{},cutoverDns:async()=>{if(!we)return[];if(!ke)return[`No DNS provider configured — point ${we} at ${re.endpoint} manually.`];let rt=Wn(ke);if(!rt)return[`DNS provider '${ke}' is not configured.`];return(await Gn({provider:M(rt),zone:Ie(we),fqdn:we,ipv4:re.endpoint,ipv6:ue(void 0)})).warnings}},Et=await Bl({slug:N,siteName:x,appBase:Ce,from:ae.name,to:re.name,targetAddress:re.endpoint,port:A.port,healthCheckPath:A.healthCheck?.path,database:Xe},St),sn=await ks(Et);if(!t.apply){if(t.json){console.log(JSON.stringify({schemaVersion:1,operation:Et.operation,target:Et.target,move:{site:x,from:ae.name,to:re.name,address:re.endpoint},steps:sn.map((rt)=>({id:rt.step.id,title:rt.step.title,state:rt.state,change:rt.step.change,reason:rt.reason}))},null,2));return}for(let rt of Is(Et,sn))console.log(rt);console.log(` The source keeps its files either way — reversible until ${ae.name} is destroyed.`),console.log(" Re-run with --apply to perform it.");return}let Mt=await xs(Et,sn,{log:(rt)=>e(rt),audit:(rt)=>z.store.appendEvent({projectId:z.project.id,type:`${rt.operation}.${rt.step}.${rt.state}`,level:rt.state==="failed"?"error":"info",payload:{site:x,from:ae.name,to:re.name,...rt.error?{error:rt.error}:{}}})});if(t.json)console.log(JSON.stringify({schemaVersion:1,outcome:Mt},null,2));if(!Mt.success){let rt=Mt.steps.find((Vt)=>Vt.state==="failed");throw Error(`${Et.operation} stopped at '${rt?.title}': ${rt?.error}. ${ae.name} still has the site and its files; re-run the same command to continue from here.`)}if(!t.json)n(`Moved ${x} from ${ae.name} to ${re.name}. ${ae.name} keeps its copy until you destroy it.`)}finally{z.store.close()}}function xa(x){x.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 dp(t,m)}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.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 A=m?.config||"cloud.config.ts",N=m?.root;if(!N){r("Missing --root <path>");return}if(!ap(A)){r(`Config file not found: ${A}`);return}try{let z=hn({configText:await cp(A,"utf8"),name:t,root:N,domain:m?.domain,path:m?.path,deploy:up(m?.deploy),build:m?.build,start:m?.start,port:mp(m?.port),type:m?.type,pathRewriteStyle:pp(m?.pathRewriteStyle)});if(m?.dryRun){console.log(z);return}await lp(A,z),n(`Added site '${t}' to ${A}`)}catch(z){r(z instanceof Error?z.message:String(z))}})}function up(x){if(!x)return;if(x==="bucket"||x==="server")return x;throw Error(`Invalid --deploy '${x}'. Expected 'server' or 'bucket'.`)}function pp(x){if(!x)return;if(x==="directory"||x==="flat")return x;throw Error(`Invalid --path-rewrite-style '${x}'. Expected 'directory' or 'flat'.`)}function mp(x){if(!x)return;let t=Number.parseInt(x,10);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`Invalid --port '${x}'. Expected a TCP port between 1 and 65535.`);return t}import{existsSync as vp}from"node:fs";import{mkdir as bp,writeFile as wp}from"node:fs/promises";import{join as Sp}from"node:path";class Ao{builder;config;environment;mergedConfig;serverEipLogicalIds=new Map;storageFailoverReplicas=[];constructor(x){this.config=x.config,this.environment=x.environment,this.builder=new Io(`${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(x){if(x.environments&&!x.environments.includes(this.environment))return!1;if(x.requiresFeatures){let t=this.config.features||{};if(!x.requiresFeatures.every((A)=>t[A]===!0))return!1}if(x.regions){let t=this.config.environments[this.environment]?.region||this.config.project.region;if(!x.regions.includes(t))return!1}if(x.condition&&typeof x.condition==="function")return x.condition(this.config,this.environment);return!0}resolveApiOriginPort(){let x=this.mergedConfig.infrastructure?.api?.port??this.mergedConfig.ports?.api,t=Number(x||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(x,t){return x==="public"||t.routeCompute===!0}storageBucketName(x,t,m){return this.mergedConfig.infrastructure?.storage?.[m]?.bucket??`${x}-${t}-${m}`}fileSystemLogicalId(x,t,m){return s(l({slug:`${x}-${m}`,environment:t,resourceType:"efs"}))}resolveSftpStorage(x,t,m){let A=x.storage;if(A?.type==="efs"&&A.fileSystem){if(!this.mergedConfig.infrastructure?.fileSystem?.[A.fileSystem])throw Error(`sftp: storage.fileSystem "${A.fileSystem}" is not defined in infrastructure.fileSystem`);return{type:"efs",fileSystemId:{Ref:this.fileSystemLogicalId(t,m,A.fileSystem)},posixProfile:A.posixProfile}}if(A?.type==="s3"&&A.storageBucket){if(!this.mergedConfig.infrastructure?.storage?.[A.storageBucket])throw Error(`sftp: storage.storageBucket "${A.storageBucket}" is not defined in infrastructure.storage`);return{type:"s3",bucket:this.storageBucketName(t,m,A.storageBucket)}}return}resolveComputeCachePathPatterns(x){if(x&&x.length>0)return x;return this.defaultComputeCachePathPatterns()}createComputeCacheBehavior(x,t){return{PathPattern:x,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(x,t,m,A,N,z){let U=this.serverEipLogicalIds.get("app"),X=this.serverEipLogicalIds.get("appInstance");if(!U)return;let te=`EC2-${A}-${N}-api`,re=this.mergedConfig.infrastructure?.servers?.app?.region||this.mergedConfig.project?.region||"us-east-1",oe=re==="us-east-1"?".compute-1.amazonaws.com":`.${re}.compute.amazonaws.com`,ae={"Fn::Join":["",["ec2-",{"Fn::Join":["-",{"Fn::Split":[".",{Ref:U}]}]},oe]]},ve=this.resolveApiOriginPort();if(x.push({Id:te,DomainName:ae,CustomOriginConfig:{HTTPPort:ve,HTTPSPort:443,OriginProtocolPolicy:"http-only",OriginSSLProtocols:["TLSv1.2"]}}),m.push(U),X)m.push(X);for(let Ce of z)t.push(this.createComputeCacheBehavior(Ce,te))}buildCaddyfile(x){let t=x.filter(([,N])=>typeof N.domain==="string"&&N.domain&&typeof N.port==="number");if(t.length===0)return;let m=new Map;for(let[,N]of t){let z=m.get(N.domain)??[];z.push({port:N.port,path:N.path}),m.set(N.domain,z)}let A=[];for(let[N,z]of m){let X=[...z].sort((te,re)=>{let oe=!te.path||te.path==="/",ae=!re.path||re.path==="/";if(oe&&!ae)return 1;if(!oe&&ae)return-1;return(re.path?.length??0)-(te.path?.length??0)}).map((te)=>{let re=!te.path||te.path==="/",oe=`reverse_proxy localhost:${te.port}`;return re?` handle {
88
88
  ${oe}
89
89
  }`:` handle ${te.path} {
90
90
  ${oe}
@@ -93,11 +93,11 @@ ${X.join(`
93
93
  `)}
94
94
  }`)}return A.join(`
95
95
 
96
- `)}normalizeMountPath(x){let t=x?.mountPath||x?.path;if(!t||t==="/")return;let m=`/${t}`.replace(/\/+/g,"/").replace(/\/$/,"");return m==="/"?void 0:m}storageBucketLogicalId(x,t,m){return`${x}-${t}-s3-${m}`.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((A)=>A.charAt(0).toUpperCase()+A.slice(1)).join("")}pathMountRewriteFunctionCode(x,t="directory"){return`function handler(event) { var request = event.request; var prefix = ${JSON.stringify(x)}; 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 x=this.mergedConfig.project.slug,t=this.environment,m=!!(this.mergedConfig.infrastructure?.functions&&Object.keys(this.mergedConfig.infrastructure.functions).length>0||this.mergedConfig.infrastructure?.api),A=!!(this.mergedConfig.infrastructure?.servers&&Object.keys(this.mergedConfig.infrastructure.servers).length>0),N=!!this.mergedConfig.infrastructure?.compute;if(!!((this.mergedConfig.mode||"server")==="serverless"&&this.mergedConfig.infrastructure?.containers&&Object.keys(this.mergedConfig.infrastructure.containers).length>0))this.generateNetworkInfrastructure(x,t),this.generateContainerInfrastructure(x,t);if(m)this.generateServerless(x,t);if(A)this.generateServer(x,t);if(N)this.generateComputeApp(x,t);if(this.mergedConfig.infrastructure?.jumpBox)this.generateJumpBox(x,t);if(this.storageFailoverReplicas=[],this.generateSharedInfrastructure(x,t),this.storageFailoverReplicas.length>0)this.builder.addMetadata(Do,this.storageFailoverReplicas);if(this.config.tags)this.applyGlobalTags(this.config.tags);return this}getStorageFailoverReplicas(){return[...this.storageFailoverReplicas]}applyGlobalTags(x){}generateNetworkInfrastructure(x,t){if(this.builder.hasResource("VPC"))return;let A=this.mergedConfig.infrastructure?.network?.cidr||"10.0.0.0/16",{vpc:N,logicalId:z}=L.createVpc({slug:x,environment:t,cidr:A,enableDnsHostnames:!0,enableDnsSupport:!0});this.builder.addResource("VPC",N);let U=`${x}${t}IGW`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(U,{Type:"AWS::EC2::InternetGateway",Properties:{Tags:[{Key:"Name",Value:`${x}-${t}-igw`},{Key:"Environment",Value:t}]}});let X=`${x}${t}IGWAttach`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(X,{Type:"AWS::EC2::VPCGatewayAttachment",Properties:{VpcId:{Ref:"VPC"},InternetGatewayId:{Ref:U}}});let te=`${x}${t}PublicRT`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(te,{Type:"AWS::EC2::RouteTable",Properties:{VpcId:{Ref:"VPC"},Tags:[{Key:"Name",Value:`${x}-${t}-public-rt`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${x}${t}PublicRoute`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::EC2::Route",Properties:{RouteTableId:{Ref:te},DestinationCidrBlock:"0.0.0.0/0",GatewayId:{Ref:U}},DependsOn:X});let re=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",oe=["a","b"];for(let ae=0;ae<2;ae++){let ve=`PublicSubnet${ae+1}`;this.builder.addResource(ve,{Type:"AWS::EC2::Subnet",Properties:{VpcId:{Ref:"VPC"},CidrBlock:`10.0.${ae}.0/24`,AvailabilityZone:`${re}${oe[ae]}`,MapPublicIpOnLaunch:!0,Tags:[{Key:"Name",Value:`${x}-${t}-public-${oe[ae]}`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${ve}RTAssoc`,{Type:"AWS::EC2::SubnetRouteTableAssociation",Properties:{SubnetId:{Ref:ve},RouteTableId:{Ref:te}}})}}generateContainerInfrastructure(x,t){let m=this.mergedConfig.infrastructure?.containers;if(!m)return;let A=this.mergedConfig.infrastructure?.loadBalancer,N=this.mergedConfig.infrastructure?.ssl,z=this.mergedConfig.infrastructure?.dns,U;if(N?.enabled&&N.domains?.length)if(N.certificateArn);else{let oe=N.domains[0],ae=N.domains.slice(1).map((Re)=>{if(Re.includes(".")&&Re.endsWith(oe))return Re.replace(`.${oe}`,"");return Re}),{certificate:ve,logicalId:Ce}=j.createCertificate({domain:oe,subdomains:ae,slug:x,environment:t,validationMethod:"DNS",hostedZoneId:z?.hostedZoneId});U=Ce,this.builder.addResource(Ce,ve)}let X="ECSCluster";this.builder.addResource(X,{Type:"AWS::ECS::Cluster",Properties:{ClusterName:`${x}-${t}`,ClusterSettings:[{Name:"containerInsights",Value:"enabled"}],Tags:[{Key:"Name",Value:`${x}-${t}`},{Key:"Environment",Value:t}]}});let te=`${x}${t}ALBSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(te,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ALB security group for ${x}-${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:`${x}-${t}-alb-sg`},{Key:"Environment",Value:t}]}});let re=`${x}${t}ECSSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");for(let[oe,ae]of Object.entries(m)){let ve=ae.port||3000;this.builder.addResource(re,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ECS tasks security group for ${x}-${t}`,VpcId:{Ref:"VPC"},SecurityGroupIngress:[{IpProtocol:"tcp",FromPort:ve,ToPort:ve,SourceSecurityGroupId:{Ref:te},Description:"Allow traffic from ALB"}],SecurityGroupEgress:[{IpProtocol:"-1",CidrIp:"0.0.0.0/0",Description:"Allow all outbound"}],Tags:[{Key:"Name",Value:`${x}-${t}-ecs-sg`},{Key:"Environment",Value:t}]}});let Ce=`${x}${t}TaskExecRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ce,{Type:"AWS::IAM::Role",Properties:{RoleName:`${x}-${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 Re=`${x}${t}TaskRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Re,{Type:"AWS::IAM::Role",Properties:{RoleName:`${x}-${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 we=`${x}${t}${oe}LogGroup`.replace(/[^a-zA-Z0-9]/g,""),ke=`/ecs/${x}-${t}-${oe}`;this.builder.addResource(we,{Type:"AWS::Logs::LogGroup",Properties:{LogGroupName:ke,RetentionInDays:30}});let Be=`${x}${t}${oe}TaskDef`.replace(/[^a-zA-Z0-9]/g,""),Ae=String(ae.cpu||512),Ie=String(ae.memory||1024);this.builder.addResource(Be,{Type:"AWS::ECS::TaskDefinition",Properties:{Family:`${x}-${t}-${oe}`,NetworkMode:"awsvpc",RequiresCompatibilities:["FARGATE"],Cpu:Ae,Memory:Ie,ExecutionRoleArn:{"Fn::GetAtt":[Ce,"Arn"]},TaskRoleArn:{"Fn::GetAtt":[Re,"Arn"]},ContainerDefinitions:[{Name:oe,Image:{"Fn::Sub":`\${AWS::AccountId}.dkr.ecr.\${AWS::Region}.amazonaws.com/${x}:latest`},Essential:!0,PortMappings:[{ContainerPort:ve,Protocol:"tcp"}],LogConfiguration:{LogDriver:"awslogs",Options:{"awslogs-group":ke,"awslogs-region":{Ref:"AWS::Region"},"awslogs-stream-prefix":oe}},HealthCheck:{Command:["CMD-SHELL",`curl -f http://localhost:${ve}${ae.healthCheck||"/health"} || exit 1`],Interval:30,Timeout:5,Retries:3,StartPeriod:60}}],Tags:[{Key:"Name",Value:`${x}-${t}-${oe}`},{Key:"Environment",Value:t}]},DependsOn:[Ce,Re,we]});let Te=`${x}${t}ALB`.replace(/[^a-zA-Z0-9]/g,"");if(A?.enabled!==!1){this.builder.addResource(Te,{Type:"AWS::ElasticLoadBalancingV2::LoadBalancer",Properties:{Name:`${x}-${t}-alb`,Scheme:"internet-facing",Type:"application",Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],SecurityGroups:[{Ref:te}],Tags:[{Key:"Name",Value:`${x}-${t}-alb`},{Key:"Environment",Value:t}]}});let Xe=`${x}${t}TargetGroup`.replace(/[^a-zA-Z0-9]/g,""),_e=A?.healthCheck?.path||ae.healthCheck||"/health";this.builder.addResource(Xe,{Type:"AWS::ElasticLoadBalancingV2::TargetGroup",Properties:{Name:`${x}-${t}-tg`,Port:ve,Protocol:"HTTP",VpcId:{Ref:"VPC"},TargetType:"ip",HealthCheckPath:_e,HealthCheckIntervalSeconds:A?.healthCheck?.interval||30,HealthyThresholdCount:A?.healthCheck?.healthyThreshold||2,UnhealthyThresholdCount:A?.healthCheck?.unhealthyThreshold||5,HealthCheckTimeoutSeconds:10,Tags:[{Key:"Name",Value:`${x}-${t}-tg`},{Key:"Environment",Value:t}]}});let Ze=`${x}${t}HTTPListener`.replace(/[^a-zA-Z0-9]/g,"");if(N?.enabled&&N?.redirectHttp)this.builder.addResource(Ze,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:80,Protocol:"HTTP",DefaultActions:[{Type:"redirect",RedirectConfig:{Protocol:"HTTPS",Port:"443",StatusCode:"HTTP_301"}}]}});else this.builder.addResource(Ze,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:80,Protocol:"HTTP",DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Xe}}]}});if(N?.enabled){let wt=N.certificateArn||(U?{Ref:U}:void 0);if(wt){let Et=`${x}${t}HTTPSListener`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Et,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:443,Protocol:"HTTPS",Certificates:[{CertificateArn:wt}],DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Xe}}],SslPolicy:"ELBSecurityPolicy-TLS13-1-2-2021-06"}})}}let et=`${x}${t}${oe}Service`.replace(/[^a-zA-Z0-9]/g,""),At=ae.desiredCount||1;this.builder.addResource(et,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${x}-${t}-${oe}`,Cluster:{Ref:X},TaskDefinition:{Ref:Be},DesiredCount:At,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:re}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},LoadBalancers:[{ContainerName:oe,ContainerPort:ve,TargetGroupArn:{Ref:Xe}}],HealthCheckGracePeriodSeconds:120,Tags:[{Key:"Name",Value:`${x}-${t}-${oe}`},{Key:"Environment",Value:t}]},DependsOn:[Be,Ze,Xe]});let Ft=ae.autoScaling;if(Ft){let wt=`${x}${t}${oe}ScalableTarget`.replace(/[^a-zA-Z0-9]/g,"");if(this.builder.addResource(wt,{Type:"AWS::ApplicationAutoScaling::ScalableTarget",Properties:{MaxCapacity:Ft.max||10,MinCapacity:Ft.min||1,ResourceId:{"Fn::Sub":`service/\${${X}}/${x}-${t}-${oe}`},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:et}),Ft.targetCpuUtilization)this.builder.addResource(`${x}${t}${oe}CPUScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${x}-${t}-${oe}-cpu-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:wt},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageCPUUtilization"},TargetValue:Ft.targetCpuUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}});if(Ft.targetMemoryUtilization)this.builder.addResource(`${x}${t}${oe}MemoryScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${x}-${t}-${oe}-memory-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:wt},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageMemoryUtilization"},TargetValue:Ft.targetMemoryUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}})}if(z?.domain&&z?.hostedZoneId)this.builder.addResource(`${x}${t}ApiDnsRecord`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:z.hostedZoneId,Name:`api.${z.domain}`,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[Te,"DNSName"]},HostedZoneId:{"Fn::GetAtt":[Te,"CanonicalHostedZoneID"]}}}});this.builder.addOutput("ECSClusterArn",{Description:"ECS Cluster ARN",Value:{"Fn::GetAtt":[X,"Arn"]},Export:{Name:{"Fn::Sub":"${AWS::StackName}-ecs-cluster-arn"}}}),this.builder.addOutput("ECSServiceName",{Description:"ECS Service Name",Value:`${x}-${t}-${oe}`,Export:{Name:{"Fn::Sub":"${AWS::StackName}-ecs-service-name"}}}),this.builder.addOutput("LoadBalancerDNS",{Description:"Application Load Balancer DNS Name",Value:{"Fn::GetAtt":[Te,"DNSName"]},Export:{Name:{"Fn::Sub":"${AWS::StackName}-alb-dns"}}})}else{let Xe=`${x}${t}${oe}Service`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Xe,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${x}-${t}-${oe}`,Cluster:{Ref:X},TaskDefinition:{Ref:Be},DesiredCount:ae.desiredCount||1,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:re}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},Tags:[{Key:"Name",Value:`${x}-${t}-${oe}`},{Key:"Environment",Value:t}]},DependsOn:[Be]})}break}}generateServerless(x,t){if(this.mergedConfig.infrastructure?.functions)for(let[m,A]of Object.entries(this.mergedConfig.infrastructure.functions)){if(!this.shouldDeploy(A))continue;let{role:N,logicalId:z}=Ee.createRole({slug:x,environment:t,roleName:`${x}-${t}-${m}-role`,servicePrincipal:"lambda.amazonaws.com",managedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]});this.builder.addResource(z,N);let{lambdaFunction:U,logicalId:X}=S.createLambdaFunction({slug:x,environment:t,functionName:`${x}-${t}-${m}`,handler:A.handler||"index.handler",runtime:A.runtime||"nodejs20.x",code:{zipFile:A.code||"export const handler = async () => ({ statusCode: 200 });"},role:z,timeout:A.timeout,memorySize:A.memorySize});this.builder.addResource(X,U)}if(this.mergedConfig.infrastructure?.api){let{restApi:m,logicalId:A}=xo.createRestApi({slug:x,environment:t,apiName:`${x}-${t}-api`});this.builder.addResource(A,m)}}generateServer(x,t){if(!this.mergedConfig.infrastructure?.servers)return;let m=this.mergedConfig.infrastructure.servers,A=this.mergedConfig.infrastructure?.compute,N=this.mergedConfig.infrastructure?.ssl;if(Object.values(m).some((U)=>!U.privateNetwork||U.privateNetwork==="create"))this.generateNetworkInfrastructure(x,t);for(let[U,X]of Object.entries(m)){let te=X.instanceType||X.size||A?.size,oe=(te?S.InstanceSize.specs[te]:void 0)?.instanceType||te||"t3.micro",ae=X.type||"app",ve=X.userData||X.startupScript;if(!ve)ve=S.UserData.generateAppServerScript({runtime:"bun",runtimeVersion:X.bunVersion||"latest",webServer:"none",domain:X.domain,enableSsl:!!N?.enabled,sslEmail:N?.letsEncrypt?.email,installRedis:ae==="cache",installDatabaseClients:!!X.database});let Ce=X.privateNetwork&&X.privateNetwork!=="create"?X.privateNetwork:{Ref:"VPC"},Re=X.subnet||{Ref:"PublicSubnet1"},ke=!!this.mergedConfig.infrastructure?.email?.server?.enabled,Be=[22,80,443,this.resolveApiOriginPort()];if(ke)Be.push(25,465,587,143,993);let Ae=S.createServerModeStack({slug:`${x}-${U}`,environment:t,vpcId:Ce,subnetId:Re,instanceType:oe,keyName:X.keyName||`${x}-${t}`,domain:X.domain,userData:ve,volumeSize:X.diskSize||20,imageId:X.image,allowedPorts:Be});for(let[Ie,Te]of Object.entries(Ae.resources))this.builder.addResource(Ie,Te);this.serverEipLogicalIds.set(U,Ae.outputs.eipLogicalId),this.serverEipLogicalIds.set(`${U}Instance`,Ae.outputs.instanceLogicalId),this.builder.addOutput(`${U}InstanceId`,{Value:{Ref:Ae.outputs.instanceLogicalId},Description:`Instance ID for ${U} server`}),this.builder.addOutput(`${U}PublicIp`,{Value:{Ref:Ae.outputs.eipLogicalId},Description:`Public IP for ${U} server`})}}generateComputeApp(x,t){let m=this.mergedConfig.infrastructure?.compute;if(!m)return;let A=this.mergedConfig.sites||{},N=Object.entries(A),U=this.mergedConfig.infrastructure?.dns?.domain,X=this.mergedConfig.infrastructure?.database,te=m.size,oe=(te?S.InstanceSize.specs[te]:void 0)?.instanceType||te||"t3.micro";if(!this.builder.hasResource("VPC"))this.generateNetworkInfrastructure(x,t);let ae=this.buildCaddyfile(N),ve=S.UserData.generateBunAppScript({runtime:m.runtime||"bun",runtimeVersion:m.runtimeVersion||"latest",systemPackages:m.systemPackages,database:X,caddyfile:ae}),Ce=$i(m.appUpdates),Re=Ce.length>0?`${ve}
96
+ `)}normalizeMountPath(x){let t=x?.mountPath||x?.path;if(!t||t==="/")return;let m=`/${t}`.replace(/\/+/g,"/").replace(/\/$/,"");return m==="/"?void 0:m}storageBucketLogicalId(x,t,m){return`${x}-${t}-s3-${m}`.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((A)=>A.charAt(0).toUpperCase()+A.slice(1)).join("")}pathMountRewriteFunctionCode(x,t="directory"){return`function handler(event) { var request = event.request; var prefix = ${JSON.stringify(x)}; 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 x=this.mergedConfig.project.slug,t=this.environment,m=!!(this.mergedConfig.infrastructure?.functions&&Object.keys(this.mergedConfig.infrastructure.functions).length>0||this.mergedConfig.infrastructure?.api),A=!!(this.mergedConfig.infrastructure?.servers&&Object.keys(this.mergedConfig.infrastructure.servers).length>0),N=!!this.mergedConfig.infrastructure?.compute;if(!!((this.mergedConfig.mode||"server")==="serverless"&&this.mergedConfig.infrastructure?.containers&&Object.keys(this.mergedConfig.infrastructure.containers).length>0))this.generateNetworkInfrastructure(x,t),this.generateContainerInfrastructure(x,t);if(m)this.generateServerless(x,t);if(A)this.generateServer(x,t);if(N)this.generateComputeApp(x,t);if(this.mergedConfig.infrastructure?.jumpBox)this.generateJumpBox(x,t);if(this.storageFailoverReplicas=[],this.generateSharedInfrastructure(x,t),this.storageFailoverReplicas.length>0)this.builder.addMetadata(Do,this.storageFailoverReplicas);if(this.config.tags)this.applyGlobalTags(this.config.tags);return this}getStorageFailoverReplicas(){return[...this.storageFailoverReplicas]}applyGlobalTags(x){}generateNetworkInfrastructure(x,t){if(this.builder.hasResource("VPC"))return;let A=this.mergedConfig.infrastructure?.network?.cidr||"10.0.0.0/16",{vpc:N,logicalId:z}=L.createVpc({slug:x,environment:t,cidr:A,enableDnsHostnames:!0,enableDnsSupport:!0});this.builder.addResource("VPC",N);let U=`${x}${t}IGW`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(U,{Type:"AWS::EC2::InternetGateway",Properties:{Tags:[{Key:"Name",Value:`${x}-${t}-igw`},{Key:"Environment",Value:t}]}});let X=`${x}${t}IGWAttach`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(X,{Type:"AWS::EC2::VPCGatewayAttachment",Properties:{VpcId:{Ref:"VPC"},InternetGatewayId:{Ref:U}}});let te=`${x}${t}PublicRT`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(te,{Type:"AWS::EC2::RouteTable",Properties:{VpcId:{Ref:"VPC"},Tags:[{Key:"Name",Value:`${x}-${t}-public-rt`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${x}${t}PublicRoute`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::EC2::Route",Properties:{RouteTableId:{Ref:te},DestinationCidrBlock:"0.0.0.0/0",GatewayId:{Ref:U}},DependsOn:X});let re=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",oe=["a","b"];for(let ae=0;ae<2;ae++){let ve=`PublicSubnet${ae+1}`;this.builder.addResource(ve,{Type:"AWS::EC2::Subnet",Properties:{VpcId:{Ref:"VPC"},CidrBlock:`10.0.${ae}.0/24`,AvailabilityZone:`${re}${oe[ae]}`,MapPublicIpOnLaunch:!0,Tags:[{Key:"Name",Value:`${x}-${t}-public-${oe[ae]}`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${ve}RTAssoc`,{Type:"AWS::EC2::SubnetRouteTableAssociation",Properties:{SubnetId:{Ref:ve},RouteTableId:{Ref:te}}})}}generateContainerInfrastructure(x,t){let m=this.mergedConfig.infrastructure?.containers;if(!m)return;let A=this.mergedConfig.infrastructure?.loadBalancer,N=this.mergedConfig.infrastructure?.ssl,z=this.mergedConfig.infrastructure?.dns,U;if(N?.enabled&&N.domains?.length)if(N.certificateArn);else{let oe=N.domains[0],ae=N.domains.slice(1).map((Re)=>{if(Re.includes(".")&&Re.endsWith(oe))return Re.replace(`.${oe}`,"");return Re}),{certificate:ve,logicalId:Ce}=j.createCertificate({domain:oe,subdomains:ae,slug:x,environment:t,validationMethod:"DNS",hostedZoneId:z?.hostedZoneId});U=Ce,this.builder.addResource(Ce,ve)}let X="ECSCluster";this.builder.addResource(X,{Type:"AWS::ECS::Cluster",Properties:{ClusterName:`${x}-${t}`,ClusterSettings:[{Name:"containerInsights",Value:"enabled"}],Tags:[{Key:"Name",Value:`${x}-${t}`},{Key:"Environment",Value:t}]}});let te=`${x}${t}ALBSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(te,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ALB security group for ${x}-${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:`${x}-${t}-alb-sg`},{Key:"Environment",Value:t}]}});let re=`${x}${t}ECSSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");for(let[oe,ae]of Object.entries(m)){let ve=ae.port||3000;this.builder.addResource(re,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ECS tasks security group for ${x}-${t}`,VpcId:{Ref:"VPC"},SecurityGroupIngress:[{IpProtocol:"tcp",FromPort:ve,ToPort:ve,SourceSecurityGroupId:{Ref:te},Description:"Allow traffic from ALB"}],SecurityGroupEgress:[{IpProtocol:"-1",CidrIp:"0.0.0.0/0",Description:"Allow all outbound"}],Tags:[{Key:"Name",Value:`${x}-${t}-ecs-sg`},{Key:"Environment",Value:t}]}});let Ce=`${x}${t}TaskExecRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ce,{Type:"AWS::IAM::Role",Properties:{RoleName:`${x}-${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 Re=`${x}${t}TaskRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Re,{Type:"AWS::IAM::Role",Properties:{RoleName:`${x}-${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 we=`${x}${t}${oe}LogGroup`.replace(/[^a-zA-Z0-9]/g,""),ke=`/ecs/${x}-${t}-${oe}`;this.builder.addResource(we,{Type:"AWS::Logs::LogGroup",Properties:{LogGroupName:ke,RetentionInDays:30}});let Be=`${x}${t}${oe}TaskDef`.replace(/[^a-zA-Z0-9]/g,""),Ae=String(ae.cpu||512),Ie=String(ae.memory||1024);this.builder.addResource(Be,{Type:"AWS::ECS::TaskDefinition",Properties:{Family:`${x}-${t}-${oe}`,NetworkMode:"awsvpc",RequiresCompatibilities:["FARGATE"],Cpu:Ae,Memory:Ie,ExecutionRoleArn:{"Fn::GetAtt":[Ce,"Arn"]},TaskRoleArn:{"Fn::GetAtt":[Re,"Arn"]},ContainerDefinitions:[{Name:oe,Image:{"Fn::Sub":`\${AWS::AccountId}.dkr.ecr.\${AWS::Region}.amazonaws.com/${x}:latest`},Essential:!0,PortMappings:[{ContainerPort:ve,Protocol:"tcp"}],LogConfiguration:{LogDriver:"awslogs",Options:{"awslogs-group":ke,"awslogs-region":{Ref:"AWS::Region"},"awslogs-stream-prefix":oe}},HealthCheck:{Command:["CMD-SHELL",`curl -f http://localhost:${ve}${ae.healthCheck||"/health"} || exit 1`],Interval:30,Timeout:5,Retries:3,StartPeriod:60}}],Tags:[{Key:"Name",Value:`${x}-${t}-${oe}`},{Key:"Environment",Value:t}]},DependsOn:[Ce,Re,we]});let Te=`${x}${t}ALB`.replace(/[^a-zA-Z0-9]/g,"");if(A?.enabled!==!1){this.builder.addResource(Te,{Type:"AWS::ElasticLoadBalancingV2::LoadBalancer",Properties:{Name:`${x}-${t}-alb`,Scheme:"internet-facing",Type:"application",Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],SecurityGroups:[{Ref:te}],Tags:[{Key:"Name",Value:`${x}-${t}-alb`},{Key:"Environment",Value:t}]}});let Xe=`${x}${t}TargetGroup`.replace(/[^a-zA-Z0-9]/g,""),_e=A?.healthCheck?.path||ae.healthCheck||"/health";this.builder.addResource(Xe,{Type:"AWS::ElasticLoadBalancingV2::TargetGroup",Properties:{Name:`${x}-${t}-tg`,Port:ve,Protocol:"HTTP",VpcId:{Ref:"VPC"},TargetType:"ip",HealthCheckPath:_e,HealthCheckIntervalSeconds:A?.healthCheck?.interval||30,HealthyThresholdCount:A?.healthCheck?.healthyThreshold||2,UnhealthyThresholdCount:A?.healthCheck?.unhealthyThreshold||5,HealthCheckTimeoutSeconds:10,Tags:[{Key:"Name",Value:`${x}-${t}-tg`},{Key:"Environment",Value:t}]}});let Ze=`${x}${t}HTTPListener`.replace(/[^a-zA-Z0-9]/g,"");if(N?.enabled&&N?.redirectHttp)this.builder.addResource(Ze,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:80,Protocol:"HTTP",DefaultActions:[{Type:"redirect",RedirectConfig:{Protocol:"HTTPS",Port:"443",StatusCode:"HTTP_301"}}]}});else this.builder.addResource(Ze,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:80,Protocol:"HTTP",DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Xe}}]}});if(N?.enabled){let St=N.certificateArn||(U?{Ref:U}:void 0);if(St){let Et=`${x}${t}HTTPSListener`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Et,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:443,Protocol:"HTTPS",Certificates:[{CertificateArn:St}],DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Xe}}],SslPolicy:"ELBSecurityPolicy-TLS13-1-2-2021-06"}})}}let et=`${x}${t}${oe}Service`.replace(/[^a-zA-Z0-9]/g,""),At=ae.desiredCount||1;this.builder.addResource(et,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${x}-${t}-${oe}`,Cluster:{Ref:X},TaskDefinition:{Ref:Be},DesiredCount:At,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:re}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},LoadBalancers:[{ContainerName:oe,ContainerPort:ve,TargetGroupArn:{Ref:Xe}}],HealthCheckGracePeriodSeconds:120,Tags:[{Key:"Name",Value:`${x}-${t}-${oe}`},{Key:"Environment",Value:t}]},DependsOn:[Be,Ze,Xe]});let Ft=ae.autoScaling;if(Ft){let St=`${x}${t}${oe}ScalableTarget`.replace(/[^a-zA-Z0-9]/g,"");if(this.builder.addResource(St,{Type:"AWS::ApplicationAutoScaling::ScalableTarget",Properties:{MaxCapacity:Ft.max||10,MinCapacity:Ft.min||1,ResourceId:{"Fn::Sub":`service/\${${X}}/${x}-${t}-${oe}`},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:et}),Ft.targetCpuUtilization)this.builder.addResource(`${x}${t}${oe}CPUScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${x}-${t}-${oe}-cpu-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:St},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageCPUUtilization"},TargetValue:Ft.targetCpuUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}});if(Ft.targetMemoryUtilization)this.builder.addResource(`${x}${t}${oe}MemoryScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${x}-${t}-${oe}-memory-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:St},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageMemoryUtilization"},TargetValue:Ft.targetMemoryUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}})}if(z?.domain&&z?.hostedZoneId)this.builder.addResource(`${x}${t}ApiDnsRecord`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:z.hostedZoneId,Name:`api.${z.domain}`,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[Te,"DNSName"]},HostedZoneId:{"Fn::GetAtt":[Te,"CanonicalHostedZoneID"]}}}});this.builder.addOutput("ECSClusterArn",{Description:"ECS Cluster ARN",Value:{"Fn::GetAtt":[X,"Arn"]},Export:{Name:{"Fn::Sub":"${AWS::StackName}-ecs-cluster-arn"}}}),this.builder.addOutput("ECSServiceName",{Description:"ECS Service Name",Value:`${x}-${t}-${oe}`,Export:{Name:{"Fn::Sub":"${AWS::StackName}-ecs-service-name"}}}),this.builder.addOutput("LoadBalancerDNS",{Description:"Application Load Balancer DNS Name",Value:{"Fn::GetAtt":[Te,"DNSName"]},Export:{Name:{"Fn::Sub":"${AWS::StackName}-alb-dns"}}})}else{let Xe=`${x}${t}${oe}Service`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Xe,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${x}-${t}-${oe}`,Cluster:{Ref:X},TaskDefinition:{Ref:Be},DesiredCount:ae.desiredCount||1,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:re}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},Tags:[{Key:"Name",Value:`${x}-${t}-${oe}`},{Key:"Environment",Value:t}]},DependsOn:[Be]})}break}}generateServerless(x,t){if(this.mergedConfig.infrastructure?.functions)for(let[m,A]of Object.entries(this.mergedConfig.infrastructure.functions)){if(!this.shouldDeploy(A))continue;let{role:N,logicalId:z}=Ee.createRole({slug:x,environment:t,roleName:`${x}-${t}-${m}-role`,servicePrincipal:"lambda.amazonaws.com",managedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]});this.builder.addResource(z,N);let{lambdaFunction:U,logicalId:X}=S.createLambdaFunction({slug:x,environment:t,functionName:`${x}-${t}-${m}`,handler:A.handler||"index.handler",runtime:A.runtime||"nodejs20.x",code:{zipFile:A.code||"export const handler = async () => ({ statusCode: 200 });"},role:z,timeout:A.timeout,memorySize:A.memorySize});this.builder.addResource(X,U)}if(this.mergedConfig.infrastructure?.api){let{restApi:m,logicalId:A}=xo.createRestApi({slug:x,environment:t,apiName:`${x}-${t}-api`});this.builder.addResource(A,m)}}generateServer(x,t){if(!this.mergedConfig.infrastructure?.servers)return;let m=this.mergedConfig.infrastructure.servers,A=this.mergedConfig.infrastructure?.compute,N=this.mergedConfig.infrastructure?.ssl;if(Object.values(m).some((U)=>!U.privateNetwork||U.privateNetwork==="create"))this.generateNetworkInfrastructure(x,t);for(let[U,X]of Object.entries(m)){let te=X.instanceType||X.size||A?.size,oe=(te?S.InstanceSize.specs[te]:void 0)?.instanceType||te||"t3.micro",ae=X.type||"app",ve=X.userData||X.startupScript;if(!ve)ve=S.UserData.generateAppServerScript({runtime:"bun",runtimeVersion:X.bunVersion||"latest",webServer:"none",domain:X.domain,enableSsl:!!N?.enabled,sslEmail:N?.letsEncrypt?.email,installRedis:ae==="cache",installDatabaseClients:!!X.database});let Ce=X.privateNetwork&&X.privateNetwork!=="create"?X.privateNetwork:{Ref:"VPC"},Re=X.subnet||{Ref:"PublicSubnet1"},ke=!!this.mergedConfig.infrastructure?.email?.server?.enabled,Be=[22,80,443,this.resolveApiOriginPort()];if(ke)Be.push(25,465,587,143,993);let Ae=S.createServerModeStack({slug:`${x}-${U}`,environment:t,vpcId:Ce,subnetId:Re,instanceType:oe,keyName:X.keyName||`${x}-${t}`,domain:X.domain,userData:ve,volumeSize:X.diskSize||20,imageId:X.image,allowedPorts:Be});for(let[Ie,Te]of Object.entries(Ae.resources))this.builder.addResource(Ie,Te);this.serverEipLogicalIds.set(U,Ae.outputs.eipLogicalId),this.serverEipLogicalIds.set(`${U}Instance`,Ae.outputs.instanceLogicalId),this.builder.addOutput(`${U}InstanceId`,{Value:{Ref:Ae.outputs.instanceLogicalId},Description:`Instance ID for ${U} server`}),this.builder.addOutput(`${U}PublicIp`,{Value:{Ref:Ae.outputs.eipLogicalId},Description:`Public IP for ${U} server`})}}generateComputeApp(x,t){let m=this.mergedConfig.infrastructure?.compute;if(!m)return;let A=this.mergedConfig.sites||{},N=Object.entries(A),U=this.mergedConfig.infrastructure?.dns?.domain,X=this.mergedConfig.infrastructure?.database,te=m.size,oe=(te?S.InstanceSize.specs[te]:void 0)?.instanceType||te||"t3.micro";if(!this.builder.hasResource("VPC"))this.generateNetworkInfrastructure(x,t);let ae=this.buildCaddyfile(N),ve=S.UserData.generateBunAppScript({runtime:m.runtime||"bun",runtimeVersion:m.runtimeVersion||"latest",systemPackages:m.systemPackages,database:X,caddyfile:ae}),Ce=$i(m.appUpdates),Re=Ce.length>0?`${ve}
97
97
  ${Ce.join(`
98
98
  `)}
99
- `:ve,we=N.map(([,_e])=>_e.port).filter((_e)=>typeof _e==="number"&&![80,443].includes(_e)),ke=this.resolveApiOriginPort(),Be=`${x}${t}DeployBucket`.replace(/[^a-zA-Z0-9]/g,""),Ae=`${x}-${t}-deploy`;this.builder.addResource(Be,{Type:"AWS::S3::Bucket",Properties:{BucketName:Ae,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:x},{Key:"Environment",Value:t},{Key:"ManagedBy",Value:"ts-cloud"}]}});let Ie=S.createServerModeStack({slug:`${x}-app`,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},instanceType:oe,keyName:m.allowSsh?`${x}-${t}`:void 0,domain:U,userData:Re,volumeSize:m.disk?.size||20,imageId:m.image,allowedPorts:[...m.allowSsh?[22]:[],80,443,ke,...we]}),Te=Ie.resources[Ie.outputs.instanceLogicalId];if(Te?.Properties){let _e=Te.Properties.Tags||[];Te.Properties.Tags=[..._e,{Key:"Project",Value:x},{Key:"Environment",Value:t},{Key:"Role",Value:"app"},{Key:"ManagedBy",Value:"ts-cloud"}]}let Xe=Ie.resources[Ie.outputs.roleLogicalId];if(Xe?.Properties)Xe.Properties.Policies=Xe.Properties.Policies||[],Xe.Properties.Policies.push({PolicyName:"DeployBucketRead",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["s3:GetObject","s3:ListBucket"],Resource:[`arn:aws:s3:::${Ae}`,`arn:aws:s3:::${Ae}/*`]}]}});for(let[_e,Ze]of Object.entries(Ie.resources))this.builder.addResource(_e,Ze);this.builder.addOutput("deployBucketName",{Value:{Ref:Be},Description:"S3 bucket where release tarballs are uploaded"}),this.builder.addOutput("appInstanceId",{Value:{Ref:Ie.outputs.instanceLogicalId},Description:"EC2 instance ID for the app server"}),this.builder.addOutput("appPublicIp",{Value:{Ref:Ie.outputs.eipLogicalId},Description:"Public IP for the app server"}),this.serverEipLogicalIds.set("app",Ie.outputs.eipLogicalId),this.serverEipLogicalIds.set("appInstance",Ie.outputs.instanceLogicalId)}generateJumpBox(x,t){let m=this.mergedConfig.infrastructure?.jumpBox;if(!m)return;let A=m===!0?{}:m;if(A.enabled===!1)return;this.generateNetworkInfrastructure(x,t);let N=A.size||"micro",U=S.InstanceSize.specs[N]?.instanceType||N||"t3.micro",X;if(A.mountEfs)X={fileSystemId:typeof A.mountEfs==="string"?A.mountEfs:{Ref:"FileSystem"},mountPath:A.mountPath||"/mnt/efs"};let te;if(A.databaseTools)te=S.JumpBox.withDatabaseTools({slug:x,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:A.keyName||`${x}-${t}`,allowedCidrs:A.allowedCidrs});else if(X)te=S.JumpBox.withEfsMount({slug:x,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:A.keyName||`${x}-${t}`,fileSystemId:X.fileSystemId,mountPath:X.mountPath,allowedCidrs:A.allowedCidrs});else te=S.createJumpBox({slug:x,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:A.keyName||`${x}-${t}`,instanceType:U,allowedCidrs:A.allowedCidrs,mountEfs:X});for(let[re,oe]of Object.entries(te.resources))this.builder.addResource(re,oe);this.builder.addOutput("JumpBoxInstanceId",{Value:{Ref:te.instanceLogicalId},Description:"The ID of the JumpBox EC2 instance (use with SSM Session Manager or SSH)"})}generateSharedInfrastructure(x,t){let m=this.mergedConfig.infrastructure?.ssl,A=this.mergedConfig.infrastructure?.dns,N=A?.domain,z=A?.hostedZoneId,U=[],X,te;if(m?.certificateArn)te=m.certificateArn;else if(m?.enabled&&N&&z){let we=m.domains||[N],ke=we[0],Be=we.slice(1).map((Te)=>{if(Te.includes(".")&&Te.endsWith(ke))return Te.replace(`.${ke}`,"");return Te}),{certificate:Ae,logicalId:Ie}=j.createCertificate({domain:ke,subdomains:Be,slug:x,environment:t,validationMethod:"DNS",hostedZoneId:z});X=Ie,this.builder.addResource(Ie,Ae)}if(this.mergedConfig.infrastructure?.storage){let we=Object.entries(this.mergedConfig.infrastructure.storage).some(([,Ae])=>Ae.website),ke;if(we&&(X||te))ke=`${x}${t}CloudFrontOAC`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(ke,{Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`${x}-${t}-s3-oac`,Description:`OAC for ${x} ${t} S3 website buckets`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}});let Be=Object.entries(this.mergedConfig.infrastructure.storage).map(([Ae,Ie])=>({name:Ae,config:Ie,mountPath:this.normalizeMountPath(Ie),rewriteStyle:Ie.pathRewriteStyle||"directory",logicalId:this.storageBucketLogicalId(x,t,Ae)})).filter((Ae)=>Ae.name!=="public"&&Ae.config.website&&Ae.mountPath);for(let[Ae,Ie]of Object.entries(this.mergedConfig.infrastructure.storage)){let Te=!!(Ie.website&&ke),Xe=this.storageBucketName(x,t,Ae),{bucket:_e,logicalId:Ze}=Ue.createBucket({slug:x,name:Ae,environment:t,bucketName:Xe,versioning:Ie.versioning,encryption:Ie.encryption,public:Te?!1:Ie.public}),et=this.normalizeMountPath(Ie),At;if(Ie.failover){let wt=`infrastructure.storage.${Ae}`;if(!Te||!N||et&&Ae!=="public")throw Error(`${wt}.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(At=ia({failover:Ie.failover,primaryBucket:Xe,primaryRegion:this.mergedConfig.project.region||"us-east-1",where:wt}),At.replicate&&_e.Properties){let Et=`${Ze}FailoverReplicationRole`;this.builder.addResource(Et,sa({primaryBucket:Xe,replicaBucket:At.replicaBucket}));let sn=_e.Properties;sn.VersioningConfiguration={Status:"Enabled"},sn.ReplicationConfiguration=aa({replicaBucket:At.replicaBucket,roleArn:{"Fn::GetAtt":[Et,"Arn"]}})}}if(Te&&_e.Properties)_e.Properties.PublicAccessBlockConfiguration={BlockPublicAcls:!0,IgnorePublicAcls:!0,BlockPublicPolicy:!1,RestrictPublicBuckets:!1};if(this.builder.addResource(Ze,_e),Ie.website){let wt=typeof Ie.website==="object"?Ie.website:{},Et=Ue.enableWebsiteHosting(_e,wt.indexDocument||"index.html",wt.errorDocument);this.builder.addResource(Ze,Et)}let Ft=this.normalizeMountPath(Ie);if(Te&&ke&&N&&!(Ft&&Ae!=="public")){let wt=`${x}${t}${Ae}CDN`.replace(/[^a-zA-Z0-9]/g,""),Et=[];if(Ie.aliases&&Ie.aliases.length>0)Et.push(...Ie.aliases);else if(Ae==="public"){if(Et.push(N),m?.domains?.includes(`www.${N}`))Et.push(`www.${N}`)}else if(Ae==="docs")Et.push(`docs.${N}`);else if(Ae==="blog")Et.push(`blog.${N}`);let sn=typeof Ie.website==="object"?Ie.website:{},Mt=Ie.spa===!0,rt=sn.errorDocument||(Mt?"index.html":"404.html"),Vt=[];if(Mt)Vt.push({ErrorCode:403,ResponseCode:200,ResponsePagePath:"/index.html",ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:200,ResponsePagePath:"/index.html",ErrorCachingMinTTL:300});else Vt.push({ErrorCode:403,ResponseCode:404,ResponsePagePath:`/${rt}`,ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:404,ResponsePagePath:`/${rt}`,ErrorCachingMinTTL:300});let qt=te&&Et.length>0?{AcmCertificateArn:te,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:X&&Et.length>0?{AcmCertificateArn:{Ref:X},SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},St=`S3-${x}-${t}-${Ae}`,Bt=this.mergedConfig.project.region||"us-east-1",un;if(!Mt)un=`${x}${t}${Ae}UrlRewrite`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(un,{Type:"AWS::CloudFront::Function",Properties:{Name:`${x}-${t}-${Ae}-url-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`URL rewrite for ${x} ${t} ${Ae} - 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 Fn=[{Id:St,DomainName:{"Fn::Sub":`\${${Ze}}.s3.${Bt}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}],Yr=[],Ki=[];if(this.shouldRouteStorageBucketToCompute(Ae,Ie)){this.appendComputeAppOrigin(Fn,Yr,Ki,x,t,this.resolveComputeCachePathPatterns(Ie.computeRoutes));for(let nn of Be){let bs=`S3-${x}-${t}-${nn.name}`,ca=`${x}${t}${nn.name}PathMountRewrite`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(ca,{Type:"AWS::CloudFront::Function",Properties:{Name:`${x}-${t}-${nn.name}-path-mount-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`Path mount rewrite for ${x} ${t} ${nn.name} at ${nn.mountPath}`,Runtime:"cloudfront-js-2.0"},FunctionCode:this.pathMountRewriteFunctionCode(nn.mountPath,nn.rewriteStyle)}}),Fn.push({Id:bs,DomainName:{"Fn::Sub":`\${${nn.logicalId}}.s3.${Bt}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}),Ki.push(nn.logicalId,ca);for(let Kd of[nn.mountPath,`${nn.mountPath}/*`])Yr.push({PathPattern:Kd,TargetOriginId:bs,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":[ca,"FunctionARN"]}}]})}}let Jn,tn=St;if(At){let nn=`${St}-failover`;Object.assign(Fn[0],At.primaryConnection),Fn.push({Id:nn,DomainName:oa(At.replicaBucket,At.replicaRegion),OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}),Jn=Po({primaryOriginId:St,secondaryOriginId:nn,statusCodes:At.statusCodes}),tn=Jn.Items[0].Id}let gr={Type:"AWS::CloudFront::Distribution",DependsOn:[Ze,ke,...un?[un]:[],...Ki],Properties:{DistributionConfig:{Enabled:!0,Comment:`${x} ${t} ${Ae} site`,DefaultRootObject:"index.html",Origins:Fn,...Jn?{OriginGroups:Jn}:{},DefaultCacheBehavior:{TargetOriginId:tn,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD","OPTIONS"],CachedMethods:["GET","HEAD","OPTIONS"],Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6",...un?{FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":[un,"FunctionARN"]}}]}:{}},...Yr.length>0?{CacheBehaviors:Yr}:{},...Et.length>0?{Aliases:Et}:{},ViewerCertificate:qt,PriceClass:"PriceClass_100",HttpVersion:"http2and3",IPV6Enabled:!0,CustomErrorResponses:Vt}}};if(X)gr.DependsOn.push(X);if(At)bi(gr.Properties.DistributionConfig,`infrastructure.storage.${Ae}`),this.storageFailoverReplicas.push({name:Ae,primaryBucket:Xe,primaryRegion:Bt,replicaBucket:At.replicaBucket,replicaRegion:At.replicaRegion,replicate:At.replicate,distributionArnOutput:`${Ae}CloudFrontDistributionArn`}),this.builder.addOutput(`${Ae}CloudFrontDistributionArn`,{Value:{"Fn::Sub":`arn:\${AWS::Partition}:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`},Description:`CloudFront distribution ARN for ${Ae} (scopes the bucket policy of the failover replica)`}),this.builder.addOutput(`${Ae}FailoverBucketName`,{Value:At.replicaBucket,Description:`Failover replica bucket for ${Ae} (${At.replicaRegion}), outside this stack`});this.builder.addResource(wt,gr);let vr=`${Ze}CloudFrontPolicy`;if(this.builder.addResource(vr,{Type:"AWS::S3::BucketPolicy",DependsOn:[Ze,wt],Properties:{Bucket:{Ref:Ze},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${Ze}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`}}}}]}}}),this.shouldRouteStorageBucketToCompute(Ae,Ie))for(let nn of Be){let bs=`${nn.logicalId}CloudFrontPolicy`;this.builder.addResource(bs,{Type:"AWS::S3::BucketPolicy",DependsOn:[nn.logicalId,wt],Properties:{Bucket:{Ref:nn.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:::\${${nn.logicalId}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`}}}}]}}})}if(Et.length>0)U.push({name:Ae,bucketLogicalId:Ze,distLogicalId:wt,oacLogicalId:ke,aliases:Et});this.builder.addOutput(`${Ae}CloudFrontDomain`,{Value:{"Fn::GetAtt":[wt,"DomainName"]},Description:`CloudFront domain for ${Ae}`}),this.builder.addOutput(`${Ae}CloudFrontDistributionId`,{Value:{Ref:wt},Description:`CloudFront distribution ID for ${Ae}`})}if(this.builder.addOutput(`${Ae}BucketName`,{Value:{Ref:Ze},Description:`S3 bucket name for ${Ae}`}),Ae==="public")this.builder.addOutput("FrontendBucketName",{Value:{Ref:Ze},Description:"Frontend S3 bucket name"});if(Ae==="docs")this.builder.addOutput("DocsBucketName",{Value:{Ref:Ze},Description:"Documentation S3 bucket name"});if(Ae==="blog")this.builder.addOutput("BlogBucketName",{Value:{Ref:Ze},Description:"Blog S3 bucket name"})}}if(this.mergedConfig.infrastructure?.sftp){let we=this.mergedConfig.infrastructure.sftp,ke=_o.create({...we,storage:this.resolveSftpStorage(we,x,t)??we.storage,slug:x,environment:t});for(let[Be,Ae]of Object.entries(ke.resources))this.builder.addResource(Be,Ae);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(z&&U.length>0)for(let{name:we,distLogicalId:ke,aliases:Be}of U)for(let Ae of Be){let Ie=Ae.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Ie}ARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[ke],Properties:{HostedZoneId:z,Name:Ae,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[ke,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}}),this.builder.addResource(`${Ie}AAAARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[ke],Properties:{HostedZoneId:z,Name:Ae,Type:"AAAA",AliasTarget:{DNSName:{"Fn::GetAtt":[ke,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}})}if(this.mergedConfig.infrastructure?.databases){for(let[we,ke]of Object.entries(this.mergedConfig.infrastructure.databases))if(ke.engine==="dynamodb"){let{table:Be,logicalId:Ae}=zt.createTable({slug:x,environment:t,tableName:`${x}-${t}-${we}`,partitionKey:ke.partitionKey||{name:"id",type:"S"},sortKey:ke.sortKey});this.builder.addResource(Ae,Be)}else if(ke.engine==="postgres"){let{dbInstance:Be,logicalId:Ae}=zt.createPostgres({slug:x,environment:t,dbInstanceIdentifier:`${x}-${t}-${we}`,masterUsername:ke.username||"admin",masterUserPassword:ke.password||"changeme123",allocatedStorage:ke.storage||20,dbInstanceClass:ke.instanceClass||"db.t3.micro"});this.builder.addResource(Ae,Be)}else if(ke.engine==="mysql"){let{dbInstance:Be,logicalId:Ae}=zt.createMysql({slug:x,environment:t,dbInstanceIdentifier:`${x}-${t}-${we}`,masterUsername:ke.username||"admin",masterUserPassword:ke.password||"changeme123",allocatedStorage:ke.storage||20,dbInstanceClass:ke.instanceClass||"db.t3.micro"});this.builder.addResource(Ae,Be)}}if(this.mergedConfig.infrastructure?.cdn)for(let[we,ke]of Object.entries(this.mergedConfig.infrastructure.cdn)){if(!ke.origin)continue;let Be=typeof ke.customDomain==="string"?ke.customDomain:ke.customDomain?.domain||ke.domain,Ae=typeof ke.customDomain==="object"?ke.customDomain?.certificateArn:ke.certificateArn,Ie=Ae||te,Te=`${x}${t}${we}CDN`.replace(/[^a-zA-Z0-9]/g,""),Xe=`S3-${x}-${t}-${we}-cdn`,_e=[{Id:Xe,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},...jt(ke,`infrastructure.cdn.${we}`)}],Ze=[],et=[],At=["GET","HEAD","OPTIONS"],Ft=Xe,wt;if(ke.failoverOrigin){Si(At,`the default cache behavior of infrastructure.cdn.${we}`);let rt=`${Xe}-failover`,Vt=typeof ke.failoverOrigin==="string"?{domain:ke.failoverOrigin}:ke.failoverOrigin;if(!Vt.domain)throw Error(`infrastructure.cdn.${we}.failoverOrigin.domain is required`);if(Vt.domain===ke.origin)throw Error(`infrastructure.cdn.${we}: failoverOrigin must differ from origin (both are ${ke.origin})`);_e.push({Id:rt,DomainName:Vt.domain,OriginPath:Vt.originPath||"",...No(Vt.domain)?{S3OriginConfig:{OriginAccessIdentity:""}}:{CustomOriginConfig:{HTTPPort:80,HTTPSPort:443,OriginProtocolPolicy:"https-only"}},OriginShield:{Enabled:!1},...jt(Vt,`infrastructure.cdn.${we}.failoverOrigin`)}),wt=Po({primaryOriginId:Xe,secondaryOriginId:rt,statusCodes:ke.failoverStatusCodes}),Ft=wt.Items[0].Id}else if(ke.failoverStatusCodes)throw Error(`infrastructure.cdn.${we}: failoverStatusCodes needs failoverOrigin to fail over to`);if(ke.routeCompute)this.appendComputeAppOrigin(_e,Ze,et,x,t,this.resolveComputeCachePathPatterns(ke.computeRoutes));let Et=Ie&&Be?{AcmCertificateArn:X&&!Ae?{Ref:X}:Ie,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},sn=[...et];if(X&&!Ae&&Be)sn.push(X);let Mt={Type:"AWS::CloudFront::Distribution",...sn.length>0?{DependsOn:sn}:{},Properties:{DistributionConfig:{Enabled:!0,Comment:`${x} ${t} ${we} CDN`,DefaultRootObject:"index.html",Origins:_e,...wt?{OriginGroups:wt}:{},DefaultCacheBehavior:{TargetOriginId:Ft,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:At,CachedMethods:["GET","HEAD","OPTIONS"],Compress:ke.compress!==!1,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6"},...Ze.length>0?{CacheBehaviors:Ze}:{},...Be&&Ie?{Aliases:[Be]}:{},ViewerCertificate:Et,PriceClass:"PriceClass_100",HttpVersion:ke.http3?"http2and3":"http2"}}};if(bi(Mt.Properties.DistributionConfig,`infrastructure.cdn.${we}`),this.builder.addResource(Te,Mt),z&&Be&&Ie){let rt=Be.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${rt}CdnARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Te,...X&&!Ae?[X]:[]],Properties:{HostedZoneId:z,Name:Be,Type:"A",AliasTarget:{HostedZoneId:"Z2FDTNDATAQYW2",DNSName:{"Fn::GetAtt":[Te,"DomainName"]}}}})}}if(this.mergedConfig.infrastructure?.queues)for(let[we,ke]of Object.entries(this.mergedConfig.infrastructure.queues)){if(!this.shouldDeploy(ke))continue;let{queue:Be,logicalId:Ae}=ge.createQueue({slug:x,environment:t,name:`${x}-${t}-${we}`,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(Ae,Be);let Ie;if(ke.deadLetterQueue){let{deadLetterQueue:Te,updatedSourceQueue:Xe,deadLetterLogicalId:_e}=ge.createDeadLetterQueue(Ae,{slug:x,environment:t,maxReceiveCount:ke.maxReceiveCount});Ie=_e,this.builder.addResource(_e,Te);let et=this.builder.getResources()[Ae];if(et?.Properties)et.Properties.RedrivePolicy=Xe.Properties?.RedrivePolicy}if(ke.trigger){let Te=ke.trigger,Xe=`${x}${t}${Te.functionName}`.replace(/[^a-zA-Z0-9]/g,""),_e={Type:"AWS::Lambda::EventSourceMapping",Properties:{EventSourceArn:{"Fn::GetAtt":[Ae,"Arn"]},FunctionName:{Ref:Xe},BatchSize:Te.batchSize||10,MaximumBatchingWindowInSeconds:Te.batchWindow||0,Enabled:!0,...Te.reportBatchItemFailures!==!1&&{FunctionResponseTypes:["ReportBatchItemFailures"]},...Te.maxConcurrency&&{ScalingConfig:{MaximumConcurrency:Te.maxConcurrency}},...Te.filterPattern&&{FilterCriteria:{Filters:[{Pattern:JSON.stringify(Te.filterPattern)}]}}},DependsOn:[Ae,Xe]};this.builder.addResource(`${Ae}Trigger`,_e)}if(ke.alarms?.enabled){let Te=ke.alarms,Xe=Te.notificationTopicArn;if(!Xe&&Te.notificationEmails?.length){let et=`${Ae}AlarmTopic`;this.builder.addResource(et,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${x}-${t}-${we}-alarms`,DisplayName:`${we} Queue Alarms`}}),Te.notificationEmails.forEach((At,Ft)=>{this.builder.addResource(`${et}Sub${Ft}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:et},Protocol:"email",Endpoint:At}})}),Xe={Ref:et}}let _e=Te.queueDepthThreshold||1000;this.builder.addResource(`${Ae}DepthAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-${we}-queue-depth`,AlarmDescription:`Queue ${we} depth exceeds ${_e} messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Average",Period:300,EvaluationPeriods:2,Threshold:_e,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Ae,"QueueName"]}}],...Xe&&{AlarmActions:[Xe],OKActions:[Xe]}}});let Ze=Te.messageAgeThreshold||3600;if(this.builder.addResource(`${Ae}AgeAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-${we}-message-age`,AlarmDescription:`Queue ${we} oldest message exceeds ${Ze} seconds`,MetricName:"ApproximateAgeOfOldestMessage",Namespace:"AWS/SQS",Statistic:"Maximum",Period:300,EvaluationPeriods:2,Threshold:Ze,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Ae,"QueueName"]}}],...Xe&&{AlarmActions:[Xe],OKActions:[Xe]}}}),Ie&&Te.dlqAlarm!==!1)this.builder.addResource(`${Ie}Alarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-${we}-dlq-messages`,AlarmDescription:`Dead letter queue for ${we} has messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Sum",Period:300,EvaluationPeriods:1,Threshold:0,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Ie,"QueueName"]}}],...Xe&&{AlarmActions:[Xe]}}})}if(ke.subscribe){let Te=ke.subscribe,Xe=Te.topicArn;if(!Xe&&Te.topicName)Xe={Ref:Te.topicName};if(Xe){this.builder.addResource(`${Ae}SnsPolicy`,{Type:"AWS::SQS::QueuePolicy",Properties:{Queues:[{Ref:Ae}],PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"sns.amazonaws.com"},Action:"sqs:SendMessage",Resource:{"Fn::GetAtt":[Ae,"Arn"]},Condition:{ArnEquals:{"aws:SourceArn":Xe}}}]}}});let _e={TopicArn:Xe,Protocol:"sqs",Endpoint:{"Fn::GetAtt":[Ae,"Arn"]},RawMessageDelivery:Te.rawMessageDelivery||!1};if(Te.filterPolicy)_e.FilterPolicy=Te.filterPolicy,_e.FilterPolicyScope=Te.filterPolicyScope||"MessageAttributes";this.builder.addResource(`${Ae}SnsSub`,{Type:"AWS::SNS::Subscription",Properties:_e,DependsOn:`${Ae}SnsPolicy`})}}}if(this.mergedConfig.infrastructure?.realtime?.enabled)if((this.mergedConfig.infrastructure.realtime.mode||"serverless")==="server")this.generateRealtimeServerResources(x,t);else this.generateRealtimeResources(x,t);let re=this.mergedConfig.infrastructure?.redirects;if(re){let we=re.target||this.mergedConfig.infrastructure?.dns?.domain||"",ke=re.protocol||"https";if(re.domains?.length&&we)for(let Be of re.domains){let{bucket:Ae,bucketPolicy:Ie,logicalId:Te,policyLogicalId:Xe}=Ct.createDomainRedirectBucket({slug:x,environment:t,sourceDomain:Be,targetDomain:we,protocol:ke});this.builder.addResource(Te,Ae),this.builder.addResource(Xe,Ie)}if(re.paths&&Object.keys(re.paths).length>0){let Be=Ct.fromMapping(re.paths,{statusCode:re.statusCode||301}),{function:Ae,logicalId:Ie}=Ct.createPathRedirectFunction({slug:x,environment:t,rules:Be});this.builder.addResource(Ie,Ae)}}if(this.mergedConfig.infrastructure?.monitoring?.alarms)for(let[we,ke]of Object.entries(this.mergedConfig.infrastructure.monitoring.alarms)){let{alarm:Be,logicalId:Ae}=Ti.createAlarm({slug:x,environment:t,alarmName:`${x}-${t}-${we}`,metricName:ke.metricName||"Errors",namespace:ke.namespace||"AWS/Lambda",threshold:ke.threshold||1,comparisonOperator:ke.comparisonOperator||"GreaterThanThreshold"});this.builder.addResource(Ae,Be)}let oe=this.mergedConfig.infrastructure?.cache;if(oe){let we=oe.type||"redis";if(we==="redis"){this.generateNetworkInfrastructure(x,t);let ke=oe.redis||{},{replicationGroup:Be,subnetGroup:Ae,logicalId:Ie,subnetGroupId:Te}=vi.createRedis({slug:x,environment:t,nodeType:ke.nodeType||oe.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(Ie,Be),Ae&&Te)this.builder.addResource(Te,Ae);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Ie,"PrimaryEndPoint.Address"]},Description:"Redis primary endpoint address"}),this.builder.addOutput("CachePort",{Value:{"Fn::GetAtt":[Ie,"PrimaryEndPoint.Port"]},Description:"Redis primary endpoint port"})}else if(we==="memcached"){this.generateNetworkInfrastructure(x,t);let ke=oe.elasticache||{},{cluster:Be,subnetGroup:Ae,logicalId:Ie,subnetGroupId:Te}=vi.createMemcached({slug:x,environment:t,nodeType:ke.nodeType||oe.nodeType||"cache.t3.micro",engineVersion:ke.engineVersion||"1.6.22",numCacheNodes:ke.numCacheNodes||2,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Ie,Be),Ae&&Te)this.builder.addResource(Te,Ae);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Ie,"ConfigurationEndpoint.Address"]},Description:"Memcached configuration endpoint address"})}}let ae=this.mergedConfig.infrastructure?.email;if(ae){let we=ae.domain||this.mergedConfig.infrastructure?.dns?.domain;if(we){let{emailIdentity:ke,logicalId:Be}=D.verifyDomain({domain:we,slug:x,environment:t,enableDkim:ae.enableDkim!==!1,dkimKeyLength:ae.dkimKeyLength||"RSA_2048_BIT"});if(this.builder.addResource(Be,ke),ae.configurationSet!==!1){let{configurationSet:Te,logicalId:Xe}=D.createConfigurationSet({slug:x,environment:t});this.builder.addResource(Xe,Te)}let Ae=ae.hostedZoneId||this.mergedConfig.infrastructure?.dns?.hostedZoneId;if(Ae){if(ae.enableDkim!==!1)for(let et=1;et<=3;et++){let At=`DkimRecord${et}${we.replace(/\./g,"")}`;this.builder.addResource(At,{Type:"AWS::Route53::RecordSet",DependsOn:[Be],Properties:{HostedZoneId:Ae,Name:{"Fn::GetAtt":[Be,`DkimDNSTokenName${et}`]},Type:"CNAME",TTL:1800,ResourceRecords:[{"Fn::GetAtt":[Be,`DkimDNSTokenValue${et}`]}]}})}let{record:Te,logicalId:Xe}=D.createSpfRecord(we,Ae);this.builder.addResource(Xe,Te);let{record:_e,logicalId:Ze}=D.createDmarcRecord(we,Ae,{policy:"none",reportingEmail:ae.dmarcReportingEmail||`dmarc-reports@${we}`});this.builder.addResource(Ze,_e)}this.builder.addOutput("EmailDomain",{Value:we,Description:"SES verified email domain"});let Ie=ae.server;if(Ie?.enabled&&Ae){let Te=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",Xe=`${x}-${t}-email`,{role:_e,policy:Ze,roleLogicalId:et,policyLogicalId:At}=D.createEmailLambdaRole({slug:x,environment:t,s3BucketArn:`arn:aws:s3:::${Xe}`,sesIdentityArn:`arn:aws:ses:${Te}:*:identity/${we}`});this.builder.addResource(et,_e),this.builder.addResource(At,Ze);let{function:Ft,permission:wt,logicalId:Et,permissionLogicalId:sn}=D.createInboundEmailLambda({slug:x,environment:t,roleArn:{"Fn::GetAtt":[et,"Arn"]},s3BucketName:Xe,organizedPrefix:"mailboxes/"});this.builder.addResource(Et,Ft),this.builder.addResource(sn,wt);let Mt=D.createInboundEmailSetup({slug:x,environment:t,domain:we,s3BucketName:Xe,s3KeyPrefix:"inbox/",region:Te,hostedZoneId:Ae,lambdaFunctionArn:{"Fn::GetAtt":[Et,"Arn"]}});for(let[Vt,qt]of Object.entries(Mt.resources)){if(qt.Type==="AWS::SES::ReceiptRule")qt.DependsOn=[sn,Et];this.builder.addResource(Vt,qt)}this.builder.addOutput("InboundEmailLambda",{Value:{Ref:Et},Description:"Inbound email processing Lambda function"}),this.builder.addOutput("EmailBucket",{Value:Xe,Description:"S3 bucket for email storage"});let rt=this.serverEipLogicalIds.get("app");if(rt&&Ae){let qt=`${Ie?.subdomain||"mail"}.${we}`,St=qt.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${St}ARecord`,{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:Ae,Name:qt,Type:"A",TTL:"300",ResourceRecords:[{Ref:rt}]}})}this.builder.addOutput("MailHost",{Value:`mail.${we}`,Description:"Mail server hostname for SMTP/IMAP clients"})}}}let ve=this.mergedConfig.infrastructure?.search;if(ve){let we=ve.vpc?{subnetIds:[{Ref:"PublicSubnet1"}],securityGroupIds:[]}:void 0;if(we)this.generateNetworkInfrastructure(x,t);let{domain:ke,logicalId:Be}=Ht.createDomain({slug:x,environment:t,engineVersion:ve.engineVersion||"OpenSearch_2.11",instanceType:ve.instanceType||"t3.small.search",instanceCount:ve.instanceCount||1,volumeSize:ve.volumeSize||10,volumeType:ve.volumeType||"gp3",dedicatedMaster:ve.dedicatedMaster||!1,dedicatedMasterType:ve.dedicatedMasterType,dedicatedMasterCount:ve.dedicatedMasterCount||3,multiAz:ve.multiAz||!1,encryption:ve.encryption||{atRest:!0,nodeToNode:!0},advancedSecurity:ve.advancedSecurity,autoTune:ve.autoTune!==!1,vpc:we});this.builder.addResource(Be,ke),this.builder.addOutput("SearchDomainEndpoint",{Value:{"Fn::GetAtt":[Be,"DomainEndpoint"]},Description:"OpenSearch domain endpoint"}),this.builder.addOutput("SearchDomainArn",{Value:{"Fn::GetAtt":[Be,"Arn"]},Description:"OpenSearch domain ARN"})}let Ce=this.mergedConfig.infrastructure?.fileSystem;if(Ce&&Object.keys(Ce).length>0){this.generateNetworkInfrastructure(x,t);for(let[we,ke]of Object.entries(Ce)){let{fileSystem:Be,logicalId:Ae}=tt.createFileSystem({slug:`${x}-${we}`,environment:t,encrypted:ke.encrypted!==!1,performanceMode:ke.performanceMode||"generalPurpose",throughputMode:ke.throughputMode||"bursting",enableBackup:!0});this.builder.addResource(Ae,Be);let{securityGroup:Ie,logicalId:Te}=tt.createEfsSecurityGroup({slug:`${x}-${we}`,environment:t,vpcId:{Ref:"VPC"},sourceCidrBlocks:["10.0.0.0/16"]});this.builder.addResource(Te,Ie);let{mountTargets:Xe,logicalIds:_e}=tt.createMultiAzMountTargets(Ae,{slug:`${x}-${we}`,environment:t,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],securityGroupId:{Ref:Te}});for(let Ze=0;Ze<Xe.length;Ze++)this.builder.addResource(_e[Ze],Xe[Ze]);this.builder.addOutput(`${we}FileSystemId`,{Value:{Ref:Ae},Description:`EFS file system ID for ${we}`})}}let Re=this.mergedConfig.infrastructure?.ai;if(Re){let we=Re.service||"ecs",ke=Re.models||["*"],Be=Re.allowStreaming!==!1,Ae;if(we==="ecs")Ae=Fe.enableBedrockForEcs({slug:x,environment:t,models:ke,allowStreaming:Be});else if(we==="ec2")Ae=Fe.enableBedrockForEc2({slug:x,environment:t,models:ke,allowStreaming:Be});else if(we==="lambda")Ae=Fe.enableBedrockForLambda({slug:x,environment:t,models:ke,allowStreaming:Be});else Ae=Fe.createBedrockRole(we,{slug:x,environment:t,models:ke,allowStreaming:Be});if(this.builder.addResource(Ae.logicalId,Ae.role),Re.allowAsync){let{policy:Ie,logicalId:Te}=Fe.createBedrockPolicy({slug:x,environment:t,models:ke,allowStreaming:Be,allowAsync:!0});this.builder.addResource(Te,Ie)}this.builder.addOutput("BedrockRoleArn",{Value:{"Fn::GetAtt":[Ae.logicalId,"Arn"]},Description:"IAM role ARN with Bedrock permissions"})}}generateRealtimeResources(x,t){let m=this.mergedConfig.infrastructure?.realtime;if(!m)return;let A=m.name||`${x}-${t}-realtime`,N=m.scaling||{},z=m.storage||{type:"dynamodb"},U=N.handlerMemory||256,X=N.handlerTimeout||30,te=`${x}${t}RealtimeConnections`.replace(/[^a-zA-Z0-9]/g,""),re=`${x}${t}RealtimeChannels`.replace(/[^a-zA-Z0-9]/g,"");if(z.type==="dynamodb"){let _e=z.dynamodb||{},Ze=_e.billingMode||"PAY_PER_REQUEST";this.builder.addResource(te,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${x}-${t}-realtime-connections`,BillingMode:Ze,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"},...Ze==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:_e.readCapacity||5,WriteCapacityUnits:_e.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},...Ze==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:_e.readCapacity||5,WriteCapacityUnits:_e.writeCapacity||5}},..._e.pointInTimeRecovery&&{PointInTimeRecoverySpecification:{PointInTimeRecoveryEnabled:!0}},Tags:[{Key:"Name",Value:`${x}-${t}-realtime-connections`},{Key:"Environment",Value:t}]}}),this.builder.addResource(re,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${x}-${t}-realtime-channels`,BillingMode:Ze,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"},...Ze==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:_e.readCapacity||5,WriteCapacityUnits:_e.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},...Ze==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:_e.readCapacity||5,WriteCapacityUnits:_e.writeCapacity||5}},Tags:[{Key:"Name",Value:`${x}-${t}-realtime-channels`},{Key:"Environment",Value:t}]}})}let oe=`${x}${t}RealtimeRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(oe,{Type:"AWS::IAM::Role",Properties:{RoleName:`${x}-${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":[te,"Arn"]},{"Fn::Sub":`\${${te}.Arn}/index/*`},{"Fn::GetAtt":[re,"Arn"]},{"Fn::Sub":`\${${re}.Arn}/index/*`}]},{Effect:"Allow",Action:"execute-api:ManageConnections",Resource:{"Fn::Sub":"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*"}}]}}]}});let ae=`${x}${t}RealtimeConnect`.replace(/[^a-zA-Z0-9]/g,""),ve=`${x}${t}RealtimeDisconnect`.replace(/[^a-zA-Z0-9]/g,""),Ce=`${x}${t}RealtimeMessage`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(ae,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${x}-${t}-realtime-connect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[oe,"Arn"]},MemorySize:U,Timeout:X,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:te},CHANNELS_TABLE:{Ref:re},ENVIRONMENT:t}},Code:{ZipFile:this.generateConnectHandlerCode()}},DependsOn:[oe,te]}),this.builder.addResource(ve,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${x}-${t}-realtime-disconnect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[oe,"Arn"]},MemorySize:U,Timeout:X,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:te},CHANNELS_TABLE:{Ref:re},ENVIRONMENT:t}},Code:{ZipFile:this.generateDisconnectHandlerCode()}},DependsOn:[oe,te,re]}),this.builder.addResource(Ce,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${x}-${t}-realtime-message`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[oe,"Arn"]},MemorySize:U,Timeout:X,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:te},CHANNELS_TABLE:{Ref:re},ENVIRONMENT:t}},Code:{ZipFile:this.generateMessageHandlerCode()}},DependsOn:[oe,te,re]});let Re=`${x}${t}RealtimeApi`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Re,{Type:"AWS::ApiGatewayV2::Api",Properties:{Name:A,ProtocolType:"WEBSOCKET",RouteSelectionExpression:"$request.body.action",Tags:{Name:A,Environment:t}}});let we=`${ae}Permission`,ke=`${ve}Permission`,Be=`${Ce}Permission`;this.builder.addResource(we,{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}:\${${Re}}/*/$connect`}}}),this.builder.addResource(ke,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:ve},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Re}}/*/$disconnect`}}}),this.builder.addResource(Be,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:Ce},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Re}}/*/$default`}}});let Ae=`${Re}ConnectInteg`,Ie=`${Re}DisconnectInteg`,Te=`${Re}MessageInteg`;this.builder.addResource(Ae,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Re},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${ae}.Arn}/invocations`}}}),this.builder.addResource(Ie,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Re},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${ve}.Arn}/invocations`}}}),this.builder.addResource(Te,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Re},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${Ce}.Arn}/invocations`}}}),this.builder.addResource(`${Re}ConnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Re},RouteKey:"$connect",AuthorizationType:"NONE",Target:{"Fn::Sub":`integrations/\${${Ae}}`}}}),this.builder.addResource(`${Re}DisconnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Re},RouteKey:"$disconnect",Target:{"Fn::Sub":`integrations/\${${Ie}}`}}}),this.builder.addResource(`${Re}DefaultRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Re},RouteKey:"$default",Target:{"Fn::Sub":`integrations/\${${Te}}`}}});let Xe=`${Re}Stage`;if(this.builder.addResource(Xe,{Type:"AWS::ApiGatewayV2::Stage",Properties:{ApiId:{Ref:Re},StageName:t,AutoDeploy:!0,DefaultRouteSettings:{ThrottlingBurstLimit:N.messagesPerSecond||1000,ThrottlingRateLimit:N.messagesPerSecond||1000}}}),m.monitoring?.enabled){let _e=m.monitoring,Ze=_e.notificationTopicArn;if(!Ze&&_e.notificationEmails?.length){let et=`${Re}AlarmTopic`;this.builder.addResource(et,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${x}-${t}-realtime-alarms`,DisplayName:"Realtime WebSocket Alarms"}}),_e.notificationEmails.forEach((At,Ft)=>{this.builder.addResource(`${et}Sub${Ft}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:et},Protocol:"email",Endpoint:At}})}),Ze={Ref:et}}if(_e.connectionThreshold)this.builder.addResource(`${Re}ConnectionAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-realtime-connections`,AlarmDescription:`WebSocket connections exceed ${_e.connectionThreshold}`,MetricName:"ConnectCount",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:300,EvaluationPeriods:2,Threshold:_e.connectionThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Re}}],...Ze&&{AlarmActions:[Ze]}}});if(_e.errorThreshold)this.builder.addResource(`${Re}ErrorAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-realtime-errors`,AlarmDescription:`WebSocket errors exceed ${_e.errorThreshold}/min`,MetricName:"ExecutionError",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:60,EvaluationPeriods:3,Threshold:_e.errorThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Re}}],...Ze&&{AlarmActions:[Ze]}}})}this.builder.addOutput(`${Re}Endpoint`,{Description:"WebSocket API endpoint URL",Value:{"Fn::Sub":`wss://\${${Re}}.execute-api.\${AWS::Region}.amazonaws.com/${t}`},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-endpoint"}}}),this.builder.addOutput(`${Re}Id`,{Description:"WebSocket API ID",Value:{Ref:Re},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-api-id"}}})}generateConnectHandlerCode(){return`
99
+ `:ve,we=N.map(([,_e])=>_e.port).filter((_e)=>typeof _e==="number"&&![80,443].includes(_e)),ke=this.resolveApiOriginPort(),Be=`${x}${t}DeployBucket`.replace(/[^a-zA-Z0-9]/g,""),Ae=`${x}-${t}-deploy`;this.builder.addResource(Be,{Type:"AWS::S3::Bucket",Properties:{BucketName:Ae,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:x},{Key:"Environment",Value:t},{Key:"ManagedBy",Value:"ts-cloud"}]}});let Ie=S.createServerModeStack({slug:`${x}-app`,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},instanceType:oe,keyName:m.allowSsh?`${x}-${t}`:void 0,domain:U,userData:Re,volumeSize:m.disk?.size||20,imageId:m.image,allowedPorts:[...m.allowSsh?[22]:[],80,443,ke,...we]}),Te=Ie.resources[Ie.outputs.instanceLogicalId];if(Te?.Properties){let _e=Te.Properties.Tags||[];Te.Properties.Tags=[..._e,{Key:"Project",Value:x},{Key:"Environment",Value:t},{Key:"Role",Value:"app"},{Key:"ManagedBy",Value:"ts-cloud"}]}let Xe=Ie.resources[Ie.outputs.roleLogicalId];if(Xe?.Properties)Xe.Properties.Policies=Xe.Properties.Policies||[],Xe.Properties.Policies.push({PolicyName:"DeployBucketRead",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["s3:GetObject","s3:ListBucket"],Resource:[`arn:aws:s3:::${Ae}`,`arn:aws:s3:::${Ae}/*`]}]}});for(let[_e,Ze]of Object.entries(Ie.resources))this.builder.addResource(_e,Ze);this.builder.addOutput("deployBucketName",{Value:{Ref:Be},Description:"S3 bucket where release tarballs are uploaded"}),this.builder.addOutput("appInstanceId",{Value:{Ref:Ie.outputs.instanceLogicalId},Description:"EC2 instance ID for the app server"}),this.builder.addOutput("appPublicIp",{Value:{Ref:Ie.outputs.eipLogicalId},Description:"Public IP for the app server"}),this.serverEipLogicalIds.set("app",Ie.outputs.eipLogicalId),this.serverEipLogicalIds.set("appInstance",Ie.outputs.instanceLogicalId)}generateJumpBox(x,t){let m=this.mergedConfig.infrastructure?.jumpBox;if(!m)return;let A=m===!0?{}:m;if(A.enabled===!1)return;this.generateNetworkInfrastructure(x,t);let N=A.size||"micro",U=S.InstanceSize.specs[N]?.instanceType||N||"t3.micro",X;if(A.mountEfs)X={fileSystemId:typeof A.mountEfs==="string"?A.mountEfs:{Ref:"FileSystem"},mountPath:A.mountPath||"/mnt/efs"};let te;if(A.databaseTools)te=S.JumpBox.withDatabaseTools({slug:x,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:A.keyName||`${x}-${t}`,allowedCidrs:A.allowedCidrs});else if(X)te=S.JumpBox.withEfsMount({slug:x,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:A.keyName||`${x}-${t}`,fileSystemId:X.fileSystemId,mountPath:X.mountPath,allowedCidrs:A.allowedCidrs});else te=S.createJumpBox({slug:x,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:A.keyName||`${x}-${t}`,instanceType:U,allowedCidrs:A.allowedCidrs,mountEfs:X});for(let[re,oe]of Object.entries(te.resources))this.builder.addResource(re,oe);this.builder.addOutput("JumpBoxInstanceId",{Value:{Ref:te.instanceLogicalId},Description:"The ID of the JumpBox EC2 instance (use with SSM Session Manager or SSH)"})}generateSharedInfrastructure(x,t){let m=this.mergedConfig.infrastructure?.ssl,A=this.mergedConfig.infrastructure?.dns,N=A?.domain,z=A?.hostedZoneId,U=[],X,te;if(m?.certificateArn)te=m.certificateArn;else if(m?.enabled&&N&&z){let we=m.domains||[N],ke=we[0],Be=we.slice(1).map((Te)=>{if(Te.includes(".")&&Te.endsWith(ke))return Te.replace(`.${ke}`,"");return Te}),{certificate:Ae,logicalId:Ie}=j.createCertificate({domain:ke,subdomains:Be,slug:x,environment:t,validationMethod:"DNS",hostedZoneId:z});X=Ie,this.builder.addResource(Ie,Ae)}if(this.mergedConfig.infrastructure?.storage){let we=Object.entries(this.mergedConfig.infrastructure.storage).some(([,Ae])=>Ae.website),ke;if(we&&(X||te))ke=`${x}${t}CloudFrontOAC`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(ke,{Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`${x}-${t}-s3-oac`,Description:`OAC for ${x} ${t} S3 website buckets`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}});let Be=Object.entries(this.mergedConfig.infrastructure.storage).map(([Ae,Ie])=>({name:Ae,config:Ie,mountPath:this.normalizeMountPath(Ie),rewriteStyle:Ie.pathRewriteStyle||"directory",logicalId:this.storageBucketLogicalId(x,t,Ae)})).filter((Ae)=>Ae.name!=="public"&&Ae.config.website&&Ae.mountPath);for(let[Ae,Ie]of Object.entries(this.mergedConfig.infrastructure.storage)){let Te=!!(Ie.website&&ke),Xe=this.storageBucketName(x,t,Ae),{bucket:_e,logicalId:Ze}=Ue.createBucket({slug:x,name:Ae,environment:t,bucketName:Xe,versioning:Ie.versioning,encryption:Ie.encryption,public:Te?!1:Ie.public}),et=this.normalizeMountPath(Ie),At;if(Ie.failover){let St=`infrastructure.storage.${Ae}`;if(!Te||!N||et&&Ae!=="public")throw Error(`${St}.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(At=ia({failover:Ie.failover,primaryBucket:Xe,primaryRegion:this.mergedConfig.project.region||"us-east-1",where:St}),At.replicate&&_e.Properties){let Et=`${Ze}FailoverReplicationRole`;this.builder.addResource(Et,sa({primaryBucket:Xe,replicaBucket:At.replicaBucket}));let sn=_e.Properties;sn.VersioningConfiguration={Status:"Enabled"},sn.ReplicationConfiguration=aa({replicaBucket:At.replicaBucket,roleArn:{"Fn::GetAtt":[Et,"Arn"]}})}}if(Te&&_e.Properties)_e.Properties.PublicAccessBlockConfiguration={BlockPublicAcls:!0,IgnorePublicAcls:!0,BlockPublicPolicy:!1,RestrictPublicBuckets:!1};if(this.builder.addResource(Ze,_e),Ie.website){let St=typeof Ie.website==="object"?Ie.website:{},Et=Ue.enableWebsiteHosting(_e,St.indexDocument||"index.html",St.errorDocument);this.builder.addResource(Ze,Et)}let Ft=this.normalizeMountPath(Ie);if(Te&&ke&&N&&!(Ft&&Ae!=="public")){let St=`${x}${t}${Ae}CDN`.replace(/[^a-zA-Z0-9]/g,""),Et=[];if(Ie.aliases&&Ie.aliases.length>0)Et.push(...Ie.aliases);else if(Ae==="public"){if(Et.push(N),m?.domains?.includes(`www.${N}`))Et.push(`www.${N}`)}else if(Ae==="docs")Et.push(`docs.${N}`);else if(Ae==="blog")Et.push(`blog.${N}`);let sn=typeof Ie.website==="object"?Ie.website:{},Mt=Ie.spa===!0,rt=sn.errorDocument||(Mt?"index.html":"404.html"),Vt=[];if(Mt)Vt.push({ErrorCode:403,ResponseCode:200,ResponsePagePath:"/index.html",ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:200,ResponsePagePath:"/index.html",ErrorCachingMinTTL:300});else Vt.push({ErrorCode:403,ResponseCode:404,ResponsePagePath:`/${rt}`,ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:404,ResponsePagePath:`/${rt}`,ErrorCachingMinTTL:300});let qt=te&&Et.length>0?{AcmCertificateArn:te,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:X&&Et.length>0?{AcmCertificateArn:{Ref:X},SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},Ct=`S3-${x}-${t}-${Ae}`,Bt=this.mergedConfig.project.region||"us-east-1",un;if(!Mt)un=`${x}${t}${Ae}UrlRewrite`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(un,{Type:"AWS::CloudFront::Function",Properties:{Name:`${x}-${t}-${Ae}-url-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`URL rewrite for ${x} ${t} ${Ae} - 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 Fn=[{Id:Ct,DomainName:{"Fn::Sub":`\${${Ze}}.s3.${Bt}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}],Yr=[],Ki=[];if(this.shouldRouteStorageBucketToCompute(Ae,Ie)){this.appendComputeAppOrigin(Fn,Yr,Ki,x,t,this.resolveComputeCachePathPatterns(Ie.computeRoutes));for(let nn of Be){let bs=`S3-${x}-${t}-${nn.name}`,ca=`${x}${t}${nn.name}PathMountRewrite`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(ca,{Type:"AWS::CloudFront::Function",Properties:{Name:`${x}-${t}-${nn.name}-path-mount-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`Path mount rewrite for ${x} ${t} ${nn.name} at ${nn.mountPath}`,Runtime:"cloudfront-js-2.0"},FunctionCode:this.pathMountRewriteFunctionCode(nn.mountPath,nn.rewriteStyle)}}),Fn.push({Id:bs,DomainName:{"Fn::Sub":`\${${nn.logicalId}}.s3.${Bt}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}),Ki.push(nn.logicalId,ca);for(let Kd of[nn.mountPath,`${nn.mountPath}/*`])Yr.push({PathPattern:Kd,TargetOriginId:bs,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":[ca,"FunctionARN"]}}]})}}let Jn,tn=Ct;if(At){let nn=`${Ct}-failover`;Object.assign(Fn[0],At.primaryConnection),Fn.push({Id:nn,DomainName:oa(At.replicaBucket,At.replicaRegion),OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:ke}}),Jn=Po({primaryOriginId:Ct,secondaryOriginId:nn,statusCodes:At.statusCodes}),tn=Jn.Items[0].Id}let gr={Type:"AWS::CloudFront::Distribution",DependsOn:[Ze,ke,...un?[un]:[],...Ki],Properties:{DistributionConfig:{Enabled:!0,Comment:`${x} ${t} ${Ae} site`,DefaultRootObject:"index.html",Origins:Fn,...Jn?{OriginGroups:Jn}:{},DefaultCacheBehavior:{TargetOriginId:tn,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:["GET","HEAD","OPTIONS"],CachedMethods:["GET","HEAD","OPTIONS"],Compress:!0,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6",...un?{FunctionAssociations:[{EventType:"viewer-request",FunctionARN:{"Fn::GetAtt":[un,"FunctionARN"]}}]}:{}},...Yr.length>0?{CacheBehaviors:Yr}:{},...Et.length>0?{Aliases:Et}:{},ViewerCertificate:qt,PriceClass:"PriceClass_100",HttpVersion:"http2and3",IPV6Enabled:!0,CustomErrorResponses:Vt}}};if(X)gr.DependsOn.push(X);if(At)bi(gr.Properties.DistributionConfig,`infrastructure.storage.${Ae}`),this.storageFailoverReplicas.push({name:Ae,primaryBucket:Xe,primaryRegion:Bt,replicaBucket:At.replicaBucket,replicaRegion:At.replicaRegion,replicate:At.replicate,distributionArnOutput:`${Ae}CloudFrontDistributionArn`}),this.builder.addOutput(`${Ae}CloudFrontDistributionArn`,{Value:{"Fn::Sub":`arn:\${AWS::Partition}:cloudfront::\${AWS::AccountId}:distribution/\${${St}}`},Description:`CloudFront distribution ARN for ${Ae} (scopes the bucket policy of the failover replica)`}),this.builder.addOutput(`${Ae}FailoverBucketName`,{Value:At.replicaBucket,Description:`Failover replica bucket for ${Ae} (${At.replicaRegion}), outside this stack`});this.builder.addResource(St,gr);let vr=`${Ze}CloudFrontPolicy`;if(this.builder.addResource(vr,{Type:"AWS::S3::BucketPolicy",DependsOn:[Ze,St],Properties:{Bucket:{Ref:Ze},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${Ze}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${St}}`}}}}]}}}),this.shouldRouteStorageBucketToCompute(Ae,Ie))for(let nn of Be){let bs=`${nn.logicalId}CloudFrontPolicy`;this.builder.addResource(bs,{Type:"AWS::S3::BucketPolicy",DependsOn:[nn.logicalId,St],Properties:{Bucket:{Ref:nn.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:::\${${nn.logicalId}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${St}}`}}}}]}}})}if(Et.length>0)U.push({name:Ae,bucketLogicalId:Ze,distLogicalId:St,oacLogicalId:ke,aliases:Et});this.builder.addOutput(`${Ae}CloudFrontDomain`,{Value:{"Fn::GetAtt":[St,"DomainName"]},Description:`CloudFront domain for ${Ae}`}),this.builder.addOutput(`${Ae}CloudFrontDistributionId`,{Value:{Ref:St},Description:`CloudFront distribution ID for ${Ae}`})}if(this.builder.addOutput(`${Ae}BucketName`,{Value:{Ref:Ze},Description:`S3 bucket name for ${Ae}`}),Ae==="public")this.builder.addOutput("FrontendBucketName",{Value:{Ref:Ze},Description:"Frontend S3 bucket name"});if(Ae==="docs")this.builder.addOutput("DocsBucketName",{Value:{Ref:Ze},Description:"Documentation S3 bucket name"});if(Ae==="blog")this.builder.addOutput("BlogBucketName",{Value:{Ref:Ze},Description:"Blog S3 bucket name"})}}if(this.mergedConfig.infrastructure?.sftp){let we=this.mergedConfig.infrastructure.sftp,ke=_o.create({...we,storage:this.resolveSftpStorage(we,x,t)??we.storage,slug:x,environment:t});for(let[Be,Ae]of Object.entries(ke.resources))this.builder.addResource(Be,Ae);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(z&&U.length>0)for(let{name:we,distLogicalId:ke,aliases:Be}of U)for(let Ae of Be){let Ie=Ae.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Ie}ARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[ke],Properties:{HostedZoneId:z,Name:Ae,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[ke,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}}),this.builder.addResource(`${Ie}AAAARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[ke],Properties:{HostedZoneId:z,Name:Ae,Type:"AAAA",AliasTarget:{DNSName:{"Fn::GetAtt":[ke,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}})}if(this.mergedConfig.infrastructure?.databases){for(let[we,ke]of Object.entries(this.mergedConfig.infrastructure.databases))if(ke.engine==="dynamodb"){let{table:Be,logicalId:Ae}=zt.createTable({slug:x,environment:t,tableName:`${x}-${t}-${we}`,partitionKey:ke.partitionKey||{name:"id",type:"S"},sortKey:ke.sortKey});this.builder.addResource(Ae,Be)}else if(ke.engine==="postgres"){let{dbInstance:Be,logicalId:Ae}=zt.createPostgres({slug:x,environment:t,dbInstanceIdentifier:`${x}-${t}-${we}`,masterUsername:ke.username||"admin",masterUserPassword:ke.password||"changeme123",allocatedStorage:ke.storage||20,dbInstanceClass:ke.instanceClass||"db.t3.micro"});this.builder.addResource(Ae,Be)}else if(ke.engine==="mysql"){let{dbInstance:Be,logicalId:Ae}=zt.createMysql({slug:x,environment:t,dbInstanceIdentifier:`${x}-${t}-${we}`,masterUsername:ke.username||"admin",masterUserPassword:ke.password||"changeme123",allocatedStorage:ke.storage||20,dbInstanceClass:ke.instanceClass||"db.t3.micro"});this.builder.addResource(Ae,Be)}}if(this.mergedConfig.infrastructure?.cdn)for(let[we,ke]of Object.entries(this.mergedConfig.infrastructure.cdn)){if(!ke.origin)continue;let Be=typeof ke.customDomain==="string"?ke.customDomain:ke.customDomain?.domain||ke.domain,Ae=typeof ke.customDomain==="object"?ke.customDomain?.certificateArn:ke.certificateArn,Ie=Ae||te,Te=`${x}${t}${we}CDN`.replace(/[^a-zA-Z0-9]/g,""),Xe=`S3-${x}-${t}-${we}-cdn`,_e=[{Id:Xe,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},...jt(ke,`infrastructure.cdn.${we}`)}],Ze=[],et=[],At=["GET","HEAD","OPTIONS"],Ft=Xe,St;if(ke.failoverOrigin){Si(At,`the default cache behavior of infrastructure.cdn.${we}`);let rt=`${Xe}-failover`,Vt=typeof ke.failoverOrigin==="string"?{domain:ke.failoverOrigin}:ke.failoverOrigin;if(!Vt.domain)throw Error(`infrastructure.cdn.${we}.failoverOrigin.domain is required`);if(Vt.domain===ke.origin)throw Error(`infrastructure.cdn.${we}: failoverOrigin must differ from origin (both are ${ke.origin})`);_e.push({Id:rt,DomainName:Vt.domain,OriginPath:Vt.originPath||"",...No(Vt.domain)?{S3OriginConfig:{OriginAccessIdentity:""}}:{CustomOriginConfig:{HTTPPort:80,HTTPSPort:443,OriginProtocolPolicy:"https-only"}},OriginShield:{Enabled:!1},...jt(Vt,`infrastructure.cdn.${we}.failoverOrigin`)}),St=Po({primaryOriginId:Xe,secondaryOriginId:rt,statusCodes:ke.failoverStatusCodes}),Ft=St.Items[0].Id}else if(ke.failoverStatusCodes)throw Error(`infrastructure.cdn.${we}: failoverStatusCodes needs failoverOrigin to fail over to`);if(ke.routeCompute)this.appendComputeAppOrigin(_e,Ze,et,x,t,this.resolveComputeCachePathPatterns(ke.computeRoutes));let Et=Ie&&Be?{AcmCertificateArn:X&&!Ae?{Ref:X}:Ie,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},sn=[...et];if(X&&!Ae&&Be)sn.push(X);let Mt={Type:"AWS::CloudFront::Distribution",...sn.length>0?{DependsOn:sn}:{},Properties:{DistributionConfig:{Enabled:!0,Comment:`${x} ${t} ${we} CDN`,DefaultRootObject:"index.html",Origins:_e,...St?{OriginGroups:St}:{},DefaultCacheBehavior:{TargetOriginId:Ft,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:At,CachedMethods:["GET","HEAD","OPTIONS"],Compress:ke.compress!==!1,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6"},...Ze.length>0?{CacheBehaviors:Ze}:{},...Be&&Ie?{Aliases:[Be]}:{},ViewerCertificate:Et,PriceClass:"PriceClass_100",HttpVersion:ke.http3?"http2and3":"http2"}}};if(bi(Mt.Properties.DistributionConfig,`infrastructure.cdn.${we}`),this.builder.addResource(Te,Mt),z&&Be&&Ie){let rt=Be.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${rt}CdnARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Te,...X&&!Ae?[X]:[]],Properties:{HostedZoneId:z,Name:Be,Type:"A",AliasTarget:{HostedZoneId:"Z2FDTNDATAQYW2",DNSName:{"Fn::GetAtt":[Te,"DomainName"]}}}})}}if(this.mergedConfig.infrastructure?.queues)for(let[we,ke]of Object.entries(this.mergedConfig.infrastructure.queues)){if(!this.shouldDeploy(ke))continue;let{queue:Be,logicalId:Ae}=ge.createQueue({slug:x,environment:t,name:`${x}-${t}-${we}`,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(Ae,Be);let Ie;if(ke.deadLetterQueue){let{deadLetterQueue:Te,updatedSourceQueue:Xe,deadLetterLogicalId:_e}=ge.createDeadLetterQueue(Ae,{slug:x,environment:t,maxReceiveCount:ke.maxReceiveCount});Ie=_e,this.builder.addResource(_e,Te);let et=this.builder.getResources()[Ae];if(et?.Properties)et.Properties.RedrivePolicy=Xe.Properties?.RedrivePolicy}if(ke.trigger){let Te=ke.trigger,Xe=`${x}${t}${Te.functionName}`.replace(/[^a-zA-Z0-9]/g,""),_e={Type:"AWS::Lambda::EventSourceMapping",Properties:{EventSourceArn:{"Fn::GetAtt":[Ae,"Arn"]},FunctionName:{Ref:Xe},BatchSize:Te.batchSize||10,MaximumBatchingWindowInSeconds:Te.batchWindow||0,Enabled:!0,...Te.reportBatchItemFailures!==!1&&{FunctionResponseTypes:["ReportBatchItemFailures"]},...Te.maxConcurrency&&{ScalingConfig:{MaximumConcurrency:Te.maxConcurrency}},...Te.filterPattern&&{FilterCriteria:{Filters:[{Pattern:JSON.stringify(Te.filterPattern)}]}}},DependsOn:[Ae,Xe]};this.builder.addResource(`${Ae}Trigger`,_e)}if(ke.alarms?.enabled){let Te=ke.alarms,Xe=Te.notificationTopicArn;if(!Xe&&Te.notificationEmails?.length){let et=`${Ae}AlarmTopic`;this.builder.addResource(et,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${x}-${t}-${we}-alarms`,DisplayName:`${we} Queue Alarms`}}),Te.notificationEmails.forEach((At,Ft)=>{this.builder.addResource(`${et}Sub${Ft}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:et},Protocol:"email",Endpoint:At}})}),Xe={Ref:et}}let _e=Te.queueDepthThreshold||1000;this.builder.addResource(`${Ae}DepthAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-${we}-queue-depth`,AlarmDescription:`Queue ${we} depth exceeds ${_e} messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Average",Period:300,EvaluationPeriods:2,Threshold:_e,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Ae,"QueueName"]}}],...Xe&&{AlarmActions:[Xe],OKActions:[Xe]}}});let Ze=Te.messageAgeThreshold||3600;if(this.builder.addResource(`${Ae}AgeAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-${we}-message-age`,AlarmDescription:`Queue ${we} oldest message exceeds ${Ze} seconds`,MetricName:"ApproximateAgeOfOldestMessage",Namespace:"AWS/SQS",Statistic:"Maximum",Period:300,EvaluationPeriods:2,Threshold:Ze,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Ae,"QueueName"]}}],...Xe&&{AlarmActions:[Xe],OKActions:[Xe]}}}),Ie&&Te.dlqAlarm!==!1)this.builder.addResource(`${Ie}Alarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-${we}-dlq-messages`,AlarmDescription:`Dead letter queue for ${we} has messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Sum",Period:300,EvaluationPeriods:1,Threshold:0,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Ie,"QueueName"]}}],...Xe&&{AlarmActions:[Xe]}}})}if(ke.subscribe){let Te=ke.subscribe,Xe=Te.topicArn;if(!Xe&&Te.topicName)Xe={Ref:Te.topicName};if(Xe){this.builder.addResource(`${Ae}SnsPolicy`,{Type:"AWS::SQS::QueuePolicy",Properties:{Queues:[{Ref:Ae}],PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"sns.amazonaws.com"},Action:"sqs:SendMessage",Resource:{"Fn::GetAtt":[Ae,"Arn"]},Condition:{ArnEquals:{"aws:SourceArn":Xe}}}]}}});let _e={TopicArn:Xe,Protocol:"sqs",Endpoint:{"Fn::GetAtt":[Ae,"Arn"]},RawMessageDelivery:Te.rawMessageDelivery||!1};if(Te.filterPolicy)_e.FilterPolicy=Te.filterPolicy,_e.FilterPolicyScope=Te.filterPolicyScope||"MessageAttributes";this.builder.addResource(`${Ae}SnsSub`,{Type:"AWS::SNS::Subscription",Properties:_e,DependsOn:`${Ae}SnsPolicy`})}}}if(this.mergedConfig.infrastructure?.realtime?.enabled)if((this.mergedConfig.infrastructure.realtime.mode||"serverless")==="server")this.generateRealtimeServerResources(x,t);else this.generateRealtimeResources(x,t);let re=this.mergedConfig.infrastructure?.redirects;if(re){let we=re.target||this.mergedConfig.infrastructure?.dns?.domain||"",ke=re.protocol||"https";if(re.domains?.length&&we)for(let Be of re.domains){let{bucket:Ae,bucketPolicy:Ie,logicalId:Te,policyLogicalId:Xe}=wt.createDomainRedirectBucket({slug:x,environment:t,sourceDomain:Be,targetDomain:we,protocol:ke});this.builder.addResource(Te,Ae),this.builder.addResource(Xe,Ie)}if(re.paths&&Object.keys(re.paths).length>0){let Be=wt.fromMapping(re.paths,{statusCode:re.statusCode||301}),{function:Ae,logicalId:Ie}=wt.createPathRedirectFunction({slug:x,environment:t,rules:Be});this.builder.addResource(Ie,Ae)}}if(this.mergedConfig.infrastructure?.monitoring?.alarms)for(let[we,ke]of Object.entries(this.mergedConfig.infrastructure.monitoring.alarms)){let{alarm:Be,logicalId:Ae}=Ti.createAlarm({slug:x,environment:t,alarmName:`${x}-${t}-${we}`,metricName:ke.metricName||"Errors",namespace:ke.namespace||"AWS/Lambda",threshold:ke.threshold||1,comparisonOperator:ke.comparisonOperator||"GreaterThanThreshold"});this.builder.addResource(Ae,Be)}let oe=this.mergedConfig.infrastructure?.cache;if(oe){let we=oe.type||"redis";if(we==="redis"){this.generateNetworkInfrastructure(x,t);let ke=oe.redis||{},{replicationGroup:Be,subnetGroup:Ae,logicalId:Ie,subnetGroupId:Te}=vi.createRedis({slug:x,environment:t,nodeType:ke.nodeType||oe.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(Ie,Be),Ae&&Te)this.builder.addResource(Te,Ae);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Ie,"PrimaryEndPoint.Address"]},Description:"Redis primary endpoint address"}),this.builder.addOutput("CachePort",{Value:{"Fn::GetAtt":[Ie,"PrimaryEndPoint.Port"]},Description:"Redis primary endpoint port"})}else if(we==="memcached"){this.generateNetworkInfrastructure(x,t);let ke=oe.elasticache||{},{cluster:Be,subnetGroup:Ae,logicalId:Ie,subnetGroupId:Te}=vi.createMemcached({slug:x,environment:t,nodeType:ke.nodeType||oe.nodeType||"cache.t3.micro",engineVersion:ke.engineVersion||"1.6.22",numCacheNodes:ke.numCacheNodes||2,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Ie,Be),Ae&&Te)this.builder.addResource(Te,Ae);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Ie,"ConfigurationEndpoint.Address"]},Description:"Memcached configuration endpoint address"})}}let ae=this.mergedConfig.infrastructure?.email;if(ae){let we=ae.domain||this.mergedConfig.infrastructure?.dns?.domain;if(we){let{emailIdentity:ke,logicalId:Be}=D.verifyDomain({domain:we,slug:x,environment:t,enableDkim:ae.enableDkim!==!1,dkimKeyLength:ae.dkimKeyLength||"RSA_2048_BIT"});if(this.builder.addResource(Be,ke),ae.configurationSet!==!1){let{configurationSet:Te,logicalId:Xe}=D.createConfigurationSet({slug:x,environment:t});this.builder.addResource(Xe,Te)}let Ae=ae.hostedZoneId||this.mergedConfig.infrastructure?.dns?.hostedZoneId;if(Ae){if(ae.enableDkim!==!1)for(let et=1;et<=3;et++){let At=`DkimRecord${et}${we.replace(/\./g,"")}`;this.builder.addResource(At,{Type:"AWS::Route53::RecordSet",DependsOn:[Be],Properties:{HostedZoneId:Ae,Name:{"Fn::GetAtt":[Be,`DkimDNSTokenName${et}`]},Type:"CNAME",TTL:1800,ResourceRecords:[{"Fn::GetAtt":[Be,`DkimDNSTokenValue${et}`]}]}})}let{record:Te,logicalId:Xe}=D.createSpfRecord(we,Ae);this.builder.addResource(Xe,Te);let{record:_e,logicalId:Ze}=D.createDmarcRecord(we,Ae,{policy:"none",reportingEmail:ae.dmarcReportingEmail||`dmarc-reports@${we}`});this.builder.addResource(Ze,_e)}this.builder.addOutput("EmailDomain",{Value:we,Description:"SES verified email domain"});let Ie=ae.server;if(Ie?.enabled&&Ae){let Te=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",Xe=`${x}-${t}-email`,{role:_e,policy:Ze,roleLogicalId:et,policyLogicalId:At}=D.createEmailLambdaRole({slug:x,environment:t,s3BucketArn:`arn:aws:s3:::${Xe}`,sesIdentityArn:`arn:aws:ses:${Te}:*:identity/${we}`});this.builder.addResource(et,_e),this.builder.addResource(At,Ze);let{function:Ft,permission:St,logicalId:Et,permissionLogicalId:sn}=D.createInboundEmailLambda({slug:x,environment:t,roleArn:{"Fn::GetAtt":[et,"Arn"]},s3BucketName:Xe,organizedPrefix:"mailboxes/"});this.builder.addResource(Et,Ft),this.builder.addResource(sn,St);let Mt=D.createInboundEmailSetup({slug:x,environment:t,domain:we,s3BucketName:Xe,s3KeyPrefix:"inbox/",region:Te,hostedZoneId:Ae,lambdaFunctionArn:{"Fn::GetAtt":[Et,"Arn"]}});for(let[Vt,qt]of Object.entries(Mt.resources)){if(qt.Type==="AWS::SES::ReceiptRule")qt.DependsOn=[sn,Et];this.builder.addResource(Vt,qt)}this.builder.addOutput("InboundEmailLambda",{Value:{Ref:Et},Description:"Inbound email processing Lambda function"}),this.builder.addOutput("EmailBucket",{Value:Xe,Description:"S3 bucket for email storage"});let rt=this.serverEipLogicalIds.get("app");if(rt&&Ae){let qt=`${Ie?.subdomain||"mail"}.${we}`,Ct=qt.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Ct}ARecord`,{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:Ae,Name:qt,Type:"A",TTL:"300",ResourceRecords:[{Ref:rt}]}})}this.builder.addOutput("MailHost",{Value:`mail.${we}`,Description:"Mail server hostname for SMTP/IMAP clients"})}}}let ve=this.mergedConfig.infrastructure?.search;if(ve){let we=ve.vpc?{subnetIds:[{Ref:"PublicSubnet1"}],securityGroupIds:[]}:void 0;if(we)this.generateNetworkInfrastructure(x,t);let{domain:ke,logicalId:Be}=Ht.createDomain({slug:x,environment:t,engineVersion:ve.engineVersion||"OpenSearch_2.11",instanceType:ve.instanceType||"t3.small.search",instanceCount:ve.instanceCount||1,volumeSize:ve.volumeSize||10,volumeType:ve.volumeType||"gp3",dedicatedMaster:ve.dedicatedMaster||!1,dedicatedMasterType:ve.dedicatedMasterType,dedicatedMasterCount:ve.dedicatedMasterCount||3,multiAz:ve.multiAz||!1,encryption:ve.encryption||{atRest:!0,nodeToNode:!0},advancedSecurity:ve.advancedSecurity,autoTune:ve.autoTune!==!1,vpc:we});this.builder.addResource(Be,ke),this.builder.addOutput("SearchDomainEndpoint",{Value:{"Fn::GetAtt":[Be,"DomainEndpoint"]},Description:"OpenSearch domain endpoint"}),this.builder.addOutput("SearchDomainArn",{Value:{"Fn::GetAtt":[Be,"Arn"]},Description:"OpenSearch domain ARN"})}let Ce=this.mergedConfig.infrastructure?.fileSystem;if(Ce&&Object.keys(Ce).length>0){this.generateNetworkInfrastructure(x,t);for(let[we,ke]of Object.entries(Ce)){let{fileSystem:Be,logicalId:Ae}=tt.createFileSystem({slug:`${x}-${we}`,environment:t,encrypted:ke.encrypted!==!1,performanceMode:ke.performanceMode||"generalPurpose",throughputMode:ke.throughputMode||"bursting",enableBackup:!0});this.builder.addResource(Ae,Be);let{securityGroup:Ie,logicalId:Te}=tt.createEfsSecurityGroup({slug:`${x}-${we}`,environment:t,vpcId:{Ref:"VPC"},sourceCidrBlocks:["10.0.0.0/16"]});this.builder.addResource(Te,Ie);let{mountTargets:Xe,logicalIds:_e}=tt.createMultiAzMountTargets(Ae,{slug:`${x}-${we}`,environment:t,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],securityGroupId:{Ref:Te}});for(let Ze=0;Ze<Xe.length;Ze++)this.builder.addResource(_e[Ze],Xe[Ze]);this.builder.addOutput(`${we}FileSystemId`,{Value:{Ref:Ae},Description:`EFS file system ID for ${we}`})}}let Re=this.mergedConfig.infrastructure?.ai;if(Re){let we=Re.service||"ecs",ke=Re.models||["*"],Be=Re.allowStreaming!==!1,Ae;if(we==="ecs")Ae=Fe.enableBedrockForEcs({slug:x,environment:t,models:ke,allowStreaming:Be});else if(we==="ec2")Ae=Fe.enableBedrockForEc2({slug:x,environment:t,models:ke,allowStreaming:Be});else if(we==="lambda")Ae=Fe.enableBedrockForLambda({slug:x,environment:t,models:ke,allowStreaming:Be});else Ae=Fe.createBedrockRole(we,{slug:x,environment:t,models:ke,allowStreaming:Be});if(this.builder.addResource(Ae.logicalId,Ae.role),Re.allowAsync){let{policy:Ie,logicalId:Te}=Fe.createBedrockPolicy({slug:x,environment:t,models:ke,allowStreaming:Be,allowAsync:!0});this.builder.addResource(Te,Ie)}this.builder.addOutput("BedrockRoleArn",{Value:{"Fn::GetAtt":[Ae.logicalId,"Arn"]},Description:"IAM role ARN with Bedrock permissions"})}}generateRealtimeResources(x,t){let m=this.mergedConfig.infrastructure?.realtime;if(!m)return;let A=m.name||`${x}-${t}-realtime`,N=m.scaling||{},z=m.storage||{type:"dynamodb"},U=N.handlerMemory||256,X=N.handlerTimeout||30,te=`${x}${t}RealtimeConnections`.replace(/[^a-zA-Z0-9]/g,""),re=`${x}${t}RealtimeChannels`.replace(/[^a-zA-Z0-9]/g,"");if(z.type==="dynamodb"){let _e=z.dynamodb||{},Ze=_e.billingMode||"PAY_PER_REQUEST";this.builder.addResource(te,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${x}-${t}-realtime-connections`,BillingMode:Ze,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"},...Ze==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:_e.readCapacity||5,WriteCapacityUnits:_e.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},...Ze==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:_e.readCapacity||5,WriteCapacityUnits:_e.writeCapacity||5}},..._e.pointInTimeRecovery&&{PointInTimeRecoverySpecification:{PointInTimeRecoveryEnabled:!0}},Tags:[{Key:"Name",Value:`${x}-${t}-realtime-connections`},{Key:"Environment",Value:t}]}}),this.builder.addResource(re,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${x}-${t}-realtime-channels`,BillingMode:Ze,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"},...Ze==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:_e.readCapacity||5,WriteCapacityUnits:_e.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},...Ze==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:_e.readCapacity||5,WriteCapacityUnits:_e.writeCapacity||5}},Tags:[{Key:"Name",Value:`${x}-${t}-realtime-channels`},{Key:"Environment",Value:t}]}})}let oe=`${x}${t}RealtimeRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(oe,{Type:"AWS::IAM::Role",Properties:{RoleName:`${x}-${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":[te,"Arn"]},{"Fn::Sub":`\${${te}.Arn}/index/*`},{"Fn::GetAtt":[re,"Arn"]},{"Fn::Sub":`\${${re}.Arn}/index/*`}]},{Effect:"Allow",Action:"execute-api:ManageConnections",Resource:{"Fn::Sub":"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*"}}]}}]}});let ae=`${x}${t}RealtimeConnect`.replace(/[^a-zA-Z0-9]/g,""),ve=`${x}${t}RealtimeDisconnect`.replace(/[^a-zA-Z0-9]/g,""),Ce=`${x}${t}RealtimeMessage`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(ae,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${x}-${t}-realtime-connect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[oe,"Arn"]},MemorySize:U,Timeout:X,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:te},CHANNELS_TABLE:{Ref:re},ENVIRONMENT:t}},Code:{ZipFile:this.generateConnectHandlerCode()}},DependsOn:[oe,te]}),this.builder.addResource(ve,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${x}-${t}-realtime-disconnect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[oe,"Arn"]},MemorySize:U,Timeout:X,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:te},CHANNELS_TABLE:{Ref:re},ENVIRONMENT:t}},Code:{ZipFile:this.generateDisconnectHandlerCode()}},DependsOn:[oe,te,re]}),this.builder.addResource(Ce,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${x}-${t}-realtime-message`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[oe,"Arn"]},MemorySize:U,Timeout:X,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:te},CHANNELS_TABLE:{Ref:re},ENVIRONMENT:t}},Code:{ZipFile:this.generateMessageHandlerCode()}},DependsOn:[oe,te,re]});let Re=`${x}${t}RealtimeApi`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Re,{Type:"AWS::ApiGatewayV2::Api",Properties:{Name:A,ProtocolType:"WEBSOCKET",RouteSelectionExpression:"$request.body.action",Tags:{Name:A,Environment:t}}});let we=`${ae}Permission`,ke=`${ve}Permission`,Be=`${Ce}Permission`;this.builder.addResource(we,{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}:\${${Re}}/*/$connect`}}}),this.builder.addResource(ke,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:ve},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Re}}/*/$disconnect`}}}),this.builder.addResource(Be,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:Ce},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Re}}/*/$default`}}});let Ae=`${Re}ConnectInteg`,Ie=`${Re}DisconnectInteg`,Te=`${Re}MessageInteg`;this.builder.addResource(Ae,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Re},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${ae}.Arn}/invocations`}}}),this.builder.addResource(Ie,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Re},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${ve}.Arn}/invocations`}}}),this.builder.addResource(Te,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Re},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${Ce}.Arn}/invocations`}}}),this.builder.addResource(`${Re}ConnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Re},RouteKey:"$connect",AuthorizationType:"NONE",Target:{"Fn::Sub":`integrations/\${${Ae}}`}}}),this.builder.addResource(`${Re}DisconnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Re},RouteKey:"$disconnect",Target:{"Fn::Sub":`integrations/\${${Ie}}`}}}),this.builder.addResource(`${Re}DefaultRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Re},RouteKey:"$default",Target:{"Fn::Sub":`integrations/\${${Te}}`}}});let Xe=`${Re}Stage`;if(this.builder.addResource(Xe,{Type:"AWS::ApiGatewayV2::Stage",Properties:{ApiId:{Ref:Re},StageName:t,AutoDeploy:!0,DefaultRouteSettings:{ThrottlingBurstLimit:N.messagesPerSecond||1000,ThrottlingRateLimit:N.messagesPerSecond||1000}}}),m.monitoring?.enabled){let _e=m.monitoring,Ze=_e.notificationTopicArn;if(!Ze&&_e.notificationEmails?.length){let et=`${Re}AlarmTopic`;this.builder.addResource(et,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${x}-${t}-realtime-alarms`,DisplayName:"Realtime WebSocket Alarms"}}),_e.notificationEmails.forEach((At,Ft)=>{this.builder.addResource(`${et}Sub${Ft}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:et},Protocol:"email",Endpoint:At}})}),Ze={Ref:et}}if(_e.connectionThreshold)this.builder.addResource(`${Re}ConnectionAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-realtime-connections`,AlarmDescription:`WebSocket connections exceed ${_e.connectionThreshold}`,MetricName:"ConnectCount",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:300,EvaluationPeriods:2,Threshold:_e.connectionThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Re}}],...Ze&&{AlarmActions:[Ze]}}});if(_e.errorThreshold)this.builder.addResource(`${Re}ErrorAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${x}-${t}-realtime-errors`,AlarmDescription:`WebSocket errors exceed ${_e.errorThreshold}/min`,MetricName:"ExecutionError",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:60,EvaluationPeriods:3,Threshold:_e.errorThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Re}}],...Ze&&{AlarmActions:[Ze]}}})}this.builder.addOutput(`${Re}Endpoint`,{Description:"WebSocket API endpoint URL",Value:{"Fn::Sub":`wss://\${${Re}}.execute-api.\${AWS::Region}.amazonaws.com/${t}`},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-endpoint"}}}),this.builder.addOutput(`${Re}Id`,{Description:"WebSocket API ID",Value:{Ref:Re},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
 
@@ -309,13 +309,13 @@ ${t.loadManagement?.enabled?`
309
309
  Generated ${Ae} resources:`);let Ie=Be.getResources(),Te={};for(let _e of Object.values(Ie)){let Ze=_e.Type;Te[Ze]=(Te[Ze]||0)+1}let Xe=Object.entries(Te).sort((_e,Ze)=>Ze[1]-_e[1]);for(let[_e,Ze]of Xe)e(` - ${_e}: ${Ze}`);e(`
310
310
  Next steps:
311
311
  1. Review the generated templates in ${N}/
312
- 2. Run 'cloud deploy' to deploy your infrastructure`)}catch(A){m.fail("Failed to generate templates"),r(A instanceof Error?A.message:"Unknown error")}}),x.command("generate:preview","Preview what will be generated").action(async()=>{Dr("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."})}),x.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 Oe(),A=t?.env||"production",{resolveProjectStackName:N}=await import("./chunk-g1krw34t.js"),z=t?.stack||N(m,A),U=m.project.region||"us-east-1";e(`Stack: ${z}`),e(`Region: ${U}`),e(`Environment: ${A}`),p("Generating template from configuration...");let X=new Ao({config:m,environment:A});X.generate();let te=X.toJSON(),re=JSON.parse(te);p("Fetching deployed template...");let oe=new v(U),ae=null;try{let Ie=await oe.getTemplate(z);if(Ie.TemplateBody)ae=JSON.parse(Ie.TemplateBody)}catch(Ie){if(Ie.message?.includes("does not exist")){a(`Stack "${z}" does not exist yet`),e(`
312
+ 2. Run 'cloud deploy' to deploy your infrastructure`)}catch(A){m.fail("Failed to generate templates"),r(A instanceof Error?A.message:"Unknown error")}}),x.command("generate:preview","Preview what will be generated").action(async()=>{Dr("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."})}),x.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 Oe(),A=t?.env||"production",{resolveProjectStackName:N}=await import("./chunk-9eq18gds.js"),z=t?.stack||N(m,A),U=m.project.region||"us-east-1";e(`Stack: ${z}`),e(`Region: ${U}`),e(`Environment: ${A}`),p("Generating template from configuration...");let X=new Ao({config:m,environment:A});X.generate();let te=X.toJSON(),re=JSON.parse(te);p("Fetching deployed template...");let oe=new v(U),ae=null;try{let Ie=await oe.getTemplate(z);if(Ie.TemplateBody)ae=JSON.parse(Ie.TemplateBody)}catch(Ie){if(Ie.message?.includes("does not exist")){a(`Stack "${z}" does not exist yet`),e(`
313
313
  This will be a new deployment with the following resources:`);let Te=Object.keys(re.Resources||{}).length;e(`
314
314
  Resources to create: ${Te}`);let Xe={};for(let _e of Object.values(re.Resources||{})){let Ze=_e.Type;Xe[Ze]=(Xe[Ze]||0)+1}for(let[_e,Ze]of Object.entries(Xe).sort((et,At)=>At[1]-et[1]))e(` + ${_e}: ${Ze}`);e("\nRun `cloud deploy` to create this stack");return}throw Ie}p("Comparing templates...");let ve=ae.Resources||{},Ce=re.Resources||{},Re=new Set(Object.keys(ve)),we=new Set(Object.keys(Ce)),ke=[];for(let Ie of we)if(!Re.has(Ie))ke.push(Ie);let Be=[];for(let Ie of Re)if(!we.has(Ie))Be.push(Ie);let Ae=[];for(let Ie of we)if(Re.has(Ie)){let Te=JSON.stringify(ve[Ie]),Xe=JSON.stringify(Ce[Ie]);if(Te!==Xe)Ae.push(Ie)}if(ke.length===0&&Be.length===0&&Ae.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 Ie of ke){let Te=Ce[Ie].Type;e(` + ${Ie} (${Te})`)}console.log()}if(Be.length>0){r(`Resources to remove (${Be.length}):`);for(let Ie of Be){let Te=ve[Ie].Type;e(` - ${Ie} (${Te})`)}console.log()}if(Ae.length>0){a(`Resources to modify (${Ae.length}):`);for(let Ie of Ae){let Te=Ce[Ie].Type;e(` ~ ${Ie} (${Te})`)}console.log()}e("Summary:"),e(` - Add: ${ke.length}`),e(` - Remove: ${Be.length}`),e(` - Modify: ${Ae.length}`),e("\nRun `cloud deploy` to apply these changes")}catch(m){r(`Diff failed: ${m.message}`)}})}function Zi(x,t=0){let m=Number(x);return Number.isFinite(m)?m:t}function Oa(x){return Zi(x)*1024}function $p(x){let t=x?.date?.trim(),m=x?.time?.trim();if(!t||!m)return null;let A=new Date(`${t}T${m}Z`);return Number.isFinite(A.getTime())?A.toISOString():null}function Cp(x,t,m){let A;try{A=JSON.parse(x)}catch{return[]}let N=(A?.sysstat?.hosts??[]).flatMap((U)=>U.statistics??[]),z=[];for(let U of N){let X=$p(U.timestamp);if(!X)continue;let te=new Date(X).getTime();if(te<t.getTime()||te>m.getTime())continue;let re=U["cpu-load"]?.find((ae)=>ae.cpu==="all")??U["cpu-load"]?.[0],oe=Zi(re?.idle,100);z.push({timestamp:X,cpuUsedPercent:Math.max(0,Math.min(100,100-oe)),cpuUserPercent:Zi(re?.user),cpuSystemPercent:Zi(re?.system),cpuIowaitPercent:Zi(re?.iowait),memoryUsedPercent:Zi(U.memory?.["memused-percent"]),memoryUsedBytes:Oa(U.memory?.memused),memoryAvailableBytes:Oa(U.memory?.avail),swapUsedPercent:Zi(U.memory?.["swpused-percent"]),swapUsedBytes:Oa(U.memory?.swpused)})}return z.sort((U,X)=>U.timestamp.localeCompare(X.timestamp))}function Ap(x){return Object.fromEntries(Object.entries(x.time_series??{}).map(([t,m])=>[t,(m.values??[]).map(([A,N])=>({timestamp:new Date(Number(A)*1000).toISOString(),value:Zi(N,Number.NaN)})).filter((A)=>Number.isFinite(A.value))]))}function Ml(x){return x.length?x.reduce((t,m)=>t+m,0)/x.length:null}function ja(x){return x.length?Math.max(...x):null}function kp(x){return x.length?Math.min(...x):null}async function _l(x){let[t,m]=await Promise.all([x.client.getServerMetrics(x.serverId,{types:["cpu","disk","network"],start:x.from,end:x.to,step:x.step}),Ne(x.remote.host,"LC_ALL=C sadf -j -- -u -r -S",x.remote).catch(()=>"")]),A=Cp(m,x.from,x.to),N=Ap(t),U=(N.cpu??[]).map((ae)=>Math.max(0,Math.min(100,ae.value/Math.max(1,x.cores)))),X=A.map((ae)=>ae.cpuUsedPercent),te=U.length?U:X,re=A.map((ae)=>ae.memoryUsedPercent),oe=A.map((ae)=>ae.swapUsedPercent);return{from:x.from.toISOString(),to:x.to.toISOString(),cores:x.cores,host:A,provider:N,summary:{cpuAveragePercent:Ml(te),cpuPeakPercent:ja(te),memoryAveragePercent:Ml(re),memoryPeakPercent:ja(re),memoryMinimumAvailableBytes:kp(A.map((ae)=>ae.memoryAvailableBytes)),swapPeakPercent:ja(oe)}}}function Rp(x){if(x.length===0)throw Error("A server name cannot be empty.");if(x.length>253)throw Error(`'${x}' is longer than the 253 characters a hostname allows.`);for(let t of x.split(".")){if(t.length===0)throw Error(`'${x}' 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 zl(x,t,m){if(Rp(t),x===t)throw Error(`'${x}' is already named that.`);if((await m.takenNames()).some((z)=>z===t&&z!==x))throw Error(`'${t}' is already taken. Server names have to be unique within a provider project.`);let N=[];if(m.providerName&&m.renameProvider){let{providerName:z,renameProvider:U}=m;N.push({id:"provider",title:"Rename the server at the provider",change:{from:x,to:t},satisfied:async()=>await z()===t,apply:()=>U(t)})}if(m.stateName&&m.writeStateName){let{stateName:z,writeStateName:U}=m;N.push({id:"state-pin",title:"Update the recorded name in the local driver state",change:{from:x,to:t},satisfied:async()=>{let X=await z();return X===void 0||X===t},apply:()=>U(t)})}if(m.remoteHostname&&m.setRemoteHostname){let{remoteHostname:z,setRemoteHostname:U}=m;N.push({id:"hostname",title:"Set the hostname on the box",change:{from:x,to:t},satisfied:async()=>await z()===t,apply:()=>U(t)})}return N.push({id:"inventory",title:"Rename the fleet inventory record",change:{from:x,to:t},satisfied:async()=>m.inventoryName()===t,apply:async()=>{await m.renameInventory(t)}}),{operation:"server:rename",target:x,steps:N}}function Ul(x){let t=`'${x.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${x}/" /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 Ip(){let x=await Oe(),t=y(process.cwd(),x),m=new Pe(t.store),A=new yn(m,[new nt("aws"),new nt("hetzner"),new nt("ssh")]);return{controlPlane:t,store:m,service:A}}async function wi(x){let t=await Ip();try{return await x(t)}finally{t.controlPlane.store.close()}}var Xi=(x,t)=>{let m=x.store.list(x.controlPlane.project.id).find((A)=>A.id===t||A.name===t);if(!m)throw Error(`Server ${t} was not found.`);return m},ar=(x)=>{r(x instanceof Error?x.message:String(x)),process.exitCode=1};function Fa(x,t){let m=Number.parseInt(x??"",10);return Number.isFinite(m)&&m>=0?m:t}function Pp(x="3h"){let t=x.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 is(x,t="%"){return x==null?"unavailable":`${x.toFixed(1)}${t}`}async function xp(x,t){let m=await Oe();if(m.cloud?.provider!=="hetzner")throw Error("server:monitoring currently requires the Hetzner provider.");let A=t.env??process.env.CLOUD_ENV??"production",N=E(m,A),z=await pe(N);if(!z?.serverId)throw Error(`No pinned Hetzner server was found for ${N}.`);if(![z.serverName,String(z.serverId),z.stackName].includes(x))throw Error(`Refusing to read ${x}: project state is pinned to ${z.serverName}.`);let U=or(m),X=new qe({apiToken:sr(U.apiToken,m)}),te=await X.getServer(z.serverId),re=z.publicIp??te.public_net.ipv4?.ip;if(!re)throw Error(`Server ${te.name} has no public IPv4 address.`);let oe=new Date,ae=new Date(oe.getTime()-Pp(t.range)),ve=Math.min(3600,Math.max(60,Fa(t.step,60))),Ce=await _l({client:X,serverId:te.id,cores:Math.max(1,Number(te.server_type.cores)||1),from:ae,to:oe,step:ve,remote:{host:re,user:z.sshUser??U.sshUser,identityFile:U.sshPrivateKeyPath,connectTimeoutSec:15}});if(t.json){console.log(JSON.stringify({schemaVersion:1,server:te.name,result:Ce},null,2));return}c(["Server","Window","CPU average / peak","RAM average / peak","Minimum available","Swap peak"],[[te.name,`${Ce.from} to ${Ce.to}`,`${is(Ce.summary.cpuAveragePercent)} / ${is(Ce.summary.cpuPeakPercent)}`,`${is(Ce.summary.memoryAveragePercent)} / ${is(Ce.summary.memoryPeakPercent)}`,Ce.summary.memoryMinimumAvailableBytes==null?"unavailable":`${(Ce.summary.memoryMinimumAvailableBytes/1073741824).toFixed(2)} GB`,is(Ce.summary.swapPeakPercent)]])}function Vl(x){let t=x.targetType.prices?.find((m)=>m.location===x.location);return{serverId:x.serverId,serverName:x.serverName,location:x.location,from:x.currentType.name,to:x.targetType.name,capacityAvailable:x.capacityAvailable,alreadyComplete:x.alreadyComplete,upgradeDisk:x.upgradeDisk,target:{cores:x.targetType.cores,memoryGb:x.targetType.memory,diskGb:x.targetType.disk,architecture:x.targetType.architecture,cpuType:x.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 Dp(x){let t=Vl(x);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 Ep(x,t,m){let A=await Oe();if(A.cloud?.provider!=="hetzner")throw Error("server:resize currently requires the Hetzner provider.");let N=m.env??process.env.CLOUD_ENV??"production",z=E(A,N),U=await pe(z),X=or(A),te=new qe({apiToken:sr(X.apiToken,A)}),oe=U&&(x===U.serverName||x===String(U.serverId)||x===U.stackName)?await te.getServer(U.serverId):(await te.listServers()).find((_e)=>_e.name===x||String(_e.id)===x);if(!oe)throw Error(`Hetzner server ${x} was not found.`);if(U?.serverId&&U.serverId!==oe.id)throw Error(`Refusing to resize ${x}: project state is pinned to server ${U.serverId}.`);let ae=lt(t),ve=m.upgradeDisk!==!1,Ce=await Kn(te,oe.id,ae,ve);if(m.json)console.log(JSON.stringify({schemaVersion:1,plan:Vl(Ce)},null,2));else Dp(Ce);if(!m.apply){if(!m.json)e(`Preview only. Re-run with --apply --confirm ${oe.name} to execute.`);return}if(m.confirm!==oe.name)throw Error(`Resize requires --confirm ${oe.name}.`);let Re=U?.publicIp??oe.public_net.ipv4?.ip;if(!Re)throw Error(`Server ${oe.name} has no public IPv4 address for verification.`);let we={host:Re,user:U?.sshUser??X.sshUser,identityFile:X.sshPrivateKeyPath,connectTimeoutSec:15,sshTimeoutMs:600000,routeTimeoutMs:20000},ke=await Zo(z),Be=new Date().toISOString(),Ae={schemaVersion:1,operationId:ke?.targetType===ae&&ke.serverId===oe.id?ke.operationId:crypto.randomUUID(),stackName:z,serverId:oe.id,serverName:oe.name,sourceType:Ce.currentType.name,targetType:ae,upgradeDisk:ve,phase:"planning",status:"running",attempts:ke?.targetType===ae?ke.attempts:0,startedAt:ke?.targetType===ae?ke.startedAt:Be,updatedAt:Be,manifest:ke?.targetType===ae?ke.manifest:void 0};await ut(Ae);let Ie=Fa(m.pollSeconds,900)*1000,Te=Fa(m.maxWaitMinutes,0)*60000,Xe=Date.now();for(;;){let _e=await Kn(te,oe.id,ae,ve);if(!_e.capacityAvailable&&!_e.alreadyComplete){if(Ae={...Ae,phase:"waiting-capacity",status:"waiting-capacity",updatedAt:new Date().toISOString()},await ut(Ae),!m.wait){if(!m.json)e(`${ae} has no capacity in ${_e.location}. The server remains running and unchanged. Use --wait to poll safely.`);return}if(Te>0&&Date.now()-Xe>=Te)throw Error(`Timed out waiting for ${ae} capacity in ${_e.location}.`);if(!m.json)e(`Waiting ${Math.round(Ie/1000)} seconds for ${ae} capacity...`);await Bun.sleep(Ie);continue}let Ze=await es(z);try{let et=await(async()=>{try{return Ae={...Ae,status:"running",attempts:Ae.attempts+1,updatedAt:new Date().toISOString()},await ut(Ae),await Go({client:te,serverId:oe.id,targetType:ae,upgradeDisk:ve,resumeFromOff:Ae.phase==="shutting-down"||Ae.phase==="resizing"||Ae.phase==="powering-on"||Ae.phase==="verifying",manifest:Ae.manifest,hooks:{preflight:async()=>{let At=await Ko(we);return Ae={...Ae,manifest:At,updatedAt:new Date().toISOString()},await ut(Ae),At},afterBoot:async(At,Ft,wt,Et)=>Jo(we,Ft,wt,Et),onPhase:async(At,Ft)=>{if(Ae={...Ae,phase:At,status:At==="waiting-capacity"?"waiting-capacity":At==="complete"?"completed":"running",updatedAt:new Date().toISOString()},await ut(Ae),!m.json)e(`${At}${Ft?` ${JSON.stringify(Ft)}`:""}`)}}})}finally{await Ze()}})();if(Ae={...Ae,status:et.status==="completed"||et.status==="already-complete"?"completed":et.status==="recovered"?"recovered":"waiting-capacity",lastError:et.status==="recovered"?et.error:void 0,updatedAt:new Date().toISOString()},await ut(Ae),m.json)console.log(JSON.stringify({schemaVersion:1,result:et},null,2));else if(et.status==="completed"||et.status==="already-complete")n(`${oe.name} is on ${ae} and passed full post-boot verification.`);else if(et.status==="recovered"&&et.retryable&&m.wait){e(`Capacity race recovered safely. Waiting ${Math.round(Ie/1000)} seconds before retry.`),await Bun.sleep(Ie);continue}else if(et.status==="recovered")throw Error(`Resize did not complete and the original server was recovered: ${et.error}`);return}catch(et){throw Ae={...Ae,status:"failed",lastError:et instanceof Error?et.message:String(et),updatedAt:new Date().toISOString()},await ut(Ae),et}}}async function Tp(x,t){let m=await Oe();if(m.cloud?.provider!=="hetzner")throw Error("server:optimize currently requires the Hetzner provider.");let A=t.env??process.env.CLOUD_ENV??"production",N=E(m,A),z=await pe(N);if(!z?.serverId)throw Error(`No pinned Hetzner server was found for ${N}.`);if(![z.serverName,String(z.serverId),z.stackName].includes(x))throw Error(`Refusing to optimize ${x}: project state is pinned to ${z.serverName}.`);let U=or(m),X=new qe({apiToken:sr(U.apiToken,m)}),te=await X.getServer(z.serverId),re=z.publicIp??te.public_net.ipv4?.ip;if(!re)throw Error(`Server ${te.name} has no public IPv4 address.`);let oe=Fi(m);if(t.json)console.log(JSON.stringify({schemaVersion:1,server:te.name,plan:oe},null,2));else c(["Server","Firewall ports","Monitoring","Auto updates","Swap","SSH passwords"],[[te.name,oe.firewallPorts.join(","),oe.monitoring?"on":"off",oe.autoUpdates?"on":"off",`${oe.swapGb} GB`,"disabled"]]);if(!t.apply){if(!t.json)e(`Preview only. Re-run with --apply --confirm ${te.name} to execute.`);return}if(t.confirm!==te.name)throw Error(`Optimization requires --confirm ${te.name}.`);let ae={host:re,user:z.sshUser??U.sshUser,identityFile:U.sshPrivateKeyPath,connectTimeoutSec:15},ve=await qr(ae),Ce=ve.routeProbes.filter((wt)=>!wt.ok),Re=m.infrastructure?.compute,we=Y(Re)?new Set(["nginx.service","bun-gateway.service","ts-cloud-nginx.service"]):new Set,ke=ve.failedUnits.filter((wt)=>!we.has(wt));if(ke.length>0)throw Error(`Preflight found failed services: ${ke.join(", ")}`);if(Ce.length>0)throw Error(`Preflight found unhealthy routes: ${Ce.map((wt)=>wt.domain).join(", ")}`);let Be=Y(Re)?[]:Object.values(m.sites??{}).flatMap((wt)=>typeof wt?.port==="number"&&![80,443].includes(wt.port)?[wt.port]:[]),Ae=zr({allowSsh:Re?.allowSsh!==!1,sitePorts:Be,allowedPorts:Re?.firewall?.allowedPorts}),Ie=(z.firewallId?(await X.listFirewalls()).find((wt)=>wt.id===z.firewallId):void 0)??(await X.listFirewalls()).find((wt)=>wt.name===`${m.project.slug}-${A}-app-fw`);if(!Ie)throw Error(`Managed firewall for ${te.name} was not found.`);let Te=await X.setFirewallRules(Ie.id,Ae);await Promise.all(Te.map((wt)=>X.waitForAction(wt.id))),await Xo(m,ae);let[Xe,_e]=await Promise.all([qr(ae),Qo(ae)]),Ze=Yo(oe,_e),et=_a(ve,Xe),At=Xe.routeProbes.filter((wt)=>!wt.ok);if(et.stoppedServices.length>0)Ze.push(`services stopped: ${et.stoppedServices.join(", ")}`);if(et.changedRouteFragments.length>0)Ze.push(`route fragments changed: ${et.changedRouteFragments.join(", ")}`);if(et.missingRouteIds.length>0)Ze.push(`routes disappeared: ${et.missingRouteIds.join(", ")}`);if(et.changedReleaseLinks.length>0)Ze.push(`release links changed: ${et.changedReleaseLinks.join(", ")}`);if(et.missingData.length>0)Ze.push(`databases or volumes disappeared: ${et.missingData.join(", ")}`);if(At.length>0)Ze.push(`unhealthy routes: ${At.map((wt)=>wt.domain).join(", ")}`);if(Ze.length>0)throw Error(`Host optimization verification failed: ${Ze.join("; ")}`);let Ft={serverId:te.id,serverName:te.name,services:Xe.runningServices.length,routes:Xe.routeIds.length,healthyDomains:Xe.routeProbes.length,releaseLinks:Xe.releaseLinks.length,databasesAndVolumes:Xe.dataCatalog.length,report:_e};if(t.json)console.log(JSON.stringify({schemaVersion:1,result:Ft},null,2));else n(`${te.name} passed full host, rpx route, service, release, and data verification.`)}async function Np(x,t,m){let A=await Oe(),N=m.env??"production",z=E(A,N);await wi(async(U)=>{let X=Xi(U,x),te=U.store.list(U.controlPlane.project.id,!0),re=X.provider==="hetzner"&&X.providerId?(()=>{try{let Ie=or(A);return new qe({apiToken:sr(Ie.apiToken,A)})}catch{return null}})():null,oe=X.providerId?Number(X.providerId):Number.NaN,ae=await pe(z),ve=ae!=null&&ae.serverId===oe,Ce=new kr,Re=X.trustState==="pinned",we={takenNames:async()=>{let Ie=te.map((Te)=>Te.name);if(re)Ie.push(...(await re.listServers()).map((Te)=>Te.name));return Ie},inventoryName:()=>U.store.get(X.id)?.name??X.name,renameInventory:(Ie)=>{U.store.update(X.id,{name:Ie})},...re&&Number.isFinite(oe)?{providerName:async()=>(await re.getServer(oe)).name,renameProvider:async(Ie)=>{await re.renameServer(oe,Ie)}}:{},...ve?{stateName:async()=>(await pe(z))?.serverName,writeStateName:async(Ie)=>{let Te=await pe(z);if(Te)await dt(z,{...Te,serverName:Ie})}}:{},...Re?{remoteHostname:async()=>{let Ie=await Ce.exec(X,"hostname");return Ie.code===0?Ie.stdout.trim():void 0},setRemoteHostname:async(Ie)=>{let Te=await Ce.exec(X,Ul(Ie));if(Te.code!==0)throw Error(Te.stderr.trim()||`hostname exited ${Te.code}`)}}:{}},ke=await zl(X.name,t,we),Be=await ks(ke);if(!m.apply){let Ie=[re?"":"provider record (no provider id or no API token)",ve?"":"local state pin (none points at this server)",Re?"":"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:X.name,to:t},notCovered:Ie,steps:Be.map((Te)=>({id:Te.step.id,title:Te.step.title,state:Te.state,change:Te.step.change,reason:Te.reason}))},null,2));return}for(let Te of Is(ke,Be))console.log(Te);for(let Te of Ie)console.log(` not covered: ${Te}`);console.log(" Re-run with --apply to perform it.");return}let Ae=await xs(ke,Be,{log:(Ie)=>e(Ie),audit:(Ie)=>U.controlPlane.store.appendEvent({projectId:U.controlPlane.project.id,resourceId:X.resourceId,type:`${Ie.operation}.${Ie.step}.${Ie.state}`,level:Ie.state==="failed"?"error":"info",payload:{target:Ie.target,renameTo:t,...Ie.error?{error:Ie.error}:{}}})});if(m.json)console.log(JSON.stringify({schemaVersion:1,outcome:Ae},null,2));if(!Ae.success){let Ie=Ae.steps.find((Te)=>Te.state==="failed");throw Error(`${ke.operation} stopped at '${Ie?.title}': ${Ie?.error}. Earlier steps stayed applied; re-run the same command to continue from here.`)}if(!m.json)n(`Renamed ${X.name} to ${t}.`)})}function Ba(x){x.command("capabilities [server]","Show provider and target operation support").option("--json","Print structured JSON").action(async(t,m)=>{try{await wi(async(A)=>{let N=t?[Xi(A,t)]:A.store.list(A.controlPlane.project.id),z=N.flatMap((U)=>Object.entries(U.capabilities).map(([X,te])=>({server:U.name,provider:U.provider,action:X,...te})));if(m.json)console.log(JSON.stringify({schemaVersion:1,targets:z},null,2));else c(["Server","Provider","Action","Support","Explanation"],z.map((U)=>[U.server,U.provider,U.action,U.supported?"supported":"unsupported",U.reason??"—"]))})}catch(A){ar(A)}}),x.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,A)=>{try{await Ep(t,m,A)}catch(N){ar(N)}}),x.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 Tp(t,m)}catch(A){ar(A)}}),x.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 xp(t,m)}catch(A){ar(A)}}),x.command("server:list","List fleet servers").option("--json","Print structured JSON").action(async(t)=>{try{await wi(async(m)=>{let A=m.store.list(m.controlPlane.project.id);if(t.json)console.log(JSON.stringify(A,null,2));else c(["Name","Provider ID","Provider / region","Status / trust","Roles","CPU / memory","Heartbeat"],A.map((N)=>[N.name,N.providerId??"—",`${N.provider} / ${N.region??"external"}`,`${N.status} / ${N.trustState}`,N.roles.join(","),`${N.capacity.cpu??0} / ${N.capacity.memoryBytes??0}`,N.heartbeatAt??"never"]))})}catch(m){ar(m)}}),x.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 wi(async(A)=>{let N=m.provider;if(!["aws","hetzner","ssh"].includes(N)||!m.endpoint)throw Error("A supported --provider and --endpoint are required; provisioning and enrollment are separate operations.");let z=A.service.enroll({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,name:t,provider:N,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((U)=>U.split("=")).filter((U)=>U[0]&&U[1]))});n(`Enrolled ${z.name} as ${z.id}; no remote mutation was performed.`)})}catch(A){ar(A)}}),x.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 wi(async(A)=>{let N=Xi(A,t);if(N=await A.service.test(N.id),N.trustState==="rotation_pending"){if(!m.acceptHostKey)throw Error(`Host key changed to ${N.pendingHostKey}; review it first.`);N=A.service.reviewHostKey(N.id,m.acceptHostKey)}let z=await A.service.validate(N.id);if(m.json)console.log(JSON.stringify(z.validation,null,2));else c(["Severity","Code","Finding","Remediation"],(z.validation?.findings??[]).map((U)=>[U.severity,U.code,U.message,U.remediation??"—"]));if(!z.validation?.valid)process.exitCode=1})}catch(A){ar(A)}}),x.command("server:bootstrap <name>","Preview or queue bootstrap").option("--apply","Apply reviewed plan").action(async(t,m)=>{try{await wi(async(A)=>{let N=A.service.bootstrap(Xi(A,t).id,!!m.apply);if(N.preview)c(["Step"],N.steps.map((z)=>[z]));else n(`Bootstrap queued: ${N.operation?.id}`)})}catch(A){ar(A)}}),x.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,A)=>{try{await Np(t,m,A)}catch(N){ar(N)}}),x.command("server:drain <name>","Drain without terminating").option("--complete","Mark movement complete").action(async(t,m)=>{try{await wi(async(A)=>n(A.service.drain(Xi(A,t).id,!!m.complete).status))}catch(A){ar(A)}}),x.command("server:uncordon <name>","Return a server to scheduling").action(async(t)=>{try{await wi(async(m)=>{m.service.uncordon(Xi(m,t).id),n(`Uncordoned ${t}.`)})}catch(m){ar(m)}}),x.command("server:archive <name>","Archive inventory without termination").option("--confirm <name>","Exact name").action(async(t,m)=>{try{await wi(async(A)=>{A.service.archive(Xi(A,t).id,m.confirm??""),n(`Archived ${t}; provider infrastructure was not terminated.`)})}catch(A){ar(A)}}),x.command("server:ssh <name>","Open strict SSH using the enrolled endpoint").action(async(t)=>{try{await wi(async(m)=>{let A=Xi(m,t);if(A.trustState!=="pinned")throw Error("Validate and pin the server host key first.");let N=Bun.spawn(["ssh","-p",String(A.sshPort),"-o","StrictHostKeyChecking=yes",`${A.sshUser}@${A.endpoint}`],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}),z=await N.exited;if(z)process.exitCode=z})}catch(m){ar(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"]])x.command(t,m).action(async(...A)=>Dr(t.split(" ")[0],{target:String(A[0]??""),nextAction:"Use the dashboard capability view or the corresponding runtime, release, firewall, backup, or maintenance service."}))}import{existsSync as Wl,mkdirSync as Op,readFileSync as Lp,writeFileSync as jp}from"node:fs";import{join as ql,resolve as Fp}from"node:path";var za=(x)=>{r(x instanceof Error?x.message:String(x)),process.exitCode=1};async function Bp(){try{return await Oe()}catch{return}}function Mp(x){if(x===void 0)return;let t=Number(x);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`--port must be a port number, not '${x}'.`);return t}function Gl(x,t,m){let A=x?.ssh?.hosts?.find((N)=>N.host===t);return new ur({hosts:t?[A??{host:t}]:x?.ssh?.hosts,user:m.user,port:Mp(m.port),privateKeyPath:m.key,hostKey:x?.ssh?.hostKey,sudo:x?.ssh?.sudo,profile:m.profile??x?.ssh?.profile,publicIp:x?.ssh?.publicIp,lan:x?.ssh?.lan})}var Hl=1073741824;function Va(x){x.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 A=await Bp(),N=Gl(A,t,m),z=t??N.targetHost(),U=await N.ensureHostKey(z),{facts:X,findings:te}=await N.preflight(z);if(m.json)console.log(JSON.stringify({schemaVersion:1,host:z,hostKeyFingerprint:U.fingerprint,facts:X,findings:te},null,2));else{if(i(`Preflight ${z}`),U.pinnedNow)e(`Pinned host key ${U.fingerprint}`);if(e(`${X.os} on ${X.arch}, ${X.cpuCores} cores, ${(X.memoryBytes/Hl).toFixed(1)} GiB memory, ${(X.diskFreeBytes/Hl).toFixed(1)} GiB free`),X.piModel)e(X.piModel);if(X.lanIp)e(`LAN address ${X.lanIp}`);if(console.log(Hr(te)),dr(te))r("The host is not ready; fix the errors above and run the preflight again.");else n("The host is ready to adopt.")}if(dr(te))process.exitCode=1}catch(A){za(A)}}),x.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 Oe(),A=t.env??"production";if(t.dryRun){let U=lr(m),[X]=U.hosts;console.log(cr({config:m,environment:A,profile:U.profile,sudoUser:X.user!=="root"?X.user:void 0,lan:U.lan}));return}let N=Gl(m,void 0,{});i(`Adopt ${N.targetHost()}`);let z=await N.provisionComputeInfrastructure({config:m,environment:A});n(`${z.appPublicIp} is bootstrapped; deploy with: cloud deploy --env ${A}`)}catch(m){za(m)}}),x.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 Ui))throw Error(`--os must be raspberry-pi-os or ubuntu, not '${m}'.`);let A=await Oe(),N=t.env??"production",z=lr(A,{hosts:A.ssh?.hosts?.length?void 0:[{host:t.hostname}],profile:m==="raspberry-pi-os"?"raspberry-pi":void 0}),U=t.user??(A.ssh?.hosts?.[0]?.user??"pi"),X=Vr(t.key??"~/.ssh/id_ed25519.pub");if(!Wl(X))throw Error(`Public key not found at ${X}; pass --key <path to a .pub file>.`);let te=Lp(X,"utf8").trim(),re;if(t.wifiSsid){let Re=t.wifiPassphraseEnv??"WIFI_PASSWORD",we=process.env[Re];if(!we)throw Error(`--wifi-ssid needs the passphrase in $${Re} (set it in the environment, not on the command line).`);if(!t.wifiCountry)throw Error("--wifi-country <cc> is required with --wifi-ssid.");re={ssid:t.wifiSsid,passphrase:we,country:t.wifiCountry}}let oe=cr({config:A,environment:N,profile:z.profile,sudoUser:U!=="root"?U:void 0,lan:z.lan}),ae=Bi({hostname:t.hostname,user:U,publicKey:te,timezone:t.timezone,locale:t.locale,wifi:re},oe,{os:m}),ve=Fp(t.out);Op(ve,{recursive:!0});let Ce=Object.keys(ae.files).filter((Re)=>Wl(ql(ve,Re)));if(Ce.length>0&&!t.force)throw Error(`${Ce.join(", ")} already exist in ${ve}; pass --force to overwrite.`);for(let[Re,we]of Object.entries(ae.files))jp(ql(ve,Re),we,{mode:384});i(`First boot for ${t.hostname}`),n(`Wrote ${Object.keys(ae.files).join(", ")} to ${ve}`),console.log(ae.instructions)}catch(m){za(m)}})}async function _p(){let x=await Oe(),t=y(process.cwd(),x),m=new Sn(t.store),A=new bn(m,new Pe(t.store));return{controlPlane:t,store:m,service:A}}async function os(x){let t=await _p();try{await x(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var Es=(x)=>Object.fromEntries(String(x??"").split(",").map((t)=>t.split("=")).filter((t)=>t[0]&&t[1])),Kl=(x)=>Object.fromEntries(Object.entries(Es(x)).map(([t,m])=>[t,Number(m)]));function Wa(x){x.command("pool:list","List capacity pools and reservations").option("--json","Print structured JSON").action(async(t)=>os((m)=>{let A=m.store.listPools(m.controlPlane.project.id);if(t.json)console.log(JSON.stringify(A,null,2));else c(["Name","Purpose","Backend","Region / arch","Status","Capacity","Concurrency"],A.map((N)=>[N.name,N.purpose,N.backend,`${N.region??"any"} / ${N.architecture??"any"}`,N.status,JSON.stringify(N.capacity),String(N.concurrency)]))})),x.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)=>os((A)=>{let{purpose:N,backend:z}=m;if(!["application","build","worker","monitoring","backup"].includes(N)||!["server","ecs","asg"].includes(z))throw Error("Unsupported pool purpose or backend.");let U=A.store.createPool({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,name:t,purpose:N,backend:z,region:m.region,architecture:m.arch,labels:Es(m.labels),requiredServerLabels:Es(m.selector),toleratedTaints:[],capacity:{cpu:0,memoryBytes:0,diskBytes:0,gpu:0,...Kl(m.capacity)},maxWorkloads:Number(m.max),costWeight:1,concurrency:Number(m.concurrency),ephemeralWorkspaces:!0,allowProductionSecrets:!1,status:"active"});n(`Created ${U.name} (${U.id}).`)})),x.command("pool:add-server <pool> <server>","Attach an enrolled server to a pool").action(async(t,m)=>os((A)=>{let N=A.store.listPools(A.controlPlane.project.id).find((U)=>U.id===t||U.name===t),z=new Pe(A.controlPlane.store).list(A.controlPlane.project.id).find((U)=>U.id===m||U.name===m);if(!N||!z)throw Error("Pool or server was not found.");A.store.addMember(N.id,z.id),n(`Attached ${z.name} to ${N.name}.`)})),x.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)=>os((m)=>{let A=m.service.explain(m.controlPlane.project.id,{purpose:t.purpose,resources:Kl(t.resources),region:t.region,architecture:t.arch,labels:Es(t.labels)});if(t.json)console.log(JSON.stringify(A,null,2));else c(["Pool","Server","Eligible","Available","Fit / spread / cost","Reasons"],A.map((N)=>[N.poolName,N.serverId??"provider-managed",N.eligible?"yes":"no",JSON.stringify(N.available),`${N.score.fit.toFixed(2)} / ${N.score.spread} / ${N.score.cost}`,N.reasons.join("; ")||"eligible"]))})),x.command("pool:drain <pool>","Move stateless placements and block stateful placements").action(async(t)=>os((m)=>{let A=m.store.listPools(m.controlPlane.project.id).find((z)=>z.id===t||z.name===t);if(!A)throw Error("Pool was not found.");let N=m.service.drainPool(A.id);n(`Draining ${A.name}: moved ${N.moved.length}, blocked ${N.blocked.length} stateful placement(s).`)}))}async function zp(){let x=await Oe(),t=y(process.cwd(),x),m=new vn(t.store),A=new Tn(m);return{controlPlane:t,store:m,service:A}}async function mo(x){let t=await zp();try{await x(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var ss=(x,t)=>{let m=x.store.list(x.controlPlane.project.id).find((A)=>A.id===t||A.name===t);if(!m)throw Error("Regional topology was not found.");return m},Up=(x)=>x.split(",").map((t,m)=>{let[A,N="aws"]=t.split(":");return{region:String(A),provider:N,role:m===0?"primary":"secondary"}}),Vp=(x)=>{let t=new Set(["s3","dynamodb","secrets"]),m=x.split(",").map((N)=>N.trim()),A=m.find((N)=>!t.has(N));if(A)throw Error(`Unsupported replication kind: ${A}`);return m};function qa(x){x.command("region:list","List regional topology, traffic, and replication health").option("--json","Print structured JSON").action(async(t)=>mo((m)=>{let A=m.store.list(m.controlPlane.project.id).map((N)=>({topology:N,targets:m.store.targets(N.id),channels:m.store.channels(N.id),route:m.store.route(N.id),executions:m.store.executions(N.id)}));if(t.json)console.log(JSON.stringify(A,null,2));else c(["Name","Hostname","Active","Status","Revision","Replication"],A.map((N)=>[N.topology.name,N.topology.hostname,N.topology.activeRegion,N.topology.status,N.topology.revision??"—",`${N.channels.filter((z)=>z.status==="in_sync").length}/${N.channels.length} in sync`]))})),x.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)=>mo((A)=>{if(!m.hostname||!m.regions)throw Error("--hostname and --regions are required.");let N=A.service.create({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,name:t,hostname:m.hostname,regions:Up(m.regions),trafficPolicy:"active_passive",dataPolicy:{replicate:Vp(String(m.replicate)),maxLagSeconds:Number(m.maxLag),retainOnDestroy:!0},dnsProvider:"route53",cdnEnabled:!!m.cdn,wafEnabled:!!m.waf});n(`Created ${N.name} (${N.id}); run region:rollout with an immutable manifest.`)})),x.command("region:plan <name> <operation>","Preview exact regional execution steps").option("--target <region>","Failover or failback region").action(async(t,m,A)=>mo((N)=>c(["Step"],N.service.plan(ss(N,t).id,m,A.target).map((z)=>[z])))),x.command("region:rollout <name> <revision>","Queue secondary-first regional stack rollout").option("--manifest <file>","Regional stack manifest JSON").action(async(t,m,A)=>mo(async(N)=>{let z=A.manifest?await Bun.file(A.manifest).json():{},U=N.service.enqueue({topologyId:ss(N,t).id,kind:"rollout",revision:m,manifest:z});n(`Regional rollout queued: ${U.operation.id}`)})),x.command("region:failover <name> <region>","Queue health and RPO-gated failover").action(async(t,m)=>mo((A)=>{let N=A.service.enqueue({topologyId:ss(A,t).id,kind:"failover",targetRegion:m});n(`Failover queued: ${N.operation.id}`)})),x.command("region:failback <name> <region>","Queue health and RPO-gated failback").action(async(t,m)=>mo((A)=>{let N=A.service.enqueue({topologyId:ss(A,t).id,kind:"failback",targetRegion:m});n(`Failback queued: ${N.operation.id}`)})),x.command("region:destroy <name>","Drain traffic and delete regional stacks while retaining data").option("--confirm <name>","Exact topology name").action(async(t,m)=>mo((A)=>{let N=A.service.enqueue({topologyId:ss(A,t).id,kind:"destroy",confirmation:m.confirm});n(`Regional teardown queued: ${N.operation.id}; replicated data remains retained.`)}))}var as="0.16.30";async function qp(){let x=await Oe(),t=y(process.cwd(),x),m=new En(t.store),A=new wn(m),N=["releases","artifacts","operations","logs","previews","snapshots","orphaned_resources"].map((z)=>new Cn(z,t.store,t.project.id,t.organization.id));return{controlPlane:t,store:m,service:A,cleanup:N}}async function Ei(x){let t=await qp();try{await x(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}function Ga(x){x.command("maintenance:list","List updates, campaigns, cleanup plans, and DR drills").option("--json","Print structured JSON").action(async(t)=>Ei((m)=>{let A={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)},N=[...A.manifests.map((z)=>["update",z.version,z.compatibility.compatible?"compatible":"blocked",z.compatibility.reasons.join("; ")||z.channel]),...A.campaigns.map((z)=>["upgrade",z.id,z.status,z.currentStage??z.fromVersion]),...A.cleanups.map((z)=>["cleanup",z.kind,z.status,`${z.candidates.length} items / ${z.estimatedBytes} bytes`]),...A.drills.map((z)=>["DR drill",z.scenario,z.status,z.isolatedTarget])];if(t.json)console.log(JSON.stringify(A,null,2));else c(["Area","Name / version","Status","Detail"],N)})),x.command("maintenance:key:add <name> <pem>","Trust an Ed25519 update signing key").action(async(t,m)=>Ei(async(A)=>{let N=A.store.addTrustedKey({organizationId:A.controlPlane.organization.id,name:t,publicKeyPem:await Bun.file(m).text()});n(`Trusted ${N.name}: ${N.fingerprint}`)})),x.command("maintenance:manifest <document> <signature> <key>","Verify and import a signed update manifest").action(async(t,m,A)=>Ei(async(N)=>{let z=await Bun.file(t).json(),U=(await Bun.file(m).text()).trim(),X=N.store.importManifest({organizationId:N.controlPlane.organization.id,keyId:A,document:z,signature:U,current:{schemaVersion:N.controlPlane.store.health().schemaVersion,currentVersion:as,platform:process.platform,architecture:process.arch}});n(`Verified ${X.version}; ${X.compatibility.compatible?"compatible":"blocked: "+X.compatibility.reasons.join("; ")}`)})),x.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,A)=>Ei((N)=>{let z=N.store.createWindow({projectId:N.controlPlane.project.id,name:t,schedule:m,timezone:A.timezone,durationMinutes:Number(A.minutes),allowedOperations:["platform.upgrade"],requireApproval:!0});n(`Created ${z.name} (${z.id}).`)})),x.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,A)=>Ei((N)=>{let z=new Pe(N.controlPlane.store).list(N.controlPlane.project.id).filter((te)=>te.status==="ready").map((te)=>({serverId:te.id,target:te.name,previousVersion:te.bootstrapVersion??as}));if(!z.length)z.push({target:"control-plane",previousVersion:as});let U=N.store.createCampaign({projectId:N.controlPlane.project.id,manifestId:t,fromVersion:as,strategy:A.strategy==="rolling"?"rolling":"canary",batchSize:Number(A.batch),healthGate:{kind:"platform"},backupId:m,targets:z}),X=N.service.enqueueCampaign(U.id);n(`Staged upgrade queued: ${X.operation.id}`)})),x.command("cleanup:preview <kind>","Discover a bounded cleanup candidate set without deleting").option("--older-than <days>","Minimum age",{default:"30"}).action(async(t,m)=>Ei(async(A)=>{let N=await A.service.previewCleanup({projectId:A.controlPlane.project.id,kind:t,criteria:{olderThanDays:Number(m.olderThan)}},A.cleanup);c(["ID","Candidate","Bytes"],N.candidates.map((z)=>[z.id,z.label,String(z.bytes)])),e(`Apply with: cloud cleanup:apply ${N.id} --confirm "DELETE ${N.candidates.length} ${N.kind}"`)})),x.command("cleanup:apply <plan>","Apply an unchanged cleanup preview").option("--confirm <text>","Exact preview confirmation").action(async(t,m)=>Ei((A)=>{let N=A.service.enqueueCleanup(t,String(m.confirm??""));n(`Cleanup queued: ${N.operation.id}`)})),x.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,A)=>Ei((N)=>{let z=N.store.createDrill({projectId:N.controlPlane.project.id,scenario:t,isolatedTarget:m,expectedRpoMinutes:Number(A.rpo),expectedRtoMinutes:Number(A.rto),recoveryPointId:A.recoveryPoint,topologyId:A.topology});n(`Planned isolated drill ${z.id}.`)})),x.command("dr:run <drill>","Queue an isolated disaster-recovery drill").action(async(t)=>Ei((m)=>{let A=m.service.enqueueDrill(t);n(`DR drill queued: ${A.operation.id}`)}))}async function Ha(){try{return(await Oe()).project.region||process.env.AWS_REGION||"us-east-1"}catch{return process.env.AWS_REGION||"us-east-1"}}function Ka(x){x.command("function:list","List all Lambda functions").option("--region <region>","AWS region").action(async(t)=>{i("Lambda Functions");let m=t?.region||await Ha(),A=new R(m);try{let{Functions:N=[]}=await A.listFunctions({MaxItems:50});if(!N.length){e("No functions found");return}c(["Name","Runtime","Memory","Timeout","Last Modified"],N.map((z)=>[z.FunctionName??"-",z.Runtime??"-",z.MemorySize?`${z.MemorySize} MB`:"-",z.Timeout?`${z.Timeout}s`:"-",z.LastModified??"-"]))}catch(N){r(`Failed to list functions: ${N.message}`),process.exitCode=1}}),x.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 A=m?.region||await Ha(),N=new R(A),z=new o("Invoking function...");z.start();try{let U=await N.invoke({FunctionName:t,InvocationType:m?.async?"Event":"RequestResponse",Payload:m?.payload||"{}",LogType:m?.async?"None":"Tail"});if(U.FunctionError){if(z.fail(`Function returned an error (${U.FunctionError})`),U.Payload)e(U.Payload);process.exitCode=1;return}if(z.succeed(`Invoked (status ${U.StatusCode})`),U.Payload)e(`
318
+ `)}async function Ip(){let x=await Oe(),t=y(process.cwd(),x),m=new Pe(t.store),A=new yn(m,[new nt("aws"),new nt("hetzner"),new nt("ssh")]);return{controlPlane:t,store:m,service:A}}async function wi(x){let t=await Ip();try{return await x(t)}finally{t.controlPlane.store.close()}}var Xi=(x,t)=>{let m=x.store.list(x.controlPlane.project.id).find((A)=>A.id===t||A.name===t);if(!m)throw Error(`Server ${t} was not found.`);return m},ar=(x)=>{r(x instanceof Error?x.message:String(x)),process.exitCode=1};function Fa(x,t){let m=Number.parseInt(x??"",10);return Number.isFinite(m)&&m>=0?m:t}function Pp(x="3h"){let t=x.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 is(x,t="%"){return x==null?"unavailable":`${x.toFixed(1)}${t}`}async function xp(x,t){let m=await Oe();if(m.cloud?.provider!=="hetzner")throw Error("server:monitoring currently requires the Hetzner provider.");let A=t.env??process.env.CLOUD_ENV??"production",N=E(m,A),z=await pe(N);if(!z?.serverId)throw Error(`No pinned Hetzner server was found for ${N}.`);if(![z.serverName,String(z.serverId),z.stackName].includes(x))throw Error(`Refusing to read ${x}: project state is pinned to ${z.serverName}.`);let U=or(m),X=new qe({apiToken:sr(U.apiToken,m)}),te=await X.getServer(z.serverId),re=z.publicIp??te.public_net.ipv4?.ip;if(!re)throw Error(`Server ${te.name} has no public IPv4 address.`);let oe=new Date,ae=new Date(oe.getTime()-Pp(t.range)),ve=Math.min(3600,Math.max(60,Fa(t.step,60))),Ce=await _l({client:X,serverId:te.id,cores:Math.max(1,Number(te.server_type.cores)||1),from:ae,to:oe,step:ve,remote:{host:re,user:z.sshUser??U.sshUser,identityFile:U.sshPrivateKeyPath,connectTimeoutSec:15}});if(t.json){console.log(JSON.stringify({schemaVersion:1,server:te.name,result:Ce},null,2));return}c(["Server","Window","CPU average / peak","RAM average / peak","Minimum available","Swap peak"],[[te.name,`${Ce.from} to ${Ce.to}`,`${is(Ce.summary.cpuAveragePercent)} / ${is(Ce.summary.cpuPeakPercent)}`,`${is(Ce.summary.memoryAveragePercent)} / ${is(Ce.summary.memoryPeakPercent)}`,Ce.summary.memoryMinimumAvailableBytes==null?"unavailable":`${(Ce.summary.memoryMinimumAvailableBytes/1073741824).toFixed(2)} GB`,is(Ce.summary.swapPeakPercent)]])}function Vl(x){let t=x.targetType.prices?.find((m)=>m.location===x.location);return{serverId:x.serverId,serverName:x.serverName,location:x.location,from:x.currentType.name,to:x.targetType.name,capacityAvailable:x.capacityAvailable,alreadyComplete:x.alreadyComplete,upgradeDisk:x.upgradeDisk,target:{cores:x.targetType.cores,memoryGb:x.targetType.memory,diskGb:x.targetType.disk,architecture:x.targetType.architecture,cpuType:x.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 Dp(x){let t=Vl(x);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 Ep(x,t,m){let A=await Oe();if(A.cloud?.provider!=="hetzner")throw Error("server:resize currently requires the Hetzner provider.");let N=m.env??process.env.CLOUD_ENV??"production",z=E(A,N),U=await pe(z),X=or(A),te=new qe({apiToken:sr(X.apiToken,A)}),oe=U&&(x===U.serverName||x===String(U.serverId)||x===U.stackName)?await te.getServer(U.serverId):(await te.listServers()).find((_e)=>_e.name===x||String(_e.id)===x);if(!oe)throw Error(`Hetzner server ${x} was not found.`);if(U?.serverId&&U.serverId!==oe.id)throw Error(`Refusing to resize ${x}: project state is pinned to server ${U.serverId}.`);let ae=lt(t),ve=m.upgradeDisk!==!1,Ce=await Kn(te,oe.id,ae,ve);if(m.json)console.log(JSON.stringify({schemaVersion:1,plan:Vl(Ce)},null,2));else Dp(Ce);if(!m.apply){if(!m.json)e(`Preview only. Re-run with --apply --confirm ${oe.name} to execute.`);return}if(m.confirm!==oe.name)throw Error(`Resize requires --confirm ${oe.name}.`);let Re=U?.publicIp??oe.public_net.ipv4?.ip;if(!Re)throw Error(`Server ${oe.name} has no public IPv4 address for verification.`);let we={host:Re,user:U?.sshUser??X.sshUser,identityFile:X.sshPrivateKeyPath,connectTimeoutSec:15,sshTimeoutMs:600000,routeTimeoutMs:20000},ke=await Zo(z),Be=new Date().toISOString(),Ae={schemaVersion:1,operationId:ke?.targetType===ae&&ke.serverId===oe.id?ke.operationId:crypto.randomUUID(),stackName:z,serverId:oe.id,serverName:oe.name,sourceType:Ce.currentType.name,targetType:ae,upgradeDisk:ve,phase:"planning",status:"running",attempts:ke?.targetType===ae?ke.attempts:0,startedAt:ke?.targetType===ae?ke.startedAt:Be,updatedAt:Be,manifest:ke?.targetType===ae?ke.manifest:void 0};await ut(Ae);let Ie=Fa(m.pollSeconds,900)*1000,Te=Fa(m.maxWaitMinutes,0)*60000,Xe=Date.now();for(;;){let _e=await Kn(te,oe.id,ae,ve);if(!_e.capacityAvailable&&!_e.alreadyComplete){if(Ae={...Ae,phase:"waiting-capacity",status:"waiting-capacity",updatedAt:new Date().toISOString()},await ut(Ae),!m.wait){if(!m.json)e(`${ae} has no capacity in ${_e.location}. The server remains running and unchanged. Use --wait to poll safely.`);return}if(Te>0&&Date.now()-Xe>=Te)throw Error(`Timed out waiting for ${ae} capacity in ${_e.location}.`);if(!m.json)e(`Waiting ${Math.round(Ie/1000)} seconds for ${ae} capacity...`);await Bun.sleep(Ie);continue}let Ze=await es(z);try{let et=await(async()=>{try{return Ae={...Ae,status:"running",attempts:Ae.attempts+1,updatedAt:new Date().toISOString()},await ut(Ae),await Go({client:te,serverId:oe.id,targetType:ae,upgradeDisk:ve,resumeFromOff:Ae.phase==="shutting-down"||Ae.phase==="resizing"||Ae.phase==="powering-on"||Ae.phase==="verifying",manifest:Ae.manifest,hooks:{preflight:async()=>{let At=await Ko(we);return Ae={...Ae,manifest:At,updatedAt:new Date().toISOString()},await ut(Ae),At},afterBoot:async(At,Ft,St,Et)=>Jo(we,Ft,St,Et),onPhase:async(At,Ft)=>{if(Ae={...Ae,phase:At,status:At==="waiting-capacity"?"waiting-capacity":At==="complete"?"completed":"running",updatedAt:new Date().toISOString()},await ut(Ae),!m.json)e(`${At}${Ft?` ${JSON.stringify(Ft)}`:""}`)}}})}finally{await Ze()}})();if(Ae={...Ae,status:et.status==="completed"||et.status==="already-complete"?"completed":et.status==="recovered"?"recovered":"waiting-capacity",lastError:et.status==="recovered"?et.error:void 0,updatedAt:new Date().toISOString()},await ut(Ae),m.json)console.log(JSON.stringify({schemaVersion:1,result:et},null,2));else if(et.status==="completed"||et.status==="already-complete")n(`${oe.name} is on ${ae} and passed full post-boot verification.`);else if(et.status==="recovered"&&et.retryable&&m.wait){e(`Capacity race recovered safely. Waiting ${Math.round(Ie/1000)} seconds before retry.`),await Bun.sleep(Ie);continue}else if(et.status==="recovered")throw Error(`Resize did not complete and the original server was recovered: ${et.error}`);return}catch(et){throw Ae={...Ae,status:"failed",lastError:et instanceof Error?et.message:String(et),updatedAt:new Date().toISOString()},await ut(Ae),et}}}async function Tp(x,t){let m=await Oe();if(m.cloud?.provider!=="hetzner")throw Error("server:optimize currently requires the Hetzner provider.");let A=t.env??process.env.CLOUD_ENV??"production",N=E(m,A),z=await pe(N);if(!z?.serverId)throw Error(`No pinned Hetzner server was found for ${N}.`);if(![z.serverName,String(z.serverId),z.stackName].includes(x))throw Error(`Refusing to optimize ${x}: project state is pinned to ${z.serverName}.`);let U=or(m),X=new qe({apiToken:sr(U.apiToken,m)}),te=await X.getServer(z.serverId),re=z.publicIp??te.public_net.ipv4?.ip;if(!re)throw Error(`Server ${te.name} has no public IPv4 address.`);let oe=Fi(m);if(t.json)console.log(JSON.stringify({schemaVersion:1,server:te.name,plan:oe},null,2));else c(["Server","Firewall ports","Monitoring","Auto updates","Swap","SSH passwords"],[[te.name,oe.firewallPorts.join(","),oe.monitoring?"on":"off",oe.autoUpdates?"on":"off",`${oe.swapGb} GB`,"disabled"]]);if(!t.apply){if(!t.json)e(`Preview only. Re-run with --apply --confirm ${te.name} to execute.`);return}if(t.confirm!==te.name)throw Error(`Optimization requires --confirm ${te.name}.`);let ae={host:re,user:z.sshUser??U.sshUser,identityFile:U.sshPrivateKeyPath,connectTimeoutSec:15},ve=await qr(ae),Ce=ve.routeProbes.filter((St)=>!St.ok),Re=m.infrastructure?.compute,we=Y(Re)?new Set(["nginx.service","bun-gateway.service","ts-cloud-nginx.service"]):new Set,ke=ve.failedUnits.filter((St)=>!we.has(St));if(ke.length>0)throw Error(`Preflight found failed services: ${ke.join(", ")}`);if(Ce.length>0)throw Error(`Preflight found unhealthy routes: ${Ce.map((St)=>St.domain).join(", ")}`);let Be=Y(Re)?[]:Object.values(m.sites??{}).flatMap((St)=>typeof St?.port==="number"&&![80,443].includes(St.port)?[St.port]:[]),Ae=zr({allowSsh:Re?.allowSsh!==!1,sitePorts:Be,allowedPorts:Re?.firewall?.allowedPorts}),Ie=(z.firewallId?(await X.listFirewalls()).find((St)=>St.id===z.firewallId):void 0)??(await X.listFirewalls()).find((St)=>St.name===`${m.project.slug}-${A}-app-fw`);if(!Ie)throw Error(`Managed firewall for ${te.name} was not found.`);let Te=await X.setFirewallRules(Ie.id,Ae);await Promise.all(Te.map((St)=>X.waitForAction(St.id))),await Xo(m,ae);let[Xe,_e]=await Promise.all([qr(ae),Qo(ae)]),Ze=Yo(oe,_e),et=_a(ve,Xe),At=Xe.routeProbes.filter((St)=>!St.ok);if(et.stoppedServices.length>0)Ze.push(`services stopped: ${et.stoppedServices.join(", ")}`);if(et.changedRouteFragments.length>0)Ze.push(`route fragments changed: ${et.changedRouteFragments.join(", ")}`);if(et.missingRouteIds.length>0)Ze.push(`routes disappeared: ${et.missingRouteIds.join(", ")}`);if(et.changedReleaseLinks.length>0)Ze.push(`release links changed: ${et.changedReleaseLinks.join(", ")}`);if(et.missingData.length>0)Ze.push(`databases or volumes disappeared: ${et.missingData.join(", ")}`);if(At.length>0)Ze.push(`unhealthy routes: ${At.map((St)=>St.domain).join(", ")}`);if(Ze.length>0)throw Error(`Host optimization verification failed: ${Ze.join("; ")}`);let Ft={serverId:te.id,serverName:te.name,services:Xe.runningServices.length,routes:Xe.routeIds.length,healthyDomains:Xe.routeProbes.length,releaseLinks:Xe.releaseLinks.length,databasesAndVolumes:Xe.dataCatalog.length,report:_e};if(t.json)console.log(JSON.stringify({schemaVersion:1,result:Ft},null,2));else n(`${te.name} passed full host, rpx route, service, release, and data verification.`)}async function Np(x,t,m){let A=await Oe(),N=m.env??"production",z=E(A,N);await wi(async(U)=>{let X=Xi(U,x),te=U.store.list(U.controlPlane.project.id,!0),re=X.provider==="hetzner"&&X.providerId?(()=>{try{let Ie=or(A);return new qe({apiToken:sr(Ie.apiToken,A)})}catch{return null}})():null,oe=X.providerId?Number(X.providerId):Number.NaN,ae=await pe(z),ve=ae!=null&&ae.serverId===oe,Ce=new kr,Re=X.trustState==="pinned",we={takenNames:async()=>{let Ie=te.map((Te)=>Te.name);if(re)Ie.push(...(await re.listServers()).map((Te)=>Te.name));return Ie},inventoryName:()=>U.store.get(X.id)?.name??X.name,renameInventory:(Ie)=>{U.store.update(X.id,{name:Ie})},...re&&Number.isFinite(oe)?{providerName:async()=>(await re.getServer(oe)).name,renameProvider:async(Ie)=>{await re.renameServer(oe,Ie)}}:{},...ve?{stateName:async()=>(await pe(z))?.serverName,writeStateName:async(Ie)=>{let Te=await pe(z);if(Te)await dt(z,{...Te,serverName:Ie})}}:{},...Re?{remoteHostname:async()=>{let Ie=await Ce.exec(X,"hostname");return Ie.code===0?Ie.stdout.trim():void 0},setRemoteHostname:async(Ie)=>{let Te=await Ce.exec(X,Ul(Ie));if(Te.code!==0)throw Error(Te.stderr.trim()||`hostname exited ${Te.code}`)}}:{}},ke=await zl(X.name,t,we),Be=await ks(ke);if(!m.apply){let Ie=[re?"":"provider record (no provider id or no API token)",ve?"":"local state pin (none points at this server)",Re?"":"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:X.name,to:t},notCovered:Ie,steps:Be.map((Te)=>({id:Te.step.id,title:Te.step.title,state:Te.state,change:Te.step.change,reason:Te.reason}))},null,2));return}for(let Te of Is(ke,Be))console.log(Te);for(let Te of Ie)console.log(` not covered: ${Te}`);console.log(" Re-run with --apply to perform it.");return}let Ae=await xs(ke,Be,{log:(Ie)=>e(Ie),audit:(Ie)=>U.controlPlane.store.appendEvent({projectId:U.controlPlane.project.id,resourceId:X.resourceId,type:`${Ie.operation}.${Ie.step}.${Ie.state}`,level:Ie.state==="failed"?"error":"info",payload:{target:Ie.target,renameTo:t,...Ie.error?{error:Ie.error}:{}}})});if(m.json)console.log(JSON.stringify({schemaVersion:1,outcome:Ae},null,2));if(!Ae.success){let Ie=Ae.steps.find((Te)=>Te.state==="failed");throw Error(`${ke.operation} stopped at '${Ie?.title}': ${Ie?.error}. Earlier steps stayed applied; re-run the same command to continue from here.`)}if(!m.json)n(`Renamed ${X.name} to ${t}.`)})}function Ba(x){x.command("capabilities [server]","Show provider and target operation support").option("--json","Print structured JSON").action(async(t,m)=>{try{await wi(async(A)=>{let N=t?[Xi(A,t)]:A.store.list(A.controlPlane.project.id),z=N.flatMap((U)=>Object.entries(U.capabilities).map(([X,te])=>({server:U.name,provider:U.provider,action:X,...te})));if(m.json)console.log(JSON.stringify({schemaVersion:1,targets:z},null,2));else c(["Server","Provider","Action","Support","Explanation"],z.map((U)=>[U.server,U.provider,U.action,U.supported?"supported":"unsupported",U.reason??"—"]))})}catch(A){ar(A)}}),x.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,A)=>{try{await Ep(t,m,A)}catch(N){ar(N)}}),x.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 Tp(t,m)}catch(A){ar(A)}}),x.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 xp(t,m)}catch(A){ar(A)}}),x.command("server:list","List fleet servers").option("--json","Print structured JSON").action(async(t)=>{try{await wi(async(m)=>{let A=m.store.list(m.controlPlane.project.id);if(t.json)console.log(JSON.stringify(A,null,2));else c(["Name","Provider ID","Provider / region","Status / trust","Roles","CPU / memory","Heartbeat"],A.map((N)=>[N.name,N.providerId??"—",`${N.provider} / ${N.region??"external"}`,`${N.status} / ${N.trustState}`,N.roles.join(","),`${N.capacity.cpu??0} / ${N.capacity.memoryBytes??0}`,N.heartbeatAt??"never"]))})}catch(m){ar(m)}}),x.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 wi(async(A)=>{let N=m.provider;if(!["aws","hetzner","ssh"].includes(N)||!m.endpoint)throw Error("A supported --provider and --endpoint are required; provisioning and enrollment are separate operations.");let z=A.service.enroll({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,name:t,provider:N,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((U)=>U.split("=")).filter((U)=>U[0]&&U[1]))});n(`Enrolled ${z.name} as ${z.id}; no remote mutation was performed.`)})}catch(A){ar(A)}}),x.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 wi(async(A)=>{let N=Xi(A,t);if(N=await A.service.test(N.id),N.trustState==="rotation_pending"){if(!m.acceptHostKey)throw Error(`Host key changed to ${N.pendingHostKey}; review it first.`);N=A.service.reviewHostKey(N.id,m.acceptHostKey)}let z=await A.service.validate(N.id);if(m.json)console.log(JSON.stringify(z.validation,null,2));else c(["Severity","Code","Finding","Remediation"],(z.validation?.findings??[]).map((U)=>[U.severity,U.code,U.message,U.remediation??"—"]));if(!z.validation?.valid)process.exitCode=1})}catch(A){ar(A)}}),x.command("server:bootstrap <name>","Preview or queue bootstrap").option("--apply","Apply reviewed plan").action(async(t,m)=>{try{await wi(async(A)=>{let N=A.service.bootstrap(Xi(A,t).id,!!m.apply);if(N.preview)c(["Step"],N.steps.map((z)=>[z]));else n(`Bootstrap queued: ${N.operation?.id}`)})}catch(A){ar(A)}}),x.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,A)=>{try{await Np(t,m,A)}catch(N){ar(N)}}),x.command("server:drain <name>","Drain without terminating").option("--complete","Mark movement complete").action(async(t,m)=>{try{await wi(async(A)=>n(A.service.drain(Xi(A,t).id,!!m.complete).status))}catch(A){ar(A)}}),x.command("server:uncordon <name>","Return a server to scheduling").action(async(t)=>{try{await wi(async(m)=>{m.service.uncordon(Xi(m,t).id),n(`Uncordoned ${t}.`)})}catch(m){ar(m)}}),x.command("server:archive <name>","Archive inventory without termination").option("--confirm <name>","Exact name").action(async(t,m)=>{try{await wi(async(A)=>{A.service.archive(Xi(A,t).id,m.confirm??""),n(`Archived ${t}; provider infrastructure was not terminated.`)})}catch(A){ar(A)}}),x.command("server:ssh <name>","Open strict SSH using the enrolled endpoint").action(async(t)=>{try{await wi(async(m)=>{let A=Xi(m,t);if(A.trustState!=="pinned")throw Error("Validate and pin the server host key first.");let N=Bun.spawn(["ssh","-p",String(A.sshPort),"-o","StrictHostKeyChecking=yes",`${A.sshUser}@${A.endpoint}`],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}),z=await N.exited;if(z)process.exitCode=z})}catch(m){ar(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"]])x.command(t,m).action(async(...A)=>Dr(t.split(" ")[0],{target:String(A[0]??""),nextAction:"Use the dashboard capability view or the corresponding runtime, release, firewall, backup, or maintenance service."}))}import{existsSync as Wl,mkdirSync as Op,readFileSync as Lp,writeFileSync as jp}from"node:fs";import{join as ql,resolve as Fp}from"node:path";var za=(x)=>{r(x instanceof Error?x.message:String(x)),process.exitCode=1};async function Bp(){try{return await Oe()}catch{return}}function Mp(x){if(x===void 0)return;let t=Number(x);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`--port must be a port number, not '${x}'.`);return t}function Gl(x,t,m){let A=x?.ssh?.hosts?.find((N)=>N.host===t);return new ur({hosts:t?[A??{host:t}]:x?.ssh?.hosts,user:m.user,port:Mp(m.port),privateKeyPath:m.key,hostKey:x?.ssh?.hostKey,sudo:x?.ssh?.sudo,profile:m.profile??x?.ssh?.profile,publicIp:x?.ssh?.publicIp,lan:x?.ssh?.lan})}var Hl=1073741824;function Va(x){x.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 A=await Bp(),N=Gl(A,t,m),z=t??N.targetHost(),U=await N.ensureHostKey(z),{facts:X,findings:te}=await N.preflight(z);if(m.json)console.log(JSON.stringify({schemaVersion:1,host:z,hostKeyFingerprint:U.fingerprint,facts:X,findings:te},null,2));else{if(i(`Preflight ${z}`),U.pinnedNow)e(`Pinned host key ${U.fingerprint}`);if(e(`${X.os} on ${X.arch}, ${X.cpuCores} cores, ${(X.memoryBytes/Hl).toFixed(1)} GiB memory, ${(X.diskFreeBytes/Hl).toFixed(1)} GiB free`),X.piModel)e(X.piModel);if(X.lanIp)e(`LAN address ${X.lanIp}`);if(console.log(Hr(te)),dr(te))r("The host is not ready; fix the errors above and run the preflight again.");else n("The host is ready to adopt.")}if(dr(te))process.exitCode=1}catch(A){za(A)}}),x.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 Oe(),A=t.env??"production";if(t.dryRun){let U=lr(m),[X]=U.hosts;console.log(cr({config:m,environment:A,profile:U.profile,sudoUser:X.user!=="root"?X.user:void 0,lan:U.lan}));return}let N=Gl(m,void 0,{});i(`Adopt ${N.targetHost()}`);let z=await N.provisionComputeInfrastructure({config:m,environment:A});n(`${z.appPublicIp} is bootstrapped; deploy with: cloud deploy --env ${A}`)}catch(m){za(m)}}),x.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 Ui))throw Error(`--os must be raspberry-pi-os or ubuntu, not '${m}'.`);let A=await Oe(),N=t.env??"production",z=lr(A,{hosts:A.ssh?.hosts?.length?void 0:[{host:t.hostname}],profile:m==="raspberry-pi-os"?"raspberry-pi":void 0}),U=t.user??(A.ssh?.hosts?.[0]?.user??"pi"),X=Vr(t.key??"~/.ssh/id_ed25519.pub");if(!Wl(X))throw Error(`Public key not found at ${X}; pass --key <path to a .pub file>.`);let te=Lp(X,"utf8").trim(),re;if(t.wifiSsid){let Re=t.wifiPassphraseEnv??"WIFI_PASSWORD",we=process.env[Re];if(!we)throw Error(`--wifi-ssid needs the passphrase in $${Re} (set it in the environment, not on the command line).`);if(!t.wifiCountry)throw Error("--wifi-country <cc> is required with --wifi-ssid.");re={ssid:t.wifiSsid,passphrase:we,country:t.wifiCountry}}let oe=cr({config:A,environment:N,profile:z.profile,sudoUser:U!=="root"?U:void 0,lan:z.lan}),ae=Bi({hostname:t.hostname,user:U,publicKey:te,timezone:t.timezone,locale:t.locale,wifi:re},oe,{os:m}),ve=Fp(t.out);Op(ve,{recursive:!0});let Ce=Object.keys(ae.files).filter((Re)=>Wl(ql(ve,Re)));if(Ce.length>0&&!t.force)throw Error(`${Ce.join(", ")} already exist in ${ve}; pass --force to overwrite.`);for(let[Re,we]of Object.entries(ae.files))jp(ql(ve,Re),we,{mode:384});i(`First boot for ${t.hostname}`),n(`Wrote ${Object.keys(ae.files).join(", ")} to ${ve}`),console.log(ae.instructions)}catch(m){za(m)}})}async function _p(){let x=await Oe(),t=y(process.cwd(),x),m=new Sn(t.store),A=new bn(m,new Pe(t.store));return{controlPlane:t,store:m,service:A}}async function os(x){let t=await _p();try{await x(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var Es=(x)=>Object.fromEntries(String(x??"").split(",").map((t)=>t.split("=")).filter((t)=>t[0]&&t[1])),Kl=(x)=>Object.fromEntries(Object.entries(Es(x)).map(([t,m])=>[t,Number(m)]));function Wa(x){x.command("pool:list","List capacity pools and reservations").option("--json","Print structured JSON").action(async(t)=>os((m)=>{let A=m.store.listPools(m.controlPlane.project.id);if(t.json)console.log(JSON.stringify(A,null,2));else c(["Name","Purpose","Backend","Region / arch","Status","Capacity","Concurrency"],A.map((N)=>[N.name,N.purpose,N.backend,`${N.region??"any"} / ${N.architecture??"any"}`,N.status,JSON.stringify(N.capacity),String(N.concurrency)]))})),x.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)=>os((A)=>{let{purpose:N,backend:z}=m;if(!["application","build","worker","monitoring","backup"].includes(N)||!["server","ecs","asg"].includes(z))throw Error("Unsupported pool purpose or backend.");let U=A.store.createPool({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,name:t,purpose:N,backend:z,region:m.region,architecture:m.arch,labels:Es(m.labels),requiredServerLabels:Es(m.selector),toleratedTaints:[],capacity:{cpu:0,memoryBytes:0,diskBytes:0,gpu:0,...Kl(m.capacity)},maxWorkloads:Number(m.max),costWeight:1,concurrency:Number(m.concurrency),ephemeralWorkspaces:!0,allowProductionSecrets:!1,status:"active"});n(`Created ${U.name} (${U.id}).`)})),x.command("pool:add-server <pool> <server>","Attach an enrolled server to a pool").action(async(t,m)=>os((A)=>{let N=A.store.listPools(A.controlPlane.project.id).find((U)=>U.id===t||U.name===t),z=new Pe(A.controlPlane.store).list(A.controlPlane.project.id).find((U)=>U.id===m||U.name===m);if(!N||!z)throw Error("Pool or server was not found.");A.store.addMember(N.id,z.id),n(`Attached ${z.name} to ${N.name}.`)})),x.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)=>os((m)=>{let A=m.service.explain(m.controlPlane.project.id,{purpose:t.purpose,resources:Kl(t.resources),region:t.region,architecture:t.arch,labels:Es(t.labels)});if(t.json)console.log(JSON.stringify(A,null,2));else c(["Pool","Server","Eligible","Available","Fit / spread / cost","Reasons"],A.map((N)=>[N.poolName,N.serverId??"provider-managed",N.eligible?"yes":"no",JSON.stringify(N.available),`${N.score.fit.toFixed(2)} / ${N.score.spread} / ${N.score.cost}`,N.reasons.join("; ")||"eligible"]))})),x.command("pool:drain <pool>","Move stateless placements and block stateful placements").action(async(t)=>os((m)=>{let A=m.store.listPools(m.controlPlane.project.id).find((z)=>z.id===t||z.name===t);if(!A)throw Error("Pool was not found.");let N=m.service.drainPool(A.id);n(`Draining ${A.name}: moved ${N.moved.length}, blocked ${N.blocked.length} stateful placement(s).`)}))}async function zp(){let x=await Oe(),t=y(process.cwd(),x),m=new vn(t.store),A=new Tn(m);return{controlPlane:t,store:m,service:A}}async function mo(x){let t=await zp();try{await x(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var ss=(x,t)=>{let m=x.store.list(x.controlPlane.project.id).find((A)=>A.id===t||A.name===t);if(!m)throw Error("Regional topology was not found.");return m},Up=(x)=>x.split(",").map((t,m)=>{let[A,N="aws"]=t.split(":");return{region:String(A),provider:N,role:m===0?"primary":"secondary"}}),Vp=(x)=>{let t=new Set(["s3","dynamodb","secrets"]),m=x.split(",").map((N)=>N.trim()),A=m.find((N)=>!t.has(N));if(A)throw Error(`Unsupported replication kind: ${A}`);return m};function qa(x){x.command("region:list","List regional topology, traffic, and replication health").option("--json","Print structured JSON").action(async(t)=>mo((m)=>{let A=m.store.list(m.controlPlane.project.id).map((N)=>({topology:N,targets:m.store.targets(N.id),channels:m.store.channels(N.id),route:m.store.route(N.id),executions:m.store.executions(N.id)}));if(t.json)console.log(JSON.stringify(A,null,2));else c(["Name","Hostname","Active","Status","Revision","Replication"],A.map((N)=>[N.topology.name,N.topology.hostname,N.topology.activeRegion,N.topology.status,N.topology.revision??"—",`${N.channels.filter((z)=>z.status==="in_sync").length}/${N.channels.length} in sync`]))})),x.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)=>mo((A)=>{if(!m.hostname||!m.regions)throw Error("--hostname and --regions are required.");let N=A.service.create({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,name:t,hostname:m.hostname,regions:Up(m.regions),trafficPolicy:"active_passive",dataPolicy:{replicate:Vp(String(m.replicate)),maxLagSeconds:Number(m.maxLag),retainOnDestroy:!0},dnsProvider:"route53",cdnEnabled:!!m.cdn,wafEnabled:!!m.waf});n(`Created ${N.name} (${N.id}); run region:rollout with an immutable manifest.`)})),x.command("region:plan <name> <operation>","Preview exact regional execution steps").option("--target <region>","Failover or failback region").action(async(t,m,A)=>mo((N)=>c(["Step"],N.service.plan(ss(N,t).id,m,A.target).map((z)=>[z])))),x.command("region:rollout <name> <revision>","Queue secondary-first regional stack rollout").option("--manifest <file>","Regional stack manifest JSON").action(async(t,m,A)=>mo(async(N)=>{let z=A.manifest?await Bun.file(A.manifest).json():{},U=N.service.enqueue({topologyId:ss(N,t).id,kind:"rollout",revision:m,manifest:z});n(`Regional rollout queued: ${U.operation.id}`)})),x.command("region:failover <name> <region>","Queue health and RPO-gated failover").action(async(t,m)=>mo((A)=>{let N=A.service.enqueue({topologyId:ss(A,t).id,kind:"failover",targetRegion:m});n(`Failover queued: ${N.operation.id}`)})),x.command("region:failback <name> <region>","Queue health and RPO-gated failback").action(async(t,m)=>mo((A)=>{let N=A.service.enqueue({topologyId:ss(A,t).id,kind:"failback",targetRegion:m});n(`Failback queued: ${N.operation.id}`)})),x.command("region:destroy <name>","Drain traffic and delete regional stacks while retaining data").option("--confirm <name>","Exact topology name").action(async(t,m)=>mo((A)=>{let N=A.service.enqueue({topologyId:ss(A,t).id,kind:"destroy",confirmation:m.confirm});n(`Regional teardown queued: ${N.operation.id}; replicated data remains retained.`)}))}var as="0.16.32";async function qp(){let x=await Oe(),t=y(process.cwd(),x),m=new En(t.store),A=new Cn(m),N=["releases","artifacts","operations","logs","previews","snapshots","orphaned_resources"].map((z)=>new wn(z,t.store,t.project.id,t.organization.id));return{controlPlane:t,store:m,service:A,cleanup:N}}async function Ei(x){let t=await qp();try{await x(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}function Ga(x){x.command("maintenance:list","List updates, campaigns, cleanup plans, and DR drills").option("--json","Print structured JSON").action(async(t)=>Ei((m)=>{let A={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)},N=[...A.manifests.map((z)=>["update",z.version,z.compatibility.compatible?"compatible":"blocked",z.compatibility.reasons.join("; ")||z.channel]),...A.campaigns.map((z)=>["upgrade",z.id,z.status,z.currentStage??z.fromVersion]),...A.cleanups.map((z)=>["cleanup",z.kind,z.status,`${z.candidates.length} items / ${z.estimatedBytes} bytes`]),...A.drills.map((z)=>["DR drill",z.scenario,z.status,z.isolatedTarget])];if(t.json)console.log(JSON.stringify(A,null,2));else c(["Area","Name / version","Status","Detail"],N)})),x.command("maintenance:key:add <name> <pem>","Trust an Ed25519 update signing key").action(async(t,m)=>Ei(async(A)=>{let N=A.store.addTrustedKey({organizationId:A.controlPlane.organization.id,name:t,publicKeyPem:await Bun.file(m).text()});n(`Trusted ${N.name}: ${N.fingerprint}`)})),x.command("maintenance:manifest <document> <signature> <key>","Verify and import a signed update manifest").action(async(t,m,A)=>Ei(async(N)=>{let z=await Bun.file(t).json(),U=(await Bun.file(m).text()).trim(),X=N.store.importManifest({organizationId:N.controlPlane.organization.id,keyId:A,document:z,signature:U,current:{schemaVersion:N.controlPlane.store.health().schemaVersion,currentVersion:as,platform:process.platform,architecture:process.arch}});n(`Verified ${X.version}; ${X.compatibility.compatible?"compatible":"blocked: "+X.compatibility.reasons.join("; ")}`)})),x.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,A)=>Ei((N)=>{let z=N.store.createWindow({projectId:N.controlPlane.project.id,name:t,schedule:m,timezone:A.timezone,durationMinutes:Number(A.minutes),allowedOperations:["platform.upgrade"],requireApproval:!0});n(`Created ${z.name} (${z.id}).`)})),x.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,A)=>Ei((N)=>{let z=new Pe(N.controlPlane.store).list(N.controlPlane.project.id).filter((te)=>te.status==="ready").map((te)=>({serverId:te.id,target:te.name,previousVersion:te.bootstrapVersion??as}));if(!z.length)z.push({target:"control-plane",previousVersion:as});let U=N.store.createCampaign({projectId:N.controlPlane.project.id,manifestId:t,fromVersion:as,strategy:A.strategy==="rolling"?"rolling":"canary",batchSize:Number(A.batch),healthGate:{kind:"platform"},backupId:m,targets:z}),X=N.service.enqueueCampaign(U.id);n(`Staged upgrade queued: ${X.operation.id}`)})),x.command("cleanup:preview <kind>","Discover a bounded cleanup candidate set without deleting").option("--older-than <days>","Minimum age",{default:"30"}).action(async(t,m)=>Ei(async(A)=>{let N=await A.service.previewCleanup({projectId:A.controlPlane.project.id,kind:t,criteria:{olderThanDays:Number(m.olderThan)}},A.cleanup);c(["ID","Candidate","Bytes"],N.candidates.map((z)=>[z.id,z.label,String(z.bytes)])),e(`Apply with: cloud cleanup:apply ${N.id} --confirm "DELETE ${N.candidates.length} ${N.kind}"`)})),x.command("cleanup:apply <plan>","Apply an unchanged cleanup preview").option("--confirm <text>","Exact preview confirmation").action(async(t,m)=>Ei((A)=>{let N=A.service.enqueueCleanup(t,String(m.confirm??""));n(`Cleanup queued: ${N.operation.id}`)})),x.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,A)=>Ei((N)=>{let z=N.store.createDrill({projectId:N.controlPlane.project.id,scenario:t,isolatedTarget:m,expectedRpoMinutes:Number(A.rpo),expectedRtoMinutes:Number(A.rto),recoveryPointId:A.recoveryPoint,topologyId:A.topology});n(`Planned isolated drill ${z.id}.`)})),x.command("dr:run <drill>","Queue an isolated disaster-recovery drill").action(async(t)=>Ei((m)=>{let A=m.service.enqueueDrill(t);n(`DR drill queued: ${A.operation.id}`)}))}async function Ha(){try{return(await Oe()).project.region||process.env.AWS_REGION||"us-east-1"}catch{return process.env.AWS_REGION||"us-east-1"}}function Ka(x){x.command("function:list","List all Lambda functions").option("--region <region>","AWS region").action(async(t)=>{i("Lambda Functions");let m=t?.region||await Ha(),A=new R(m);try{let{Functions:N=[]}=await A.listFunctions({MaxItems:50});if(!N.length){e("No functions found");return}c(["Name","Runtime","Memory","Timeout","Last Modified"],N.map((z)=>[z.FunctionName??"-",z.Runtime??"-",z.MemorySize?`${z.MemorySize} MB`:"-",z.Timeout?`${z.Timeout}s`:"-",z.LastModified??"-"]))}catch(N){r(`Failed to list functions: ${N.message}`),process.exitCode=1}}),x.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 A=m?.region||await Ha(),N=new R(A),z=new o("Invoking function...");z.start();try{let U=await N.invoke({FunctionName:t,InvocationType:m?.async?"Event":"RequestResponse",Payload:m?.payload||"{}",LogType:m?.async?"None":"Tail"});if(U.FunctionError){if(z.fail(`Function returned an error (${U.FunctionError})`),U.Payload)e(U.Payload);process.exitCode=1;return}if(z.succeed(`Invoked (status ${U.StatusCode})`),U.Payload)e(`
319
319
  Response:`),e(U.Payload);if(U.LogResult)e(`
320
320
  Logs:`),e(Buffer.from(U.LogResult,"base64").toString("utf-8"))}catch(U){z.fail(`Invocation failed: ${U.message}`),process.exitCode=1}}),x.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 A=m?.region||await Ha(),N=new ie(A),z=t.startsWith("/aws/lambda/")?t:`/aws/lambda/${t}`,U=Number(m?.since??15),X=(te)=>{for(let re of te){let oe=re.timestamp?new Date(re.timestamp).toISOString():"";e(`${oe} ${(re.message??"").trimEnd()}`)}};try{let te=Date.now()-U*60000,{events:re=[]}=await N.filterLogEvents({logGroupName:z,startTime:te,filterPattern:m?.filter,limit:200});if(X(re),re.length)te=(re[re.length-1].timestamp??te)+1;if(m?.tail){e(`
321
321
  (streaming — Ctrl+C to stop)`);for(;;){await new Promise((ae)=>setTimeout(ae,3000));let{events:oe=[]}=await N.filterLogEvents({logGroupName:z,startTime:te,filterPattern:m?.filter,limit:200});if(oe.length)X(oe),te=(oe[oe.length-1].timestamp??te)+1}}}catch(te){r(`Failed to read logs: ${te.message}`),process.exitCode=1}})}async function Jl(x){let t=Bun.spawn(["docker",...x],{stdin:"inherit",stdout:"inherit",stderr:"pipe"}),[m,A]=await Promise.all([t.exited,new Response(t.stderr).text()]);return{ok:m===0,exitCode:m,command:["docker",...x].join(" "),stderr:A.trim()}}function Ja(x){x.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 A=await Jl(m);if(t.json)console.log(JSON.stringify(A));if(!A.ok)r(A.stderr||`docker exited ${A.exitCode}`),process.exitCode=A.exitCode;else n(`Built ${t.tag??"latest"}.`)}),x.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 Jl(["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}.`)}),x.command("container:deploy","Deploy a container image").action(async()=>Dr("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 Ya(x){x.command("domain:list","List all domains").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").action(async(t)=>{i("Domains");try{let m=gi(t?.provider),A=Wn(t?.provider)?.provider||"unknown",N=new o(`Fetching domains from ${A}...`);N.start();let z=await m.listDomains();if(N.succeed(`Found ${z.length} domain(s)`),z.length===0){e("No domains found in this provider");return}let U=z.map((X)=>[X,"Active","-",A.charAt(0).toUpperCase()+A.slice(1)]);c(["Domain","Status","SSL","Provider"],U)}catch(m){r(`Failed to list domains: ${m instanceof Error?m.message:String(m)}`)}}),x.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 A=gi(m?.provider),N=Wn(m?.provider)?.provider||"unknown",z=new o(`Checking if ${N} can manage ${t}...`);if(z.start(),await A.canManageDomain(t))z.succeed(`Domain ${t} is already available in ${N}`),e(`
@@ -370,39 +370,39 @@ ${oe.length} difference(s)`)}catch(A){r(`Comparison failed: ${A.message}`),proce
370
370
  Deploy assets now?`,!0)){e("Deployment cancelled");return}let oe=new g(A),ae=new o("Uploading assets to S3...");ae.start(),await oe.sync({source:N,bucket:z,prefix:U,delete:X,cacheControl:te,acl:"public-read"}),ae.succeed("Assets deployed successfully!");let Ce=(await oe.getBucketSize(z,U)/1024/1024).toFixed(2);n(`
371
371
  Deployment complete!`),e(`Total size: ${Ce} MB`),e(`
372
372
  Assets URL: https://${z}.s3.${A}.amazonaws.com${U?`/${U}`:""}`)}catch(m){r(`Deployment failed: ${m.message}`)}}),x.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 N=(t?.paths||"/*").split(",").map((oe)=>oe.trim()),z=t?.wait||!1;if(e(`Distribution: ${m}`),e(`Paths: ${N.join(", ")}`),!await d(`
373
- Invalidate cache now?`,!0)){e("Invalidation cancelled");return}let X=new w,te=new o("Creating invalidation...");te.start();let re=await X.invalidatePaths(m,N);if(te.succeed("Invalidation created"),n(`
373
+ Invalidate cache now?`,!0)){e("Invalidation cancelled");return}let X=new C,te=new o("Creating invalidation...");te.start();let re=await X.invalidatePaths(m,N);if(te.succeed("Invalidation created"),n(`
374
374
  Invalidation ID: ${re.Id}`),e(`Status: ${re.Status}`),e(`Created: ${new Date(re.CreateTime).toLocaleString()}`),z){let oe=new o("Waiting for invalidation to complete...");oe.start(),await X.waitForInvalidation(m,re.Id),oe.succeed("Invalidation completed!")}else e(`
375
- Invalidation is in progress. Use --wait to wait for completion.`)}catch(m){r(`Invalidation failed: ${m.message}`)}})}function Ac(x){for(let[t,m]of[["team:add <email> <role>","Invite an organization member"],["team:list","List organization members"],["team:remove <email>","Revoke organization membership"]])x.command(t,m).action(async(...A)=>Dr(t.split(" ")[0],{target:String(A[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 Dm,execSync as Oc}from"node:child_process";import{copyFileSync as gs,existsSync as fr,mkdirSync as vd,readFileSync as Nc,writeFileSync as Em}from"node:fs";import{tmpdir as bd}from"node:os";import{dirname as Tm,join as wd,resolve as Nm}from"node:path";import{existsSync as kc,readdirSync as md,statSync as Rc}from"node:fs";import{join as Us}from"node:path";var $m=new Set([".DS_Store","Thumbs.db",".gitkeep",".keep"]);function fd(x){return $m.has(x)}function gd(x,t=0){if(t>8)return!0;let m;try{m=md(x)}catch{return!1}for(let A of m){if(fd(A))continue;let N=Us(x,A),z;try{z=Rc(N)}catch{continue}if(z.isFile())return!0;if(z.isDirectory()&&gd(N,t+1))return!0}return!1}function Cm(x){let t;try{t=md(x)}catch{return[]}let m=[];for(let A of t){if(fd(A))continue;let N=Us(x,A);try{if(Rc(N).isDirectory()&&kc(Us(N,"index.html")))m.push(A)}catch{}}return m.sort((A,N)=>A.localeCompare(N))}function hd(x){let{root:t,siteName:m,expectIndex:A}=x,N=[];if(!kc(t))return N.push({level:"error",message:`Build output not found at ${t} for site '${m}'`}),N;if(!Rc(t).isDirectory())return N.push({level:"error",message:`Site '${m}' has root ${t}, which is a file, not a directory`}),N;if(!gd(t))return N.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.`}),N;if(!A)return N;if(kc(Us(t,"index.html")))return N;let z=Cm(t);if(z.length===1){let U=`${t.replace(/\/+$/,"")}/${z[0]}`;return N.push({level:"error",message:`Site '${m}' has no index.html at the top of ${t}, but ${U} has one. The generator wrote one level down, so this release would serve 404s at the domain root. Set root: '${U}'.`}),N}if(z.length>1)return N.push({level:"warning",message:`Site '${m}' has no index.html at the top of ${t}. These subdirectories have one: ${z.join(", ")}. The domain root will 404.`}),N;return N.push({level:"warning",message:`Site '${m}' has no index.html anywhere under ${t}. The domain root will 404.`}),N}function Am(x){if(!x)return[];if(Array.isArray(x))return x.map(String);let t=x,m=t.Items??t.Item;if(!m)return[];if(Array.isArray(m))return m.map(String);if(typeof m==="object"&&m!==null){let A=m;if("Item"in A){let N=A.Item;if(!N)return[];return Array.isArray(N)?N.map(String):[String(N)]}if("CNAME"in A){let N=A.CNAME;if(!N)return[];return Array.isArray(N)?N.map(String):[String(N)]}return Object.values(A).flatMap((N)=>{if(Array.isArray(N))return N.map(String);return typeof N==="string"?[N]:[]})}return[String(m)]}async function Vs(x){if(x.length===0)return;let t=new w,m=await t.listDistributions(),A=[...new Set(x.map((N)=>N.toLowerCase()))];for(let N of A){let z=m.find((X)=>Am(X.Aliases).some((re)=>re.toLowerCase()===N));if(!z?.Id){a(`No CloudFront distribution found for ${N} — skipping dynamic HTTP method sync`);continue}if(await t.ensureDynamicHttpMethods(z.Id))n(`CloudFront ${z.Id} (${N}): enabled POST/PUT/PATCH/DELETE on dynamic paths`);else e(`CloudFront ${z.Id} (${N}): already allows dynamic HTTP methods`)}}import{execSync as km}from"node:child_process";async function Rm(x,t,m,A={},N=process.cwd()){if(!x)return;if(A.step?.(`Running ${m} hook`),typeof x==="string"){km(x,{stdio:"inherit",cwd:N});return}await x(t)}async function ps(x,t,m={},A){try{return await Rm(x.hooks?.[t],x,t,m,A),!0}catch(N){return m.error?.(`${t} hook failed: ${N?.message||N}`),!1}}var Ci=(x)=>x===void 0?void 0:x?"on":"off";function Ic(x){let t={ssl:x.ssl,always_use_https:Ci(x.alwaysUseHttps),automatic_https_rewrites:Ci(x.automaticHttpsRewrites),min_tls_version:x.minTlsVersion,tls_1_3:Ci(x.tls13),brotli:Ci(x.brotli),http3:Ci(x.http3),"0rtt":Ci(x.zeroRtt),early_hints:Ci(x.earlyHints),browser_cache_ttl:x.browserCacheTtl,always_online:Ci(x.alwaysOnline),websockets:Ci(x.websockets),email_obfuscation:Ci(x.emailObfuscation)};if(x.hsts)t.security_header={strict_transport_security:{enabled:x.hsts.enabled,max_age:x.hsts.maxAge??31536000,include_subdomains:x.hsts.includeSubdomains??!1,preload:x.hsts.preload??!1,nosniff:x.hsts.noSniff??!0}};for(let m of Object.keys(t))if(t[m]===void 0)delete t[m];return{...t,...x.raw??{}}}var ms={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 Im}from"node:tls";async function Pc(x){let{address:t,serverName:m,port:A=443,timeoutMs:N=5000}=x;return new Promise((z)=>{let U=!1,X=(re)=>{if(U)return;U=!0;try{te.destroy()}catch{}z(re)},te=Im({host:t,port:A,servername:m,rejectUnauthorized:!1,timeout:N});te.once("secureConnect",()=>{if(te.authorized){X({reachable:!0,trusted:!0});return}X({reachable:!0,trusted:!1,reason:te.authorizationError?String(te.authorizationError):"origin certificate is not trusted for this hostname"})}),te.once("timeout",()=>{X({reachable:!1,trusted:!1,reason:`no TLS handshake within ${N}ms`})}),te.once("error",(re)=>{X({reachable:!1,trusted:!1,reason:re.message})})})}function Pm(x,t){let m=t.replace(/\.$/,"").toLowerCase();return x.some((A)=>{let N=A.replace(/\.$/,"").toLowerCase();if(N===m)return!0;if(!N.startsWith("*."))return!1;let z=N.slice(2);if(!m.endsWith(`.${z}`))return!1;return!m.slice(0,m.length-z.length-1).includes(".")})}async function xc(x){let{provider:t,zone:m,hosts:A,ipv4:N,ipv6:z,proxied:U=!0,settings:X=ms,cache:te,originGuard:re,purge:oe=!0,ttl:ae=300,skipOriginProbe:ve=!1,skipEdgeCertificateCheck:Ce=!1}=x,Re={records:[],settingsChanged:[],cacheRules:0,originGuard:!1,purged:!1,deferredProxy:[],warnings:[]};if(A.length===0)return Re;let we=[],ke=U&&!Ce?await t.listEdgeCertificateHosts(m):null;for(let Ae of A){let Ie=U;if(Ie&&ke&&!Pm(ke,Ae)){Ie=!1;let Xe=ke.length===0?"the zone has no active edge certificate yet":`no active edge certificate covers it (${ke.join(", ")})`;Re.deferredProxy.push({host:Ae,reason:Xe}),Re.warnings.push(`${Ae}: publishing DNS-only — ${Xe}. 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(Ie&&!ve){let Xe=await Pc({address:N,serverName:Ae});if(!Xe.trusted){Ie=!1;let _e=Xe.reason||"origin is not serving a trusted certificate";Re.deferredProxy.push({host:Ae,reason:_e}),Re.warnings.push(`${Ae}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${_e}). 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(Ie)we.push(Ae);let Te=[{type:"A",content:N}];if(z&&_i(Ae))Te.push({type:"AAAA",content:z});for(let{type:Xe,content:_e}of Te){let Ze=await t.upsertRecord(m,{name:Ae,type:Xe,content:_e,ttl:ae,proxied:Ie});if(Ze.success)Re.records.push({host:Ae,type:Xe,content:_e,proxied:Ie});else Re.warnings.push(`${Ae} ${Xe} → ${_e} failed: ${Ze.message||"unknown provider error"}`)}}if(we.length===0){if(U)Re.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");return Re}try{let Ae=Ic(X),Ie=await t.applyZoneSettings(m,Ae);Re.settingsChanged=Ie.changed;for(let Te of Ie.failed)Re.warnings.push(`zone setting '${Te.id}' could not be applied: ${Te.error}`)}catch(Ae){Re.warnings.push(`zone settings could not be read: ${Ae instanceof Error?Ae.message:String(Ae)}`)}let Be=vs(we,te);if(Be.length>0){let Ae=await t.putManagedPhaseRules(m,"http_request_cache_settings",Be);if(Ae.success)Re.cacheRules=Be.length;else Re.warnings.push(`cache rules could not be written: ${Ae.message}`)}if(re){let Ae=Ts(we,re.header,re.value);if(Ae.length>0){let Ie=await t.putManagedPhaseRules(m,"http_request_late_transform",Ae);if(Ie.success)Re.originGuard=!0;else Re.warnings.push(`origin guard header could not be written: ${Ie.message}. `+"The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.")}}if(oe){if(Re.purged=await t.purgeCache(m,{hosts:we}),!Re.purged)Re.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.")}return Re}var xm=(x)=>process.env[x];function yd(x,t){let m=x.replace(/\.$/,"").toLowerCase();if(t){let A=t.replace(/\.$/,"").toLowerCase();if(m===A||m.endsWith(`.${A}`))return A}return m.split(".").slice(-2).join(".")}function Dc(x,t={}){let m=t.env??xm,A=x.infrastructure?.compute?.proxy?.cdn,N=[];if(!A||A.provider!=="cloudflare")return{plan:null,errors:N};if(A.originDomain)N.push(`compute.proxy.cdn.originDomain (${A.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 z=m("CLOUDFLARE_API_TOKEN");if(!z)return N.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled."),{plan:null,errors:N};let U=x.sites??{},X=A.frontedHosts?.length?[...new Set(A.frontedHosts.map((ve)=>ve.toLowerCase()))]:Oi(U,{autoWww:x.infrastructure?.compute?.proxy?.autoWww!==!1});if(X.length===0)return N.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain."),{plan:null,errors:N};let te=x.infrastructure?.dns?.domain,re=new Set(X.map((ve)=>yd(ve,te)));if(re.size>1)return N.push(`Fronted hosts span more than one zone (${[...re].join(", ")}). A Cloudflare zone is configured as a unit, so run one CDN config per zone.`),{plan:null,errors:N};let oe=A.cloudflare??{},ae=A.secret?{header:A.secretHeader??"X-Origin-Verify",value:A.secret}:void 0;return{plan:{zone:[...re][0],hosts:X,apiToken:z,zoneId:oe.zoneId??m("CLOUDFLARE_ZONE_ID"),accountId:oe.accountId??m("CLOUDFLARE_ACCOUNT_ID"),proxied:oe.proxied!==!1,settings:oe.settings??ms,cache:oe.cache,originGuard:ae,purge:oe.purgeOnDeploy!==!1,skipOriginProbe:oe.skipOriginProbe===!0},errors:N}}async function Om(x){let t=Hs();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 A=await new ct(x).getCallerIdentity();if(A.Arn)e(`AWS identity: ${A.Arn}`)}catch(m){a(`Could not verify AWS identity: ${m.message}`)}}function Lm(){try{return Oc("git rev-parse --short HEAD",{stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return Date.now().toString(36)}}async function Ec(x,t,m,A){let{ensureManagementDashboard:N}=await import("./chunk-je602ntp.js");N(x,{logger:{info:e,warn:a}});let z=x.sites||{},U=Object.entries(z);if(U.length===0)return a("infrastructure.compute is set but no sites are configured — nothing to deploy. Add sites to `cloud.config.ts`."),!0;if(A&&!z[A])return r(`Site '${A}' was not found. Configured sites: ${U.map(([Be])=>Be).join(", ")}`),!1;let X=U.filter(([Be,Ae])=>{if(!Ae)return!1;if(A&&Be!==A)return!1;if(P(Ae)==="bucket")return a(`Site '${Be}' targets a bucket — skipping compute deploy (handled by the static-site path).`),!1;return Os(Ae)});if(A)e(`Deploying only '${A}'. Other sites keep their current release; proxy routes are still regenerated in full.`);let te=x.project.slug,oe=(x.infrastructure?.compute||{}).runtime||"bun",ae=O({config:x}),ve=Lm(),Ce=new Map,Re=new Map,we={step:(Be)=>p(Be),error:(Be)=>r(Be)};if(!await ps(x,"beforeDeploy",we))return!1;if(!await ps(x,"beforeBuild",we))return!1;for(let[Be,Ae]of X){let Ie=P(Ae);if(i(`Preparing site: ${Be}`),e(`Domain: ${Ae.domain||"(none)"}`),e(`Source: ${Ae.root}`),e(`Kind: ${Ie==="server-static"?"static (shipped to /var/www)":"app (systemd service)"}`),Ie==="server-app")e(`Start: ${Ae.start}`),e(`Port: ${Ae.port??"(unset)"}`);if(Ae.build){p(`Running build: ${Ae.build}`);try{Oc(Ae.build,{stdio:"inherit",cwd:process.cwd()})}catch(et){return r(`Build failed for site '${Be}': ${et.message}`),!1}}let Te=hd({root:Ae.root,siteName:Be,expectIndex:Ie==="server-static"});for(let et of Te)if(et.level==="error")r(et.message);else a(et.message);if(Te.some((et)=>et.level==="error"))return!1;let Xe=Ae.build?void 0:JSON.stringify([Nm(Ae.root),[...Ae.exclude||[]].sort()]),_e=Xe?Re.get(Xe):void 0;if(_e){p(`Reusing package for ${Ae.root} → ${_e}`),Ce.set(Be,_e);continue}let Ze=wd(bd(),`${te}-${Be}-${ve}.tar.gz`);p(`Packaging ${Ae.root} → ${Ze}`);try{let et=(Ae.exclude||[]).map((At)=>`--exclude=${At}`);Dm("tar",["czf",Ze,"-C",Ae.root,...et,"."],{stdio:"inherit",env:{...process.env,COPYFILE_DISABLE:"1"}})}catch(et){return r(`Failed to package '${Be}': ${et.message}`),!1}if(Ce.set(Be,Ze),Xe)Re.set(Xe,Ze)}if(!await ps(x,"afterBuild",we))return!1;let ke=await ji({config:A?{...x,sites:Object.fromEntries(X)}:x,rpxConfig:A?x:void 0,environment:t,driver:ae,sha:ve,runtime:oe,tarballForSite:(Be)=>{let Ae=Ce.get(Be);if(!Ae)throw Error(`Missing tarball for site '${Be}'`);return Ae},logger:{info:(Be)=>e(Be),warn:(Be)=>a(Be),error:(Be)=>r(Be),step:(Be)=>p(Be),success:(Be)=>n(Be)}});if(ke&&!await ps(x,"afterDeploy",we))return!1;return ke}async function Ws(x){let t=new Mr;if(p("Running pre-deployment security scan..."),x.exclude?.length)e(`Excluding from the scan: ${x.exclude.join(", ")}`);let m=await t.scan({directory:x.sourceDir,failOnSeverity:x.failOnSeverity||"critical",skipPatterns:x.skipPatterns,exclude:x.exclude});return{passed:m.passed,result:m}}function jm(){let x=wd(bd(),`ts-cloud-empty-scan-${process.pid}`);return vd(x,{recursive:!0}),x}function qs(x){let{summary:t,findings:m,scannedFiles:A,duration:N}=x;if(e(`
375
+ Invalidation is in progress. Use --wait to wait for completion.`)}catch(m){r(`Invalidation failed: ${m.message}`)}})}function Ac(x){for(let[t,m]of[["team:add <email> <role>","Invite an organization member"],["team:list","List organization members"],["team:remove <email>","Revoke organization membership"]])x.command(t,m).action(async(...A)=>Dr(t.split(" ")[0],{target:String(A[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 Dm,execSync as Oc}from"node:child_process";import{copyFileSync as gs,existsSync as fr,mkdirSync as vd,readFileSync as Nc,writeFileSync as Em}from"node:fs";import{tmpdir as bd}from"node:os";import{dirname as Tm,join as wd,resolve as Nm}from"node:path";import{existsSync as kc,readdirSync as md,statSync as Rc}from"node:fs";import{join as Us}from"node:path";var $m=new Set([".DS_Store","Thumbs.db",".gitkeep",".keep"]);function fd(x){return $m.has(x)}function gd(x,t=0){if(t>8)return!0;let m;try{m=md(x)}catch{return!1}for(let A of m){if(fd(A))continue;let N=Us(x,A),z;try{z=Rc(N)}catch{continue}if(z.isFile())return!0;if(z.isDirectory()&&gd(N,t+1))return!0}return!1}function Cm(x){let t;try{t=md(x)}catch{return[]}let m=[];for(let A of t){if(fd(A))continue;let N=Us(x,A);try{if(Rc(N).isDirectory()&&kc(Us(N,"index.html")))m.push(A)}catch{}}return m.sort((A,N)=>A.localeCompare(N))}function hd(x){let{root:t,siteName:m,expectIndex:A}=x,N=[];if(!kc(t))return N.push({level:"error",message:`Build output not found at ${t} for site '${m}'`}),N;if(!Rc(t).isDirectory())return N.push({level:"error",message:`Site '${m}' has root ${t}, which is a file, not a directory`}),N;if(!gd(t))return N.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.`}),N;if(!A)return N;if(kc(Us(t,"index.html")))return N;let z=Cm(t);if(z.length===1){let U=`${t.replace(/\/+$/,"")}/${z[0]}`;return N.push({level:"error",message:`Site '${m}' has no index.html at the top of ${t}, but ${U} has one. The generator wrote one level down, so this release would serve 404s at the domain root. Set root: '${U}'.`}),N}if(z.length>1)return N.push({level:"warning",message:`Site '${m}' has no index.html at the top of ${t}. These subdirectories have one: ${z.join(", ")}. The domain root will 404.`}),N;return N.push({level:"warning",message:`Site '${m}' has no index.html anywhere under ${t}. The domain root will 404.`}),N}function Am(x){if(!x)return[];if(Array.isArray(x))return x.map(String);let t=x,m=t.Items??t.Item;if(!m)return[];if(Array.isArray(m))return m.map(String);if(typeof m==="object"&&m!==null){let A=m;if("Item"in A){let N=A.Item;if(!N)return[];return Array.isArray(N)?N.map(String):[String(N)]}if("CNAME"in A){let N=A.CNAME;if(!N)return[];return Array.isArray(N)?N.map(String):[String(N)]}return Object.values(A).flatMap((N)=>{if(Array.isArray(N))return N.map(String);return typeof N==="string"?[N]:[]})}return[String(m)]}async function Vs(x){if(x.length===0)return;let t=new C,m=await t.listDistributions(),A=[...new Set(x.map((N)=>N.toLowerCase()))];for(let N of A){let z=m.find((X)=>Am(X.Aliases).some((re)=>re.toLowerCase()===N));if(!z?.Id){a(`No CloudFront distribution found for ${N} — skipping dynamic HTTP method sync`);continue}if(await t.ensureDynamicHttpMethods(z.Id))n(`CloudFront ${z.Id} (${N}): enabled POST/PUT/PATCH/DELETE on dynamic paths`);else e(`CloudFront ${z.Id} (${N}): already allows dynamic HTTP methods`)}}import{execSync as km}from"node:child_process";async function Rm(x,t,m,A={},N=process.cwd()){if(!x)return;if(A.step?.(`Running ${m} hook`),typeof x==="string"){km(x,{stdio:"inherit",cwd:N});return}await x(t)}async function ps(x,t,m={},A){try{return await Rm(x.hooks?.[t],x,t,m,A),!0}catch(N){return m.error?.(`${t} hook failed: ${N?.message||N}`),!1}}var Ci=(x)=>x===void 0?void 0:x?"on":"off";function Ic(x){let t={ssl:x.ssl,always_use_https:Ci(x.alwaysUseHttps),automatic_https_rewrites:Ci(x.automaticHttpsRewrites),min_tls_version:x.minTlsVersion,tls_1_3:Ci(x.tls13),brotli:Ci(x.brotli),http3:Ci(x.http3),"0rtt":Ci(x.zeroRtt),early_hints:Ci(x.earlyHints),browser_cache_ttl:x.browserCacheTtl,always_online:Ci(x.alwaysOnline),websockets:Ci(x.websockets),email_obfuscation:Ci(x.emailObfuscation)};if(x.hsts)t.security_header={strict_transport_security:{enabled:x.hsts.enabled,max_age:x.hsts.maxAge??31536000,include_subdomains:x.hsts.includeSubdomains??!1,preload:x.hsts.preload??!1,nosniff:x.hsts.noSniff??!0}};for(let m of Object.keys(t))if(t[m]===void 0)delete t[m];return{...t,...x.raw??{}}}var ms={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 Im}from"node:tls";async function Pc(x){let{address:t,serverName:m,port:A=443,timeoutMs:N=5000}=x;return new Promise((z)=>{let U=!1,X=(re)=>{if(U)return;U=!0;try{te.destroy()}catch{}z(re)},te=Im({host:t,port:A,servername:m,rejectUnauthorized:!1,timeout:N});te.once("secureConnect",()=>{if(te.authorized){X({reachable:!0,trusted:!0});return}X({reachable:!0,trusted:!1,reason:te.authorizationError?String(te.authorizationError):"origin certificate is not trusted for this hostname"})}),te.once("timeout",()=>{X({reachable:!1,trusted:!1,reason:`no TLS handshake within ${N}ms`})}),te.once("error",(re)=>{X({reachable:!1,trusted:!1,reason:re.message})})})}function Pm(x,t){let m=t.replace(/\.$/,"").toLowerCase();return x.some((A)=>{let N=A.replace(/\.$/,"").toLowerCase();if(N===m)return!0;if(!N.startsWith("*."))return!1;let z=N.slice(2);if(!m.endsWith(`.${z}`))return!1;return!m.slice(0,m.length-z.length-1).includes(".")})}async function xc(x){let{provider:t,zone:m,hosts:A,ipv4:N,ipv6:z,proxied:U=!0,settings:X=ms,cache:te,originGuard:re,purge:oe=!0,ttl:ae=300,skipOriginProbe:ve=!1,skipEdgeCertificateCheck:Ce=!1}=x,Re={records:[],settingsChanged:[],cacheRules:0,originGuard:!1,purged:!1,deferredProxy:[],warnings:[]};if(A.length===0)return Re;let we=[],ke=U&&!Ce?await t.listEdgeCertificateHosts(m):null;for(let Ae of A){let Ie=U;if(Ie&&ke&&!Pm(ke,Ae)){Ie=!1;let Xe=ke.length===0?"the zone has no active edge certificate yet":`no active edge certificate covers it (${ke.join(", ")})`;Re.deferredProxy.push({host:Ae,reason:Xe}),Re.warnings.push(`${Ae}: publishing DNS-only — ${Xe}. 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(Ie&&!ve){let Xe=await Pc({address:N,serverName:Ae});if(!Xe.trusted){Ie=!1;let _e=Xe.reason||"origin is not serving a trusted certificate";Re.deferredProxy.push({host:Ae,reason:_e}),Re.warnings.push(`${Ae}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${_e}). 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(Ie)we.push(Ae);let Te=[{type:"A",content:N}];if(z&&_i(Ae))Te.push({type:"AAAA",content:z});for(let{type:Xe,content:_e}of Te){let Ze=await t.upsertRecord(m,{name:Ae,type:Xe,content:_e,ttl:ae,proxied:Ie});if(Ze.success)Re.records.push({host:Ae,type:Xe,content:_e,proxied:Ie});else Re.warnings.push(`${Ae} ${Xe} → ${_e} failed: ${Ze.message||"unknown provider error"}`)}}if(we.length===0){if(U)Re.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");return Re}try{let Ae=Ic(X),Ie=await t.applyZoneSettings(m,Ae);Re.settingsChanged=Ie.changed;for(let Te of Ie.failed)Re.warnings.push(`zone setting '${Te.id}' could not be applied: ${Te.error}`)}catch(Ae){Re.warnings.push(`zone settings could not be read: ${Ae instanceof Error?Ae.message:String(Ae)}`)}let Be=vs(we,te);if(Be.length>0){let Ae=await t.putManagedPhaseRules(m,"http_request_cache_settings",Be);if(Ae.success)Re.cacheRules=Be.length;else Re.warnings.push(`cache rules could not be written: ${Ae.message}`)}if(re){let Ae=Ts(we,re.header,re.value);if(Ae.length>0){let Ie=await t.putManagedPhaseRules(m,"http_request_late_transform",Ae);if(Ie.success)Re.originGuard=!0;else Re.warnings.push(`origin guard header could not be written: ${Ie.message}. `+"The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.")}}if(oe){if(Re.purged=await t.purgeCache(m,{hosts:we}),!Re.purged)Re.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.")}return Re}var xm=(x)=>process.env[x];function yd(x,t){let m=x.replace(/\.$/,"").toLowerCase();if(t){let A=t.replace(/\.$/,"").toLowerCase();if(m===A||m.endsWith(`.${A}`))return A}return m.split(".").slice(-2).join(".")}function Dc(x,t={}){let m=t.env??xm,A=x.infrastructure?.compute?.proxy?.cdn,N=[];if(!A||A.provider!=="cloudflare")return{plan:null,errors:N};if(A.originDomain)N.push(`compute.proxy.cdn.originDomain (${A.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 z=m("CLOUDFLARE_API_TOKEN");if(!z)return N.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled."),{plan:null,errors:N};let U=x.sites??{},X=A.frontedHosts?.length?[...new Set(A.frontedHosts.map((ve)=>ve.toLowerCase()))]:Oi(U,{autoWww:x.infrastructure?.compute?.proxy?.autoWww!==!1});if(X.length===0)return N.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain."),{plan:null,errors:N};let te=x.infrastructure?.dns?.domain,re=new Set(X.map((ve)=>yd(ve,te)));if(re.size>1)return N.push(`Fronted hosts span more than one zone (${[...re].join(", ")}). A Cloudflare zone is configured as a unit, so run one CDN config per zone.`),{plan:null,errors:N};let oe=A.cloudflare??{},ae=A.secret?{header:A.secretHeader??"X-Origin-Verify",value:A.secret}:void 0;return{plan:{zone:[...re][0],hosts:X,apiToken:z,zoneId:oe.zoneId??m("CLOUDFLARE_ZONE_ID"),accountId:oe.accountId??m("CLOUDFLARE_ACCOUNT_ID"),proxied:oe.proxied!==!1,settings:oe.settings??ms,cache:oe.cache,originGuard:ae,purge:oe.purgeOnDeploy!==!1,skipOriginProbe:oe.skipOriginProbe===!0},errors:N}}async function Om(x){let t=Hs();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 A=await new ct(x).getCallerIdentity();if(A.Arn)e(`AWS identity: ${A.Arn}`)}catch(m){a(`Could not verify AWS identity: ${m.message}`)}}function Lm(){try{return Oc("git rev-parse --short HEAD",{stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return Date.now().toString(36)}}async function Ec(x,t,m,A){let{ensureManagementDashboard:N}=await import("./chunk-cr56pxpt.js");N(x,{logger:{info:e,warn:a}});let z=x.sites||{},U=Object.entries(z);if(U.length===0)return a("infrastructure.compute is set but no sites are configured — nothing to deploy. Add sites to `cloud.config.ts`."),!0;if(A&&!z[A])return r(`Site '${A}' was not found. Configured sites: ${U.map(([Be])=>Be).join(", ")}`),!1;let X=U.filter(([Be,Ae])=>{if(!Ae)return!1;if(A&&Be!==A)return!1;if(P(Ae)==="bucket")return a(`Site '${Be}' targets a bucket — skipping compute deploy (handled by the static-site path).`),!1;return Os(Ae)});if(A)e(`Deploying only '${A}'. Other sites keep their current release; proxy routes are still regenerated in full.`);let te=x.project.slug,oe=(x.infrastructure?.compute||{}).runtime||"bun",ae=O({config:x}),ve=Lm(),Ce=new Map,Re=new Map,we={step:(Be)=>p(Be),error:(Be)=>r(Be)};if(!await ps(x,"beforeDeploy",we))return!1;if(!await ps(x,"beforeBuild",we))return!1;for(let[Be,Ae]of X){let Ie=P(Ae);if(i(`Preparing site: ${Be}`),e(`Domain: ${Ae.domain||"(none)"}`),e(`Source: ${Ae.root}`),e(`Kind: ${Ie==="server-static"?"static (shipped to /var/www)":"app (systemd service)"}`),Ie==="server-app")e(`Start: ${Ae.start}`),e(`Port: ${Ae.port??"(unset)"}`);if(Ae.build){p(`Running build: ${Ae.build}`);try{Oc(Ae.build,{stdio:"inherit",cwd:process.cwd()})}catch(et){return r(`Build failed for site '${Be}': ${et.message}`),!1}}let Te=hd({root:Ae.root,siteName:Be,expectIndex:Ie==="server-static"});for(let et of Te)if(et.level==="error")r(et.message);else a(et.message);if(Te.some((et)=>et.level==="error"))return!1;let Xe=Ae.build?void 0:JSON.stringify([Nm(Ae.root),[...Ae.exclude||[]].sort()]),_e=Xe?Re.get(Xe):void 0;if(_e){p(`Reusing package for ${Ae.root} → ${_e}`),Ce.set(Be,_e);continue}let Ze=wd(bd(),`${te}-${Be}-${ve}.tar.gz`);p(`Packaging ${Ae.root} → ${Ze}`);try{let et=(Ae.exclude||[]).map((At)=>`--exclude=${At}`);Dm("tar",["czf",Ze,"-C",Ae.root,...et,"."],{stdio:"inherit",env:{...process.env,COPYFILE_DISABLE:"1"}})}catch(et){return r(`Failed to package '${Be}': ${et.message}`),!1}if(Ce.set(Be,Ze),Xe)Re.set(Xe,Ze)}if(!await ps(x,"afterBuild",we))return!1;let ke=await ji({config:A?{...x,sites:Object.fromEntries(X)}:x,rpxConfig:A?x:void 0,environment:t,driver:ae,sha:ve,runtime:oe,tarballForSite:(Be)=>{let Ae=Ce.get(Be);if(!Ae)throw Error(`Missing tarball for site '${Be}'`);return Ae},logger:{info:(Be)=>e(Be),warn:(Be)=>a(Be),error:(Be)=>r(Be),step:(Be)=>p(Be),success:(Be)=>n(Be)}});if(ke&&!await ps(x,"afterDeploy",we))return!1;return ke}async function Ws(x){let t=new Mr;if(p("Running pre-deployment security scan..."),x.exclude?.length)e(`Excluding from the scan: ${x.exclude.join(", ")}`);let m=await t.scan({directory:x.sourceDir,failOnSeverity:x.failOnSeverity||"critical",skipPatterns:x.skipPatterns,exclude:x.exclude});return{passed:m.passed,result:m}}function jm(){let x=wd(bd(),`ts-cloud-empty-scan-${process.pid}`);return vd(x,{recursive:!0}),x}function qs(x){let{summary:t,findings:m,scannedFiles:A,duration:N}=x;if(e(`
376
376
  Scanned ${A} files in ${N}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 z=m.filter((oe)=>oe.pattern.severity==="critical"),U=m.filter((oe)=>oe.pattern.severity==="high"),X=m.filter((oe)=>oe.pattern.severity==="medium"),te=m.filter((oe)=>oe.pattern.severity==="low"),re=(oe,ae,ve)=>{if(oe.length>0){console.log(`
378
378
  ${Ut(`[${ae}]`,ve)}`);for(let Ce of oe.slice(0,10))e(` ${Ce.pattern.name}`),e(` File: ${Ce.file}:${Ce.line}`),e(` Match: ${Ce.match}`);if(oe.length>10)e(` ... and ${oe.length-10} more ${ae.toLowerCase()} findings`)}};re(z,"CRITICAL","red"),re(U,"HIGH","yellow"),re(X,"MEDIUM","blue"),re(te,"LOW","gray")}}function Sd(x,t){let m=Object.keys(x.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 Tc(x,t,m){let A=Sd(x,t),N=y(process.cwd(),x);try{let z=new Nt(N.store);Bn(N);let U=le(N,A);if(m)Ra(z,U,m);else Pa(z,U);let X=nr(z,{scope:U,desiredConfigHash:N.project.desiredConfigHash});return e(`Security policy: ${X.decision.policyId} v${X.decision.policyVersion} — ${X.decision.outcome.toUpperCase()}`),e(`Security decision: ${X.decision.explanation}`),{allowed:X.decision.outcome!=="block",explanation:X.decision.explanation}}finally{N.store.close()}}async function Fm(x,t){let m=Sd(x),A=y(process.cwd(),x);try{let N=new Nt(A.store);Bn(A);let z=le(A,m),U=t.imageSha256?`${t.imageRef}@sha256:${t.imageSha256.replace(/^sha256:/,"")}`:t.imageRef;p("Scanning the built image and producing release security artifacts...");let X=await Na(N,{scope:z,releaseId:U,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: ${X.scan.run.status} (${X.scan.findings.length} findings)`),e(`Release SBOM: ${X.sbomSource==="image"?"full image inventory":X.sbomSource==="manifest"?"package-manifest fallback":"unavailable"}`);let te=nr(N,{scope:z,operationId:t.invocationId,desiredConfigHash:A.project.desiredConfigHash});return e(`Release policy: ${te.decision.policyId} v${te.decision.policyVersion} — ${te.decision.outcome.toUpperCase()}`),e(`Release decision: ${te.decision.explanation}`),{allowed:te.decision.outcome!=="block",explanation:te.decision.explanation}}finally{A.store.close()}}function Bm(x){try{let t=y(process.cwd(),x),m=new Kt(t.store).current();if(!m.attackMode&&!m.mitigationPause&&m.ipRules.allow.length===0&&m.ipRules.block.length===0)return x;let A=zo(x,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":""}`),A}catch{return x}}function Lc(x){x.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||".",A=t?.failOn||"critical",N=t?.skipPatterns?.split(",").map((X)=>X.trim())||[];if(!fr(m)){r(`Source directory not found: ${m}`);return}e(`Source: ${m}`),e(`Fail on: ${A} or higher severity`);let{passed:z,result:U}=await Ws({sourceDir:m,failOnSeverity:A,skipPatterns:N});if(qs(U),z)n(`
379
379
  ✓ Security scan passed - no blocking issues found`);else r(`
380
380
  ✗ Security scan failed - blocking issues detected`),e(`
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)}}),x.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",A=t?.env||"staging",N=null;N=await Mm(A);try{let z=await Oe(),U=Bm(z),{errors:X,warnings:te}=$s(U);for(let St of te)a(St);if(X.length>0){r(`
382
- ✗ Deployment configuration is invalid:`);for(let St of X)r(` • ${St}`);process.exitCode=1;return}if(t?.dryRun){if(t.site&&!U.sites?.[t.site]){r(`Site '${t.site}' was not found. Configured sites: ${Object.keys(U.sites||{}).join(", ")||"none"}`),process.exitCode=1;return}let St=H(U),Bt=t.stack||E(U,A),un=F(U),Fn=U.project.region||"us-east-1",Yr=t.site?[t.site]:Object.keys(U.sites||{});i("Deployment Plan"),e(`Cloud provider: ${un}`),e(`Mode: ${St}`),e(`Stack: ${Bt}`),e(`Region: ${Fn}`),e(`Environment: ${A}`),e(`Sites: ${Yr.length>0?Yr.join(", "):"none"}`),n("Dry run complete. No infrastructure, DNS, certificates, or application files were changed.");return}if(F(U)==="aws"){let St=U.project?.region||"us-east-1";await Om(St)}let re;if(!t?.skipSecurityScan){let St=lc(U,t?.site);if(!St)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:Bt}=await Ws({sourceDir:St?process.cwd():jm(),failOnSeverity:t?.securityFailOn||"critical",exclude:U.infrastructure?.security?.scan?.exclude});qs(Bt),re=Bt}else a(`Security scan skipped (--skip-security-scan)
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)}}),x.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",A=t?.env||"staging",N=null;N=await Mm(A);try{let z=await Oe(),U=Bm(z),{errors:X,warnings:te}=$s(U);for(let Ct of te)a(Ct);if(X.length>0){r(`
382
+ ✗ Deployment configuration is invalid:`);for(let Ct of X)r(` • ${Ct}`);process.exitCode=1;return}if(t?.dryRun){if(t.site&&!U.sites?.[t.site]){r(`Site '${t.site}' was not found. Configured sites: ${Object.keys(U.sites||{}).join(", ")||"none"}`),process.exitCode=1;return}let Ct=H(U),Bt=t.stack||E(U,A),un=F(U),Fn=U.project.region||"us-east-1",Yr=t.site?[t.site]:Object.keys(U.sites||{});i("Deployment Plan"),e(`Cloud provider: ${un}`),e(`Mode: ${Ct}`),e(`Stack: ${Bt}`),e(`Region: ${Fn}`),e(`Environment: ${A}`),e(`Sites: ${Yr.length>0?Yr.join(", "):"none"}`),n("Dry run complete. No infrastructure, DNS, certificates, or application files were changed.");return}if(F(U)==="aws"){let Ct=U.project?.region||"us-east-1";await Om(Ct)}let re;if(!t?.skipSecurityScan){let Ct=lc(U,t?.site);if(!Ct)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:Bt}=await Ws({sourceDir:Ct?process.cwd():jm(),failOnSeverity:t?.securityFailOn||"critical",exclude:U.infrastructure?.security?.scan?.exclude});qs(Bt),re=Bt}else a(`Security scan skipped (--skip-security-scan)
383
383
  `);let oe=Tc(U,A,re);if(!oe.allowed){r(`
384
384
  ✗ Deployment blocked by security policy: ${oe.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
- `),H(U)==="serverless"){p("Serverless project detected: deploying the Lambda application");let{deployServerlessApp:St}=await import("./chunk-gz0gahzh.js");await St(U,A,{}),n("Serverless deployment complete");return}let ae=t?.stack||E(U,A),ve=U.project.region||"us-east-1",Ce=U.infrastructure?.deployStack!==!1,Re=U.sites&&Object.keys(U.sites).length>0,we=U.infrastructure?.dns?.provider;if(Re&&we){if(await Vm(U,t?.site,we,ve,t?.skipDnsVerification,A,m),!Ce){if(U.infrastructure?.compute){let St=Object.values(U.sites).map((Bt)=>Bt.domain).filter((Bt)=>!!Bt);if(St.length>0)p("Syncing CloudFront dynamic HTTP methods for app domains..."),await Vs(St)}return}}if(!Ce){e("Infrastructure stack deployment disabled (infrastructure.deployStack: false)");return}let ke=F(U);if(e(`Cloud provider: ${ke}`),e(`Stack: ${ae}`),e(`Region: ${ve}`),e(`Environment: ${A}`),(ke==="hetzner"||U.infrastructure?.compute?.mode==="server")&&fo(U)){p(`Provisioning ${ke} compute infrastructure...`);let St=O({config:U,provider:ke});if(!St.provisionComputeInfrastructure){r(`${ke} driver does not support compute provisioning`),process.exitCode=1;return}let Bt=await St.provisionComputeInfrastructure({config:U,environment:A});if(n(`${ke} compute infrastructure ready`),Bt.appPublicIp)e(`App server: ${Bt.appPublicIp}`);if(Bt.appInstanceId)e(`Server ID: ${Bt.appInstanceId}`);if(!await Ec(U,A,ve,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 _m(U,Bt.appPublicIp,we,Bt.appPublicIpv6),await zm(U,we);let Fn=await Vi({config:U,environment:A,driver:St,logger:{info:e,warn:a,error:r,step:p,success:n}});if(!Fn)r("App deployed, but rpx TLS certificate reconciliation failed");if(!Fn)process.exitCode=1;if(!t?.skipDnsVerification)await Um(U,Bt.appPublicIp,Bt.appPublicIpv6)}return}p("Generating CloudFormation template...");let Ae=new Ao({config:U,environment:A});Ae.generate();let Ie=Ae.toJSON(),Te=JSON.parse(Ie);if(Object.keys(Te.Resources||{}).length===0){e(`No infrastructure resources to deploy for environment '${A}' — skipping stack ${ae}.`);return}p("Validating template...");let _e=Vo(Te),Ze=ts(Ie),et=rs(Te),At=[..._e.errors,...Ze.errors,...et.errors];if(At.length>0){r("Template validation failed:");for(let St of At)r(` - ${St.path}: ${St.message}`);return}let Ft=[..._e.warnings,...Ze.warnings,...et.warnings];if(Ft.length>0)for(let St of Ft)a(` - ${St.path}: ${St.message}`);n("Template validated successfully");let wt=Object.keys(Te.Resources).length;e(`
386
- Resources to deploy: ${wt}`);let Et={};for(let St of Object.values(Te.Resources)){let Bt=St.Type;Et[Bt]=(Et[Bt]||0)+1}for(let[St,Bt]of Object.entries(Et).sort((un,Fn)=>Fn[1]-un[1]).slice(0,5))e(` - ${St}: ${Bt}`);if(!(m||await d(`
387
- Deploy now?`,!0))){e("Deployment cancelled");return}let Mt=new v(ve),rt=la(Te);if(rt.length>0){let{ensureFailoverReplicaBuckets:St}=await import("./chunk-5nf3fxgw.js");p("Ensuring failover replica buckets..."),await St(rt,(Bt)=>e(` ${Bt}`))}p("Checking stack status...");let Vt=!1;try{let St=await Mt.describeStacks({stackName:ae});Vt=St.Stacks&&St.Stacks.length>0}catch(St){Vt=!1}if(Vt){e("Stack exists, updating...");let St=new o("Updating CloudFormation stack...");St.start();try{await Mt.updateStack({stackName:ae,templateBody:Ie,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:U.project.name},{Key:"Environment",Value:A},{Key:"ManagedBy",Value:"ts-cloud"}]}),St.succeed("Update initiated"),p("Waiting for stack update to complete..."),await Mt.waitForStack(ae,"stack-update-complete"),n("Stack updated successfully!")}catch(Bt){if(Bt.message.includes("No updates are to be performed"))St.succeed("No changes detected"),e("Stack is already up to date — continuing to app deploy");else throw Bt}}else{e("Creating new stack...");let St=new o("Creating CloudFormation stack...");St.start(),await Mt.createStack({stackName:ae,templateBody:Ie,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:U.project.name},{Key:"Environment",Value:A},{Key:"ManagedBy",Value:"ts-cloud"}]}),St.succeed("Stack creation initiated"),p("Waiting for stack creation to complete..."),await Mt.waitForStack(ae,"stack-create-complete"),n("Stack created successfully!")}let qt=await Mt.getStackOutputs(ae);if(Se(`Deployment Complete!
385
+ `),H(U)==="serverless"){p("Serverless project detected: deploying the Lambda application");let{deployServerlessApp:Ct}=await import("./chunk-4er4ktcs.js");await Ct(U,A,{}),n("Serverless deployment complete");return}let ae=t?.stack||E(U,A),ve=U.project.region||"us-east-1",Ce=U.infrastructure?.deployStack!==!1,Re=U.sites&&Object.keys(U.sites).length>0,we=U.infrastructure?.dns?.provider;if(Re&&we){if(await Vm(U,t?.site,we,ve,t?.skipDnsVerification,A,m),!Ce){if(U.infrastructure?.compute){let Ct=Object.values(U.sites).map((Bt)=>Bt.domain).filter((Bt)=>!!Bt);if(Ct.length>0)p("Syncing CloudFront dynamic HTTP methods for app domains..."),await Vs(Ct)}return}}if(!Ce){e("Infrastructure stack deployment disabled (infrastructure.deployStack: false)");return}let ke=F(U);if(e(`Cloud provider: ${ke}`),e(`Stack: ${ae}`),e(`Region: ${ve}`),e(`Environment: ${A}`),(ke==="hetzner"||U.infrastructure?.compute?.mode==="server")&&fo(U)){p(`Provisioning ${ke} compute infrastructure...`);let Ct=O({config:U,provider:ke});if(!Ct.provisionComputeInfrastructure){r(`${ke} driver does not support compute provisioning`),process.exitCode=1;return}let Bt=await Ct.provisionComputeInfrastructure({config:U,environment:A});if(n(`${ke} compute infrastructure ready`),Bt.appPublicIp)e(`App server: ${Bt.appPublicIp}`);if(Bt.appInstanceId)e(`Server ID: ${Bt.appInstanceId}`);if(!await Ec(U,A,ve,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 _m(U,Bt.appPublicIp,we,Bt.appPublicIpv6),await zm(U,we);let Fn=await Vi({config:U,environment:A,driver:Ct,logger:{info:e,warn:a,error:r,step:p,success:n}});if(!Fn)r("App deployed, but rpx TLS certificate reconciliation failed");if(!Fn)process.exitCode=1;if(!t?.skipDnsVerification)await Um(U,Bt.appPublicIp,Bt.appPublicIpv6)}return}p("Generating CloudFormation template...");let Ae=new Ao({config:U,environment:A});Ae.generate();let Ie=Ae.toJSON(),Te=JSON.parse(Ie);if(Object.keys(Te.Resources||{}).length===0){e(`No infrastructure resources to deploy for environment '${A}' — skipping stack ${ae}.`);return}p("Validating template...");let _e=Vo(Te),Ze=ts(Ie),et=rs(Te),At=[..._e.errors,...Ze.errors,...et.errors];if(At.length>0){r("Template validation failed:");for(let Ct of At)r(` - ${Ct.path}: ${Ct.message}`);return}let Ft=[..._e.warnings,...Ze.warnings,...et.warnings];if(Ft.length>0)for(let Ct of Ft)a(` - ${Ct.path}: ${Ct.message}`);n("Template validated successfully");let St=Object.keys(Te.Resources).length;e(`
386
+ Resources to deploy: ${St}`);let Et={};for(let Ct of Object.values(Te.Resources)){let Bt=Ct.Type;Et[Bt]=(Et[Bt]||0)+1}for(let[Ct,Bt]of Object.entries(Et).sort((un,Fn)=>Fn[1]-un[1]).slice(0,5))e(` - ${Ct}: ${Bt}`);if(!(m||await d(`
387
+ Deploy now?`,!0))){e("Deployment cancelled");return}let Mt=new v(ve),rt=la(Te);if(rt.length>0){let{ensureFailoverReplicaBuckets:Ct}=await import("./chunk-12j17qm5.js");p("Ensuring failover replica buckets..."),await Ct(rt,(Bt)=>e(` ${Bt}`))}p("Checking stack status...");let Vt=!1;try{let Ct=await Mt.describeStacks({stackName:ae});Vt=Ct.Stacks&&Ct.Stacks.length>0}catch(Ct){Vt=!1}if(Vt){e("Stack exists, updating...");let Ct=new o("Updating CloudFormation stack...");Ct.start();try{await Mt.updateStack({stackName:ae,templateBody:Ie,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:U.project.name},{Key:"Environment",Value:A},{Key:"ManagedBy",Value:"ts-cloud"}]}),Ct.succeed("Update initiated"),p("Waiting for stack update to complete..."),await Mt.waitForStack(ae,"stack-update-complete"),n("Stack updated successfully!")}catch(Bt){if(Bt.message.includes("No updates are to be performed"))Ct.succeed("No changes detected"),e("Stack is already up to date — continuing to app deploy");else throw Bt}}else{e("Creating new stack...");let Ct=new o("Creating CloudFormation stack...");Ct.start(),await Mt.createStack({stackName:ae,templateBody:Ie,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:U.project.name},{Key:"Environment",Value:A},{Key:"ManagedBy",Value:"ts-cloud"}]}),Ct.succeed("Stack creation initiated"),p("Waiting for stack creation to complete..."),await Mt.waitForStack(ae,"stack-create-complete"),n("Stack created successfully!")}let qt=await Mt.getStackOutputs(ae);if(Se(`Deployment Complete!
388
388
 
389
389
  Stack: ${ae}
390
390
  Region: ${ve}
391
391
  Environment: ${A}
392
- Resources: ${wt}
392
+ Resources: ${St}
393
393
 
394
394
  View in console:
395
395
  https://console.aws.amazon.com/cloudformation/home?region=${ve}#/stacks/stackinfo?stackId=${encodeURIComponent(ae)}`,"green"),Object.keys(qt).length>0){e(`
396
- Stack Outputs:`);for(let[St,Bt]of Object.entries(qt))e(` - ${St}: ${Bt}`)}if(rt.length>0){let{grantFailoverReplicaAccess:St}=await import("./chunk-5nf3fxgw.js");p("Granting the distribution read access to its failover replicas..."),await St(rt,qt,(Bt)=>e(` ${Bt}`))}if(U.infrastructure?.storage)for(let[St,Bt]of Object.entries(U.infrastructure.storage)){if(!Bt.website||!Bt.root)continue;let un=qt[`${St}BucketName`]||qt.FrontendBucketName,Fn=qt[`${St}CloudFrontDistributionId`];if(!un){a(`Could not find bucket name for storage '${St}' — skipping file upload`);continue}if(!fr(Bt.root)){a(`Source directory not found: ${Bt.root} — skipping file upload for '${St}'`);continue}p(`Uploading files from ${Bt.root} to s3://${un}...`);let{uploadStaticFiles:Yr,invalidateCache:Ki}=await import("./chunk-98e48n5j.js"),Jn=await Yr({sourceDir:Bt.root,bucket:un,region:ve,onProgress:(tn,gr,vr)=>{if(tn%10===0||tn===gr)e(` ${tn}/${gr}: ${vr}`)}});if(Jn.errors.length>0)a(`Upload completed with errors: ${Jn.errors.join(", ")}`);else{let tn=Jn.skipped>0?`Uploaded ${Jn.uploaded} files (${Jn.skipped} unchanged)`:`Uploaded ${Jn.uploaded} files`;n(tn)}if(Fn&&Jn.uploaded>0){p("Invalidating CloudFront cache...");let{invalidationId:tn}=await Ki(Fn);n(`Cache invalidation created: ${tn}`)}}if(rt.length>0){let{seedFailoverReplicas:St}=await import("./chunk-5nf3fxgw.js");p("Syncing failover replicas..."),await St(rt,(Bt)=>e(` ${Bt}`))}if(U.infrastructure?.compute)await Ec(U,A,ve,t?.site);if(U.infrastructure?.compute&&U.sites){let St=Object.values(U.sites).map((Bt)=>Bt.domain).filter((Bt)=>!!Bt);if(St.length>0)p("Syncing CloudFront dynamic HTTP methods for app domains..."),await Vs(St)}}catch(z){if(r(`Deployment failed: ${z.message}`),process.exitCode=1,z.stack)e(`
396
+ Stack Outputs:`);for(let[Ct,Bt]of Object.entries(qt))e(` - ${Ct}: ${Bt}`)}if(rt.length>0){let{grantFailoverReplicaAccess:Ct}=await import("./chunk-12j17qm5.js");p("Granting the distribution read access to its failover replicas..."),await Ct(rt,qt,(Bt)=>e(` ${Bt}`))}if(U.infrastructure?.storage)for(let[Ct,Bt]of Object.entries(U.infrastructure.storage)){if(!Bt.website||!Bt.root)continue;let un=qt[`${Ct}BucketName`]||qt.FrontendBucketName,Fn=qt[`${Ct}CloudFrontDistributionId`];if(!un){a(`Could not find bucket name for storage '${Ct}' — skipping file upload`);continue}if(!fr(Bt.root)){a(`Source directory not found: ${Bt.root} — skipping file upload for '${Ct}'`);continue}p(`Uploading files from ${Bt.root} to s3://${un}...`);let{uploadStaticFiles:Yr,invalidateCache:Ki}=await import("./chunk-bmmr5mq6.js"),Jn=await Yr({sourceDir:Bt.root,bucket:un,region:ve,onProgress:(tn,gr,vr)=>{if(tn%10===0||tn===gr)e(` ${tn}/${gr}: ${vr}`)}});if(Jn.errors.length>0)a(`Upload completed with errors: ${Jn.errors.join(", ")}`);else{let tn=Jn.skipped>0?`Uploaded ${Jn.uploaded} files (${Jn.skipped} unchanged)`:`Uploaded ${Jn.uploaded} files`;n(tn)}if(Fn&&Jn.uploaded>0){p("Invalidating CloudFront cache...");let{invalidationId:tn}=await Ki(Fn);n(`Cache invalidation created: ${tn}`)}}if(rt.length>0){let{seedFailoverReplicas:Ct}=await import("./chunk-12j17qm5.js");p("Syncing failover replicas..."),await Ct(rt,(Bt)=>e(` ${Bt}`))}if(U.infrastructure?.compute)await Ec(U,A,ve,t?.site);if(U.infrastructure?.compute&&U.sites){let Ct=Object.values(U.sites).map((Bt)=>Bt.domain).filter((Bt)=>!!Bt);if(Ct.length>0)p("Syncing CloudFront dynamic HTTP methods for app domains..."),await Vs(Ct)}}catch(z){if(r(`Deployment failed: ${z.message}`),process.exitCode=1,z.stack)e(`
397
397
  Stack trace:`),console.error(z.stack)}finally{if(N)await N()}}),x.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 Oe(),A=t?.env||"production",N=m.project.region||"us-east-1",z=Pr(m);if(z){r(z),process.exitCode=1;return}if(!fo(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: ${N}`),e(`Environment: ${A}`),!await Ec(m,A,N,t?.site)){process.exitCode=1;return}if(m.sites){let X=Object.values(m.sites).map((te)=>te.domain).filter((te)=>!!te);if(X.length>0)p("Syncing CloudFront dynamic HTTP methods for app domains..."),await Vs(X)}n(`
398
- Server deployment complete!`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",N=Pr(m);if(N){r(N),process.exitCode=1;return}let{deployServerlessApp:z,redeployServerlessApp:U}=await import("./chunk-gz0gahzh.js");if(t?.redeploy){await U(m,A);return}await z(m,A,{skipBuild:t?.skipBuild,skipDeployHooks:t?.skipHooks})}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",{rollbackServerlessApp:N}=await import("./chunk-gz0gahzh.js");await N(m,A)}catch(m){r(`Rollback failed: ${m.message}`),process.exitCode=1}}),x.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)").action(async(t,m)=>{i("Rolling Back Release");try{let A=await Oe(),N=m?.env||"production",z=t||Object.keys(A.sites||{})[0];if(!z){r("No site configured to roll back."),process.exitCode=1;return}let{rollbackComputeSite:U}=await import("./chunk-hg60j47r.js"),X=await U({driver:O({config:A}),slug:A.project.slug,environment:N,logger:f},{siteName:z,to:m?.to});if(!X.success){r(`Rollback failed: ${X.error||"unknown error"}`),process.exitCode=1;return}for(let te of X.perInstance||[])e(` ${te.instanceId}: ${te.output?.trim()||te.status}`)}catch(A){r(`Rollback failed: ${A.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",z=t||Object.keys(A.sites||{})[0];if(!z){r("No site configured."),process.exitCode=1;return}let{getComputeDeployHistory:U}=await import("./chunk-hg60j47r.js"),X=await U({driver:O({config:A}),slug:A.project.slug,environment:N,logger:f},{siteName:z,limit:Number.parseInt(m?.limit||"20",10)||20});if(!X.success){r(`Could not read history: ${X.error||"unknown error"}`),process.exitCode=1;return}for(let te of X.perInstance||[])e(`
398
+ Server deployment complete!`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",N=Pr(m);if(N){r(N),process.exitCode=1;return}let{deployServerlessApp:z,redeployServerlessApp:U}=await import("./chunk-4er4ktcs.js");if(t?.redeploy){await U(m,A);return}await z(m,A,{skipBuild:t?.skipBuild,skipDeployHooks:t?.skipHooks})}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",{rollbackServerlessApp:N}=await import("./chunk-4er4ktcs.js");await N(m,A)}catch(m){r(`Rollback failed: ${m.message}`),process.exitCode=1}}),x.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)").action(async(t,m)=>{i("Rolling Back Release");try{let A=await Oe(),N=m?.env||"production",z=t||Object.keys(A.sites||{})[0];if(!z){r("No site configured to roll back."),process.exitCode=1;return}let{rollbackComputeSite:U}=await import("./chunk-prq5gw4j.js"),X=await U({driver:O({config:A}),slug:A.project.slug,environment:N,logger:f},{siteName:z,to:m?.to});if(!X.success){r(`Rollback failed: ${X.error||"unknown error"}`),process.exitCode=1;return}for(let te of X.perInstance||[])e(` ${te.instanceId}: ${te.output?.trim()||te.status}`)}catch(A){r(`Rollback failed: ${A.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",z=t||Object.keys(A.sites||{})[0];if(!z){r("No site configured."),process.exitCode=1;return}let{getComputeDeployHistory:U}=await import("./chunk-prq5gw4j.js"),X=await U({driver:O({config:A}),slug:A.project.slug,environment:N,logger:f},{siteName:z,limit:Number.parseInt(m?.limit||"20",10)||20});if(!X.success){r(`Could not read history: ${X.error||"unknown error"}`),process.exitCode=1;return}for(let te of X.perInstance||[])e(`
399
399
  ${te.instanceId}:`),e(te.output?.trimEnd()||"(no output)")}catch(A){r(`History lookup failed: ${A.message}`),process.exitCode=1}}),x.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,A)=>{i(`Running Recipe: ${t}`);try{if(!fr(m)){r(`Recipe script not found: ${m}`),process.exitCode=1;return}let N=await Oe(),z=A?.env||"production",U=Nc(m,"utf8").split(`
400
- `),{runComputeRecipe:X}=await import("./chunk-hg60j47r.js"),te=await X({driver:O({config:N}),slug:N.project.slug,environment:z,logger:f},{name:t,script:U,user:A?.user});for(let re of te.perInstance||[])e(`
401
- ${re.instanceId}:`),e(re.output?.trimEnd()||"(no output)");if(!te.success)process.exitCode=1}catch(N){r(`Recipe failed: ${N.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",{restoreDatabaseBackup:z}=await import("./chunk-hg60j47r.js"),U=await z({driver:O({config:A}),slug:A.project.slug,environment:N,logger:f},{database:C(A),from:t});for(let X of U.perInstance||[])e(` ${X.instanceId}: ${X.output?.trim()||X.status}`);if(!U.success)r(`Restore failed: ${U.error||"unknown error"}`),process.exitCode=1}catch(A){r(`Restore failed: ${A.message}`),process.exitCode=1}}),x.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 Oe(),{buildQuickDeployCi:A,inferQuickDeployProvider:N}=await import("./chunk-8c4fnga8.js"),z=t?.provider?.toLowerCase();if(z&&!["github","gitlab","bitbucket"].includes(z))throw Error(`Unsupported CI provider '${t?.provider}'. Use github, gitlab, or bitbucket.`);let U=z;if(!U)try{let te=Oc("git remote get-url origin",{stdio:["ignore","pipe","ignore"]}).toString().trim();U=N(te)}catch{}let X=A(m,t?.env||"production",{provider:U,site:t?.site,skipDnsVerification:t?.skipDnsVerification,setup:fr("pantry.lock")?"pantry":"bun",sshPrivateKeySecret:t?.sshKeySecret});if(!X){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(fr(X.path)&&!t?.force){a(`${X.path} already exists — re-run with --force to overwrite.`);return}vd(Tm(X.path),{recursive:!0}),Em(X.path,X.content),n(`Wrote ${X.path} (${X.provider}, deploys on push to '${X.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}}),x.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 Oe(),A=t?.env||"production",{setMaintenance:N}=await import("./chunk-gz0gahzh.js");await N(m,A,!0,t?.secret)}catch(m){r(`Failed to enable maintenance mode: ${m.message}`),process.exitCode=1}}),x.command("up","Bring the serverless app out of maintenance mode").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{try{let m=await Oe(),A=t?.env||"production",{setMaintenance:N}=await import("./chunk-gz0gahzh.js");await N(m,A,!1)}catch(m){r(`Failed to disable maintenance mode: ${m.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",{runAndRecordCommand:z}=await import("./chunk-gz0gahzh.js"),{id:U,output:X}=await z(A,N,t);if(e(X),U>0)e(`
402
- (recorded as #${U} — re-run with \`cloud command:again ${U}\`)`)}catch(A){r(`Command failed: ${A.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",{listCommandHistory:N}=await import("./chunk-gz0gahzh.js"),z=await N(m,A);if(!z.length){e("No command history yet.");return}let U=Number(t?.limit??20);i(`Command history — ${m.project.slug} (${A})`),c(["#","When","Status","Command"],z.slice(-U).reverse().map((X)=>[String(X.id),X.timestamp,X.status,X.command]))}catch(m){r(`Failed to read history: ${m.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",{getCommandRecord:z,runAndRecordCommand:U}=await import("./chunk-gz0gahzh.js"),X=await z(A,N,t?Number(t):void 0);if(!X){r(t?`No command #${t} in history.`:"No command history to re-run."),process.exitCode=1;return}e(`Re-running #${X.id}: ${X.command}`);let te=await U(A,N,X.command);if(e(te.output),te.id>0)e(`
403
- (recorded as #${te.id})`)}catch(A){r(`Command failed: ${A.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",{runDbQuery:z}=await import("./chunk-gz0gahzh.js"),U=await z(A,N,t);e(U)}catch(A){r(`Query failed: ${A.message}`),process.exitCode=1}}),x.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,A)=>{i("Scaling serverless database");try{let N=await Oe(),z=A?.env||"production",U=Number(t),X=Number(m);if(!(U>0)||!(X>=U)){r("Provide valid capacities: min > 0 and max >= min (e.g. `serverless:db-scale 0.5 8`)."),process.exitCode=1;return}let{scaleServerlessDatabase:te}=await import("./chunk-gz0gahzh.js");await te(N,z,U,X)}catch(N){r(`Scale failed: ${N.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",N;if(t?.to){if(N=new Date(t.to),Number.isNaN(N.getTime())){r(`Invalid --to timestamp: ${t.to} (use ISO-8601, e.g. 2026-06-18T10:00:00Z).`),process.exitCode=1;return}}let{restoreServerlessDatabase:z}=await import("./chunk-gz0gahzh.js");await z(m,A,{toTime:N,latest:t?.latest,target:t?.target})}catch(m){r(`Restore failed: ${m.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",{serverlessInfo:N}=await import("./chunk-gz0gahzh.js"),z=await N(m,A);if(i(`${z.slug} (${z.environment})`),e(`Stack: ${z.stackStatus}`),e(`Region: ${z.region}`),z.endpoint)e(`URL: ${z.endpoint}`);if(z.assetUrl)e(`Assets: ${z.assetUrl}`);if(e(`Scheduler:${z.scheduler==="off"?" off":` ${z.scheduler}`}`),e(`Queues: ${z.queues.length?z.queues.join(", "):"none"}`),e(`Warming: ${z.provisionedConcurrency>0?`provisioned concurrency ×${z.provisionedConcurrency}`:"none / ping-warm"}`),z.lastRelease)e(`Release: ${z.lastRelease.sha.slice(0,12)} @ ${z.lastRelease.timestamp}`);c(["Function","Version","Provisioned"],z.functions.map((U)=>[U.name,U.version,U.provisioned?`${U.provisioned.status} ${U.provisioned.allocated}/${U.provisioned.requested}`:"—"]))}catch(m){r(`Failed to read info: ${m.message}`),process.exitCode=1}}),x.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"),A=t?.ui||"ui";if(!m(`${A}/package.json`)){r(`No UI project at ${A}/ (expected ${A}/package.json). Pass --ui <dir>.`),process.exitCode=1;return}let N=await Oe(),z=t?.env||"production";p("Gathering live data from AWS");let U=null;if(N.environments?.[z]?.app){let{resolveDashboardData:oe}=await import("./chunk-hdkxj8p9.js"),ae=await oe(N,z).catch((ve)=>(a(`Serverless data unavailable (${ve.message}) — that view uses sample data.`),null));if(ae)U={...U??{},...ae}}if(N.infrastructure?.compute){let{resolveServerDashboardData:oe}=await import("./chunk-ckmgwd81.js"),ae=await oe(N,z).catch(()=>null);if(ae){if(ae._serverReachable===!1)a("Server box not reachable over SSM — server view uses config + sample data.");U={...U??{},...ae}}}p("Building dashboard");let{encodeDashboardPayload:X}=await import("./chunk-9jmt42fx.js"),{execSync:te}=await import("node:child_process"),{rmSync:re}=await import("node:fs");for(let oe of[".stx/ssg-cache",".stx/cache"])re(`${A}/${oe}`,{recursive:!0,force:!0});te("bun run build",{cwd:A,stdio:"inherit",env:{...process.env,...U?{TSCLOUD_DASHBOARD_DATA:X(U)}:{}}}),n(`Dashboard built → ${A}/dist${U?" (live data)":" (sample data)"}`)}catch(m){r(`Dashboard build failed: ${m.message}`),process.exitCode=1}}),x.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 Oe().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",N=t?.arch||"x86_64",z=t?.php||"8.3",U=`${t?.name||"tscloud-php"}-${z.replace(".","")}-${N}`,X=t?.bucket||`${m?.project.slug||"tscloud"}-layers`,{buildPhpRuntimeLayerZip:te}=await import("./chunk-g1krw34t.js"),{S3Client:re}=await import("./chunk-1d4663e1.js"),{LambdaClient:oe}=await import("./chunk-052sk11z.js"),ae=te({architecture:N,phpVersion:z,onStep:(ke)=>p(ke)});e(`Layer: ${ae.fileCount} files, ${(ae.zip.length/1024/1024).toFixed(1)} MB`);let ve=new re(A);if(!await ve.bucketExists(X))p(`Creating layer bucket ${X}`),await ve.createBucket(X);let Ce=`layers/${U}.zip`;p("Uploading layer zip"),await ve.putObject({bucket:X,key:Ce,body:ae.zip,contentType:"application/zip"}),p("Publishing layer version");let we=await new oe(A).publishLayerVersion({LayerName:U,Description:`ts-cloud PHP ${z} runtime (${N})`,Content:{S3Bucket:X,S3Key:Ce},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[N]});Se(["PHP runtime layer published","",`ARN: ${we.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'php', layers: ['${we.LayerVersionArn}'] }`,"or set TSCLOUD_PHP_LAYER_ARN before deploying."].join(`
404
- `),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),x.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 Oe().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",N=t?.arch||"x86_64",z=t?.node||"24",{buildNodeRuntimeLayerZip:U}=await import("./chunk-g1krw34t.js"),{S3Client:X}=await import("./chunk-1d4663e1.js"),{LambdaClient:te}=await import("./chunk-052sk11z.js"),re=U({architecture:N,version:z,onStep:(ke)=>p(ke)}),oe=`${t?.name||"tscloud-node"}-${re.version.split(".")[0]}-${N}`,ae=t?.bucket||`${m?.project.slug||"tscloud"}-layers`;e(`Layer: Node ${re.version}, ${(re.zip.length/1024/1024).toFixed(1)} MB`);let ve=new X(A);if(!await ve.bucketExists(ae))p(`Creating layer bucket ${ae}`),await ve.createBucket(ae);let Ce=`layers/${oe}.zip`;p("Uploading layer zip"),await ve.putObject({bucket:ae,key:Ce,body:re.zip,contentType:"application/zip"}),p("Publishing layer version");let we=await new te(A).publishLayerVersion({LayerName:oe,Description:`ts-cloud Node ${re.version} runtime (${N})`,Content:{S3Bucket:ae,S3Key:Ce},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[N]});Se(["Node runtime layer published","",`ARN: ${we.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'node', runtimeVersion: '${re.version.split(".")[0]}', layers: ['${we.LayerVersionArn}'] }`,"or set TSCLOUD_NODE_LAYER_ARN before deploying."].join(`
405
- `),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),x.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 Oe().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",N=t?.arch||"x86_64",z=t?.bun||"latest",{buildBunRuntimeLayerZip:U}=await import("./chunk-g1krw34t.js"),{S3Client:X}=await import("./chunk-1d4663e1.js"),{LambdaClient:te}=await import("./chunk-052sk11z.js"),re=U({architecture:N,version:z,onStep:(ke)=>p(ke)}),oe=`${t?.name||"tscloud-bun"}-${re.version.replace(/\./g,"")}-${N}`,ae=t?.bucket||`${m?.project.slug||"tscloud"}-layers`;e(`Layer: Bun ${re.version}, ${(re.zip.length/1024/1024).toFixed(1)} MB`);let ve=new X(A);if(!await ve.bucketExists(ae))p(`Creating layer bucket ${ae}`),await ve.createBucket(ae);let Ce=`layers/${oe}.zip`;p("Uploading layer zip"),await ve.putObject({bucket:ae,key:Ce,body:re.zip,contentType:"application/zip"}),p("Publishing layer version");let we=await new te(A).publishLayerVersion({LayerName:oe,Description:`ts-cloud Bun ${re.version} runtime (${N})`,Content:{S3Bucket:ae,S3Key:Ce},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[N]});Se(["Bun runtime layer published","",`ARN: ${we.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'bun', layers: ['${we.LayerVersionArn}'] }`,"or set TSCLOUD_BUN_LAYER_ARN before deploying."].join(`
400
+ `),{runComputeRecipe:X}=await import("./chunk-prq5gw4j.js"),te=await X({driver:O({config:N}),slug:N.project.slug,environment:z,logger:f},{name:t,script:U,user:A?.user});for(let re of te.perInstance||[])e(`
401
+ ${re.instanceId}:`),e(re.output?.trimEnd()||"(no output)");if(!te.success)process.exitCode=1}catch(N){r(`Recipe failed: ${N.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",{restoreDatabaseBackup:z}=await import("./chunk-prq5gw4j.js"),U=await z({driver:O({config:A}),slug:A.project.slug,environment:N,logger:f},{database:w(A),from:t});for(let X of U.perInstance||[])e(` ${X.instanceId}: ${X.output?.trim()||X.status}`);if(!U.success)r(`Restore failed: ${U.error||"unknown error"}`),process.exitCode=1}catch(A){r(`Restore failed: ${A.message}`),process.exitCode=1}}),x.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 Oe(),{buildQuickDeployCi:A,inferQuickDeployProvider:N}=await import("./chunk-79sd00d1.js"),z=t?.provider?.toLowerCase();if(z&&!["github","gitlab","bitbucket"].includes(z))throw Error(`Unsupported CI provider '${t?.provider}'. Use github, gitlab, or bitbucket.`);let U=z;if(!U)try{let te=Oc("git remote get-url origin",{stdio:["ignore","pipe","ignore"]}).toString().trim();U=N(te)}catch{}let X=A(m,t?.env||"production",{provider:U,site:t?.site,skipDnsVerification:t?.skipDnsVerification,setup:fr("pantry.lock")?"pantry":"bun",sshPrivateKeySecret:t?.sshKeySecret});if(!X){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(fr(X.path)&&!t?.force){a(`${X.path} already exists — re-run with --force to overwrite.`);return}vd(Tm(X.path),{recursive:!0}),Em(X.path,X.content),n(`Wrote ${X.path} (${X.provider}, deploys on push to '${X.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}}),x.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 Oe(),A=t?.env||"production",{setMaintenance:N}=await import("./chunk-4er4ktcs.js");await N(m,A,!0,t?.secret)}catch(m){r(`Failed to enable maintenance mode: ${m.message}`),process.exitCode=1}}),x.command("up","Bring the serverless app out of maintenance mode").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{try{let m=await Oe(),A=t?.env||"production",{setMaintenance:N}=await import("./chunk-4er4ktcs.js");await N(m,A,!1)}catch(m){r(`Failed to disable maintenance mode: ${m.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",{runAndRecordCommand:z}=await import("./chunk-4er4ktcs.js"),{id:U,output:X}=await z(A,N,t);if(e(X),U>0)e(`
402
+ (recorded as #${U} — re-run with \`cloud command:again ${U}\`)`)}catch(A){r(`Command failed: ${A.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",{listCommandHistory:N}=await import("./chunk-4er4ktcs.js"),z=await N(m,A);if(!z.length){e("No command history yet.");return}let U=Number(t?.limit??20);i(`Command history — ${m.project.slug} (${A})`),c(["#","When","Status","Command"],z.slice(-U).reverse().map((X)=>[String(X.id),X.timestamp,X.status,X.command]))}catch(m){r(`Failed to read history: ${m.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",{getCommandRecord:z,runAndRecordCommand:U}=await import("./chunk-4er4ktcs.js"),X=await z(A,N,t?Number(t):void 0);if(!X){r(t?`No command #${t} in history.`:"No command history to re-run."),process.exitCode=1;return}e(`Re-running #${X.id}: ${X.command}`);let te=await U(A,N,X.command);if(e(te.output),te.id>0)e(`
403
+ (recorded as #${te.id})`)}catch(A){r(`Command failed: ${A.message}`),process.exitCode=1}}),x.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 A=await Oe(),N=m?.env||"production",{runDbQuery:z}=await import("./chunk-4er4ktcs.js"),U=await z(A,N,t);e(U)}catch(A){r(`Query failed: ${A.message}`),process.exitCode=1}}),x.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,A)=>{i("Scaling serverless database");try{let N=await Oe(),z=A?.env||"production",U=Number(t),X=Number(m);if(!(U>0)||!(X>=U)){r("Provide valid capacities: min > 0 and max >= min (e.g. `serverless:db-scale 0.5 8`)."),process.exitCode=1;return}let{scaleServerlessDatabase:te}=await import("./chunk-4er4ktcs.js");await te(N,z,U,X)}catch(N){r(`Scale failed: ${N.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",N;if(t?.to){if(N=new Date(t.to),Number.isNaN(N.getTime())){r(`Invalid --to timestamp: ${t.to} (use ISO-8601, e.g. 2026-06-18T10:00:00Z).`),process.exitCode=1;return}}let{restoreServerlessDatabase:z}=await import("./chunk-4er4ktcs.js");await z(m,A,{toTime:N,latest:t?.latest,target:t?.target})}catch(m){r(`Restore failed: ${m.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",{serverlessInfo:N}=await import("./chunk-4er4ktcs.js"),z=await N(m,A);if(i(`${z.slug} (${z.environment})`),e(`Stack: ${z.stackStatus}`),e(`Region: ${z.region}`),z.endpoint)e(`URL: ${z.endpoint}`);if(z.assetUrl)e(`Assets: ${z.assetUrl}`);if(e(`Scheduler:${z.scheduler==="off"?" off":` ${z.scheduler}`}`),e(`Queues: ${z.queues.length?z.queues.join(", "):"none"}`),e(`Warming: ${z.provisionedConcurrency>0?`provisioned concurrency ×${z.provisionedConcurrency}`:"none / ping-warm"}`),z.lastRelease)e(`Release: ${z.lastRelease.sha.slice(0,12)} @ ${z.lastRelease.timestamp}`);c(["Function","Version","Provisioned"],z.functions.map((U)=>[U.name,U.version,U.provisioned?`${U.provisioned.status} ${U.provisioned.allocated}/${U.provisioned.requested}`:"—"]))}catch(m){r(`Failed to read info: ${m.message}`),process.exitCode=1}}),x.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"),A=t?.ui||"ui";if(!m(`${A}/package.json`)){r(`No UI project at ${A}/ (expected ${A}/package.json). Pass --ui <dir>.`),process.exitCode=1;return}let N=await Oe(),z=t?.env||"production";p("Gathering live data from AWS");let U=null;if(N.environments?.[z]?.app){let{resolveDashboardData:oe}=await import("./chunk-fnnv0t66.js"),ae=await oe(N,z).catch((ve)=>(a(`Serverless data unavailable (${ve.message}) — that view uses sample data.`),null));if(ae)U={...U??{},...ae}}if(N.infrastructure?.compute){let{resolveServerDashboardData:oe}=await import("./chunk-zmjm7bmm.js"),ae=await oe(N,z).catch(()=>null);if(ae){if(ae._serverReachable===!1)a("Server box not reachable over SSM — server view uses config + sample data.");U={...U??{},...ae}}}p("Building dashboard");let{encodeDashboardPayload:X}=await import("./chunk-9jmt42fx.js"),{execSync:te}=await import("node:child_process"),{rmSync:re}=await import("node:fs");for(let oe of[".stx/ssg-cache",".stx/cache"])re(`${A}/${oe}`,{recursive:!0,force:!0});te("bun run build",{cwd:A,stdio:"inherit",env:{...process.env,...U?{TSCLOUD_DASHBOARD_DATA:X(U)}:{}}}),n(`Dashboard built → ${A}/dist${U?" (live data)":" (sample data)"}`)}catch(m){r(`Dashboard build failed: ${m.message}`),process.exitCode=1}}),x.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 Oe().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",N=t?.arch||"x86_64",z=t?.php||"8.3",U=`${t?.name||"tscloud-php"}-${z.replace(".","")}-${N}`,X=t?.bucket||`${m?.project.slug||"tscloud"}-layers`,{buildPhpRuntimeLayerZip:te}=await import("./chunk-9eq18gds.js"),{S3Client:re}=await import("./chunk-yf3zys06.js"),{LambdaClient:oe}=await import("./chunk-9zhp2z0s.js"),ae=te({architecture:N,phpVersion:z,onStep:(ke)=>p(ke)});e(`Layer: ${ae.fileCount} files, ${(ae.zip.length/1024/1024).toFixed(1)} MB`);let ve=new re(A);if(!await ve.bucketExists(X))p(`Creating layer bucket ${X}`),await ve.createBucket(X);let Ce=`layers/${U}.zip`;p("Uploading layer zip"),await ve.putObject({bucket:X,key:Ce,body:ae.zip,contentType:"application/zip"}),p("Publishing layer version");let we=await new oe(A).publishLayerVersion({LayerName:U,Description:`ts-cloud PHP ${z} runtime (${N})`,Content:{S3Bucket:X,S3Key:Ce},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[N]});Se(["PHP runtime layer published","",`ARN: ${we.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'php', layers: ['${we.LayerVersionArn}'] }`,"or set TSCLOUD_PHP_LAYER_ARN before deploying."].join(`
404
+ `),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),x.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 Oe().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",N=t?.arch||"x86_64",z=t?.node||"24",{buildNodeRuntimeLayerZip:U}=await import("./chunk-9eq18gds.js"),{S3Client:X}=await import("./chunk-yf3zys06.js"),{LambdaClient:te}=await import("./chunk-9zhp2z0s.js"),re=U({architecture:N,version:z,onStep:(ke)=>p(ke)}),oe=`${t?.name||"tscloud-node"}-${re.version.split(".")[0]}-${N}`,ae=t?.bucket||`${m?.project.slug||"tscloud"}-layers`;e(`Layer: Node ${re.version}, ${(re.zip.length/1024/1024).toFixed(1)} MB`);let ve=new X(A);if(!await ve.bucketExists(ae))p(`Creating layer bucket ${ae}`),await ve.createBucket(ae);let Ce=`layers/${oe}.zip`;p("Uploading layer zip"),await ve.putObject({bucket:ae,key:Ce,body:re.zip,contentType:"application/zip"}),p("Publishing layer version");let we=await new te(A).publishLayerVersion({LayerName:oe,Description:`ts-cloud Node ${re.version} runtime (${N})`,Content:{S3Bucket:ae,S3Key:Ce},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[N]});Se(["Node runtime layer published","",`ARN: ${we.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'node', runtimeVersion: '${re.version.split(".")[0]}', layers: ['${we.LayerVersionArn}'] }`,"or set TSCLOUD_NODE_LAYER_ARN before deploying."].join(`
405
+ `),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),x.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 Oe().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",N=t?.arch||"x86_64",z=t?.bun||"latest",{buildBunRuntimeLayerZip:U}=await import("./chunk-9eq18gds.js"),{S3Client:X}=await import("./chunk-yf3zys06.js"),{LambdaClient:te}=await import("./chunk-9zhp2z0s.js"),re=U({architecture:N,version:z,onStep:(ke)=>p(ke)}),oe=`${t?.name||"tscloud-bun"}-${re.version.replace(/\./g,"")}-${N}`,ae=t?.bucket||`${m?.project.slug||"tscloud"}-layers`;e(`Layer: Bun ${re.version}, ${(re.zip.length/1024/1024).toFixed(1)} MB`);let ve=new X(A);if(!await ve.bucketExists(ae))p(`Creating layer bucket ${ae}`),await ve.createBucket(ae);let Ce=`layers/${oe}.zip`;p("Uploading layer zip"),await ve.putObject({bucket:ae,key:Ce,body:re.zip,contentType:"application/zip"}),p("Publishing layer version");let we=await new te(A).publishLayerVersion({LayerName:oe,Description:`ts-cloud Bun ${re.version} runtime (${N})`,Content:{S3Bucket:ae,S3Key:Ce},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[N]});Se(["Bun runtime layer published","",`ARN: ${we.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'bun', layers: ['${we.LayerVersionArn}'] }`,"or set TSCLOUD_BUN_LAYER_ARN before deploying."].join(`
406
406
  `),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),x.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 Oe(),A=t?.env||"production",N=t?.stack||E(m,A),z=m.project.region||"us-east-1";e(`Stack: ${N}`),e(`Region: ${z}`);let U=new o("Checking deployment status...");U.start();let te=await new v(z).describeStacks({stackName:N});if(te.Stacks.length===0){U.fail("Stack not found"),br("No deployment found for this environment");return}let re=te.Stacks[0];if(U.succeed("Status retrieved"),e(`
407
407
  Status: ${re.StackStatus}`),e(`Created: ${re.CreationTime}`),re.LastUpdatedTime)e(`Last Updated: ${re.LastUpdatedTime}`);if(re.Outputs&&re.Outputs.length>0){e(`
408
408
  Outputs:`);for(let oe of re.Outputs)e(` ${oe.OutputKey}: ${oe.OutputValue}`)}}catch(m){r(`Failed to get status: ${m.message}`)}}),x.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 Oe(),A=t?.env||"production",N=t?.stack||E(m,A),z=m.project.region||"us-east-1";if(e(`Stack: ${N}`),e(`Region: ${z}`),!await d(`
@@ -410,7 +410,7 @@ Are you sure you want to rollback?`,!1)){e("Rollback cancelled");return}let X=ne
410
410
  `);let U=Tc(m,void 0,z);if(!U.allowed){r(`
411
411
  ✗ Static deployment blocked by security policy: ${U.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
412
  `);let X=t?.bucket,te=t?.distribution,re=t?.prefix,oe=t?.delete||!1,ae=t?.cacheControl||"public, max-age=31536000",ve=t?.invalidate!==!1,Ce=t?.wait||!1;if(!X){r("--bucket is required");return}if(!fr(N)){r(`Source directory not found: ${N}`);return}if(e(`Source: ${N}`),e(`Bucket: s3://${X}${re?`/${re}`:""}`),e(`Cache-Control: ${ae}`),te)e(`CloudFront Distribution: ${te}`);if(oe)a("Delete mode enabled - files not in source will be removed");if(!await d(`
413
- Deploy static site now?`,!0)){e("Deployment cancelled");return}let we=new g(A),ke=new o("Uploading files to S3...");ke.start(),await we.sync({source:N,bucket:X,prefix:re,delete:oe,cacheControl:ae,acl:"public-read"}),ke.succeed("Files uploaded successfully!");let Ae=(await we.getBucketSize(X,re)/1024/1024).toFixed(2);if(e(`Total size: ${Ae} MB`),ve&&te){let Ie=new w,Te=new o("Invalidating CloudFront cache...");Te.start();let Xe=await Ie.invalidateAll(te);if(Te.succeed("Invalidation created"),e(`Invalidation ID: ${Xe.Id}`),Ce){let _e=new o("Waiting for invalidation to complete...");_e.start(),await Ie.waitForInvalidation(te,Xe.Id),_e.succeed("Invalidation completed!")}}Se(`Static Site Deployed!
413
+ Deploy static site now?`,!0)){e("Deployment cancelled");return}let we=new g(A),ke=new o("Uploading files to S3...");ke.start(),await we.sync({source:N,bucket:X,prefix:re,delete:oe,cacheControl:ae,acl:"public-read"}),ke.succeed("Files uploaded successfully!");let Ae=(await we.getBucketSize(X,re)/1024/1024).toFixed(2);if(e(`Total size: ${Ae} MB`),ve&&te){let Ie=new C,Te=new o("Invalidating CloudFront cache...");Te.start();let Xe=await Ie.invalidateAll(te);if(Te.succeed("Invalidation created"),e(`Invalidation ID: ${Xe.Id}`),Ce){let _e=new o("Waiting for invalidation to complete...");_e.start(),await Ie.waitForInvalidation(te,Xe.Id),_e.succeed("Invalidation completed!")}}Se(`Static Site Deployed!
414
414
 
415
415
  Source: ${N}
416
416
  Bucket: s3://${X}${re?`/${re}`:""}
@@ -422,17 +422,17 @@ https://${X}.s3.${A}.amazonaws.com${re?`/${re}`:""}/index.html`,"green")}catch(m
422
422
  `);let we=Tc(m,void 0,Re);if(!we.allowed){r(`
423
423
  ✗ Container deployment blocked by security policy: ${we.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: ${N}`),e(`Service: ${z}`),e(`Repository: ${U}`),e(`Image Tag: ${X}`),e(`Dockerfile: ${te}`),!await d(`
425
- Deploy container now?`,!0)){e("Deployment cancelled");return}let Be=new ht(A),Ae=new Pt(A),Ie=new o("Getting ECR credentials...");Ie.start();let Te=await Be.getAuthorizationToken();if(!Te.authorizationData?.[0]){Ie.fail("Failed to get ECR credentials");return}let Xe=Te.authorizationData[0],Ze=(Xe.proxyEndpoint||"").replace("https://","");Ie.succeed("ECR credentials obtained");let et=new o("Logging into ECR...");et.start();let At=Xe.authorizationToken||"",wt=Buffer.from(At,"base64").toString("utf8").split(":")[1],{spawn:Et}=await import("child_process"),sn=Et("docker",["login","--username","AWS","--password-stdin",Ze],{stdio:["pipe","pipe","pipe"]});sn.stdin.write(wt),sn.stdin.end(),await new Promise((tn,gr)=>{sn.on("close",(vr)=>{if(vr===0)tn();else gr(Error(`Docker login failed with code ${vr}`))})}),et.succeed("Logged into ECR");let Mt=new o("Building Docker image...");Mt.start();let rt=`${Ze}/${U}:${X}`,Vt=Et("docker",["build","-t",rt,"-f",te,re],{stdio:["pipe","pipe","pipe"]});await new Promise((tn,gr)=>{let vr="";Vt.stderr.on("data",(nn)=>{vr+=nn.toString()}),Vt.on("close",(nn)=>{if(nn===0)tn();else gr(Error(`Docker build failed: ${vr}`))})}),Mt.succeed("Docker image built");let qt=new o("Pushing image to ECR...");qt.start();let St=Et("docker",["push",rt],{stdio:["pipe","pipe","pipe"]});await new Promise((tn,gr)=>{let vr="";St.stderr.on("data",(nn)=>{vr+=nn.toString()}),St.on("close",(nn)=>{if(nn===0)tn();else gr(Error(`Docker push failed: ${vr}`))})}),qt.succeed("Image pushed to ECR");let Bt,un=Et("docker",["image","inspect","--format={{.Id}}",rt],{stdio:["ignore","pipe","pipe"]}),[Fn,Yr]=await Promise.all([new Promise((tn)=>un.on("close",tn)),new Response(un.stdout).text()]);if(Fn===0){let tn=Yr.trim();if(/^sha256:[a-f0-9]{64}$/i.test(tn))Bt=tn}let Ki=await Fm(m,{imageRef:rt,imageSha256:Bt,artifactRoot:re,invocationId:Ce,startedAt:ve});if(!Ki.allowed){r(`
425
+ Deploy container now?`,!0)){e("Deployment cancelled");return}let Be=new ht(A),Ae=new Pt(A),Ie=new o("Getting ECR credentials...");Ie.start();let Te=await Be.getAuthorizationToken();if(!Te.authorizationData?.[0]){Ie.fail("Failed to get ECR credentials");return}let Xe=Te.authorizationData[0],Ze=(Xe.proxyEndpoint||"").replace("https://","");Ie.succeed("ECR credentials obtained");let et=new o("Logging into ECR...");et.start();let At=Xe.authorizationToken||"",St=Buffer.from(At,"base64").toString("utf8").split(":")[1],{spawn:Et}=await import("child_process"),sn=Et("docker",["login","--username","AWS","--password-stdin",Ze],{stdio:["pipe","pipe","pipe"]});sn.stdin.write(St),sn.stdin.end(),await new Promise((tn,gr)=>{sn.on("close",(vr)=>{if(vr===0)tn();else gr(Error(`Docker login failed with code ${vr}`))})}),et.succeed("Logged into ECR");let Mt=new o("Building Docker image...");Mt.start();let rt=`${Ze}/${U}:${X}`,Vt=Et("docker",["build","-t",rt,"-f",te,re],{stdio:["pipe","pipe","pipe"]});await new Promise((tn,gr)=>{let vr="";Vt.stderr.on("data",(nn)=>{vr+=nn.toString()}),Vt.on("close",(nn)=>{if(nn===0)tn();else gr(Error(`Docker build failed: ${vr}`))})}),Mt.succeed("Docker image built");let qt=new o("Pushing image to ECR...");qt.start();let Ct=Et("docker",["push",rt],{stdio:["pipe","pipe","pipe"]});await new Promise((tn,gr)=>{let vr="";Ct.stderr.on("data",(nn)=>{vr+=nn.toString()}),Ct.on("close",(nn)=>{if(nn===0)tn();else gr(Error(`Docker push failed: ${vr}`))})}),qt.succeed("Image pushed to ECR");let Bt,un=Et("docker",["image","inspect","--format={{.Id}}",rt],{stdio:["ignore","pipe","pipe"]}),[Fn,Yr]=await Promise.all([new Promise((tn)=>un.on("close",tn)),new Response(un.stdout).text()]);if(Fn===0){let tn=Yr.trim();if(/^sha256:[a-f0-9]{64}$/i.test(tn))Bt=tn}let Ki=await Fm(m,{imageRef:rt,imageSha256:Bt,artifactRoot:re,invocationId:Ce,startedAt:ve});if(!Ki.allowed){r(`
426
426
  ✗ ECS service update blocked by release security policy: ${Ki.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
427
  `);let Jn=new o("Updating ECS service...");if(Jn.start(),await Ae.updateService({cluster:N,service:z,forceNewDeployment:oe}),Jn.succeed("ECS service updated"),ae){let tn=new o("Waiting for deployment to stabilize...");tn.start(),await Ae.waitForServiceStable(N,z),tn.succeed("Deployment stabilized")}n(`
428
428
  Container deployment complete!`),e(`
429
429
  Image: ${rt}`),e(`Cluster: ${N}`),e(`Service: ${z}`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}})}async function Mm(x){let t=process.cwd(),m=`${t}/.env`,A=`${t}/.env.local`,U=({stage:["stage","staging"],staging:["staging","stage"]}[x]||[x]).map((ae)=>`${t}/.env.${ae}`).find((ae)=>fr(ae));if(!U){if(fr(m))a(`No .env.${x} found, falling back to .env`);else e(`No .env.${x} or .env file found; using the existing process environment`);return null}let X=U.split("/").pop();p(`Loading environment file: ${X}`);let te=null,re=null;if(fr(A)){te=`${A}.bak`,gs(A,te);let ae=Nc(A,"utf-8");for(let Ce of ae.split(`
430
430
  `)){let Re=Ce.trim();if(!Re||Re.startsWith("#"))continue;let we=Re.indexOf("=");if(we===-1)continue;let ke=Re.slice(0,we).trim();delete process.env[ke]}let{unlinkSync:ve}=await import("node:fs");ve(A),e("Temporarily moved .env.local out of the way and purged its values from process.env")}if(fr(m))re=`${m}.bak`,gs(m,re);gs(U,m);let oe=Nc(U,"utf-8");for(let ae of oe.split(`
431
431
  `)){let ve=ae.trim();if(!ve||ve.startsWith("#"))continue;let Ce=ve.indexOf("=");if(Ce===-1)continue;let Re=ve.slice(0,Ce).trim(),we=ve.slice(Ce+1).trim();process.env[Re]=we}return n(`Loaded ${X}`),async()=>{let{unlinkSync:ae}=await import("node:fs");if(re)gs(re,`${t}/.env`),ae(re),e("Restored .env");if(te)gs(te,`${t}/.env.local`),ae(te),e("Restored .env.local")}}async function _m(x,t,m,A){if(!m)return;let N=Wo(x.sites||{});if(N.size===0)return;if(!t){a("DNS: no app server IP resolved — skipping A-record reconciliation.");return}let z;try{z=Wn(m)}catch(oe){a(`DNS: ${oe instanceof Error?oe.message:String(oe)}`),e(`Point each site domain at ${t} manually.`);return}if(!z){a(`DNS provider '${m}' is not configured — skipping A-record reconciliation.`),e(`Point each site domain at ${t} manually.`);return}if(z.provider==="route53"){let oe=x.infrastructure?.dns?.hostedZoneId;if(oe&&!z.hostedZoneId)z.hostedZoneId=oe}let U=M(z),X=x.infrastructure?.dns?.domain,te=ue(A),re=te?"A/AAAA":"A";p(`Reconciling DNS: ${N.size} ${re} record(s) → ${t} via ${m}...`);for(let oe of N){let ae=X&&oe.endsWith(X)?X:oe.split(".").slice(-2).join(".");try{let ve=await Gn({provider:U,zone:ae,fqdn:oe,ipv4:t,ipv6:te});for(let Ce of ve.published)n(` ✓ ${Ce.fqdn} ${Ce.type} → ${Ce.content}`);for(let Ce of ve.warnings)a(` ⚠ ${oe}: ${Ce}`)}catch(ve){a(` ⚠ ${oe}: ${ve instanceof Error?ve.message:String(ve)} — set the address record manually.`)}}}async function zm(x,t){let m=x.infrastructure?.dns?.records;if(!t||!m?.length)return;let A=x.infrastructure?.dns?.domain;if(!A){a("DNS records: infrastructure.dns.domain is not set — skipping declared records.");return}let N;try{N=Wn(t)}catch(X){a(`DNS records: ${X instanceof Error?X.message:String(X)}`);return}if(!N){a(`DNS records: provider '${t}' is not configured — skipping.`);return}let z=M(N),U=to(m,A);p(`Reconciling ${U.length} declared DNS record(s) on ${A}...`);try{let X=await ro({provider:z,zone:A,records:U});for(let te of X.outcomes){let{record:re,action:oe}=te,ae=`${re.type} ${re.name} -> ${re.content.slice(0,60)}`;if(oe==="failed")a(` ⚠ ${ae}: ${te.message||"failed"}`);else if(oe==="unchanged")e(` = ${ae}`);else n(` ✓ ${ae} (${oe})`)}for(let te of X.warnings)a(` ⚠ ${te}`)}catch(X){a(`DNS records: ${X instanceof Error?X.message:String(X)}`)}}async function Um(x,t,m){let{plan:A,errors:N}=Dc(x);for(let U of N)a(`Cloudflare CDN: ${U}`);if(!A)return;if(!t){a("Cloudflare CDN: no app server IP resolved — skipping.");return}p(`Cloudflare CDN: reconciling ${A.hosts.length} host(s) on ${A.zone}...`);let z=new Ye(A.apiToken,{zoneId:A.zoneId,accountId:A.accountId});try{let U=await xc({provider:z,zone:A.zone,hosts:A.hosts,ipv4:t,ipv6:ue(m),proxied:A.proxied,settings:A.settings,cache:A.cache,originGuard:A.originGuard,purge:A.purge,skipOriginProbe:A.skipOriginProbe});for(let X of U.records)n(` ✓ ${X.host} ${X.type} → ${X.content}${X.proxied?" (proxied)":" (DNS-only)"}`);for(let X of U.settingsChanged)e(` ✓ ${X.id}: ${JSON.stringify(X.from)} → ${JSON.stringify(X.to)}`);if(U.cacheRules>0)n(` ✓ ${U.cacheRules} cache rule(s) applied`);if(U.originGuard)n(" ✓ origin guard header applied");if(U.purged)n(" ✓ edge cache purged");for(let X of U.warnings)a(` ⚠ ${X}`)}catch(U){a(`Cloudflare CDN: ${U instanceof Error?U.message:String(U)}`)}}async function Vm(x,t,m,A,N,z,U=!1){let X=x.sites||{},te=t?[t]:Object.keys(X);if(te.length===0){a("No sites configured in cloud.config.ts");return}let re=null,oe=!1,ae=()=>{if(re||oe)return re;let ve=null;try{ve=Wn(m)}catch(Ce){r(Ce.message)}if(!ve)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"),oe=!0,null;if(ve.provider==="route53"){let Ce=x.infrastructure?.dns?.hostedZoneId;if(Ce&&!ve.hostedZoneId)ve.hostedZoneId=Ce}return re={config:ve,provider:M(ve)},re};for(let ve of te){let Ce=X[ve];if(!Ce){r(`Site '${ve}' not found in configuration`);continue}if(!cc(Ce)){let Ze=P(Ce);e(`Site '${ve}' is ${Ze} — handled outside the static bucket pipeline.`);continue}let Re=ae();if(!Re)return;let we=Ce.domain;if(!we){r(`Site '${ve}' has no domain configured`);continue}if(i(`Deploying Site: ${ve}`),e(`Domain: ${we}`),e(`Source: ${Ce.root}`),e(`DNS Provider: ${m}`),Ce.build){p(`Running build command: ${Ce.build}`);try{let{execSync:Ze}=await import("node:child_process");Ze(Ce.build,{stdio:"inherit",cwd:process.cwd()}),n("Build completed successfully")}catch(Ze){r(`Build failed: ${Ze.message}`);continue}}if(!fr(Ce.root)){if(r(`Source directory not found: ${Ce.root}`),!Ce.build)e('Run your build command first (e.g., bun run generate) or add a "build" option to your site config');continue}p("Checking existing DNS records...");let ke=await Re.provider.listRecords(we);if(ke.success&&ke.records.length>0){let Ze=we.split("."),et=Ze.length>2?Ze[0]:"@",At=Ze.slice(-2).join("."),Ft=ke.records.find((wt)=>wt.type==="CNAME"&&(wt.name===we||wt.name===et||wt.name===`${et}.${At}`));if(Ft){let wt=Ft.content.includes("netlify"),Et=Ft.content.includes("vercel");if(Ft.content.includes("cloudfront.net"))e(`Domain already points to CloudFront: ${Ft.content}`),e("Proceeding with file upload...");else{let Mt=wt?"Netlify":Et?"Vercel":"another provider";if(a(`
433
- Existing CNAME record detected:`),e(` ${Ft.name} -> ${Ft.content}`),wt||Et)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"),oe=!0,null;if(ve.provider==="route53"){let Ce=x.infrastructure?.dns?.hostedZoneId;if(Ce&&!ve.hostedZoneId)ve.hostedZoneId=Ce}return re={config:ve,provider:M(ve)},re};for(let ve of te){let Ce=X[ve];if(!Ce){r(`Site '${ve}' not found in configuration`);continue}if(!cc(Ce)){let Ze=P(Ce);e(`Site '${ve}' is ${Ze} — handled outside the static bucket pipeline.`);continue}let Re=ae();if(!Re)return;let we=Ce.domain;if(!we){r(`Site '${ve}' has no domain configured`);continue}if(i(`Deploying Site: ${ve}`),e(`Domain: ${we}`),e(`Source: ${Ce.root}`),e(`DNS Provider: ${m}`),Ce.build){p(`Running build command: ${Ce.build}`);try{let{execSync:Ze}=await import("node:child_process");Ze(Ce.build,{stdio:"inherit",cwd:process.cwd()}),n("Build completed successfully")}catch(Ze){r(`Build failed: ${Ze.message}`);continue}}if(!fr(Ce.root)){if(r(`Source directory not found: ${Ce.root}`),!Ce.build)e('Run your build command first (e.g., bun run generate) or add a "build" option to your site config');continue}p("Checking existing DNS records...");let ke=await Re.provider.listRecords(we);if(ke.success&&ke.records.length>0){let Ze=we.split("."),et=Ze.length>2?Ze[0]:"@",At=Ze.slice(-2).join("."),Ft=ke.records.find((St)=>St.type==="CNAME"&&(St.name===we||St.name===et||St.name===`${et}.${At}`));if(Ft){let St=Ft.content.includes("netlify"),Et=Ft.content.includes("vercel");if(Ft.content.includes("cloudfront.net"))e(`Domain already points to CloudFront: ${Ft.content}`),e("Proceeding with file upload...");else{let Mt=St?"Netlify":Et?"Vercel":"another provider";if(a(`
433
+ Existing CNAME record detected:`),e(` ${Ft.name} -> ${Ft.content}`),St||Et)e(`
434
434
  This domain is currently pointing to ${Mt}.`),e("Deploying will update this record to point to AWS CloudFront.");if(!(U||await d(`
435
- Update DNS record to point to AWS CloudFront?`,!0))){e("Deployment cancelled");continue}p(`Removing old ${Mt} CNAME record...`);let Vt=await Re.provider.deleteRecord(we,{name:Ft.name,type:"CNAME",content:Ft.content});if(Vt.success)n(`Removed CNAME: ${Ft.name} -> ${Ft.content}`);else a(`Could not remove old record: ${Vt.message}`),e("The deployment will attempt to update it instead.")}}}let Be=Ce.root,Ae,Ie=!!Ce.installScript;if(Ie){let{mkdtempSync:Ze,copyFileSync:et,readdirSync:At,statSync:Ft}=await import("node:fs"),{tmpdir:wt}=await import("node:os"),{join:Et,resolve:sn}=await import("node:path");Ae=Ze(Et(wt(),"cloud-install-"));let Mt=sn(Ce.installScript);et(Mt,Et(Ae,"index.html"));let rt=Mt.split("/").pop();if(rt!=="index.html")et(Mt,Et(Ae,rt));if(fr(Ce.root)){let Vt=At(Ce.root);for(let qt of Vt){let St=Et(Ce.root,qt);if(Ft(St).isFile()&&qt!=="index.html")et(St,Et(Ae,qt))}}Be=Ae,e(`Install script: ${Ce.installScript}`),e(`Serving at: curl -fsSL https://${we} | bash`)}p("Deploying to AWS (S3 + CloudFront)...");let Te=Ys(x,ve,Ce,z),Xe=Zs(x,ve);e(`Stack: ${Te}`);let _e=await Pi({siteName:Xe,stackName:Te,domain:we,region:A,bucket:ea(x.project.slug,z,ve,Ce.bucket),sourceDir:Be,certificateArn:Ce.certificateArn,dnsProvider:Re.config,skipDnsVerification:N,passthroughUrls:Ie,dynamicApp:!!x.infrastructure?.compute?.cloudFrontOriginDomain,computeOriginDomain:x.infrastructure?.compute?.cloudFrontOriginDomain,computeOriginPort:x.infrastructure?.compute?.cloudFrontOriginPort??x.infrastructure?.api?.port??3008,computeOriginId:x.infrastructure?.compute?.cloudFrontOriginId??`${x.project.slug}-site-ec2`,onProgress:(Ze,et)=>{if(Ze==="infrastructure")p(et||"Setting up infrastructure...");else if(Ze==="upload"){if(et?.includes("/")&&!et.includes("1/")){let At=et.match(/(\d+)\/(\d+)/);if(At){let[,Ft,wt]=At;if(Number(Ft)%10===0||Ft===wt)e(` Uploaded ${Ft}/${wt} files`)}}}else if(Ze==="invalidate")p("Invalidating CDN cache...")}});if(Ae){let{rmSync:Ze}=await import("node:fs");Ze(Ae,{recursive:!0,force:!0})}if(_e.success){n(`
435
+ Update DNS record to point to AWS CloudFront?`,!0))){e("Deployment cancelled");continue}p(`Removing old ${Mt} CNAME record...`);let Vt=await Re.provider.deleteRecord(we,{name:Ft.name,type:"CNAME",content:Ft.content});if(Vt.success)n(`Removed CNAME: ${Ft.name} -> ${Ft.content}`);else a(`Could not remove old record: ${Vt.message}`),e("The deployment will attempt to update it instead.")}}}let Be=Ce.root,Ae,Ie=!!Ce.installScript;if(Ie){let{mkdtempSync:Ze,copyFileSync:et,readdirSync:At,statSync:Ft}=await import("node:fs"),{tmpdir:St}=await import("node:os"),{join:Et,resolve:sn}=await import("node:path");Ae=Ze(Et(St(),"cloud-install-"));let Mt=sn(Ce.installScript);et(Mt,Et(Ae,"index.html"));let rt=Mt.split("/").pop();if(rt!=="index.html")et(Mt,Et(Ae,rt));if(fr(Ce.root)){let Vt=At(Ce.root);for(let qt of Vt){let Ct=Et(Ce.root,qt);if(Ft(Ct).isFile()&&qt!=="index.html")et(Ct,Et(Ae,qt))}}Be=Ae,e(`Install script: ${Ce.installScript}`),e(`Serving at: curl -fsSL https://${we} | bash`)}p("Deploying to AWS (S3 + CloudFront)...");let Te=Ys(x,ve,Ce,z),Xe=Zs(x,ve);e(`Stack: ${Te}`);let _e=await Pi({siteName:Xe,stackName:Te,domain:we,region:A,bucket:ea(x.project.slug,z,ve,Ce.bucket),sourceDir:Be,certificateArn:Ce.certificateArn,dnsProvider:Re.config,skipDnsVerification:N,passthroughUrls:Ie,dynamicApp:!!x.infrastructure?.compute?.cloudFrontOriginDomain,computeOriginDomain:x.infrastructure?.compute?.cloudFrontOriginDomain,computeOriginPort:x.infrastructure?.compute?.cloudFrontOriginPort??x.infrastructure?.api?.port??3008,computeOriginId:x.infrastructure?.compute?.cloudFrontOriginId??`${x.project.slug}-site-ec2`,onProgress:(Ze,et)=>{if(Ze==="infrastructure")p(et||"Setting up infrastructure...");else if(Ze==="upload"){if(et?.includes("/")&&!et.includes("1/")){let At=et.match(/(\d+)\/(\d+)/);if(At){let[,Ft,St]=At;if(Number(Ft)%10===0||Ft===St)e(` Uploaded ${Ft}/${St} files`)}}}else if(Ze==="invalidate")p("Invalidating CDN cache...")}});if(Ae){let{rmSync:Ze}=await import("node:fs");Ze(Ae,{recursive:!0,force:!0})}if(_e.success){n(`
436
436
  Deployment successful!`);let Ze=_e.filesSkipped>0?`${_e.filesUploaded} uploaded, ${_e.filesSkipped} unchanged`:`${_e.filesUploaded}`,et=Ie?`
437
437
  Install: curl -fsSL https://${_e.domain} | bash`:"";Se(`Site Deployed!
438
438
 
@@ -441,7 +441,7 @@ CloudFront: ${_e.distributionDomain}
441
441
  Bucket: ${_e.bucket}
442
442
  Files: ${Ze}${et}
443
443
 
444
- Your site is now live at https://${_e.domain}`,"green")}else throw Error(_e.message||`Site '${ve}' deployment failed`)}}import{execFileSync as Wm}from"node:child_process";import{createHash as qm}from"node:crypto";import{existsSync as $d,mkdirSync as Gm,mkdtempSync as Hm,readdirSync as Km,readFileSync as Jm,rmSync as Ym,statSync as Cd,writeFileSync as Zm}from"node:fs";import{tmpdir as Qm}from"node:os";import{basename as Xm,join as Oo,relative as ef,resolve as jc}from"node:path";var tf=new Set([".git","node_modules","dist","coverage"]);function Ad(x,t=x){let m=[];for(let A of Km(x).sort()){if(tf.has(A))continue;let N=Oo(x,A);if(Js(N,t))continue;let z=Cd(N);if(z.isDirectory())m.push(...Ad(N,t));else if(z.isFile())m.push(ef(t,N))}return m}function nf(x){let t=jc(x);if(!$d(t)||!Cd(t).isDirectory())throw Error(`Container context does not exist: ${t}`);let m=qm("sha256");for(let A of Ad(t))m.update(A),m.update("\x00"),m.update(Jm(Oo(t,A))),m.update("\x00");return m.digest("hex")}function rf(x){return{ecr:new ht(x.region||"us-east-1",x.profile),run:(t,m,A)=>{Wm(t,m,{cwd:A?.cwd,env:A?.env,stdio:A?.stdio||"inherit"})}}}async function kd(x,t){let m=jc(x.context),A=jc(x.dockerfile||Oo(m,"Dockerfile"));if(!$d(A))throw Error(`Dockerfile does not exist: ${A}`);if(!x.repository.split("/").every((Re)=>/^[a-z0-9][a-z0-9._-]*$/.test(Re)&&!/[._-]$/.test(Re)&&!/[._-]{2}/.test(Re)))throw Error("ECR repository name is invalid");let U=t||rf(x),X=nf(m),te=`sha-${X.slice(0,20)}`,re="";try{re=(await U.ecr.describeRepositories({repositoryNames:[x.repository]})).repositories?.[0]?.repositoryUri||""}catch{}if(!re)re=(await U.ecr.createRepository({repositoryName:x.repository,imageTagMutability:"IMMUTABLE",imageScanningConfiguration:{scanOnPush:!0},tags:[{Key:"ManagedBy",Value:"ts-cloud"}]})).repository?.repositoryUri||"";if(!re)throw Error(`Could not resolve ECR repository URI for ${x.repository}`);await U.ecr.putLifecyclePolicy({repositoryName:x.repository,lifecyclePolicyText:JSON.stringify({rules:[{rulePriority:1,description:"Retain the newest 25 release images",selection:{tagStatus:"any",countType:"imageCountMoreThan",countNumber:25},action:{type:"expire"}}]})});let oe=`${re}:${te}`,ae=Hm(Oo(Qm(),"ts-cloud-container-"));try{U.run("docker",["version"],{stdio:"ignore"}),U.run("docker",["build","--platform",x.platform||"linux/amd64","--provenance=false","--sbom=false","--file",A,"--tag",oe,m],{cwd:m});let Re=(await U.ecr.getAuthorizationToken()).authorizationData?.[0];if(!Re?.authorizationToken||!Re.proxyEndpoint)throw Error("ECR did not return Docker authorization data");let we=Re.proxyEndpoint.replace(/^https?:\/\//,""),ke=Oo(ae,"docker");Gm(ke,{recursive:!0}),Zm(Oo(ke,"config.json"),JSON.stringify({auths:{[we]:{auth:Re.authorizationToken}}}),{mode:384}),U.run("docker",["push",oe],{env:{...process.env,DOCKER_CONFIG:ke}})}finally{Ym(ae,{recursive:!0,force:!0})}let Ce=(await U.ecr.describeImages({repositoryName:x.repository,imageIds:[{imageTag:te}]})).imageDetails?.[0]?.imageDigest;if(!Ce||!/^sha256:[a-f0-9]{64}$/i.test(Ce))throw Error(`ECR did not return an immutable digest for ${Xm(oe)}`);return{repository:x.repository,tag:te,imageUri:oe,digest:Ce,digestUri:`${re}@${Ce}`,contextSha256:X}}import{createHash as of}from"node:crypto";function sf(x){let t=x;return String(t?.code||t?.name||t?.statusCode||"")}function af(x){return sf(x)==="ValidationError"&&String(x.message).includes("does not exist")}function cf(x){return Object.fromEntries((x||[]).map((t)=>[t.OutputKey,t.OutputValue]))}function lf(x){let t=x?.Aliases?.Items??x?.Aliases,m=t?.CNAME??t;if(Array.isArray(m))return m.map(String);if(typeof m==="string")return[m];return[]}function Rd(x){if(!/^[a-z][a-z0-9-]{1,62}$/.test(x.slug))throw Error("Slug must be a lowercase DNS-safe identifier");if(!/^[A-Z0-9]{8,32}$/.test(x.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(x.expectedAlias))throw Error("Expected distribution alias is invalid");let t=x.imageUri.match(/@((?:sha256:)[a-f0-9]{64})$/i)?.[1];if(!t)throw Error("Container image must use an immutable ECR digest URI ending in @sha256:<64 hex>");let m=x.stackName||`${x.slug}-backend`;if(!/^[A-Za-z][A-Za-z0-9-]{0,127}$/.test(m))throw Error("CloudFormation stack name is invalid");let A=x.pathPattern||"/api/*";if(!/^\/[A-Za-z0-9_.*?~!$&'()+,;=:@%/-]+\*$/.test(A)||A==="/*")throw Error("Backend path must be a non-default wildcard pattern");let N=x.originId||`${x.slug}-backend`;if(!/^[A-Za-z0-9_.-]{1,128}$/.test(N))throw Error("CloudFront origin ID is invalid");if(x.originVerifySecret&&x.originVerifySecret.length<24)throw Error("Origin verification secret must contain at least 24 characters");if(!!x.originDomain!==!!x.certificateArn)throw Error("A custom origin domain and matching ACM certificate ARN must be provided together");return{stackName:m,region:x.region||"us-east-1",pathPattern:A,originId:N,digest:t}}function Fc(x){let t=x.region||"us-east-1";return{cloudformation:new v(t,x.profile),cloudfront:new w(x.profile),sts:new ct(t,x.profile),fetch,sleep:(m)=>new Promise((A)=>setTimeout(A,m))}}function df(x){let t=Rd(x),m=ua({name:x.name,slug:x.slug,domain:x.originDomain||x.expectedAlias,imageUri:x.imageUri,certificateArn:x.certificateArn,originVerifySecret:x.originVerifySecret,desiredCount:x.desiredCount,database:x.database,cache:x.cache,queue:x.queue}),A=da(m);return A.Description=`Managed backend for existing static frontend ${x.expectedAlias}`,A.Metadata={ManagedBy:"ts-cloud",ExistingDistributionId:x.distributionId,ExistingDistributionAlias:x.expectedAlias,BackendPathPattern:t.pathPattern,ContainerDigest:t.digest},A}async function uf(x,t,m){let A;for(let N=0;N<30;N++){let z=Date.now();try{let U=await x.fetch(t,{redirect:"manual",headers:m?{"x-origin-verify":m}:void 0}),X=Date.now()-z,te=await U.text(),re=te;try{re=JSON.parse(te)}catch{}if(U.ok)return{url:t,status:U.status,latencyMs:X,body:re};A=Error(`origin returned ${U.status}`)}catch(U){A=U}if(N<29)await x.sleep(1e4)}throw Error(`Backend origin did not become healthy: ${A instanceof Error?A.message:String(A)}`)}async function Id(x,t){let m=Rd(x),A=t||Fc({region:m.region,profile:x.profile}),[N,z]=await Promise.all([A.sts.getCallerIdentity(),A.cloudfront.getDistribution(x.distributionId)]);if(!N.Account)throw Error("AWS identity did not return an account ID");if(!z.Enabled)throw Error(`CloudFront distribution ${x.distributionId} is disabled`);if(!lf(z).includes(x.expectedAlias))throw Error(`Distribution ${x.distributionId} does not contain expected alias ${x.expectedAlias}`);let U;try{U=(await A.cloudformation.describeStacks({stackName:m.stackName})).Stacks[0]}catch(Te){if(!af(Te))throw Te}let X=df(x),te=JSON.stringify(X);if(Buffer.byteLength(te)>51200)throw Error("Generated template exceeds the direct CloudFormation template body limit");let re=Object.values(X.Resources||{}).reduce((Te,Xe)=>(Te[Xe.Type]=(Te[Xe.Type]||0)+1,Te),{}),oe=[`cloud cdn:origin:remove ${x.distributionId} ${x.originDomain||"ALB_HOST"} --id ${m.originId} --path '${m.pathPattern}' --profile ${x.profile||"default"} --apply --confirm 'remove:${x.distributionId}:${m.pathPattern}'`,`After CloudFront is deployed, restore external DNS if changed and delete stack ${m.stackName} only with an approved data-retention decision.`],ae={mode:x.apply?"apply":"plan",accountId:N.Account,stack:{name:m.stackName,existed:!!U,status:U?.StackStatus,templateSha256:of("sha256").update(te).digest("hex"),resourceCount:Object.keys(X.Resources||{}).length,resourceTypes:re},distribution:{id:z.Id,alias:x.expectedAlias,domainName:z.DomainName,status:z.Status,pathPattern:m.pathPattern,originId:m.originId},artifact:{imageUri:x.imageUri,digest:m.digest},services:{database:x.database!==!1,cache:x.cache!==!1,queue:x.queue!==!1,mail:"ses",desiredCount:x.desiredCount||1},cloudFrontApplied:!1,dnsApplied:!1,applied:!1,rollback:oe};if(!x.apply)return ae;let ve=`${x.distributionId}:${m.pathPattern}:${m.stackName}`;if(x.confirm!==ve)throw Error(`Pass exact confirmation token ${ve} to provision the backend and change live routing`);if(U)try{await A.cloudformation.updateStack({stackName:m.stackName,templateBody:te,capabilities:["CAPABILITY_NAMED_IAM"],tags:[{Key:"ManagedBy",Value:"ts-cloud"},{Key:"Application",Value:x.slug}]}),await A.cloudformation.waitForStack(m.stackName,"stack-update-complete")}catch(Te){if(!String(Te.message).includes("No updates are to be performed"))throw Te}else await A.cloudformation.createStack({stackName:m.stackName,templateBody:te,capabilities:["CAPABILITY_NAMED_IAM"],onFailure:"ROLLBACK",tags:[{Key:"ManagedBy",Value:"ts-cloud"},{Key:"Application",Value:x.slug}]}),await A.cloudformation.waitForStack(m.stackName,"stack-create-complete");let Ce=(await A.cloudformation.describeStacks({stackName:m.stackName})).Stacks[0],Re=cf(Ce?.Outputs),we=Re.AppLoadBalancerDnsName;if(!we)throw Error("Backend stack did not return AppLoadBalancerDnsName");let ke=!1;if(x.originDomain&&x.originDomain!==we){if(!A.dns)throw Error(`External DNS provider is required to map ${x.originDomain} to ${we}`);let Te=await A.dns.upsertRecord(x.originDomain,{name:x.originDomain,type:"CNAME",content:we,ttl:300});if(!Te.success)throw Error(Te.message||"External DNS update failed");ke=!0}let Be=x.originDomain||we,Ae=x.certificateArn?"https-only":"http-only",Ie=x.skipHealthCheck?void 0:await uf(A,`${Ae==="https-only"?"https":"http"}://${Be}/api/health`,x.originVerifySecret);return await A.cloudfront.upsertExistingDistributionOrigin(x.distributionId,{id:m.originId,domainName:Be,pathPattern:m.pathPattern,protocolPolicy:Ae,customHeaders:x.originVerifySecret?{"X-Origin-Verify":x.originVerifySecret}:void 0}),{...ae,outputs:Re,health:Ie,cloudFrontApplied:!0,dnsApplied:ke,applied:!0}}function Pd(x={}){let m={fargate:730*(x.desiredCount||1)*0.012342500000000001,applicationLoadBalancer:16.425,natGateway:32.85,postgres:730*(x.multiAzDatabase?0.032:0.016),redis:23.36};return{monthlyUsd:Number(Object.values(m).reduce((A,N)=>A+N,0).toFixed(2)),components:Object.fromEntries(Object.entries(m).map(([A,N])=>[A,Number(N.toFixed(2))])),assumptions:["us-east-1 public on-demand rate snapshot, July 2026","one 0.25 vCPU/0.5 GB Linux Fargate task unless desiredCount is changed","one ALB before LCU charges and one NAT gateway before data processing","single-AZ db.t4g.micro PostgreSQL by default and two cache.t4g.micro Redis nodes","excludes storage, backups, CloudFront, data transfer, public IPv4, logs, email, queue requests, taxes, and free-tier credits"]}}function Bc(x){x.command("container:artifact <repository>","Build, scan, push, and resolve an immutable ECR container artifact").option("--context <path>","Docker build context",{default:"."}).option("--dockerfile <path>","Dockerfile path").option("--platform <platform>","Target platform",{default:"linux/amd64"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").action(async(t,m)=>{try{let A=await kd({repository:t,context:m.context||".",dockerfile:m.dockerfile,platform:m.platform,region:m.region,profile:m.profile});n("Immutable container artifact published."),e(JSON.stringify(A,null,2))}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.command("deploy:fullstack <distributionId> <alias>","Plan or deploy an ECS/RDS/Redis/SQS/SES backend behind an existing static site").option("--name <name>","Application display name").option("--slug <slug>","Application slug").option("--image <digest-uri>","Immutable ECR image URI with @sha256 digest").option("--stack <name>","Backend CloudFormation stack name").option("--path <pattern>","CloudFront backend path",{default:"/api/*"}).option("--origin-id <id>","CloudFront origin ID").option("--origin-domain <domain>","DNS name for the ALB origin").option("--certificate-arn <arn>","ACM certificate matching the origin domain").option("--dns-provider <provider>","External DNS provider (porkbun, godaddy, cloudflare, route53)").option("--origin-secret-env <name>","Environment variable holding the CloudFront-to-ALB secret",{default:"TS_CLOUD_ORIGIN_SECRET"}).option("--desired-count <count>","Baseline Fargate task count",{default:"1"}).option("--no-database","Do not provision PostgreSQL").option("--no-cache","Do not provision Redis").option("--no-queue","Do not provision SQS").option("--region <region>","AWS region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").option("--skip-health-check","Skip the ALB health gate before CloudFront").option("--apply","Provision and change live routing").option("--confirm <text>","Exact distribution:path:stack confirmation").action(async(t,m,A)=>{try{if(!A.slug)throw Error("--slug is required");if(!A.image)throw Error("--image must be an immutable digest URI from container:artifact");let N=A.originSecretEnv||"TS_CLOUD_ORIGIN_SECRET",z=process.env[N];if(A.apply&&!z)throw Error(`${N} must contain a stable random CloudFront-to-ALB secret before apply`);let U=Fc({region:A.region,profile:A.profile}),X=A.originDomain&&A.apply?{...U,dns:gi(A.dnsProvider)}:U,te=await Id({name:A.name||A.slug,slug:A.slug,imageUri:A.image,distributionId:t,expectedAlias:m,stackName:A.stack,pathPattern:A.path,originId:A.originId,originDomain:A.originDomain,certificateArn:A.certificateArn,originVerifySecret:z,desiredCount:Number(A.desiredCount)||1,database:A.database!==!1,cache:A.cache!==!1,queue:A.queue!==!1,region:A.region,profile:A.profile,skipHealthCheck:!!A.skipHealthCheck,apply:!!A.apply,confirm:A.confirm},X);if(e(JSON.stringify(te,null,2)),te.applied)n("Full-stack backend is healthy and the CloudFront path patch was submitted.");else e(`Plan only. Apply with --confirm '${t}:${te.distribution.pathPattern}:${te.stack.name}'.`)}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.command("deploy:fullstack:cost","Estimate the always-on full-stack backend baseline").option("--desired-count <count>","Fargate task count",{default:"1"}).option("--multi-az-database","Estimate a two-instance database baseline").action((t)=>e(JSON.stringify(Pd({desiredCount:Number(t.desiredCount)||1,multiAzDatabase:!!t.multiAzDatabase}),null,2)))}function pf(x){return x.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function xd(x,t="/var/www"){let m=`${t}/${x}-`;return["set -u",`for TS_CLOUD_DIR in ${m}*; do`,' [ -d "$TS_CLOUD_DIR" ] || continue',' [ -d "$TS_CLOUD_DIR/releases" ] || continue',` TS_CLOUD_SITE="\${TS_CLOUD_DIR#${m}}"`," TS_CLOUD_ACTIVE=no",` for TS_CLOUD_UNIT in $(ls /etc/systemd/system/ 2>/dev/null | grep -E "^${pf(x)}-$TS_CLOUD_SITE[-@.]" || true); do`,' systemctl is-active "$TS_CLOUD_UNIT" >/dev/null 2>&1 && TS_CLOUD_ACTIVE=yes'," done",' TS_CLOUD_SIZE="$(du -sh "$TS_CLOUD_DIR" 2>/dev/null | cut -f1)"',' echo "site:$TS_CLOUD_SITE:$TS_CLOUD_ACTIVE:${TS_CLOUD_SIZE:-?}"',"done","exit 0"]}function Dd(x){if(!x)return[];let t=[];for(let m of x.split(`
444
+ Your site is now live at https://${_e.domain}`,"green")}else throw Error(_e.message||`Site '${ve}' deployment failed`)}}import{execFileSync as Wm}from"node:child_process";import{createHash as qm}from"node:crypto";import{existsSync as $d,mkdirSync as Gm,mkdtempSync as Hm,readdirSync as Km,readFileSync as Jm,rmSync as Ym,statSync as Cd,writeFileSync as Zm}from"node:fs";import{tmpdir as Qm}from"node:os";import{basename as Xm,join as Oo,relative as ef,resolve as jc}from"node:path";var tf=new Set([".git","node_modules","dist","coverage"]);function Ad(x,t=x){let m=[];for(let A of Km(x).sort()){if(tf.has(A))continue;let N=Oo(x,A);if(Js(N,t))continue;let z=Cd(N);if(z.isDirectory())m.push(...Ad(N,t));else if(z.isFile())m.push(ef(t,N))}return m}function nf(x){let t=jc(x);if(!$d(t)||!Cd(t).isDirectory())throw Error(`Container context does not exist: ${t}`);let m=qm("sha256");for(let A of Ad(t))m.update(A),m.update("\x00"),m.update(Jm(Oo(t,A))),m.update("\x00");return m.digest("hex")}function rf(x){return{ecr:new ht(x.region||"us-east-1",x.profile),run:(t,m,A)=>{Wm(t,m,{cwd:A?.cwd,env:A?.env,stdio:A?.stdio||"inherit"})}}}async function kd(x,t){let m=jc(x.context),A=jc(x.dockerfile||Oo(m,"Dockerfile"));if(!$d(A))throw Error(`Dockerfile does not exist: ${A}`);if(!x.repository.split("/").every((Re)=>/^[a-z0-9][a-z0-9._-]*$/.test(Re)&&!/[._-]$/.test(Re)&&!/[._-]{2}/.test(Re)))throw Error("ECR repository name is invalid");let U=t||rf(x),X=nf(m),te=`sha-${X.slice(0,20)}`,re="";try{re=(await U.ecr.describeRepositories({repositoryNames:[x.repository]})).repositories?.[0]?.repositoryUri||""}catch{}if(!re)re=(await U.ecr.createRepository({repositoryName:x.repository,imageTagMutability:"IMMUTABLE",imageScanningConfiguration:{scanOnPush:!0},tags:[{Key:"ManagedBy",Value:"ts-cloud"}]})).repository?.repositoryUri||"";if(!re)throw Error(`Could not resolve ECR repository URI for ${x.repository}`);await U.ecr.putLifecyclePolicy({repositoryName:x.repository,lifecyclePolicyText:JSON.stringify({rules:[{rulePriority:1,description:"Retain the newest 25 release images",selection:{tagStatus:"any",countType:"imageCountMoreThan",countNumber:25},action:{type:"expire"}}]})});let oe=`${re}:${te}`,ae=Hm(Oo(Qm(),"ts-cloud-container-"));try{U.run("docker",["version"],{stdio:"ignore"}),U.run("docker",["build","--platform",x.platform||"linux/amd64","--provenance=false","--sbom=false","--file",A,"--tag",oe,m],{cwd:m});let Re=(await U.ecr.getAuthorizationToken()).authorizationData?.[0];if(!Re?.authorizationToken||!Re.proxyEndpoint)throw Error("ECR did not return Docker authorization data");let we=Re.proxyEndpoint.replace(/^https?:\/\//,""),ke=Oo(ae,"docker");Gm(ke,{recursive:!0}),Zm(Oo(ke,"config.json"),JSON.stringify({auths:{[we]:{auth:Re.authorizationToken}}}),{mode:384}),U.run("docker",["push",oe],{env:{...process.env,DOCKER_CONFIG:ke}})}finally{Ym(ae,{recursive:!0,force:!0})}let Ce=(await U.ecr.describeImages({repositoryName:x.repository,imageIds:[{imageTag:te}]})).imageDetails?.[0]?.imageDigest;if(!Ce||!/^sha256:[a-f0-9]{64}$/i.test(Ce))throw Error(`ECR did not return an immutable digest for ${Xm(oe)}`);return{repository:x.repository,tag:te,imageUri:oe,digest:Ce,digestUri:`${re}@${Ce}`,contextSha256:X}}import{createHash as of}from"node:crypto";function sf(x){let t=x;return String(t?.code||t?.name||t?.statusCode||"")}function af(x){return sf(x)==="ValidationError"&&String(x.message).includes("does not exist")}function cf(x){return Object.fromEntries((x||[]).map((t)=>[t.OutputKey,t.OutputValue]))}function lf(x){let t=x?.Aliases?.Items??x?.Aliases,m=t?.CNAME??t;if(Array.isArray(m))return m.map(String);if(typeof m==="string")return[m];return[]}function Rd(x){if(!/^[a-z][a-z0-9-]{1,62}$/.test(x.slug))throw Error("Slug must be a lowercase DNS-safe identifier");if(!/^[A-Z0-9]{8,32}$/.test(x.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(x.expectedAlias))throw Error("Expected distribution alias is invalid");let t=x.imageUri.match(/@((?:sha256:)[a-f0-9]{64})$/i)?.[1];if(!t)throw Error("Container image must use an immutable ECR digest URI ending in @sha256:<64 hex>");let m=x.stackName||`${x.slug}-backend`;if(!/^[A-Za-z][A-Za-z0-9-]{0,127}$/.test(m))throw Error("CloudFormation stack name is invalid");let A=x.pathPattern||"/api/*";if(!/^\/[A-Za-z0-9_.*?~!$&'()+,;=:@%/-]+\*$/.test(A)||A==="/*")throw Error("Backend path must be a non-default wildcard pattern");let N=x.originId||`${x.slug}-backend`;if(!/^[A-Za-z0-9_.-]{1,128}$/.test(N))throw Error("CloudFront origin ID is invalid");if(x.originVerifySecret&&x.originVerifySecret.length<24)throw Error("Origin verification secret must contain at least 24 characters");if(!!x.originDomain!==!!x.certificateArn)throw Error("A custom origin domain and matching ACM certificate ARN must be provided together");return{stackName:m,region:x.region||"us-east-1",pathPattern:A,originId:N,digest:t}}function Fc(x){let t=x.region||"us-east-1";return{cloudformation:new v(t,x.profile),cloudfront:new C(x.profile),sts:new ct(t,x.profile),fetch,sleep:(m)=>new Promise((A)=>setTimeout(A,m))}}function df(x){let t=Rd(x),m=ua({name:x.name,slug:x.slug,domain:x.originDomain||x.expectedAlias,imageUri:x.imageUri,certificateArn:x.certificateArn,originVerifySecret:x.originVerifySecret,desiredCount:x.desiredCount,database:x.database,cache:x.cache,queue:x.queue}),A=da(m);return A.Description=`Managed backend for existing static frontend ${x.expectedAlias}`,A.Metadata={ManagedBy:"ts-cloud",ExistingDistributionId:x.distributionId,ExistingDistributionAlias:x.expectedAlias,BackendPathPattern:t.pathPattern,ContainerDigest:t.digest},A}async function uf(x,t,m){let A;for(let N=0;N<30;N++){let z=Date.now();try{let U=await x.fetch(t,{redirect:"manual",headers:m?{"x-origin-verify":m}:void 0}),X=Date.now()-z,te=await U.text(),re=te;try{re=JSON.parse(te)}catch{}if(U.ok)return{url:t,status:U.status,latencyMs:X,body:re};A=Error(`origin returned ${U.status}`)}catch(U){A=U}if(N<29)await x.sleep(1e4)}throw Error(`Backend origin did not become healthy: ${A instanceof Error?A.message:String(A)}`)}async function Id(x,t){let m=Rd(x),A=t||Fc({region:m.region,profile:x.profile}),[N,z]=await Promise.all([A.sts.getCallerIdentity(),A.cloudfront.getDistribution(x.distributionId)]);if(!N.Account)throw Error("AWS identity did not return an account ID");if(!z.Enabled)throw Error(`CloudFront distribution ${x.distributionId} is disabled`);if(!lf(z).includes(x.expectedAlias))throw Error(`Distribution ${x.distributionId} does not contain expected alias ${x.expectedAlias}`);let U;try{U=(await A.cloudformation.describeStacks({stackName:m.stackName})).Stacks[0]}catch(Te){if(!af(Te))throw Te}let X=df(x),te=JSON.stringify(X);if(Buffer.byteLength(te)>51200)throw Error("Generated template exceeds the direct CloudFormation template body limit");let re=Object.values(X.Resources||{}).reduce((Te,Xe)=>(Te[Xe.Type]=(Te[Xe.Type]||0)+1,Te),{}),oe=[`cloud cdn:origin:remove ${x.distributionId} ${x.originDomain||"ALB_HOST"} --id ${m.originId} --path '${m.pathPattern}' --profile ${x.profile||"default"} --apply --confirm 'remove:${x.distributionId}:${m.pathPattern}'`,`After CloudFront is deployed, restore external DNS if changed and delete stack ${m.stackName} only with an approved data-retention decision.`],ae={mode:x.apply?"apply":"plan",accountId:N.Account,stack:{name:m.stackName,existed:!!U,status:U?.StackStatus,templateSha256:of("sha256").update(te).digest("hex"),resourceCount:Object.keys(X.Resources||{}).length,resourceTypes:re},distribution:{id:z.Id,alias:x.expectedAlias,domainName:z.DomainName,status:z.Status,pathPattern:m.pathPattern,originId:m.originId},artifact:{imageUri:x.imageUri,digest:m.digest},services:{database:x.database!==!1,cache:x.cache!==!1,queue:x.queue!==!1,mail:"ses",desiredCount:x.desiredCount||1},cloudFrontApplied:!1,dnsApplied:!1,applied:!1,rollback:oe};if(!x.apply)return ae;let ve=`${x.distributionId}:${m.pathPattern}:${m.stackName}`;if(x.confirm!==ve)throw Error(`Pass exact confirmation token ${ve} to provision the backend and change live routing`);if(U)try{await A.cloudformation.updateStack({stackName:m.stackName,templateBody:te,capabilities:["CAPABILITY_NAMED_IAM"],tags:[{Key:"ManagedBy",Value:"ts-cloud"},{Key:"Application",Value:x.slug}]}),await A.cloudformation.waitForStack(m.stackName,"stack-update-complete")}catch(Te){if(!String(Te.message).includes("No updates are to be performed"))throw Te}else await A.cloudformation.createStack({stackName:m.stackName,templateBody:te,capabilities:["CAPABILITY_NAMED_IAM"],onFailure:"ROLLBACK",tags:[{Key:"ManagedBy",Value:"ts-cloud"},{Key:"Application",Value:x.slug}]}),await A.cloudformation.waitForStack(m.stackName,"stack-create-complete");let Ce=(await A.cloudformation.describeStacks({stackName:m.stackName})).Stacks[0],Re=cf(Ce?.Outputs),we=Re.AppLoadBalancerDnsName;if(!we)throw Error("Backend stack did not return AppLoadBalancerDnsName");let ke=!1;if(x.originDomain&&x.originDomain!==we){if(!A.dns)throw Error(`External DNS provider is required to map ${x.originDomain} to ${we}`);let Te=await A.dns.upsertRecord(x.originDomain,{name:x.originDomain,type:"CNAME",content:we,ttl:300});if(!Te.success)throw Error(Te.message||"External DNS update failed");ke=!0}let Be=x.originDomain||we,Ae=x.certificateArn?"https-only":"http-only",Ie=x.skipHealthCheck?void 0:await uf(A,`${Ae==="https-only"?"https":"http"}://${Be}/api/health`,x.originVerifySecret);return await A.cloudfront.upsertExistingDistributionOrigin(x.distributionId,{id:m.originId,domainName:Be,pathPattern:m.pathPattern,protocolPolicy:Ae,customHeaders:x.originVerifySecret?{"X-Origin-Verify":x.originVerifySecret}:void 0}),{...ae,outputs:Re,health:Ie,cloudFrontApplied:!0,dnsApplied:ke,applied:!0}}function Pd(x={}){let m={fargate:730*(x.desiredCount||1)*0.012342500000000001,applicationLoadBalancer:16.425,natGateway:32.85,postgres:730*(x.multiAzDatabase?0.032:0.016),redis:23.36};return{monthlyUsd:Number(Object.values(m).reduce((A,N)=>A+N,0).toFixed(2)),components:Object.fromEntries(Object.entries(m).map(([A,N])=>[A,Number(N.toFixed(2))])),assumptions:["us-east-1 public on-demand rate snapshot, July 2026","one 0.25 vCPU/0.5 GB Linux Fargate task unless desiredCount is changed","one ALB before LCU charges and one NAT gateway before data processing","single-AZ db.t4g.micro PostgreSQL by default and two cache.t4g.micro Redis nodes","excludes storage, backups, CloudFront, data transfer, public IPv4, logs, email, queue requests, taxes, and free-tier credits"]}}function Bc(x){x.command("container:artifact <repository>","Build, scan, push, and resolve an immutable ECR container artifact").option("--context <path>","Docker build context",{default:"."}).option("--dockerfile <path>","Dockerfile path").option("--platform <platform>","Target platform",{default:"linux/amd64"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").action(async(t,m)=>{try{let A=await kd({repository:t,context:m.context||".",dockerfile:m.dockerfile,platform:m.platform,region:m.region,profile:m.profile});n("Immutable container artifact published."),e(JSON.stringify(A,null,2))}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.command("deploy:fullstack <distributionId> <alias>","Plan or deploy an ECS/RDS/Redis/SQS/SES backend behind an existing static site").option("--name <name>","Application display name").option("--slug <slug>","Application slug").option("--image <digest-uri>","Immutable ECR image URI with @sha256 digest").option("--stack <name>","Backend CloudFormation stack name").option("--path <pattern>","CloudFront backend path",{default:"/api/*"}).option("--origin-id <id>","CloudFront origin ID").option("--origin-domain <domain>","DNS name for the ALB origin").option("--certificate-arn <arn>","ACM certificate matching the origin domain").option("--dns-provider <provider>","External DNS provider (porkbun, godaddy, cloudflare, route53)").option("--origin-secret-env <name>","Environment variable holding the CloudFront-to-ALB secret",{default:"TS_CLOUD_ORIGIN_SECRET"}).option("--desired-count <count>","Baseline Fargate task count",{default:"1"}).option("--no-database","Do not provision PostgreSQL").option("--no-cache","Do not provision Redis").option("--no-queue","Do not provision SQS").option("--region <region>","AWS region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").option("--skip-health-check","Skip the ALB health gate before CloudFront").option("--apply","Provision and change live routing").option("--confirm <text>","Exact distribution:path:stack confirmation").action(async(t,m,A)=>{try{if(!A.slug)throw Error("--slug is required");if(!A.image)throw Error("--image must be an immutable digest URI from container:artifact");let N=A.originSecretEnv||"TS_CLOUD_ORIGIN_SECRET",z=process.env[N];if(A.apply&&!z)throw Error(`${N} must contain a stable random CloudFront-to-ALB secret before apply`);let U=Fc({region:A.region,profile:A.profile}),X=A.originDomain&&A.apply?{...U,dns:gi(A.dnsProvider)}:U,te=await Id({name:A.name||A.slug,slug:A.slug,imageUri:A.image,distributionId:t,expectedAlias:m,stackName:A.stack,pathPattern:A.path,originId:A.originId,originDomain:A.originDomain,certificateArn:A.certificateArn,originVerifySecret:z,desiredCount:Number(A.desiredCount)||1,database:A.database!==!1,cache:A.cache!==!1,queue:A.queue!==!1,region:A.region,profile:A.profile,skipHealthCheck:!!A.skipHealthCheck,apply:!!A.apply,confirm:A.confirm},X);if(e(JSON.stringify(te,null,2)),te.applied)n("Full-stack backend is healthy and the CloudFront path patch was submitted.");else e(`Plan only. Apply with --confirm '${t}:${te.distribution.pathPattern}:${te.stack.name}'.`)}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.command("deploy:fullstack:cost","Estimate the always-on full-stack backend baseline").option("--desired-count <count>","Fargate task count",{default:"1"}).option("--multi-az-database","Estimate a two-instance database baseline").action((t)=>e(JSON.stringify(Pd({desiredCount:Number(t.desiredCount)||1,multiAzDatabase:!!t.multiAzDatabase}),null,2)))}function pf(x){return x.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function xd(x,t="/var/www"){let m=`${t}/${x}-`;return["set -u",`for TS_CLOUD_DIR in ${m}*; do`,' [ -d "$TS_CLOUD_DIR" ] || continue',' [ -d "$TS_CLOUD_DIR/releases" ] || continue',` TS_CLOUD_SITE="\${TS_CLOUD_DIR#${m}}"`," TS_CLOUD_ACTIVE=no",` for TS_CLOUD_UNIT in $(ls /etc/systemd/system/ 2>/dev/null | grep -E "^${pf(x)}-$TS_CLOUD_SITE[-@.]" || true); do`,' systemctl is-active "$TS_CLOUD_UNIT" >/dev/null 2>&1 && TS_CLOUD_ACTIVE=yes'," done",' TS_CLOUD_SIZE="$(du -sh "$TS_CLOUD_DIR" 2>/dev/null | cut -f1)"',' echo "site:$TS_CLOUD_SITE:$TS_CLOUD_ACTIVE:${TS_CLOUD_SIZE:-?}"',"done","exit 0"]}function Dd(x){if(!x)return[];let t=[];for(let m of x.split(`
445
445
  `)){let A=m.trim();if(!A.startsWith("site:"))continue;let[,N,z,U]=A.split(":");if(!N||z!=="no")continue;t.push({name:N,size:U||"?"})}return t}function Ed(x,t,m){let A=x.map((z)=>` ${t}-${z.name} (${z.size})`).join(`
446
446
  `),N=x.length===1?"it":"them";return`This server still holds ${x.length} site tree${x.length===1?"":"s"} with nothing running for ${N}:
447
447
  ${A}
@@ -457,7 +457,7 @@ Resources (${te.StackResourceSummaries.length}):`);let re=["Logical ID","Type","
457
457
  ... and ${te.StackResourceSummaries.length-10} more resources`)}}catch(m){r(`Failed to describe stack: ${m.message}`)}}),x.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 N=(await Oe()).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 U=new v(N),X=new o("Deleting stack...");X.start(),await U.deleteStack(t),X.succeed("Stack deletion initiated"),p("Waiting for stack deletion..."),await U.waitForStack(t,"stack-delete-complete"),n("Stack deleted successfully!")}catch(A){r(`Failed to delete stack: ${A.message}`)}}),x.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 N=(await Oe()).project.region||"us-east-1",z=new v(N),U=new o("Loading events...");U.start();let X=await z.describeStackEvents(t);U.succeed(`Found ${X.StackEvents.length} events`);let te=m?.limit?Number.parseInt(m.limit):20,re=X.StackEvents.slice(0,te);if(re.length===0){e("No events found");return}let oe=["Time","Resource","Status","Reason"],ae=re.map((ve)=>[new Date(ve.Timestamp).toLocaleString(),ve.LogicalResourceId,ve.ResourceStatus,ve.ResourceStatusReason||""]);c(oe,ae)}catch(A){r(`Failed to load events: ${A.message}`)}}),x.command("stack:outputs STACK_NAME","Show stack outputs").action(async(t)=>{i(`Stack Outputs: ${t}`);try{let A=(await Oe()).project.region||"us-east-1",N=new v(A),z=new o("Loading stack outputs...");z.start();let U=await N.describeStacks({stackName:t});if(!U.Stacks||U.Stacks.length===0){z.fail("Stack not found");return}let X=U.Stacks[0];if(z.succeed("Stack outputs loaded"),!X.Outputs||X.Outputs.length===0){e("No outputs found for this stack");return}let te=["Key","Value","Description","Export Name"],re=X.Outputs.map((oe)=>[oe.OutputKey||"",oe.OutputValue||"",oe.Description||"",oe.ExportName||""]);c(te,re),e(`
459
459
  Copy-friendly format:`);for(let oe of X.Outputs)e(`${oe.OutputKey}=${oe.OutputValue}`)}catch(m){r(`Failed to load outputs: ${m.message}`)}}),x.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 N=(await Oe()).project.region||"us-east-1",z=new v(N),U=new o("Fetching stack template...");U.start();let X=await z.getTemplate(t);if(!X.TemplateBody){U.fail("Template not found");return}U.succeed("Template fetched");let te=m?.format||"json",re=X.TemplateBody;if(te==="json"){let oe=JSON.parse(re);re=JSON.stringify(oe,null,2)}if(m?.output){let oe=m.output;hf(oe,re,"utf-8"),n(`Template exported to: ${oe}`);let ve=(gf(oe).size/1024).toFixed(2);e(`File size: ${ve} KB`)}else e(`
460
- Template:`),console.log(re)}catch(A){r(`Failed to export template: ${A.message}`)}})}import{existsSync as yf}from"node:fs";function zc(x,t){x.command("upgrade","Upgrade CLI to latest version").action(async()=>{Dr("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."})}),x.command("doctor","Check system requirements and AWS credentials").action(async()=>{if(i("System Diagnostics"),p("Checking Bun..."),n(`Bun ${process.versions.bun}`),p("Checking AWS CLI..."),await Rs())n("AWS CLI is installed");else r("AWS CLI is not installed"),e("Install: https://aws.amazon.com/cli/");if(p("Checking AWS credentials..."),await Ps()){n("AWS credentials are configured");let z=await Ns();if(z)e(`Account ID: ${z}`)}else r("AWS credentials are not configured"),e("Run: aws configure");p("Checking CloudFront list access...");let N=!1;try{let{CloudFrontClient:z}=await import("./chunk-675txngy.js");await new z().listDistributions(),n("CloudFront list access is enabled"),N=!0}catch(z){a(`CloudFront list check failed: ${z.message}`)}if(N){p("Checking CloudFront create access...");try{let z=new v("us-east-1"),U=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 z.validateTemplate(U),n("CloudFront create access appears enabled")}catch(z){if(z.message?.includes("403")||z.message?.includes("AccessDenied")||z.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)")}}p("Checking S3 access...");try{let{S3Client:z}=await import("./chunk-1d4663e1.js");await new z("us-east-1").listBuckets(),n("S3 access is enabled")}catch(z){a(`S3 check failed: ${z.message}`)}p("Checking ACM (SSL certificates) access...");try{await new G("us-east-1").listCertificates(),n("ACM access is enabled")}catch(z){a(`ACM check failed: ${z.message}`)}if(p("Checking configuration..."),yf("cloud.config.ts"))n("cloud.config.ts found");else a("cloud.config.ts not found"),e("Run: cloud init")}),x.command("regions","List available AWS regions").action(async()=>{i("AWS Regions");let m=new o("Fetching regions...");m.start();let A=await Ds();m.stop(),A.forEach((N)=>{console.log(` ${N}`)})}),x.command("version","Show the version of the CLI").alias("v").action(()=>{console.log(`ts-cloud v${t}`)})}function Uc(x){x.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 q(t.region),A=new o("Fetching sites...");A.start();let N=await m.scan({TableName:t.table,FilterExpression:"begins_with(pk, :pk)",ExpressionAttributeValues:{":pk":{S:"SITE#"}}});if(A.succeed(`Found ${N.Items?.length||0} site(s)`),!N.Items||N.Items.length===0){e("No analytics sites found"),e("Use `cloud analytics:sites:create` to create a new site");return}let z=N.Items.map((U)=>q.unmarshal(U));c(["ID","Name","Domains","Active","Created"],z.map((U)=>[U.siteId||"N/A",U.name||"Unnamed",(U.domains||[]).join(", ")||"-",U.isActive?"Yes":"No",U.createdAt?new Date(U.createdAt).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list sites: ${m.message}`),process.exit(1)}}),x.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 q(t.region),A=t.name||await I("Site name","My Site"),N=[];if(t.domain)N=Array.isArray(t.domain)?t.domain:[t.domain];else N=(await I("Site domains (comma-separated)","example.com")).split(",").map((oe)=>oe.trim()).filter(Boolean);if(e(`
460
+ Template:`),console.log(re)}catch(A){r(`Failed to export template: ${A.message}`)}})}import{existsSync as yf}from"node:fs";function zc(x,t){x.command("upgrade","Upgrade CLI to latest version").action(async()=>{Dr("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."})}),x.command("doctor","Check system requirements and AWS credentials").action(async()=>{if(i("System Diagnostics"),p("Checking Bun..."),n(`Bun ${process.versions.bun}`),p("Checking AWS CLI..."),await Rs())n("AWS CLI is installed");else r("AWS CLI is not installed"),e("Install: https://aws.amazon.com/cli/");if(p("Checking AWS credentials..."),await Ps()){n("AWS credentials are configured");let z=await Ns();if(z)e(`Account ID: ${z}`)}else r("AWS credentials are not configured"),e("Run: aws configure");p("Checking CloudFront list access...");let N=!1;try{let{CloudFrontClient:z}=await import("./chunk-3932p1z3.js");await new z().listDistributions(),n("CloudFront list access is enabled"),N=!0}catch(z){a(`CloudFront list check failed: ${z.message}`)}if(N){p("Checking CloudFront create access...");try{let z=new v("us-east-1"),U=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 z.validateTemplate(U),n("CloudFront create access appears enabled")}catch(z){if(z.message?.includes("403")||z.message?.includes("AccessDenied")||z.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)")}}p("Checking S3 access...");try{let{S3Client:z}=await import("./chunk-yf3zys06.js");await new z("us-east-1").listBuckets(),n("S3 access is enabled")}catch(z){a(`S3 check failed: ${z.message}`)}p("Checking ACM (SSL certificates) access...");try{await new G("us-east-1").listCertificates(),n("ACM access is enabled")}catch(z){a(`ACM check failed: ${z.message}`)}if(p("Checking configuration..."),yf("cloud.config.ts"))n("cloud.config.ts found");else a("cloud.config.ts not found"),e("Run: cloud init")}),x.command("regions","List available AWS regions").action(async()=>{i("AWS Regions");let m=new o("Fetching regions...");m.start();let A=await Ds();m.stop(),A.forEach((N)=>{console.log(` ${N}`)})}),x.command("version","Show the version of the CLI").alias("v").action(()=>{console.log(`ts-cloud v${t}`)})}function Uc(x){x.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 q(t.region),A=new o("Fetching sites...");A.start();let N=await m.scan({TableName:t.table,FilterExpression:"begins_with(pk, :pk)",ExpressionAttributeValues:{":pk":{S:"SITE#"}}});if(A.succeed(`Found ${N.Items?.length||0} site(s)`),!N.Items||N.Items.length===0){e("No analytics sites found"),e("Use `cloud analytics:sites:create` to create a new site");return}let z=N.Items.map((U)=>q.unmarshal(U));c(["ID","Name","Domains","Active","Created"],z.map((U)=>[U.siteId||"N/A",U.name||"Unnamed",(U.domains||[]).join(", ")||"-",U.isActive?"Yes":"No",U.createdAt?new Date(U.createdAt).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list sites: ${m.message}`),process.exit(1)}}),x.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 q(t.region),A=t.name||await I("Site name","My Site"),N=[];if(t.domain)N=Array.isArray(t.domain)?t.domain:[t.domain];else N=(await I("Site domains (comma-separated)","example.com")).split(",").map((oe)=>oe.trim()).filter(Boolean);if(e(`
461
461
  Creating site: ${A}`),e(`Domains: ${N.join(", ")}`),!await d(`
462
462
  Create this site?`,!0)){e("Operation cancelled");return}let U=new o("Creating site...");U.start();let X=`site_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,te=new Date().toISOString();await m.putItem({TableName:t.table,Item:{pk:{S:`SITE#${X}`},sk:{S:`SITE#${X}`},siteId:{S:X},name:{S:A},domains:{L:N.map((re)=>({S:re}))},isActive:{BOOL:!0},createdAt:{S:te},updatedAt:{S:te}}}),U.succeed("Site created successfully"),n(`
463
463
  Site ID: ${X}`),e(`
@@ -475,19 +475,19 @@ 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 Ld(x){let t=x;return String(t?.code||t?.name||t?.statusCode||"")}function Rf(x){return["NoSuchEntity","ResourceNotFoundException","404"].includes(Ld(x))}function Nd(x){return["EntityAlreadyExists","ResourceAlreadyExistsException","ResourceConflictException","409"].includes(Ld(x))}function Od(x,t){let m=x?.Items;if(!m)return[];if(Array.isArray(m))return m;let A=m[t];if(A===void 0)return[];return Array.isArray(A)?A:[A]}function Wc(x){let t=x?.Aliases?.Items,m=t?.CNAME??t;if(Array.isArray(m))return m.map(String);if(typeof m==="string")return[m];return[]}function If(x){if(!/^[A-Z0-9]{8,32}$/.test(x.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(x.expectedAlias))throw Error("Expected CloudFront alias is invalid");if(!/^[A-Za-z0-9-_]{1,64}$/.test(x.functionName))throw Error("Lambda function name is invalid");let t=x.pathPattern?.startsWith("/")?x.pathPattern:`/${x.pathPattern||"api/*"}`;if(t==="/"||t==="/*"||!t.endsWith("*"))throw Error("API path must be a non-default wildcard pattern such as /api/*");let m=x.originId||`${x.functionName}-url`,A=x.roleName||`${x.functionName}-execution`;if(!/^[A-Za-z0-9_.-]{1,64}$/.test(A))throw Error("IAM role name is invalid");let N=x.memorySize??256,z=x.timeout??10,U=x.logRetentionDays??14;if(N<128||N>10240)throw Error("Lambda memory must be between 128 and 10240 MB");if(z<1||z>900)throw Error("Lambda timeout must be between 1 and 900 seconds");return{region:x.region||"us-east-1",pathPattern:t,originId:m,roleName:A,memorySize:N,timeout:z,logRetentionDays:U}}function Pf(x){let t=x.region||"us-east-1";return{cloudfront:new w(x.profile),iam:new ee(t,x.profile),lambda:new R(t,x.profile),logs:new ie(t,x.profile),sts:new ct(t,x.profile),sleep:(m)=>new Promise((A)=>setTimeout(A,m))}}async function jd(x,t){let m=If(x),A=t||Pf({profile:x.profile,region:m.region}),[N,z,U]=await Promise.all([A.sts.getCallerIdentity(),A.cloudfront.getDistribution(x.distributionId),A.cloudfront.getDistributionConfig(x.distributionId)]);if(!N.Account)throw Error("AWS identity did not return an account ID");if(!z.Enabled)throw Error(`CloudFront distribution ${x.distributionId} is disabled`);if(!(Wc(z).length?Wc(z):Wc(U.DistributionConfig)).includes(x.expectedAlias))throw Error(`Distribution ${x.distributionId} does not contain expected alias ${x.expectedAlias}`);let te,re=!0;try{te=await A.iam.getRole({RoleName:m.roleName})}catch(Mt){if(!Rf(Mt))throw Mt;re=!1}let oe=te?.Arn||`arn:aws:iam::${N.Account}:role/${m.roleName}`,ae=await A.lambda.functionExists(x.functionName),ve=ae?await A.lambda.getFunctionUrl(x.functionName):null,Ce=await A.cloudfront.listOriginAccessControls(),Re=`${x.functionName}-lambda-url`,we=Ce.some((Mt)=>Mt.Name===Re&&Mt.OriginAccessControlOriginType==="lambda"),ke=Od(U.DistributionConfig.Origins,"Origin"),Be=Od(U.DistributionConfig.CacheBehaviors,"CacheBehavior"),Ae=ke.some((Mt)=>String(Mt.Id)===m.originId),Ie=Be.some((Mt)=>String(Mt.PathPattern)===m.pathPattern),Te=[...!re?[`create IAM execution role ${m.roleName}`]:[`reconcile IAM execution policy ${m.roleName}`],...ae?[`update Lambda ${x.functionName} code and configuration`]:[`create Lambda ${x.functionName}`],...!ve?["create private AWS_IAM function URL"]:[],...!we?[`create Lambda origin access control ${Re}`]:[],"grant CloudFront function-URL and invocation permissions",`${Ie?"reconcile":"add"} ${m.pathPattern} behavior without changing the default behavior`],Xe={command:`cloud cdn:origin:remove ${x.distributionId} FUNCTION_URL_HOST --id ${m.originId} --path '${m.pathPattern}' --apply --confirm 'remove:${x.distributionId}:${m.pathPattern}'`,removeFunction:!ae,removeRole:!re},_e={mode:x.apply?"apply":"plan",accountId:N.Account,identityArn:N.Arn,distribution:{id:z.Id,arn:z.ARN,alias:x.expectedAlias,domainName:z.DomainName,status:z.Status},function:{name:x.functionName,existed:ae,runtime:"nodejs22.x",urlExisted:!!ve,url:ve?.FunctionUrl},role:{name:m.roleName,existed:re,arn:oe},origin:{id:m.originId,pathPattern:m.pathPattern,existed:Ae,behaviorExisted:Ie,accessControlExisted:we},changes:Te,rollback:Xe,applied:!1};if(!x.apply)return _e;let Ze=`${x.distributionId}:${m.pathPattern}`;if(x.confirm!==Ze)throw Error(`Pass exact confirmation token ${Ze} to mutate the live distribution`);if(!te)te=await A.iam.createRole({RoleName:m.roleName,Description:`Execution role for ${x.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 A.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}:${N.Account}:log-group:/aws/lambda/${x.functionName}:*`}]})});try{await A.logs.createLogGroup(`/aws/lambda/${x.functionName}`,{ManagedBy:"ts-cloud"})}catch(Mt){if(!Nd(Mt))throw Mt}await A.logs.putRetentionPolicy(`/aws/lambda/${x.functionName}`,m.logRetentionDays);let et=x.code||kf;if(ae)await A.lambda.updateFunctionCodeInline(x.functionName,et,"index.mjs"),await A.lambda.waitForFunctionActive(x.functionName),await A.lambda.updateFunctionConfiguration({FunctionName:x.functionName,Runtime:"nodejs22.x",Role:te.Arn,Handler:"index.handler",Description:`Private API origin for ${x.expectedAlias}`,Timeout:m.timeout,MemorySize:m.memorySize});else{let Mt;for(let rt=0;rt<8;rt++)try{await A.lambda.createFunctionWithCode({FunctionName:x.functionName,Runtime:"nodejs22.x",Role:te.Arn,Handler:"index.handler",Code:et,Filename:"index.mjs",Description:`Private API origin for ${x.expectedAlias}`,Timeout:m.timeout,MemorySize:m.memorySize}),Mt=void 0;break}catch(Vt){if(Mt=Vt,!String(Vt.message).toLowerCase().includes("role"))throw Vt;await A.sleep(1000)}if(Mt)throw Mt}await A.lambda.waitForFunctionActive(x.functionName);let At=ve||await A.lambda.createFunctionUrl({FunctionName:x.functionName,AuthType:"AWS_IAM",InvokeMode:"BUFFERED"});if(!At.FunctionUrl)throw Error("Lambda did not return a function URL");let Ft=new URL(At.FunctionUrl).hostname,wt=await A.cloudfront.findOrCreateOriginAccessControl(Re,"lambda"),Et=z.ARN||`arn:aws:cloudfront::${N.Account}:distribution/${x.distributionId}`,sn=[{FunctionName:x.functionName,StatementId:"AllowCloudFrontFunctionUrl",Action:"lambda:InvokeFunctionUrl",Principal:"cloudfront.amazonaws.com",SourceArn:Et,FunctionUrlAuthType:"AWS_IAM"},{FunctionName:x.functionName,StatementId:"AllowCloudFrontInvoke",Action:"lambda:InvokeFunction",Principal:"cloudfront.amazonaws.com",SourceArn:Et,InvokedViaFunctionUrl:!0}];for(let Mt of sn)try{await A.lambda.addPermission(Mt)}catch(rt){if(!Nd(rt))throw rt}return await A.cloudfront.upsertExistingDistributionOrigin(x.distributionId,{id:m.originId,domainName:Ft,pathPattern:m.pathPattern,originAccessControlId:wt.Id}),{..._e,function:{..._e.function,url:At.FunctionUrl},applied:!0}}async function Fd(x){let t=`https://${x.alias}/`,m=`https://${x.alias}/api/health`,A=await fetch(t,{redirect:"follow",headers:{"cache-control":"no-cache"}}),N=new Uint8Array(await A.arrayBuffer()),z=Af("sha256").update(N).digest("hex"),U=Date.now(),X=await fetch(m,{redirect:"follow",headers:{"cache-control":"no-cache"}}),te=Date.now()-U,re=await X.text(),oe=re;try{oe=JSON.parse(re)}catch{}let ae;if(x.logs&&x.functionName){let ve=await x.logs.filterLogEvents({logGroupName:`/aws/lambda/${x.functionName}`,startTime:U-60000,filterPattern:'"Init Duration"',limit:25});for(let Ce of ve.events||[]){let Re=Ce.message?.match(/Init Duration:\s*([0-9.]+)\s*ms/i);if(Re)ae=Number(Re[1])}}return{frontend:{url:t,status:A.status,sha256:z,unchanged:x.expectedFrontendSha256?z===x.expectedFrontendSha256:void 0,bytes:N.byteLength},api:{url:m,status:X.status,latencyMs:te,healthy:X.ok&&!!oe?.ok,body:oe},coldStart:{observed:ae!==void 0,initDurationMs:ae},verifiedAt:(x.now?.()||new Date).toISOString()}}function Bd(x){let t=(x.memoryMb||256)/1024,m=Math.max(0,x.requests-(x.includeFreeTier===!1?0:1e6)),A=Math.max(0,x.requests*(x.averageDurationMs/1000)*t-(x.includeFreeTier===!1?0:400000)),N=m/1e6*0.2+A*0.0000166667,z=9.010025,U=16.425;return{lambdaUsd:Number(N.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 qc(x){x.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,A)=>{try{let N=await jd({distributionId:t,expectedAlias:m,functionName:A.function||"ts-cloud-static-api",pathPattern:A.path,originId:A.originId,roleName:A.role,region:A.region,profile:A.profile,memorySize:Number(A.memory),timeout:Number(A.timeout),logRetentionDays:Number(A.retention),apply:!!A.apply,confirm:A.confirm});if(e(JSON.stringify(N,null,2)),N.applied)n("Private API origin submitted to CloudFront. Run cdn:api:verify after propagation.");else e(`Plan only. Re-run with --apply --confirm '${t}:${N.origin.pathPattern}'.`)}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.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 A=new ie(m.region||"us-east-1",m.profile),N=await Fd({alias:t,expectedFrontendSha256:m.frontendSha256,logs:A,functionName:m.function||"ts-cloud-static-api"});if(e(JSON.stringify(N,null,2)),!N.api.healthy||N.frontend.unchanged===!1)throw Error("Static API verification failed");n("Static frontend and Lambda API verification passed.")}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.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 A=Bd({requests:Number(t),averageDurationMs:Number(m.duration),memoryMb:Number(m.memory),includeFreeTier:m.freeTier!==!1});e(JSON.stringify(A,null,2))}),x.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,A)=>{try{let N=new w(A.profile),z={id:A.id??"api-backend",domainName:m,pathPattern:A.path??"/api/*",replaceExisting:!!A.replace},U=await N.upsertExistingDistributionOrigin(t,{...z,dryRun:!0});if(e(JSON.stringify(U,null,2)),!A.apply){e("Dry run only. Re-run with --apply and the exact --confirm token.");return}let X=`${t}:${U.pathPattern}`;if(A.confirm!==X)throw Error(`Pass --confirm "${X}" to apply this live distribution change`);let te=await N.upsertExistingDistributionOrigin(t,z);n(te.changed?"Backend origin patch submitted.":"Backend origin already matches."),e(JSON.stringify(te,null,2))}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.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,A)=>{try{let N=new w(A.profile),z={id:A.id??"api-backend",domainName:m,pathPattern:A.path??"/api/*"},U=await N.removeExistingDistributionOrigin(t,{...z,dryRun:!0});if(e(JSON.stringify(U,null,2)),!A.apply){e("Dry run only. Re-run with --apply and the exact --confirm token.");return}let X=`remove:${t}:${U.pathPattern}`;if(A.confirm!==X)throw Error(`Pass --confirm "${X}" to apply this live distribution rollback`);let te=await N.removeExistingDistributionOrigin(t,z);n(te.changed?"Backend behavior rollback submitted.":"Backend behavior was already absent."),e(JSON.stringify(te,null,2))}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.command("cdn:list","List all CloudFront distributions").action(async()=>{i("CloudFront Distributions");try{let t=await Oe(),m=new w,A=new o("Fetching distributions...");A.start();let N=await m.listDistributions();if(A.succeed(`Found ${N.length} distribution(s)`),N.length===0){e("No CloudFront distributions found"),e("Use `cloud cdn:create` to create a new distribution");return}c(["ID","Domain","Status","Enabled","Aliases"],N.map((z)=>[z.Id||"N/A",z.DomainName||"N/A",z.Status||"N/A",z.Enabled?"Yes":"No",(z.Aliases?.Items?.length||0).toString()]))}catch(t){r(`Failed to list distributions: ${t.message}`),process.exit(1)}}),x.command("cdn:status <distributionId>","Get CloudFront distribution status").action(async(t)=>{i(`CloudFront Distribution: ${t}`);try{let m=new w,A=new o("Fetching distribution details...");A.start();let N=await m.getDistribution(t);if(A.succeed("Distribution details loaded"),e(`
478
+ `;function Ld(x){let t=x;return String(t?.code||t?.name||t?.statusCode||"")}function Rf(x){return["NoSuchEntity","ResourceNotFoundException","404"].includes(Ld(x))}function Nd(x){return["EntityAlreadyExists","ResourceAlreadyExistsException","ResourceConflictException","409"].includes(Ld(x))}function Od(x,t){let m=x?.Items;if(!m)return[];if(Array.isArray(m))return m;let A=m[t];if(A===void 0)return[];return Array.isArray(A)?A:[A]}function Wc(x){let t=x?.Aliases?.Items,m=t?.CNAME??t;if(Array.isArray(m))return m.map(String);if(typeof m==="string")return[m];return[]}function If(x){if(!/^[A-Z0-9]{8,32}$/.test(x.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(x.expectedAlias))throw Error("Expected CloudFront alias is invalid");if(!/^[A-Za-z0-9-_]{1,64}$/.test(x.functionName))throw Error("Lambda function name is invalid");let t=x.pathPattern?.startsWith("/")?x.pathPattern:`/${x.pathPattern||"api/*"}`;if(t==="/"||t==="/*"||!t.endsWith("*"))throw Error("API path must be a non-default wildcard pattern such as /api/*");let m=x.originId||`${x.functionName}-url`,A=x.roleName||`${x.functionName}-execution`;if(!/^[A-Za-z0-9_.-]{1,64}$/.test(A))throw Error("IAM role name is invalid");let N=x.memorySize??256,z=x.timeout??10,U=x.logRetentionDays??14;if(N<128||N>10240)throw Error("Lambda memory must be between 128 and 10240 MB");if(z<1||z>900)throw Error("Lambda timeout must be between 1 and 900 seconds");return{region:x.region||"us-east-1",pathPattern:t,originId:m,roleName:A,memorySize:N,timeout:z,logRetentionDays:U}}function Pf(x){let t=x.region||"us-east-1";return{cloudfront:new C(x.profile),iam:new ee(t,x.profile),lambda:new R(t,x.profile),logs:new ie(t,x.profile),sts:new ct(t,x.profile),sleep:(m)=>new Promise((A)=>setTimeout(A,m))}}async function jd(x,t){let m=If(x),A=t||Pf({profile:x.profile,region:m.region}),[N,z,U]=await Promise.all([A.sts.getCallerIdentity(),A.cloudfront.getDistribution(x.distributionId),A.cloudfront.getDistributionConfig(x.distributionId)]);if(!N.Account)throw Error("AWS identity did not return an account ID");if(!z.Enabled)throw Error(`CloudFront distribution ${x.distributionId} is disabled`);if(!(Wc(z).length?Wc(z):Wc(U.DistributionConfig)).includes(x.expectedAlias))throw Error(`Distribution ${x.distributionId} does not contain expected alias ${x.expectedAlias}`);let te,re=!0;try{te=await A.iam.getRole({RoleName:m.roleName})}catch(Mt){if(!Rf(Mt))throw Mt;re=!1}let oe=te?.Arn||`arn:aws:iam::${N.Account}:role/${m.roleName}`,ae=await A.lambda.functionExists(x.functionName),ve=ae?await A.lambda.getFunctionUrl(x.functionName):null,Ce=await A.cloudfront.listOriginAccessControls(),Re=`${x.functionName}-lambda-url`,we=Ce.some((Mt)=>Mt.Name===Re&&Mt.OriginAccessControlOriginType==="lambda"),ke=Od(U.DistributionConfig.Origins,"Origin"),Be=Od(U.DistributionConfig.CacheBehaviors,"CacheBehavior"),Ae=ke.some((Mt)=>String(Mt.Id)===m.originId),Ie=Be.some((Mt)=>String(Mt.PathPattern)===m.pathPattern),Te=[...!re?[`create IAM execution role ${m.roleName}`]:[`reconcile IAM execution policy ${m.roleName}`],...ae?[`update Lambda ${x.functionName} code and configuration`]:[`create Lambda ${x.functionName}`],...!ve?["create private AWS_IAM function URL"]:[],...!we?[`create Lambda origin access control ${Re}`]:[],"grant CloudFront function-URL and invocation permissions",`${Ie?"reconcile":"add"} ${m.pathPattern} behavior without changing the default behavior`],Xe={command:`cloud cdn:origin:remove ${x.distributionId} FUNCTION_URL_HOST --id ${m.originId} --path '${m.pathPattern}' --apply --confirm 'remove:${x.distributionId}:${m.pathPattern}'`,removeFunction:!ae,removeRole:!re},_e={mode:x.apply?"apply":"plan",accountId:N.Account,identityArn:N.Arn,distribution:{id:z.Id,arn:z.ARN,alias:x.expectedAlias,domainName:z.DomainName,status:z.Status},function:{name:x.functionName,existed:ae,runtime:"nodejs22.x",urlExisted:!!ve,url:ve?.FunctionUrl},role:{name:m.roleName,existed:re,arn:oe},origin:{id:m.originId,pathPattern:m.pathPattern,existed:Ae,behaviorExisted:Ie,accessControlExisted:we},changes:Te,rollback:Xe,applied:!1};if(!x.apply)return _e;let Ze=`${x.distributionId}:${m.pathPattern}`;if(x.confirm!==Ze)throw Error(`Pass exact confirmation token ${Ze} to mutate the live distribution`);if(!te)te=await A.iam.createRole({RoleName:m.roleName,Description:`Execution role for ${x.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 A.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}:${N.Account}:log-group:/aws/lambda/${x.functionName}:*`}]})});try{await A.logs.createLogGroup(`/aws/lambda/${x.functionName}`,{ManagedBy:"ts-cloud"})}catch(Mt){if(!Nd(Mt))throw Mt}await A.logs.putRetentionPolicy(`/aws/lambda/${x.functionName}`,m.logRetentionDays);let et=x.code||kf;if(ae)await A.lambda.updateFunctionCodeInline(x.functionName,et,"index.mjs"),await A.lambda.waitForFunctionActive(x.functionName),await A.lambda.updateFunctionConfiguration({FunctionName:x.functionName,Runtime:"nodejs22.x",Role:te.Arn,Handler:"index.handler",Description:`Private API origin for ${x.expectedAlias}`,Timeout:m.timeout,MemorySize:m.memorySize});else{let Mt;for(let rt=0;rt<8;rt++)try{await A.lambda.createFunctionWithCode({FunctionName:x.functionName,Runtime:"nodejs22.x",Role:te.Arn,Handler:"index.handler",Code:et,Filename:"index.mjs",Description:`Private API origin for ${x.expectedAlias}`,Timeout:m.timeout,MemorySize:m.memorySize}),Mt=void 0;break}catch(Vt){if(Mt=Vt,!String(Vt.message).toLowerCase().includes("role"))throw Vt;await A.sleep(1000)}if(Mt)throw Mt}await A.lambda.waitForFunctionActive(x.functionName);let At=ve||await A.lambda.createFunctionUrl({FunctionName:x.functionName,AuthType:"AWS_IAM",InvokeMode:"BUFFERED"});if(!At.FunctionUrl)throw Error("Lambda did not return a function URL");let Ft=new URL(At.FunctionUrl).hostname,St=await A.cloudfront.findOrCreateOriginAccessControl(Re,"lambda"),Et=z.ARN||`arn:aws:cloudfront::${N.Account}:distribution/${x.distributionId}`,sn=[{FunctionName:x.functionName,StatementId:"AllowCloudFrontFunctionUrl",Action:"lambda:InvokeFunctionUrl",Principal:"cloudfront.amazonaws.com",SourceArn:Et,FunctionUrlAuthType:"AWS_IAM"},{FunctionName:x.functionName,StatementId:"AllowCloudFrontInvoke",Action:"lambda:InvokeFunction",Principal:"cloudfront.amazonaws.com",SourceArn:Et,InvokedViaFunctionUrl:!0}];for(let Mt of sn)try{await A.lambda.addPermission(Mt)}catch(rt){if(!Nd(rt))throw rt}return await A.cloudfront.upsertExistingDistributionOrigin(x.distributionId,{id:m.originId,domainName:Ft,pathPattern:m.pathPattern,originAccessControlId:St.Id}),{..._e,function:{..._e.function,url:At.FunctionUrl},applied:!0}}async function Fd(x){let t=`https://${x.alias}/`,m=`https://${x.alias}/api/health`,A=await fetch(t,{redirect:"follow",headers:{"cache-control":"no-cache"}}),N=new Uint8Array(await A.arrayBuffer()),z=Af("sha256").update(N).digest("hex"),U=Date.now(),X=await fetch(m,{redirect:"follow",headers:{"cache-control":"no-cache"}}),te=Date.now()-U,re=await X.text(),oe=re;try{oe=JSON.parse(re)}catch{}let ae;if(x.logs&&x.functionName){let ve=await x.logs.filterLogEvents({logGroupName:`/aws/lambda/${x.functionName}`,startTime:U-60000,filterPattern:'"Init Duration"',limit:25});for(let Ce of ve.events||[]){let Re=Ce.message?.match(/Init Duration:\s*([0-9.]+)\s*ms/i);if(Re)ae=Number(Re[1])}}return{frontend:{url:t,status:A.status,sha256:z,unchanged:x.expectedFrontendSha256?z===x.expectedFrontendSha256:void 0,bytes:N.byteLength},api:{url:m,status:X.status,latencyMs:te,healthy:X.ok&&!!oe?.ok,body:oe},coldStart:{observed:ae!==void 0,initDurationMs:ae},verifiedAt:(x.now?.()||new Date).toISOString()}}function Bd(x){let t=(x.memoryMb||256)/1024,m=Math.max(0,x.requests-(x.includeFreeTier===!1?0:1e6)),A=Math.max(0,x.requests*(x.averageDurationMs/1000)*t-(x.includeFreeTier===!1?0:400000)),N=m/1e6*0.2+A*0.0000166667,z=9.010025,U=16.425;return{lambdaUsd:Number(N.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 qc(x){x.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,A)=>{try{let N=await jd({distributionId:t,expectedAlias:m,functionName:A.function||"ts-cloud-static-api",pathPattern:A.path,originId:A.originId,roleName:A.role,region:A.region,profile:A.profile,memorySize:Number(A.memory),timeout:Number(A.timeout),logRetentionDays:Number(A.retention),apply:!!A.apply,confirm:A.confirm});if(e(JSON.stringify(N,null,2)),N.applied)n("Private API origin submitted to CloudFront. Run cdn:api:verify after propagation.");else e(`Plan only. Re-run with --apply --confirm '${t}:${N.origin.pathPattern}'.`)}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.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 A=new ie(m.region||"us-east-1",m.profile),N=await Fd({alias:t,expectedFrontendSha256:m.frontendSha256,logs:A,functionName:m.function||"ts-cloud-static-api"});if(e(JSON.stringify(N,null,2)),!N.api.healthy||N.frontend.unchanged===!1)throw Error("Static API verification failed");n("Static frontend and Lambda API verification passed.")}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.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 A=Bd({requests:Number(t),averageDurationMs:Number(m.duration),memoryMb:Number(m.memory),includeFreeTier:m.freeTier!==!1});e(JSON.stringify(A,null,2))}),x.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,A)=>{try{let N=new C(A.profile),z={id:A.id??"api-backend",domainName:m,pathPattern:A.path??"/api/*",replaceExisting:!!A.replace},U=await N.upsertExistingDistributionOrigin(t,{...z,dryRun:!0});if(e(JSON.stringify(U,null,2)),!A.apply){e("Dry run only. Re-run with --apply and the exact --confirm token.");return}let X=`${t}:${U.pathPattern}`;if(A.confirm!==X)throw Error(`Pass --confirm "${X}" to apply this live distribution change`);let te=await N.upsertExistingDistributionOrigin(t,z);n(te.changed?"Backend origin patch submitted.":"Backend origin already matches."),e(JSON.stringify(te,null,2))}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.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,A)=>{try{let N=new C(A.profile),z={id:A.id??"api-backend",domainName:m,pathPattern:A.path??"/api/*"},U=await N.removeExistingDistributionOrigin(t,{...z,dryRun:!0});if(e(JSON.stringify(U,null,2)),!A.apply){e("Dry run only. Re-run with --apply and the exact --confirm token.");return}let X=`remove:${t}:${U.pathPattern}`;if(A.confirm!==X)throw Error(`Pass --confirm "${X}" to apply this live distribution rollback`);let te=await N.removeExistingDistributionOrigin(t,z);n(te.changed?"Backend behavior rollback submitted.":"Backend behavior was already absent."),e(JSON.stringify(te,null,2))}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.command("cdn:list","List all CloudFront distributions").action(async()=>{i("CloudFront Distributions");try{let t=await Oe(),m=new C,A=new o("Fetching distributions...");A.start();let N=await m.listDistributions();if(A.succeed(`Found ${N.length} distribution(s)`),N.length===0){e("No CloudFront distributions found"),e("Use `cloud cdn:create` to create a new distribution");return}c(["ID","Domain","Status","Enabled","Aliases"],N.map((z)=>[z.Id||"N/A",z.DomainName||"N/A",z.Status||"N/A",z.Enabled?"Yes":"No",(z.Aliases?.Items?.length||0).toString()]))}catch(t){r(`Failed to list distributions: ${t.message}`),process.exit(1)}}),x.command("cdn:status <distributionId>","Get CloudFront distribution status").action(async(t)=>{i(`CloudFront Distribution: ${t}`);try{let m=new C,A=new o("Fetching distribution details...");A.start();let N=await m.getDistribution(t);if(A.succeed("Distribution details loaded"),e(`
479
479
  Distribution Information:`),e(` ID: ${N.Id}`),e(` Domain: ${N.DomainName}`),e(` Status: ${N.Status}`),e(` ARN: ${N.ARN}`),e(` Enabled: ${N.Enabled?"Yes":"No"}`),N.Aliases?.Items?.length)e(` Aliases: ${N.Aliases.Items.join(", ")}`);let z=await m.getDistributionConfig(t);e(`
480
480
  Origins:`);for(let U of k(z.DistributionConfig?.Origins?.Items?.Origin))e(` - ${U.Id}: ${U.DomainName}`);if(z.DistributionConfig?.DefaultCacheBehavior){let U=z.DistributionConfig.DefaultCacheBehavior;e(`
481
- Default Cache Behavior:`),e(` Target Origin: ${U.TargetOriginId}`),e(` Viewer Protocol: ${U.ViewerProtocolPolicy}`)}}catch(m){r(`Failed to get distribution: ${m.message}`),process.exit(1)}}),x.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 w;if(e(`Bucket: ${t.bucket}`),e(`Region: ${t.region}`),t.alias)e(`Alias: ${t.alias}`);if(!await d(`
481
+ Default Cache Behavior:`),e(` Target Origin: ${U.TargetOriginId}`),e(` Viewer Protocol: ${U.ViewerProtocolPolicy}`)}}catch(m){r(`Failed to get distribution: ${m.message}`),process.exit(1)}}),x.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 C;if(e(`Bucket: ${t.bucket}`),e(`Region: ${t.region}`),t.alias)e(`Alias: ${t.alias}`);if(!await d(`
482
482
  Create this distribution?`,!0)){e("Operation cancelled");return}let N=new o("Setting up Origin Access Control...");N.start();let z=await m.findOrCreateOriginAccessControl(`OAC-${t.bucket}`);if(z.isNew)N.text="Created new Origin Access Control";N.text="Creating distribution...";let U=t.alias?[t.alias]:[],X=await m.createDistributionForS3({bucketName:t.bucket,bucketRegion:t.region,originAccessControlId:z.Id,aliases:U,certificateArn:t.certificate,comment:t.comment||"Created by ts-cloud CLI"});N.succeed("Distribution created"),n(`
483
483
  Distribution ID: ${X.Id}`),e(`Domain: ${X.DomainName}`),e(`Status: ${X.Status}`),e(`
484
484
  Note: Distribution deployment may take 15-30 minutes.`),e(`
485
- IMPORTANT: Update your S3 bucket policy to allow CloudFront access:`);let te=w.getS3BucketPolicyForCloudFront(t.bucket,X.ARN);e(JSON.stringify(te,null,2))}catch(m){r(`Failed to create distribution: ${m.message}`),process.exit(1)}}),x.command("cdn:invalidate <distributionId>","Invalidate CloudFront cache").option("--paths <paths>","Paths to invalidate (comma-separated)",{default:"/*"}).action(async(t,m)=>{i("Invalidate CloudFront Cache");try{let A=new w,N=m.paths.split(",").map((te)=>te.trim());if(e(`Distribution: ${t}`),e(`Paths: ${N.join(", ")}`),!await d(`
485
+ IMPORTANT: Update your S3 bucket policy to allow CloudFront access:`);let te=C.getS3BucketPolicyForCloudFront(t.bucket,X.ARN);e(JSON.stringify(te,null,2))}catch(m){r(`Failed to create distribution: ${m.message}`),process.exit(1)}}),x.command("cdn:invalidate <distributionId>","Invalidate CloudFront cache").option("--paths <paths>","Paths to invalidate (comma-separated)",{default:"/*"}).action(async(t,m)=>{i("Invalidate CloudFront Cache");try{let A=new C,N=m.paths.split(",").map((te)=>te.trim());if(e(`Distribution: ${t}`),e(`Paths: ${N.join(", ")}`),!await d(`
486
486
  Create invalidation?`,!0)){e("Operation cancelled");return}let U=new o("Creating invalidation...");U.start();let X=await A.createInvalidation({distributionId:t,paths:N,callerReference:`cli-${Date.now()}`});U.succeed("Invalidation created"),n(`
487
487
  Invalidation ID: ${X.Id}`),e(`Status: ${X.Status}`),e(`
488
- Note: Invalidation typically completes in 5-10 minutes.`)}catch(A){r(`Failed to create invalidation: ${A.message}`),process.exit(1)}}),x.command("cdn:disable <distributionId>","Disable a CloudFront distribution").action(async(t)=>{i("Disable CloudFront Distribution");try{let m=new w;if(a(`This will disable distribution: ${t}`),e("The distribution will stop serving content."),!await d(`
488
+ Note: Invalidation typically completes in 5-10 minutes.`)}catch(A){r(`Failed to create invalidation: ${A.message}`),process.exit(1)}}),x.command("cdn:disable <distributionId>","Disable a CloudFront distribution").action(async(t)=>{i("Disable CloudFront Distribution");try{let m=new C;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 N=new o("Disabling distribution...");N.start(),await m.disableDistribution(t),N.succeed("Distribution disabled"),e(`
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)}}),x.command("cdn:delete <distributionId>","Delete a CloudFront distribution").action(async(t)=>{i("Delete CloudFront Distribution");try{let m=new w;if(a(`This will permanently delete distribution: ${t}`),a("The distribution must be disabled first."),!await d(`
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)}}),x.command("cdn:delete <distributionId>","Delete a CloudFront distribution").action(async(t)=>{i("Delete CloudFront Distribution");try{let m=new C;if(a(`This will permanently delete distribution: ${t}`),a("The distribution must be disabled first."),!await d(`
491
491
  Delete this distribution?`,!1)){e("Operation cancelled");return}let N=new o("Checking distribution status...");N.start();let z=await m.getDistribution(t);if(z.Status!=="Deployed"||z.Enabled){N.fail("Distribution must be disabled and deployed before deletion"),e('\nRun `cloud cdn:disable` first and wait for status to be "Deployed"');return}N.text="Deleting distribution...",await m.deleteDistribution(t),N.succeed("Distribution deleted")}catch(m){r(`Failed to delete distribution: ${m.message}`),process.exit(1)}})}import{readdirSync as xf,statSync as Df}from"node:fs";import{join as Md,relative as Ef}from"node:path";async function pi(x){let t=await Oe(),m=y(process.cwd(),t),A=x??Object.keys(t.environments??{})[0]??"production",N=m.environments.get(A);if(!N)throw m.store.close(),Error(`Environment ${A} was not found.`);let z=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),U=new pt(m.store),X=new Kr(U,[new _t,new Jr(Md(process.cwd(),".ts-cloud","volumes"))]);return{controlPlane:m,environmentRecord:N,actor:z,store:U,service:X}}function Tf(x){return x.map((t)=>[t.name,`${t.provider}/${t.type}`,t.status,t.capacityBytes==null?"—":hs(t.capacityBytes),t.usedBytes==null?"—":hs(t.usedBytes),String(t.attachments.filter((m)=>m.observedState==="attached").length),t.backupState,t.orphaned?"orphan":"managed"])}function Gc(x){x.command("storage:list","List all S3 buckets").option("--region <region>","AWS region").action(async(m)=>{i("S3 Buckets");try{let A=await Oe(),N=m.region||A.project.region||"us-east-1",z=new g(N),U=new o("Fetching buckets...");U.start();let te=(await z.listBuckets()).Buckets||[];if(U.succeed(`Found ${te.length} bucket(s)`),te.length===0){e("No S3 buckets found"),e("Use `cloud storage:create` to create a new bucket");return}c(["Name","Created"],te.map((re)=>[re.Name||"N/A",re.CreationDate?new Date(re.CreationDate).toLocaleDateString():"N/A"]))}catch(A){r(`Failed to list buckets: ${A.message}`),process.exit(1)}}),x.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,A)=>{i("Create S3 Bucket");try{let N=new g(A.region);if(e(`Bucket name: ${m}`),e(`Region: ${A.region}`),e(`Public access: ${A.public?"Yes":"No"}`),e(`Versioning: ${A.versioning?"Yes":"No"}`),!await d(`
492
492
  Create this bucket?`,!0)){e("Operation cancelled");return}let U=new o("Creating bucket...");if(U.start(),await N.createBucket(m),!A.public)U.text="Configuring public access block...",await N.putPublicAccessBlock(m,{BlockPublicAcls:!0,IgnorePublicAcls:!0,BlockPublicPolicy:!0,RestrictPublicBuckets:!0});if(A.versioning)U.text="Enabling versioning...",await N.putBucketVersioning(m,"Enabled");U.succeed("Bucket created"),n(`
493
493
  Bucket: ${m}`),e(`Region: ${A.region}`),e(`URL: s3://${m}`)}catch(N){r(`Failed to create bucket: ${N.message}`),process.exit(1)}}),x.command("storage:delete <name>","Delete an S3 bucket").option("--force","Delete all objects first").action(async(m,A)=>{i("Delete S3 Bucket");try{let N=new g("us-east-1");if(a(`This will permanently delete bucket: ${m}`),A.force)a("All objects in the bucket will be deleted!");if(!await d(`
@@ -614,9 +614,9 @@ Attached Policies:`);for(let te of U.AttachedPolicies)e(` - ${te.PolicyName}`)}
614
614
  Access Keys:`);for(let te of X.AccessKeyMetadata)if(e(` - ${te.AccessKeyId}: ${te.Status}`),te.CreateDate)e(` Created: ${new Date(te.CreateDate).toLocaleString()}`)}if(N.Tags&&N.Tags.length>0){e(`
615
615
  Tags:`);for(let te of N.Tags)e(` ${te.Key}: ${te.Value}`)}}catch(m){r(`Failed to get user: ${m.message}`),process.exit(1)}}),x.command("iam:groups","List IAM groups").option("--path <path>","Filter by path prefix",{default:"/"}).action(async(t)=>{i("IAM Groups");try{let m=new ee,A=new o("Fetching groups...");A.start();let z=(await m.listGroups({PathPrefix:t.path})).Groups||[];if(A.succeed(`Found ${z.length} group(s)`),z.length===0){e("No IAM groups found");return}c(["Group Name","Group ID","Path","Created"],z.map((U)=>[U.GroupName||"N/A",U.GroupId||"N/A",U.Path||"/",U.CreateDate?new Date(U.CreateDate).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list groups: ${m.message}`),process.exit(1)}}),x.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 A=new ee,N=m.action||await I("Action to simulate","s3:GetObject"),z=m.resource||await I("Resource ARN","*");e(`
616
616
  Policy: ${t}`),e(`Action: ${N}`),e(`Resource: ${z}`);let U=new o("Running simulation...");U.start();let X=await A.simulatePrincipalPolicy({PolicySourceArn:t,ActionNames:[N],ResourceArns:[z]});if(U.succeed("Simulation complete"),X.EvaluationResults&&X.EvaluationResults.length>0)for(let te of X.EvaluationResults){let re=te.EvalDecision;if(e(`
617
- Result for ${te.EvalActionName}:`),re==="allowed")n(" Decision: ALLOWED");else if(re==="implicitDeny")a(" Decision: IMPLICIT DENY (no matching allow statement)");else r(" Decision: EXPLICIT DENY");if(te.MatchedStatements&&te.MatchedStatements.length>0){e(" Matched Statements:");for(let oe of te.MatchedStatements)e(` - ${oe.SourcePolicyId}: ${oe.SourcePolicyType}`)}}}catch(A){r(`Failed to simulate policy: ${A.message}`),process.exit(1)}}),x.command("iam:whoami","Show current IAM identity").action(async()=>{i("Current IAM Identity");try{let t=new ee,m=new o("Fetching identity...");m.start();let{STSClient:A}=await import("./chunk-mv0t7j76.js"),z=await new A("us-east-1").getCallerIdentity();m.succeed("Identity loaded"),e(`
617
+ Result for ${te.EvalActionName}:`),re==="allowed")n(" Decision: ALLOWED");else if(re==="implicitDeny")a(" Decision: IMPLICIT DENY (no matching allow statement)");else r(" Decision: EXPLICIT DENY");if(te.MatchedStatements&&te.MatchedStatements.length>0){e(" Matched Statements:");for(let oe of te.MatchedStatements)e(` - ${oe.SourcePolicyId}: ${oe.SourcePolicyType}`)}}}catch(A){r(`Failed to simulate policy: ${A.message}`),process.exit(1)}}),x.command("iam:whoami","Show current IAM identity").action(async()=>{i("Current IAM Identity");try{let t=new ee,m=new o("Fetching identity...");m.start();let{STSClient:A}=await import("./chunk-eyrmv6c4.js"),z=await new A("us-east-1").getCallerIdentity();m.succeed("Identity loaded"),e(`
618
618
  Caller Identity:`),e(` Account: ${z.Account}`),e(` ARN: ${z.Arn}`),e(` User ID: ${z.UserId}`);let U=z.Arn||"";if(U.includes(":user/"))e(" Type: IAM User");else if(U.includes(":assumed-role/"))e(" Type: Assumed Role");else if(U.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 ao(x){let{AWSClient:t}=await import("./chunk-jxh78anm.js");class m{client;region;constructor(A){this.region=A,this.client=new t}async jsonRpcRequest(A,N){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.${A}`},body:JSON.stringify(N)})}async describeTrails(){return this.jsonRpcRequest("DescribeTrails",{})}async getTrailStatus(A){return this.jsonRpcRequest("GetTrailStatus",{Name:A})}async lookupEvents(A){return this.jsonRpcRequest("LookupEvents",{...A,StartTime:A.StartTime?.toISOString(),EndTime:A.EndTime?.toISOString()})}async getEventSelectors(A){return this.jsonRpcRequest("GetEventSelectors",{TrailName:A})}async createTrail(A){return this.jsonRpcRequest("CreateTrail",A)}async startLogging(A){return this.jsonRpcRequest("StartLogging",{Name:A})}async stopLogging(A){return this.jsonRpcRequest("StopLogging",{Name:A})}async deleteTrail(A){return this.jsonRpcRequest("DeleteTrail",{Name:A})}}return new m(x)}function nl(x){x.command("audit:trails","List CloudTrail trails").option("--region <region>","AWS region").action(async(t)=>{i("CloudTrail Trails");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",N=await ao(A),z=new o("Fetching trails...");z.start();let X=(await N.describeTrails()).trailList||[];if(z.succeed(`Found ${X.length} trail(s)`),X.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"],X.map((te)=>[te.Name||"N/A",te.IsMultiRegionTrail?"Yes":"No",te.S3BucketName||"N/A",te.LogFileValidationEnabled?"Yes":"No",te.HomeRegion||"N/A"]))}catch(m){r(`Failed to list trails: ${m.message}`),process.exit(1)}}),x.command("audit:trail <trailName>","Show CloudTrail trail details").option("--region <region>","AWS region").action(async(t,m)=>{i(`CloudTrail: ${t}`);try{let A=await Oe(),N=m.region||A.project.region||"us-east-1",z=await ao(N),U=new o("Fetching trail details...");U.start();let[X,te]=await Promise.all([z.describeTrails(),z.getTrailStatus(t)]),re=(X.trailList||[]).find((oe)=>oe.Name===t);if(!re){U.fail("Trail not found");return}if(U.succeed("Trail details loaded"),e(`
619
+ Warning: Using root credentials is not recommended!`)}catch(t){r(`Failed to get identity: ${t.message}`),process.exit(1)}})}async function ao(x){let{AWSClient:t}=await import("./chunk-dvv2z19r.js");class m{client;region;constructor(A){this.region=A,this.client=new t}async jsonRpcRequest(A,N){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.${A}`},body:JSON.stringify(N)})}async describeTrails(){return this.jsonRpcRequest("DescribeTrails",{})}async getTrailStatus(A){return this.jsonRpcRequest("GetTrailStatus",{Name:A})}async lookupEvents(A){return this.jsonRpcRequest("LookupEvents",{...A,StartTime:A.StartTime?.toISOString(),EndTime:A.EndTime?.toISOString()})}async getEventSelectors(A){return this.jsonRpcRequest("GetEventSelectors",{TrailName:A})}async createTrail(A){return this.jsonRpcRequest("CreateTrail",A)}async startLogging(A){return this.jsonRpcRequest("StartLogging",{Name:A})}async stopLogging(A){return this.jsonRpcRequest("StopLogging",{Name:A})}async deleteTrail(A){return this.jsonRpcRequest("DeleteTrail",{Name:A})}}return new m(x)}function nl(x){x.command("audit:trails","List CloudTrail trails").option("--region <region>","AWS region").action(async(t)=>{i("CloudTrail Trails");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",N=await ao(A),z=new o("Fetching trails...");z.start();let X=(await N.describeTrails()).trailList||[];if(z.succeed(`Found ${X.length} trail(s)`),X.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"],X.map((te)=>[te.Name||"N/A",te.IsMultiRegionTrail?"Yes":"No",te.S3BucketName||"N/A",te.LogFileValidationEnabled?"Yes":"No",te.HomeRegion||"N/A"]))}catch(m){r(`Failed to list trails: ${m.message}`),process.exit(1)}}),x.command("audit:trail <trailName>","Show CloudTrail trail details").option("--region <region>","AWS region").action(async(t,m)=>{i(`CloudTrail: ${t}`);try{let A=await Oe(),N=m.region||A.project.region||"us-east-1",z=await ao(N),U=new o("Fetching trail details...");U.start();let[X,te]=await Promise.all([z.describeTrails(),z.getTrailStatus(t)]),re=(X.trailList||[]).find((oe)=>oe.Name===t);if(!re){U.fail("Trail not found");return}if(U.succeed("Trail details loaded"),e(`
620
620
  Trail Configuration:`),e(` Name: ${re.Name}`),e(` ARN: ${re.TrailARN}`),e(` Home Region: ${re.HomeRegion}`),e(` Multi-Region: ${re.IsMultiRegionTrail?"Yes":"No"}`),e(` Organization Trail: ${re.IsOrganizationTrail?"Yes":"No"}`),e(`
621
621
  Storage:`),e(` S3 Bucket: ${re.S3BucketName}`),re.S3KeyPrefix)e(` S3 Prefix: ${re.S3KeyPrefix}`);if(e(` Log Validation: ${re.LogFileValidationEnabled?"Enabled":"Disabled"}`),re.CloudWatchLogsLogGroupArn)e(`
622
622
  CloudWatch Logs:`),e(` Log Group: ${re.CloudWatchLogsLogGroupArn}`);if(re.KMSKeyId)e(`
@@ -629,10 +629,10 @@ Full Event Record:`);let re=JSON.parse(te.CloudTrailEvent);console.log(JSON.stri
629
629
  Create this trail?`,!0)){e("Operation cancelled");return}let z=new o("Creating trail...");z.start();let U=await A.createTrail({Name:t,S3BucketName:m.bucket,S3KeyPrefix:m.prefix,IsMultiRegionTrail:m.multiRegion,EnableLogFileValidation:m.validation,IncludeGlobalServiceEvents:m.globalEvents??!0});z.text="Starting logging...",await A.startLogging(t),z.succeed("Trail created and logging started"),n(`
630
630
  Trail ARN: ${U.TrailARN}`),e(`S3 Bucket: ${U.S3BucketName}`)}catch(A){r(`Failed to create trail: ${A.message}`),process.exit(1)}}),x.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 A=await ao(m.region),N=new o("Starting logging...");N.start(),await A.startLogging(t),N.succeed("Logging started")}catch(A){r(`Failed to start logging: ${A.message}`),process.exit(1)}}),x.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 A=await ao(m.region);if(a(`This will stop logging for trail: ${t}`),!await d(`
631
631
  Stop logging?`,!1)){e("Operation cancelled");return}let z=new o("Stopping logging...");z.start(),await A.stopLogging(t),z.succeed("Logging stopped")}catch(A){r(`Failed to stop logging: ${A.message}`),process.exit(1)}}),x.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 A=await ao(m.region);if(a(`This will permanently delete trail: ${t}`),!await d(`
632
- Delete this trail?`,!1)){e("Operation cancelled");return}let z=new o("Deleting trail...");z.start(),await A.deleteTrail(t),z.succeed("Trail deleted")}catch(A){r(`Failed to delete trail: ${A.message}`),process.exit(1)}})}function rl(x){x.command("status","Show overall infrastructure health dashboard").option("--region <region>","AWS region").action(async(t)=>{i("Infrastructure Status Dashboard");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1";e(`Region: ${A}`),e(`Project: ${m.project.name||"Unknown"}`),e("");let N=[],z=new o("Checking EC2 instances...");z.start();try{let{EC2Client:we}=await import("./chunk-ecxchy9q.js"),Be=await new we(A).describeInstances({Filters:[{Name:"instance-state-name",Values:["running","pending","stopping","stopped"]}]}),Ae=0,Ie=0,Te=0;for(let Xe of Be.Reservations||[])for(let _e of Xe.Instances||[])if(Te++,_e.State?.Name==="running")Ae++;else if(_e.State?.Name==="stopped")Ie++;z.succeed("EC2 instances checked"),N.push({name:"EC2 Instances",status:Ae>0?"OK":Te>0?"WARN":"INFO",details:`${Ae} running, ${Ie} stopped, ${Te} total`})}catch(we){z.fail("EC2 check failed"),N.push({name:"EC2 Instances",status:"ERROR",details:we.message})}let U=new o("Checking RDS instances...");U.start();try{let{RDSClient:we}=await import("./chunk-gcazbfsv.js"),Ae=(await new we(A).describeDBInstances()).DBInstances||[],Ie=Ae.filter((Te)=>Te.DBInstanceStatus==="available").length;U.succeed("RDS instances checked"),N.push({name:"RDS Databases",status:Ae.length>0&&Ie===Ae.length?"OK":Ae.length>0?"WARN":"INFO",details:`${Ie} available, ${Ae.length} total`})}catch(we){U.fail("RDS check failed"),N.push({name:"RDS Databases",status:"ERROR",details:we.message})}let X=new o("Checking Lambda functions...");X.start();try{let{LambdaClient:we}=await import("./chunk-052sk11z.js"),Ae=(await new we(A).listFunctions()).Functions||[];X.succeed("Lambda functions checked"),N.push({name:"Lambda Functions",status:"OK",details:`${Ae.length} function(s)`})}catch(we){X.fail("Lambda check failed"),N.push({name:"Lambda Functions",status:"ERROR",details:we.message})}let te=new o("Checking S3 buckets...");te.start();try{let{S3Client:we}=await import("./chunk-1d4663e1.js"),Ae=(await new we(A).listBuckets()).Buckets||[];te.succeed("S3 buckets checked"),N.push({name:"S3 Buckets",status:"OK",details:`${Ae.length} bucket(s)`})}catch(we){te.fail("S3 check failed"),N.push({name:"S3 Buckets",status:"ERROR",details:we.message})}let re=new o("Checking CloudFront distributions...");re.start();try{let{CloudFrontClient:we}=await import("./chunk-675txngy.js"),Be=await new we().listDistributions(),Ae=Be.filter((Ie)=>Ie.Status==="Deployed").length;re.succeed("CloudFront checked"),N.push({name:"CloudFront",status:Be.length>0&&Ae===Be.length?"OK":Be.length>0?"WARN":"INFO",details:`${Ae} deployed, ${Be.length} total`})}catch(we){re.fail("CloudFront check failed"),N.push({name:"CloudFront",status:"ERROR",details:we.message})}let oe=new o("Checking SQS queues...");oe.start();try{let{SQSClient:we}=await import("./chunk-w3815pxd.js"),Ae=(await new we(A).listQueues()).QueueUrls||[];oe.succeed("SQS queues checked"),N.push({name:"SQS Queues",status:"OK",details:`${Ae.length} queue(s)`})}catch(we){oe.fail("SQS check failed"),N.push({name:"SQS Queues",status:"ERROR",details:we.message})}let ae=new o("Checking CloudFormation stacks...");ae.start();try{let{CloudFormationClient:we}=await import("./chunk-pa106cr4.js"),Ae=(await new we(A).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE","CREATE_IN_PROGRESS","UPDATE_IN_PROGRESS","ROLLBACK_COMPLETE","UPDATE_ROLLBACK_COMPLETE"])).StackSummaries||[],Ie=Ae.filter((_e)=>_e.StackStatus==="CREATE_COMPLETE"||_e.StackStatus==="UPDATE_COMPLETE").length,Te=Ae.filter((_e)=>_e.StackStatus?.includes("IN_PROGRESS")).length,Xe=Ae.filter((_e)=>_e.StackStatus?.includes("ROLLBACK")).length;ae.succeed("CloudFormation checked"),N.push({name:"CloudFormation",status:Xe>0?"WARN":Te>0?"INFO":"OK",details:`${Ie} healthy, ${Te} in progress, ${Xe} rolled back`})}catch(we){ae.fail("CloudFormation check failed"),N.push({name:"CloudFormation",status:"ERROR",details:we.message})}let ve=new o("Checking SSL certificates...");ve.start();try{let{ACMClient:we}=await import("./chunk-p8f1qgz2.js"),ke=new we("us-east-1"),Ae=(await ke.listCertificates()).CertificateSummaryList||[],Ie=await Promise.all(Ae.map((Ze)=>ke.describeCertificate({CertificateArn:Ze.CertificateArn}))),Te=Ie.filter((Ze)=>Ze.Status==="ISSUED").length,Xe=Ie.filter((Ze)=>Ze.Status==="PENDING_VALIDATION").length,_e=Ie.filter((Ze)=>{if(Ze.NotAfter)return(new Date(Ze.NotAfter).getTime()-Date.now())/86400000<30;return!1}).length;ve.succeed("SSL certificates checked"),N.push({name:"SSL Certificates",status:_e>0?"WARN":Xe>0?"INFO":"OK",details:`${Te} issued, ${Xe} pending${_e>0?`, ${_e} expiring soon`:""}`})}catch(we){ve.fail("ACM check failed"),N.push({name:"SSL Certificates",status:"ERROR",details:we.message})}e(`
632
+ Delete this trail?`,!1)){e("Operation cancelled");return}let z=new o("Deleting trail...");z.start(),await A.deleteTrail(t),z.succeed("Trail deleted")}catch(A){r(`Failed to delete trail: ${A.message}`),process.exit(1)}})}function rl(x){x.command("status","Show overall infrastructure health dashboard").option("--region <region>","AWS region").action(async(t)=>{i("Infrastructure Status Dashboard");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1";e(`Region: ${A}`),e(`Project: ${m.project.name||"Unknown"}`),e("");let N=[],z=new o("Checking EC2 instances...");z.start();try{let{EC2Client:we}=await import("./chunk-3eztnxgw.js"),Be=await new we(A).describeInstances({Filters:[{Name:"instance-state-name",Values:["running","pending","stopping","stopped"]}]}),Ae=0,Ie=0,Te=0;for(let Xe of Be.Reservations||[])for(let _e of Xe.Instances||[])if(Te++,_e.State?.Name==="running")Ae++;else if(_e.State?.Name==="stopped")Ie++;z.succeed("EC2 instances checked"),N.push({name:"EC2 Instances",status:Ae>0?"OK":Te>0?"WARN":"INFO",details:`${Ae} running, ${Ie} stopped, ${Te} total`})}catch(we){z.fail("EC2 check failed"),N.push({name:"EC2 Instances",status:"ERROR",details:we.message})}let U=new o("Checking RDS instances...");U.start();try{let{RDSClient:we}=await import("./chunk-pt71fyem.js"),Ae=(await new we(A).describeDBInstances()).DBInstances||[],Ie=Ae.filter((Te)=>Te.DBInstanceStatus==="available").length;U.succeed("RDS instances checked"),N.push({name:"RDS Databases",status:Ae.length>0&&Ie===Ae.length?"OK":Ae.length>0?"WARN":"INFO",details:`${Ie} available, ${Ae.length} total`})}catch(we){U.fail("RDS check failed"),N.push({name:"RDS Databases",status:"ERROR",details:we.message})}let X=new o("Checking Lambda functions...");X.start();try{let{LambdaClient:we}=await import("./chunk-9zhp2z0s.js"),Ae=(await new we(A).listFunctions()).Functions||[];X.succeed("Lambda functions checked"),N.push({name:"Lambda Functions",status:"OK",details:`${Ae.length} function(s)`})}catch(we){X.fail("Lambda check failed"),N.push({name:"Lambda Functions",status:"ERROR",details:we.message})}let te=new o("Checking S3 buckets...");te.start();try{let{S3Client:we}=await import("./chunk-yf3zys06.js"),Ae=(await new we(A).listBuckets()).Buckets||[];te.succeed("S3 buckets checked"),N.push({name:"S3 Buckets",status:"OK",details:`${Ae.length} bucket(s)`})}catch(we){te.fail("S3 check failed"),N.push({name:"S3 Buckets",status:"ERROR",details:we.message})}let re=new o("Checking CloudFront distributions...");re.start();try{let{CloudFrontClient:we}=await import("./chunk-3932p1z3.js"),Be=await new we().listDistributions(),Ae=Be.filter((Ie)=>Ie.Status==="Deployed").length;re.succeed("CloudFront checked"),N.push({name:"CloudFront",status:Be.length>0&&Ae===Be.length?"OK":Be.length>0?"WARN":"INFO",details:`${Ae} deployed, ${Be.length} total`})}catch(we){re.fail("CloudFront check failed"),N.push({name:"CloudFront",status:"ERROR",details:we.message})}let oe=new o("Checking SQS queues...");oe.start();try{let{SQSClient:we}=await import("./chunk-fwmje99n.js"),Ae=(await new we(A).listQueues()).QueueUrls||[];oe.succeed("SQS queues checked"),N.push({name:"SQS Queues",status:"OK",details:`${Ae.length} queue(s)`})}catch(we){oe.fail("SQS check failed"),N.push({name:"SQS Queues",status:"ERROR",details:we.message})}let ae=new o("Checking CloudFormation stacks...");ae.start();try{let{CloudFormationClient:we}=await import("./chunk-smj97rs5.js"),Ae=(await new we(A).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE","CREATE_IN_PROGRESS","UPDATE_IN_PROGRESS","ROLLBACK_COMPLETE","UPDATE_ROLLBACK_COMPLETE"])).StackSummaries||[],Ie=Ae.filter((_e)=>_e.StackStatus==="CREATE_COMPLETE"||_e.StackStatus==="UPDATE_COMPLETE").length,Te=Ae.filter((_e)=>_e.StackStatus?.includes("IN_PROGRESS")).length,Xe=Ae.filter((_e)=>_e.StackStatus?.includes("ROLLBACK")).length;ae.succeed("CloudFormation checked"),N.push({name:"CloudFormation",status:Xe>0?"WARN":Te>0?"INFO":"OK",details:`${Ie} healthy, ${Te} in progress, ${Xe} rolled back`})}catch(we){ae.fail("CloudFormation check failed"),N.push({name:"CloudFormation",status:"ERROR",details:we.message})}let ve=new o("Checking SSL certificates...");ve.start();try{let{ACMClient:we}=await import("./chunk-ppj604c9.js"),ke=new we("us-east-1"),Ae=(await ke.listCertificates()).CertificateSummaryList||[],Ie=await Promise.all(Ae.map((Ze)=>ke.describeCertificate({CertificateArn:Ze.CertificateArn}))),Te=Ie.filter((Ze)=>Ze.Status==="ISSUED").length,Xe=Ie.filter((Ze)=>Ze.Status==="PENDING_VALIDATION").length,_e=Ie.filter((Ze)=>{if(Ze.NotAfter)return(new Date(Ze.NotAfter).getTime()-Date.now())/86400000<30;return!1}).length;ve.succeed("SSL certificates checked"),N.push({name:"SSL Certificates",status:_e>0?"WARN":Xe>0?"INFO":"OK",details:`${Te} issued, ${Xe} pending${_e>0?`, ${_e} expiring soon`:""}`})}catch(we){ve.fail("ACM check failed"),N.push({name:"SSL Certificates",status:"ERROR",details:we.message})}e(`
633
633
  `+"=".repeat(60)),e("HEALTH SUMMARY"),e("=".repeat(60)+`
634
- `);for(let we of N){let ke="";if(we.status==="OK")ke=`${T.green}[OK]${T.reset}`;else if(we.status==="WARN")ke=`${T.yellow}[WARN]${T.reset}`;else if(we.status==="ERROR")ke=`${T.red}[ERROR]${T.reset}`;else ke=`${T.blue}[INFO]${T.reset}`;console.log(`${ke} ${we.name.padEnd(20)} ${we.details}`)}let Ce=N.some((we)=>we.status==="ERROR"),Re=N.some((we)=>we.status==="WARN");if(e(""),Ce)r("Some services have errors. Check the details above.");else if(Re)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)}}),x.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,A=new Date(m.getFullYear(),m.getMonth(),1),N=new Date(m.getFullYear(),m.getMonth()+1,0);e(`Period: ${A.toISOString().split("T")[0]} to ${N.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)}}),x.command("status:alarms","Show CloudWatch alarm status").option("--region <region>","AWS region").action(async(t)=>{i("CloudWatch Alarms");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",{AWSClient:N}=await import("./chunk-jxh78anm.js");class z{client;region;constructor(Ce){this.region=Ce,this.client=new N}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 U=new z(A),X=new o("Fetching alarms...");X.start();let re=(await U.describeAlarms()).MetricAlarms||[];if(X.succeed(`Found ${re.length} alarm(s)`),re.length===0){e("No CloudWatch alarms configured");return}let oe=re.filter((Ce)=>Ce.StateValue==="ALARM"),ae=re.filter((Ce)=>Ce.StateValue==="OK"),ve=re.filter((Ce)=>Ce.StateValue==="INSUFFICIENT_DATA");if(e(""),e(`Alarming: ${oe.length}`),e(`OK: ${ae.length}`),e(`Insufficient Data: ${ve.length}`),oe.length>0){e(`
635
- Alarms in ALARM state:`);for(let Ce of oe)r(` - ${Ce.AlarmName}: ${Ce.AlarmDescription||"No description"}`)}}catch(m){r(`Failed to get alarms: ${m.message}`),process.exit(1)}})}async function mi(x){let{AWSClient:t}=await import("./chunk-jxh78anm.js");class m{client;region;constructor(A){this.region=A,this.client=new t}async jsonRpcRequest(A,N){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.${A}`},body:JSON.stringify(N)})}async listBackupVaults(){return this.jsonRpcRequest("ListBackupVaults",{})}async listBackupPlans(){return this.jsonRpcRequest("ListBackupPlans",{})}async listRecoveryPointsByBackupVault(A,N){return this.jsonRpcRequest("ListRecoveryPointsByBackupVault",{BackupVaultName:A,...N})}async describeBackupVault(A){return this.jsonRpcRequest("DescribeBackupVault",{BackupVaultName:A})}async getBackupPlan(A){return this.jsonRpcRequest("GetBackupPlan",{BackupPlanId:A})}async createBackupVault(A,N){return this.jsonRpcRequest("CreateBackupVault",{BackupVaultName:A,...N})}async deleteBackupVault(A){return this.jsonRpcRequest("DeleteBackupVault",{BackupVaultName:A})}async createBackupPlan(A){return this.jsonRpcRequest("CreateBackupPlan",{BackupPlan:A})}async deleteBackupPlan(A){return this.jsonRpcRequest("DeleteBackupPlan",{BackupPlanId:A})}async startBackupJob(A){return this.jsonRpcRequest("StartBackupJob",A)}async startRestoreJob(A){return this.jsonRpcRequest("StartRestoreJob",A)}async describeBackupJob(A){return this.jsonRpcRequest("DescribeBackupJob",{BackupJobId:A})}async describeRestoreJob(A){return this.jsonRpcRequest("DescribeRestoreJob",{RestoreJobId:A})}async listBackupJobs(A){return this.jsonRpcRequest("ListBackupJobs",A||{})}async listRestoreJobs(A){return this.jsonRpcRequest("ListRestoreJobs",A||{})}async createBackupSelection(A,N){return this.jsonRpcRequest("CreateBackupSelection",{BackupPlanId:A,BackupSelection:N})}async listBackupSelections(A){return this.jsonRpcRequest("ListBackupSelections",{BackupPlanId:A})}}return new m(x)}function il(x){x.command("backup:vaults","List backup vaults").option("--region <region>","AWS region").action(async(t)=>{i("Backup Vaults");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",N=await mi(A),z=new o("Fetching vaults...");z.start();let X=(await N.listBackupVaults()).BackupVaultList||[];if(z.succeed(`Found ${X.length} vault(s)`),X.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"],X.map((te)=>[te.BackupVaultName||"N/A",(te.NumberOfRecoveryPoints||0).toString(),te.CreationDate?new Date(te.CreationDate).toLocaleDateString():"N/A",te.EncryptionKeyArn?"Yes":"Default"]))}catch(m){r(`Failed to list vaults: ${m.message}`),process.exit(1)}}),x.command("backup:list","List backup plans").option("--region <region>","AWS region").action(async(t)=>{i("Backup Plans");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",N=await mi(A),z=new o("Fetching backup plans...");z.start();let X=(await N.listBackupPlans()).BackupPlansList||[];if(z.succeed(`Found ${X.length} backup plan(s)`),X.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"],X.map((te)=>[te.BackupPlanName||"N/A",te.BackupPlanId||"N/A",te.VersionId?.substring(0,8)||"N/A",te.CreationDate?new Date(te.CreationDate).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list backup plans: ${m.message}`),process.exit(1)}}),x.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 A=await Oe(),N=m.region||A.project.region||"us-east-1",z=await mi(N),U=new o("Fetching recovery points...");U.start();let te=(await z.listRecoveryPointsByBackupVault(t,{MaxResults:Number.parseInt(m.limit)})).RecoveryPoints||[];if(U.succeed(`Found ${te.length} recovery point(s)`),te.length===0){e("No recovery points found");return}c(["Resource","Status","Created","Size","Lifecycle"],te.map((re)=>[re.ResourceArn?.split(":").pop()||"N/A",re.Status||"N/A",re.CreationDate?new Date(re.CreationDate).toLocaleString():"N/A",re.BackupSizeInBytes?Vd(re.BackupSizeInBytes):"N/A",re.Lifecycle?.DeleteAfterDays?`${re.Lifecycle.DeleteAfterDays} days`:"Indefinite"]))}catch(A){r(`Failed to list recovery points: ${A.message}`),process.exit(1)}}),x.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 A=await mi(m.region);if(e(`Vault Name: ${t}`),e(`Region: ${m.region}`),e(`Encryption: ${m.kmsKey?"Custom KMS":"AWS Managed"}`),!await d(`
634
+ `);for(let we of N){let ke="";if(we.status==="OK")ke=`${T.green}[OK]${T.reset}`;else if(we.status==="WARN")ke=`${T.yellow}[WARN]${T.reset}`;else if(we.status==="ERROR")ke=`${T.red}[ERROR]${T.reset}`;else ke=`${T.blue}[INFO]${T.reset}`;console.log(`${ke} ${we.name.padEnd(20)} ${we.details}`)}let Ce=N.some((we)=>we.status==="ERROR"),Re=N.some((we)=>we.status==="WARN");if(e(""),Ce)r("Some services have errors. Check the details above.");else if(Re)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)}}),x.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,A=new Date(m.getFullYear(),m.getMonth(),1),N=new Date(m.getFullYear(),m.getMonth()+1,0);e(`Period: ${A.toISOString().split("T")[0]} to ${N.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)}}),x.command("status:alarms","Show CloudWatch alarm status").option("--region <region>","AWS region").action(async(t)=>{i("CloudWatch Alarms");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",{AWSClient:N}=await import("./chunk-dvv2z19r.js");class z{client;region;constructor(Ce){this.region=Ce,this.client=new N}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 U=new z(A),X=new o("Fetching alarms...");X.start();let re=(await U.describeAlarms()).MetricAlarms||[];if(X.succeed(`Found ${re.length} alarm(s)`),re.length===0){e("No CloudWatch alarms configured");return}let oe=re.filter((Ce)=>Ce.StateValue==="ALARM"),ae=re.filter((Ce)=>Ce.StateValue==="OK"),ve=re.filter((Ce)=>Ce.StateValue==="INSUFFICIENT_DATA");if(e(""),e(`Alarming: ${oe.length}`),e(`OK: ${ae.length}`),e(`Insufficient Data: ${ve.length}`),oe.length>0){e(`
635
+ Alarms in ALARM state:`);for(let Ce of oe)r(` - ${Ce.AlarmName}: ${Ce.AlarmDescription||"No description"}`)}}catch(m){r(`Failed to get alarms: ${m.message}`),process.exit(1)}})}async function mi(x){let{AWSClient:t}=await import("./chunk-dvv2z19r.js");class m{client;region;constructor(A){this.region=A,this.client=new t}async jsonRpcRequest(A,N){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.${A}`},body:JSON.stringify(N)})}async listBackupVaults(){return this.jsonRpcRequest("ListBackupVaults",{})}async listBackupPlans(){return this.jsonRpcRequest("ListBackupPlans",{})}async listRecoveryPointsByBackupVault(A,N){return this.jsonRpcRequest("ListRecoveryPointsByBackupVault",{BackupVaultName:A,...N})}async describeBackupVault(A){return this.jsonRpcRequest("DescribeBackupVault",{BackupVaultName:A})}async getBackupPlan(A){return this.jsonRpcRequest("GetBackupPlan",{BackupPlanId:A})}async createBackupVault(A,N){return this.jsonRpcRequest("CreateBackupVault",{BackupVaultName:A,...N})}async deleteBackupVault(A){return this.jsonRpcRequest("DeleteBackupVault",{BackupVaultName:A})}async createBackupPlan(A){return this.jsonRpcRequest("CreateBackupPlan",{BackupPlan:A})}async deleteBackupPlan(A){return this.jsonRpcRequest("DeleteBackupPlan",{BackupPlanId:A})}async startBackupJob(A){return this.jsonRpcRequest("StartBackupJob",A)}async startRestoreJob(A){return this.jsonRpcRequest("StartRestoreJob",A)}async describeBackupJob(A){return this.jsonRpcRequest("DescribeBackupJob",{BackupJobId:A})}async describeRestoreJob(A){return this.jsonRpcRequest("DescribeRestoreJob",{RestoreJobId:A})}async listBackupJobs(A){return this.jsonRpcRequest("ListBackupJobs",A||{})}async listRestoreJobs(A){return this.jsonRpcRequest("ListRestoreJobs",A||{})}async createBackupSelection(A,N){return this.jsonRpcRequest("CreateBackupSelection",{BackupPlanId:A,BackupSelection:N})}async listBackupSelections(A){return this.jsonRpcRequest("ListBackupSelections",{BackupPlanId:A})}}return new m(x)}function il(x){x.command("backup:vaults","List backup vaults").option("--region <region>","AWS region").action(async(t)=>{i("Backup Vaults");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",N=await mi(A),z=new o("Fetching vaults...");z.start();let X=(await N.listBackupVaults()).BackupVaultList||[];if(z.succeed(`Found ${X.length} vault(s)`),X.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"],X.map((te)=>[te.BackupVaultName||"N/A",(te.NumberOfRecoveryPoints||0).toString(),te.CreationDate?new Date(te.CreationDate).toLocaleDateString():"N/A",te.EncryptionKeyArn?"Yes":"Default"]))}catch(m){r(`Failed to list vaults: ${m.message}`),process.exit(1)}}),x.command("backup:list","List backup plans").option("--region <region>","AWS region").action(async(t)=>{i("Backup Plans");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",N=await mi(A),z=new o("Fetching backup plans...");z.start();let X=(await N.listBackupPlans()).BackupPlansList||[];if(z.succeed(`Found ${X.length} backup plan(s)`),X.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"],X.map((te)=>[te.BackupPlanName||"N/A",te.BackupPlanId||"N/A",te.VersionId?.substring(0,8)||"N/A",te.CreationDate?new Date(te.CreationDate).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list backup plans: ${m.message}`),process.exit(1)}}),x.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 A=await Oe(),N=m.region||A.project.region||"us-east-1",z=await mi(N),U=new o("Fetching recovery points...");U.start();let te=(await z.listRecoveryPointsByBackupVault(t,{MaxResults:Number.parseInt(m.limit)})).RecoveryPoints||[];if(U.succeed(`Found ${te.length} recovery point(s)`),te.length===0){e("No recovery points found");return}c(["Resource","Status","Created","Size","Lifecycle"],te.map((re)=>[re.ResourceArn?.split(":").pop()||"N/A",re.Status||"N/A",re.CreationDate?new Date(re.CreationDate).toLocaleString():"N/A",re.BackupSizeInBytes?Vd(re.BackupSizeInBytes):"N/A",re.Lifecycle?.DeleteAfterDays?`${re.Lifecycle.DeleteAfterDays} days`:"Indefinite"]))}catch(A){r(`Failed to list recovery points: ${A.message}`),process.exit(1)}}),x.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 A=await mi(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 z=new o("Creating vault...");z.start(),await A.createBackupVault(t,{EncryptionKeyArn:m.kmsKey}),z.succeed("Vault created"),n(`
637
637
  Vault: ${t}`)}catch(A){r(`Failed to create vault: ${A.message}`),process.exit(1)}}),x.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 A=await mi(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 z=new o("Creating backup plan...");z.start();let U={DeleteAfterDays:Number.parseInt(m.retention)};if(m.lifecycleCold)U.MoveToColdStorageAfterDays=Number.parseInt(m.lifecycleCold);let X=await A.createBackupPlan({BackupPlanName:t,Rules:[{RuleName:`${t}-daily`,TargetBackupVaultName:m.vault,ScheduleExpression:m.schedule,StartWindowMinutes:60,CompletionWindowMinutes:180,Lifecycle:U}]});z.succeed("Backup plan created"),n(`
@@ -644,7 +644,7 @@ Job ID: ${U.BackupJobId}`),e("Use `cloud backup:jobs` to check status")}catch(A)
644
644
  Restore will create new resources. Review carefully.`),!await d(`
645
645
  Start restore?`,!1)){e("Operation cancelled");return}let U=new o("Starting restore job...");U.start();let X=await A.startRestoreJob({RecoveryPointArn:t,IamRoleArn:m.role,Metadata:N,IdempotencyToken:`cli-${Date.now()}`});U.succeed("Restore job started"),n(`
646
646
  Job ID: ${X.RestoreJobId}`),e("Use `cloud backup:restore-jobs` to check status")}catch(A){r(`Failed to start restore: ${A.message}`),process.exit(1)}}),x.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 Oe(),A=t.region||m.project.region||"us-east-1",N=await mi(A),z=new o("Fetching backup jobs...");z.start();let X=(await N.listBackupJobs({ByState:t.state,MaxResults:50})).BackupJobs||[];if(z.succeed(`Found ${X.length} job(s)`),X.length===0){e("No backup jobs found");return}c(["Job ID","Resource","State","Started","Size"],X.map((te)=>[te.BackupJobId?.substring(0,16)||"N/A",te.ResourceArn?.split(":").pop()||"N/A",te.State||"N/A",te.CreationDate?new Date(te.CreationDate).toLocaleString():"N/A",te.BackupSizeInBytes?Vd(te.BackupSizeInBytes):"N/A"]))}catch(m){r(`Failed to list backup jobs: ${m.message}`),process.exit(1)}}),x.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 Oe(),A=t.region||m.project.region||"us-east-1",N=await mi(A),z=new o("Fetching restore jobs...");z.start();let X=(await N.listRestoreJobs({ByStatus:t.status,MaxResults:50})).RestoreJobs||[];if(z.succeed(`Found ${X.length} job(s)`),X.length===0){e("No restore jobs found");return}c(["Job ID","Resource","Status","Started","Completed"],X.map((te)=>[te.RestoreJobId?.substring(0,16)||"N/A",te.CreatedResourceArn?.split(":").pop()||"Pending",te.Status||"N/A",te.CreationDate?new Date(te.CreationDate).toLocaleString():"N/A",te.CompletionDate?new Date(te.CompletionDate).toLocaleString():"-"]))}catch(m){r(`Failed to list restore jobs: ${m.message}`),process.exit(1)}}),x.command("backup:schedule","Show backup schedule overview").option("--region <region>","AWS region").action(async(t)=>{i("Backup Schedule Overview");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",N=await mi(A),z=new o("Fetching backup plans...");z.start();let X=(await N.listBackupPlans()).BackupPlansList||[];if(z.succeed(`Found ${X.length} backup plan(s)`),X.length===0){e("No backup plans configured");return}for(let te of X){e(`
647
- ${te.BackupPlanName}`),e("=".repeat(40));try{let oe=(await N.getBackupPlan(te.BackupPlanId)).BackupPlan?.Rules||[];for(let ve of oe)if(e(` Rule: ${ve.RuleName}`),e(` Schedule: ${ve.ScheduleExpression}`),e(` Vault: ${ve.TargetBackupVaultName}`),ve.Lifecycle?.DeleteAfterDays)e(` Retention: ${ve.Lifecycle.DeleteAfterDays} days`);let ae=await N.listBackupSelections(te.BackupPlanId);if(ae.BackupSelectionsList&&ae.BackupSelectionsList.length>0)e(` Selections: ${ae.BackupSelectionsList.length}`)}catch{e(" (Unable to load plan details)")}}}catch(m){r(`Failed to get schedule: ${m.message}`),process.exit(1)}})}function Vd(x){if(x===0)return"0 B";let t=1024,m=["B","KB","MB","GB","TB"],A=Math.floor(Math.log(x)/Math.log(t));return`${Number.parseFloat((x/t**A).toFixed(2))} ${m[A]}`}async function ng(x){let t=await Oe(),m=y(process.cwd(),t),A=x??Object.keys(t.environments??{})[0]??"production",N=m.environments.get(A);if(!N)throw m.store.close(),Error(`Environment ${A} was not found.`);let z=new b(m.store,{workerId:`cli:${process.pid}`}),U=new zn(m.store),X=new Fr(U,z),te=new Xt(m.store),re=new Qt(m.store,_(process.cwd())),oe=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:t,controlPlane:m,env:A,environmentRecord:N,queue:z,store:U,coordinator:X,dataServices:te,secrets:re,actor:oe}}async function Ai(x,t){let m=await ng(x);try{return await t(m)}finally{m.controlPlane.store.close()}}async function Ii(x){try{await x()}catch(t){r(t instanceof Error?t.message:String(t))}}function Hi(x,t,m,A){x.controlPlane.store.appendEvent({organizationId:x.controlPlane.organization.id,projectId:x.controlPlane.project.id,resourceId:A,actorId:x.actor.id,type:`backup.${t}`,payload:m})}function rg(x,t){let m=x.store.getPolicy(t)??x.store.listPolicies(x.controlPlane.project.id,x.environmentRecord.id).find((A)=>A.name===t);if(!m||m.environmentId!==x.environmentRecord.id)throw Error(`Backup policy ${t} was not found in ${x.env}.`);return m}function ta(x,t){let m=x.store.getRecoveryPoint(t),A=x.store.listRecoveryPoints(x.controlPlane.project.id).filter((z)=>z.id.startsWith(t)),N=m??(A.length===1?A[0]:void 0);if(!N)throw Error(A.length>1?`Recovery point prefix ${t} is ambiguous.`:`Recovery point ${t} was not found.`);return N}function Wd(x,t){let m=x.store.getDestination(t)??x.store.listDestinations(x.controlPlane.project.id).find((A)=>A.name===t);if(!m)throw Error(`Backup destination ${t} was not found.`);return m}function ig(x,t){return x.kind==="volume"?{volumeName:t}:x.kind==="logical_database"?{targetId:t,dataServiceId:t}:{targetId:t}}function og(x,t=[]){let m=new Map(t.map((A)=>[A.id,A.name]));return x.map((A)=>[A.id,A.status,A.verificationState,A.kind,m.get(A.policyId??"")??"manual",A.pointInTime,A.sizeBytes?`${A.sizeBytes} B`:"provider",A.held?"hold":A.pinned?"pinned":"retention"])}function ol(x){x.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)=>Ii(async()=>Ai(t.env,async(m)=>{let A=m.store.listPolicies(m.controlPlane.project.id,m.environmentRecord.id),N=new Set(A.map((X)=>X.id)),z=m.store.listRecoveryPoints(m.controlPlane.project.id).filter((X)=>!X.policyId||N.has(X.policyId)),U={coverage:m.store.coverage(m.controlPlane.project.id).filter((X)=>N.has(X.policy.id)),destinations:m.store.listDestinations(m.controlPlane.project.id).map((X)=>({...X,credentialRef:void 0,encryptionKeyRef:void 0,credentialsConfigured:!!X.credentialRef,clientEncryptionConfigured:!!X.encryptionKeyRef})),policies:A,recoveryPoints:z,jobs:m.store.listJobs(m.controlPlane.project.id).filter((X)=>!X.policyId||N.has(X.policyId))};if(t.json)e(JSON.stringify(U,null,2));else c(["Policy","RPO","Destination","Latest point","Verification"],U.coverage.map((X)=>[X.policy.name,X.missedRpo?"MISSED":"protected",X.destinationHealthy?"healthy":"attention",X.lastRecoveryPoint?.pointInTime??"none",X.lastRecoveryPoint?.verificationState??"none"])),c(["ID","State","Verification","Kind","Policy","Point in time","Size","Protection"],og(z,A))}))),x.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)=>Ii(async()=>Ai(t.env,async(m)=>{let A=new Set(m.store.listPolicies(m.controlPlane.project.id,m.environmentRecord.id).map((z)=>z.id)),N=m.store.listJobs(m.controlPlane.project.id).filter((z)=>!z.policyId||A.has(z.policyId));if(t.json)e(JSON.stringify(N,null,2));else c(["ID","Kind","State","Phase","Operation","Started","Finished","Error"],N.map((z)=>[z.id,z.kind,z.status,String(z.progress.phase??"queued"),z.operationId??"—",z.startedAt??"—",z.finishedAt??"—",z.error??"—"]))}))),x.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)=>Ii(async()=>Ai(m.env,async(A)=>{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 N=`secret://data-services/backups/${A.controlPlane.project.id}/${t}`,z=[];try{let U,X;if(m.credentialsEnv){let re=process.env[m.credentialsEnv];if(!re)throw Error(`${m.credentialsEnv} is empty.`);U=`${N}/credentials`,await A.secrets.put(U,re),z.push(U)}if(m.encryption!=="provider"){if(!m.encryptionKeyEnv)throw Error("Client encryption requires --encryption-key-env.");let re=process.env[m.encryptionKeyEnv];if(!re||re.length<32)throw Error(`${m.encryptionKeyEnv} must contain at least 32 characters.`);X=`${N}/encryption-key`,await A.secrets.put(X,re),z.push(X)}let te=A.store.createDestination({organizationId:A.controlPlane.organization.id,projectId:A.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??A.config.project.region,forcePathStyle:!!m.pathStyle,credentialRef:U,encryption:m.encryption,encryptionKeyRef:X,immutability:{objectLock:!!m.lockDays,defaultRetentionDays:m.lockDays?Number(m.lockDays):void 0},status:"untested"});Hi(A,"destination_created",{destinationId:te.id,provider:te.provider}),n(`Created destination ${te.name} (${te.id}); test it with cloud recovery:destination:test ${te.name}`)}catch(U){for(let X of z)await A.secrets.remove(X).catch(()=>{});throw U}}))),x.command("recovery:destination:test <destination>","Write, read, verify, and clean a destination health object").option("--env <environment>","Target environment").action(async(t,m)=>Ii(async()=>Ai(m.env,async(A)=>{let N=Wd(A,t);if(N.provider==="aws_backup")throw Error("AWS Backup is verified by creating a provider recovery point.");try{await new Hn(A.secrets).test(N),A.store.recordDestinationTest(N.id,{ok:!0}),Hi(A,"destination_tested",{destinationId:N.id,ok:!0}),n(`${N.name} passed write, read, checksum, and cleanup checks.`)}catch(z){let U=z instanceof Error?z.message:String(z);throw A.store.recordDestinationTest(N.id,{ok:!1,error:U}),Hi(A,"destination_tested",{destinationId:N.id,ok:!1}),z}}))),x.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)=>Ii(async()=>Ai(m.env,async(A)=>{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 N=Wd(A,m.destination),z=A.dataServices.list(A.controlPlane.project.id,A.environmentRecord.id),U=m.dataService?A.dataServices.get(m.dataService)??z.find((ve)=>ve.name===m.dataService):void 0,X=A.controlPlane.store.listResources(A.controlPlane.project.id,A.environmentRecord.id),te=m.resource?X.find((ve)=>ve.id===m.resource||ve.slug===m.resource):void 0,re=m.include?Array.isArray(m.include)?m.include:[m.include]:[],oe=m.exclude?Array.isArray(m.exclude)?m.exclude:[m.exclude]:[];if(["managed_database","logical_database"].includes(m.kind??"")&&!U)throw Error("--data-service must identify a service in this environment.");if(m.kind==="volume"&&(!te||!m.volume))throw Error("Volume policies require --resource and --volume.");if(m.kind==="files"&&(!te||!re.length))throw Error("File policies require --resource and at least one --include path.");if(m.kind==="infrastructure"&&(!re.some((ve)=>ve.startsWith("resource:arn:"))||!re.some((ve)=>ve.startsWith("role:arn:"))))throw Error("Infrastructure policies require --include resource:<arn> and --include role:<arn>.");let ae=A.store.createPolicy({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,environmentId:A.environmentRecord.id,resourceId:te?.id,dataServiceId:U?.id,destinationId:N.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,...re]:re,excludePatterns:oe,expectedRpoMinutes:Number(m.rpo)||1440,expectedRtoMinutes:Number(m.rto)||120,enabled:!m.disabled});Hi(A,"policy_created",{policyId:ae.id,resourceKind:ae.resourceKind},ae.resourceId),n(`Created policy ${ae.name} (${ae.id}); next run ${ae.nextRunAt}`)}))),x.command("recovery:run <policy>","Queue an on-demand run of a recovery policy").option("--env <environment>","Target environment").action(async(t,m)=>Ii(async()=>Ai(m.env,async(A)=>{let N=rg(A,t),z=A.coordinator.enqueueBackup(N,new Date().toISOString(),A.actor.id);Hi(A,"run_queued",{policyId:N.id,backupJobId:z.id},N.resourceId),n(`Queued backup job ${z.id}; inspect it with cloud recovery:jobs`)}))),x.command("recovery:verify <point>","Queue independent verification of a recovery point").option("--env <environment>","Target environment").action(async(t,m)=>Ii(async()=>Ai(m.env,async(A)=>{let N=ta(A,t),z=A.coordinator.enqueueVerification(N);Hi(A,"verification_queued",{recoveryPointId:N.id,backupJobId:z.id},N.resourceId),n(`Queued verification job ${z.id}`)}))),x.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,A)=>Ii(async()=>Ai(A.env,async(N)=>{if(!["isolated","in_place"].includes(A.mode??""))throw Error("--mode must be isolated or in_place.");if(A.drill&&A.mode==="in_place")throw Error("Recovery drills require --mode isolated.");let z=ta(N,t),U={mode:A.mode,target:{...ig(z,m),...A.metadata?{metadata:JSON.parse(A.metadata)}:{},inPlace:A.mode==="in_place"},targetName:m,confirm:A.confirm,recentAuth:!0,downtimeAcknowledged:!!A.ackDowntime,safetyBackupId:A.safety?ta(N,A.safety).id:void 0},X=N.coordinator.planRestore(z,U);if(!A.execute){e(JSON.stringify({...X,productionExecutionCreated:!1},null,2));return}let te=N.coordinator.enqueueRestore(z,{...U,drill:!!A.drill,actorId:N.actor.id});Hi(N,A.drill?"drill_queued":"restore_queued",{recoveryPointId:z.id,backupJobId:te.id,mode:U.mode,target:m},z.resourceId),n(`Queued ${A.drill?"drill":"restore"} job ${te.id}`)}))),x.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)=>Ii(async()=>Ai(m.env,async(A)=>{let N=ta(A,t);if(![m.pin,m.unpin,m.hold,m.releaseHold].filter(Boolean).length)throw Error("Choose --pin, --unpin, --hold, or --release-hold.");let z=A.store.updateRecoveryPoint(N.id,{pinned:m.pin?!0:m.unpin?!1:N.pinned,held:m.hold?!0:m.releaseHold?!1:N.held});Hi(A,"protection_updated",{recoveryPointId:N.id,pinned:z.pinned,held:z.held},N.resourceId),n(`${z.id}: pinned=${z.pinned} held=${z.held}`)}))),x.command("recovery:retention","Queue deletion of expired, unlocked, unheld recovery points").option("--env <environment>","Target environment").action(async(t)=>Ii(async()=>Ai(t.env,async(m)=>{let A=m.coordinator.enqueueRetention();Hi(m,"retention_queued",{jobs:A.length}),n(`Queued ${A.length} retention cleanup job${A.length===1?"":"s"}.`)})))}function sl(x){x.command("api:list","List all API Gateway APIs").option("--region <region>","AWS region").action(async(t)=>{i("API Gateway APIs");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",{LambdaClient:N}=await import("./chunk-052sk11z.js"),z=new N(A),U=new o("Fetching APIs...");U.start();let{CloudFormationClient:X}=await import("./chunk-pa106cr4.js"),oe=(await new X(A).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE"])).StackSummaries.filter((ae)=>ae.StackName?.includes("api")||ae.StackName?.includes("Api")||ae.StackName?.includes("API"));if(U.succeed("APIs listed"),e(`
647
+ ${te.BackupPlanName}`),e("=".repeat(40));try{let oe=(await N.getBackupPlan(te.BackupPlanId)).BackupPlan?.Rules||[];for(let ve of oe)if(e(` Rule: ${ve.RuleName}`),e(` Schedule: ${ve.ScheduleExpression}`),e(` Vault: ${ve.TargetBackupVaultName}`),ve.Lifecycle?.DeleteAfterDays)e(` Retention: ${ve.Lifecycle.DeleteAfterDays} days`);let ae=await N.listBackupSelections(te.BackupPlanId);if(ae.BackupSelectionsList&&ae.BackupSelectionsList.length>0)e(` Selections: ${ae.BackupSelectionsList.length}`)}catch{e(" (Unable to load plan details)")}}}catch(m){r(`Failed to get schedule: ${m.message}`),process.exit(1)}})}function Vd(x){if(x===0)return"0 B";let t=1024,m=["B","KB","MB","GB","TB"],A=Math.floor(Math.log(x)/Math.log(t));return`${Number.parseFloat((x/t**A).toFixed(2))} ${m[A]}`}async function ng(x){let t=await Oe(),m=y(process.cwd(),t),A=x??Object.keys(t.environments??{})[0]??"production",N=m.environments.get(A);if(!N)throw m.store.close(),Error(`Environment ${A} was not found.`);let z=new b(m.store,{workerId:`cli:${process.pid}`}),U=new zn(m.store),X=new Fr(U,z),te=new Xt(m.store),re=new Qt(m.store,_(process.cwd())),oe=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:t,controlPlane:m,env:A,environmentRecord:N,queue:z,store:U,coordinator:X,dataServices:te,secrets:re,actor:oe}}async function Ai(x,t){let m=await ng(x);try{return await t(m)}finally{m.controlPlane.store.close()}}async function Ii(x){try{await x()}catch(t){r(t instanceof Error?t.message:String(t))}}function Hi(x,t,m,A){x.controlPlane.store.appendEvent({organizationId:x.controlPlane.organization.id,projectId:x.controlPlane.project.id,resourceId:A,actorId:x.actor.id,type:`backup.${t}`,payload:m})}function rg(x,t){let m=x.store.getPolicy(t)??x.store.listPolicies(x.controlPlane.project.id,x.environmentRecord.id).find((A)=>A.name===t);if(!m||m.environmentId!==x.environmentRecord.id)throw Error(`Backup policy ${t} was not found in ${x.env}.`);return m}function ta(x,t){let m=x.store.getRecoveryPoint(t),A=x.store.listRecoveryPoints(x.controlPlane.project.id).filter((z)=>z.id.startsWith(t)),N=m??(A.length===1?A[0]:void 0);if(!N)throw Error(A.length>1?`Recovery point prefix ${t} is ambiguous.`:`Recovery point ${t} was not found.`);return N}function Wd(x,t){let m=x.store.getDestination(t)??x.store.listDestinations(x.controlPlane.project.id).find((A)=>A.name===t);if(!m)throw Error(`Backup destination ${t} was not found.`);return m}function ig(x,t){return x.kind==="volume"?{volumeName:t}:x.kind==="logical_database"?{targetId:t,dataServiceId:t}:{targetId:t}}function og(x,t=[]){let m=new Map(t.map((A)=>[A.id,A.name]));return x.map((A)=>[A.id,A.status,A.verificationState,A.kind,m.get(A.policyId??"")??"manual",A.pointInTime,A.sizeBytes?`${A.sizeBytes} B`:"provider",A.held?"hold":A.pinned?"pinned":"retention"])}function ol(x){x.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)=>Ii(async()=>Ai(t.env,async(m)=>{let A=m.store.listPolicies(m.controlPlane.project.id,m.environmentRecord.id),N=new Set(A.map((X)=>X.id)),z=m.store.listRecoveryPoints(m.controlPlane.project.id).filter((X)=>!X.policyId||N.has(X.policyId)),U={coverage:m.store.coverage(m.controlPlane.project.id).filter((X)=>N.has(X.policy.id)),destinations:m.store.listDestinations(m.controlPlane.project.id).map((X)=>({...X,credentialRef:void 0,encryptionKeyRef:void 0,credentialsConfigured:!!X.credentialRef,clientEncryptionConfigured:!!X.encryptionKeyRef})),policies:A,recoveryPoints:z,jobs:m.store.listJobs(m.controlPlane.project.id).filter((X)=>!X.policyId||N.has(X.policyId))};if(t.json)e(JSON.stringify(U,null,2));else c(["Policy","RPO","Destination","Latest point","Verification"],U.coverage.map((X)=>[X.policy.name,X.missedRpo?"MISSED":"protected",X.destinationHealthy?"healthy":"attention",X.lastRecoveryPoint?.pointInTime??"none",X.lastRecoveryPoint?.verificationState??"none"])),c(["ID","State","Verification","Kind","Policy","Point in time","Size","Protection"],og(z,A))}))),x.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)=>Ii(async()=>Ai(t.env,async(m)=>{let A=new Set(m.store.listPolicies(m.controlPlane.project.id,m.environmentRecord.id).map((z)=>z.id)),N=m.store.listJobs(m.controlPlane.project.id).filter((z)=>!z.policyId||A.has(z.policyId));if(t.json)e(JSON.stringify(N,null,2));else c(["ID","Kind","State","Phase","Operation","Started","Finished","Error"],N.map((z)=>[z.id,z.kind,z.status,String(z.progress.phase??"queued"),z.operationId??"—",z.startedAt??"—",z.finishedAt??"—",z.error??"—"]))}))),x.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)=>Ii(async()=>Ai(m.env,async(A)=>{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 N=`secret://data-services/backups/${A.controlPlane.project.id}/${t}`,z=[];try{let U,X;if(m.credentialsEnv){let re=process.env[m.credentialsEnv];if(!re)throw Error(`${m.credentialsEnv} is empty.`);U=`${N}/credentials`,await A.secrets.put(U,re),z.push(U)}if(m.encryption!=="provider"){if(!m.encryptionKeyEnv)throw Error("Client encryption requires --encryption-key-env.");let re=process.env[m.encryptionKeyEnv];if(!re||re.length<32)throw Error(`${m.encryptionKeyEnv} must contain at least 32 characters.`);X=`${N}/encryption-key`,await A.secrets.put(X,re),z.push(X)}let te=A.store.createDestination({organizationId:A.controlPlane.organization.id,projectId:A.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??A.config.project.region,forcePathStyle:!!m.pathStyle,credentialRef:U,encryption:m.encryption,encryptionKeyRef:X,immutability:{objectLock:!!m.lockDays,defaultRetentionDays:m.lockDays?Number(m.lockDays):void 0},status:"untested"});Hi(A,"destination_created",{destinationId:te.id,provider:te.provider}),n(`Created destination ${te.name} (${te.id}); test it with cloud recovery:destination:test ${te.name}`)}catch(U){for(let X of z)await A.secrets.remove(X).catch(()=>{});throw U}}))),x.command("recovery:destination:test <destination>","Write, read, verify, and clean a destination health object").option("--env <environment>","Target environment").action(async(t,m)=>Ii(async()=>Ai(m.env,async(A)=>{let N=Wd(A,t);if(N.provider==="aws_backup")throw Error("AWS Backup is verified by creating a provider recovery point.");try{await new Hn(A.secrets).test(N),A.store.recordDestinationTest(N.id,{ok:!0}),Hi(A,"destination_tested",{destinationId:N.id,ok:!0}),n(`${N.name} passed write, read, checksum, and cleanup checks.`)}catch(z){let U=z instanceof Error?z.message:String(z);throw A.store.recordDestinationTest(N.id,{ok:!1,error:U}),Hi(A,"destination_tested",{destinationId:N.id,ok:!1}),z}}))),x.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)=>Ii(async()=>Ai(m.env,async(A)=>{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 N=Wd(A,m.destination),z=A.dataServices.list(A.controlPlane.project.id,A.environmentRecord.id),U=m.dataService?A.dataServices.get(m.dataService)??z.find((ve)=>ve.name===m.dataService):void 0,X=A.controlPlane.store.listResources(A.controlPlane.project.id,A.environmentRecord.id),te=m.resource?X.find((ve)=>ve.id===m.resource||ve.slug===m.resource):void 0,re=m.include?Array.isArray(m.include)?m.include:[m.include]:[],oe=m.exclude?Array.isArray(m.exclude)?m.exclude:[m.exclude]:[];if(["managed_database","logical_database"].includes(m.kind??"")&&!U)throw Error("--data-service must identify a service in this environment.");if(m.kind==="volume"&&(!te||!m.volume))throw Error("Volume policies require --resource and --volume.");if(m.kind==="files"&&(!te||!re.length))throw Error("File policies require --resource and at least one --include path.");if(m.kind==="infrastructure"&&(!re.some((ve)=>ve.startsWith("resource:arn:"))||!re.some((ve)=>ve.startsWith("role:arn:"))))throw Error("Infrastructure policies require --include resource:<arn> and --include role:<arn>.");let ae=A.store.createPolicy({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,environmentId:A.environmentRecord.id,resourceId:te?.id,dataServiceId:U?.id,destinationId:N.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,...re]:re,excludePatterns:oe,expectedRpoMinutes:Number(m.rpo)||1440,expectedRtoMinutes:Number(m.rto)||120,enabled:!m.disabled});Hi(A,"policy_created",{policyId:ae.id,resourceKind:ae.resourceKind},ae.resourceId),n(`Created policy ${ae.name} (${ae.id}); next run ${ae.nextRunAt}`)}))),x.command("recovery:run <policy>","Queue an on-demand run of a recovery policy").option("--env <environment>","Target environment").action(async(t,m)=>Ii(async()=>Ai(m.env,async(A)=>{let N=rg(A,t),z=A.coordinator.enqueueBackup(N,new Date().toISOString(),A.actor.id);Hi(A,"run_queued",{policyId:N.id,backupJobId:z.id},N.resourceId),n(`Queued backup job ${z.id}; inspect it with cloud recovery:jobs`)}))),x.command("recovery:verify <point>","Queue independent verification of a recovery point").option("--env <environment>","Target environment").action(async(t,m)=>Ii(async()=>Ai(m.env,async(A)=>{let N=ta(A,t),z=A.coordinator.enqueueVerification(N);Hi(A,"verification_queued",{recoveryPointId:N.id,backupJobId:z.id},N.resourceId),n(`Queued verification job ${z.id}`)}))),x.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,A)=>Ii(async()=>Ai(A.env,async(N)=>{if(!["isolated","in_place"].includes(A.mode??""))throw Error("--mode must be isolated or in_place.");if(A.drill&&A.mode==="in_place")throw Error("Recovery drills require --mode isolated.");let z=ta(N,t),U={mode:A.mode,target:{...ig(z,m),...A.metadata?{metadata:JSON.parse(A.metadata)}:{},inPlace:A.mode==="in_place"},targetName:m,confirm:A.confirm,recentAuth:!0,downtimeAcknowledged:!!A.ackDowntime,safetyBackupId:A.safety?ta(N,A.safety).id:void 0},X=N.coordinator.planRestore(z,U);if(!A.execute){e(JSON.stringify({...X,productionExecutionCreated:!1},null,2));return}let te=N.coordinator.enqueueRestore(z,{...U,drill:!!A.drill,actorId:N.actor.id});Hi(N,A.drill?"drill_queued":"restore_queued",{recoveryPointId:z.id,backupJobId:te.id,mode:U.mode,target:m},z.resourceId),n(`Queued ${A.drill?"drill":"restore"} job ${te.id}`)}))),x.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)=>Ii(async()=>Ai(m.env,async(A)=>{let N=ta(A,t);if(![m.pin,m.unpin,m.hold,m.releaseHold].filter(Boolean).length)throw Error("Choose --pin, --unpin, --hold, or --release-hold.");let z=A.store.updateRecoveryPoint(N.id,{pinned:m.pin?!0:m.unpin?!1:N.pinned,held:m.hold?!0:m.releaseHold?!1:N.held});Hi(A,"protection_updated",{recoveryPointId:N.id,pinned:z.pinned,held:z.held},N.resourceId),n(`${z.id}: pinned=${z.pinned} held=${z.held}`)}))),x.command("recovery:retention","Queue deletion of expired, unlocked, unheld recovery points").option("--env <environment>","Target environment").action(async(t)=>Ii(async()=>Ai(t.env,async(m)=>{let A=m.coordinator.enqueueRetention();Hi(m,"retention_queued",{jobs:A.length}),n(`Queued ${A.length} retention cleanup job${A.length===1?"":"s"}.`)})))}function sl(x){x.command("api:list","List all API Gateway APIs").option("--region <region>","AWS region").action(async(t)=>{i("API Gateway APIs");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1",{LambdaClient:N}=await import("./chunk-9zhp2z0s.js"),z=new N(A),U=new o("Fetching APIs...");U.start();let{CloudFormationClient:X}=await import("./chunk-smj97rs5.js"),oe=(await new X(A).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE"])).StackSummaries.filter((ae)=>ae.StackName?.includes("api")||ae.StackName?.includes("Api")||ae.StackName?.includes("API"));if(U.succeed("APIs listed"),e(`
648
648
  API-related CloudFormation stacks:`),oe.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"],oe.map((ae)=>[ae.StackName||"N/A",ae.StackStatus||"N/A",ae.CreationTime?new Date(ae.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)}}),x.command("api:describe <apiId>","Show API Gateway API details").option("--region <region>","AWS region").action(async(t,m)=>{i(`API: ${t}`);try{let A=await Oe(),N=m.region||A.project.region||"us-east-1";e(`API ID: ${t}`),e(`Region: ${N}`),e(""),e("For detailed API information, use AWS CLI:"),e(` aws apigateway get-rest-api --rest-api-id ${t} --region ${N}`),e(` aws apigateway get-resources --rest-api-id ${t} --region ${N}`),e(` aws apigateway get-stages --rest-api-id ${t} --region ${N}`)}catch(A){r(`Failed to describe API: ${A.message}`),process.exit(1)}}),x.command("api:stages <apiId>","List API stages").option("--region <region>","AWS region").action(async(t,m)=>{i(`API Stages: ${t}`);try{let A=await Oe(),N=m.region||A.project.region||"us-east-1";e(`API ID: ${t}`),e(`Region: ${N}`),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 ${N}`)}catch(A){r(`Failed to list stages: ${A.message}`),process.exit(1)}}),x.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(A){r(`Failed to deploy API: ${A.message}`),process.exit(1)}}),x.command("api:domains","List custom domain names").option("--region <region>","AWS region").action(async(t)=>{i("API Gateway Custom Domains");try{let m=await Oe(),A=t.region||m.project.region||"us-east-1";e(`Region: ${A}`),e(""),e("To list custom domains, use AWS CLI:"),e(` aws apigateway get-domain-names --region ${A}`),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)}}),x.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 A=await Oe(),N=m.region||A.project.region||"us-east-1";e(`API ID: ${t}`),e(`Stage: ${m.stage}`),e(`Period: Last ${m.days} days`),e("");let z=new Date,U=new Date;U.setDate(U.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 ${U.toISOString()} \\`),e(` --end-time ${z.toISOString()} \\`),e(" --period 86400 \\"),e(" --statistics Sum \\"),e(` --region ${N}`),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(A){r(`Failed to get usage: ${A.message}`),process.exit(1)}}),x.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 A=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 ${A} \\`),e(" --accepts application/json \\"),e(` --region ${m.region} \\`),e(` ${m.output||"api-spec.json"}`)}catch(A){r(`Failed to export API: ${A.message}`),process.exit(1)}}),x.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 A=await Oe(),N=m.region||A.project.region||"us-east-1",z=`API-Gateway-Execution-Logs_${t}/${m.stage}`;if(e(`Log Group: ${z}`),e(""),e("To view logs:"),e(" aws logs filter-log-events \\"),e(` --log-group-name "${z}" \\`),e(` --region ${N}`),m.tail)e(""),e("For real-time log tailing, use:"),e(` aws logs tail "${z}" --follow --region ${N}`);e(""),e("Note: Ensure logging is enabled for the API stage."),e("You can enable it in the stage settings.")}catch(A){r(`Failed to get logs: ${A.message}`),process.exit(1)}}),x.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,A)=>{i("Test API Endpoint");try{let N=await Oe(),z=A.region||N.project.region||"us-east-1",U=`https://${t}.execute-api.${z}.amazonaws.com/${A.stage}${m.startsWith("/")?m:`/${m}`}`;e(`URL: ${U}`),e(`Method: ${A.method}`);let X={"Content-Type":"application/json"};if(A.header){let Re=Array.isArray(A.header)?A.header:[A.header];for(let we of Re){let[ke,...Be]=we.split(":");X[ke.trim()]=Be.join(":").trim()}}e("Headers:");for(let[Re,we]of Object.entries(X))e(` ${Re}: ${we}`);if(A.body)e(`Body: ${A.body}`);if(!await d(`
@@ -652,7 +652,7 @@ Send request?`,!0)){e("Operation cancelled");return}let re=new o("Sending reques
652
652
  Status: ${ae.status} ${ae.statusText}`),e(`
653
653
  Response Headers:`),ae.headers.forEach((Re,we)=>{e(` ${we}: ${Re}`)}),e(`
654
654
  Response Body:`);try{let Re=JSON.parse(Ce);console.log(JSON.stringify(Re,null,2))}catch{console.log(Ce)}}catch(N){r(`Failed to test API: ${N.message}`),process.exit(1)}})}async function vo(x){let t=await Oe(),m=y(process.cwd(),t),A=new b(m.store,{workerId:`cli:${process.pid}`});try{return await x(A,m.project.id,m.organization.id)}finally{m.store.close()}}function sg(x){if(!x)return;if(!["queued","running","succeeded","failed","cancelled","timed_out"].includes(x))throw Error("State must be queued, running, succeeded, failed, cancelled, or timed_out");return x}function ag(x,t){if(!x)return"—";let m=Math.max(0,new Date(t??Date.now()).getTime()-new Date(x).getTime());return m<1000?`${m}ms`:m<60000?`${Math.round(m/1000)}s`:`${Math.round(m/60000)}m`}function al(x){x.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 vo(async(m,A)=>{let N=m.list({projectId:t.allProjects?void 0:A,state:sg(t.state),limit:500});c(["ID","State","Operation","Target","Attempt","Step / blocked","Queued","Duration"],N.map(({operation:z,job:U,approximatePosition:X})=>[z.id,z.state,z.kind,z.resourceId??z.environmentId??z.projectId??"global",`${z.attempt}/${U.maxAttempts}`,U.blockedReason??U.currentStep??(X?`${X.ahead} ahead`:"—"),z.createdAt,ag(z.startedAt,z.finishedAt)]))})}catch(m){r(m instanceof Error?m.message:String(m))}}),x.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 vo(async(A)=>{let N=A.view(t);if(!N)throw Error(`Operation ${t} was not found`);e(JSON.stringify(N,null,2));for(let z of A.logs(t,{after:Number(m.after)||0,limit:500}))e(`${z.sequence} ${z.stream.toUpperCase()}${z.step?` [${z.step}]`:""} ${z.message}`)})}catch(A){r(A instanceof Error?A.message:String(A))}}),x.command("ops:cancel <id>","Cancel queued work or request running cancellation").option("--yes","Skip confirmation").action(async(t,m)=>{try{await vo(async(A)=>{let N=A.view(t);if(!N)throw Error(`Operation ${t} was not found`);if(!m.yes&&!await d(`Cancel ${N.operation.kind} (${t})?`,!1))return;let z=A.requestCancellation(t);n(`${z.state==="cancelled"?"Cancelled":"Cancellation requested for"} ${t}`)})}catch(A){r(A instanceof Error?A.message:String(A))}}),x.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 vo(async(A)=>{let N=A.retry(t,m.class,{delayMs:Number(m.delay)||0});n(`Retry queued: ${N.id} · attempt ${N.attempt+1}`)})}catch(A){r(A instanceof Error?A.message:String(A))}}),x.command("ops:concurrency","Show effective durable queue concurrency limits").action(async()=>{try{await vo(async(t)=>e(JSON.stringify(t.limits,null,2)))}catch(t){r(t instanceof Error?t.message:String(t))}}),x.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 A of["project","environment","provider","builds"])if(t[A]!==void 0)m[A]=Number(t[A]);await vo(async(A,N,z)=>n(`Queue concurrency updated: ${JSON.stringify(A.configureConcurrency(m,{organizationId:z}))}`))}catch(m){r(m instanceof Error?m.message:String(m))}}),x.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 vo(async(m,A)=>{if(!t.yes&&!await d("Clear completed queue history whose retention has elapsed?",!1))return;let N=m.clearCompleted({projectId:A,before:t.before});n(`Cleared ${N} retained operation${N===1?"":"s"}.`)})}catch(m){r(m instanceof Error?m.message:String(m))}})}import{readFile as cg,writeFile as lg}from"node:fs/promises";function dg(x){if(x==null)return"—";if(x<60)return`${x}s`;if(x<3600)return`${Math.floor(x/60)}m`;if(x<86400)return`${Math.floor(x/3600)}h`;return`${Math.floor(x/86400)}d`}function ug(x){return x==null?"—":en(x)}function pg(x){return x.map((t)=>[t.id,t.name,t.provider,t.kind,t.status,t.desiredReplicas==null?"—":`${t.runningReplicas??0}/${t.desiredReplicas}`,t.image??t.runtime??"—",ug(t.resources?.memoryBytes??t.resources?.memoryLimitBytes),dg(t.ageSeconds)])}function mg(x,t){let m=t.search?.toLowerCase();return{...x,workloads:x.workloads.filter((A)=>(!t.provider||A.provider===t.provider)&&(!t.status||A.status===t.status)&&(!m||`${A.name} ${A.id} ${A.links.service??""} ${Object.values(A.tags).join(" ")}`.toLowerCase().includes(m)))}}async function bo(x){let t=await Oe(),m=x??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let A=new he(t,m);return{config:t,env:m,service:A}}async function na(x,t){if(t!==void 0)return t;return I(`Type ${x.name} to confirm`,"")}async function fg(x,t,m,A,N,z){return ra(x,`runtime.${t}`,{workloadId:x.id,provider:x.provider,action:t,replicas:m??null},N,()=>z.run({workloadId:x.id,action:t,replicas:m,confirm:A,recentAuth:!0}))}async function ra(x,t,m,A,N){let z=await Oe(),U=y(process.cwd(),z),X=new b(U.store,{workerId:`cli:${process.pid}`});try{let te=U.environments.get(A),re=U.store.listResources(U.project.id,te?.id).find((ve)=>ve.slug===x.links.service),oe=U.store.getActorByExternalId("system","cli")??U.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI",metadata:{source:"cli"}}),ae=X.enqueue({projectId:U.project.id,environmentId:te?.id,resourceId:re?.id,actorId:oe.id,kind:t,input:h(m),lockKey:re?`resource:${re.id}`:`runtime:${x.id}`,providerKey:x.provider,maxAttempts:1,timeoutSeconds:3600,resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:90}).operation;if(!X.claim(ae.id))throw Error(`Workload is locked by another operation. Inspect ${ae.id} with cloud ops:show.`);try{let ve=await N();if(ve.stdout)X.appendLog(ae.id,ve.stdout,{stream:"stdout"});if(ve.stderr)X.appendLog(ae.id,ve.stderr,{stream:"stderr"});if(ve.ok)X.complete(ae.id,{ok:!0,command:ve.command??null});else X.fail(ae.id,ve.error??ve.stderr??"Runtime operation failed",{ok:!1});return{result:ve,operationId:ae.id}}catch(ve){throw X.fail(ae.id,ve instanceof Error?ve.message:String(ve),{ok:!1,threw:!0}),ve}}finally{U.store.close()}}function cl(x){x.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 bo(t.env),A=mg(await m.inventory(),t);if(t.json)e(JSON.stringify(A,null,2));else{c(["ID","Name","Provider","Kind","Status","Replicas","Image / runtime","Memory","Age"],pg(A.workloads));for(let N of A.sources.filter((z)=>z.status!=="fresh"))a(`${N.id}: ${N.status}${N.message?` — ${N.message}`:""}`)}}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}}),x.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:A}=await bo(m.env),N=await A.workload(t);if(!N)throw Error("Workload was not found in this project and environment");e(JSON.stringify(N,null,2))}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.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:A}=await bo(m.env),N=await A.logs(t,{limit:Number(m.lines)||200});if(m.json)e(JSON.stringify(N,null,2));else for(let z of N.lines)console.log(`${z.timestamp??"—"} ${z.message}`)}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.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,A)=>{try{let N=m;if(!["start","stop","restart","redeploy","scale"].includes(N))throw Error("Action must be start, stop, restart, redeploy, or scale");let z=A.replicas==null?void 0:Number(A.replicas);if(N==="scale"&&(!Number.isInteger(z)||z<0))throw Error("Scale requires a non-negative integer --replicas value");let{env:U,service:X}=await bo(A.env),te=await X.inventory(),re=te.workloads.find((ve)=>ve.id===t);if(!re)throw Error("Workload was not found in this project and environment");let oe=new he(await Oe(),U,{inventory:async()=>te}),ae=await fg(re,N,z,await na(re,A.confirm),U,oe);if(!ae.result.ok)throw Error(ae.result.error??ae.result.stderr??"Runtime operation failed");if(n(`${N} completed for ${re.name} · operation ${ae.operationId}`),ae.result.stdout)e(ae.result.stdout)}catch(N){r(N instanceof Error?N.message:String(N)),process.exitCode=1}}),x.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:A,service:N}=await bo(m.env),U=(await N.inventory()).workloads.find((oe)=>oe.id===t);if(!U)throw Error("Workload was not found in this project and environment");let X=["process","sockets","filesystem"].includes(String(m.preset))?m.preset:void 0;if(m.preset&&!X)throw Error("Preset must be process, sockets, or filesystem");if(!X&&!m.command)throw Error("Pass --preset or --command");let te=await na(U,m.confirm),re=await ra(U,"runtime.exec",{workloadId:t,provider:U.provider,preset:X??null,freeForm:!!m.command},A,()=>N.exec({workloadId:t,preset:X,command:m.command,confirm:te,recentAuth:!0}));if(!re.result.ok)throw Error(re.result.error??re.result.stderr??"Exec failed");if(re.result.stdout)e(re.result.stdout);if(re.result.stderr)a(re.result.stderr);n(`Exec completed for ${U.name} · operation ${re.operationId}`)}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),x.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,A,N)=>{try{let{env:z,service:U}=await bo(N.env),te=(await U.inventory()).workloads.find((ae)=>ae.id===t);if(!te)throw Error("Workload was not found in this project and environment");let re=await na(te,N.confirm),oe=await ra(te,"runtime.file.read",{workloadId:t,provider:te.provider,path:m},z,()=>U.readFile({workloadId:t,path:m,confirm:re,recentAuth:!0}));if(!oe.result.ok||!oe.result.contentBase64)throw Error(oe.result.error??"File download failed");await lg(A,Buffer.from(oe.result.contentBase64,"base64")),n(`Downloaded ${oe.result.size??0} bytes to ${A} · operation ${oe.operationId}${oe.result.truncated?" (truncated)":""}`)}catch(z){r(z instanceof Error?z.message:String(z)),process.exitCode=1}}),x.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,A,N)=>{try{let{env:z,service:U}=await bo(N.env),te=(await U.inventory()).workloads.find((ve)=>ve.id===t);if(!te)throw Error("Workload was not found in this project and environment");let re=await na(te,N.confirm),oe=(await cg(m)).toString("base64"),ae=await ra(te,"runtime.file.write",{workloadId:t,provider:te.provider,path:A,bytes:Buffer.from(oe,"base64").byteLength},z,()=>U.writeFile({workloadId:t,path:A,contentBase64:oe,confirm:re,recentAuth:!0}));if(!ae.result.ok)throw Error(ae.result.error??"File upload failed");n(`Uploaded ${ae.result.size??0} bytes to ${A} · operation ${ae.operationId}`)}catch(z){r(z instanceof Error?z.message:String(z)),process.exitCode=1}})}function gg(x,t=new Date){let m=x.to?new Date(x.to):t;if(!Number.isFinite(m.getTime()))throw Error("--to must be a valid ISO-8601 instant");if(x.from){let z=new Date(x.from);if(!Number.isFinite(z.getTime()))throw Error("--from must be a valid ISO-8601 instant");return{from:z.toISOString(),to:m.toISOString()}}let A=String(x.range??"1h").match(/^(\d+)(m|h|d)$/);if(!A)throw Error("--range must use minutes, hours, or days (for example 30m, 6h, or 7d)");let N=A[2]==="m"?60000:A[2]==="h"?3600000:86400000;return{from:new Date(m.getTime()-Number(A[1])*N).toISOString(),to:m.toISOString()}}async function ys(x){let t=await Oe(),m=x??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let A=y(process.cwd(),t),N=A.environments.get(m),z=new Je(A.store);return{config:t,env:m,controlPlane:A,environmentRecord:N,telemetry:z}}function qd(x,t,m){let A=(z)=>z?.split(",").map((U)=>U.trim()).filter(Boolean)||void 0,N=A(x.kind)?.filter((z)=>["metric","log","trace","request","event"].includes(z));return{projectId:t,environmentId:m,...gg(x),kinds:N,names:A(x.name),sources:A(x.source),text:x.text,limit:Math.min(5000,Math.max(1,Number(x.limit)||200))}}function hg(x){let t=["timestamp","kind","source","name","level","value","unit","message","traceId","requestId","deploymentId","releaseId","workloadId"];return[t.join(","),...x.map((m)=>t.map((A)=>`"${String(m[A]??"").replaceAll('"','""')}"`).join(","))].join(`
655
- `)}function ll(x){x.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 ys(t.env);try{let A=await qn({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(A,null,2));else n(`Collected ${A.collected} normalized records${A.cached?" (cached provider result)":""}.`),c(["Source","Freshness","Lag","Sampling","Retention"],A.statuses.map((N)=>[N.source,N.freshness,N.lagSeconds==null?"—":`${N.lagSeconds}s`,`${Math.round(N.samplingRate*100)}%`,`${N.retentionDays}d`]))}finally{m.controlPlane.store.close()}}),x.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 ys(t.env);try{let A=m.telemetry.query(qd(t,m.controlPlane.project.id,m.environmentRecord?.id));if(t.json)e(JSON.stringify(A,null,2));else c(["Time","Kind","Source","Signal","Value / message","Correlation"],A.records.map((N)=>[N.timestamp,N.kind,N.source,N.name,N.message??(N.value==null?"—":`${N.value} ${N.unit??""}`),N.requestId??N.traceId??N.releaseId??N.deploymentId??"—"]));if(A.truncated)a("More records are available; narrow the window or use the dashboard cursor explorer.")}finally{m.controlPlane.store.close()}}),x.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 A=await ys(m.env);try{let N=qd(m,A.controlPlane.project.id,A.environmentRecord?.id),z=A.telemetry.query(N);await Bun.write(t,t.toLowerCase().endsWith(".csv")?hg(z.records):JSON.stringify({exportedAt:new Date().toISOString(),query:N,...z},null,2)),n(`Exported ${z.records.length} records to ${t}${z.truncated?" (bounded result)":""}.`)}finally{A.controlPlane.store.close()}}),x.command("telemetry:compact","Downsample old metrics and enforce configured retention").option("--env <environment>","Target environment").action(async(t)=>{let m=await ys(t.env);try{let A=Ke(m.controlPlane.store,m.controlPlane.project.id);e(JSON.stringify(m.telemetry.enforceRetention(A,m.controlPlane.project.id),null,2))}finally{m.controlPlane.store.close()}}),x.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 ys(t.env);try{let A={};if(t.rawDays!=null)A.rawDays=Number(t.rawDays);if(t.downsampleDays!=null)A.downsampleAfterDays=Number(t.downsampleDays);if(t.sampling!=null)A.samplingRate=Number(t.sampling);if(t.storageUsdGbMonth!=null)A.estimatedStorageUsdPerGbMonth=Number(t.storageUsdGbMonth);let N=Object.keys(A).length?ei(m.controlPlane.store,m.controlPlane.project.id,A):Ke(m.controlPlane.store,m.controlPlane.project.id),U=m.telemetry.status(m.controlPlane.project.id,m.environmentRecord?.id,N.rawDays,N.samplingRate).reduce((te,re)=>te+re.estimatedDailyBytes*30,0),X={policy:N,estimatedMonthlyBytes:U,estimatedMonthlyCostUsd:ti(U,N),costEstimateConfigured:N.estimatedStorageUsdPerGbMonth>0};if(t.json)e(JSON.stringify(X,null,2));else e(JSON.stringify(N,null,2)),e(X.costEstimateConfigured?`Estimated local storage: $${X.estimatedMonthlyCostUsd}/month`:"Storage cost remains unavailable until --storage-usd-gb-month is set.")}finally{m.controlPlane.store.close()}})}async function co(x){let t=await Oe(),m=x??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let A=y(process.cwd(),t),N=A.environments.get(m),z=new gt(A.store,{encryptionKey:_(process.cwd())});return{config:t,env:m,controlPlane:A,environmentRecord:N,alerts:z}}function Gd(x){return new mr(x,{emailImpl:async(t)=>{let{email:m}=await import("./chunk-223yq2z0.js");return m.send(t)}})}function dl(x){x.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 co(t.env);try{let A=m.alerts.listHealthChecks(m.controlPlane.project.id,m.environmentRecord?.id).map((N)=>({...N,latest:m.alerts.listHealthResults(N.id,1)[0]}));if(t.json)e(JSON.stringify(A,null,2));else c(["ID","Name","Kind","Target","Status","Interval","Enabled"],A.map((N)=>[N.id,N.name,N.kind,N.target,N.latest?.status??"no data",`${N.intervalSeconds}s`,N.enabled?"yes":"no"]))}finally{m.controlPlane.store.close()}}),x.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 A=await co(m.env);try{let N=A.alerts.getHealthCheck(t)??A.alerts.listHealthChecks(A.controlPlane.project.id,A.environmentRecord?.id).find((X)=>X.name===t);if(!N)throw Error("Health check was not found");let z=new Me(A.alerts);if(m.test){let X=await z.probe(N);e(JSON.stringify({...X,productionIncidentCreated:!1},null,2));return}let U=await z.runAndEvaluate(N);if(e(JSON.stringify(U,null,2)),U.result.status==="healthy")n(`${N.name} is healthy.`);else a(`${N.name} is ${U.result.status}.`)}finally{A.controlPlane.store.close()}}),x.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 co(t.env);try{let A=m.alerts.listAlerts(m.controlPlane.project.id,{environmentId:m.environmentRecord?.id,states:t.state?.split(",").filter(Boolean)});if(t.json)e(JSON.stringify(A,null,2));else c(["ID","State","Severity","Title","Owner","First seen","Last seen"],A.map((N)=>[N.id,N.state,N.severity,N.title,N.ownerActorId??"unassigned",N.firstSeenAt,N.lastSeenAt]))}finally{m.controlPlane.store.close()}}),x.command("alerts:evaluate","Evaluate persisted telemetry rules now").option("--env <environment>","Target environment").action(async(t)=>{let m=await co(t.env);try{let A=Xr(m.alerts,m.controlPlane.project.id,m.environmentRecord?.id);e(JSON.stringify(A,null,2))}finally{m.controlPlane.store.close()}}),x.command("alerts:ack <alert>","Acknowledge an alert as the CLI actor").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await co(m.env);try{let N=A.controlPlane.store.getActorByExternalId("system","cli")??A.controlPlane.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});n(`Acknowledged ${A.alerts.acknowledge(t,N.id).title}.`)}finally{A.controlPlane.store.close()}}),x.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 co(t.env);try{let A={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(A,null,2));else c(["Channel","Type","Status","Tested","Error"],A.channels.map((N)=>[N.name,N.kind,N.status,N.lastTestedAt??"never",N.lastError??"—"])),c(["Event","State","Attempts","Status","Error"],A.deliveries.map((N)=>[N.eventType,N.state,`${N.attempt}/${N.maxAttempts}`,String(N.responseStatus??"—"),N.error??"—"]))}finally{m.controlPlane.store.close()}}),x.command("notifications:test <channel>","Send a channel test without creating a production incident").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await co(m.env);try{let N=A.alerts.getChannel(t)??A.alerts.listChannels(A.controlPlane.organization.id).find((U)=>U.name===t);if(!N)throw Error("Notification channel was not found");let z=await Gd(A.alerts).testChannel(N.id);if(z.ok)n("Test delivered; no production incident was created.");else throw Error(z.error)}finally{A.controlPlane.store.close()}}),x.command("notifications:retry <delivery>","Retry one failed notification delivery").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await co(m.env);try{let N=A.alerts.getDelivery(t);if(!N)throw Error("Notification delivery was not found");A.alerts.updateDelivery(t,{state:"retrying",attempt:Math.max(0,N.attempt-1),nextAttemptAt:new Date().toISOString()}),e(JSON.stringify(await Gd(A.alerts).deliver(t),null,2))}finally{A.controlPlane.store.close()}})}async function yg(x,t){let m=await Oe(),A=y(process.cwd(),m),N=!x&&t?new Ur(A.store).get(t):void 0,z=N?.environmentId?A.store.listEnvironments(A.project.id).find((Ce)=>Ce.id===N.environmentId)?.slug:void 0,U=x??z??Object.keys(m.environments??{})[0]??"production";if(!Object.hasOwn(m.environments??{},U))throw A.store.close(),Error(`Environment ${U} was not found`);let X=A.environments.get(U);if(!X)throw A.store.close(),Error(`Environment ${U} was not found in the control plane`);let te=new Ur(A.store),re=new b(A.store,{workerId:`cli:${process.pid}`}),oe=new Vn(te,{queue:re}),ae=A.store.listResources(A.project.id,X.id);Br(te,m,{organization:A.organization,project:A.project,environment:X,resources:ae});let ve=A.store.getActorByExternalId("system","cli")??A.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:m,env:U,controlPlane:A,environmentRecord:X,resources:ae,store:te,queue:re,service:oe,actor:ve}}async function fi(x,t,m){let A=await yg(x,m);try{return await t(A)}finally{A.controlPlane.store.close()}}async function Gr(x){try{await x()}catch(t){r(t instanceof Error?t.message:String(t))}}function Lo(x,t){let m=x.store.list(x.controlPlane.project.id,{environmentId:x.environmentRecord.id}),A=x.store.get(t)??m.find((N)=>N.name===t);if(!A||A.environmentId!==x.environmentRecord.id)throw Error(`Scheduled job ${t} was not found in ${x.env}`);return A}function wo(x,t,m,A){x.controlPlane.store.appendEvent({organizationId:x.controlPlane.organization.id,projectId:x.controlPlane.project.id,resourceId:A,actorId:x.actor.id,type:`job.${t}`,payload:m})}function Hd(x,t,m){if(x.environmentRecord.kind==="production"&&m!==t)throw Error(`Production ${t} requires --confirm ${t}`)}function vg(x){if(!x)return{};if(!/^(?:secret|ssm|arn|env):\/\//.test(x))throw Error("Payloads must use a secret://, ssm://, arn://, or env:// reference");return{input:x}}function bg(x){return x.map((t)=>[t.id,t.enabled?"enabled":"disabled",t.provider,t.name,t.expression,t.timezone,t.nextRunAt??"—",t.reconciliationStatus])}function ul(x){x.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)=>Gr(async()=>fi(t.env,async(m)=>{let A=m.store.list(m.controlPlane.project.id,{environmentId:m.environmentRecord.id});if(t.json)e(JSON.stringify(A,null,2));else c(["ID","State","Provider","Name","Expression","Timezone","Next run","Reconciliation"],bg(A))}))),x.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)=>Gr(async()=>{let A=at(t,m.timezone??"UTC",m.from?new Date(m.from):new Date,Number(m.count)||5);e(JSON.stringify({...A,productionExecutionCreated:!1},null,2))})),x.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,A)=>Gr(async()=>fi(A.env,async(N)=>{if(!["skip","catch_up"].includes(A.missed??"skip"))throw Error("--missed must be skip or catch_up");if(!["allow","forbid","replace"].includes(A.overlap??"forbid"))throw Error("--overlap must be allow, forbid, or replace");let z=H(N.config),X={workers:N.store.listWorkers(N.controlPlane.project.id,N.environmentRecord.id).map((Ce)=>({name:Ce.name}))},te;if(z==="server"){let Ce=Or(N.config,X).filter((we)=>!we.danger&&["scheduler","backup","worker"].includes(we.group)),Re=A.operation?Ce.find((we)=>we.id===A.operation):Ce.length===1?Ce[0]:void 0;if(!Re)throw Error(`Choose a safe target with --operation. Available: ${Ce.map((we)=>we.id).join(", ")||"none"}`);te={kind:"dashboard_operation",operationId:Re.id}}else te={kind:"serverless_scheduler",action:"run"};let re=z==="server"?"server":"eventbridge",oe=jn({provider:re,expression:m,flexibleMinutes:Number(A.flexibleMinutes)||0,missedRunPolicy:A.missed??"skip",overlapPolicy:A.overlap??"forbid"});if(!oe.supported)throw Error(oe.notes.join(" "));let ae=A.resource?N.resources.find((Ce)=>Ce.id===A.resource||Ce.slug===A.resource):void 0;if(A.resource&&!ae)throw Error(`Resource ${A.resource} was not found in ${N.env}`);let ve=N.store.create({organizationId:N.controlPlane.organization.id,projectId:N.controlPlane.project.id,environmentId:N.environmentRecord.id,resourceId:ae?.id,name:t,provider:re,expression:m,timezone:A.timezone??"UTC",startsAt:A.startsAt,endsAt:A.endsAt,flexibleMinutes:Number(A.flexibleMinutes)||0,target:te,payloadRefs:vg(A.payloadRef),missedRunPolicy:A.missed??"skip",overlapPolicy:A.overlap??"forbid",retryPolicy:{maxAttempts:Number(A.attempts)||3,backoffSeconds:Number(A.backoff)||30,deadLetterRef:A.deadLetter},timeoutSeconds:Number(A.timeout)||900,enabled:!A.disabled,origin:"managed",ownerActorId:N.actor.id,observedState:{},reconciliationStatus:"pending"});wo(N,"created",{jobId:ve.id,provider:ve.provider,expression:ve.normalizedExpression},ve.resourceId),n(`Created ${ve.name} (${ve.id}); next run ${ve.nextRunAt??"outside the active window"}`)}))),x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t);if(N.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 z=A.store.updateSchedule(N.id,{expression:m.expression,timezone:m.timezone,missedRunPolicy:m.missed,overlapPolicy:m.overlap,timeoutSeconds:m.timeout==null?void 0:Number(m.timeout)});wo(A,"updated",{jobId:z.id,expression:z.normalizedExpression,version:z.version},z.resourceId),n(`Updated ${z.name}; next run ${z.nextRunAt??"outside the active window"}`)}))),x.command("jobs:run <job>","Queue a manual run through the durable operation worker").option("--env <environment>","Target environment").action(async(t,m)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t),z=A.service.enqueue(N,{trigger:"manual",actorId:A.actor.id});wo(A,"run_queued",{jobId:N.id,executionId:z.id},N.resourceId),n(`Queued execution ${z.id}; inspect it with cloud jobs:history "${N.name}"`)}))),x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t);if(!N.enabled)throw Error(`${N.name} is disabled`);let z=m.scheduledFor?new Date(m.scheduledFor):new Date;if(!m.scheduledFor)z.setUTCSeconds(0,0);let U=z.toISOString(),X=A.service.enqueue(N,{trigger:m.scheduled?"scheduled":"external",scheduledFor:U,actorId:A.actor.id}),te=st(N.normalizedExpression,N.timezone,z,1)[0];A.store.markScheduled(N.id,U,te),n(`Accepted ${N.name} for ${U} as ${X.id}`)},t)));for(let t of["enable","disable"])x.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,A)=>Gr(async()=>fi(A.env,async(N)=>{let z=Lo(N,m);if(t==="disable")Hd(N,t,A.confirm);let U=N.store.setEnabled(z.id,t==="enable");wo(N,t==="enable"?"enabled":"disabled",{jobId:U.id},U.resourceId),n(`${U.name} is ${U.enabled?"enabled":"disabled"}`)})));x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t);Hd(A,"delete",m.confirm),A.store.remove(N.id),wo(A,"deleted",{jobId:N.id,name:N.name},N.resourceId),n(`Deleted ${N.name}`)}))),x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t),z=A.store.listExecutions(N.id,Number(m.limit)||100).map((U)=>({...U,logs:m.logs&&U.operationId?A.queue.logs(U.operationId,{limit:500}):[]}));if(m.json)e(JSON.stringify(z,null,2));else if(c(["ID","Status","Trigger","Scheduled for","Attempt","Started","Finished","Error"],z.map((U)=>[U.id,U.status,U.trigger,U.scheduledFor,String(U.attempt),U.startedAt??"—",U.finishedAt??"—",U.error??"—"])),m.logs)for(let U of z)for(let X of U.logs)e(`${U.id} ${X.sequence} ${X.stream.toUpperCase()} ${X.message}`)}))),x.command("jobs:reconcile","Import declarative schedules and workers without destructive deletion").option("--env <environment>","Target environment").action(async(t)=>Gr(async()=>fi(t.env,async(m)=>{let A=Br(m.store,m.config,{organization:m.controlPlane.organization,project:m.controlPlane.project,environment:m.environmentRecord,resources:m.resources});wo(m,"reconciled",A),n(`Reconciled ${A.jobs} schedules and ${A.workers} workers; retained ${A.drifted} missing definitions as drifted`)}))),x.command("workers:list","List configured systemd, ECS, and Lambda workers").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>Gr(async()=>fi(t.env,async(m)=>{let A=m.store.listWorkers(m.controlPlane.project.id,m.environmentRecord.id);if(t.json)e(JSON.stringify(A,null,2));else c(["ID","Provider","Name","Queue","Processes","Timeout","Restart","Reconciliation"],A.map((N)=>[N.id,N.provider,N.name,N.queue,String(N.processes),`${N.timeoutSeconds}s`,N.restartPolicy,N.reconciliationStatus]))}))),x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=A.store.listWorkers(A.controlPlane.project.id,A.environmentRecord.id),z=A.store.getWorker(t)??N.find((te)=>te.name===t);if(!z||z.environmentId!==A.environmentRecord.id)throw Error(`Worker ${t} was not found in ${A.env}`);if(m.confirm!==z.name)throw Error(`Restart requires --confirm "${z.name}"`);if(z.provider!=="systemd")throw Error(`${z.provider} workers are provider/config managed and do not expose a safe restart adapter`);let U=Un(String(z.target.operationId??""),A.config,{workers:N.map((te)=>({name:te.name}))});if(!U||U.danger||U.group!=="worker")throw Error("The allowlisted worker restart operation is unavailable");let X=await Lr(A.config,A.env,U);if(A.store.reconcileWorker(z.id,X.ok?"in_sync":"unavailable",{...z.observedState,lastRestartAt:new Date().toISOString(),command:X.command??null,error:X.error??X.stderr??null}),wo(A,"worker_restarted",{workerId:z.id,ok:X.ok},z.resourceId),!X.ok)throw Error(X.error??X.stderr??"Worker restart failed");n(`Restarted ${z.name}`)})))}function pl(x){x.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 A=new o("Connecting to tunnel server...");A.start();try{let N;try{let re=await import("localtunnels");N=re.TunnelClient}catch{A.fail("localtunnels package not found"),e(`
655
+ `)}function ll(x){x.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 ys(t.env);try{let A=await qn({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(A,null,2));else n(`Collected ${A.collected} normalized records${A.cached?" (cached provider result)":""}.`),c(["Source","Freshness","Lag","Sampling","Retention"],A.statuses.map((N)=>[N.source,N.freshness,N.lagSeconds==null?"—":`${N.lagSeconds}s`,`${Math.round(N.samplingRate*100)}%`,`${N.retentionDays}d`]))}finally{m.controlPlane.store.close()}}),x.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 ys(t.env);try{let A=m.telemetry.query(qd(t,m.controlPlane.project.id,m.environmentRecord?.id));if(t.json)e(JSON.stringify(A,null,2));else c(["Time","Kind","Source","Signal","Value / message","Correlation"],A.records.map((N)=>[N.timestamp,N.kind,N.source,N.name,N.message??(N.value==null?"—":`${N.value} ${N.unit??""}`),N.requestId??N.traceId??N.releaseId??N.deploymentId??"—"]));if(A.truncated)a("More records are available; narrow the window or use the dashboard cursor explorer.")}finally{m.controlPlane.store.close()}}),x.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 A=await ys(m.env);try{let N=qd(m,A.controlPlane.project.id,A.environmentRecord?.id),z=A.telemetry.query(N);await Bun.write(t,t.toLowerCase().endsWith(".csv")?hg(z.records):JSON.stringify({exportedAt:new Date().toISOString(),query:N,...z},null,2)),n(`Exported ${z.records.length} records to ${t}${z.truncated?" (bounded result)":""}.`)}finally{A.controlPlane.store.close()}}),x.command("telemetry:compact","Downsample old metrics and enforce configured retention").option("--env <environment>","Target environment").action(async(t)=>{let m=await ys(t.env);try{let A=Ke(m.controlPlane.store,m.controlPlane.project.id);e(JSON.stringify(m.telemetry.enforceRetention(A,m.controlPlane.project.id),null,2))}finally{m.controlPlane.store.close()}}),x.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 ys(t.env);try{let A={};if(t.rawDays!=null)A.rawDays=Number(t.rawDays);if(t.downsampleDays!=null)A.downsampleAfterDays=Number(t.downsampleDays);if(t.sampling!=null)A.samplingRate=Number(t.sampling);if(t.storageUsdGbMonth!=null)A.estimatedStorageUsdPerGbMonth=Number(t.storageUsdGbMonth);let N=Object.keys(A).length?ei(m.controlPlane.store,m.controlPlane.project.id,A):Ke(m.controlPlane.store,m.controlPlane.project.id),U=m.telemetry.status(m.controlPlane.project.id,m.environmentRecord?.id,N.rawDays,N.samplingRate).reduce((te,re)=>te+re.estimatedDailyBytes*30,0),X={policy:N,estimatedMonthlyBytes:U,estimatedMonthlyCostUsd:ti(U,N),costEstimateConfigured:N.estimatedStorageUsdPerGbMonth>0};if(t.json)e(JSON.stringify(X,null,2));else e(JSON.stringify(N,null,2)),e(X.costEstimateConfigured?`Estimated local storage: $${X.estimatedMonthlyCostUsd}/month`:"Storage cost remains unavailable until --storage-usd-gb-month is set.")}finally{m.controlPlane.store.close()}})}async function co(x){let t=await Oe(),m=x??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let A=y(process.cwd(),t),N=A.environments.get(m),z=new gt(A.store,{encryptionKey:_(process.cwd())});return{config:t,env:m,controlPlane:A,environmentRecord:N,alerts:z}}function Gd(x){return new mr(x,{emailImpl:async(t)=>{let{email:m}=await import("./chunk-qvj454fy.js");return m.send(t)}})}function dl(x){x.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 co(t.env);try{let A=m.alerts.listHealthChecks(m.controlPlane.project.id,m.environmentRecord?.id).map((N)=>({...N,latest:m.alerts.listHealthResults(N.id,1)[0]}));if(t.json)e(JSON.stringify(A,null,2));else c(["ID","Name","Kind","Target","Status","Interval","Enabled"],A.map((N)=>[N.id,N.name,N.kind,N.target,N.latest?.status??"no data",`${N.intervalSeconds}s`,N.enabled?"yes":"no"]))}finally{m.controlPlane.store.close()}}),x.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 A=await co(m.env);try{let N=A.alerts.getHealthCheck(t)??A.alerts.listHealthChecks(A.controlPlane.project.id,A.environmentRecord?.id).find((X)=>X.name===t);if(!N)throw Error("Health check was not found");let z=new Me(A.alerts);if(m.test){let X=await z.probe(N);e(JSON.stringify({...X,productionIncidentCreated:!1},null,2));return}let U=await z.runAndEvaluate(N);if(e(JSON.stringify(U,null,2)),U.result.status==="healthy")n(`${N.name} is healthy.`);else a(`${N.name} is ${U.result.status}.`)}finally{A.controlPlane.store.close()}}),x.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 co(t.env);try{let A=m.alerts.listAlerts(m.controlPlane.project.id,{environmentId:m.environmentRecord?.id,states:t.state?.split(",").filter(Boolean)});if(t.json)e(JSON.stringify(A,null,2));else c(["ID","State","Severity","Title","Owner","First seen","Last seen"],A.map((N)=>[N.id,N.state,N.severity,N.title,N.ownerActorId??"unassigned",N.firstSeenAt,N.lastSeenAt]))}finally{m.controlPlane.store.close()}}),x.command("alerts:evaluate","Evaluate persisted telemetry rules now").option("--env <environment>","Target environment").action(async(t)=>{let m=await co(t.env);try{let A=Xr(m.alerts,m.controlPlane.project.id,m.environmentRecord?.id);e(JSON.stringify(A,null,2))}finally{m.controlPlane.store.close()}}),x.command("alerts:ack <alert>","Acknowledge an alert as the CLI actor").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await co(m.env);try{let N=A.controlPlane.store.getActorByExternalId("system","cli")??A.controlPlane.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});n(`Acknowledged ${A.alerts.acknowledge(t,N.id).title}.`)}finally{A.controlPlane.store.close()}}),x.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 co(t.env);try{let A={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(A,null,2));else c(["Channel","Type","Status","Tested","Error"],A.channels.map((N)=>[N.name,N.kind,N.status,N.lastTestedAt??"never",N.lastError??"—"])),c(["Event","State","Attempts","Status","Error"],A.deliveries.map((N)=>[N.eventType,N.state,`${N.attempt}/${N.maxAttempts}`,String(N.responseStatus??"—"),N.error??"—"]))}finally{m.controlPlane.store.close()}}),x.command("notifications:test <channel>","Send a channel test without creating a production incident").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await co(m.env);try{let N=A.alerts.getChannel(t)??A.alerts.listChannels(A.controlPlane.organization.id).find((U)=>U.name===t);if(!N)throw Error("Notification channel was not found");let z=await Gd(A.alerts).testChannel(N.id);if(z.ok)n("Test delivered; no production incident was created.");else throw Error(z.error)}finally{A.controlPlane.store.close()}}),x.command("notifications:retry <delivery>","Retry one failed notification delivery").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await co(m.env);try{let N=A.alerts.getDelivery(t);if(!N)throw Error("Notification delivery was not found");A.alerts.updateDelivery(t,{state:"retrying",attempt:Math.max(0,N.attempt-1),nextAttemptAt:new Date().toISOString()}),e(JSON.stringify(await Gd(A.alerts).deliver(t),null,2))}finally{A.controlPlane.store.close()}})}async function yg(x,t){let m=await Oe(),A=y(process.cwd(),m),N=!x&&t?new Ur(A.store).get(t):void 0,z=N?.environmentId?A.store.listEnvironments(A.project.id).find((Ce)=>Ce.id===N.environmentId)?.slug:void 0,U=x??z??Object.keys(m.environments??{})[0]??"production";if(!Object.hasOwn(m.environments??{},U))throw A.store.close(),Error(`Environment ${U} was not found`);let X=A.environments.get(U);if(!X)throw A.store.close(),Error(`Environment ${U} was not found in the control plane`);let te=new Ur(A.store),re=new b(A.store,{workerId:`cli:${process.pid}`}),oe=new Vn(te,{queue:re}),ae=A.store.listResources(A.project.id,X.id);Br(te,m,{organization:A.organization,project:A.project,environment:X,resources:ae});let ve=A.store.getActorByExternalId("system","cli")??A.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:m,env:U,controlPlane:A,environmentRecord:X,resources:ae,store:te,queue:re,service:oe,actor:ve}}async function fi(x,t,m){let A=await yg(x,m);try{return await t(A)}finally{A.controlPlane.store.close()}}async function Gr(x){try{await x()}catch(t){r(t instanceof Error?t.message:String(t))}}function Lo(x,t){let m=x.store.list(x.controlPlane.project.id,{environmentId:x.environmentRecord.id}),A=x.store.get(t)??m.find((N)=>N.name===t);if(!A||A.environmentId!==x.environmentRecord.id)throw Error(`Scheduled job ${t} was not found in ${x.env}`);return A}function wo(x,t,m,A){x.controlPlane.store.appendEvent({organizationId:x.controlPlane.organization.id,projectId:x.controlPlane.project.id,resourceId:A,actorId:x.actor.id,type:`job.${t}`,payload:m})}function Hd(x,t,m){if(x.environmentRecord.kind==="production"&&m!==t)throw Error(`Production ${t} requires --confirm ${t}`)}function vg(x){if(!x)return{};if(!/^(?:secret|ssm|arn|env):\/\//.test(x))throw Error("Payloads must use a secret://, ssm://, arn://, or env:// reference");return{input:x}}function bg(x){return x.map((t)=>[t.id,t.enabled?"enabled":"disabled",t.provider,t.name,t.expression,t.timezone,t.nextRunAt??"—",t.reconciliationStatus])}function ul(x){x.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)=>Gr(async()=>fi(t.env,async(m)=>{let A=m.store.list(m.controlPlane.project.id,{environmentId:m.environmentRecord.id});if(t.json)e(JSON.stringify(A,null,2));else c(["ID","State","Provider","Name","Expression","Timezone","Next run","Reconciliation"],bg(A))}))),x.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)=>Gr(async()=>{let A=at(t,m.timezone??"UTC",m.from?new Date(m.from):new Date,Number(m.count)||5);e(JSON.stringify({...A,productionExecutionCreated:!1},null,2))})),x.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,A)=>Gr(async()=>fi(A.env,async(N)=>{if(!["skip","catch_up"].includes(A.missed??"skip"))throw Error("--missed must be skip or catch_up");if(!["allow","forbid","replace"].includes(A.overlap??"forbid"))throw Error("--overlap must be allow, forbid, or replace");let z=H(N.config),X={workers:N.store.listWorkers(N.controlPlane.project.id,N.environmentRecord.id).map((Ce)=>({name:Ce.name}))},te;if(z==="server"){let Ce=Or(N.config,X).filter((we)=>!we.danger&&["scheduler","backup","worker"].includes(we.group)),Re=A.operation?Ce.find((we)=>we.id===A.operation):Ce.length===1?Ce[0]:void 0;if(!Re)throw Error(`Choose a safe target with --operation. Available: ${Ce.map((we)=>we.id).join(", ")||"none"}`);te={kind:"dashboard_operation",operationId:Re.id}}else te={kind:"serverless_scheduler",action:"run"};let re=z==="server"?"server":"eventbridge",oe=jn({provider:re,expression:m,flexibleMinutes:Number(A.flexibleMinutes)||0,missedRunPolicy:A.missed??"skip",overlapPolicy:A.overlap??"forbid"});if(!oe.supported)throw Error(oe.notes.join(" "));let ae=A.resource?N.resources.find((Ce)=>Ce.id===A.resource||Ce.slug===A.resource):void 0;if(A.resource&&!ae)throw Error(`Resource ${A.resource} was not found in ${N.env}`);let ve=N.store.create({organizationId:N.controlPlane.organization.id,projectId:N.controlPlane.project.id,environmentId:N.environmentRecord.id,resourceId:ae?.id,name:t,provider:re,expression:m,timezone:A.timezone??"UTC",startsAt:A.startsAt,endsAt:A.endsAt,flexibleMinutes:Number(A.flexibleMinutes)||0,target:te,payloadRefs:vg(A.payloadRef),missedRunPolicy:A.missed??"skip",overlapPolicy:A.overlap??"forbid",retryPolicy:{maxAttempts:Number(A.attempts)||3,backoffSeconds:Number(A.backoff)||30,deadLetterRef:A.deadLetter},timeoutSeconds:Number(A.timeout)||900,enabled:!A.disabled,origin:"managed",ownerActorId:N.actor.id,observedState:{},reconciliationStatus:"pending"});wo(N,"created",{jobId:ve.id,provider:ve.provider,expression:ve.normalizedExpression},ve.resourceId),n(`Created ${ve.name} (${ve.id}); next run ${ve.nextRunAt??"outside the active window"}`)}))),x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t);if(N.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 z=A.store.updateSchedule(N.id,{expression:m.expression,timezone:m.timezone,missedRunPolicy:m.missed,overlapPolicy:m.overlap,timeoutSeconds:m.timeout==null?void 0:Number(m.timeout)});wo(A,"updated",{jobId:z.id,expression:z.normalizedExpression,version:z.version},z.resourceId),n(`Updated ${z.name}; next run ${z.nextRunAt??"outside the active window"}`)}))),x.command("jobs:run <job>","Queue a manual run through the durable operation worker").option("--env <environment>","Target environment").action(async(t,m)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t),z=A.service.enqueue(N,{trigger:"manual",actorId:A.actor.id});wo(A,"run_queued",{jobId:N.id,executionId:z.id},N.resourceId),n(`Queued execution ${z.id}; inspect it with cloud jobs:history "${N.name}"`)}))),x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t);if(!N.enabled)throw Error(`${N.name} is disabled`);let z=m.scheduledFor?new Date(m.scheduledFor):new Date;if(!m.scheduledFor)z.setUTCSeconds(0,0);let U=z.toISOString(),X=A.service.enqueue(N,{trigger:m.scheduled?"scheduled":"external",scheduledFor:U,actorId:A.actor.id}),te=st(N.normalizedExpression,N.timezone,z,1)[0];A.store.markScheduled(N.id,U,te),n(`Accepted ${N.name} for ${U} as ${X.id}`)},t)));for(let t of["enable","disable"])x.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,A)=>Gr(async()=>fi(A.env,async(N)=>{let z=Lo(N,m);if(t==="disable")Hd(N,t,A.confirm);let U=N.store.setEnabled(z.id,t==="enable");wo(N,t==="enable"?"enabled":"disabled",{jobId:U.id},U.resourceId),n(`${U.name} is ${U.enabled?"enabled":"disabled"}`)})));x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t);Hd(A,"delete",m.confirm),A.store.remove(N.id),wo(A,"deleted",{jobId:N.id,name:N.name},N.resourceId),n(`Deleted ${N.name}`)}))),x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=Lo(A,t),z=A.store.listExecutions(N.id,Number(m.limit)||100).map((U)=>({...U,logs:m.logs&&U.operationId?A.queue.logs(U.operationId,{limit:500}):[]}));if(m.json)e(JSON.stringify(z,null,2));else if(c(["ID","Status","Trigger","Scheduled for","Attempt","Started","Finished","Error"],z.map((U)=>[U.id,U.status,U.trigger,U.scheduledFor,String(U.attempt),U.startedAt??"—",U.finishedAt??"—",U.error??"—"])),m.logs)for(let U of z)for(let X of U.logs)e(`${U.id} ${X.sequence} ${X.stream.toUpperCase()} ${X.message}`)}))),x.command("jobs:reconcile","Import declarative schedules and workers without destructive deletion").option("--env <environment>","Target environment").action(async(t)=>Gr(async()=>fi(t.env,async(m)=>{let A=Br(m.store,m.config,{organization:m.controlPlane.organization,project:m.controlPlane.project,environment:m.environmentRecord,resources:m.resources});wo(m,"reconciled",A),n(`Reconciled ${A.jobs} schedules and ${A.workers} workers; retained ${A.drifted} missing definitions as drifted`)}))),x.command("workers:list","List configured systemd, ECS, and Lambda workers").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>Gr(async()=>fi(t.env,async(m)=>{let A=m.store.listWorkers(m.controlPlane.project.id,m.environmentRecord.id);if(t.json)e(JSON.stringify(A,null,2));else c(["ID","Provider","Name","Queue","Processes","Timeout","Restart","Reconciliation"],A.map((N)=>[N.id,N.provider,N.name,N.queue,String(N.processes),`${N.timeoutSeconds}s`,N.restartPolicy,N.reconciliationStatus]))}))),x.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)=>Gr(async()=>fi(m.env,async(A)=>{let N=A.store.listWorkers(A.controlPlane.project.id,A.environmentRecord.id),z=A.store.getWorker(t)??N.find((te)=>te.name===t);if(!z||z.environmentId!==A.environmentRecord.id)throw Error(`Worker ${t} was not found in ${A.env}`);if(m.confirm!==z.name)throw Error(`Restart requires --confirm "${z.name}"`);if(z.provider!=="systemd")throw Error(`${z.provider} workers are provider/config managed and do not expose a safe restart adapter`);let U=Un(String(z.target.operationId??""),A.config,{workers:N.map((te)=>({name:te.name}))});if(!U||U.danger||U.group!=="worker")throw Error("The allowlisted worker restart operation is unavailable");let X=await Lr(A.config,A.env,U);if(A.store.reconcileWorker(z.id,X.ok?"in_sync":"unavailable",{...z.observedState,lastRestartAt:new Date().toISOString(),command:X.command??null,error:X.error??X.stderr??null}),wo(A,"worker_restarted",{workerId:z.id,ok:X.ok},z.resourceId),!X.ok)throw Error(X.error??X.stderr??"Worker restart failed");n(`Restarted ${z.name}`)})))}function pl(x){x.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 A=new o("Connecting to tunnel server...");A.start();try{let N;try{let re=await import("localtunnels");N=re.TunnelClient}catch{A.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 z=t.server.replace(/^(wss?|https?):\/\//,""),U=t.server.startsWith("wss://")||t.server.startsWith("https://")||z==="localtunnel.dev",X=new N({localPort:m,localHost:t.host,subdomain:t.subdomain,host:z,port:U?443:80,secure:U,verbose:t.verbose});X.on("connected",(re)=>{A.succeed("Connected!"),n(`
658
658
  Your tunnel URL: ${re.url}`),e(`Subdomain: ${re.subdomain}`),e(`