@stacksjs/ts-cloud 0.16.40 → 0.16.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aws/ec2-launch.test.d.ts +1 -0
- package/dist/aws/ec2.d.ts +47 -0
- package/dist/aws/index.js +1 -1
- package/dist/bin/{chunk-az504aqf.js → chunk-040x6j9v.js} +1 -1
- package/dist/bin/{chunk-0716rh6x.js → chunk-2b9e10db.js} +1 -1
- package/dist/bin/chunk-2tvtr5fy.js +1 -0
- package/dist/bin/{chunk-22x7vxxw.js → chunk-4azcshw6.js} +1 -1
- package/dist/bin/{chunk-t5kqyr8m.js → chunk-5dqnj5n9.js} +1 -1
- package/dist/bin/chunk-6kt12934.js +1 -0
- package/dist/bin/chunk-7nsjehvx.js +1 -0
- package/dist/bin/{chunk-w9rx90s2.js → chunk-89hvgp5e.js} +1 -1
- package/dist/bin/{chunk-8fxksvw4.js → chunk-8e4ck7cg.js} +1 -1
- package/dist/bin/{chunk-f2q239cy.js → chunk-a06d7cyt.js} +1 -1
- package/dist/bin/{chunk-7453cepa.js → chunk-aptdpa3m.js} +1 -1
- package/dist/bin/{chunk-w232tc20.js → chunk-c23r6gjb.js} +1 -1
- package/dist/bin/{chunk-1jwmkbzx.js → chunk-ddwp5np9.js} +9 -9
- package/dist/bin/{chunk-jmkfx9th.js → chunk-e8jwnsee.js} +2 -2
- package/dist/bin/{chunk-pj5svs6q.js → chunk-eaet154r.js} +1 -1
- package/dist/bin/{chunk-9vszh4dj.js → chunk-f7jpzyhr.js} +2 -2
- package/dist/bin/{chunk-sfzdr2b3.js → chunk-f8689v5g.js} +2 -2
- package/dist/bin/chunk-fhq31xzt.js +1 -0
- package/dist/bin/chunk-gj26v5hg.js +1 -0
- package/dist/bin/{chunk-b2kdxqd5.js → chunk-gn03w47w.js} +2 -2
- package/dist/bin/{chunk-1dqn0y04.js → chunk-grwth4nw.js} +2 -2
- package/dist/bin/{chunk-4g1z81pt.js → chunk-k0p394bn.js} +4 -4
- package/dist/bin/{chunk-h0s69e9p.js → chunk-ky4xdskt.js} +1 -1
- package/dist/bin/{chunk-js888a3w.js → chunk-kzgt05w5.js} +1 -1
- package/dist/bin/{chunk-bmj1d9xc.js → chunk-ndf3t76p.js} +11 -11
- package/dist/bin/{chunk-v9wf266f.js → chunk-p8xy9qm7.js} +2 -2
- package/dist/bin/chunk-pf9dbm3b.js +4 -0
- package/dist/bin/chunk-s64d5nw2.js +1 -0
- package/dist/bin/{chunk-5k3npcm6.js → chunk-x6w8jvs1.js} +3 -3
- package/dist/bin/{chunk-nqn983fx.js → chunk-xe9p2q8f.js} +17 -17
- package/dist/bin/{chunk-mex7tac5.js → chunk-xq7ngz6q.js} +1 -1
- package/dist/bin/cli.js +29 -29
- package/dist/bin/dashboard-server.js +1 -1
- package/dist/{chunk-bez9ytaz.js → chunk-ckqjqw4w.js} +1 -1
- package/dist/chunk-fwcf0n8f.js +4 -0
- package/dist/{chunk-yjh3y68t.js → chunk-jaswfk6g.js} +1 -1
- package/dist/deploy/index.js +1 -1
- package/dist/drivers/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/ui/404.html +1 -1
- package/dist/ui/accept-invitation.html +1 -1
- package/dist/ui/access-denied.html +1 -1
- package/dist/ui/account/automation.html +6 -6
- package/dist/ui/account/security.html +2 -2
- package/dist/ui/applications/compose.html +6 -6
- package/dist/ui/applications/new.html +2 -2
- package/dist/ui/data/backups.html +6 -6
- package/dist/ui/data/services.html +5 -5
- package/dist/ui/data/volumes.html +6 -6
- package/dist/ui/index.html +5 -5
- package/dist/ui/infrastructure/topology.html +5 -5
- package/dist/ui/integrations.html +1 -1
- package/dist/ui/operations/alerts.html +6 -6
- package/dist/ui/operations/configuration.html +6 -6
- package/dist/ui/operations/jobs.html +6 -6
- package/dist/ui/operations/maintenance.html +6 -6
- package/dist/ui/operations/observability.html +6 -6
- package/dist/ui/operations/previews.html +6 -6
- package/dist/ui/operations/queue.html +5 -5
- package/dist/ui/operations/regions.html +6 -6
- package/dist/ui/operations/releases.html +6 -6
- package/dist/ui/operations/spend.html +6 -6
- package/dist/ui/operations/workloads.html +6 -6
- package/dist/ui/security.html +1 -1
- package/dist/ui/server/actions.html +6 -6
- package/dist/ui/server/activity.html +1 -1
- package/dist/ui/server/capacity.html +6 -6
- package/dist/ui/server/database.html +6 -6
- package/dist/ui/server/deployments.html +5 -5
- package/dist/ui/server/diagnostics.html +1 -1
- package/dist/ui/server/firewall.html +6 -6
- package/dist/ui/server/fleet.html +6 -6
- package/dist/ui/server/logs.html +6 -6
- package/dist/ui/server/metrics.html +5 -5
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +2 -2
- package/dist/ui/server/sites.html +6 -6
- package/dist/ui/server/ssh-keys.html +6 -6
- package/dist/ui/server/team.html +6 -6
- package/dist/ui/server/terminal.html +2 -2
- package/dist/ui/serverless/alarms.html +6 -6
- package/dist/ui/serverless/assets.html +2 -2
- package/dist/ui/serverless/cost.html +2 -2
- package/dist/ui/serverless/data.html +6 -6
- package/dist/ui/serverless/deployments.html +2 -2
- package/dist/ui/serverless/firewall.html +1 -1
- package/dist/ui/serverless/functions.html +6 -6
- package/dist/ui/serverless/logs.html +6 -6
- package/dist/ui/serverless/metrics.html +2 -2
- package/dist/ui/serverless/queues.html +6 -6
- package/dist/ui/serverless/secrets.html +6 -6
- package/dist/ui/serverless/traces.html +6 -6
- package/dist/ui/serverless.html +5 -5
- package/package.json +3 -3
- package/dist/bin/chunk-0scywedn.js +0 -4
- package/dist/bin/chunk-459waehf.js +0 -1
- package/dist/bin/chunk-4y8zhe38.js +0 -1
- package/dist/bin/chunk-68tgx5j2.js +0 -1
- package/dist/bin/chunk-jxfgkhkc.js +0 -1
- package/dist/bin/chunk-k2mdcb66.js +0 -1
- package/dist/bin/chunk-kva8aybv.js +0 -1
- package/dist/chunk-ysx5m6cs.js +0 -4
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import{C,jt,n,r,a,Rr,e,u,i,s,R,d,Do,c,dn,ve,Ms,Us,Bs,Fs,f}from"./chunk-45a8yv3j.js";import{sa,H,$r,w,F,Fo,l,o,T,la,da,ua,xe,Fe,tt,Vo,bn,xi,Sn,zo,fa,ha,ya,ba,va,j,b,L,rt,k,ge,
|
|
2
|
+
import{C,jt,n,r,a,Rr,e,u,i,s,R,d,Do,c,dn,ve,Ms,Us,Bs,Fs,f}from"./chunk-45a8yv3j.js";import{sa,H,$r,w,F,Fo,l,o,T,la,da,ua,xe,Fe,tt,Vo,bn,xi,Sn,zo,fa,ha,ya,ba,va,j,b,L,rt,k,ge,qo,Ve,qt,_i,we,Go,Oi,Gt,At,Ca,Ta}from"./chunk-ddwp5np9.js";import"./chunk-4yym8xm4.js";import{Oe,x,Pn,y,Dn,Hi,kn,es,Fr,it,De,Nn,xn,_n,On,$n,Ln,Mn,Un,ie,Zt,er,Bn,jr,jn,Vn,zn,Hn,qn,Gn,Wn,Kn,ot,tr,rr,Dt,Vr,nr,ir,qi,kt,Jn,ce,Xn,Qn,Yn,zr,Hr,Zn,qr,or,sr,st,Nt,xt,Gr,le,ei,ac,cc,ar,lc,q,Gi,ze,at,ct,ti,Wr,Kr,ri,ni,ii,Jr,ts,de,ye,$e,be,oi,cr}from"./chunk-xe9p2q8f.js";import{p,ta}from"./chunk-dkf2kcsc.js";import{ae,h,Or}from"./chunk-xq7ngz6q.js";import{Dr,Tt,kr,Nr,xr,_r,gn}from"./chunk-8e4ck7cg.js";import{S,Ot,pi,fs,hr,yc,bc,Sc,mi,gi,mt,nn,$t,on,gt,Je,Me,Xe,ft,yr,Ue,Qe,fi,hi,Ye,sn,an,so,co,vr,Lt,ht,Mt,ee,mo,Si,vi,Is}from"./chunk-p8xy9qm7.js";import{Ze,Ut,Tr,ks,Ns,Er,Ne,Ci,wr,yo,bo,So,vo,Co,To,W,Bt,M,Ti}from"./chunk-ecd4swb7.js";import{J,P,ko,Hs,qs,Vc,zc,Ai,vt,Ri}from"./chunk-x6w8jvs1.js";import{v}from"./chunk-7593hjcq.js";import{Q}from"./chunk-pf9dbm3b.js";import{Ct,g}from"./chunk-nmqjmx30.js";import{qe,rs,ue,Wi,ai,Ki,Ji,Xr,Xi,Qi,Se,Z,Ge,ke,Qr,lr,We,dr,Yr,dt,pe,ut,pr,mr,gr,Zr,fr,O,Zi,eo,ci,ns,en,is,os,pc,to,ss,as,cs,ls,pt,ds,ro,no}from"./chunk-ndf3t76p.js";import"./chunk-k0p394bn.js";import{I}from"./chunk-7p2va3nj.js";import{me}from"./chunk-kzpzkdpd.js";import{K,bt,Io}from"./chunk-grwth4nw.js";import{oe,G,dc,uc,He}from"./chunk-f8689v5g.js";import{Qt,Rn,Yt,Vi,In,Zo}from"./chunk-zvbc92pc.js";import"./chunk-e8jwnsee.js";import{E}from"./chunk-gn03w47w.js";import{lt}from"./chunk-35x7z78g.js";import{te,Ce}from"./chunk-5wx26yjy.js";import{V}from"./chunk-9eqpnanr.js";import"./chunk-nj9x0tb8.js";import{zi}from"./chunk-f7jpzyhr.js";import"./chunk-4azcshw6.js";import"./chunk-a06d7cyt.js";import{ui}from"./chunk-n7yefy09.js";import{Cr}from"./chunk-k343q24p.js";import{yt}from"./chunk-fkzp9sg5.js";import{B}from"./chunk-f64jvh96.js";import{N}from"./chunk-kbwm0sqv.js";import{existsSync as yu}from"node:fs";import $a from"node:fs/promises";import xl from"node:os";import Aa from"node:path";import ao from"node:process";import{EventEmitter as vu}from"node:events";import ji from"node:process";import Dl from"node:process";import Ia from"node:process";import xs from"node:process";import Uo from"node:process";import Ol from"node:tty";import sm,{stdin as fh,stdout as gh}from"node:process";class Ll{configPath;config=null;events=[];retryCount=0;maxRetries=3;retryDelayMs=1000;constructor(){let D=xl.homedir(),t=Aa.join(D,".config","clapp");this.configPath=Aa.join(t,"telemetry.json")}async isEnabled(){if(ao.env.DO_NOT_TRACK==="1"||ao.env.DO_NOT_TRACK==="true")return!1;if(ao.env.NO_TELEMETRY==="1"||ao.env.NO_TELEMETRY==="true")return!1;return(await this.loadConfig()).enabled}async enable(){let D=await this.loadConfig();if(D.enabled=!0,!D.userId)D.userId=this.generateUserId();await this.saveConfig(D)}async disable(){let D=await this.loadConfig();D.enabled=!1,await this.saveConfig(D)}async track(D,t){if(!await this.isEnabled())return;let A={event:D,...t,timestamp:Date.now(),platform:xl.platform(),nodeVersion:ao.version};if(this.events.push(A),this.events.length>=10)await this.send()}async trackCommand(D,t){await this.track("command",{command:D,duration:t})}async trackError(D,t){await this.track("error",{error:D,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(D){return new Promise((t)=>setTimeout(t,D))}async status(){let D=await this.loadConfig();return{enabled:D.enabled,doNotTrack:ao.env.DO_NOT_TRACK==="1"||ao.env.DO_NOT_TRACK==="true",eventsQueued:this.events.length,lastSent:D.lastSent}}async loadConfig(){if(this.config)return this.config;try{if(yu(this.configPath)){let D=await $a.readFile(this.configPath,"utf-8");return this.config=JSON.parse(D),this.config}}catch{}return this.config={enabled:!1},this.config}async saveConfig(D){this.config=D;try{let t=Aa.dirname(this.configPath);await $a.mkdir(t,{recursive:!0}),await $a.writeFile(this.configPath,JSON.stringify(D,null,2),"utf-8")}catch{}}generateUserId(){let D=Math.random().toString(36).substring(2,15),t=Date.now().toString(36);return`${D}-${t}`}}var th=new Ll;function ka(D){return D.replace(/[<[].+/,"").trim()}function bu(D){let t=/<([^>]+)>/g,m=/\[([^\]]+)\]/g,A=[],_=(Y)=>{let X=!1,ne=Y[1];if(ne.startsWith("..."))ne=ne.slice(3),X=!0;return{required:Y[0].startsWith("<"),value:ne,variadic:X}},z;while(z=t.exec(D))A.push(_(z));let U;while(U=m.exec(D))A.push(_(U));return A}function wu(D){let t={alias:{},boolean:[]};for(let[m,A]of D.entries()){if(A.names.length>1)t.alias[A.names[0]]=A.names.slice(1);if(A.isBoolean)if(A.negated){if(!D.some((z,U)=>U!==m&&z.names.some((Y)=>A.names.includes(Y))&&typeof z.required==="boolean"))t.boolean.push(A.names[0])}else t.boolean.push(A.names[0])}return t}function El(D){return D.reduce((t,m)=>t.length>=m.length?t:m,"")}function Ra(D,t){return D.length>=t?D:`${D}${" ".repeat(t-D.length)}`}function Ps(D){return D.replace(/([a-z])-([a-z])/g,(t,m,A)=>m+A.toUpperCase())}function Su(D,t,m){let A=0,_=t.length,z=D,U;for(;A<_;++A)U=z[t[A]],z=z[t[A]]=A===_-1?m:U!=null?U:!!~t[A+1].indexOf(".")||!(+t[A+1]>-1)?{}:[]}function Cu(D,t){for(let m of Object.keys(t)){let A=t[m];if(A.shouldTransform){if(D[m]=Array.prototype.concat.call([],D[m]),typeof A.transformFunction==="function")D[m]=D[m].map(A.transformFunction)}}}function $u(D){let t=/([^\\/]+)$/.exec(D);return t?t[1]:""}function Wo(D){return D.split(".").map((t,m)=>m===0?Ps(t):t).join(".")}class Ho extends Error{exitCode=2;isUsageError=!0;usage;constructor(D,t){super(D);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(D).stack}format(D=!1){if(D&&this.stack)return`${this.message}
|
|
3
3
|
|
|
4
4
|
Stack trace:
|
|
5
5
|
${this.stack}`;return this.message}}function Au(D){if(D instanceof Ho)return!0;return!!D&&typeof D==="object"&&D.name==="ClappError"&&typeof D.message==="string"}function Ru(){let{env:D}=Dl,{TERM:t,TERM_PROGRAM:m}=D;if(Dl.platform!=="win32")return t!=="linux";return Boolean(D.WT_SESSION)||Boolean(D.TERMINUS_SUBLIME)||D.ConEmuTask==="{cmd::Cmder}"||m==="Terminus-Sublime"||m==="vscode"||t==="xterm-256color"||t==="alacritty"||t==="rxvt-unicode"||t==="rxvt-unicode-256color"||D.TERMINAL_EMULATOR==="JetBrains-JediTerm"}var Ds="\x1B",cn=`${Ds}[`;var Tl={to(D,t){if(!t)return`${cn}${D+1}G`;return`${cn}${t+1};${D+1}H`},move(D,t){let m="";if(D<0)m+=`${cn}${-D}D`;else if(D>0)m+=`${cn}${D}C`;if(t<0)m+=`${cn}${-t}A`;else if(t>0)m+=`${cn}${t}B`;return m},up:(D=1)=>`${cn}${D}A`,down:(D=1)=>`${cn}${D}B`,forward:(D=1)=>`${cn}${D}C`,backward:(D=1)=>`${cn}${D}D`,nextLine:(D=1)=>`${cn}E`.repeat(D),prevLine:(D=1)=>`${cn}F`.repeat(D),left:`${cn}G`,hide:`${cn}?25l`,show:`${cn}?25h`,save:`${Ds}7`,restore:`${Ds}8`};var oh={screen:`${cn}2J`,up:(D=1)=>`${cn}1J`.repeat(D),down:(D=1)=>`${cn}J`.repeat(D),line:`${cn}2K`,lineEnd:`${cn}K`,lineStart:`${cn}1K`,lines(D){let t="";for(let m=0;m<D;m++)t+=this.line+(m<D-1?Tl.up():"");if(D)t+=Tl.left;return t}},sh={screen:`${Ds}c`};function ku(D,t){let m=[];for(let A=0;A<=t.length;A++)m[A]=[A];for(let A=0;A<=D.length;A++)m[0][A]=A;for(let A=1;A<=t.length;A++)for(let _=1;_<=D.length;_++)if(t.charAt(A-1)===D.charAt(_-1))m[A][_]=m[A-1][_-1];else m[A][_]=Math.min(m[A-1][_-1]+1,m[A][_-1]+1,m[A-1][_]+1);return m[t.length][D.length]}function jl(D,t,m=2,A=3){return t.map((_)=>({cmd:_,distance:ku(D,_)})).filter(({distance:_})=>_<=m).sort((_,z)=>_.distance-z.distance).slice(0,A).map(({cmd:_})=>_)}function Iu(D,t={}){let m={_:[]},A=t.alias||{},_=new Set(t.boolean||[]),z={};for(let X of Object.keys(A))for(let ne of A[X])z[ne]=X;for(let X of _)if(A[X])for(let ne of A[X])_.add(ne);function U(X,ne){if(X===void 0)return ne;if(typeof ne==="boolean")return ne;return Array.isArray(X)?[...X,ne]:[X,ne]}function Y(X,ne){let re=z[X]||X,se=U(m[re],ne);if(m[re]=se,A[re])for(let fe of A[re])m[fe]=se;if(z[X]&&A[z[X]])for(let fe of A[z[X]])m[fe]=se;m[X]=se}for(let X=0;X<D.length;X++){let ne=D[X];if(ne==="--"){m._.push(...D.slice(X+1));break}if(ne.startsWith("--")){let re=ne.indexOf("=");if(re!==-1){let se=Ps(ne.slice(2,re)),fe=ne.slice(re+1);Y(se,fe)}else{let se=ne.slice(2);if(se.startsWith("no-")){let Pe=Ps(se.slice(3));Y(Pe,!1);continue}let fe=Ps(se),Ae=z[fe]||fe;if(_.has(Ae)||_.has(fe))Y(fe,!0);else{let Pe=D[X+1];if(Pe!==void 0&&!Pe.startsWith("-"))Y(fe,Pe),X++;else Y(fe,!0)}}}else if(ne.startsWith("-")&&ne.length>1){let re=ne.slice(1);for(let se=0;se<re.length;se++){let fe=re[se],Ae=z[fe]||fe;if(se===re.length-1&&!_.has(Ae)&&!_.has(fe)){let Pe=D[X+1];if(Pe!==void 0&&!Pe.startsWith("-"))Y(fe,Pe),X++;else Y(fe,!0)}else Y(fe,!0)}}else m._.push(ne)}return m}class Fl{rawName;description;name;names;isBoolean;required;config;negated;constructor(D,t,m){if(this.rawName=D,this.description=t,this.config=Object.assign({},m),D=D.replace(/\.\*/g,""),this.negated=!1,this.names=ka(D).split(",").map((A)=>{let _=A.trim().replace(/^-{1,2}/,"");if(_.startsWith("no-"))this.negated=!0,_=_.replace(/^no-/,"");return Wo(_)}).sort((A,_)=>A.length>_.length?1:-1),this.name=this.names[this.names.length-1],this.negated&&this.config.default==null)this.config.default=!0;if(D.includes("<"))this.required=!0;else if(D.includes("["))this.required=!1;else this.isBoolean=!0}}var ch=Ia.argv,Pu=`${Ia.platform}-${Ia.arch} bun-v${typeof Bun<"u"?Bun.version:"unknown"}`,Nl=xs.argv,xu=`${xs.platform}-${xs.arch} node-${xs.version}`;class Pa{rawName;description;config;cli;options;aliasNames;name;namespace;args;commandAction;usageText;versionNumber;examples;helpCallback;globalCommand;beforeHooks;afterHooks;middleware;constructor(D,t,m,A){this.rawName=D,this.description=t,this.config=m,this.cli=A,this.options=[],this.aliasNames=[],this.name=ka(D);let _=ka(D),z=_.indexOf(":");if(z>0)this.namespace=_.substring(0,z),this.name=_.substring(z+1);if(this.args=bu(D),this.examples=[],this.beforeHooks=[],this.afterHooks=[],this.middleware=[],!m)this.config={}}usage(D){return this.usageText=D,this}allowUnknownOptions(){return this.config.allowUnknownOptions=!0,this}ignoreOptionDefaultValue(){return this.config.ignoreOptionDefaultValue=!0,this}version(D,t="-v, --version"){return this.versionNumber=D,this.option(t,"Display version number"),this}example(D){return this.examples.push(D),this}option(D,t,m){let A=new Fl(D,t,m);return this.options.push(A),this}alias(D){return this.aliasNames.push(D),this}action(D){return this.commandAction=D,this}before(D){return this.beforeHooks.push(D),this}after(D){return this.afterHooks.push(D),this}use(D){return this.middleware.push(D),this}isMatched(D){if(this.aliasNames.includes(D))return!0;if(this.namespace)return`${this.namespace}:${this.name}`===D;return this.name===D}get isDefaultCommand(){return this.name===""||this.aliasNames.includes("!")}get isGlobalCommand(){return this instanceof xa}get displayName(){return this.namespace?`${this.namespace}:${this.name}`:this.name}hasOption(D){let t=Wo(D.split(".")[0]);return!!this.options.find((m)=>m.names.some((A)=>Wo(A)===t))}outputHelp(){let{name:D,commands:t}=this.cli,{versionNumber:m,options:A,helpCallback:_}=this.cli.globalCommand,z=[{body:`${D}${m?`/${m}`:""}`}];if(z.push({title:"Usage",body:` $ ${D} ${this.usageText||this.rawName}`}),(this.isGlobalCommand||this.isDefaultCommand)&&t.length>0){let ne=El(t.map((Pe)=>Pe.rawName)),re=new Map,se=[];for(let Pe of t)if(Pe.namespace){if(!re.has(Pe.namespace))re.set(Pe.namespace,[]);re.get(Pe.namespace).push(Pe)}else se.push(Pe);let fe="";if(se.sort((Pe,he)=>Pe.rawName<he.rawName?-1:Pe.rawName>he.rawName?1:0),se.length>0)fe+=se.map((Pe)=>` ${Ra(Pe.rawName,ne.length)} ${Pe.description}`).join(`
|
|
@@ -28,7 +28,7 @@ Received ${m}, cleaning up...`),D)try{await D()}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=jl(D,t);if(m.length>0)console.log(_o.yellow("Did you mean one of these?")),m.forEach((A)=>console.log(` ${_o.dim("•")} ${this.name} ${A}`)),console.log("")}console.log(_o.dim("Run"),`${this.name} --help`,_o.dim("to see all available commands")),ji.exit(1)}async parse(D=Nl,t={}){let{run:m=!0,exitOnError:A=!1}=t;if(A)try{return await this.parse(D,{run:m})}catch(X){throw this.handleUsageError(X),X}if(this.rawArgs=D,!this.name)this.name=D[1]?$u(D[1]):"cli";let _=!0,z=D.slice(2),U=z[0];if(U&&!U.startsWith("-")){for(let X of this.commands)if(X.isMatched(U)){let ne=this.mri(z,X);_=!1;let re={...ne,args:ne.args.slice(1)};this.setParsedInfo(re,X,U),this.emit(`command:${U}`,X);break}if(_){let X=z[1];if(X&&!X.startsWith("-")){let ne=`${U}:${X}`;for(let re of this.commands)if(re.isMatched(ne)){let se=this.mri(z,re);_=!1;let fe={...se,args:se.args.slice(2)};this.setParsedInfo(fe,re,ne),this.emit(`command:${ne}`,re);break}}}}if(_){for(let X of this.commands)if(X.name===""){_=!1;let ne=this.mri(z,X);this.setParsedInfo(ne,X),this.emit("command:!",X);break}}if(_){let X=this.mri(D.slice(2));this.setParsedInfo(X)}if(this.options.verbose)this.isVerbose=!0;if(this.options.quiet)this.isQuiet=!0;if(this.options.debug)this.isDebug=!0;if(this.options.noInteraction||this.options.interaction===!1)this.isNoInteraction=!0;if(this.options.env)this.environment=String(this.options.env);if(this.options.dryRun)this.isDryRun=!0;if(this.options.force)this.isForce=!0;if(this.options.noEmoji||this.options.emoji===!1)this.useEmoji=!1;if(this.options.theme)this.theme=String(this.options.theme);if(this.options.noCache||this.options.cache===!1)this.isNoCache=!0;if(this.options.help&&this.showHelpOnExit)this.outputHelp(),m=!1,this.unsetMatchedCommand();if(this.options.version&&this.showVersionOnExit&&this.matchedCommandName==null)this.outputVersion(),m=!1,this.unsetMatchedCommand();let Y={args:this.args,options:this.options};if(m)await this.runMatchedCommand();if(!this.matchedCommand&&this.args[0]){if(this.emit("command:*"),!(this.listenerCount("command:*")>0))this.showCommandNotFound(this.args[0])}return Y}mri(D,t){let m=[...this.globalCommand.options,...t?t.options:[]],A=wu(m),_=[],z=D.indexOf("--");if(z>-1)_=D.slice(z+1),D=D.slice(0,z);let U=Iu(D,A),Y={_:U._};for(let fe of Object.keys(U))if(fe!=="_")Y[Wo(fe)]=U[fe];let X=Y._,ne={"--":_},re=t&&t.config.ignoreOptionDefaultValue?t.config.ignoreOptionDefaultValue:this.globalCommand.config.ignoreOptionDefaultValue,se=Object.create(null);for(let fe of m){if(!re&&fe.config.default!==void 0)for(let Ae of fe.names)ne[Ae]=fe.config.default;if(Array.isArray(fe.config.type)){if(se[fe.name]===void 0)se[fe.name]={shouldTransform:!0,transformFunction:fe.config.type[0]}}}for(let fe of Object.keys(Y))if(fe!=="_"){let Ae=fe.split(".");Su(ne,Ae,Y[fe]),Cu(ne,se)}return{args:X,options:ne}}async run(D=Nl){return this.parse(D,{run:!0,exitOnError:!0})}handleUsageError(D){if(!Au(D)||D.isUsageError===!1)return;let t=D.message||"command-line error",m=this.name?`${this.name}: `:"",A=/--help/.test(t)?"":D.usage?`
|
|
29
29
|
Usage: ${D.usage}`:`
|
|
30
30
|
Run \`${this.name??"cli"} --help\` for usage.`;ji.stderr.write(`${m}${t}${A}
|
|
31
|
-
`),ji.exit(D.exitCode??2)}async runMatchedCommand(){let{args:D,options:t,matchedCommand:m}=this;if(!m||!m.commandAction)return;m.checkUnknownOptions(),m.checkOptionValue(),m.checkRequiredArgs();let A=[];m.args.forEach((Y,X)=>{if(Y.variadic)A.push(D.slice(X));else A.push(D[X])}),A.push(t);let _={command:m,args:A,options:t};for(let Y of m.beforeHooks)await Y(_);let z,U=async()=>{let Y=m.commandAction.apply(this,A);if(Y instanceof Promise)z=await Y;else z=Y;return z};if(m.middleware.length>0){let Y=0,X=async()=>{if(Y<m.middleware.length){let ne=m.middleware[Y++];await ne({..._,next:X})}else await U()};await X()}else await U();for(let Y of m.afterHooks)await Y(_);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 Bl{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(D){if(!this.enabled){this.misses++;return}let t=this.cache.get(D);if(!t){this.misses++;return}if(Date.now()-t.timestamp>t.ttl){this.cache.delete(D),this.misses++;return}return this.hits++,t.value}set(D,t,m=5000){if(!this.enabled)return;this.cache.set(D,{value:t,timestamp:Date.now(),ttl:m})}has(D){if(!this.enabled)return!1;let t=this.cache.get(D);if(!t)return!1;if(Date.now()-t.timestamp>t.ttl)return this.cache.delete(D),!1;return!0}delete(D){this.cache.delete(D)}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 D=Date.now();for(let[t,m]of this.cache.entries())if(D-m.timestamp>m.ttl)this.cache.delete(t)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var dh=new Bl;function Nu(){if("FORCE_COLOR"in Uo.env)return Uo.env.FORCE_COLOR!=="0";if("NO_COLOR"in Uo.env||Uo.env.TERM==="dumb")return!1;if(Uo.platform==="win32")return!0;return Ol.isatty(1)&&Ol.isatty(2)}var Ml=Nu();function rn(D,t){if(!Ml)return(m)=>m;return(m)=>D+m+t}var Ou=rn("\x1B[0m","\x1B[0m"),Lu=rn("\x1B[31m","\x1B[39m"),ju=rn("\x1B[32m","\x1B[39m"),Fu=rn("\x1B[33m","\x1B[39m"),Bu=rn("\x1B[34m","\x1B[39m"),Mu=rn("\x1B[35m","\x1B[39m"),_u=rn("\x1B[36m","\x1B[39m"),zu=rn("\x1B[37m","\x1B[39m"),Uu=rn("\x1B[90m","\x1B[39m"),Vu=rn("\x1B[1m","\x1B[22m"),Wu=rn("\x1B[3m","\x1B[23m"),qu=rn("\x1B[4m","\x1B[24m"),Gu=rn("\x1B[2m","\x1B[22m"),Hu=rn("\x1B[7m","\x1B[27m"),Ku=rn("\x1B[8m","\x1B[28m"),Ju=rn("\x1B[9m","\x1B[29m"),Yu=rn("\x1B[41m","\x1B[49m"),Zu=rn("\x1B[42m","\x1B[49m"),Qu=rn("\x1B[43m","\x1B[49m"),Xu=rn("\x1B[44m","\x1B[49m"),em=rn("\x1B[45m","\x1B[49m"),tm=rn("\x1B[46m","\x1B[49m"),nm=rn("\x1B[47m","\x1B[49m"),rm=Ml,im={reset:Ou,red:Lu,green:ju,yellow:Fu,blue:Bu,magenta:Mu,cyan:_u,white:zu,gray:Uu,bold:Vu,italic:Wu,underline:qu,dim:Gu,inverse:Hu,hidden:Ku,strikethrough:Ju,bgRed:Yu,bgGreen:Zu,bgYellow:Qu,bgBlue:Xu,bgMagenta:em,bgCyan:tm,bgWhite:nm,isColorSupported:rm},om=im,am=["up","down","left","right","space","enter","cancel"],hh={actions:new Set(am),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};var yh=sm.platform.startsWith("win"),vh=Symbol("clapp:cancel");var cm=Ru();var tn=(D,t)=>cm?D:t,bh=tn("◆","*"),wh=tn("■","x"),Sh=tn("▲","x"),Ch=tn("◇","o"),$h=tn("┌","T"),lm=tn("│","|"),Ah=tn("└","—"),Rh=tn("●",">"),kh=tn("○"," "),Ih=tn("◻","[•]"),Ph=tn("◼","[+]"),xh=tn("◻","[ ]"),Dh=tn("▪","•"),Eh=tn("─","-"),Th=tn("╮","+"),Nh=tn("├","+"),Oh=tn("╯","+"),Lh=tn("●","•"),jh=tn("◆","*"),Fh=tn("▲","!"),Bh=tn("■","x");var Mh={light:tn("─","-"),heavy:tn("━","="),block:tn("█","#")};function dm(){return`${om.gray(lm)} `}var _h=dm();var Ea="0.16.
|
|
31
|
+
`),ji.exit(D.exitCode??2)}async runMatchedCommand(){let{args:D,options:t,matchedCommand:m}=this;if(!m||!m.commandAction)return;m.checkUnknownOptions(),m.checkOptionValue(),m.checkRequiredArgs();let A=[];m.args.forEach((Y,X)=>{if(Y.variadic)A.push(D.slice(X));else A.push(D[X])}),A.push(t);let _={command:m,args:A,options:t};for(let Y of m.beforeHooks)await Y(_);let z,U=async()=>{let Y=m.commandAction.apply(this,A);if(Y instanceof Promise)z=await Y;else z=Y;return z};if(m.middleware.length>0){let Y=0,X=async()=>{if(Y<m.middleware.length){let ne=m.middleware[Y++];await ne({..._,next:X})}else await U()};await X()}else await U();for(let Y of m.afterHooks)await Y(_);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 Bl{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(D){if(!this.enabled){this.misses++;return}let t=this.cache.get(D);if(!t){this.misses++;return}if(Date.now()-t.timestamp>t.ttl){this.cache.delete(D),this.misses++;return}return this.hits++,t.value}set(D,t,m=5000){if(!this.enabled)return;this.cache.set(D,{value:t,timestamp:Date.now(),ttl:m})}has(D){if(!this.enabled)return!1;let t=this.cache.get(D);if(!t)return!1;if(Date.now()-t.timestamp>t.ttl)return this.cache.delete(D),!1;return!0}delete(D){this.cache.delete(D)}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 D=Date.now();for(let[t,m]of this.cache.entries())if(D-m.timestamp>m.ttl)this.cache.delete(t)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var dh=new Bl;function Nu(){if("FORCE_COLOR"in Uo.env)return Uo.env.FORCE_COLOR!=="0";if("NO_COLOR"in Uo.env||Uo.env.TERM==="dumb")return!1;if(Uo.platform==="win32")return!0;return Ol.isatty(1)&&Ol.isatty(2)}var Ml=Nu();function rn(D,t){if(!Ml)return(m)=>m;return(m)=>D+m+t}var Ou=rn("\x1B[0m","\x1B[0m"),Lu=rn("\x1B[31m","\x1B[39m"),ju=rn("\x1B[32m","\x1B[39m"),Fu=rn("\x1B[33m","\x1B[39m"),Bu=rn("\x1B[34m","\x1B[39m"),Mu=rn("\x1B[35m","\x1B[39m"),_u=rn("\x1B[36m","\x1B[39m"),zu=rn("\x1B[37m","\x1B[39m"),Uu=rn("\x1B[90m","\x1B[39m"),Vu=rn("\x1B[1m","\x1B[22m"),Wu=rn("\x1B[3m","\x1B[23m"),qu=rn("\x1B[4m","\x1B[24m"),Gu=rn("\x1B[2m","\x1B[22m"),Hu=rn("\x1B[7m","\x1B[27m"),Ku=rn("\x1B[8m","\x1B[28m"),Ju=rn("\x1B[9m","\x1B[29m"),Yu=rn("\x1B[41m","\x1B[49m"),Zu=rn("\x1B[42m","\x1B[49m"),Qu=rn("\x1B[43m","\x1B[49m"),Xu=rn("\x1B[44m","\x1B[49m"),em=rn("\x1B[45m","\x1B[49m"),tm=rn("\x1B[46m","\x1B[49m"),nm=rn("\x1B[47m","\x1B[49m"),rm=Ml,im={reset:Ou,red:Lu,green:ju,yellow:Fu,blue:Bu,magenta:Mu,cyan:_u,white:zu,gray:Uu,bold:Vu,italic:Wu,underline:qu,dim:Gu,inverse:Hu,hidden:Ku,strikethrough:Ju,bgRed:Yu,bgGreen:Zu,bgYellow:Qu,bgBlue:Xu,bgMagenta:em,bgCyan:tm,bgWhite:nm,isColorSupported:rm},om=im,am=["up","down","left","right","space","enter","cancel"],hh={actions:new Set(am),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};var yh=sm.platform.startsWith("win"),vh=Symbol("clapp:cancel");var cm=Ru();var tn=(D,t)=>cm?D:t,bh=tn("◆","*"),wh=tn("■","x"),Sh=tn("▲","x"),Ch=tn("◇","o"),$h=tn("┌","T"),lm=tn("│","|"),Ah=tn("└","—"),Rh=tn("●",">"),kh=tn("○"," "),Ih=tn("◻","[•]"),Ph=tn("◼","[+]"),xh=tn("◻","[ ]"),Dh=tn("▪","•"),Eh=tn("─","-"),Th=tn("╮","+"),Nh=tn("├","+"),Oh=tn("╯","+"),Lh=tn("●","•"),jh=tn("◆","*"),Fh=tn("▲","!"),Bh=tn("■","x");var Mh={light:tn("─","-"),heavy:tn("━","="),block:tn("█","#")};function dm(){return`${om.gray(lm)} `}var _h=dm();var Ea="0.16.41";function _l(D,t,m=process.env){let A=t?.options.find((U)=>U.name==="profile");if((!A||/\baws\b/i.test(A.description))&&typeof D.profile==="string"&&D.profile)m.AWS_PROFILE=D.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 D.region==="string"&&D.region)m.AWS_REGION=D.region,m.AWS_DEFAULT_REGION=D.region}import{existsSync as Na}from"node:fs";import{mkdir as mm,writeFile as zl}from"node:fs/promises";function Oa(D){D.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"),Na("cloud.config.ts")){if(!await d("cloud.config.ts already exists. Overwrite?",!1)){e("Initialization cancelled");return}}let m=t?.name||await R("Project name","my-app"),A=t?.mode||await Do("Select deployment mode",["serverless","server"]),_=t?.region||await Do("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),z=new s("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")}),D.command("init:server","Initialize server-based (EC2) project").action(async()=>{i("Initializing Server-Based Project"),await D.parse(["init","--mode","server"])}),D.command("init:serverless","Initialize serverless (Fargate/Lambda) project").action(async()=>{i("Initializing Serverless Project"),await D.parse(["init","--mode","serverless"])}),D.command("init:hybrid","Initialize hybrid project").action(async()=>{i("Initializing Hybrid Project"),await D.parse(["init","--mode","hybrid"])})}import{readFile as Ul,writeFile as pm}from"node:fs/promises";import{resolve as Es}from"node:path";async function je(){let D=await Hi();if(!D.project)throw Error("Missing required project configuration in cloud.config.ts");return D}function pn(D){if(D)switch(D.toLowerCase()){case"porkbun":{let A=process.env.PORKBUN_API_KEY,_=process.env.PORKBUN_SECRET_KEY;if(!A||!_)throw Error("PORKBUN_API_KEY and PORKBUN_SECRET_KEY environment variables are required for Porkbun provider");return{provider:"porkbun",apiKey:A,secretKey:_}}case"godaddy":{let A=process.env.GODADDY_API_KEY,_=process.env.GODADDY_API_SECRET;if(!A||!_)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:_,environment:z}}case"route53":{let A=process.env.AWS_REGION||"us-east-1",_=process.env.AWS_HOSTED_ZONE_ID;return{provider:"route53",region:A,hostedZoneId:_}}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: ${D}. Supported: porkbun, godaddy, route53, cloudflare`)}if(new Ti().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 li(D){let t=pn(D);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 fm(D){let t=await je(),m=y(process.cwd(),t),A=D??Object.keys(t.environments??{})[0]??"production",_=m.environments.get(A);if(!_)throw m.store.close(),Error(`Environment ${A} was not found.`);let z=t.project?.region??"us-east-1",U=x(process.cwd()),Y=new Dr(m.store),X=new _r(Y,{encryptionKey:U,backends:[new Tt(m.store,U),new kr(new K(z),z),new Nr(new Ge(z),z),new xr]}),ne=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return await gn(X,m,t,ne.id),{config:t,controlPlane:m,env:A,environmentRecord:_,store:Y,service:X,actor:ne}}async function di(D,t){let m=await fm(D);try{return await t(m)}finally{m.controlPlane.store.close()}}async function An(D){try{await D()}catch(t){r(t instanceof Error?t.message:String(t))}}function gm(D){return D.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 Vl(D,t=process.env){if(D.value!=null)throw Error("Secret values are not accepted in command arguments; use --from-env or the interactive prompt.");if(!D.fromEnv)return;let m=t[D.fromEnv];if(!m)throw Error(`${D.fromEnv} is empty or unavailable.`);return m}function Ar(D,t){let m=t.scope??"environment";if(m==="project")return{type:m,id:D.controlPlane.project.id};if(m==="environment"){let A=t.target?D.controlPlane.store.listEnvironments(D.controlPlane.project.id).find((_)=>_.id===t.target||_.slug===t.target):D.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=D.controlPlane.store.listResources(D.controlPlane.project.id),_=A.find((z)=>z.id===t.target||z.slug===t.target);if(!_)throw Error("Service scope requires --target with a resource ID or slug.");return{type:m,id:_.id,environmentId:_.environmentId,resourceId:_.id}}if(m==="function"){let A=D.controlPlane.store.listResources(D.controlPlane.project.id),_=A.find((z)=>z.kind==="function"&&(z.id===t.target||z.slug===t.target));if(!_)throw Error("Function scope requires --target with a function resource ID or slug.");return{type:m,id:_.id,environmentId:_.environmentId,resourceId:_.id}}if(m==="preview"){let A=new Me(D.controlPlane.store).listInstances({projectId:D.controlPlane.project.id}).find((_)=>_.id===t.target||_.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 La(D){let t=D??"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 Ao(D,t,m){let A=D.store.find(D.controlPlane.project.id,m,t);if(!A)throw Error(`${t} was not found in the selected scope.`);return A}async function hm(D,t){if(D==="secret")return Vl(t)??await R("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 R("Enter variable value","")}function yi(D){return D.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 ja(D){D.command("config","Show validated file configuration").action(async()=>An(async()=>{let t=await je();console.log(JSON.stringify(t,null,2))})),D.command("config:validate","Validate configuration file").action(async()=>An(async()=>{let t=await je();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(D.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)=>An(async()=>di(t.env,async(m)=>{let A=Ar(m,t),_=["variable","secret"].includes(t.kind??"")?t.kind:void 0,z=m.service.list({projectId:m.controlPlane.project.id,scope:A,kind:_,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"],gm(z))}))),yi(D.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)=>An(async()=>di(m.env,async(A)=>{let _=Ar(A,m),z=m.secret?"secret":"variable",U=z==="secret"?La(m.backend):void 0,Y=U==="external"?void 0:await hm(z,m),X=await A.service.set({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,scope:_,key:t,kind:z,value:Y,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:_,canReadSecretMetadata:!0}).find((ne)=>ne.id===X.entry.id),mutation:X.mutation,warnings:X.warnings},null,2));else n(`${t}: ${X.mutation.added.length?"created":X.mutation.changed.length?"updated":"unchanged"}; ${X.mutation.affectedResourceIds.length} affected service(s).`)}))),yi(D.command("config:delete <key>","Delete a scoped variable or secret")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>An(async()=>di(m.env,async(A)=>{let _=Ao(A,t,Ar(A,m));await A.service.remove({entryId:_.id,expectedVersion:_.version,confirmed:m.confirm===t,actorId:A.actor.id}),n(`Deleted ${t}.`)}))),yi(D.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)=>An(async()=>di(m.env,async(A)=>{let _=await A.service.importDotenv({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,scope:Ar(A,m),source:await Ul(Es(t),"utf8"),secretKeys:m.secretKeys?.split(",").map((z)=>z.trim()).filter(Boolean),backend:La(m.backend),confirmed:m.confirm==="import",actorId:A.actor.id,idempotencyKey:`cli-import:${crypto.randomUUID()}`});if(!_.document.valid)throw Error(_.document.diagnostics.map((z)=>`line ${z.line}: ${z.message}`).join(`
|
|
68
|
-
`));if(m.json)console.log(JSON.stringify({mutation:_.mutation,diagnostics:_.document.diagnostics,warnings:_.warnings},null,2));else n(`Imported ${_.mutation?.added.length??0} new, ${_.mutation?.changed.length??0} changed, and ${_.mutation?.unchanged.length??0} unchanged entries.`)}))),yi(D.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)=>An(async()=>di(m.env,async(A)=>{let{parseDotenv:_}=await import("./chunk-
|
|
67
|
+
3. Run 'cloud deploy' to deploy your infrastructure`,"green")}),D.command("init:server","Initialize server-based (EC2) project").action(async()=>{i("Initializing Server-Based Project"),await D.parse(["init","--mode","server"])}),D.command("init:serverless","Initialize serverless (Fargate/Lambda) project").action(async()=>{i("Initializing Serverless Project"),await D.parse(["init","--mode","serverless"])}),D.command("init:hybrid","Initialize hybrid project").action(async()=>{i("Initializing Hybrid Project"),await D.parse(["init","--mode","hybrid"])})}import{readFile as Ul,writeFile as pm}from"node:fs/promises";import{resolve as Es}from"node:path";async function je(){let D=await Hi();if(!D.project)throw Error("Missing required project configuration in cloud.config.ts");return D}function pn(D){if(D)switch(D.toLowerCase()){case"porkbun":{let A=process.env.PORKBUN_API_KEY,_=process.env.PORKBUN_SECRET_KEY;if(!A||!_)throw Error("PORKBUN_API_KEY and PORKBUN_SECRET_KEY environment variables are required for Porkbun provider");return{provider:"porkbun",apiKey:A,secretKey:_}}case"godaddy":{let A=process.env.GODADDY_API_KEY,_=process.env.GODADDY_API_SECRET;if(!A||!_)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:_,environment:z}}case"route53":{let A=process.env.AWS_REGION||"us-east-1",_=process.env.AWS_HOSTED_ZONE_ID;return{provider:"route53",region:A,hostedZoneId:_}}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: ${D}. Supported: porkbun, godaddy, route53, cloudflare`)}if(new Ti().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 li(D){let t=pn(D);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 fm(D){let t=await je(),m=y(process.cwd(),t),A=D??Object.keys(t.environments??{})[0]??"production",_=m.environments.get(A);if(!_)throw m.store.close(),Error(`Environment ${A} was not found.`);let z=t.project?.region??"us-east-1",U=x(process.cwd()),Y=new Dr(m.store),X=new _r(Y,{encryptionKey:U,backends:[new Tt(m.store,U),new kr(new K(z),z),new Nr(new qe(z),z),new xr]}),ne=m.store.getActorByExternalId("system","cli")??m.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return await gn(X,m,t,ne.id),{config:t,controlPlane:m,env:A,environmentRecord:_,store:Y,service:X,actor:ne}}async function di(D,t){let m=await fm(D);try{return await t(m)}finally{m.controlPlane.store.close()}}async function An(D){try{await D()}catch(t){r(t instanceof Error?t.message:String(t))}}function gm(D){return D.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 Vl(D,t=process.env){if(D.value!=null)throw Error("Secret values are not accepted in command arguments; use --from-env or the interactive prompt.");if(!D.fromEnv)return;let m=t[D.fromEnv];if(!m)throw Error(`${D.fromEnv} is empty or unavailable.`);return m}function Ar(D,t){let m=t.scope??"environment";if(m==="project")return{type:m,id:D.controlPlane.project.id};if(m==="environment"){let A=t.target?D.controlPlane.store.listEnvironments(D.controlPlane.project.id).find((_)=>_.id===t.target||_.slug===t.target):D.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=D.controlPlane.store.listResources(D.controlPlane.project.id),_=A.find((z)=>z.id===t.target||z.slug===t.target);if(!_)throw Error("Service scope requires --target with a resource ID or slug.");return{type:m,id:_.id,environmentId:_.environmentId,resourceId:_.id}}if(m==="function"){let A=D.controlPlane.store.listResources(D.controlPlane.project.id),_=A.find((z)=>z.kind==="function"&&(z.id===t.target||z.slug===t.target));if(!_)throw Error("Function scope requires --target with a function resource ID or slug.");return{type:m,id:_.id,environmentId:_.environmentId,resourceId:_.id}}if(m==="preview"){let A=new Me(D.controlPlane.store).listInstances({projectId:D.controlPlane.project.id}).find((_)=>_.id===t.target||_.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 La(D){let t=D??"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 Ao(D,t,m){let A=D.store.find(D.controlPlane.project.id,m,t);if(!A)throw Error(`${t} was not found in the selected scope.`);return A}async function hm(D,t){if(D==="secret")return Vl(t)??await R("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 R("Enter variable value","")}function yi(D){return D.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 ja(D){D.command("config","Show validated file configuration").action(async()=>An(async()=>{let t=await je();console.log(JSON.stringify(t,null,2))})),D.command("config:validate","Validate configuration file").action(async()=>An(async()=>{let t=await je();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(D.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)=>An(async()=>di(t.env,async(m)=>{let A=Ar(m,t),_=["variable","secret"].includes(t.kind??"")?t.kind:void 0,z=m.service.list({projectId:m.controlPlane.project.id,scope:A,kind:_,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"],gm(z))}))),yi(D.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)=>An(async()=>di(m.env,async(A)=>{let _=Ar(A,m),z=m.secret?"secret":"variable",U=z==="secret"?La(m.backend):void 0,Y=U==="external"?void 0:await hm(z,m),X=await A.service.set({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,scope:_,key:t,kind:z,value:Y,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:_,canReadSecretMetadata:!0}).find((ne)=>ne.id===X.entry.id),mutation:X.mutation,warnings:X.warnings},null,2));else n(`${t}: ${X.mutation.added.length?"created":X.mutation.changed.length?"updated":"unchanged"}; ${X.mutation.affectedResourceIds.length} affected service(s).`)}))),yi(D.command("config:delete <key>","Delete a scoped variable or secret")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>An(async()=>di(m.env,async(A)=>{let _=Ao(A,t,Ar(A,m));await A.service.remove({entryId:_.id,expectedVersion:_.version,confirmed:m.confirm===t,actorId:A.actor.id}),n(`Deleted ${t}.`)}))),yi(D.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)=>An(async()=>di(m.env,async(A)=>{let _=await A.service.importDotenv({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,scope:Ar(A,m),source:await Ul(Es(t),"utf8"),secretKeys:m.secretKeys?.split(",").map((z)=>z.trim()).filter(Boolean),backend:La(m.backend),confirmed:m.confirm==="import",actorId:A.actor.id,idempotencyKey:`cli-import:${crypto.randomUUID()}`});if(!_.document.valid)throw Error(_.document.diagnostics.map((z)=>`line ${z.line}: ${z.message}`).join(`
|
|
68
|
+
`));if(m.json)console.log(JSON.stringify({mutation:_.mutation,diagnostics:_.document.diagnostics,warnings:_.warnings},null,2));else n(`Imported ${_.mutation?.added.length??0} new, ${_.mutation?.changed.length??0} changed, and ${_.mutation?.unchanged.length??0} unchanged entries.`)}))),yi(D.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)=>An(async()=>di(m.env,async(A)=>{let{parseDotenv:_}=await import("./chunk-040x6j9v.js"),z=_(await Ul(Es(t),"utf8"));if(!z.valid)throw Error(z.diagnostics.map((Y)=>`line ${Y.line}: ${Y.message}`).join(`
|
|
69
69
|
`));let U=A.service.plan({projectId:A.controlPlane.project.id,scope:Ar(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(D.command("config:export [file]","Export plaintext variables only as dotenv")).action(async(t,m)=>An(async()=>di(m.env,async(A)=>{let _=A.service.exportVariables({projectId:A.controlPlane.project.id,scope:Ar(A,m)});if(t)await pm(Es(t),_,{mode:384}),n(`Exported variables to ${Es(t)}; secrets were excluded.`);else process.stdout.write(_)}))),yi(D.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)=>An(async()=>di(m.env,async(A)=>{let _=Ar(A,m),z=Ao(A,t,_),U=Ar(A,{env:m.env,scope:m.toScope,target:m.toTarget}),Y=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"}; ${Y.mutation.affectedResourceIds.length} affected service(s).`)}))),yi(D.command("config:reveal <key>","Reveal a secret after exact confirmation")).option("--confirm <key>","Exact key confirmation").action(async(t,m)=>An(async()=>di(m.env,async(A)=>{if(m.confirm!==t)throw Error(`Use --confirm ${t} to reveal this secret.`);let _=Ao(A,t,Ar(A,m));console.log(await A.service.reveal({entryId:_.id,canRevealSecrets:!0,recentlyAuthenticated:!0,actorId:A.actor.id}))}))),yi(D.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)=>An(async()=>di(t.environment??t.env,async(m)=>{let A=Ar(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((_)=>[_.key,_.value??"",_.updatedAt]));else if(t.set){let _=t.set.indexOf("=");if(_<1)throw Error("Use --set KEY=VALUE.");let z=t.set.slice(0,_),U=t.set.slice(_+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 _=Ao(m,t.unset,A);await m.service.remove({entryId:_.id,expectedVersion:_.version,confirmed:await d(`Delete ${_.key}?`,!1),actorId:m.actor.id}),n(`Deleted ${_.key}.`)}else e("Use --list, --set KEY=VALUE, or --unset KEY.")}))),yi(D.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)=>An(async()=>di(t.env,async(m)=>{let A=Ar(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((_)=>[_.key,_.backend??"restricted",_.backendVersion??"—",_.rotatedAt??"never"]));else if(t.create||t.update){let _=t.create??t.update,z=Vl(t)??await R("Enter secret value (write-only)","");await m.service.set({organizationId:m.controlPlane.organization.id,projectId:m.controlPlane.project.id,scope:A,key:_,kind:"secret",value:z,backend:La(t.backend),confirmed:t.confirm===_,actorId:m.actor.id}),n(`${_} ${t.update?"rotated":"created"}.`)}else if(t.get){if(t.confirm!==t.get)throw Error(`Use --confirm ${t.get} to reveal.`);let _=Ao(m,t.get,A);console.log(await m.service.reveal({entryId:_.id,canRevealSecrets:!0,recentlyAuthenticated:!0,actorId:m.actor.id}))}else if(t.delete){let _=Ao(m,t.delete,A);await m.service.remove({entryId:_.id,expectedVersion:_.version,confirmed:t.confirm===_.key,actorId:m.actor.id}),n(`Deleted ${_.key}.`)}else e("Use --list, --create, --update, --get, or --delete.")})))}import{existsSync as xm}from"node:fs";import{readFile as Dm,writeFile as Em}from"node:fs/promises";async function Ts(D){let t=[];for(let m of D.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 Wl(D){return D.filter((t)=>t.state!=="satisfied")}function ym(D){return Wl(D).some((t)=>t.step.destructive===!0)}function Os(D,t){let m=Wl(t),A=[`${D.operation} ${D.target}`];if(m.length===0)return A.push(" Nothing to do — every step is already satisfied."),A;for(let{step:z,state:U,reason:Y}of t){let X=U==="satisfied"?"ok ":U==="unknown"?"? ":"→ ",ne=[z.destructive?"DESTRUCTIVE":"",U==="satisfied"?"already done":""].filter(Boolean);if(A.push(` ${X}${z.title}${ne.length>0?` [${ne.join(", ")}]`:""}`),z.change)A.push(` ${z.change.from} → ${z.change.to}`);if(Y)A.push(` could not check: ${Y}`)}let _=m.filter((z)=>z.step.destructive).length;return A.push(` ${m.length} step(s) would run`+(_>0?`, ${_} irreversible — re-run with --confirm ${D.target}`:"")),A}async function Ls(D,t,m={}){let A={operation:D.operation,target:D.target,steps:[],success:!0};if(ym(t)&&m.confirm!==D.target)throw Error(`${D.operation} includes an irreversible step. Re-run with --confirm ${D.target} to authorize it.`);for(let{step:_,state:z}of t){if(z==="satisfied"){A.steps.push({id:_.id,title:_.title,state:"skipped"}),m.log?.(`skip ${_.title} (already done)`);continue}m.audit?.({operation:D.operation,target:D.target,step:_.id,state:"started"});try{await _.apply(),A.steps.push({id:_.id,title:_.title,state:"applied"}),m.log?.(`done ${_.title}`),m.audit?.({operation:D.operation,target:D.target,step:_.id,state:"applied"})}catch(U){let Y=U instanceof Error?U.message:String(U);return A.steps.push({id:_.id,title:_.title,state:"failed",error:Y}),A.success=!1,m.log?.(`FAIL ${_.title}: ${Y}`),m.audit?.({operation:D.operation,target:D.target,step:_.id,state:"failed",error:Y}),A}}return A}function Ro(D,t){return`/tmp/ts-cloud-move-${D}-${t}.tar.gz`}function ql(D,t){return`${D}-${t}*.service`}function Vt(D){return`'${D.split("'").join("'\\''")}'`}function vm(D,t){return["set -eu",`for unit in $(ls /etc/systemd/system/ 2>/dev/null | grep -E ${Vt(Gl(D,t))} || true); do`,' systemctl stop "$unit" 2>/dev/null || true',"done"].join(`
|
|
70
70
|
`)}function Gl(D,t){return`^${D}-${t}-((queue|daemon)-.*|scheduler)\\.service$`}function bm(D,t){return Hl(Gl(D,t))}function Hl(D){return[`ls /etc/systemd/system/ 2>/dev/null | grep -E ${Vt(D)} | while read -r unit; do`,' systemctl is-active "$unit" >/dev/null 2>&1 && echo "active:$unit" || true',"done"].join(`
|
|
71
71
|
`)}function wm(D){return!D.split(`
|
|
@@ -79,7 +79,7 @@ Next steps:
|
|
|
79
79
|
`)}function Fa(D){let t=new Map;for(let m of D.split(`
|
|
80
80
|
`)){let A=m.trim();if(!A.startsWith("cert:"))continue;let[,_,z]=A.split(":");if(_&&z)t.set(_,z)}return t}function Zl(D,t){for(let[m,A]of D){if(A==="absent")continue;if(t.get(m)!==A)return!1}return!0}function Ql(D,t,m){let A=t.flatMap((_)=>[`${_}.crt`,`${_}.key`]);return["set -eu",`rm -f ${Vt(m)}`,`cd ${Vt(D)} 2>/dev/null || { echo "no certs dir at ${D}" >&2; exit 0; }`,`TS_CLOUD_CERTS="$(ls ${A.map(Vt).join(" ")} 2>/dev/null | tr '\\n' ' ' || true)"`,'[ -n "$TS_CLOUD_CERTS" ] || { echo "no certificates to carry"; exit 0; }',`tar czf ${Vt(m)} $TS_CLOUD_CERTS`].join(`
|
|
81
81
|
`)}function Xl(D,t){return["set -eu",`[ -s ${Vt(t)} ] || { echo "no certificate archive staged"; exit 0; }`,`mkdir -p ${Vt(D)}`,`tar xzf ${Vt(t)} -C ${Vt(D)}`,`chmod 600 ${Vt(D)}/*.key 2>/dev/null || true`,`rm -f ${Vt(t)}`].join(`
|
|
82
|
-
`)}async function ed(D,t){let{slug:m,siteName:A,appBase:_,from:z,to:U,targetAddress:Y}=D;if(z===U)throw Error(`'${A}' is already on ${U}.`);if(D.database&&!t.database)throw Error(`'${A}' uses the on-box database '${D.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(D.database&&t.database&&await t.database.targetHasData())throw Error(`${U} already has a database named '${D.database.name}' with tables in it. Restoring this site's dump over it would destroy that data. Rename or drop it on the target first, or point this project at a different database name.`);let X=[{id:"pause-workers",title:`Stop background work on ${z} so the snapshot is consistent`,satisfied:async()=>wm(await t.runOnSource(bm(m,A))),apply:async()=>{await t.runOnSource(vm(m,A))}},...t.database?[{id:"database-dump",title:`Dump the '${D.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}:${_}`,to:Ro(m,A)},satisfied:async()=>!1,apply:async()=>{await t.runOnSource(Sm(m,A,_))}},{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 '${D.database?.name}' on ${U}`,satisfied:async()=>!1,apply:()=>t.database.restore()}]:[],{id:"restore",title:`Unpack the site on ${U} and start it`,change:{from:Ro(m,A),to:`${U}:${_}`},satisfied:async()=>Am(await t.runOnTarget($m(m,A,_))),apply:async()=>{await t.runOnTarget(Cm(m,A,_))}}];if(D.port!=null){let{port:ne}=D;X.push({id:"health",title:`Wait for the site to answer on ${U} (127.0.0.1:${ne})`,satisfied:async()=>!1,apply:async()=>{await t.runOnTarget(Rm(ne,D.healthCheckPath??"/"))}})}return X.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:Y},satisfied:async()=>await t.publishedAddress()===Y,apply:async()=>{let ne=await t.cutoverDns();if(ne.length>0)throw Error(ne.join("; "))}},{id:"drain-source",title:`Stop the site on ${z}, leaving its files in place`,satisfied:async()=>Pm(await t.runOnSource(Im(m,A))),apply:async()=>{await t.runOnSource(km(m,A))}}),{operation:"site:move",target:A,steps:X}}async function lo(D,t,m){let A=await D.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 Po(D,t,m){let A=Bun.spawn(["scp","-P",String(D.sshPort),"-o","BatchMode=yes","-o","StrictHostKeyChecking=yes",t,m],{stdout:"pipe",stderr:"pipe"}),[_,z]=await Promise.all([A.exited,new Response(A.stderr).text()]);if(_!==0)throw Error(`scp failed: ${z.trim()||`exited ${_}`}`)}async function Tm(D,t){if(!t.to)throw Error("Name the target server with --to <server>.");let m=await je(),A=m.sites?.[D];if(!A)throw Error(`Site '${D}' is not in cloud.config.ts.`);let _=m.project.slug,z=y(process.cwd(),m);try{let Y=new De(z.store).list(z.project.id),X=(nt)=>{let zt=Y.find((Wt)=>Wt.id===nt||Wt.name===nt);if(!zt)throw Error(`Server ${nt} was not found. Run \`cloud server:list\`.`);if(zt.trustState!=="pinned")throw Error(`${zt.name} has no pinned host key. Run \`cloud server:validate ${zt.name}\` first.`);return zt},ne=X(t.to),re=Y.filter((nt)=>nt.id!==ne.id);if(!t.from&&re.length!==1)throw Error(`Name the source server with --from <server> — ${re.length} others are enrolled.`);let se=X(t.from??re[0].name),fe=new Fr,Ae=J(_,D),Pe=Ro(_,D),he=A.domain,Ie=m.infrastructure?.dns?.provider,Le=m.infrastructure?.compute?.proxy?.engine==="rpx"?m.infrastructure.compute.proxy:void 0,Re=m.infrastructure?.dns?.domain,Ee=(nt)=>Re&&nt.endsWith(Re)?Re:nt.split(".").slice(-2).join("."),Te=w(m),Ke=Te?.name&&Ai(Te)?{name:Te.name}:void 0,Be=`/tmp/ts-cloud-move-${_}-${D}.sql.gz`,_e=Jl(_,D),et=Le?.certsDir??Xr,Rt=[he,...A.aliases??[]].filter((nt)=>!!nt),Et=Te?.engine??"mysql",wt={runOnSource:(nt)=>lo(fe,se,nt),runOnTarget:(nt)=>lo(fe,ne,nt),archiveStaged:async()=>(await fe.exec(ne,`test -f ${Pe} && echo staged || true`)).stdout.includes("staged"),transferArchive:async()=>{let nt=`${process.cwd()}/.ts-cloud-move-${_}-${D}.tar.gz`;await Po(se,`${se.sshUser}@${se.endpoint}:${Pe}`,nt),await Po(ne,nt,`${ne.sshUser}@${ne.endpoint}:${Pe}`),await Bun.file(nt).delete().catch(()=>{})},targetRoutesSite:async()=>{if(!Le||!he)return!0;return(await fe.exec(ne,`grep -qF ${JSON.stringify(he)} /etc/rpx/sites.d/${_}.json 2>/dev/null && echo routed || true`)).stdout.includes("routed")},refreshTargetGateway:async()=>{if(!Le)throw Error(`This project does not run the rpx gateway, so the move cannot publish a route. Point ${ne.name}'s web server at ${Ae}/current yourself, then re-run to continue.`);await lo(fe,ne,
|
|
82
|
+
`)}async function ed(D,t){let{slug:m,siteName:A,appBase:_,from:z,to:U,targetAddress:Y}=D;if(z===U)throw Error(`'${A}' is already on ${U}.`);if(D.database&&!t.database)throw Error(`'${A}' uses the on-box database '${D.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(D.database&&t.database&&await t.database.targetHasData())throw Error(`${U} already has a database named '${D.database.name}' with tables in it. Restoring this site's dump over it would destroy that data. Rename or drop it on the target first, or point this project at a different database name.`);let X=[{id:"pause-workers",title:`Stop background work on ${z} so the snapshot is consistent`,satisfied:async()=>wm(await t.runOnSource(bm(m,A))),apply:async()=>{await t.runOnSource(vm(m,A))}},...t.database?[{id:"database-dump",title:`Dump the '${D.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}:${_}`,to:Ro(m,A)},satisfied:async()=>!1,apply:async()=>{await t.runOnSource(Sm(m,A,_))}},{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 '${D.database?.name}' on ${U}`,satisfied:async()=>!1,apply:()=>t.database.restore()}]:[],{id:"restore",title:`Unpack the site on ${U} and start it`,change:{from:Ro(m,A),to:`${U}:${_}`},satisfied:async()=>Am(await t.runOnTarget($m(m,A,_))),apply:async()=>{await t.runOnTarget(Cm(m,A,_))}}];if(D.port!=null){let{port:ne}=D;X.push({id:"health",title:`Wait for the site to answer on ${U} (127.0.0.1:${ne})`,satisfied:async()=>!1,apply:async()=>{await t.runOnTarget(Rm(ne,D.healthCheckPath??"/"))}})}return X.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:Y},satisfied:async()=>await t.publishedAddress()===Y,apply:async()=>{let ne=await t.cutoverDns();if(ne.length>0)throw Error(ne.join("; "))}},{id:"drain-source",title:`Stop the site on ${z}, leaving its files in place`,satisfied:async()=>Pm(await t.runOnSource(Im(m,A))),apply:async()=>{await t.runOnSource(km(m,A))}}),{operation:"site:move",target:A,steps:X}}async function lo(D,t,m){let A=await D.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 Po(D,t,m){let A=Bun.spawn(["scp","-P",String(D.sshPort),"-o","BatchMode=yes","-o","StrictHostKeyChecking=yes",t,m],{stdout:"pipe",stderr:"pipe"}),[_,z]=await Promise.all([A.exited,new Response(A.stderr).text()]);if(_!==0)throw Error(`scp failed: ${z.trim()||`exited ${_}`}`)}async function Tm(D,t){if(!t.to)throw Error("Name the target server with --to <server>.");let m=await je(),A=m.sites?.[D];if(!A)throw Error(`Site '${D}' is not in cloud.config.ts.`);let _=m.project.slug,z=y(process.cwd(),m);try{let Y=new De(z.store).list(z.project.id),X=(nt)=>{let zt=Y.find((Wt)=>Wt.id===nt||Wt.name===nt);if(!zt)throw Error(`Server ${nt} was not found. Run \`cloud server:list\`.`);if(zt.trustState!=="pinned")throw Error(`${zt.name} has no pinned host key. Run \`cloud server:validate ${zt.name}\` first.`);return zt},ne=X(t.to),re=Y.filter((nt)=>nt.id!==ne.id);if(!t.from&&re.length!==1)throw Error(`Name the source server with --from <server> — ${re.length} others are enrolled.`);let se=X(t.from??re[0].name),fe=new Fr,Ae=J(_,D),Pe=Ro(_,D),he=A.domain,Ie=m.infrastructure?.dns?.provider,Le=m.infrastructure?.compute?.proxy?.engine==="rpx"?m.infrastructure.compute.proxy:void 0,Re=m.infrastructure?.dns?.domain,Ee=(nt)=>Re&&nt.endsWith(Re)?Re:nt.split(".").slice(-2).join("."),Te=w(m),Ke=Te?.name&&Ai(Te)?{name:Te.name}:void 0,Be=`/tmp/ts-cloud-move-${_}-${D}.sql.gz`,_e=Jl(_,D),et=Le?.certsDir??Xr,Rt=[he,...A.aliases??[]].filter((nt)=>!!nt),Et=Te?.engine??"mysql",wt={runOnSource:(nt)=>lo(fe,se,nt),runOnTarget:(nt)=>lo(fe,ne,nt),archiveStaged:async()=>(await fe.exec(ne,`test -f ${Pe} && echo staged || true`)).stdout.includes("staged"),transferArchive:async()=>{let nt=`${process.cwd()}/.ts-cloud-move-${_}-${D}.tar.gz`;await Po(se,`${se.sshUser}@${se.endpoint}:${Pe}`,nt),await Po(ne,nt,`${ne.sshUser}@${ne.endpoint}:${Pe}`),await Bun.file(nt).delete().catch(()=>{})},targetRoutesSite:async()=>{if(!Le||!he)return!0;return(await fe.exec(ne,`grep -qF ${JSON.stringify(he)} /etc/rpx/sites.d/${_}.json 2>/dev/null && echo routed || true`)).stdout.includes("routed")},refreshTargetGateway:async()=>{if(!Le)throw Error(`This project does not run the rpx gateway, so the move cannot publish a route. Point ${ne.name}'s web server at ${Ae}/current yourself, then re-run to continue.`);await lo(fe,ne,Ge({config:Se(m.sites??{},{proxy:Le,slug:_,lan:Qi(m)}),slug:_,preserveManagementDashboardRoutes:!0}).join(`
|
|
83
83
|
`))},publishedAddress:async()=>{if(!he||!Ie)return ne.endpoint;let nt=pn(Ie);if(!nt)return;return await Ki(M(nt),Ee(he),he,ne.endpoint,"A")?ne.endpoint:void 0},...Ke?{database:{dump:async()=>{await lo(fe,se,["set -euo pipefail",'eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true',...es(Et,Ke.name,"/tmp",Te),`mv -f "$(ls -1t /tmp/${Ke.name}-*.sql.gz | head -1)" ${Be}`].join(`
|
|
84
84
|
`))},dumpStaged:async()=>(await fe.exec(ne,`test -s ${Be} && echo staged || true`)).stdout.includes("staged"),transferDump:async()=>{let nt=`${process.cwd()}/.ts-cloud-move-${_}-${D}.sql.gz`;await Po(se,`${se.sshUser}@${se.endpoint}:${Be}`,nt),await Po(ne,nt,`${ne.sshUser}@${ne.endpoint}:${Be}`),await Bun.file(nt).delete().catch(()=>{})},restore:async()=>{await lo(fe,ne,[...vt(Te,m.infrastructure?.compute?.managedServices??{}),...Ri(Te,{from:Be}),`rm -f ${Be}`].join(`
|
|
85
85
|
`))},targetHasData:async()=>{let nt=Et==="postgres"?`psql -tAc "select count(*) from information_schema.tables where table_schema='public'" -d ${Ke.name} 2>/dev/null || echo 0`:`mysql -N -B -e "select count(*) from information_schema.tables where table_schema='${Ke.name}'" 2>/dev/null || echo 0`,zt=await fe.exec(ne,`eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true
|
|
@@ -93,11 +93,11 @@ ${Y.join(`
|
|
|
93
93
|
`)}
|
|
94
94
|
}`)}return A.join(`
|
|
95
95
|
|
|
96
|
-
`)}normalizeMountPath(D){let t=D?.mountPath||D?.path;if(!t||t==="/")return;let m=`/${t}`.replace(/\/+/g,"/").replace(/\/$/,"");return m==="/"?void 0:m}storageBucketLogicalId(D,t,m){return`${D}-${t}-s3-${m}`.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((A)=>A.charAt(0).toUpperCase()+A.slice(1)).join("")}pathMountRewriteFunctionCode(D,t="directory"){return`function handler(event) { var request = event.request; var prefix = ${JSON.stringify(D)}; 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 D=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),_=!!this.mergedConfig.infrastructure?.compute;if(!!((this.mergedConfig.mode||"server")==="serverless"&&this.mergedConfig.infrastructure?.containers&&Object.keys(this.mergedConfig.infrastructure.containers).length>0))this.generateNetworkInfrastructure(D,t),this.generateContainerInfrastructure(D,t);if(m)this.generateServerless(D,t);if(A)this.generateServer(D,t);if(_)this.generateComputeApp(D,t);if(this.mergedConfig.infrastructure?.jumpBox)this.generateJumpBox(D,t);if(this.storageFailoverReplicas=[],this.generateSharedInfrastructure(D,t),this.storageFailoverReplicas.length>0)this.builder.addMetadata(zo,this.storageFailoverReplicas);if(this.config.tags)this.applyGlobalTags(this.config.tags);return this}getStorageFailoverReplicas(){return[...this.storageFailoverReplicas]}applyGlobalTags(D){}generateNetworkInfrastructure(D,t){if(this.builder.hasResource("VPC"))return;let A=this.mergedConfig.infrastructure?.network?.cidr||"10.0.0.0/16",{vpc:_,logicalId:z}=L.createVpc({slug:D,environment:t,cidr:A,enableDnsHostnames:!0,enableDnsSupport:!0});this.builder.addResource("VPC",_);let U=`${D}${t}IGW`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(U,{Type:"AWS::EC2::InternetGateway",Properties:{Tags:[{Key:"Name",Value:`${D}-${t}-igw`},{Key:"Environment",Value:t}]}});let Y=`${D}${t}IGWAttach`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Y,{Type:"AWS::EC2::VPCGatewayAttachment",Properties:{VpcId:{Ref:"VPC"},InternetGatewayId:{Ref:U}}});let X=`${D}${t}PublicRT`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(X,{Type:"AWS::EC2::RouteTable",Properties:{VpcId:{Ref:"VPC"},Tags:[{Key:"Name",Value:`${D}-${t}-public-rt`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${D}${t}PublicRoute`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::EC2::Route",Properties:{RouteTableId:{Ref:X},DestinationCidrBlock:"0.0.0.0/0",GatewayId:{Ref:U}},DependsOn:Y});let ne=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",re=["a","b"];for(let se=0;se<2;se++){let fe=`PublicSubnet${se+1}`;this.builder.addResource(fe,{Type:"AWS::EC2::Subnet",Properties:{VpcId:{Ref:"VPC"},CidrBlock:`10.0.${se}.0/24`,AvailabilityZone:`${ne}${re[se]}`,MapPublicIpOnLaunch:!0,Tags:[{Key:"Name",Value:`${D}-${t}-public-${re[se]}`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${fe}RTAssoc`,{Type:"AWS::EC2::SubnetRouteTableAssociation",Properties:{SubnetId:{Ref:fe},RouteTableId:{Ref:X}}})}}generateContainerInfrastructure(D,t){let m=this.mergedConfig.infrastructure?.containers;if(!m)return;let A=this.mergedConfig.infrastructure?.loadBalancer,_=this.mergedConfig.infrastructure?.ssl,z=this.mergedConfig.infrastructure?.dns,U;if(_?.enabled&&_.domains?.length)if(_.certificateArn);else{let re=_.domains[0],se=_.domains.slice(1).map((Pe)=>{if(Pe.includes(".")&&Pe.endsWith(re))return Pe.replace(`.${re}`,"");return Pe}),{certificate:fe,logicalId:Ae}=j.createCertificate({domain:re,subdomains:se,slug:D,environment:t,validationMethod:"DNS",hostedZoneId:z?.hostedZoneId});U=Ae,this.builder.addResource(Ae,fe)}let Y="ECSCluster";this.builder.addResource(Y,{Type:"AWS::ECS::Cluster",Properties:{ClusterName:`${D}-${t}`,ClusterSettings:[{Name:"containerInsights",Value:"enabled"}],Tags:[{Key:"Name",Value:`${D}-${t}`},{Key:"Environment",Value:t}]}});let X=`${D}${t}ALBSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(X,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ALB security group for ${D}-${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:`${D}-${t}-alb-sg`},{Key:"Environment",Value:t}]}});let ne=`${D}${t}ECSSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");for(let[re,se]of Object.entries(m)){let fe=se.port||3000;this.builder.addResource(ne,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ECS tasks security group for ${D}-${t}`,VpcId:{Ref:"VPC"},SecurityGroupIngress:[{IpProtocol:"tcp",FromPort:fe,ToPort:fe,SourceSecurityGroupId:{Ref:X},Description:"Allow traffic from ALB"}],SecurityGroupEgress:[{IpProtocol:"-1",CidrIp:"0.0.0.0/0",Description:"Allow all outbound"}],Tags:[{Key:"Name",Value:`${D}-${t}-ecs-sg`},{Key:"Environment",Value:t}]}});let Ae=`${D}${t}TaskExecRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ae,{Type:"AWS::IAM::Role",Properties:{RoleName:`${D}-${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 Pe=`${D}${t}TaskRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Pe,{Type:"AWS::IAM::Role",Properties:{RoleName:`${D}-${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 he=`${D}${t}${re}LogGroup`.replace(/[^a-zA-Z0-9]/g,""),Ie=`/ecs/${D}-${t}-${re}`;this.builder.addResource(he,{Type:"AWS::Logs::LogGroup",Properties:{LogGroupName:Ie,RetentionInDays:30}});let Le=`${D}${t}${re}TaskDef`.replace(/[^a-zA-Z0-9]/g,""),Re=String(se.cpu||512),Ee=String(se.memory||1024);this.builder.addResource(Le,{Type:"AWS::ECS::TaskDefinition",Properties:{Family:`${D}-${t}-${re}`,NetworkMode:"awsvpc",RequiresCompatibilities:["FARGATE"],Cpu:Re,Memory:Ee,ExecutionRoleArn:{"Fn::GetAtt":[Ae,"Arn"]},TaskRoleArn:{"Fn::GetAtt":[Pe,"Arn"]},ContainerDefinitions:[{Name:re,Image:{"Fn::Sub":`\${AWS::AccountId}.dkr.ecr.\${AWS::Region}.amazonaws.com/${D}:latest`},Essential:!0,PortMappings:[{ContainerPort:fe,Protocol:"tcp"}],LogConfiguration:{LogDriver:"awslogs",Options:{"awslogs-group":Ie,"awslogs-region":{Ref:"AWS::Region"},"awslogs-stream-prefix":re}},HealthCheck:{Command:["CMD-SHELL",`curl -f http://localhost:${fe}${se.healthCheck||"/health"} || exit 1`],Interval:30,Timeout:5,Retries:3,StartPeriod:60}}],Tags:[{Key:"Name",Value:`${D}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[Ae,Pe,he]});let Te=`${D}${t}ALB`.replace(/[^a-zA-Z0-9]/g,"");if(A?.enabled!==!1){this.builder.addResource(Te,{Type:"AWS::ElasticLoadBalancingV2::LoadBalancer",Properties:{Name:`${D}-${t}-alb`,Scheme:"internet-facing",Type:"application",Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],SecurityGroups:[{Ref:X}],Tags:[{Key:"Name",Value:`${D}-${t}-alb`},{Key:"Environment",Value:t}]}});let Ke=`${D}${t}TargetGroup`.replace(/[^a-zA-Z0-9]/g,""),Be=A?.healthCheck?.path||se.healthCheck||"/health";this.builder.addResource(Ke,{Type:"AWS::ElasticLoadBalancingV2::TargetGroup",Properties:{Name:`${D}-${t}-tg`,Port:fe,Protocol:"HTTP",VpcId:{Ref:"VPC"},TargetType:"ip",HealthCheckPath:Be,HealthCheckIntervalSeconds:A?.healthCheck?.interval||30,HealthyThresholdCount:A?.healthCheck?.healthyThreshold||2,UnhealthyThresholdCount:A?.healthCheck?.unhealthyThreshold||5,HealthCheckTimeoutSeconds:10,Tags:[{Key:"Name",Value:`${D}-${t}-tg`},{Key:"Environment",Value:t}]}});let _e=`${D}${t}HTTPListener`.replace(/[^a-zA-Z0-9]/g,"");if(_?.enabled&&_?.redirectHttp)this.builder.addResource(_e,{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(_e,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:80,Protocol:"HTTP",DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Ke}}]}});if(_?.enabled){let wt=_.certificateArn||(U?{Ref:U}:void 0);if(wt){let Pt=`${D}${t}HTTPSListener`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Pt,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:443,Protocol:"HTTPS",Certificates:[{CertificateArn:wt}],DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Ke}}],SslPolicy:"ELBSecurityPolicy-TLS13-1-2-2021-06"}})}}let et=`${D}${t}${re}Service`.replace(/[^a-zA-Z0-9]/g,""),Rt=se.desiredCount||1;this.builder.addResource(et,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${D}-${t}-${re}`,Cluster:{Ref:Y},TaskDefinition:{Ref:Le},DesiredCount:Rt,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:ne}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},LoadBalancers:[{ContainerName:re,ContainerPort:fe,TargetGroupArn:{Ref:Ke}}],HealthCheckGracePeriodSeconds:120,Tags:[{Key:"Name",Value:`${D}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[Le,_e,Ke]});let Et=se.autoScaling;if(Et){let wt=`${D}${t}${re}ScalableTarget`.replace(/[^a-zA-Z0-9]/g,"");if(this.builder.addResource(wt,{Type:"AWS::ApplicationAutoScaling::ScalableTarget",Properties:{MaxCapacity:Et.max||10,MinCapacity:Et.min||1,ResourceId:{"Fn::Sub":`service/\${${Y}}/${D}-${t}-${re}`},ScalableDimension:"ecs:service:DesiredCount",ServiceNamespace:"ecs",RoleARN:{"Fn::Sub":"arn:aws:iam::${AWS::AccountId}:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService"}},DependsOn:et}),Et.targetCpuUtilization)this.builder.addResource(`${D}${t}${re}CPUScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${D}-${t}-${re}-cpu-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:wt},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageCPUUtilization"},TargetValue:Et.targetCpuUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}});if(Et.targetMemoryUtilization)this.builder.addResource(`${D}${t}${re}MemoryScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${D}-${t}-${re}-memory-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:wt},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageMemoryUtilization"},TargetValue:Et.targetMemoryUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}})}if(z?.domain&&z?.hostedZoneId)this.builder.addResource(`${D}${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":[Y,"Arn"]},Export:{Name:{"Fn::Sub":"${AWS::StackName}-ecs-cluster-arn"}}}),this.builder.addOutput("ECSServiceName",{Description:"ECS Service Name",Value:`${D}-${t}-${re}`,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 Ke=`${D}${t}${re}Service`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ke,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${D}-${t}-${re}`,Cluster:{Ref:Y},TaskDefinition:{Ref:Le},DesiredCount:se.desiredCount||1,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:ne}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},Tags:[{Key:"Name",Value:`${D}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[Le]})}break}}generateServerless(D,t){if(this.mergedConfig.infrastructure?.functions)for(let[m,A]of Object.entries(this.mergedConfig.infrastructure.functions)){if(!this.shouldDeploy(A))continue;let{role:_,logicalId:z}=we.createRole({slug:D,environment:t,roleName:`${D}-${t}-${m}-role`,servicePrincipal:"lambda.amazonaws.com",managedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]});this.builder.addResource(z,_);let{lambdaFunction:U,logicalId:Y}=b.createLambdaFunction({slug:D,environment:t,functionName:`${D}-${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(Y,U)}if(this.mergedConfig.infrastructure?.api){let{restApi:m,logicalId:A}=qo.createRestApi({slug:D,environment:t,apiName:`${D}-${t}-api`});this.builder.addResource(A,m)}}generateServer(D,t){if(!this.mergedConfig.infrastructure?.servers)return;let m=this.mergedConfig.infrastructure.servers,A=this.mergedConfig.infrastructure?.compute,_=this.mergedConfig.infrastructure?.ssl;if(Object.values(m).some((U)=>!U.privateNetwork||U.privateNetwork==="create"))this.generateNetworkInfrastructure(D,t);for(let[U,Y]of Object.entries(m)){let X=Y.instanceType||Y.size||A?.size,re=(X?b.InstanceSize.specs[X]:void 0)?.instanceType||X||"t3.micro",se=Y.type||"app",fe=Y.userData||Y.startupScript;if(!fe)fe=b.UserData.generateAppServerScript({runtime:"bun",runtimeVersion:Y.bunVersion||"latest",webServer:"none",domain:Y.domain,enableSsl:!!_?.enabled,sslEmail:_?.letsEncrypt?.email,installRedis:se==="cache",installDatabaseClients:!!Y.database});let Ae=Y.privateNetwork&&Y.privateNetwork!=="create"?Y.privateNetwork:{Ref:"VPC"},Pe=Y.subnet||{Ref:"PublicSubnet1"},Ie=!!this.mergedConfig.infrastructure?.email?.server?.enabled,Le=[22,80,443,this.resolveApiOriginPort()];if(Ie)Le.push(25,465,587,143,993);let Re=b.createServerModeStack({slug:`${D}-${U}`,environment:t,vpcId:Ae,subnetId:Pe,instanceType:re,keyName:Y.keyName||`${D}-${t}`,domain:Y.domain,userData:fe,volumeSize:Y.diskSize||20,imageId:Y.image,allowedPorts:Le});for(let[Ee,Te]of Object.entries(Re.resources))this.builder.addResource(Ee,Te);this.serverEipLogicalIds.set(U,Re.outputs.eipLogicalId),this.serverEipLogicalIds.set(`${U}Instance`,Re.outputs.instanceLogicalId),this.builder.addOutput(`${U}InstanceId`,{Value:{Ref:Re.outputs.instanceLogicalId},Description:`Instance ID for ${U} server`}),this.builder.addOutput(`${U}PublicIp`,{Value:{Ref:Re.outputs.eipLogicalId},Description:`Public IP for ${U} server`})}}generateComputeApp(D,t){let m=this.mergedConfig.infrastructure?.compute;if(!m)return;let A=this.mergedConfig.sites||{},_=Object.entries(A),U=this.mergedConfig.infrastructure?.dns?.domain,Y=this.mergedConfig.infrastructure?.database,X=m.size,re=(X?b.InstanceSize.specs[X]:void 0)?.instanceType||X||"t3.micro";if(!this.builder.hasResource("VPC"))this.generateNetworkInfrastructure(D,t);let se=this.buildCaddyfile(_),fe=b.UserData.generateBunAppScript({runtime:m.runtime||"bun",runtimeVersion:m.runtimeVersion||"latest",systemPackages:m.systemPackages,database:Y,caddyfile:se}),Ae=Ji(m.appUpdates),Pe=Ae.length>0?`${fe}
|
|
96
|
+
`)}normalizeMountPath(D){let t=D?.mountPath||D?.path;if(!t||t==="/")return;let m=`/${t}`.replace(/\/+/g,"/").replace(/\/$/,"");return m==="/"?void 0:m}storageBucketLogicalId(D,t,m){return`${D}-${t}-s3-${m}`.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((A)=>A.charAt(0).toUpperCase()+A.slice(1)).join("")}pathMountRewriteFunctionCode(D,t="directory"){return`function handler(event) { var request = event.request; var prefix = ${JSON.stringify(D)}; 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 D=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),_=!!this.mergedConfig.infrastructure?.compute;if(!!((this.mergedConfig.mode||"server")==="serverless"&&this.mergedConfig.infrastructure?.containers&&Object.keys(this.mergedConfig.infrastructure.containers).length>0))this.generateNetworkInfrastructure(D,t),this.generateContainerInfrastructure(D,t);if(m)this.generateServerless(D,t);if(A)this.generateServer(D,t);if(_)this.generateComputeApp(D,t);if(this.mergedConfig.infrastructure?.jumpBox)this.generateJumpBox(D,t);if(this.storageFailoverReplicas=[],this.generateSharedInfrastructure(D,t),this.storageFailoverReplicas.length>0)this.builder.addMetadata(zo,this.storageFailoverReplicas);if(this.config.tags)this.applyGlobalTags(this.config.tags);return this}getStorageFailoverReplicas(){return[...this.storageFailoverReplicas]}applyGlobalTags(D){}generateNetworkInfrastructure(D,t){if(this.builder.hasResource("VPC"))return;let A=this.mergedConfig.infrastructure?.network?.cidr||"10.0.0.0/16",{vpc:_,logicalId:z}=L.createVpc({slug:D,environment:t,cidr:A,enableDnsHostnames:!0,enableDnsSupport:!0});this.builder.addResource("VPC",_);let U=`${D}${t}IGW`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(U,{Type:"AWS::EC2::InternetGateway",Properties:{Tags:[{Key:"Name",Value:`${D}-${t}-igw`},{Key:"Environment",Value:t}]}});let Y=`${D}${t}IGWAttach`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Y,{Type:"AWS::EC2::VPCGatewayAttachment",Properties:{VpcId:{Ref:"VPC"},InternetGatewayId:{Ref:U}}});let X=`${D}${t}PublicRT`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(X,{Type:"AWS::EC2::RouteTable",Properties:{VpcId:{Ref:"VPC"},Tags:[{Key:"Name",Value:`${D}-${t}-public-rt`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${D}${t}PublicRoute`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::EC2::Route",Properties:{RouteTableId:{Ref:X},DestinationCidrBlock:"0.0.0.0/0",GatewayId:{Ref:U}},DependsOn:Y});let ne=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",re=["a","b"];for(let se=0;se<2;se++){let fe=`PublicSubnet${se+1}`;this.builder.addResource(fe,{Type:"AWS::EC2::Subnet",Properties:{VpcId:{Ref:"VPC"},CidrBlock:`10.0.${se}.0/24`,AvailabilityZone:`${ne}${re[se]}`,MapPublicIpOnLaunch:!0,Tags:[{Key:"Name",Value:`${D}-${t}-public-${re[se]}`},{Key:"Environment",Value:t}]}}),this.builder.addResource(`${fe}RTAssoc`,{Type:"AWS::EC2::SubnetRouteTableAssociation",Properties:{SubnetId:{Ref:fe},RouteTableId:{Ref:X}}})}}generateContainerInfrastructure(D,t){let m=this.mergedConfig.infrastructure?.containers;if(!m)return;let A=this.mergedConfig.infrastructure?.loadBalancer,_=this.mergedConfig.infrastructure?.ssl,z=this.mergedConfig.infrastructure?.dns,U;if(_?.enabled&&_.domains?.length)if(_.certificateArn);else{let re=_.domains[0],se=_.domains.slice(1).map((Pe)=>{if(Pe.includes(".")&&Pe.endsWith(re))return Pe.replace(`.${re}`,"");return Pe}),{certificate:fe,logicalId:Ae}=j.createCertificate({domain:re,subdomains:se,slug:D,environment:t,validationMethod:"DNS",hostedZoneId:z?.hostedZoneId});U=Ae,this.builder.addResource(Ae,fe)}let Y="ECSCluster";this.builder.addResource(Y,{Type:"AWS::ECS::Cluster",Properties:{ClusterName:`${D}-${t}`,ClusterSettings:[{Name:"containerInsights",Value:"enabled"}],Tags:[{Key:"Name",Value:`${D}-${t}`},{Key:"Environment",Value:t}]}});let X=`${D}${t}ALBSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(X,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ALB security group for ${D}-${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:`${D}-${t}-alb-sg`},{Key:"Environment",Value:t}]}});let ne=`${D}${t}ECSSecurityGroup`.replace(/[^a-zA-Z0-9]/g,"");for(let[re,se]of Object.entries(m)){let fe=se.port||3000;this.builder.addResource(ne,{Type:"AWS::EC2::SecurityGroup",Properties:{GroupDescription:`ECS tasks security group for ${D}-${t}`,VpcId:{Ref:"VPC"},SecurityGroupIngress:[{IpProtocol:"tcp",FromPort:fe,ToPort:fe,SourceSecurityGroupId:{Ref:X},Description:"Allow traffic from ALB"}],SecurityGroupEgress:[{IpProtocol:"-1",CidrIp:"0.0.0.0/0",Description:"Allow all outbound"}],Tags:[{Key:"Name",Value:`${D}-${t}-ecs-sg`},{Key:"Environment",Value:t}]}});let Ae=`${D}${t}TaskExecRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ae,{Type:"AWS::IAM::Role",Properties:{RoleName:`${D}-${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 Pe=`${D}${t}TaskRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Pe,{Type:"AWS::IAM::Role",Properties:{RoleName:`${D}-${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 he=`${D}${t}${re}LogGroup`.replace(/[^a-zA-Z0-9]/g,""),Ie=`/ecs/${D}-${t}-${re}`;this.builder.addResource(he,{Type:"AWS::Logs::LogGroup",Properties:{LogGroupName:Ie,RetentionInDays:30}});let Le=`${D}${t}${re}TaskDef`.replace(/[^a-zA-Z0-9]/g,""),Re=String(se.cpu||512),Ee=String(se.memory||1024);this.builder.addResource(Le,{Type:"AWS::ECS::TaskDefinition",Properties:{Family:`${D}-${t}-${re}`,NetworkMode:"awsvpc",RequiresCompatibilities:["FARGATE"],Cpu:Re,Memory:Ee,ExecutionRoleArn:{"Fn::GetAtt":[Ae,"Arn"]},TaskRoleArn:{"Fn::GetAtt":[Pe,"Arn"]},ContainerDefinitions:[{Name:re,Image:{"Fn::Sub":`\${AWS::AccountId}.dkr.ecr.\${AWS::Region}.amazonaws.com/${D}:latest`},Essential:!0,PortMappings:[{ContainerPort:fe,Protocol:"tcp"}],LogConfiguration:{LogDriver:"awslogs",Options:{"awslogs-group":Ie,"awslogs-region":{Ref:"AWS::Region"},"awslogs-stream-prefix":re}},HealthCheck:{Command:["CMD-SHELL",`curl -f http://localhost:${fe}${se.healthCheck||"/health"} || exit 1`],Interval:30,Timeout:5,Retries:3,StartPeriod:60}}],Tags:[{Key:"Name",Value:`${D}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[Ae,Pe,he]});let Te=`${D}${t}ALB`.replace(/[^a-zA-Z0-9]/g,"");if(A?.enabled!==!1){this.builder.addResource(Te,{Type:"AWS::ElasticLoadBalancingV2::LoadBalancer",Properties:{Name:`${D}-${t}-alb`,Scheme:"internet-facing",Type:"application",Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],SecurityGroups:[{Ref:X}],Tags:[{Key:"Name",Value:`${D}-${t}-alb`},{Key:"Environment",Value:t}]}});let Ke=`${D}${t}TargetGroup`.replace(/[^a-zA-Z0-9]/g,""),Be=A?.healthCheck?.path||se.healthCheck||"/health";this.builder.addResource(Ke,{Type:"AWS::ElasticLoadBalancingV2::TargetGroup",Properties:{Name:`${D}-${t}-tg`,Port:fe,Protocol:"HTTP",VpcId:{Ref:"VPC"},TargetType:"ip",HealthCheckPath:Be,HealthCheckIntervalSeconds:A?.healthCheck?.interval||30,HealthyThresholdCount:A?.healthCheck?.healthyThreshold||2,UnhealthyThresholdCount:A?.healthCheck?.unhealthyThreshold||5,HealthCheckTimeoutSeconds:10,Tags:[{Key:"Name",Value:`${D}-${t}-tg`},{Key:"Environment",Value:t}]}});let _e=`${D}${t}HTTPListener`.replace(/[^a-zA-Z0-9]/g,"");if(_?.enabled&&_?.redirectHttp)this.builder.addResource(_e,{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(_e,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:80,Protocol:"HTTP",DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Ke}}]}});if(_?.enabled){let wt=_.certificateArn||(U?{Ref:U}:void 0);if(wt){let Pt=`${D}${t}HTTPSListener`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Pt,{Type:"AWS::ElasticLoadBalancingV2::Listener",Properties:{LoadBalancerArn:{Ref:Te},Port:443,Protocol:"HTTPS",Certificates:[{CertificateArn:wt}],DefaultActions:[{Type:"forward",TargetGroupArn:{Ref:Ke}}],SslPolicy:"ELBSecurityPolicy-TLS13-1-2-2021-06"}})}}let et=`${D}${t}${re}Service`.replace(/[^a-zA-Z0-9]/g,""),Rt=se.desiredCount||1;this.builder.addResource(et,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${D}-${t}-${re}`,Cluster:{Ref:Y},TaskDefinition:{Ref:Le},DesiredCount:Rt,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:ne}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},LoadBalancers:[{ContainerName:re,ContainerPort:fe,TargetGroupArn:{Ref:Ke}}],HealthCheckGracePeriodSeconds:120,Tags:[{Key:"Name",Value:`${D}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[Le,_e,Ke]});let Et=se.autoScaling;if(Et){let wt=`${D}${t}${re}ScalableTarget`.replace(/[^a-zA-Z0-9]/g,"");if(this.builder.addResource(wt,{Type:"AWS::ApplicationAutoScaling::ScalableTarget",Properties:{MaxCapacity:Et.max||10,MinCapacity:Et.min||1,ResourceId:{"Fn::Sub":`service/\${${Y}}/${D}-${t}-${re}`},ScalableDimension:"ecs:service:DesiredCount",ServiceNamespace:"ecs",RoleARN:{"Fn::Sub":"arn:aws:iam::${AWS::AccountId}:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService"}},DependsOn:et}),Et.targetCpuUtilization)this.builder.addResource(`${D}${t}${re}CPUScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${D}-${t}-${re}-cpu-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:wt},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageCPUUtilization"},TargetValue:Et.targetCpuUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}});if(Et.targetMemoryUtilization)this.builder.addResource(`${D}${t}${re}MemoryScaling`.replace(/[^a-zA-Z0-9]/g,""),{Type:"AWS::ApplicationAutoScaling::ScalingPolicy",Properties:{PolicyName:`${D}-${t}-${re}-memory-scaling`,PolicyType:"TargetTrackingScaling",ScalingTargetId:{Ref:wt},TargetTrackingScalingPolicyConfiguration:{PredefinedMetricSpecification:{PredefinedMetricType:"ECSServiceAverageMemoryUtilization"},TargetValue:Et.targetMemoryUtilization,ScaleInCooldown:300,ScaleOutCooldown:60}}})}if(z?.domain&&z?.hostedZoneId)this.builder.addResource(`${D}${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":[Y,"Arn"]},Export:{Name:{"Fn::Sub":"${AWS::StackName}-ecs-cluster-arn"}}}),this.builder.addOutput("ECSServiceName",{Description:"ECS Service Name",Value:`${D}-${t}-${re}`,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 Ke=`${D}${t}${re}Service`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Ke,{Type:"AWS::ECS::Service",Properties:{ServiceName:`${D}-${t}-${re}`,Cluster:{Ref:Y},TaskDefinition:{Ref:Le},DesiredCount:se.desiredCount||1,LaunchType:"FARGATE",NetworkConfiguration:{AwsvpcConfiguration:{AssignPublicIp:"ENABLED",SecurityGroups:[{Ref:ne}],Subnets:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]}},Tags:[{Key:"Name",Value:`${D}-${t}-${re}`},{Key:"Environment",Value:t}]},DependsOn:[Le]})}break}}generateServerless(D,t){if(this.mergedConfig.infrastructure?.functions)for(let[m,A]of Object.entries(this.mergedConfig.infrastructure.functions)){if(!this.shouldDeploy(A))continue;let{role:_,logicalId:z}=we.createRole({slug:D,environment:t,roleName:`${D}-${t}-${m}-role`,servicePrincipal:"lambda.amazonaws.com",managedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]});this.builder.addResource(z,_);let{lambdaFunction:U,logicalId:Y}=b.createLambdaFunction({slug:D,environment:t,functionName:`${D}-${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(Y,U)}if(this.mergedConfig.infrastructure?.api){let{restApi:m,logicalId:A}=Go.createRestApi({slug:D,environment:t,apiName:`${D}-${t}-api`});this.builder.addResource(A,m)}}generateServer(D,t){if(!this.mergedConfig.infrastructure?.servers)return;let m=this.mergedConfig.infrastructure.servers,A=this.mergedConfig.infrastructure?.compute,_=this.mergedConfig.infrastructure?.ssl;if(Object.values(m).some((U)=>!U.privateNetwork||U.privateNetwork==="create"))this.generateNetworkInfrastructure(D,t);for(let[U,Y]of Object.entries(m)){let X=Y.instanceType||Y.size||A?.size,re=(X?b.InstanceSize.specs[X]:void 0)?.instanceType||X||"t3.micro",se=Y.type||"app",fe=Y.userData||Y.startupScript;if(!fe)fe=b.UserData.generateAppServerScript({runtime:"bun",runtimeVersion:Y.bunVersion||"latest",webServer:"none",domain:Y.domain,enableSsl:!!_?.enabled,sslEmail:_?.letsEncrypt?.email,installRedis:se==="cache",installDatabaseClients:!!Y.database});let Ae=Y.privateNetwork&&Y.privateNetwork!=="create"?Y.privateNetwork:{Ref:"VPC"},Pe=Y.subnet||{Ref:"PublicSubnet1"},Ie=!!this.mergedConfig.infrastructure?.email?.server?.enabled,Le=[22,80,443,this.resolveApiOriginPort()];if(Ie)Le.push(25,465,587,143,993);let Re=b.createServerModeStack({slug:`${D}-${U}`,environment:t,vpcId:Ae,subnetId:Pe,instanceType:re,keyName:Y.keyName||`${D}-${t}`,domain:Y.domain,userData:fe,volumeSize:Y.diskSize||20,imageId:Y.image,allowedPorts:Le});for(let[Ee,Te]of Object.entries(Re.resources))this.builder.addResource(Ee,Te);this.serverEipLogicalIds.set(U,Re.outputs.eipLogicalId),this.serverEipLogicalIds.set(`${U}Instance`,Re.outputs.instanceLogicalId),this.builder.addOutput(`${U}InstanceId`,{Value:{Ref:Re.outputs.instanceLogicalId},Description:`Instance ID for ${U} server`}),this.builder.addOutput(`${U}PublicIp`,{Value:{Ref:Re.outputs.eipLogicalId},Description:`Public IP for ${U} server`})}}generateComputeApp(D,t){let m=this.mergedConfig.infrastructure?.compute;if(!m)return;let A=this.mergedConfig.sites||{},_=Object.entries(A),U=this.mergedConfig.infrastructure?.dns?.domain,Y=this.mergedConfig.infrastructure?.database,X=m.size,re=(X?b.InstanceSize.specs[X]:void 0)?.instanceType||X||"t3.micro";if(!this.builder.hasResource("VPC"))this.generateNetworkInfrastructure(D,t);let se=this.buildCaddyfile(_),fe=b.UserData.generateBunAppScript({runtime:m.runtime||"bun",runtimeVersion:m.runtimeVersion||"latest",systemPackages:m.systemPackages,database:Y,caddyfile:se}),Ae=Ji(m.appUpdates),Pe=Ae.length>0?`${fe}
|
|
97
97
|
${Ae.join(`
|
|
98
98
|
`)}
|
|
99
99
|
`:fe,he=_.map(([,Be])=>Be.port).filter((Be)=>typeof Be==="number"&&![80,443].includes(Be)),Ie=this.resolveApiOriginPort(),Le=`${D}${t}DeployBucket`.replace(/[^a-zA-Z0-9]/g,""),Re=`${D}-${t}-deploy`;this.builder.addResource(Le,{Type:"AWS::S3::Bucket",Properties:{BucketName:Re,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:D},{Key:"Environment",Value:t},{Key:"ManagedBy",Value:"ts-cloud"}]}});let Ee=b.createServerModeStack({slug:`${D}-app`,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},instanceType:re,keyName:m.allowSsh?`${D}-${t}`:void 0,domain:U,userData:Pe,volumeSize:m.disk?.size||20,imageId:m.image,allowedPorts:[...m.allowSsh?[22]:[],80,443,Ie,...he]}),Te=Ee.resources[Ee.outputs.instanceLogicalId];if(Te?.Properties){let Be=Te.Properties.Tags||[];Te.Properties.Tags=[...Be,{Key:"Project",Value:D},{Key:"Environment",Value:t},{Key:"Role",Value:"app"},{Key:"ManagedBy",Value:"ts-cloud"}]}let Ke=Ee.resources[Ee.outputs.roleLogicalId];if(Ke?.Properties)Ke.Properties.Policies=Ke.Properties.Policies||[],Ke.Properties.Policies.push({PolicyName:"DeployBucketRead",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["s3:GetObject","s3:ListBucket"],Resource:[`arn:aws:s3:::${Re}`,`arn:aws:s3:::${Re}/*`]}]}});for(let[Be,_e]of Object.entries(Ee.resources))this.builder.addResource(Be,_e);this.builder.addOutput("deployBucketName",{Value:{Ref:Le},Description:"S3 bucket where release tarballs are uploaded"}),this.builder.addOutput("appInstanceId",{Value:{Ref:Ee.outputs.instanceLogicalId},Description:"EC2 instance ID for the app server"}),this.builder.addOutput("appPublicIp",{Value:{Ref:Ee.outputs.eipLogicalId},Description:"Public IP for the app server"}),this.serverEipLogicalIds.set("app",Ee.outputs.eipLogicalId),this.serverEipLogicalIds.set("appInstance",Ee.outputs.instanceLogicalId)}jumpBoxFileSystemId(D,t,m){let A=Object.keys(this.mergedConfig.infrastructure?.fileSystem??{});if(typeof m==="string")return A.includes(m)?{Ref:this.fileSystemLogicalId(D,t,m)}:m;if(A.length===1)return{Ref:this.fileSystemLogicalId(D,t,A[0])};throw Error(A.length===0?"infrastructure.jumpBox.mountEfs is true, but infrastructure.fileSystem declares no file system. Declare one, or set mountEfs to an EFS id.":`infrastructure.jumpBox.mountEfs is true, but infrastructure.fileSystem declares ${A.length} (${A.join(", ")}). Set mountEfs to the name of the one to mount.`)}generateJumpBox(D,t){let m=this.mergedConfig.infrastructure?.jumpBox;if(!m)return;let A=m===!0?{}:m;if(A.enabled===!1)return;this.generateNetworkInfrastructure(D,t);let _=A.size||"micro",U=b.InstanceSize.specs[_]?.instanceType||_||"t3.micro",Y=b.createJumpBox({slug:D,environment:t,vpcId:{Ref:"VPC"},subnetId:{Ref:"PublicSubnet1"},keyName:A.keyName,instanceType:U,allowedCidrs:A.allowedCidrs,mountEfs:A.mountEfs?{fileSystemId:this.jumpBoxFileSystemId(D,t,A.mountEfs),mountPath:A.mountPath||"/mnt/efs"}:void 0,databaseTools:A.databaseTools});for(let[X,ne]of Object.entries(Y.resources))this.builder.addResource(X,ne);this.builder.addOutput("JumpBoxInstanceId",{Value:{Ref:Y.instanceLogicalId},Description:"The ID of the JumpBox EC2 instance (use with SSM Session Manager or SSH)"})}generateSharedInfrastructure(D,t){let m=this.mergedConfig.infrastructure?.ssl,A=this.mergedConfig.infrastructure?.dns,_=A?.domain,z=A?.hostedZoneId,U=[],Y,X;if(m?.certificateArn)X=m.certificateArn;else if(m?.enabled&&_&&z){let he=m.domains||[_],Ie=he[0],Le=he.slice(1).map((Te)=>{if(Te.includes(".")&&Te.endsWith(Ie))return Te.replace(`.${Ie}`,"");return Te}),{certificate:Re,logicalId:Ee}=j.createCertificate({domain:Ie,subdomains:Le,slug:D,environment:t,validationMethod:"DNS",hostedZoneId:z});Y=Ee,this.builder.addResource(Ee,Re)}if(this.mergedConfig.infrastructure?.storage){let he=Object.entries(this.mergedConfig.infrastructure.storage).some(([,Re])=>Re.website),Ie;if(he&&(Y||X))Ie=`${D}${t}CloudFrontOAC`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(Ie,{Type:"AWS::CloudFront::OriginAccessControl",Properties:{OriginAccessControlConfig:{Name:`${D}-${t}-s3-oac`,Description:`OAC for ${D} ${t} S3 website buckets`,OriginAccessControlOriginType:"s3",SigningBehavior:"always",SigningProtocol:"sigv4"}}});let Le=Object.entries(this.mergedConfig.infrastructure.storage).map(([Re,Ee])=>({name:Re,config:Ee,mountPath:this.normalizeMountPath(Ee),rewriteStyle:Ee.pathRewriteStyle||"directory",logicalId:this.storageBucketLogicalId(D,t,Re)})).filter((Re)=>Re.name!=="public"&&Re.config.website&&Re.mountPath);for(let[Re,Ee]of Object.entries(this.mergedConfig.infrastructure.storage)){let Te=!!(Ee.website&&Ie),Ke=this.storageBucketName(D,t,Re),{bucket:Be,logicalId:_e}=Fe.createBucket({slug:D,name:Re,environment:t,bucketName:Ke,versioning:Ee.versioning,encryption:Ee.encryption,public:Te?!1:Ee.public}),et=this.normalizeMountPath(Ee),Rt;if(Ee.failover){let wt=`infrastructure.storage.${Re}`;if(!Te||!_||et&&Re!=="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(Rt=fa({failover:Ee.failover,primaryBucket:Ke,primaryRegion:this.mergedConfig.project.region||"us-east-1",where:wt}),Rt.replicate&&Be.Properties){let Pt=`${_e}FailoverReplicationRole`;this.builder.addResource(Pt,ya({primaryBucket:Ke,replicaBucket:Rt.replicaBucket}));let Xt=Be.Properties;Xt.VersioningConfiguration={Status:"Enabled"},Xt.ReplicationConfiguration=ba({replicaBucket:Rt.replicaBucket,roleArn:{"Fn::GetAtt":[Pt,"Arn"]}})}}if(Te&&Be.Properties)Be.Properties.PublicAccessBlockConfiguration={BlockPublicAcls:!0,IgnorePublicAcls:!0,BlockPublicPolicy:!1,RestrictPublicBuckets:!1};if(this.builder.addResource(_e,Be),Ee.website){let wt=typeof Ee.website==="object"?Ee.website:{},Pt=Fe.enableWebsiteHosting(Be,wt.indexDocument||"index.html",wt.errorDocument);this.builder.addResource(_e,Pt)}let Et=this.normalizeMountPath(Ee);if(Te&&Ie&&_&&!(Et&&Re!=="public")){let wt=`${D}${t}${Re}CDN`.replace(/[^a-zA-Z0-9]/g,""),Pt=[];if(Ee.aliases&&Ee.aliases.length>0)Pt.push(...Ee.aliases);else if(Re==="public"){if(Pt.push(_),m?.domains?.includes(`www.${_}`))Pt.push(`www.${_}`)}else if(Re==="docs")Pt.push(`docs.${_}`);else if(Re==="blog")Pt.push(`blog.${_}`);let Xt=typeof Ee.website==="object"?Ee.website:{},_t=Ee.spa===!0,nt=Xt.errorDocument||(_t?"index.html":"404.html"),zt=[];if(_t)zt.push({ErrorCode:403,ResponseCode:200,ResponsePagePath:"/index.html",ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:200,ResponsePagePath:"/index.html",ErrorCachingMinTTL:300});else zt.push({ErrorCode:403,ResponseCode:404,ResponsePagePath:`/${nt}`,ErrorCachingMinTTL:300},{ErrorCode:404,ResponseCode:404,ResponsePagePath:`/${nt}`,ErrorCachingMinTTL:300});let Wt=X&&Pt.length>0?{AcmCertificateArn:X,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:Y&&Pt.length>0?{AcmCertificateArn:{Ref:Y},SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},St=`S3-${D}-${t}-${Re}`,Ft=this.mergedConfig.project.region||"us-east-1",un;if(!_t)un=`${D}${t}${Re}UrlRewrite`.replace(/[^a-zA-Z0-9]/g,""),this.builder.addResource(un,{Type:"AWS::CloudFront::Function",Properties:{Name:`${D}-${t}-${Re}-url-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`URL rewrite for ${D} ${t} ${Re} - 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 mn=[{Id:St,DomainName:{"Fn::Sub":`\${${_e}}.s3.${Ft}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:Ie}}],Sr=[],Li=[];if(this.shouldRouteStorageBucketToCompute(Re,Ee)){this.appendComputeAppOrigin(mn,Sr,Li,D,t,this.resolveComputeCachePathPatterns(Ee.computeRoutes));for(let Kt of Le){let Rs=`S3-${D}-${t}-${Kt.name}`,Sa=`${D}${t}${Kt.name}PathMountRewrite`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Sa,{Type:"AWS::CloudFront::Function",Properties:{Name:`${D}-${t}-${Kt.name}-path-mount-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`Path mount rewrite for ${D} ${t} ${Kt.name} at ${Kt.mountPath}`,Runtime:"cloudfront-js-2.0"},FunctionCode:this.pathMountRewriteFunctionCode(Kt.mountPath,Kt.rewriteStyle)}}),mn.push({Id:Rs,DomainName:{"Fn::Sub":`\${${Kt.logicalId}}.s3.${Ft}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:Ie}}),Li.push(Kt.logicalId,Sa);for(let hu of[Kt.mountPath,`${Kt.mountPath}/*`])Sr.push({PathPattern:hu,TargetOriginId:Rs,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":[Sa,"FunctionARN"]}}]})}}let fn,Ht=St;if(Rt){let Kt=`${St}-failover`;Object.assign(mn[0],Rt.primaryConnection),mn.push({Id:Kt,DomainName:ha(Rt.replicaBucket,Rt.replicaRegion),OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:Ie}}),fn=Vo({primaryOriginId:St,secondaryOriginId:Kt,statusCodes:Rt.statusCodes}),Ht=fn.Items[0].Id}let wn={Type:"AWS::CloudFront::Distribution",DependsOn:[_e,Ie,...un?[un]:[],...Li],Properties:{DistributionConfig:{Enabled:!0,Comment:`${D} ${t} ${Re} site`,DefaultRootObject:"index.html",Origins:mn,...fn?{OriginGroups:fn}:{},DefaultCacheBehavior:{TargetOriginId:Ht,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"]}}]}:{}},...Sr.length>0?{CacheBehaviors:Sr}:{},...Pt.length>0?{Aliases:Pt}:{},ViewerCertificate:Wt,PriceClass:"PriceClass_100",HttpVersion:"http2and3",IPV6Enabled:!0,CustomErrorResponses:zt}}};if(Y)wn.DependsOn.push(Y);if(Rt)Sn(wn.Properties.DistributionConfig,`infrastructure.storage.${Re}`),this.storageFailoverReplicas.push({name:Re,primaryBucket:Ke,primaryRegion:Ft,replicaBucket:Rt.replicaBucket,replicaRegion:Rt.replicaRegion,replicate:Rt.replicate,distributionArnOutput:`${Re}CloudFrontDistributionArn`}),this.builder.addOutput(`${Re}CloudFrontDistributionArn`,{Value:{"Fn::Sub":`arn:\${AWS::Partition}:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`},Description:`CloudFront distribution ARN for ${Re} (scopes the bucket policy of the failover replica)`}),this.builder.addOutput(`${Re}FailoverBucketName`,{Value:Rt.replicaBucket,Description:`Failover replica bucket for ${Re} (${Rt.replicaRegion}), outside this stack`});this.builder.addResource(wt,wn);let Cn=`${_e}CloudFrontPolicy`;if(this.builder.addResource(Cn,{Type:"AWS::S3::BucketPolicy",DependsOn:[_e,wt],Properties:{Bucket:{Ref:_e},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${_e}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`}}}}]}}}),this.shouldRouteStorageBucketToCompute(Re,Ee))for(let Kt of Le){let Rs=`${Kt.logicalId}CloudFrontPolicy`;this.builder.addResource(Rs,{Type:"AWS::S3::BucketPolicy",DependsOn:[Kt.logicalId,wt],Properties:{Bucket:{Ref:Kt.logicalId},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${Kt.logicalId}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`}}}}]}}})}if(Pt.length>0)U.push({name:Re,bucketLogicalId:_e,distLogicalId:wt,oacLogicalId:Ie,aliases:Pt});this.builder.addOutput(`${Re}CloudFrontDomain`,{Value:{"Fn::GetAtt":[wt,"DomainName"]},Description:`CloudFront domain for ${Re}`}),this.builder.addOutput(`${Re}CloudFrontDistributionId`,{Value:{Ref:wt},Description:`CloudFront distribution ID for ${Re}`})}if(this.builder.addOutput(`${Re}BucketName`,{Value:{Ref:_e},Description:`S3 bucket name for ${Re}`}),Re==="public")this.builder.addOutput("FrontendBucketName",{Value:{Ref:_e},Description:"Frontend S3 bucket name"});if(Re==="docs")this.builder.addOutput("DocsBucketName",{Value:{Ref:_e},Description:"Documentation S3 bucket name"});if(Re==="blog")this.builder.addOutput("BlogBucketName",{Value:{Ref:_e},Description:"Blog S3 bucket name"})}}if(this.mergedConfig.infrastructure?.sftp){let he=this.mergedConfig.infrastructure.sftp,Ie=Go.create({...he,storage:this.resolveSftpStorage(he,D,t)??he.storage,slug:D,environment:t});for(let[Le,Re]of Object.entries(Ie.resources))this.builder.addResource(Le,Re);this.builder.addOutput("SftpServerId",{Value:{Ref:Ie.serverLogicalId},Description:"AWS Transfer Family SFTP server ID"}),this.builder.addOutput("SftpEndpoint",{Value:{"Fn::Sub":`\${${Ie.serverLogicalId}}.server.transfer.\${AWS::Region}.amazonaws.com`},Description:"AWS Transfer Family SFTP endpoint"}),this.builder.addOutput("SftpStorageDomain",{Value:Ie.domain,Description:"Storage backing the SFTP server (EFS or S3)"})}if(z&&U.length>0)for(let{name:he,distLogicalId:Ie,aliases:Le}of U)for(let Re of Le){let Ee=Re.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Ee}ARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Ie],Properties:{HostedZoneId:z,Name:Re,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[Ie,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}}),this.builder.addResource(`${Ee}AAAARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Ie],Properties:{HostedZoneId:z,Name:Re,Type:"AAAA",AliasTarget:{DNSName:{"Fn::GetAtt":[Ie,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}})}if(this.mergedConfig.infrastructure?.databases){for(let[he,Ie]of Object.entries(this.mergedConfig.infrastructure.databases))if(Ie.engine==="dynamodb"){let{table:Le,logicalId:Re}=Gt.createTable({slug:D,environment:t,tableName:`${D}-${t}-${he}`,partitionKey:Ie.partitionKey||{name:"id",type:"S"},sortKey:Ie.sortKey});this.builder.addResource(Re,Le)}else if(Ie.engine==="postgres"){let{dbInstance:Le,logicalId:Re}=Gt.createPostgres({slug:D,environment:t,dbInstanceIdentifier:`${D}-${t}-${he}`,masterUsername:Ie.username||"admin",masterUserPassword:Ie.password||"changeme123",allocatedStorage:Ie.storage||20,dbInstanceClass:Ie.instanceClass||"db.t3.micro"});this.builder.addResource(Re,Le)}else if(Ie.engine==="mysql"){let{dbInstance:Le,logicalId:Re}=Gt.createMysql({slug:D,environment:t,dbInstanceIdentifier:`${D}-${t}-${he}`,masterUsername:Ie.username||"admin",masterUserPassword:Ie.password||"changeme123",allocatedStorage:Ie.storage||20,dbInstanceClass:Ie.instanceClass||"db.t3.micro"});this.builder.addResource(Re,Le)}}if(this.mergedConfig.infrastructure?.cdn)for(let[he,Ie]of Object.entries(this.mergedConfig.infrastructure.cdn)){if(!Ie.origin)continue;let Le=typeof Ie.customDomain==="string"?Ie.customDomain:Ie.customDomain?.domain||Ie.domain,Re=typeof Ie.customDomain==="object"?Ie.customDomain?.certificateArn:Ie.certificateArn,Ee=Re||X,Te=`${D}${t}${he}CDN`.replace(/[^a-zA-Z0-9]/g,""),Ke=`S3-${D}-${t}-${he}-cdn`,Be=[{Id:Ke,DomainName:Ie.origin,OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginShield:Ie.originShield?{Enabled:!0,OriginShieldRegion:Ie.originShieldRegion||this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1"}:{Enabled:!1},...tt(Ie,`infrastructure.cdn.${he}`)}],_e=[],et=[],Rt=["GET","HEAD","OPTIONS"],Et=Ke,wt;if(Ie.failoverOrigin){bn(Rt,`the default cache behavior of infrastructure.cdn.${he}`);let nt=`${Ke}-failover`,zt=typeof Ie.failoverOrigin==="string"?{domain:Ie.failoverOrigin}:Ie.failoverOrigin;if(!zt.domain)throw Error(`infrastructure.cdn.${he}.failoverOrigin.domain is required`);if(zt.domain===Ie.origin)throw Error(`infrastructure.cdn.${he}: failoverOrigin must differ from origin (both are ${Ie.origin})`);Be.push({Id:nt,DomainName:zt.domain,OriginPath:zt.originPath||"",...xi(zt.domain)?{S3OriginConfig:{OriginAccessIdentity:""}}:{CustomOriginConfig:{HTTPPort:80,HTTPSPort:443,OriginProtocolPolicy:"https-only"}},OriginShield:{Enabled:!1},...tt(zt,`infrastructure.cdn.${he}.failoverOrigin`)}),wt=Vo({primaryOriginId:Ke,secondaryOriginId:nt,statusCodes:Ie.failoverStatusCodes}),Et=wt.Items[0].Id}else if(Ie.failoverStatusCodes)throw Error(`infrastructure.cdn.${he}: failoverStatusCodes needs failoverOrigin to fail over to`);if(Ie.routeCompute)this.appendComputeAppOrigin(Be,_e,et,D,t,this.resolveComputeCachePathPatterns(Ie.computeRoutes));let Pt=Ee&&Le?{AcmCertificateArn:Y&&!Re?{Ref:Y}:Ee,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},Xt=[...et];if(Y&&!Re&&Le)Xt.push(Y);let _t={Type:"AWS::CloudFront::Distribution",...Xt.length>0?{DependsOn:Xt}:{},Properties:{DistributionConfig:{Enabled:!0,Comment:`${D} ${t} ${he} CDN`,DefaultRootObject:"index.html",Origins:Be,...wt?{OriginGroups:wt}:{},DefaultCacheBehavior:{TargetOriginId:Et,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:Rt,CachedMethods:["GET","HEAD","OPTIONS"],Compress:Ie.compress!==!1,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6"},..._e.length>0?{CacheBehaviors:_e}:{},...Le&&Ee?{Aliases:[Le]}:{},ViewerCertificate:Pt,PriceClass:"PriceClass_100",HttpVersion:Ie.http3?"http2and3":"http2"}}};if(Sn(_t.Properties.DistributionConfig,`infrastructure.cdn.${he}`),this.builder.addResource(Te,_t),z&&Le&&Ee){let nt=Le.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${nt}CdnARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Te,...Y&&!Re?[Y]:[]],Properties:{HostedZoneId:z,Name:Le,Type:"A",AliasTarget:{HostedZoneId:"Z2FDTNDATAQYW2",DNSName:{"Fn::GetAtt":[Te,"DomainName"]}}}})}}if(this.mergedConfig.infrastructure?.queues)for(let[he,Ie]of Object.entries(this.mergedConfig.infrastructure.queues)){if(!this.shouldDeploy(Ie))continue;let{queue:Le,logicalId:Re}=ge.createQueue({slug:D,environment:t,name:`${D}-${t}-${he}`,fifo:Ie.fifo,visibilityTimeout:Ie.visibilityTimeout,messageRetentionPeriod:Ie.messageRetentionPeriod,delaySeconds:Ie.delaySeconds,maxMessageSize:Ie.maxMessageSize,receiveMessageWaitTime:Ie.receiveMessageWaitTime,contentBasedDeduplication:Ie.contentBasedDeduplication,encrypted:Ie.encrypted,kmsKeyId:Ie.kmsKeyId});this.builder.addResource(Re,Le);let Ee;if(Ie.deadLetterQueue){let{deadLetterQueue:Te,updatedSourceQueue:Ke,deadLetterLogicalId:Be}=ge.createDeadLetterQueue(Re,{slug:D,environment:t,maxReceiveCount:Ie.maxReceiveCount});Ee=Be,this.builder.addResource(Be,Te);let et=this.builder.getResources()[Re];if(et?.Properties)et.Properties.RedrivePolicy=Ke.Properties?.RedrivePolicy}if(Ie.trigger){let Te=Ie.trigger,Ke=`${D}${t}${Te.functionName}`.replace(/[^a-zA-Z0-9]/g,""),Be={Type:"AWS::Lambda::EventSourceMapping",Properties:{EventSourceArn:{"Fn::GetAtt":[Re,"Arn"]},FunctionName:{Ref:Ke},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:[Re,Ke]};this.builder.addResource(`${Re}Trigger`,Be)}if(Ie.alarms?.enabled){let Te=Ie.alarms,Ke=Te.notificationTopicArn;if(!Ke&&Te.notificationEmails?.length){let et=`${Re}AlarmTopic`;this.builder.addResource(et,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${D}-${t}-${he}-alarms`,DisplayName:`${he} Queue Alarms`}}),Te.notificationEmails.forEach((Rt,Et)=>{this.builder.addResource(`${et}Sub${Et}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:et},Protocol:"email",Endpoint:Rt}})}),Ke={Ref:et}}let Be=Te.queueDepthThreshold||1000;this.builder.addResource(`${Re}DepthAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-${he}-queue-depth`,AlarmDescription:`Queue ${he} depth exceeds ${Be} messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Average",Period:300,EvaluationPeriods:2,Threshold:Be,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Re,"QueueName"]}}],...Ke&&{AlarmActions:[Ke],OKActions:[Ke]}}});let _e=Te.messageAgeThreshold||3600;if(this.builder.addResource(`${Re}AgeAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-${he}-message-age`,AlarmDescription:`Queue ${he} oldest message exceeds ${_e} seconds`,MetricName:"ApproximateAgeOfOldestMessage",Namespace:"AWS/SQS",Statistic:"Maximum",Period:300,EvaluationPeriods:2,Threshold:_e,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Re,"QueueName"]}}],...Ke&&{AlarmActions:[Ke],OKActions:[Ke]}}}),Ee&&Te.dlqAlarm!==!1)this.builder.addResource(`${Ee}Alarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-${he}-dlq-messages`,AlarmDescription:`Dead letter queue for ${he} has messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Sum",Period:300,EvaluationPeriods:1,Threshold:0,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Ee,"QueueName"]}}],...Ke&&{AlarmActions:[Ke]}}})}if(Ie.subscribe){let Te=Ie.subscribe,Ke=Te.topicArn;if(!Ke&&Te.topicName)Ke={Ref:Te.topicName};if(Ke){this.builder.addResource(`${Re}SnsPolicy`,{Type:"AWS::SQS::QueuePolicy",Properties:{Queues:[{Ref:Re}],PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"sns.amazonaws.com"},Action:"sqs:SendMessage",Resource:{"Fn::GetAtt":[Re,"Arn"]},Condition:{ArnEquals:{"aws:SourceArn":Ke}}}]}}});let Be={TopicArn:Ke,Protocol:"sqs",Endpoint:{"Fn::GetAtt":[Re,"Arn"]},RawMessageDelivery:Te.rawMessageDelivery||!1};if(Te.filterPolicy)Be.FilterPolicy=Te.filterPolicy,Be.FilterPolicyScope=Te.filterPolicyScope||"MessageAttributes";this.builder.addResource(`${Re}SnsSub`,{Type:"AWS::SNS::Subscription",Properties:Be,DependsOn:`${Re}SnsPolicy`})}}}if(this.mergedConfig.infrastructure?.realtime?.enabled)if((this.mergedConfig.infrastructure.realtime.mode||"serverless")==="server")this.generateRealtimeServerResources(D,t);else this.generateRealtimeResources(D,t);let ne=this.mergedConfig.infrastructure?.redirects;if(ne){let he=ne.target||this.mergedConfig.infrastructure?.dns?.domain||"",Ie=ne.protocol||"https";if(ne.domains?.length&&he)for(let Le of ne.domains){let{bucket:Re,bucketPolicy:Ee,logicalId:Te,policyLogicalId:Ke}=At.createDomainRedirectBucket({slug:D,environment:t,sourceDomain:Le,targetDomain:he,protocol:Ie});this.builder.addResource(Te,Re),this.builder.addResource(Ke,Ee)}if(ne.paths&&Object.keys(ne.paths).length>0){let Le=At.fromMapping(ne.paths,{statusCode:ne.statusCode||301}),{function:Re,logicalId:Ee}=At.createPathRedirectFunction({slug:D,environment:t,rules:Le});this.builder.addResource(Ee,Re)}}if(this.mergedConfig.infrastructure?.monitoring?.alarms)for(let[he,Ie]of Object.entries(this.mergedConfig.infrastructure.monitoring.alarms)){let{alarm:Le,logicalId:Re}=Oi.createAlarm({slug:D,environment:t,alarmName:`${D}-${t}-${he}`,metricName:Ie.metricName||"Errors",namespace:Ie.namespace||"AWS/Lambda",threshold:Ie.threshold||1,comparisonOperator:Ie.comparisonOperator||"GreaterThanThreshold"});this.builder.addResource(Re,Le)}let re=this.mergedConfig.infrastructure?.cache;if(re){let he=re.type||"redis";if(he==="redis"){this.generateNetworkInfrastructure(D,t);let Ie=re.redis||{},{replicationGroup:Le,subnetGroup:Re,logicalId:Ee,subnetGroupId:Te}=_i.createRedis({slug:D,environment:t,nodeType:Ie.nodeType||re.nodeType||"cache.t3.micro",engineVersion:Ie.engineVersion||"7.1",port:Ie.port||6379,numCacheClusters:Ie.numCacheNodes||2,automaticFailover:Ie.automaticFailoverEnabled!==!1,atRestEncryption:!0,transitEncryption:!0,snapshotRetentionDays:Ie.snapshotRetentionLimit||7,snapshotWindow:Ie.snapshotWindow,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Ee,Le),Re&&Te)this.builder.addResource(Te,Re);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Ee,"PrimaryEndPoint.Address"]},Description:"Redis primary endpoint address"}),this.builder.addOutput("CachePort",{Value:{"Fn::GetAtt":[Ee,"PrimaryEndPoint.Port"]},Description:"Redis primary endpoint port"})}else if(he==="memcached"){this.generateNetworkInfrastructure(D,t);let Ie=re.elasticache||{},{cluster:Le,subnetGroup:Re,logicalId:Ee,subnetGroupId:Te}=_i.createMemcached({slug:D,environment:t,nodeType:Ie.nodeType||re.nodeType||"cache.t3.micro",engineVersion:Ie.engineVersion||"1.6.22",numCacheNodes:Ie.numCacheNodes||2,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Ee,Le),Re&&Te)this.builder.addResource(Te,Re);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Ee,"ConfigurationEndpoint.Address"]},Description:"Memcached configuration endpoint address"})}}let se=this.mergedConfig.infrastructure?.email;if(se){let he=se.domain||this.mergedConfig.infrastructure?.dns?.domain;if(he){let{emailIdentity:Ie,logicalId:Le}=k.verifyDomain({domain:he,slug:D,environment:t,enableDkim:se.enableDkim!==!1,dkimKeyLength:se.dkimKeyLength||"RSA_2048_BIT"});if(this.builder.addResource(Le,Ie),se.configurationSet!==!1){let{configurationSet:Te,logicalId:Ke}=k.createConfigurationSet({slug:D,environment:t});this.builder.addResource(Ke,Te)}let Re=se.hostedZoneId||this.mergedConfig.infrastructure?.dns?.hostedZoneId;if(Re){if(se.enableDkim!==!1)for(let et=1;et<=3;et++){let Rt=`DkimRecord${et}${he.replace(/\./g,"")}`;this.builder.addResource(Rt,{Type:"AWS::Route53::RecordSet",DependsOn:[Le],Properties:{HostedZoneId:Re,Name:{"Fn::GetAtt":[Le,`DkimDNSTokenName${et}`]},Type:"CNAME",TTL:1800,ResourceRecords:[{"Fn::GetAtt":[Le,`DkimDNSTokenValue${et}`]}]}})}let{record:Te,logicalId:Ke}=k.createSpfRecord(he,Re);this.builder.addResource(Ke,Te);let{record:Be,logicalId:_e}=k.createDmarcRecord(he,Re,{policy:"none",reportingEmail:se.dmarcReportingEmail||`dmarc-reports@${he}`});this.builder.addResource(_e,Be)}this.builder.addOutput("EmailDomain",{Value:he,Description:"SES verified email domain"});let Ee=se.server;if(Ee?.enabled&&Re){let Te=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",Ke=`${D}-${t}-email`,{role:Be,policy:_e,roleLogicalId:et,policyLogicalId:Rt}=k.createEmailLambdaRole({slug:D,environment:t,s3BucketArn:`arn:aws:s3:::${Ke}`,sesIdentityArn:`arn:aws:ses:${Te}:*:identity/${he}`});this.builder.addResource(et,Be),this.builder.addResource(Rt,_e);let{function:Et,permission:wt,logicalId:Pt,permissionLogicalId:Xt}=k.createInboundEmailLambda({slug:D,environment:t,roleArn:{"Fn::GetAtt":[et,"Arn"]},s3BucketName:Ke,organizedPrefix:"mailboxes/"});this.builder.addResource(Pt,Et),this.builder.addResource(Xt,wt);let _t=k.createInboundEmailSetup({slug:D,environment:t,domain:he,s3BucketName:Ke,s3KeyPrefix:"inbox/",region:Te,hostedZoneId:Re,lambdaFunctionArn:{"Fn::GetAtt":[Pt,"Arn"]}});for(let[zt,Wt]of Object.entries(_t.resources)){if(Wt.Type==="AWS::SES::ReceiptRule")Wt.DependsOn=[Xt,Pt];this.builder.addResource(zt,Wt)}this.builder.addOutput("InboundEmailLambda",{Value:{Ref:Pt},Description:"Inbound email processing Lambda function"}),this.builder.addOutput("EmailBucket",{Value:Ke,Description:"S3 bucket for email storage"});let nt=this.serverEipLogicalIds.get("app");if(nt&&Re){let Wt=`${Ee?.subdomain||"mail"}.${he}`,St=Wt.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${St}ARecord`,{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:Re,Name:Wt,Type:"A",TTL:"300",ResourceRecords:[{Ref:nt}]}})}this.builder.addOutput("MailHost",{Value:`mail.${he}`,Description:"Mail server hostname for SMTP/IMAP clients"})}}}let fe=this.mergedConfig.infrastructure?.search;if(fe){let he=fe.vpc?{subnetIds:[{Ref:"PublicSubnet1"}],securityGroupIds:[]}:void 0;if(he)this.generateNetworkInfrastructure(D,t);let{domain:Ie,logicalId:Le}=qt.createDomain({slug:D,environment:t,engineVersion:fe.engineVersion||"OpenSearch_2.11",instanceType:fe.instanceType||"t3.small.search",instanceCount:fe.instanceCount||1,volumeSize:fe.volumeSize||10,volumeType:fe.volumeType||"gp3",dedicatedMaster:fe.dedicatedMaster||!1,dedicatedMasterType:fe.dedicatedMasterType,dedicatedMasterCount:fe.dedicatedMasterCount||3,multiAz:fe.multiAz||!1,encryption:fe.encryption||{atRest:!0,nodeToNode:!0},advancedSecurity:fe.advancedSecurity,autoTune:fe.autoTune!==!1,vpc:he});this.builder.addResource(Le,Ie),this.builder.addOutput("SearchDomainEndpoint",{Value:{"Fn::GetAtt":[Le,"DomainEndpoint"]},Description:"OpenSearch domain endpoint"}),this.builder.addOutput("SearchDomainArn",{Value:{"Fn::GetAtt":[Le,"Arn"]},Description:"OpenSearch domain ARN"})}let Ae=this.mergedConfig.infrastructure?.fileSystem;if(Ae&&Object.keys(Ae).length>0){this.generateNetworkInfrastructure(D,t);for(let[he,Ie]of Object.entries(Ae)){let{fileSystem:Le,logicalId:Re}=rt.createFileSystem({slug:`${D}-${he}`,environment:t,encrypted:Ie.encrypted!==!1,performanceMode:Ie.performanceMode||"generalPurpose",throughputMode:Ie.throughputMode||"bursting",enableBackup:!0});this.builder.addResource(Re,Le);let{securityGroup:Ee,logicalId:Te}=rt.createEfsSecurityGroup({slug:`${D}-${he}`,environment:t,vpcId:{Ref:"VPC"},sourceCidrBlocks:["10.0.0.0/16"]});this.builder.addResource(Te,Ee);let{mountTargets:Ke,logicalIds:Be}=rt.createMultiAzMountTargets(Re,{slug:`${D}-${he}`,environment:t,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],securityGroupId:{Ref:Te}});for(let _e=0;_e<Ke.length;_e++)this.builder.addResource(Be[_e],Ke[_e]);this.builder.addOutput(`${he}FileSystemId`,{Value:{Ref:Re},Description:`EFS file system ID for ${he}`})}}let Pe=this.mergedConfig.infrastructure?.ai;if(Pe){let he=Pe.service||"ecs",Ie=Pe.models||["*"],Le=Pe.allowStreaming!==!1,Re;if(he==="ecs")Re=Ve.enableBedrockForEcs({slug:D,environment:t,models:Ie,allowStreaming:Le});else if(he==="ec2")Re=Ve.enableBedrockForEc2({slug:D,environment:t,models:Ie,allowStreaming:Le});else if(he==="lambda")Re=Ve.enableBedrockForLambda({slug:D,environment:t,models:Ie,allowStreaming:Le});else Re=Ve.createBedrockRole(he,{slug:D,environment:t,models:Ie,allowStreaming:Le});if(this.builder.addResource(Re.logicalId,Re.role),Pe.allowAsync){let{policy:Ee,logicalId:Te}=Ve.createBedrockPolicy({slug:D,environment:t,models:Ie,allowStreaming:Le,allowAsync:!0});this.builder.addResource(Te,Ee)}this.builder.addOutput("BedrockRoleArn",{Value:{"Fn::GetAtt":[Re.logicalId,"Arn"]},Description:"IAM role ARN with Bedrock permissions"})}}generateRealtimeResources(D,t){let m=this.mergedConfig.infrastructure?.realtime;if(!m)return;let A=m.name||`${D}-${t}-realtime`,_=m.scaling||{},z=m.storage||{type:"dynamodb"},U=_.handlerMemory||256,Y=_.handlerTimeout||30,X=`${D}${t}RealtimeConnections`.replace(/[^a-zA-Z0-9]/g,""),ne=`${D}${t}RealtimeChannels`.replace(/[^a-zA-Z0-9]/g,"");if(z.type==="dynamodb"){let Be=z.dynamodb||{},_e=Be.billingMode||"PAY_PER_REQUEST";this.builder.addResource(X,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${D}-${t}-realtime-connections`,BillingMode:_e,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"},..._e==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Be.readCapacity||5,WriteCapacityUnits:Be.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},..._e==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Be.readCapacity||5,WriteCapacityUnits:Be.writeCapacity||5}},...Be.pointInTimeRecovery&&{PointInTimeRecoverySpecification:{PointInTimeRecoveryEnabled:!0}},Tags:[{Key:"Name",Value:`${D}-${t}-realtime-connections`},{Key:"Environment",Value:t}]}}),this.builder.addResource(ne,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${D}-${t}-realtime-channels`,BillingMode:_e,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"},..._e==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Be.readCapacity||5,WriteCapacityUnits:Be.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},..._e==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Be.readCapacity||5,WriteCapacityUnits:Be.writeCapacity||5}},Tags:[{Key:"Name",Value:`${D}-${t}-realtime-channels`},{Key:"Environment",Value:t}]}})}let re=`${D}${t}RealtimeRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(re,{Type:"AWS::IAM::Role",Properties:{RoleName:`${D}-${t}-realtime-handler-role`,AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]},ManagedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"],Policies:[{PolicyName:"RealtimeHandlerPolicy",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["dynamodb:GetItem","dynamodb:PutItem","dynamodb:DeleteItem","dynamodb:Query","dynamodb:Scan","dynamodb:UpdateItem"],Resource:[{"Fn::GetAtt":[X,"Arn"]},{"Fn::Sub":`\${${X}.Arn}/index/*`},{"Fn::GetAtt":[ne,"Arn"]},{"Fn::Sub":`\${${ne}.Arn}/index/*`}]},{Effect:"Allow",Action:"execute-api:ManageConnections",Resource:{"Fn::Sub":"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*"}}]}}]}});let se=`${D}${t}RealtimeConnect`.replace(/[^a-zA-Z0-9]/g,""),fe=`${D}${t}RealtimeDisconnect`.replace(/[^a-zA-Z0-9]/g,""),Ae=`${D}${t}RealtimeMessage`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(se,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${D}-${t}-realtime-connect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:U,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateConnectHandlerCode()}},DependsOn:[re,X]}),this.builder.addResource(fe,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${D}-${t}-realtime-disconnect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:U,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateDisconnectHandlerCode()}},DependsOn:[re,X,ne]}),this.builder.addResource(Ae,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${D}-${t}-realtime-message`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:U,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateMessageHandlerCode()}},DependsOn:[re,X,ne]});let Pe=`${D}${t}RealtimeApi`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Pe,{Type:"AWS::ApiGatewayV2::Api",Properties:{Name:A,ProtocolType:"WEBSOCKET",RouteSelectionExpression:"$request.body.action",Tags:{Name:A,Environment:t}}});let he=`${se}Permission`,Ie=`${fe}Permission`,Le=`${Ae}Permission`;this.builder.addResource(he,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:se},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Pe}}/*/$connect`}}}),this.builder.addResource(Ie,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:fe},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Pe}}/*/$disconnect`}}}),this.builder.addResource(Le,{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}:\${${Pe}}/*/$default`}}});let Re=`${Pe}ConnectInteg`,Ee=`${Pe}DisconnectInteg`,Te=`${Pe}MessageInteg`;this.builder.addResource(Re,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Pe},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${se}.Arn}/invocations`}}}),this.builder.addResource(Ee,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Pe},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${fe}.Arn}/invocations`}}}),this.builder.addResource(Te,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Pe},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${Ae}.Arn}/invocations`}}}),this.builder.addResource(`${Pe}ConnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Pe},RouteKey:"$connect",AuthorizationType:"NONE",Target:{"Fn::Sub":`integrations/\${${Re}}`}}}),this.builder.addResource(`${Pe}DisconnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Pe},RouteKey:"$disconnect",Target:{"Fn::Sub":`integrations/\${${Ee}}`}}}),this.builder.addResource(`${Pe}DefaultRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Pe},RouteKey:"$default",Target:{"Fn::Sub":`integrations/\${${Te}}`}}});let Ke=`${Pe}Stage`;if(this.builder.addResource(Ke,{Type:"AWS::ApiGatewayV2::Stage",Properties:{ApiId:{Ref:Pe},StageName:t,AutoDeploy:!0,DefaultRouteSettings:{ThrottlingBurstLimit:_.messagesPerSecond||1000,ThrottlingRateLimit:_.messagesPerSecond||1000}}}),m.monitoring?.enabled){let Be=m.monitoring,_e=Be.notificationTopicArn;if(!_e&&Be.notificationEmails?.length){let et=`${Pe}AlarmTopic`;this.builder.addResource(et,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${D}-${t}-realtime-alarms`,DisplayName:"Realtime WebSocket Alarms"}}),Be.notificationEmails.forEach((Rt,Et)=>{this.builder.addResource(`${et}Sub${Et}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:et},Protocol:"email",Endpoint:Rt}})}),_e={Ref:et}}if(Be.connectionThreshold)this.builder.addResource(`${Pe}ConnectionAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-realtime-connections`,AlarmDescription:`WebSocket connections exceed ${Be.connectionThreshold}`,MetricName:"ConnectCount",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:300,EvaluationPeriods:2,Threshold:Be.connectionThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Pe}}],..._e&&{AlarmActions:[_e]}}});if(Be.errorThreshold)this.builder.addResource(`${Pe}ErrorAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-realtime-errors`,AlarmDescription:`WebSocket errors exceed ${Be.errorThreshold}/min`,MetricName:"ExecutionError",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:60,EvaluationPeriods:3,Threshold:Be.errorThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Pe}}],..._e&&{AlarmActions:[_e]}}})}this.builder.addOutput(`${Pe}Endpoint`,{Description:"WebSocket API endpoint URL",Value:{"Fn::Sub":`wss://\${${Pe}}.execute-api.\${AWS::Region}.amazonaws.com/${t}`},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-endpoint"}}}),this.builder.addOutput(`${Pe}Id`,{Description:"WebSocket API ID",Value:{Ref:Pe},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-api-id"}}})}generateConnectHandlerCode(){return`
|
|
100
|
+
else if (!uri.includes('.')) { request.uri += '.html'; } return request; }`}});let mn=[{Id:St,DomainName:{"Fn::Sub":`\${${_e}}.s3.${Ft}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:Ie}}],Sr=[],Li=[];if(this.shouldRouteStorageBucketToCompute(Re,Ee)){this.appendComputeAppOrigin(mn,Sr,Li,D,t,this.resolveComputeCachePathPatterns(Ee.computeRoutes));for(let Kt of Le){let Rs=`S3-${D}-${t}-${Kt.name}`,Sa=`${D}${t}${Kt.name}PathMountRewrite`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Sa,{Type:"AWS::CloudFront::Function",Properties:{Name:`${D}-${t}-${Kt.name}-path-mount-rewrite`,AutoPublish:!0,FunctionConfig:{Comment:`Path mount rewrite for ${D} ${t} ${Kt.name} at ${Kt.mountPath}`,Runtime:"cloudfront-js-2.0"},FunctionCode:this.pathMountRewriteFunctionCode(Kt.mountPath,Kt.rewriteStyle)}}),mn.push({Id:Rs,DomainName:{"Fn::Sub":`\${${Kt.logicalId}}.s3.${Ft}.amazonaws.com`},OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:Ie}}),Li.push(Kt.logicalId,Sa);for(let hu of[Kt.mountPath,`${Kt.mountPath}/*`])Sr.push({PathPattern:hu,TargetOriginId:Rs,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":[Sa,"FunctionARN"]}}]})}}let fn,Ht=St;if(Rt){let Kt=`${St}-failover`;Object.assign(mn[0],Rt.primaryConnection),mn.push({Id:Kt,DomainName:ha(Rt.replicaBucket,Rt.replicaRegion),OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginAccessControlId:{Ref:Ie}}),fn=Vo({primaryOriginId:St,secondaryOriginId:Kt,statusCodes:Rt.statusCodes}),Ht=fn.Items[0].Id}let wn={Type:"AWS::CloudFront::Distribution",DependsOn:[_e,Ie,...un?[un]:[],...Li],Properties:{DistributionConfig:{Enabled:!0,Comment:`${D} ${t} ${Re} site`,DefaultRootObject:"index.html",Origins:mn,...fn?{OriginGroups:fn}:{},DefaultCacheBehavior:{TargetOriginId:Ht,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"]}}]}:{}},...Sr.length>0?{CacheBehaviors:Sr}:{},...Pt.length>0?{Aliases:Pt}:{},ViewerCertificate:Wt,PriceClass:"PriceClass_100",HttpVersion:"http2and3",IPV6Enabled:!0,CustomErrorResponses:zt}}};if(Y)wn.DependsOn.push(Y);if(Rt)Sn(wn.Properties.DistributionConfig,`infrastructure.storage.${Re}`),this.storageFailoverReplicas.push({name:Re,primaryBucket:Ke,primaryRegion:Ft,replicaBucket:Rt.replicaBucket,replicaRegion:Rt.replicaRegion,replicate:Rt.replicate,distributionArnOutput:`${Re}CloudFrontDistributionArn`}),this.builder.addOutput(`${Re}CloudFrontDistributionArn`,{Value:{"Fn::Sub":`arn:\${AWS::Partition}:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`},Description:`CloudFront distribution ARN for ${Re} (scopes the bucket policy of the failover replica)`}),this.builder.addOutput(`${Re}FailoverBucketName`,{Value:Rt.replicaBucket,Description:`Failover replica bucket for ${Re} (${Rt.replicaRegion}), outside this stack`});this.builder.addResource(wt,wn);let Cn=`${_e}CloudFrontPolicy`;if(this.builder.addResource(Cn,{Type:"AWS::S3::BucketPolicy",DependsOn:[_e,wt],Properties:{Bucket:{Ref:_e},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${_e}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`}}}}]}}}),this.shouldRouteStorageBucketToCompute(Re,Ee))for(let Kt of Le){let Rs=`${Kt.logicalId}CloudFrontPolicy`;this.builder.addResource(Rs,{Type:"AWS::S3::BucketPolicy",DependsOn:[Kt.logicalId,wt],Properties:{Bucket:{Ref:Kt.logicalId},PolicyDocument:{Version:"2012-10-17",Statement:[{Sid:"AllowCloudFrontServicePrincipal",Effect:"Allow",Principal:{Service:"cloudfront.amazonaws.com"},Action:"s3:GetObject",Resource:{"Fn::Sub":`arn:aws:s3:::\${${Kt.logicalId}}/*`},Condition:{StringEquals:{"AWS:SourceArn":{"Fn::Sub":`arn:aws:cloudfront::\${AWS::AccountId}:distribution/\${${wt}}`}}}}]}}})}if(Pt.length>0)U.push({name:Re,bucketLogicalId:_e,distLogicalId:wt,oacLogicalId:Ie,aliases:Pt});this.builder.addOutput(`${Re}CloudFrontDomain`,{Value:{"Fn::GetAtt":[wt,"DomainName"]},Description:`CloudFront domain for ${Re}`}),this.builder.addOutput(`${Re}CloudFrontDistributionId`,{Value:{Ref:wt},Description:`CloudFront distribution ID for ${Re}`})}if(this.builder.addOutput(`${Re}BucketName`,{Value:{Ref:_e},Description:`S3 bucket name for ${Re}`}),Re==="public")this.builder.addOutput("FrontendBucketName",{Value:{Ref:_e},Description:"Frontend S3 bucket name"});if(Re==="docs")this.builder.addOutput("DocsBucketName",{Value:{Ref:_e},Description:"Documentation S3 bucket name"});if(Re==="blog")this.builder.addOutput("BlogBucketName",{Value:{Ref:_e},Description:"Blog S3 bucket name"})}}if(this.mergedConfig.infrastructure?.sftp){let he=this.mergedConfig.infrastructure.sftp,Ie=qo.create({...he,storage:this.resolveSftpStorage(he,D,t)??he.storage,slug:D,environment:t});for(let[Le,Re]of Object.entries(Ie.resources))this.builder.addResource(Le,Re);this.builder.addOutput("SftpServerId",{Value:{Ref:Ie.serverLogicalId},Description:"AWS Transfer Family SFTP server ID"}),this.builder.addOutput("SftpEndpoint",{Value:{"Fn::Sub":`\${${Ie.serverLogicalId}}.server.transfer.\${AWS::Region}.amazonaws.com`},Description:"AWS Transfer Family SFTP endpoint"}),this.builder.addOutput("SftpStorageDomain",{Value:Ie.domain,Description:"Storage backing the SFTP server (EFS or S3)"})}if(z&&U.length>0)for(let{name:he,distLogicalId:Ie,aliases:Le}of U)for(let Re of Le){let Ee=Re.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${Ee}ARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Ie],Properties:{HostedZoneId:z,Name:Re,Type:"A",AliasTarget:{DNSName:{"Fn::GetAtt":[Ie,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}}),this.builder.addResource(`${Ee}AAAARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Ie],Properties:{HostedZoneId:z,Name:Re,Type:"AAAA",AliasTarget:{DNSName:{"Fn::GetAtt":[Ie,"DomainName"]},HostedZoneId:"Z2FDTNDATAQYW2",EvaluateTargetHealth:!1}}})}if(this.mergedConfig.infrastructure?.databases){for(let[he,Ie]of Object.entries(this.mergedConfig.infrastructure.databases))if(Ie.engine==="dynamodb"){let{table:Le,logicalId:Re}=qt.createTable({slug:D,environment:t,tableName:`${D}-${t}-${he}`,partitionKey:Ie.partitionKey||{name:"id",type:"S"},sortKey:Ie.sortKey});this.builder.addResource(Re,Le)}else if(Ie.engine==="postgres"){let{dbInstance:Le,logicalId:Re}=qt.createPostgres({slug:D,environment:t,dbInstanceIdentifier:`${D}-${t}-${he}`,masterUsername:Ie.username||"admin",masterUserPassword:Ie.password||"changeme123",allocatedStorage:Ie.storage||20,dbInstanceClass:Ie.instanceClass||"db.t3.micro"});this.builder.addResource(Re,Le)}else if(Ie.engine==="mysql"){let{dbInstance:Le,logicalId:Re}=qt.createMysql({slug:D,environment:t,dbInstanceIdentifier:`${D}-${t}-${he}`,masterUsername:Ie.username||"admin",masterUserPassword:Ie.password||"changeme123",allocatedStorage:Ie.storage||20,dbInstanceClass:Ie.instanceClass||"db.t3.micro"});this.builder.addResource(Re,Le)}}if(this.mergedConfig.infrastructure?.cdn)for(let[he,Ie]of Object.entries(this.mergedConfig.infrastructure.cdn)){if(!Ie.origin)continue;let Le=typeof Ie.customDomain==="string"?Ie.customDomain:Ie.customDomain?.domain||Ie.domain,Re=typeof Ie.customDomain==="object"?Ie.customDomain?.certificateArn:Ie.certificateArn,Ee=Re||X,Te=`${D}${t}${he}CDN`.replace(/[^a-zA-Z0-9]/g,""),Ke=`S3-${D}-${t}-${he}-cdn`,Be=[{Id:Ke,DomainName:Ie.origin,OriginPath:"",S3OriginConfig:{OriginAccessIdentity:""},OriginShield:Ie.originShield?{Enabled:!0,OriginShieldRegion:Ie.originShieldRegion||this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1"}:{Enabled:!1},...tt(Ie,`infrastructure.cdn.${he}`)}],_e=[],et=[],Rt=["GET","HEAD","OPTIONS"],Et=Ke,wt;if(Ie.failoverOrigin){bn(Rt,`the default cache behavior of infrastructure.cdn.${he}`);let nt=`${Ke}-failover`,zt=typeof Ie.failoverOrigin==="string"?{domain:Ie.failoverOrigin}:Ie.failoverOrigin;if(!zt.domain)throw Error(`infrastructure.cdn.${he}.failoverOrigin.domain is required`);if(zt.domain===Ie.origin)throw Error(`infrastructure.cdn.${he}: failoverOrigin must differ from origin (both are ${Ie.origin})`);Be.push({Id:nt,DomainName:zt.domain,OriginPath:zt.originPath||"",...xi(zt.domain)?{S3OriginConfig:{OriginAccessIdentity:""}}:{CustomOriginConfig:{HTTPPort:80,HTTPSPort:443,OriginProtocolPolicy:"https-only"}},OriginShield:{Enabled:!1},...tt(zt,`infrastructure.cdn.${he}.failoverOrigin`)}),wt=Vo({primaryOriginId:Ke,secondaryOriginId:nt,statusCodes:Ie.failoverStatusCodes}),Et=wt.Items[0].Id}else if(Ie.failoverStatusCodes)throw Error(`infrastructure.cdn.${he}: failoverStatusCodes needs failoverOrigin to fail over to`);if(Ie.routeCompute)this.appendComputeAppOrigin(Be,_e,et,D,t,this.resolveComputeCachePathPatterns(Ie.computeRoutes));let Pt=Ee&&Le?{AcmCertificateArn:Y&&!Re?{Ref:Y}:Ee,SslSupportMethod:"sni-only",MinimumProtocolVersion:"TLSv1.2_2021"}:{CloudFrontDefaultCertificate:!0},Xt=[...et];if(Y&&!Re&&Le)Xt.push(Y);let _t={Type:"AWS::CloudFront::Distribution",...Xt.length>0?{DependsOn:Xt}:{},Properties:{DistributionConfig:{Enabled:!0,Comment:`${D} ${t} ${he} CDN`,DefaultRootObject:"index.html",Origins:Be,...wt?{OriginGroups:wt}:{},DefaultCacheBehavior:{TargetOriginId:Et,ViewerProtocolPolicy:"redirect-to-https",AllowedMethods:Rt,CachedMethods:["GET","HEAD","OPTIONS"],Compress:Ie.compress!==!1,CachePolicyId:"658327ea-f89d-4fab-a63d-7e88639e58f6"},..._e.length>0?{CacheBehaviors:_e}:{},...Le&&Ee?{Aliases:[Le]}:{},ViewerCertificate:Pt,PriceClass:"PriceClass_100",HttpVersion:Ie.http3?"http2and3":"http2"}}};if(Sn(_t.Properties.DistributionConfig,`infrastructure.cdn.${he}`),this.builder.addResource(Te,_t),z&&Le&&Ee){let nt=Le.replace(/\./g,"").replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${nt}CdnARecord`,{Type:"AWS::Route53::RecordSet",DependsOn:[Te,...Y&&!Re?[Y]:[]],Properties:{HostedZoneId:z,Name:Le,Type:"A",AliasTarget:{HostedZoneId:"Z2FDTNDATAQYW2",DNSName:{"Fn::GetAtt":[Te,"DomainName"]}}}})}}if(this.mergedConfig.infrastructure?.queues)for(let[he,Ie]of Object.entries(this.mergedConfig.infrastructure.queues)){if(!this.shouldDeploy(Ie))continue;let{queue:Le,logicalId:Re}=ge.createQueue({slug:D,environment:t,name:`${D}-${t}-${he}`,fifo:Ie.fifo,visibilityTimeout:Ie.visibilityTimeout,messageRetentionPeriod:Ie.messageRetentionPeriod,delaySeconds:Ie.delaySeconds,maxMessageSize:Ie.maxMessageSize,receiveMessageWaitTime:Ie.receiveMessageWaitTime,contentBasedDeduplication:Ie.contentBasedDeduplication,encrypted:Ie.encrypted,kmsKeyId:Ie.kmsKeyId});this.builder.addResource(Re,Le);let Ee;if(Ie.deadLetterQueue){let{deadLetterQueue:Te,updatedSourceQueue:Ke,deadLetterLogicalId:Be}=ge.createDeadLetterQueue(Re,{slug:D,environment:t,maxReceiveCount:Ie.maxReceiveCount});Ee=Be,this.builder.addResource(Be,Te);let et=this.builder.getResources()[Re];if(et?.Properties)et.Properties.RedrivePolicy=Ke.Properties?.RedrivePolicy}if(Ie.trigger){let Te=Ie.trigger,Ke=`${D}${t}${Te.functionName}`.replace(/[^a-zA-Z0-9]/g,""),Be={Type:"AWS::Lambda::EventSourceMapping",Properties:{EventSourceArn:{"Fn::GetAtt":[Re,"Arn"]},FunctionName:{Ref:Ke},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:[Re,Ke]};this.builder.addResource(`${Re}Trigger`,Be)}if(Ie.alarms?.enabled){let Te=Ie.alarms,Ke=Te.notificationTopicArn;if(!Ke&&Te.notificationEmails?.length){let et=`${Re}AlarmTopic`;this.builder.addResource(et,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${D}-${t}-${he}-alarms`,DisplayName:`${he} Queue Alarms`}}),Te.notificationEmails.forEach((Rt,Et)=>{this.builder.addResource(`${et}Sub${Et}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:et},Protocol:"email",Endpoint:Rt}})}),Ke={Ref:et}}let Be=Te.queueDepthThreshold||1000;this.builder.addResource(`${Re}DepthAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-${he}-queue-depth`,AlarmDescription:`Queue ${he} depth exceeds ${Be} messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Average",Period:300,EvaluationPeriods:2,Threshold:Be,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Re,"QueueName"]}}],...Ke&&{AlarmActions:[Ke],OKActions:[Ke]}}});let _e=Te.messageAgeThreshold||3600;if(this.builder.addResource(`${Re}AgeAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-${he}-message-age`,AlarmDescription:`Queue ${he} oldest message exceeds ${_e} seconds`,MetricName:"ApproximateAgeOfOldestMessage",Namespace:"AWS/SQS",Statistic:"Maximum",Period:300,EvaluationPeriods:2,Threshold:_e,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Re,"QueueName"]}}],...Ke&&{AlarmActions:[Ke],OKActions:[Ke]}}}),Ee&&Te.dlqAlarm!==!1)this.builder.addResource(`${Ee}Alarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-${he}-dlq-messages`,AlarmDescription:`Dead letter queue for ${he} has messages`,MetricName:"ApproximateNumberOfMessagesVisible",Namespace:"AWS/SQS",Statistic:"Sum",Period:300,EvaluationPeriods:1,Threshold:0,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"QueueName",Value:{"Fn::GetAtt":[Ee,"QueueName"]}}],...Ke&&{AlarmActions:[Ke]}}})}if(Ie.subscribe){let Te=Ie.subscribe,Ke=Te.topicArn;if(!Ke&&Te.topicName)Ke={Ref:Te.topicName};if(Ke){this.builder.addResource(`${Re}SnsPolicy`,{Type:"AWS::SQS::QueuePolicy",Properties:{Queues:[{Ref:Re}],PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"sns.amazonaws.com"},Action:"sqs:SendMessage",Resource:{"Fn::GetAtt":[Re,"Arn"]},Condition:{ArnEquals:{"aws:SourceArn":Ke}}}]}}});let Be={TopicArn:Ke,Protocol:"sqs",Endpoint:{"Fn::GetAtt":[Re,"Arn"]},RawMessageDelivery:Te.rawMessageDelivery||!1};if(Te.filterPolicy)Be.FilterPolicy=Te.filterPolicy,Be.FilterPolicyScope=Te.filterPolicyScope||"MessageAttributes";this.builder.addResource(`${Re}SnsSub`,{Type:"AWS::SNS::Subscription",Properties:Be,DependsOn:`${Re}SnsPolicy`})}}}if(this.mergedConfig.infrastructure?.realtime?.enabled)if((this.mergedConfig.infrastructure.realtime.mode||"serverless")==="server")this.generateRealtimeServerResources(D,t);else this.generateRealtimeResources(D,t);let ne=this.mergedConfig.infrastructure?.redirects;if(ne){let he=ne.target||this.mergedConfig.infrastructure?.dns?.domain||"",Ie=ne.protocol||"https";if(ne.domains?.length&&he)for(let Le of ne.domains){let{bucket:Re,bucketPolicy:Ee,logicalId:Te,policyLogicalId:Ke}=At.createDomainRedirectBucket({slug:D,environment:t,sourceDomain:Le,targetDomain:he,protocol:Ie});this.builder.addResource(Te,Re),this.builder.addResource(Ke,Ee)}if(ne.paths&&Object.keys(ne.paths).length>0){let Le=At.fromMapping(ne.paths,{statusCode:ne.statusCode||301}),{function:Re,logicalId:Ee}=At.createPathRedirectFunction({slug:D,environment:t,rules:Le});this.builder.addResource(Ee,Re)}}if(this.mergedConfig.infrastructure?.monitoring?.alarms)for(let[he,Ie]of Object.entries(this.mergedConfig.infrastructure.monitoring.alarms)){let{alarm:Le,logicalId:Re}=Oi.createAlarm({slug:D,environment:t,alarmName:`${D}-${t}-${he}`,metricName:Ie.metricName||"Errors",namespace:Ie.namespace||"AWS/Lambda",threshold:Ie.threshold||1,comparisonOperator:Ie.comparisonOperator||"GreaterThanThreshold"});this.builder.addResource(Re,Le)}let re=this.mergedConfig.infrastructure?.cache;if(re){let he=re.type||"redis";if(he==="redis"){this.generateNetworkInfrastructure(D,t);let Ie=re.redis||{},{replicationGroup:Le,subnetGroup:Re,logicalId:Ee,subnetGroupId:Te}=_i.createRedis({slug:D,environment:t,nodeType:Ie.nodeType||re.nodeType||"cache.t3.micro",engineVersion:Ie.engineVersion||"7.1",port:Ie.port||6379,numCacheClusters:Ie.numCacheNodes||2,automaticFailover:Ie.automaticFailoverEnabled!==!1,atRestEncryption:!0,transitEncryption:!0,snapshotRetentionDays:Ie.snapshotRetentionLimit||7,snapshotWindow:Ie.snapshotWindow,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Ee,Le),Re&&Te)this.builder.addResource(Te,Re);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Ee,"PrimaryEndPoint.Address"]},Description:"Redis primary endpoint address"}),this.builder.addOutput("CachePort",{Value:{"Fn::GetAtt":[Ee,"PrimaryEndPoint.Port"]},Description:"Redis primary endpoint port"})}else if(he==="memcached"){this.generateNetworkInfrastructure(D,t);let Ie=re.elasticache||{},{cluster:Le,subnetGroup:Re,logicalId:Ee,subnetGroupId:Te}=_i.createMemcached({slug:D,environment:t,nodeType:Ie.nodeType||re.nodeType||"cache.t3.micro",engineVersion:Ie.engineVersion||"1.6.22",numCacheNodes:Ie.numCacheNodes||2,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}]});if(this.builder.addResource(Ee,Le),Re&&Te)this.builder.addResource(Te,Re);this.builder.addOutput("CacheEndpoint",{Value:{"Fn::GetAtt":[Ee,"ConfigurationEndpoint.Address"]},Description:"Memcached configuration endpoint address"})}}let se=this.mergedConfig.infrastructure?.email;if(se){let he=se.domain||this.mergedConfig.infrastructure?.dns?.domain;if(he){let{emailIdentity:Ie,logicalId:Le}=k.verifyDomain({domain:he,slug:D,environment:t,enableDkim:se.enableDkim!==!1,dkimKeyLength:se.dkimKeyLength||"RSA_2048_BIT"});if(this.builder.addResource(Le,Ie),se.configurationSet!==!1){let{configurationSet:Te,logicalId:Ke}=k.createConfigurationSet({slug:D,environment:t});this.builder.addResource(Ke,Te)}let Re=se.hostedZoneId||this.mergedConfig.infrastructure?.dns?.hostedZoneId;if(Re){if(se.enableDkim!==!1)for(let et=1;et<=3;et++){let Rt=`DkimRecord${et}${he.replace(/\./g,"")}`;this.builder.addResource(Rt,{Type:"AWS::Route53::RecordSet",DependsOn:[Le],Properties:{HostedZoneId:Re,Name:{"Fn::GetAtt":[Le,`DkimDNSTokenName${et}`]},Type:"CNAME",TTL:1800,ResourceRecords:[{"Fn::GetAtt":[Le,`DkimDNSTokenValue${et}`]}]}})}let{record:Te,logicalId:Ke}=k.createSpfRecord(he,Re);this.builder.addResource(Ke,Te);let{record:Be,logicalId:_e}=k.createDmarcRecord(he,Re,{policy:"none",reportingEmail:se.dmarcReportingEmail||`dmarc-reports@${he}`});this.builder.addResource(_e,Be)}this.builder.addOutput("EmailDomain",{Value:he,Description:"SES verified email domain"});let Ee=se.server;if(Ee?.enabled&&Re){let Te=this.mergedConfig.environments[t]?.region||this.mergedConfig.project.region||"us-east-1",Ke=`${D}-${t}-email`,{role:Be,policy:_e,roleLogicalId:et,policyLogicalId:Rt}=k.createEmailLambdaRole({slug:D,environment:t,s3BucketArn:`arn:aws:s3:::${Ke}`,sesIdentityArn:`arn:aws:ses:${Te}:*:identity/${he}`});this.builder.addResource(et,Be),this.builder.addResource(Rt,_e);let{function:Et,permission:wt,logicalId:Pt,permissionLogicalId:Xt}=k.createInboundEmailLambda({slug:D,environment:t,roleArn:{"Fn::GetAtt":[et,"Arn"]},s3BucketName:Ke,organizedPrefix:"mailboxes/"});this.builder.addResource(Pt,Et),this.builder.addResource(Xt,wt);let _t=k.createInboundEmailSetup({slug:D,environment:t,domain:he,s3BucketName:Ke,s3KeyPrefix:"inbox/",region:Te,hostedZoneId:Re,lambdaFunctionArn:{"Fn::GetAtt":[Pt,"Arn"]}});for(let[zt,Wt]of Object.entries(_t.resources)){if(Wt.Type==="AWS::SES::ReceiptRule")Wt.DependsOn=[Xt,Pt];this.builder.addResource(zt,Wt)}this.builder.addOutput("InboundEmailLambda",{Value:{Ref:Pt},Description:"Inbound email processing Lambda function"}),this.builder.addOutput("EmailBucket",{Value:Ke,Description:"S3 bucket for email storage"});let nt=this.serverEipLogicalIds.get("app");if(nt&&Re){let Wt=`${Ee?.subdomain||"mail"}.${he}`,St=Wt.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(`${St}ARecord`,{Type:"AWS::Route53::RecordSet",Properties:{HostedZoneId:Re,Name:Wt,Type:"A",TTL:"300",ResourceRecords:[{Ref:nt}]}})}this.builder.addOutput("MailHost",{Value:`mail.${he}`,Description:"Mail server hostname for SMTP/IMAP clients"})}}}let fe=this.mergedConfig.infrastructure?.search;if(fe){let he=fe.vpc?{subnetIds:[{Ref:"PublicSubnet1"}],securityGroupIds:[]}:void 0;if(he)this.generateNetworkInfrastructure(D,t);let{domain:Ie,logicalId:Le}=Gt.createDomain({slug:D,environment:t,engineVersion:fe.engineVersion||"OpenSearch_2.11",instanceType:fe.instanceType||"t3.small.search",instanceCount:fe.instanceCount||1,volumeSize:fe.volumeSize||10,volumeType:fe.volumeType||"gp3",dedicatedMaster:fe.dedicatedMaster||!1,dedicatedMasterType:fe.dedicatedMasterType,dedicatedMasterCount:fe.dedicatedMasterCount||3,multiAz:fe.multiAz||!1,encryption:fe.encryption||{atRest:!0,nodeToNode:!0},advancedSecurity:fe.advancedSecurity,autoTune:fe.autoTune!==!1,vpc:he});this.builder.addResource(Le,Ie),this.builder.addOutput("SearchDomainEndpoint",{Value:{"Fn::GetAtt":[Le,"DomainEndpoint"]},Description:"OpenSearch domain endpoint"}),this.builder.addOutput("SearchDomainArn",{Value:{"Fn::GetAtt":[Le,"Arn"]},Description:"OpenSearch domain ARN"})}let Ae=this.mergedConfig.infrastructure?.fileSystem;if(Ae&&Object.keys(Ae).length>0){this.generateNetworkInfrastructure(D,t);for(let[he,Ie]of Object.entries(Ae)){let{fileSystem:Le,logicalId:Re}=rt.createFileSystem({slug:`${D}-${he}`,environment:t,encrypted:Ie.encrypted!==!1,performanceMode:Ie.performanceMode||"generalPurpose",throughputMode:Ie.throughputMode||"bursting",enableBackup:!0});this.builder.addResource(Re,Le);let{securityGroup:Ee,logicalId:Te}=rt.createEfsSecurityGroup({slug:`${D}-${he}`,environment:t,vpcId:{Ref:"VPC"},sourceCidrBlocks:["10.0.0.0/16"]});this.builder.addResource(Te,Ee);let{mountTargets:Ke,logicalIds:Be}=rt.createMultiAzMountTargets(Re,{slug:`${D}-${he}`,environment:t,subnetIds:[{Ref:"PublicSubnet1"},{Ref:"PublicSubnet2"}],securityGroupId:{Ref:Te}});for(let _e=0;_e<Ke.length;_e++)this.builder.addResource(Be[_e],Ke[_e]);this.builder.addOutput(`${he}FileSystemId`,{Value:{Ref:Re},Description:`EFS file system ID for ${he}`})}}let Pe=this.mergedConfig.infrastructure?.ai;if(Pe){let he=Pe.service||"ecs",Ie=Pe.models||["*"],Le=Pe.allowStreaming!==!1,Re;if(he==="ecs")Re=Ve.enableBedrockForEcs({slug:D,environment:t,models:Ie,allowStreaming:Le});else if(he==="ec2")Re=Ve.enableBedrockForEc2({slug:D,environment:t,models:Ie,allowStreaming:Le});else if(he==="lambda")Re=Ve.enableBedrockForLambda({slug:D,environment:t,models:Ie,allowStreaming:Le});else Re=Ve.createBedrockRole(he,{slug:D,environment:t,models:Ie,allowStreaming:Le});if(this.builder.addResource(Re.logicalId,Re.role),Pe.allowAsync){let{policy:Ee,logicalId:Te}=Ve.createBedrockPolicy({slug:D,environment:t,models:Ie,allowStreaming:Le,allowAsync:!0});this.builder.addResource(Te,Ee)}this.builder.addOutput("BedrockRoleArn",{Value:{"Fn::GetAtt":[Re.logicalId,"Arn"]},Description:"IAM role ARN with Bedrock permissions"})}}generateRealtimeResources(D,t){let m=this.mergedConfig.infrastructure?.realtime;if(!m)return;let A=m.name||`${D}-${t}-realtime`,_=m.scaling||{},z=m.storage||{type:"dynamodb"},U=_.handlerMemory||256,Y=_.handlerTimeout||30,X=`${D}${t}RealtimeConnections`.replace(/[^a-zA-Z0-9]/g,""),ne=`${D}${t}RealtimeChannels`.replace(/[^a-zA-Z0-9]/g,"");if(z.type==="dynamodb"){let Be=z.dynamodb||{},_e=Be.billingMode||"PAY_PER_REQUEST";this.builder.addResource(X,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${D}-${t}-realtime-connections`,BillingMode:_e,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"},..._e==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Be.readCapacity||5,WriteCapacityUnits:Be.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},..._e==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Be.readCapacity||5,WriteCapacityUnits:Be.writeCapacity||5}},...Be.pointInTimeRecovery&&{PointInTimeRecoverySpecification:{PointInTimeRecoveryEnabled:!0}},Tags:[{Key:"Name",Value:`${D}-${t}-realtime-connections`},{Key:"Environment",Value:t}]}}),this.builder.addResource(ne,{Type:"AWS::DynamoDB::Table",Properties:{TableName:`${D}-${t}-realtime-channels`,BillingMode:_e,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"},..._e==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Be.readCapacity||5,WriteCapacityUnits:Be.writeCapacity||5}}}],TimeToLiveSpecification:{AttributeName:"ttl",Enabled:!0},..._e==="PROVISIONED"&&{ProvisionedThroughput:{ReadCapacityUnits:Be.readCapacity||5,WriteCapacityUnits:Be.writeCapacity||5}},Tags:[{Key:"Name",Value:`${D}-${t}-realtime-channels`},{Key:"Environment",Value:t}]}})}let re=`${D}${t}RealtimeRole`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(re,{Type:"AWS::IAM::Role",Properties:{RoleName:`${D}-${t}-realtime-handler-role`,AssumeRolePolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]},ManagedPolicyArns:["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"],Policies:[{PolicyName:"RealtimeHandlerPolicy",PolicyDocument:{Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["dynamodb:GetItem","dynamodb:PutItem","dynamodb:DeleteItem","dynamodb:Query","dynamodb:Scan","dynamodb:UpdateItem"],Resource:[{"Fn::GetAtt":[X,"Arn"]},{"Fn::Sub":`\${${X}.Arn}/index/*`},{"Fn::GetAtt":[ne,"Arn"]},{"Fn::Sub":`\${${ne}.Arn}/index/*`}]},{Effect:"Allow",Action:"execute-api:ManageConnections",Resource:{"Fn::Sub":"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:*/*"}}]}}]}});let se=`${D}${t}RealtimeConnect`.replace(/[^a-zA-Z0-9]/g,""),fe=`${D}${t}RealtimeDisconnect`.replace(/[^a-zA-Z0-9]/g,""),Ae=`${D}${t}RealtimeMessage`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(se,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${D}-${t}-realtime-connect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:U,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateConnectHandlerCode()}},DependsOn:[re,X]}),this.builder.addResource(fe,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${D}-${t}-realtime-disconnect`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:U,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateDisconnectHandlerCode()}},DependsOn:[re,X,ne]}),this.builder.addResource(Ae,{Type:"AWS::Lambda::Function",Properties:{FunctionName:`${D}-${t}-realtime-message`,Runtime:"nodejs20.x",Handler:"index.handler",Role:{"Fn::GetAtt":[re,"Arn"]},MemorySize:U,Timeout:Y,Environment:{Variables:{CONNECTIONS_TABLE:{Ref:X},CHANNELS_TABLE:{Ref:ne},ENVIRONMENT:t}},Code:{ZipFile:this.generateMessageHandlerCode()}},DependsOn:[re,X,ne]});let Pe=`${D}${t}RealtimeApi`.replace(/[^a-zA-Z0-9]/g,"");this.builder.addResource(Pe,{Type:"AWS::ApiGatewayV2::Api",Properties:{Name:A,ProtocolType:"WEBSOCKET",RouteSelectionExpression:"$request.body.action",Tags:{Name:A,Environment:t}}});let he=`${se}Permission`,Ie=`${fe}Permission`,Le=`${Ae}Permission`;this.builder.addResource(he,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:se},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Pe}}/*/$connect`}}}),this.builder.addResource(Ie,{Type:"AWS::Lambda::Permission",Properties:{FunctionName:{Ref:fe},Action:"lambda:InvokeFunction",Principal:"apigateway.amazonaws.com",SourceArn:{"Fn::Sub":`arn:aws:execute-api:\${AWS::Region}:\${AWS::AccountId}:\${${Pe}}/*/$disconnect`}}}),this.builder.addResource(Le,{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}:\${${Pe}}/*/$default`}}});let Re=`${Pe}ConnectInteg`,Ee=`${Pe}DisconnectInteg`,Te=`${Pe}MessageInteg`;this.builder.addResource(Re,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Pe},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${se}.Arn}/invocations`}}}),this.builder.addResource(Ee,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Pe},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${fe}.Arn}/invocations`}}}),this.builder.addResource(Te,{Type:"AWS::ApiGatewayV2::Integration",Properties:{ApiId:{Ref:Pe},IntegrationType:"AWS_PROXY",IntegrationUri:{"Fn::Sub":`arn:aws:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/\${${Ae}.Arn}/invocations`}}}),this.builder.addResource(`${Pe}ConnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Pe},RouteKey:"$connect",AuthorizationType:"NONE",Target:{"Fn::Sub":`integrations/\${${Re}}`}}}),this.builder.addResource(`${Pe}DisconnectRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Pe},RouteKey:"$disconnect",Target:{"Fn::Sub":`integrations/\${${Ee}}`}}}),this.builder.addResource(`${Pe}DefaultRoute`,{Type:"AWS::ApiGatewayV2::Route",Properties:{ApiId:{Ref:Pe},RouteKey:"$default",Target:{"Fn::Sub":`integrations/\${${Te}}`}}});let Ke=`${Pe}Stage`;if(this.builder.addResource(Ke,{Type:"AWS::ApiGatewayV2::Stage",Properties:{ApiId:{Ref:Pe},StageName:t,AutoDeploy:!0,DefaultRouteSettings:{ThrottlingBurstLimit:_.messagesPerSecond||1000,ThrottlingRateLimit:_.messagesPerSecond||1000}}}),m.monitoring?.enabled){let Be=m.monitoring,_e=Be.notificationTopicArn;if(!_e&&Be.notificationEmails?.length){let et=`${Pe}AlarmTopic`;this.builder.addResource(et,{Type:"AWS::SNS::Topic",Properties:{TopicName:`${D}-${t}-realtime-alarms`,DisplayName:"Realtime WebSocket Alarms"}}),Be.notificationEmails.forEach((Rt,Et)=>{this.builder.addResource(`${et}Sub${Et}`,{Type:"AWS::SNS::Subscription",Properties:{TopicArn:{Ref:et},Protocol:"email",Endpoint:Rt}})}),_e={Ref:et}}if(Be.connectionThreshold)this.builder.addResource(`${Pe}ConnectionAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-realtime-connections`,AlarmDescription:`WebSocket connections exceed ${Be.connectionThreshold}`,MetricName:"ConnectCount",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:300,EvaluationPeriods:2,Threshold:Be.connectionThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Pe}}],..._e&&{AlarmActions:[_e]}}});if(Be.errorThreshold)this.builder.addResource(`${Pe}ErrorAlarm`,{Type:"AWS::CloudWatch::Alarm",Properties:{AlarmName:`${D}-${t}-realtime-errors`,AlarmDescription:`WebSocket errors exceed ${Be.errorThreshold}/min`,MetricName:"ExecutionError",Namespace:"AWS/ApiGateway",Statistic:"Sum",Period:60,EvaluationPeriods:3,Threshold:Be.errorThreshold,ComparisonOperator:"GreaterThanThreshold",Dimensions:[{Name:"ApiId",Value:{Ref:Pe}}],..._e&&{AlarmActions:[_e]}}})}this.builder.addOutput(`${Pe}Endpoint`,{Description:"WebSocket API endpoint URL",Value:{"Fn::Sub":`wss://\${${Pe}}.execute-api.\${AWS::Region}.amazonaws.com/${t}`},Export:{Name:{"Fn::Sub":"${AWS::StackName}-realtime-endpoint"}}}),this.builder.addOutput(`${Pe}Id`,{Description:"WebSocket API ID",Value:{Ref:Pe},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 ${Re} resources:`);let Ee=Le.getResources(),Te={};for(let Be of Object.values(Ee)){let _e=Be.Type;Te[_e]=(Te[_e]||0)+1}let Ke=Object.entries(Te).sort((Be,_e)=>_e[1]-Be[1]);for(let[Be,_e]of Ke)e(` - ${Be}: ${_e}`);e(`
|
|
310
310
|
Next steps:
|
|
311
311
|
1. Review the generated templates in ${_}/
|
|
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")}}),D.command("generate:preview","Preview what will be generated").action(async()=>{En("generate:preview",{message:"The legacy preview renderer has no stable output contract.",nextAction:"Run `cloud generate` into a temporary output directory and inspect the generated templates."})}),D.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 je(),A=t?.env||"production",{resolveProjectStackName:_}=await import("./chunk-
|
|
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")}}),D.command("generate:preview","Preview what will be generated").action(async()=>{En("generate:preview",{message:"The legacy preview renderer has no stable output contract.",nextAction:"Run `cloud generate` into a temporary output directory and inspect the generated templates."})}),D.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 je(),A=t?.env||"production",{resolveProjectStackName:_}=await import("./chunk-kzgt05w5.js"),z=t?.stack||_(m,A),U=m.project.region||"us-east-1";e(`Stack: ${z}`),e(`Region: ${U}`),e(`Environment: ${A}`),u("Generating template from configuration...");let Y=new xo({config:m,environment:A});Y.generate();let X=Y.toJSON(),ne=JSON.parse(X);u("Fetching deployed template...");let re=new v(U),se=null;try{let Ee=await re.getTemplate(z);if(Ee.TemplateBody)se=JSON.parse(Ee.TemplateBody)}catch(Ee){if(Ee.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(ne.Resources||{}).length;e(`
|
|
314
314
|
Resources to create: ${Te}`);let Ke={};for(let Be of Object.values(ne.Resources||{})){let _e=Be.Type;Ke[_e]=(Ke[_e]||0)+1}for(let[Be,_e]of Object.entries(Ke).sort((et,Rt)=>Rt[1]-et[1]))e(` + ${Be}: ${_e}`);e("\nRun `cloud deploy` to create this stack");return}throw Ee}u("Comparing templates...");let fe=se.Resources||{},Ae=ne.Resources||{},Pe=new Set(Object.keys(fe)),he=new Set(Object.keys(Ae)),Ie=[];for(let Ee of he)if(!Pe.has(Ee))Ie.push(Ee);let Le=[];for(let Ee of Pe)if(!he.has(Ee))Le.push(Ee);let Re=[];for(let Ee of he)if(Pe.has(Ee)){let Te=JSON.stringify(fe[Ee]),Ke=JSON.stringify(Ae[Ee]);if(Te!==Ke)Re.push(Ee)}if(Ie.length===0&&Le.length===0&&Re.length===0){n(`
|
|
315
315
|
No changes detected - infrastructure is up to date`);return}if(e(`
|
|
316
316
|
Changes detected:
|
|
317
317
|
`),Ie.length>0){n(`Resources to add (${Ie.length}):`);for(let Ee of Ie){let Te=Ae[Ee].Type;e(` + ${Ee} (${Te})`)}console.log()}if(Le.length>0){r(`Resources to remove (${Le.length}):`);for(let Ee of Le){let Te=fe[Ee].Type;e(` - ${Ee} (${Te})`)}console.log()}if(Re.length>0){a(`Resources to modify (${Re.length}):`);for(let Ee of Re){let Te=Ae[Ee].Type;e(` ~ ${Ee} (${Te})`)}console.log()}e("Summary:"),e(` - Add: ${Ie.length}`),e(` - Remove: ${Le.length}`),e(` - Modify: ${Re.length}`),e("\nRun `cloud deploy` to apply these changes")}catch(m){r(`Diff failed: ${m.message}`)}})}function Bi(D,t=0){let m=Number(D);return Number.isFinite(m)?m:t}function za(D){return Bi(D)*1024}function Wm(D){let t=D?.date?.trim(),m=D?.time?.trim();if(!t||!m)return null;let A=new Date(`${t}T${m}Z`);return Number.isFinite(A.getTime())?A.toISOString():null}function qm(D,t,m){let A;try{A=JSON.parse(D)}catch{return[]}let _=(A?.sysstat?.hosts??[]).flatMap((U)=>U.statistics??[]),z=[];for(let U of _){let Y=Wm(U.timestamp);if(!Y)continue;let X=new Date(Y).getTime();if(X<t.getTime()||X>m.getTime())continue;let ne=U["cpu-load"]?.find((se)=>se.cpu==="all")??U["cpu-load"]?.[0],re=Bi(ne?.idle,100);z.push({timestamp:Y,cpuUsedPercent:Math.max(0,Math.min(100,100-re)),cpuUserPercent:Bi(ne?.user),cpuSystemPercent:Bi(ne?.system),cpuIowaitPercent:Bi(ne?.iowait),memoryUsedPercent:Bi(U.memory?.["memused-percent"]),memoryUsedBytes:za(U.memory?.memused),memoryAvailableBytes:za(U.memory?.avail),swapUsedPercent:Bi(U.memory?.["swpused-percent"]),swapUsedBytes:za(U.memory?.swpused)})}return z.sort((U,Y)=>U.timestamp.localeCompare(Y.timestamp))}function Gm(D){return Object.fromEntries(Object.entries(D.time_series??{}).map(([t,m])=>[t,(m.values??[]).map(([A,_])=>({timestamp:new Date(Number(A)*1000).toISOString(),value:Bi(_,Number.NaN)})).filter((A)=>Number.isFinite(A.value))]))}function td(D){return D.length?D.reduce((t,m)=>t+m,0)/D.length:null}function Ua(D){return D.length?Math.max(...D):null}function Hm(D){return D.length?Math.min(...D):null}async function nd(D){let[t,m]=await Promise.all([D.client.getServerMetrics(D.serverId,{types:["cpu","disk","network"],start:D.from,end:D.to,step:D.step}),ke(D.remote.host,"LC_ALL=C sadf -j -- -u -r -S",D.remote).catch(()=>"")]),A=qm(m,D.from,D.to),_=Gm(t),U=(_.cpu??[]).map((se)=>Math.max(0,Math.min(100,se.value/Math.max(1,D.cores)))),Y=A.map((se)=>se.cpuUsedPercent),X=U.length?U:Y,ne=A.map((se)=>se.memoryUsedPercent),re=A.map((se)=>se.swapUsedPercent);return{from:D.from.toISOString(),to:D.to.toISOString(),cores:D.cores,host:A,provider:_,summary:{cpuAveragePercent:td(X),cpuPeakPercent:Ua(X),memoryAveragePercent:td(ne),memoryPeakPercent:Ua(ne),memoryMinimumAvailableBytes:Hm(A.map((se)=>se.memoryAvailableBytes)),swapPeakPercent:Ua(re)}}}function Km(D){if(D.length===0)throw Error("A server name cannot be empty.");if(D.length>253)throw Error(`'${D}' is longer than the 253 characters a hostname allows.`);for(let t of D.split(".")){if(t.length===0)throw Error(`'${D}' 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 rd(D,t,m){if(Km(t),D===t)throw Error(`'${D}' is already named that.`);if((await m.takenNames()).some((z)=>z===t&&z!==D))throw Error(`'${t}' is already taken. Server names have to be unique within a provider project.`);let _=[];if(m.providerName&&m.renameProvider){let{providerName:z,renameProvider:U}=m;_.push({id:"provider",title:"Rename the server at the provider",change:{from:D,to:t},satisfied:async()=>await z()===t,apply:()=>U(t)})}if(m.stateName&&m.writeStateName){let{stateName:z,writeStateName:U}=m;_.push({id:"state-pin",title:"Update the recorded name in the local driver state",change:{from:D,to:t},satisfied:async()=>{let Y=await z();return Y===void 0||Y===t},apply:()=>U(t)})}if(m.remoteHostname&&m.setRemoteHostname){let{remoteHostname:z,setRemoteHostname:U}=m;_.push({id:"hostname",title:"Set the hostname on the box",change:{from:D,to:t},satisfied:async()=>await z()===t,apply:()=>U(t)})}return _.push({id:"inventory",title:"Rename the fleet inventory record",change:{from:D,to:t},satisfied:async()=>m.inventoryName()===t,apply:async()=>{await m.renameInventory(t)}}),{operation:"server:rename",target:D,steps:_}}function id(D){let t=`'${D.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${D}/" /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 Jm(){let D=await je(),t=y(process.cwd(),D),m=new De(t.store),A=new Nn(m,[new it("aws"),new it("hetzner"),new it("ssh")]);return{controlPlane:t,store:m,service:A}}async function bi(D){let t=await Jm();try{return await D(t)}finally{t.controlPlane.store.close()}}var Mi=(D,t)=>{let m=D.store.list(D.controlPlane.project.id).find((A)=>A.id===t||A.name===t);if(!m)throw Error(`Server ${t} was not found.`);return m},yn=(D)=>{r(D instanceof Error?D.message:String(D)),process.exitCode=1};function Va(D,t){let m=Number.parseInt(D??"",10);return Number.isFinite(m)&&m>=0?m:t}function Ym(D="3h"){let t=D.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 Qo(D,t="%"){return D==null?"unavailable":`${D.toFixed(1)}${t}`}async function Zm(D,t){let m=await je();if(m.cloud?.provider!=="hetzner")throw Error("server:monitoring currently requires the Hetzner provider.");let A=t.env??process.env.CLOUD_ENV??"production",_=T(m,A),z=await pe(_);if(!z?.serverId)throw Error(`No pinned Hetzner server was found for ${_}.`);if(![z.serverName,String(z.serverId),z.stackName].includes(D))throw Error(`Refusing to read ${D}: project state is pinned to ${z.serverName}.`);let U=lr(m),Y=new We({apiToken:dr(U.apiToken,m)}),X=await Y.getServer(z.serverId),ne=z.publicIp??X.public_net.ipv4?.ip;if(!ne)throw Error(`Server ${X.name} has no public IPv4 address.`);let re=new Date,se=new Date(re.getTime()-Ym(t.range)),fe=Math.min(3600,Math.max(60,Va(t.step,60))),Ae=await nd({client:Y,serverId:X.id,cores:Math.max(1,Number(X.server_type.cores)||1),from:se,to:re,step:fe,remote:{host:ne,user:z.sshUser??U.sshUser,identityFile:U.sshPrivateKeyPath,connectTimeoutSec:15}});if(t.json){console.log(JSON.stringify({schemaVersion:1,server:X.name,result:Ae},null,2));return}c(["Server","Window","CPU average / peak","RAM average / peak","Minimum available","Swap peak"],[[X.name,`${Ae.from} to ${Ae.to}`,`${Qo(Ae.summary.cpuAveragePercent)} / ${Qo(Ae.summary.cpuPeakPercent)}`,`${Qo(Ae.summary.memoryAveragePercent)} / ${Qo(Ae.summary.memoryPeakPercent)}`,Ae.summary.memoryMinimumAvailableBytes==null?"unavailable":`${(Ae.summary.memoryMinimumAvailableBytes/1073741824).toFixed(2)} GB`,Qo(Ae.summary.swapPeakPercent)]])}function od(D){let t=D.targetType.prices?.find((m)=>m.location===D.location);return{serverId:D.serverId,serverName:D.serverName,location:D.location,from:D.currentType.name,to:D.targetType.name,capacityAvailable:D.capacityAvailable,alreadyComplete:D.alreadyComplete,upgradeDisk:D.upgradeDisk,target:{cores:D.targetType.cores,memoryGb:D.targetType.memory,diskGb:D.targetType.disk,architecture:D.targetType.architecture,cpuType:D.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 Qm(D){let t=od(D);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 Xm(D,t,m){let A=await je();if(A.cloud?.provider!=="hetzner")throw Error("server:resize currently requires the Hetzner provider.");let _=m.env??process.env.CLOUD_ENV??"production",z=T(A,_),U=await pe(z),Y=lr(A),X=new We({apiToken:dr(Y.apiToken,A)}),re=U&&(D===U.serverName||D===String(U.serverId)||D===U.stackName)?await X.getServer(U.serverId):(await X.listServers()).find((Be)=>Be.name===D||String(Be.id)===D);if(!re)throw Error(`Hetzner server ${D} was not found.`);if(U?.serverId&&U.serverId!==re.id)throw Error(`Refusing to resize ${D}: project state is pinned to server ${U.serverId}.`);let se=dt(t),fe=m.upgradeDisk!==!1,Ae=await ci(X,re.id,se,fe);if(m.json)console.log(JSON.stringify({schemaVersion:1,plan:od(Ae)},null,2));else Qm(Ae);if(!m.apply){if(!m.json)e(`Preview only. Re-run with --apply --confirm ${re.name} to execute.`);return}if(m.confirm!==re.name)throw Error(`Resize requires --confirm ${re.name}.`);let Pe=U?.publicIp??re.public_net.ipv4?.ip;if(!Pe)throw Error(`Server ${re.name} has no public IPv4 address for verification.`);let he={host:Pe,user:U?.sshUser??Y.sshUser,identityFile:Y.sshPrivateKeyPath,connectTimeoutSec:15,sshTimeoutMs:600000,routeTimeoutMs:20000},Ie=await ls(z),Le=new Date().toISOString(),Re={schemaVersion:1,operationId:Ie?.targetType===se&&Ie.serverId===re.id?Ie.operationId:crypto.randomUUID(),stackName:z,serverId:re.id,serverName:re.name,sourceType:Ae.currentType.name,targetType:se,upgradeDisk:fe,phase:"planning",status:"running",attempts:Ie?.targetType===se?Ie.attempts:0,startedAt:Ie?.targetType===se?Ie.startedAt:Le,updatedAt:Le,manifest:Ie?.targetType===se?Ie.manifest:void 0};await pt(Re);let Ee=Va(m.pollSeconds,900)*1000,Te=Va(m.maxWaitMinutes,0)*60000,Ke=Date.now();for(;;){let Be=await ci(X,re.id,se,fe);if(!Be.capacityAvailable&&!Be.alreadyComplete){if(Re={...Re,phase:"waiting-capacity",status:"waiting-capacity",updatedAt:new Date().toISOString()},await pt(Re),!m.wait){if(!m.json)e(`${se} has no capacity in ${Be.location}. The server remains running and unchanged. Use --wait to poll safely.`);return}if(Te>0&&Date.now()-Ke>=Te)throw Error(`Timed out waiting for ${se} capacity in ${Be.location}.`);if(!m.json)e(`Waiting ${Math.round(Ee/1000)} seconds for ${se} capacity...`);await Bun.sleep(Ee);continue}let _e=await ds(z);try{let et=await(async()=>{try{return Re={...Re,status:"running",attempts:Re.attempts+1,updatedAt:new Date().toISOString()},await pt(Re),await ns({client:X,serverId:re.id,targetType:se,upgradeDisk:fe,resumeFromOff:Re.phase==="shutting-down"||Re.phase==="resizing"||Re.phase==="powering-on"||Re.phase==="verifying",manifest:Re.manifest,hooks:{preflight:async()=>{let Rt=await is(he);return Re={...Re,manifest:Rt,updatedAt:new Date().toISOString()},await pt(Re),Rt},afterBoot:async(Rt,Et,wt,Pt)=>os(he,Et,wt,Pt),onPhase:async(Rt,Et)=>{if(Re={...Re,phase:Rt,status:Rt==="waiting-capacity"?"waiting-capacity":Rt==="complete"?"completed":"running",updatedAt:new Date().toISOString()},await pt(Re),!m.json)e(`${Rt}${Et?` ${JSON.stringify(Et)}`:""}`)}}})}finally{await _e()}})();if(Re={...Re,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 pt(Re),m.json)console.log(JSON.stringify({schemaVersion:1,result:et},null,2));else if(et.status==="completed"||et.status==="already-complete")n(`${re.name} is on ${se} and passed full post-boot verification.`);else if(et.status==="recovered"&&et.retryable&&m.wait){e(`Capacity race recovered safely. Waiting ${Math.round(Ee/1000)} seconds before retry.`),await Bun.sleep(Ee);continue}else if(et.status==="recovered")throw Error(`Resize did not complete and the original server was recovered: ${et.error}`);return}catch(et){throw Re={...Re,status:"failed",lastError:et instanceof Error?et.message:String(et),updatedAt:new Date().toISOString()},await pt(Re),et}}}async function ep(D,t){let m=await je();if(m.cloud?.provider!=="hetzner")throw Error("server:optimize currently requires the Hetzner provider.");let A=t.env??process.env.CLOUD_ENV??"production",_=T(m,A),z=await pe(_);if(!z?.serverId)throw Error(`No pinned Hetzner server was found for ${_}.`);if(![z.serverName,String(z.serverId),z.stackName].includes(D))throw Error(`Refusing to optimize ${D}: project state is pinned to ${z.serverName}.`);let U=lr(m),Y=new We({apiToken:dr(U.apiToken,m)}),X=await Y.getServer(z.serverId),ne=z.publicIp??X.public_net.ipv4?.ip;if(!ne)throw Error(`Server ${X.name} has no public IPv4 address.`);let re=to(m);if(t.json)console.log(JSON.stringify({schemaVersion:1,server:X.name,plan:re},null,2));else c(["Server","Firewall ports","Monitoring","Auto updates","Swap","SSH passwords"],[[X.name,re.firewallPorts.join(","),re.monitoring?"on":"off",re.autoUpdates?"on":"off",`${re.swapGb} GB`,"disabled"]]);if(!t.apply){if(!t.json)e(`Preview only. Re-run with --apply --confirm ${X.name} to execute.`);return}if(t.confirm!==X.name)throw Error(`Optimization requires --confirm ${X.name}.`);let se={host:ne,user:z.sshUser??U.sshUser,identityFile:U.sshPrivateKeyPath,connectTimeoutSec:15},fe=await en(se),Ae=fe.routeProbes.filter((wt)=>!wt.ok),Pe=m.infrastructure?.compute,he=Z(Pe)?new Set(["nginx.service","bun-gateway.service","ts-cloud-nginx.service"]):new Set,Ie=fe.failedUnits.filter((wt)=>!he.has(wt));if(Ie.length>0)throw Error(`Preflight found failed services: ${Ie.join(", ")}`);if(Ae.length>0)throw Error(`Preflight found unhealthy routes: ${Ae.map((wt)=>wt.domain).join(", ")}`);let Le=Z(Pe)?[]:Object.values(m.sites??{}).flatMap((wt)=>typeof wt?.port==="number"&&![80,443].includes(wt.port)?[wt.port]:[]),Re=Yr({allowSsh:Pe?.allowSsh!==!1,sitePorts:Le,allowedPorts:Pe?.firewall?.allowedPorts}),Ee=(z.firewallId?(await Y.listFirewalls()).find((wt)=>wt.id===z.firewallId):void 0)??(await Y.listFirewalls()).find((wt)=>wt.name===`${m.project.slug}-${A}-app-fw`);if(!Ee)throw Error(`Managed firewall for ${X.name} was not found.`);let Te=await Y.setFirewallRules(Ee.id,Re);await Promise.all(Te.map((wt)=>Y.waitForAction(wt.id))),await ss(m,se);let[Ke,Be]=await Promise.all([en(se),as(se)]),_e=cs(re,Be),et=pc(fe,Ke),Rt=Ke.routeProbes.filter((wt)=>!wt.ok);if(et.stoppedServices.length>0)_e.push(`services stopped: ${et.stoppedServices.join(", ")}`);if(et.changedRouteFragments.length>0)_e.push(`route fragments changed: ${et.changedRouteFragments.join(", ")}`);if(et.missingRouteIds.length>0)_e.push(`routes disappeared: ${et.missingRouteIds.join(", ")}`);if(et.changedReleaseLinks.length>0)_e.push(`release links changed: ${et.changedReleaseLinks.join(", ")}`);if(et.missingData.length>0)_e.push(`databases or volumes disappeared: ${et.missingData.join(", ")}`);if(Rt.length>0)_e.push(`unhealthy routes: ${Rt.map((wt)=>wt.domain).join(", ")}`);if(_e.length>0)throw Error(`Host optimization verification failed: ${_e.join("; ")}`);let Et={serverId:X.id,serverName:X.name,services:Ke.runningServices.length,routes:Ke.routeIds.length,healthyDomains:Ke.routeProbes.length,releaseLinks:Ke.releaseLinks.length,databasesAndVolumes:Ke.dataCatalog.length,report:Be};if(t.json)console.log(JSON.stringify({schemaVersion:1,result:Et},null,2));else n(`${X.name} passed full host, rpx route, service, release, and data verification.`)}async function tp(D,t,m){let A=await je(),_=m.env??"production",z=T(A,_);await bi(async(U)=>{let Y=Mi(U,D),X=U.store.list(U.controlPlane.project.id,!0),ne=Y.provider==="hetzner"&&Y.providerId?(()=>{try{let Ee=lr(A);return new We({apiToken:dr(Ee.apiToken,A)})}catch{return null}})():null,re=Y.providerId?Number(Y.providerId):Number.NaN,se=await pe(z),fe=se!=null&&se.serverId===re,Ae=new Fr,Pe=Y.trustState==="pinned",he={takenNames:async()=>{let Ee=X.map((Te)=>Te.name);if(ne)Ee.push(...(await ne.listServers()).map((Te)=>Te.name));return Ee},inventoryName:()=>U.store.get(Y.id)?.name??Y.name,renameInventory:(Ee)=>{U.store.update(Y.id,{name:Ee})},...ne&&Number.isFinite(re)?{providerName:async()=>(await ne.getServer(re)).name,renameProvider:async(Ee)=>{await ne.renameServer(re,Ee)}}:{},...fe?{stateName:async()=>(await pe(z))?.serverName,writeStateName:async(Ee)=>{let Te=await pe(z);if(Te)await ut(z,{...Te,serverName:Ee})}}:{},...Pe?{remoteHostname:async()=>{let Ee=await Ae.exec(Y,"hostname");return Ee.code===0?Ee.stdout.trim():void 0},setRemoteHostname:async(Ee)=>{let Te=await Ae.exec(Y,id(Ee));if(Te.code!==0)throw Error(Te.stderr.trim()||`hostname exited ${Te.code}`)}}:{}},Ie=await rd(Y.name,t,he),Le=await Ts(Ie);if(!m.apply){let Ee=[ne?"":"provider record (no provider id or no API token)",fe?"":"local state pin (none points at this server)",Pe?"":"box hostname (host key is not pinned — run server:validate first)"].filter(Boolean);if(m.json){console.log(JSON.stringify({schemaVersion:1,operation:Ie.operation,target:Ie.target,rename:{from:Y.name,to:t},notCovered:Ee,steps:Le.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 Os(Ie,Le))console.log(Te);for(let Te of Ee)console.log(` not covered: ${Te}`);console.log(" Re-run with --apply to perform it.");return}let Re=await Ls(Ie,Le,{log:(Ee)=>e(Ee),audit:(Ee)=>U.controlPlane.store.appendEvent({projectId:U.controlPlane.project.id,resourceId:Y.resourceId,type:`${Ee.operation}.${Ee.step}.${Ee.state}`,level:Ee.state==="failed"?"error":"info",payload:{target:Ee.target,renameTo:t,...Ee.error?{error:Ee.error}:{}}})});if(m.json)console.log(JSON.stringify({schemaVersion:1,outcome:Re},null,2));if(!Re.success){let Ee=Re.steps.find((Te)=>Te.state==="failed");throw Error(`${Ie.operation} stopped at '${Ee?.title}': ${Ee?.error}. Earlier steps stayed applied; re-run the same command to continue from here.`)}if(!m.json)n(`Renamed ${Y.name} to ${t}.`)})}function Wa(D){D.command("capabilities [server]","Show provider and target operation support").option("--json","Print structured JSON").action(async(t,m)=>{try{await bi(async(A)=>{let _=t?[Mi(A,t)]:A.store.list(A.controlPlane.project.id),z=_.flatMap((U)=>Object.entries(U.capabilities).map(([Y,X])=>({server:U.name,provider:U.provider,action:Y,...X})));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){yn(A)}}),D.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 Xm(t,m,A)}catch(_){yn(_)}}),D.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 ep(t,m)}catch(A){yn(A)}}),D.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 Zm(t,m)}catch(A){yn(A)}}),D.command("server:list","List fleet servers").option("--json","Print structured JSON").action(async(t)=>{try{await bi(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((_)=>[_.name,_.providerId??"—",`${_.provider} / ${_.region??"external"}`,`${_.status} / ${_.trustState}`,_.roles.join(","),`${_.capacity.cpu??0} / ${_.capacity.memoryBytes??0}`,_.heartbeatAt??"never"]))})}catch(m){yn(m)}}),D.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 bi(async(A)=>{let _=m.provider;if(!["aws","hetzner","ssh"].includes(_)||!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:_,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){yn(A)}}),D.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 bi(async(A)=>{let _=Mi(A,t);if(_=await A.service.test(_.id),_.trustState==="rotation_pending"){if(!m.acceptHostKey)throw Error(`Host key changed to ${_.pendingHostKey}; review it first.`);_=A.service.reviewHostKey(_.id,m.acceptHostKey)}let z=await A.service.validate(_.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){yn(A)}}),D.command("server:bootstrap <name>","Preview or queue bootstrap").option("--apply","Apply reviewed plan").action(async(t,m)=>{try{await bi(async(A)=>{let _=A.service.bootstrap(Mi(A,t).id,!!m.apply);if(_.preview)c(["Step"],_.steps.map((z)=>[z]));else n(`Bootstrap queued: ${_.operation?.id}`)})}catch(A){yn(A)}}),D.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 tp(t,m,A)}catch(_){yn(_)}}),D.command("server:drain <name>","Drain without terminating").option("--complete","Mark movement complete").action(async(t,m)=>{try{await bi(async(A)=>n(A.service.drain(Mi(A,t).id,!!m.complete).status))}catch(A){yn(A)}}),D.command("server:uncordon <name>","Return a server to scheduling").action(async(t)=>{try{await bi(async(m)=>{m.service.uncordon(Mi(m,t).id),n(`Uncordoned ${t}.`)})}catch(m){yn(m)}}),D.command("server:archive <name>","Archive inventory without termination").option("--confirm <name>","Exact name").action(async(t,m)=>{try{await bi(async(A)=>{A.service.archive(Mi(A,t).id,m.confirm??""),n(`Archived ${t}; provider infrastructure was not terminated.`)})}catch(A){yn(A)}}),D.command("server:ssh <name>","Open strict SSH using the enrolled endpoint").action(async(t)=>{try{await bi(async(m)=>{let A=Mi(m,t);if(A.trustState!=="pinned")throw Error("Validate and pin the server host key first.");let _=Bun.spawn(["ssh","-p",String(A.sshPort),"-o","StrictHostKeyChecking=yes",`${A.sshUser}@${A.endpoint}`],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}),z=await _.exited;if(z)process.exitCode=z})}catch(m){yn(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"]])D.command(t,m).action(async(...A)=>En(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 sd,mkdirSync as np,readFileSync as rp,writeFileSync as ip}from"node:fs";import{join as ad,resolve as op}from"node:path";var qa=(D)=>{r(D instanceof Error?D.message:String(D)),process.exitCode=1};async function sp(){try{return await je()}catch{return}}function ap(D){if(D===void 0)return;let t=Number(D);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`--port must be a port number, not '${D}'.`);return t}function cd(D,t,m){let A=D?.ssh?.hosts?.find((_)=>_.host===t);return new fr({hosts:t?[A??{host:t}]:D?.ssh?.hosts,user:m.user,port:ap(m.port),privateKeyPath:m.key,hostKey:D?.ssh?.hostKey,sudo:D?.ssh?.sudo,profile:m.profile??D?.ssh?.profile,publicIp:D?.ssh?.publicIp,lan:D?.ssh?.lan})}var ld=1073741824;function Ga(D){D.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 sp(),_=cd(A,t,m),z=t??_.targetHost(),U=await _.ensureHostKey(z),{facts:Y,findings:X}=await _.preflight(z);if(m.json)console.log(JSON.stringify({schemaVersion:1,host:z,hostKeyFingerprint:U.fingerprint,facts:Y,findings:X},null,2));else{if(i(`Preflight ${z}`),U.pinnedNow)e(`Pinned host key ${U.fingerprint}`);if(e(`${Y.os} on ${Y.arch}, ${Y.cpuCores} cores, ${(Y.memoryBytes/ld).toFixed(1)} GiB memory, ${(Y.diskFreeBytes/ld).toFixed(1)} GiB free`),Y.piModel)e(Y.piModel);if(Y.lanIp)e(`LAN address ${Y.lanIp}`);if(console.log(Zr(X)),gr(X))r("The host is not ready; fix the errors above and run the preflight again.");else n("The host is ready to adopt.")}if(gr(X))process.exitCode=1}catch(A){qa(A)}}),D.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 je(),A=t.env??"production";if(t.dryRun){let U=mr(m),[Y]=U.hosts;console.log(pr({config:m,environment:A,profile:U.profile,sudoUser:Y.user!=="root"?Y.user:void 0,lan:U.lan}));return}let _=cd(m,void 0,{});i(`Adopt ${_.targetHost()}`);let z=await _.provisionComputeInfrastructure({config:m,environment:A});n(`${z.appPublicIp} is bootstrapped; deploy with: cloud deploy --env ${A}`)}catch(m){qa(m)}}),D.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 Zi))throw Error(`--os must be raspberry-pi-os or ubuntu, not '${m}'.`);let A=await je(),_=t.env??"production",z=mr(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"),Y=Qr(t.key??"~/.ssh/id_ed25519.pub");if(!sd(Y))throw Error(`Public key not found at ${Y}; pass --key <path to a .pub file>.`);let X=rp(Y,"utf8").trim(),ne;if(t.wifiSsid){let Pe=t.wifiPassphraseEnv??"WIFI_PASSWORD",he=process.env[Pe];if(!he)throw Error(`--wifi-ssid needs the passphrase in $${Pe} (set it in the environment, not on the command line).`);if(!t.wifiCountry)throw Error("--wifi-country <cc> is required with --wifi-ssid.");ne={ssid:t.wifiSsid,passphrase:he,country:t.wifiCountry}}let re=pr({config:A,environment:_,profile:z.profile,sudoUser:U!=="root"?U:void 0,lan:z.lan}),se=eo({hostname:t.hostname,user:U,publicKey:X,timezone:t.timezone,locale:t.locale,wifi:ne},re,{os:m}),fe=op(t.out);np(fe,{recursive:!0});let Ae=Object.keys(se.files).filter((Pe)=>sd(ad(fe,Pe)));if(Ae.length>0&&!t.force)throw Error(`${Ae.join(", ")} already exist in ${fe}; pass --force to overwrite.`);for(let[Pe,he]of Object.entries(se.files))ip(ad(fe,Pe),he,{mode:384});i(`First boot for ${t.hostname}`),n(`Wrote ${Object.keys(se.files).join(", ")} to ${fe}`),console.log(se.instructions)}catch(m){qa(m)}})}async function cp(){let D=await je(),t=y(process.cwd(),D),m=new xn(t.store),A=new _n(m,new De(t.store));return{controlPlane:t,store:m,service:A}}async function Xo(D){let t=await cp();try{await D(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var js=(D)=>Object.fromEntries(String(D??"").split(",").map((t)=>t.split("=")).filter((t)=>t[0]&&t[1])),dd=(D)=>Object.fromEntries(Object.entries(js(D)).map(([t,m])=>[t,Number(m)]));function Ha(D){D.command("pool:list","List capacity pools and reservations").option("--json","Print structured JSON").action(async(t)=>Xo((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((_)=>[_.name,_.purpose,_.backend,`${_.region??"any"} / ${_.architecture??"any"}`,_.status,JSON.stringify(_.capacity),String(_.concurrency)]))})),D.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)=>Xo((A)=>{let{purpose:_,backend:z}=m;if(!["application","build","worker","monitoring","backup"].includes(_)||!["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:_,backend:z,region:m.region,architecture:m.arch,labels:js(m.labels),requiredServerLabels:js(m.selector),toleratedTaints:[],capacity:{cpu:0,memoryBytes:0,diskBytes:0,gpu:0,...dd(m.capacity)},maxWorkloads:Number(m.max),costWeight:1,concurrency:Number(m.concurrency),ephemeralWorkspaces:!0,allowProductionSecrets:!1,status:"active"});n(`Created ${U.name} (${U.id}).`)})),D.command("pool:add-server <pool> <server>","Attach an enrolled server to a pool").action(async(t,m)=>Xo((A)=>{let _=A.store.listPools(A.controlPlane.project.id).find((U)=>U.id===t||U.name===t),z=new De(A.controlPlane.store).list(A.controlPlane.project.id).find((U)=>U.id===m||U.name===m);if(!_||!z)throw Error("Pool or server was not found.");A.store.addMember(_.id,z.id),n(`Attached ${z.name} to ${_.name}.`)})),D.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)=>Xo((m)=>{let A=m.service.explain(m.controlPlane.project.id,{purpose:t.purpose,resources:dd(t.resources),region:t.region,architecture:t.arch,labels:js(t.labels)});if(t.json)console.log(JSON.stringify(A,null,2));else c(["Pool","Server","Eligible","Available","Fit / spread / cost","Reasons"],A.map((_)=>[_.poolName,_.serverId??"provider-managed",_.eligible?"yes":"no",JSON.stringify(_.available),`${_.score.fit.toFixed(2)} / ${_.score.spread} / ${_.score.cost}`,_.reasons.join("; ")||"eligible"]))})),D.command("pool:drain <pool>","Move stateless placements and block stateful placements").action(async(t)=>Xo((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 _=m.service.drainPool(A.id);n(`Draining ${A.name}: moved ${_.moved.length}, blocked ${_.blocked.length} stateful placement(s).`)}))}async function lp(){let D=await je(),t=y(process.cwd(),D),m=new On(t.store),A=new $n(m);return{controlPlane:t,store:m,service:A}}async function uo(D){let t=await lp();try{await D(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var us=(D,t)=>{let m=D.store.list(D.controlPlane.project.id).find((A)=>A.id===t||A.name===t);if(!m)throw Error("Regional topology was not found.");return m},dp=(D)=>D.split(",").map((t,m)=>{let[A,_="aws"]=t.split(":");return{region:String(A),provider:_,role:m===0?"primary":"secondary"}}),up=(D)=>{let t=new Set(["s3","dynamodb","secrets"]),m=D.split(",").map((_)=>_.trim()),A=m.find((_)=>!t.has(_));if(A)throw Error(`Unsupported replication kind: ${A}`);return m};function Ka(D){D.command("region:list","List regional topology, traffic, and replication health").option("--json","Print structured JSON").action(async(t)=>uo((m)=>{let A=m.store.list(m.controlPlane.project.id).map((_)=>({topology:_,targets:m.store.targets(_.id),channels:m.store.channels(_.id),route:m.store.route(_.id),executions:m.store.executions(_.id)}));if(t.json)console.log(JSON.stringify(A,null,2));else c(["Name","Hostname","Active","Status","Revision","Replication"],A.map((_)=>[_.topology.name,_.topology.hostname,_.topology.activeRegion,_.topology.status,_.topology.revision??"—",`${_.channels.filter((z)=>z.status==="in_sync").length}/${_.channels.length} in sync`]))})),D.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)=>uo((A)=>{if(!m.hostname||!m.regions)throw Error("--hostname and --regions are required.");let _=A.service.create({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,name:t,hostname:m.hostname,regions:dp(m.regions),trafficPolicy:"active_passive",dataPolicy:{replicate:up(String(m.replicate)),maxLagSeconds:Number(m.maxLag),retainOnDestroy:!0},dnsProvider:"route53",cdnEnabled:!!m.cdn,wafEnabled:!!m.waf});n(`Created ${_.name} (${_.id}); run region:rollout with an immutable manifest.`)})),D.command("region:plan <name> <operation>","Preview exact regional execution steps").option("--target <region>","Failover or failback region").action(async(t,m,A)=>uo((_)=>c(["Step"],_.service.plan(us(_,t).id,m,A.target).map((z)=>[z])))),D.command("region:rollout <name> <revision>","Queue secondary-first regional stack rollout").option("--manifest <file>","Regional stack manifest JSON").action(async(t,m,A)=>uo(async(_)=>{let z=A.manifest?await Bun.file(A.manifest).json():{},U=_.service.enqueue({topologyId:us(_,t).id,kind:"rollout",revision:m,manifest:z});n(`Regional rollout queued: ${U.operation.id}`)})),D.command("region:failover <name> <region>","Queue health and RPO-gated failover").action(async(t,m)=>uo((A)=>{let _=A.service.enqueue({topologyId:us(A,t).id,kind:"failover",targetRegion:m});n(`Failover queued: ${_.operation.id}`)})),D.command("region:failback <name> <region>","Queue health and RPO-gated failback").action(async(t,m)=>uo((A)=>{let _=A.service.enqueue({topologyId:us(A,t).id,kind:"failback",targetRegion:m});n(`Failback queued: ${_.operation.id}`)})),D.command("region:destroy <name>","Drain traffic and delete regional stacks while retaining data").option("--confirm <name>","Exact topology name").action(async(t,m)=>uo((A)=>{let _=A.service.enqueue({topologyId:us(A,t).id,kind:"destroy",confirmation:m.confirm});n(`Regional teardown queued: ${_.operation.id}; replicated data remains retained.`)}))}var ms="0.16.40";async function pp(){let D=await je(),t=y(process.cwd(),D),m=new Ln(t.store),A=new Mn(m),_=["releases","artifacts","operations","logs","previews","snapshots","orphaned_resources"].map((z)=>new Un(z,t.store,t.project.id,t.organization.id));return{controlPlane:t,store:m,service:A,cleanup:_}}async function Ii(D){let t=await pp();try{await D(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}function Ja(D){D.command("maintenance:list","List updates, campaigns, cleanup plans, and DR drills").option("--json","Print structured JSON").action(async(t)=>Ii((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)},_=[...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"],_)})),D.command("maintenance:key:add <name> <pem>","Trust an Ed25519 update signing key").action(async(t,m)=>Ii(async(A)=>{let _=A.store.addTrustedKey({organizationId:A.controlPlane.organization.id,name:t,publicKeyPem:await Bun.file(m).text()});n(`Trusted ${_.name}: ${_.fingerprint}`)})),D.command("maintenance:manifest <document> <signature> <key>","Verify and import a signed update manifest").action(async(t,m,A)=>Ii(async(_)=>{let z=await Bun.file(t).json(),U=(await Bun.file(m).text()).trim(),Y=_.store.importManifest({organizationId:_.controlPlane.organization.id,keyId:A,document:z,signature:U,current:{schemaVersion:_.controlPlane.store.health().schemaVersion,currentVersion:ms,platform:process.platform,architecture:process.arch}});n(`Verified ${Y.version}; ${Y.compatibility.compatible?"compatible":"blocked: "+Y.compatibility.reasons.join("; ")}`)})),D.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)=>Ii((_)=>{let z=_.store.createWindow({projectId:_.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}).`)})),D.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)=>Ii((_)=>{let z=new De(_.controlPlane.store).list(_.controlPlane.project.id).filter((X)=>X.status==="ready").map((X)=>({serverId:X.id,target:X.name,previousVersion:X.bootstrapVersion??ms}));if(!z.length)z.push({target:"control-plane",previousVersion:ms});let U=_.store.createCampaign({projectId:_.controlPlane.project.id,manifestId:t,fromVersion:ms,strategy:A.strategy==="rolling"?"rolling":"canary",batchSize:Number(A.batch),healthGate:{kind:"platform"},backupId:m,targets:z}),Y=_.service.enqueueCampaign(U.id);n(`Staged upgrade queued: ${Y.operation.id}`)})),D.command("cleanup:preview <kind>","Discover a bounded cleanup candidate set without deleting").option("--older-than <days>","Minimum age",{default:"30"}).action(async(t,m)=>Ii(async(A)=>{let _=await A.service.previewCleanup({projectId:A.controlPlane.project.id,kind:t,criteria:{olderThanDays:Number(m.olderThan)}},A.cleanup);c(["ID","Candidate","Bytes"],_.candidates.map((z)=>[z.id,z.label,String(z.bytes)])),e(`Apply with: cloud cleanup:apply ${_.id} --confirm "DELETE ${_.candidates.length} ${_.kind}"`)})),D.command("cleanup:apply <plan>","Apply an unchanged cleanup preview").option("--confirm <text>","Exact preview confirmation").action(async(t,m)=>Ii((A)=>{let _=A.service.enqueueCleanup(t,String(m.confirm??""));n(`Cleanup queued: ${_.operation.id}`)})),D.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)=>Ii((_)=>{let z=_.store.createDrill({projectId:_.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}.`)})),D.command("dr:run <drill>","Queue an isolated disaster-recovery drill").action(async(t)=>Ii((m)=>{let A=m.service.enqueueDrill(t);n(`DR drill queued: ${A.operation.id}`)}))}async function Ya(){try{return(await je()).project.region||process.env.AWS_REGION||"us-east-1"}catch{return process.env.AWS_REGION||"us-east-1"}}function Za(D){D.command("function:list","List all Lambda functions").option("--region <region>","AWS region").action(async(t)=>{i("Lambda Functions");let m=t?.region||await Ya(),A=new I(m);try{let{Functions:_=[]}=await A.listFunctions({MaxItems:50});if(!_.length){e("No functions found");return}c(["Name","Runtime","Memory","Timeout","Last Modified"],_.map((z)=>[z.FunctionName??"-",z.Runtime??"-",z.MemorySize?`${z.MemorySize} MB`:"-",z.Timeout?`${z.Timeout}s`:"-",z.LastModified??"-"]))}catch(_){r(`Failed to list functions: ${_.message}`),process.exitCode=1}}),D.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 Ya(),_=new I(A),z=new s("Invoking function...");z.start();try{let U=await _.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 Jm(){let D=await je(),t=y(process.cwd(),D),m=new De(t.store),A=new Nn(m,[new it("aws"),new it("hetzner"),new it("ssh")]);return{controlPlane:t,store:m,service:A}}async function bi(D){let t=await Jm();try{return await D(t)}finally{t.controlPlane.store.close()}}var Mi=(D,t)=>{let m=D.store.list(D.controlPlane.project.id).find((A)=>A.id===t||A.name===t);if(!m)throw Error(`Server ${t} was not found.`);return m},yn=(D)=>{r(D instanceof Error?D.message:String(D)),process.exitCode=1};function Va(D,t){let m=Number.parseInt(D??"",10);return Number.isFinite(m)&&m>=0?m:t}function Ym(D="3h"){let t=D.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 Qo(D,t="%"){return D==null?"unavailable":`${D.toFixed(1)}${t}`}async function Zm(D,t){let m=await je();if(m.cloud?.provider!=="hetzner")throw Error("server:monitoring currently requires the Hetzner provider.");let A=t.env??process.env.CLOUD_ENV??"production",_=T(m,A),z=await pe(_);if(!z?.serverId)throw Error(`No pinned Hetzner server was found for ${_}.`);if(![z.serverName,String(z.serverId),z.stackName].includes(D))throw Error(`Refusing to read ${D}: project state is pinned to ${z.serverName}.`);let U=lr(m),Y=new We({apiToken:dr(U.apiToken,m)}),X=await Y.getServer(z.serverId),ne=z.publicIp??X.public_net.ipv4?.ip;if(!ne)throw Error(`Server ${X.name} has no public IPv4 address.`);let re=new Date,se=new Date(re.getTime()-Ym(t.range)),fe=Math.min(3600,Math.max(60,Va(t.step,60))),Ae=await nd({client:Y,serverId:X.id,cores:Math.max(1,Number(X.server_type.cores)||1),from:se,to:re,step:fe,remote:{host:ne,user:z.sshUser??U.sshUser,identityFile:U.sshPrivateKeyPath,connectTimeoutSec:15}});if(t.json){console.log(JSON.stringify({schemaVersion:1,server:X.name,result:Ae},null,2));return}c(["Server","Window","CPU average / peak","RAM average / peak","Minimum available","Swap peak"],[[X.name,`${Ae.from} to ${Ae.to}`,`${Qo(Ae.summary.cpuAveragePercent)} / ${Qo(Ae.summary.cpuPeakPercent)}`,`${Qo(Ae.summary.memoryAveragePercent)} / ${Qo(Ae.summary.memoryPeakPercent)}`,Ae.summary.memoryMinimumAvailableBytes==null?"unavailable":`${(Ae.summary.memoryMinimumAvailableBytes/1073741824).toFixed(2)} GB`,Qo(Ae.summary.swapPeakPercent)]])}function od(D){let t=D.targetType.prices?.find((m)=>m.location===D.location);return{serverId:D.serverId,serverName:D.serverName,location:D.location,from:D.currentType.name,to:D.targetType.name,capacityAvailable:D.capacityAvailable,alreadyComplete:D.alreadyComplete,upgradeDisk:D.upgradeDisk,target:{cores:D.targetType.cores,memoryGb:D.targetType.memory,diskGb:D.targetType.disk,architecture:D.targetType.architecture,cpuType:D.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 Qm(D){let t=od(D);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 Xm(D,t,m){let A=await je();if(A.cloud?.provider!=="hetzner")throw Error("server:resize currently requires the Hetzner provider.");let _=m.env??process.env.CLOUD_ENV??"production",z=T(A,_),U=await pe(z),Y=lr(A),X=new We({apiToken:dr(Y.apiToken,A)}),re=U&&(D===U.serverName||D===String(U.serverId)||D===U.stackName)?await X.getServer(U.serverId):(await X.listServers()).find((Be)=>Be.name===D||String(Be.id)===D);if(!re)throw Error(`Hetzner server ${D} was not found.`);if(U?.serverId&&U.serverId!==re.id)throw Error(`Refusing to resize ${D}: project state is pinned to server ${U.serverId}.`);let se=dt(t),fe=m.upgradeDisk!==!1,Ae=await ci(X,re.id,se,fe);if(m.json)console.log(JSON.stringify({schemaVersion:1,plan:od(Ae)},null,2));else Qm(Ae);if(!m.apply){if(!m.json)e(`Preview only. Re-run with --apply --confirm ${re.name} to execute.`);return}if(m.confirm!==re.name)throw Error(`Resize requires --confirm ${re.name}.`);let Pe=U?.publicIp??re.public_net.ipv4?.ip;if(!Pe)throw Error(`Server ${re.name} has no public IPv4 address for verification.`);let he={host:Pe,user:U?.sshUser??Y.sshUser,identityFile:Y.sshPrivateKeyPath,connectTimeoutSec:15,sshTimeoutMs:600000,routeTimeoutMs:20000},Ie=await ls(z),Le=new Date().toISOString(),Re={schemaVersion:1,operationId:Ie?.targetType===se&&Ie.serverId===re.id?Ie.operationId:crypto.randomUUID(),stackName:z,serverId:re.id,serverName:re.name,sourceType:Ae.currentType.name,targetType:se,upgradeDisk:fe,phase:"planning",status:"running",attempts:Ie?.targetType===se?Ie.attempts:0,startedAt:Ie?.targetType===se?Ie.startedAt:Le,updatedAt:Le,manifest:Ie?.targetType===se?Ie.manifest:void 0};await pt(Re);let Ee=Va(m.pollSeconds,900)*1000,Te=Va(m.maxWaitMinutes,0)*60000,Ke=Date.now();for(;;){let Be=await ci(X,re.id,se,fe);if(!Be.capacityAvailable&&!Be.alreadyComplete){if(Re={...Re,phase:"waiting-capacity",status:"waiting-capacity",updatedAt:new Date().toISOString()},await pt(Re),!m.wait){if(!m.json)e(`${se} has no capacity in ${Be.location}. The server remains running and unchanged. Use --wait to poll safely.`);return}if(Te>0&&Date.now()-Ke>=Te)throw Error(`Timed out waiting for ${se} capacity in ${Be.location}.`);if(!m.json)e(`Waiting ${Math.round(Ee/1000)} seconds for ${se} capacity...`);await Bun.sleep(Ee);continue}let _e=await ds(z);try{let et=await(async()=>{try{return Re={...Re,status:"running",attempts:Re.attempts+1,updatedAt:new Date().toISOString()},await pt(Re),await ns({client:X,serverId:re.id,targetType:se,upgradeDisk:fe,resumeFromOff:Re.phase==="shutting-down"||Re.phase==="resizing"||Re.phase==="powering-on"||Re.phase==="verifying",manifest:Re.manifest,hooks:{preflight:async()=>{let Rt=await is(he);return Re={...Re,manifest:Rt,updatedAt:new Date().toISOString()},await pt(Re),Rt},afterBoot:async(Rt,Et,wt,Pt)=>os(he,Et,wt,Pt),onPhase:async(Rt,Et)=>{if(Re={...Re,phase:Rt,status:Rt==="waiting-capacity"?"waiting-capacity":Rt==="complete"?"completed":"running",updatedAt:new Date().toISOString()},await pt(Re),!m.json)e(`${Rt}${Et?` ${JSON.stringify(Et)}`:""}`)}}})}finally{await _e()}})();if(Re={...Re,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 pt(Re),m.json)console.log(JSON.stringify({schemaVersion:1,result:et},null,2));else if(et.status==="completed"||et.status==="already-complete")n(`${re.name} is on ${se} and passed full post-boot verification.`);else if(et.status==="recovered"&&et.retryable&&m.wait){e(`Capacity race recovered safely. Waiting ${Math.round(Ee/1000)} seconds before retry.`),await Bun.sleep(Ee);continue}else if(et.status==="recovered")throw Error(`Resize did not complete and the original server was recovered: ${et.error}`);return}catch(et){throw Re={...Re,status:"failed",lastError:et instanceof Error?et.message:String(et),updatedAt:new Date().toISOString()},await pt(Re),et}}}async function ep(D,t){let m=await je();if(m.cloud?.provider!=="hetzner")throw Error("server:optimize currently requires the Hetzner provider.");let A=t.env??process.env.CLOUD_ENV??"production",_=T(m,A),z=await pe(_);if(!z?.serverId)throw Error(`No pinned Hetzner server was found for ${_}.`);if(![z.serverName,String(z.serverId),z.stackName].includes(D))throw Error(`Refusing to optimize ${D}: project state is pinned to ${z.serverName}.`);let U=lr(m),Y=new We({apiToken:dr(U.apiToken,m)}),X=await Y.getServer(z.serverId),ne=z.publicIp??X.public_net.ipv4?.ip;if(!ne)throw Error(`Server ${X.name} has no public IPv4 address.`);let re=to(m);if(t.json)console.log(JSON.stringify({schemaVersion:1,server:X.name,plan:re},null,2));else c(["Server","Firewall ports","Monitoring","Auto updates","Swap","SSH passwords"],[[X.name,re.firewallPorts.join(","),re.monitoring?"on":"off",re.autoUpdates?"on":"off",`${re.swapGb} GB`,"disabled"]]);if(!t.apply){if(!t.json)e(`Preview only. Re-run with --apply --confirm ${X.name} to execute.`);return}if(t.confirm!==X.name)throw Error(`Optimization requires --confirm ${X.name}.`);let se={host:ne,user:z.sshUser??U.sshUser,identityFile:U.sshPrivateKeyPath,connectTimeoutSec:15},fe=await en(se),Ae=fe.routeProbes.filter((wt)=>!wt.ok),Pe=m.infrastructure?.compute,he=Z(Pe)?new Set(["nginx.service","bun-gateway.service","ts-cloud-nginx.service"]):new Set,Ie=fe.failedUnits.filter((wt)=>!he.has(wt));if(Ie.length>0)throw Error(`Preflight found failed services: ${Ie.join(", ")}`);if(Ae.length>0)throw Error(`Preflight found unhealthy routes: ${Ae.map((wt)=>wt.domain).join(", ")}`);let Le=Z(Pe)?[]:Object.values(m.sites??{}).flatMap((wt)=>typeof wt?.port==="number"&&![80,443].includes(wt.port)?[wt.port]:[]),Re=Yr({allowSsh:Pe?.allowSsh!==!1,sitePorts:Le,allowedPorts:Pe?.firewall?.allowedPorts}),Ee=(z.firewallId?(await Y.listFirewalls()).find((wt)=>wt.id===z.firewallId):void 0)??(await Y.listFirewalls()).find((wt)=>wt.name===`${m.project.slug}-${A}-app-fw`);if(!Ee)throw Error(`Managed firewall for ${X.name} was not found.`);let Te=await Y.setFirewallRules(Ee.id,Re);await Promise.all(Te.map((wt)=>Y.waitForAction(wt.id))),await ss(m,se);let[Ke,Be]=await Promise.all([en(se),as(se)]),_e=cs(re,Be),et=pc(fe,Ke),Rt=Ke.routeProbes.filter((wt)=>!wt.ok);if(et.stoppedServices.length>0)_e.push(`services stopped: ${et.stoppedServices.join(", ")}`);if(et.changedRouteFragments.length>0)_e.push(`route fragments changed: ${et.changedRouteFragments.join(", ")}`);if(et.missingRouteIds.length>0)_e.push(`routes disappeared: ${et.missingRouteIds.join(", ")}`);if(et.changedReleaseLinks.length>0)_e.push(`release links changed: ${et.changedReleaseLinks.join(", ")}`);if(et.missingData.length>0)_e.push(`databases or volumes disappeared: ${et.missingData.join(", ")}`);if(Rt.length>0)_e.push(`unhealthy routes: ${Rt.map((wt)=>wt.domain).join(", ")}`);if(_e.length>0)throw Error(`Host optimization verification failed: ${_e.join("; ")}`);let Et={serverId:X.id,serverName:X.name,services:Ke.runningServices.length,routes:Ke.routeIds.length,healthyDomains:Ke.routeProbes.length,releaseLinks:Ke.releaseLinks.length,databasesAndVolumes:Ke.dataCatalog.length,report:Be};if(t.json)console.log(JSON.stringify({schemaVersion:1,result:Et},null,2));else n(`${X.name} passed full host, rpx route, service, release, and data verification.`)}async function tp(D,t,m){let A=await je(),_=m.env??"production",z=T(A,_);await bi(async(U)=>{let Y=Mi(U,D),X=U.store.list(U.controlPlane.project.id,!0),ne=Y.provider==="hetzner"&&Y.providerId?(()=>{try{let Ee=lr(A);return new We({apiToken:dr(Ee.apiToken,A)})}catch{return null}})():null,re=Y.providerId?Number(Y.providerId):Number.NaN,se=await pe(z),fe=se!=null&&se.serverId===re,Ae=new Fr,Pe=Y.trustState==="pinned",he={takenNames:async()=>{let Ee=X.map((Te)=>Te.name);if(ne)Ee.push(...(await ne.listServers()).map((Te)=>Te.name));return Ee},inventoryName:()=>U.store.get(Y.id)?.name??Y.name,renameInventory:(Ee)=>{U.store.update(Y.id,{name:Ee})},...ne&&Number.isFinite(re)?{providerName:async()=>(await ne.getServer(re)).name,renameProvider:async(Ee)=>{await ne.renameServer(re,Ee)}}:{},...fe?{stateName:async()=>(await pe(z))?.serverName,writeStateName:async(Ee)=>{let Te=await pe(z);if(Te)await ut(z,{...Te,serverName:Ee})}}:{},...Pe?{remoteHostname:async()=>{let Ee=await Ae.exec(Y,"hostname");return Ee.code===0?Ee.stdout.trim():void 0},setRemoteHostname:async(Ee)=>{let Te=await Ae.exec(Y,id(Ee));if(Te.code!==0)throw Error(Te.stderr.trim()||`hostname exited ${Te.code}`)}}:{}},Ie=await rd(Y.name,t,he),Le=await Ts(Ie);if(!m.apply){let Ee=[ne?"":"provider record (no provider id or no API token)",fe?"":"local state pin (none points at this server)",Pe?"":"box hostname (host key is not pinned — run server:validate first)"].filter(Boolean);if(m.json){console.log(JSON.stringify({schemaVersion:1,operation:Ie.operation,target:Ie.target,rename:{from:Y.name,to:t},notCovered:Ee,steps:Le.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 Os(Ie,Le))console.log(Te);for(let Te of Ee)console.log(` not covered: ${Te}`);console.log(" Re-run with --apply to perform it.");return}let Re=await Ls(Ie,Le,{log:(Ee)=>e(Ee),audit:(Ee)=>U.controlPlane.store.appendEvent({projectId:U.controlPlane.project.id,resourceId:Y.resourceId,type:`${Ee.operation}.${Ee.step}.${Ee.state}`,level:Ee.state==="failed"?"error":"info",payload:{target:Ee.target,renameTo:t,...Ee.error?{error:Ee.error}:{}}})});if(m.json)console.log(JSON.stringify({schemaVersion:1,outcome:Re},null,2));if(!Re.success){let Ee=Re.steps.find((Te)=>Te.state==="failed");throw Error(`${Ie.operation} stopped at '${Ee?.title}': ${Ee?.error}. Earlier steps stayed applied; re-run the same command to continue from here.`)}if(!m.json)n(`Renamed ${Y.name} to ${t}.`)})}function Wa(D){D.command("capabilities [server]","Show provider and target operation support").option("--json","Print structured JSON").action(async(t,m)=>{try{await bi(async(A)=>{let _=t?[Mi(A,t)]:A.store.list(A.controlPlane.project.id),z=_.flatMap((U)=>Object.entries(U.capabilities).map(([Y,X])=>({server:U.name,provider:U.provider,action:Y,...X})));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){yn(A)}}),D.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 Xm(t,m,A)}catch(_){yn(_)}}),D.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 ep(t,m)}catch(A){yn(A)}}),D.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 Zm(t,m)}catch(A){yn(A)}}),D.command("server:list","List fleet servers").option("--json","Print structured JSON").action(async(t)=>{try{await bi(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((_)=>[_.name,_.providerId??"—",`${_.provider} / ${_.region??"external"}`,`${_.status} / ${_.trustState}`,_.roles.join(","),`${_.capacity.cpu??0} / ${_.capacity.memoryBytes??0}`,_.heartbeatAt??"never"]))})}catch(m){yn(m)}}),D.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 bi(async(A)=>{let _=m.provider;if(!["aws","hetzner","ssh"].includes(_)||!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:_,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){yn(A)}}),D.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 bi(async(A)=>{let _=Mi(A,t);if(_=await A.service.test(_.id),_.trustState==="rotation_pending"){if(!m.acceptHostKey)throw Error(`Host key changed to ${_.pendingHostKey}; review it first.`);_=A.service.reviewHostKey(_.id,m.acceptHostKey)}let z=await A.service.validate(_.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){yn(A)}}),D.command("server:bootstrap <name>","Preview or queue bootstrap").option("--apply","Apply reviewed plan").action(async(t,m)=>{try{await bi(async(A)=>{let _=A.service.bootstrap(Mi(A,t).id,!!m.apply);if(_.preview)c(["Step"],_.steps.map((z)=>[z]));else n(`Bootstrap queued: ${_.operation?.id}`)})}catch(A){yn(A)}}),D.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 tp(t,m,A)}catch(_){yn(_)}}),D.command("server:drain <name>","Drain without terminating").option("--complete","Mark movement complete").action(async(t,m)=>{try{await bi(async(A)=>n(A.service.drain(Mi(A,t).id,!!m.complete).status))}catch(A){yn(A)}}),D.command("server:uncordon <name>","Return a server to scheduling").action(async(t)=>{try{await bi(async(m)=>{m.service.uncordon(Mi(m,t).id),n(`Uncordoned ${t}.`)})}catch(m){yn(m)}}),D.command("server:archive <name>","Archive inventory without termination").option("--confirm <name>","Exact name").action(async(t,m)=>{try{await bi(async(A)=>{A.service.archive(Mi(A,t).id,m.confirm??""),n(`Archived ${t}; provider infrastructure was not terminated.`)})}catch(A){yn(A)}}),D.command("server:ssh <name>","Open strict SSH using the enrolled endpoint").action(async(t)=>{try{await bi(async(m)=>{let A=Mi(m,t);if(A.trustState!=="pinned")throw Error("Validate and pin the server host key first.");let _=Bun.spawn(["ssh","-p",String(A.sshPort),"-o","StrictHostKeyChecking=yes",`${A.sshUser}@${A.endpoint}`],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}),z=await _.exited;if(z)process.exitCode=z})}catch(m){yn(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"]])D.command(t,m).action(async(...A)=>En(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 sd,mkdirSync as np,readFileSync as rp,writeFileSync as ip}from"node:fs";import{join as ad,resolve as op}from"node:path";var qa=(D)=>{r(D instanceof Error?D.message:String(D)),process.exitCode=1};async function sp(){try{return await je()}catch{return}}function ap(D){if(D===void 0)return;let t=Number(D);if(!Number.isInteger(t)||t<1||t>65535)throw Error(`--port must be a port number, not '${D}'.`);return t}function cd(D,t,m){let A=D?.ssh?.hosts?.find((_)=>_.host===t);return new fr({hosts:t?[A??{host:t}]:D?.ssh?.hosts,user:m.user,port:ap(m.port),privateKeyPath:m.key,hostKey:D?.ssh?.hostKey,sudo:D?.ssh?.sudo,profile:m.profile??D?.ssh?.profile,publicIp:D?.ssh?.publicIp,lan:D?.ssh?.lan})}var ld=1073741824;function Ga(D){D.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 sp(),_=cd(A,t,m),z=t??_.targetHost(),U=await _.ensureHostKey(z),{facts:Y,findings:X}=await _.preflight(z);if(m.json)console.log(JSON.stringify({schemaVersion:1,host:z,hostKeyFingerprint:U.fingerprint,facts:Y,findings:X},null,2));else{if(i(`Preflight ${z}`),U.pinnedNow)e(`Pinned host key ${U.fingerprint}`);if(e(`${Y.os} on ${Y.arch}, ${Y.cpuCores} cores, ${(Y.memoryBytes/ld).toFixed(1)} GiB memory, ${(Y.diskFreeBytes/ld).toFixed(1)} GiB free`),Y.piModel)e(Y.piModel);if(Y.lanIp)e(`LAN address ${Y.lanIp}`);if(console.log(Zr(X)),gr(X))r("The host is not ready; fix the errors above and run the preflight again.");else n("The host is ready to adopt.")}if(gr(X))process.exitCode=1}catch(A){qa(A)}}),D.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 je(),A=t.env??"production";if(t.dryRun){let U=mr(m),[Y]=U.hosts;console.log(pr({config:m,environment:A,profile:U.profile,sudoUser:Y.user!=="root"?Y.user:void 0,lan:U.lan}));return}let _=cd(m,void 0,{});i(`Adopt ${_.targetHost()}`);let z=await _.provisionComputeInfrastructure({config:m,environment:A});n(`${z.appPublicIp} is bootstrapped; deploy with: cloud deploy --env ${A}`)}catch(m){qa(m)}}),D.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 Zi))throw Error(`--os must be raspberry-pi-os or ubuntu, not '${m}'.`);let A=await je(),_=t.env??"production",z=mr(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"),Y=Qr(t.key??"~/.ssh/id_ed25519.pub");if(!sd(Y))throw Error(`Public key not found at ${Y}; pass --key <path to a .pub file>.`);let X=rp(Y,"utf8").trim(),ne;if(t.wifiSsid){let Pe=t.wifiPassphraseEnv??"WIFI_PASSWORD",he=process.env[Pe];if(!he)throw Error(`--wifi-ssid needs the passphrase in $${Pe} (set it in the environment, not on the command line).`);if(!t.wifiCountry)throw Error("--wifi-country <cc> is required with --wifi-ssid.");ne={ssid:t.wifiSsid,passphrase:he,country:t.wifiCountry}}let re=pr({config:A,environment:_,profile:z.profile,sudoUser:U!=="root"?U:void 0,lan:z.lan}),se=eo({hostname:t.hostname,user:U,publicKey:X,timezone:t.timezone,locale:t.locale,wifi:ne},re,{os:m}),fe=op(t.out);np(fe,{recursive:!0});let Ae=Object.keys(se.files).filter((Pe)=>sd(ad(fe,Pe)));if(Ae.length>0&&!t.force)throw Error(`${Ae.join(", ")} already exist in ${fe}; pass --force to overwrite.`);for(let[Pe,he]of Object.entries(se.files))ip(ad(fe,Pe),he,{mode:384});i(`First boot for ${t.hostname}`),n(`Wrote ${Object.keys(se.files).join(", ")} to ${fe}`),console.log(se.instructions)}catch(m){qa(m)}})}async function cp(){let D=await je(),t=y(process.cwd(),D),m=new xn(t.store),A=new _n(m,new De(t.store));return{controlPlane:t,store:m,service:A}}async function Xo(D){let t=await cp();try{await D(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var js=(D)=>Object.fromEntries(String(D??"").split(",").map((t)=>t.split("=")).filter((t)=>t[0]&&t[1])),dd=(D)=>Object.fromEntries(Object.entries(js(D)).map(([t,m])=>[t,Number(m)]));function Ha(D){D.command("pool:list","List capacity pools and reservations").option("--json","Print structured JSON").action(async(t)=>Xo((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((_)=>[_.name,_.purpose,_.backend,`${_.region??"any"} / ${_.architecture??"any"}`,_.status,JSON.stringify(_.capacity),String(_.concurrency)]))})),D.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)=>Xo((A)=>{let{purpose:_,backend:z}=m;if(!["application","build","worker","monitoring","backup"].includes(_)||!["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:_,backend:z,region:m.region,architecture:m.arch,labels:js(m.labels),requiredServerLabels:js(m.selector),toleratedTaints:[],capacity:{cpu:0,memoryBytes:0,diskBytes:0,gpu:0,...dd(m.capacity)},maxWorkloads:Number(m.max),costWeight:1,concurrency:Number(m.concurrency),ephemeralWorkspaces:!0,allowProductionSecrets:!1,status:"active"});n(`Created ${U.name} (${U.id}).`)})),D.command("pool:add-server <pool> <server>","Attach an enrolled server to a pool").action(async(t,m)=>Xo((A)=>{let _=A.store.listPools(A.controlPlane.project.id).find((U)=>U.id===t||U.name===t),z=new De(A.controlPlane.store).list(A.controlPlane.project.id).find((U)=>U.id===m||U.name===m);if(!_||!z)throw Error("Pool or server was not found.");A.store.addMember(_.id,z.id),n(`Attached ${z.name} to ${_.name}.`)})),D.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)=>Xo((m)=>{let A=m.service.explain(m.controlPlane.project.id,{purpose:t.purpose,resources:dd(t.resources),region:t.region,architecture:t.arch,labels:js(t.labels)});if(t.json)console.log(JSON.stringify(A,null,2));else c(["Pool","Server","Eligible","Available","Fit / spread / cost","Reasons"],A.map((_)=>[_.poolName,_.serverId??"provider-managed",_.eligible?"yes":"no",JSON.stringify(_.available),`${_.score.fit.toFixed(2)} / ${_.score.spread} / ${_.score.cost}`,_.reasons.join("; ")||"eligible"]))})),D.command("pool:drain <pool>","Move stateless placements and block stateful placements").action(async(t)=>Xo((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 _=m.service.drainPool(A.id);n(`Draining ${A.name}: moved ${_.moved.length}, blocked ${_.blocked.length} stateful placement(s).`)}))}async function lp(){let D=await je(),t=y(process.cwd(),D),m=new On(t.store),A=new $n(m);return{controlPlane:t,store:m,service:A}}async function uo(D){let t=await lp();try{await D(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}var us=(D,t)=>{let m=D.store.list(D.controlPlane.project.id).find((A)=>A.id===t||A.name===t);if(!m)throw Error("Regional topology was not found.");return m},dp=(D)=>D.split(",").map((t,m)=>{let[A,_="aws"]=t.split(":");return{region:String(A),provider:_,role:m===0?"primary":"secondary"}}),up=(D)=>{let t=new Set(["s3","dynamodb","secrets"]),m=D.split(",").map((_)=>_.trim()),A=m.find((_)=>!t.has(_));if(A)throw Error(`Unsupported replication kind: ${A}`);return m};function Ka(D){D.command("region:list","List regional topology, traffic, and replication health").option("--json","Print structured JSON").action(async(t)=>uo((m)=>{let A=m.store.list(m.controlPlane.project.id).map((_)=>({topology:_,targets:m.store.targets(_.id),channels:m.store.channels(_.id),route:m.store.route(_.id),executions:m.store.executions(_.id)}));if(t.json)console.log(JSON.stringify(A,null,2));else c(["Name","Hostname","Active","Status","Revision","Replication"],A.map((_)=>[_.topology.name,_.topology.hostname,_.topology.activeRegion,_.topology.status,_.topology.revision??"—",`${_.channels.filter((z)=>z.status==="in_sync").length}/${_.channels.length} in sync`]))})),D.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)=>uo((A)=>{if(!m.hostname||!m.regions)throw Error("--hostname and --regions are required.");let _=A.service.create({organizationId:A.controlPlane.organization.id,projectId:A.controlPlane.project.id,name:t,hostname:m.hostname,regions:dp(m.regions),trafficPolicy:"active_passive",dataPolicy:{replicate:up(String(m.replicate)),maxLagSeconds:Number(m.maxLag),retainOnDestroy:!0},dnsProvider:"route53",cdnEnabled:!!m.cdn,wafEnabled:!!m.waf});n(`Created ${_.name} (${_.id}); run region:rollout with an immutable manifest.`)})),D.command("region:plan <name> <operation>","Preview exact regional execution steps").option("--target <region>","Failover or failback region").action(async(t,m,A)=>uo((_)=>c(["Step"],_.service.plan(us(_,t).id,m,A.target).map((z)=>[z])))),D.command("region:rollout <name> <revision>","Queue secondary-first regional stack rollout").option("--manifest <file>","Regional stack manifest JSON").action(async(t,m,A)=>uo(async(_)=>{let z=A.manifest?await Bun.file(A.manifest).json():{},U=_.service.enqueue({topologyId:us(_,t).id,kind:"rollout",revision:m,manifest:z});n(`Regional rollout queued: ${U.operation.id}`)})),D.command("region:failover <name> <region>","Queue health and RPO-gated failover").action(async(t,m)=>uo((A)=>{let _=A.service.enqueue({topologyId:us(A,t).id,kind:"failover",targetRegion:m});n(`Failover queued: ${_.operation.id}`)})),D.command("region:failback <name> <region>","Queue health and RPO-gated failback").action(async(t,m)=>uo((A)=>{let _=A.service.enqueue({topologyId:us(A,t).id,kind:"failback",targetRegion:m});n(`Failback queued: ${_.operation.id}`)})),D.command("region:destroy <name>","Drain traffic and delete regional stacks while retaining data").option("--confirm <name>","Exact topology name").action(async(t,m)=>uo((A)=>{let _=A.service.enqueue({topologyId:us(A,t).id,kind:"destroy",confirmation:m.confirm});n(`Regional teardown queued: ${_.operation.id}; replicated data remains retained.`)}))}var ms="0.16.41";async function pp(){let D=await je(),t=y(process.cwd(),D),m=new Ln(t.store),A=new Mn(m),_=["releases","artifacts","operations","logs","previews","snapshots","orphaned_resources"].map((z)=>new Un(z,t.store,t.project.id,t.organization.id));return{controlPlane:t,store:m,service:A,cleanup:_}}async function Ii(D){let t=await pp();try{await D(t)}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}finally{t.controlPlane.store.close()}}function Ja(D){D.command("maintenance:list","List updates, campaigns, cleanup plans, and DR drills").option("--json","Print structured JSON").action(async(t)=>Ii((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)},_=[...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"],_)})),D.command("maintenance:key:add <name> <pem>","Trust an Ed25519 update signing key").action(async(t,m)=>Ii(async(A)=>{let _=A.store.addTrustedKey({organizationId:A.controlPlane.organization.id,name:t,publicKeyPem:await Bun.file(m).text()});n(`Trusted ${_.name}: ${_.fingerprint}`)})),D.command("maintenance:manifest <document> <signature> <key>","Verify and import a signed update manifest").action(async(t,m,A)=>Ii(async(_)=>{let z=await Bun.file(t).json(),U=(await Bun.file(m).text()).trim(),Y=_.store.importManifest({organizationId:_.controlPlane.organization.id,keyId:A,document:z,signature:U,current:{schemaVersion:_.controlPlane.store.health().schemaVersion,currentVersion:ms,platform:process.platform,architecture:process.arch}});n(`Verified ${Y.version}; ${Y.compatibility.compatible?"compatible":"blocked: "+Y.compatibility.reasons.join("; ")}`)})),D.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)=>Ii((_)=>{let z=_.store.createWindow({projectId:_.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}).`)})),D.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)=>Ii((_)=>{let z=new De(_.controlPlane.store).list(_.controlPlane.project.id).filter((X)=>X.status==="ready").map((X)=>({serverId:X.id,target:X.name,previousVersion:X.bootstrapVersion??ms}));if(!z.length)z.push({target:"control-plane",previousVersion:ms});let U=_.store.createCampaign({projectId:_.controlPlane.project.id,manifestId:t,fromVersion:ms,strategy:A.strategy==="rolling"?"rolling":"canary",batchSize:Number(A.batch),healthGate:{kind:"platform"},backupId:m,targets:z}),Y=_.service.enqueueCampaign(U.id);n(`Staged upgrade queued: ${Y.operation.id}`)})),D.command("cleanup:preview <kind>","Discover a bounded cleanup candidate set without deleting").option("--older-than <days>","Minimum age",{default:"30"}).action(async(t,m)=>Ii(async(A)=>{let _=await A.service.previewCleanup({projectId:A.controlPlane.project.id,kind:t,criteria:{olderThanDays:Number(m.olderThan)}},A.cleanup);c(["ID","Candidate","Bytes"],_.candidates.map((z)=>[z.id,z.label,String(z.bytes)])),e(`Apply with: cloud cleanup:apply ${_.id} --confirm "DELETE ${_.candidates.length} ${_.kind}"`)})),D.command("cleanup:apply <plan>","Apply an unchanged cleanup preview").option("--confirm <text>","Exact preview confirmation").action(async(t,m)=>Ii((A)=>{let _=A.service.enqueueCleanup(t,String(m.confirm??""));n(`Cleanup queued: ${_.operation.id}`)})),D.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)=>Ii((_)=>{let z=_.store.createDrill({projectId:_.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}.`)})),D.command("dr:run <drill>","Queue an isolated disaster-recovery drill").action(async(t)=>Ii((m)=>{let A=m.service.enqueueDrill(t);n(`DR drill queued: ${A.operation.id}`)}))}async function Ya(){try{return(await je()).project.region||process.env.AWS_REGION||"us-east-1"}catch{return process.env.AWS_REGION||"us-east-1"}}function Za(D){D.command("function:list","List all Lambda functions").option("--region <region>","AWS region").action(async(t)=>{i("Lambda Functions");let m=t?.region||await Ya(),A=new I(m);try{let{Functions:_=[]}=await A.listFunctions({MaxItems:50});if(!_.length){e("No functions found");return}c(["Name","Runtime","Memory","Timeout","Last Modified"],_.map((z)=>[z.FunctionName??"-",z.Runtime??"-",z.MemorySize?`${z.MemorySize} MB`:"-",z.Timeout?`${z.Timeout}s`:"-",z.LastModified??"-"]))}catch(_){r(`Failed to list functions: ${_.message}`),process.exitCode=1}}),D.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 Ya(),_=new I(A),z=new s("Invoking function...");z.start();try{let U=await _.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}}),D.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 Ya(),_=new oe(A),z=t.startsWith("/aws/lambda/")?t:`/aws/lambda/${t}`,U=Number(m?.since??15),Y=(X)=>{for(let ne of X){let re=ne.timestamp?new Date(ne.timestamp).toISOString():"";e(`${re} ${(ne.message??"").trimEnd()}`)}};try{let X=Date.now()-U*60000,{events:ne=[]}=await _.filterLogEvents({logGroupName:z,startTime:X,filterPattern:m?.filter,limit:200});if(Y(ne),ne.length)X=(ne[ne.length-1].timestamp??X)+1;if(m?.tail){e(`
|
|
321
321
|
(streaming — Ctrl+C to stop)`);for(;;){await new Promise((se)=>setTimeout(se,3000));let{events:re=[]}=await _.filterLogEvents({logGroupName:z,startTime:X,filterPattern:m?.filter,limit:200});if(re.length)Y(re),X=(re[re.length-1].timestamp??X)+1}}}catch(X){r(`Failed to read logs: ${X.message}`),process.exitCode=1}})}async function ud(D){let t=Bun.spawn(["docker",...D],{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",...D].join(" "),stderr:A.trim()}}function Qa(D){D.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 ud(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"}.`)}),D.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 ud(["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}.`)}),D.command("container:deploy","Deploy a container image").action(async()=>En("container:deploy",{message:"Direct ECS mutation is not registered as a shared release driver.",nextAction:"Import the image as an application and use the immutable release deployment workflow."}))}function Xa(D){D.command("domain:list","List all domains").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").action(async(t)=>{i("Domains");try{let m=li(t?.provider),A=pn(t?.provider)?.provider||"unknown",_=new s(`Fetching domains from ${A}...`);_.start();let z=await m.listDomains();if(_.succeed(`Found ${z.length} domain(s)`),z.length===0){e("No domains found in this provider");return}let U=z.map((Y)=>[Y,"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)}`)}}),D.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=li(m?.provider),_=pn(m?.provider)?.provider||"unknown",z=new s(`Checking if ${_} can manage ${t}...`);if(z.start(),await A.canManageDomain(t))z.succeed(`Domain ${t} is already available in ${_}`),e(`
|
|
@@ -331,13 +331,13 @@ Record summary:`);for(let[Ae,Pe]of fe)e(` - ${Ae}: ${Pe}`)}}catch(A){r(`Failed
|
|
|
331
331
|
Record created!`),e(`
|
|
332
332
|
Note: DNS changes may take a few minutes to propagate`)}catch(z){r(`Failed to add record: ${z instanceof Error?z.message:String(z)}`)}}),D.command("dns:delete <domain> <type>","Delete DNS record").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").option("--name <name>","Record name (subdomain)",{default:"@"}).option("--value <value>","Record value (required for multi-value records)").action(async(t,m,A)=>{i("Deleting DNS Record");try{let _=li(A?.provider),z=pn(A?.provider)?.provider||"unknown",U=A?.name||"@",Y=m.toUpperCase();e(`Provider: ${z}`),e(`Domain: ${t}`),e(`Type: ${Y}`),e(`Name: ${U}`);let X=new s("Finding record...");X.start();let se=((await _.listRecords(t)).records||[]).filter((Pe)=>Pe.type.toUpperCase()===Y&&(Pe.name===U||Pe.name===""&&U==="@"));if(se.length===0){X.fail("No matching record found");return}if(se.length>1&&!A?.value){X.warn("Multiple records found"),e(`
|
|
333
333
|
Please specify --value to identify which record to delete:`);for(let Pe of se)e(` - ${Pe.content}`);return}let fe=A?.value?se.find((Pe)=>Pe.content===A.value)||se[0]:se[0];if(e(`Value: ${fe.content}`),!await d("Delete this record?",!1)){e("Deletion cancelled");return}X.text=`Deleting record via ${z}...`,X.start(),await _.deleteRecord(t,fe),X.succeed("DNS record deleted successfully"),n(`
|
|
334
|
-
Record deleted!`)}catch(_){r(`Failed to delete record: ${_ instanceof Error?_.message:String(_)}`)}})}function fp(){if(process.env.GODADDY_PAT)return"godaddy";if(process.env.GODADDY_API_KEY&&process.env.GODADDY_API_SECRET)return"godaddy";if(process.env.PORKBUN_API_KEY&&process.env.PORKBUN_SECRET_KEY)return"porkbun";return null}var md=["postgres","mysql","mariadb","redis","mongodb","libsql"],pd=["aws_rds","aws_aurora","aws_elasticache","server","container","external"],fd=["observe","connect","backup","restore","restart","resize","version","rotate","expose","delete","logs","slow_queries","users","databases"];async function gp(D,t){let m=await je(),A=y(process.cwd(),m),_=new er(A.store),z=!D&&t?_.get(t)??_.list(A.project.id).find((he)=>he.name===t):void 0,U=z?.environmentId?A.store.listEnvironments(A.project.id).find((he)=>he.id===z.environmentId)?.slug:void 0,Y=D??U??Object.keys(m.environments??{})[0]??"production",X=A.environments.get(Y);if(!X)throw A.store.close(),Error(`Environment ${Y} was not found.`);let ne=new S(A.store,{workerId:`cli:${process.pid}`}),re=new tr(A.store,x(process.cwd())),se=new jr(_,ne,re),fe=A.store.getActorByExternalId("system","cli")??A.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),Ae=m.project?.region??"us-east-1",Pe={aws_rds:new jn(new qn(new me(Ae))),aws_aurora:new Vn(new Wn(new me(Ae))),aws_elasticache:new zn(new Kn(new ie(Ae))),server:new Hn(new ot),container:new Gn(new ot)};return{config:m,controlPlane:A,environmentRecord:X,env:Y,store:_,queue:ne,secrets:re,lifecycle:se,actor:fe,resolveAdapter:(he)=>Pe[he.provider]}}async function Ir(D,t,m){let A=await gp(D,m);try{return await t(A)}finally{A.controlPlane.store.close()}}async function Tn(D){try{await D()}catch(t){r(t instanceof Error?t.message:String(t))}}function ec(D){if(!md.includes(D))throw Error(`--engine must be ${md.join(", ")}.`);return D}function tc(D){if(!pd.includes(D))throw Error(`--provider must be ${pd.join(", ")}.`);return D}function po(D,t){let m=D.store.list(D.controlPlane.project.id,D.environmentRecord.id),A=D.store.get(t)??m.find((_)=>_.name===t);if(!A||A.environmentId!==D.environmentRecord.id)throw Error(`Data service ${t} was not found in ${D.env}.`);return A}function gd(D){return D.map((t)=>[t.id,t.name,t.engine,t.provider,t.status,t.plan,t.publicExposure?"public allowlist":"private",t.managementEnabled?"managed":"read-only"])}function hp(D,t){return D!==null&&typeof D==="object"&&!Array.isArray(D)&&D.serviceId===t}function yp(D){let t={};if(D.plan)t.plan=D.plan;if(D.storage!=null)t.storageGb=Number(D.storage);if(D.engineVersion)t.engineVersion=D.engineVersion;if(D.retention)t.retention=D.retention;if(D.confirm)t.confirm=D.confirm;if(D.backupId)t.backupId=D.backupId;if(D.compatibilityReviewed)t.compatibilityReviewed=!0;if(D.minCapacity!=null)t.minCapacity=Number(D.minCapacity);if(D.maxCapacity!=null)t.maxCapacity=Number(D.maxCapacity);if(D.public!=null)t.publicExposure=D.public;if(D.cidrs)t.allowedCidrs=D.cidrs.split(",").map((m)=>m.trim()).filter(Boolean);return t}function hd(D){let t=typeof D.observedState.endpoint==="string"?D.observedState.endpoint:D.placement,m=Number(D.observedState.port)||(D.engine==="postgres"?5432:D.engine==="redis"?6379:D.engine==="mongodb"?27017:D.engine==="libsql"?8080:3306),A=typeof D.observedState.database==="string"?D.observedState.database:void 0;return[{type:D.publicExposure?"external":"internal",host:t,port:m,database:A,tls:typeof D.observedState.tls==="boolean"?D.observedState.tls:D.provider.startsWith("aws_")},{type:"tunnel",host:t,port:m,database:A,tls:typeof D.observedState.tls==="boolean"?D.observedState.tls:D.provider.startsWith("aws_")}]}function yd(D){D.option("--env <environment>","Target environment").option("--engine <engine>","postgres, mysql, mariadb, redis, mongodb, or libsql",{default:"postgres"}).option("--provider <provider>","aws_rds, aws_aurora, aws_elasticache, server, container, or external",{default:"aws_rds"}).option("--placement <id>","Provider identifier or runtime placement").option("--plan <plan>","Provider plan or instance type",{default:"db.t4g.micro"}).option("--engine-version <version>","Engine version").option("--storage <gb>","Allocated storage in GB",{default:"20"}).option("--username <name>","Generated credential username",{default:"app"}).option("--database <name>","Initial logical database").option("--subnet-group <name>","Private subnet group").option("--security-groups <ids>","Comma-separated security group IDs").option("--high-availability","Enable provider high availability").option("--public","Request a public endpoint; requires narrow --cidrs").option("--cidrs <ranges>","Comma-separated narrow public CIDRs")}function nc(D){D.command("data:list","List first-class databases and caches").option("--env <environment>","Target environment").option("--json","Print structured JSON").action((A)=>Tn(()=>Ir(A.env,async(_)=>{let z=_.store.list(_.controlPlane.project.id,_.environmentRecord.id);if(A.json)e(JSON.stringify(z,null,2));else c(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],gd(z))}))),D.command("data:capabilities <engine> <provider>","Explain lifecycle support before taking action").action((A,_)=>Tn(async()=>{e(JSON.stringify(Zt(ec(A),tc(_)),null,2))}));let t=D.command("data:create <name>","Provision an encrypted, private-by-default data service");yd(t),t.action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=ec(_.engine),Y=tc(_.provider),X=z.resolveAdapter({provider:Y});if(!X||!X.engines.includes(U))throw Error(`No ${Y}/${U} provisioning adapter is configured. Use data:adopt for read-only inventory.`);let ne={..._.database?{database:_.database}:{},..._.subnetGroup?{subnetGroup:_.subnetGroup}:{},..._.securityGroups?{securityGroupIds:_.securityGroups.split(",").map((se)=>se.trim()).filter(Boolean)}:{}},re=await z.lifecycle.create({organizationId:z.controlPlane.organization.id,projectId:z.controlPlane.project.id,environmentId:z.environmentRecord.id,name:A,engine:U,provider:Y,placement:_.placement??A,engineVersion:_.engineVersion,plan:_.plan??"db.t4g.micro",storageGb:Number(_.storage)||void 0,highAvailability:_.highAvailability===!0,publicExposure:_.public===!0,allowedCidrs:_.cidrs?_.cidrs.split(",").map((se)=>se.trim()).filter(Boolean):[],desiredState:ne,observedState:{},origin:"managed",managementEnabled:!0,ownerActorId:z.actor.id,username:_.username});if(n(`Queued ${re.service.name} as operation ${re.operationId}.`),re.credential)Rr("One-time credential — copy it now:"),e(`Username: ${re.credential.username}`),e(`Password: ${re.credential.password}`),e(`Reference: ${re.credential.secretRef}`)})));let m=D.command("data:adopt <name>","Adopt an existing provider resource as read-only inventory");yd(m),m.action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=z.store.create({organizationId:z.controlPlane.organization.id,projectId:z.controlPlane.project.id,environmentId:z.environmentRecord.id,name:A,engine:ec(_.engine),provider:tc(_.provider),placement:_.placement??A,engineVersion:_.engineVersion,plan:_.plan??"external",storageGb:Number(_.storage)||void 0,highAvailability:_.highAvailability===!0,publicExposure:_.public===!0,allowedCidrs:_.cidrs?_.cidrs.split(",").map((Y)=>Y.trim()).filter(Boolean):[],desiredState:{},observedState:{},credentialRef:void 0,status:"adopted",origin:"adopted",managementEnabled:!1,ownerActorId:z.actor.id});n(`Adopted ${U.name} (${U.id}) read-only.`)}))),D.command("data:show <service>","Show safe service, capability, dependency, and operation metadata").option("--env <environment>","Target environment").option("--reveal","Reveal the encrypted credential with exact confirmation").option("--confirm <name>","Exact service name required with --reveal").action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=po(z,A),Y=z.store.credential(U.id),X={...U,credentialRef:void 0,credential:Y?{configured:!0,username:Y.username,version:Y.version,rotatedAt:Y.rotatedAt}:void 0,dependencies:z.store.dependencies(U.id).map((ne)=>({resourceId:ne.resourceId,requiresRedeploy:ne.requiresRedeploy})),operations:z.queue.list({projectId:U.projectId,limit:250}).filter((ne)=>hp(ne.operation.input,U.id))};if(e(JSON.stringify(X,null,2)),_.reveal){if(_.confirm!==U.name)throw Error(`Credential reveal requires --confirm ${U.name}.`);if(!Y)throw Error("No managed credential is configured.");Rr("Credential value follows; it will not be logged or persisted by ts-cloud:"),e(`Username: ${Y.username}`),e(`Password: ${await z.secrets.resolve(Y.secretRef)}`)}},A))),D.command("data:connect <service>","Print secret-free connection and tunnel guidance").option("--env <environment>","Target environment").action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=po(z,A);c(["Type","Command","Credential reference"],Bn(U,hd(U)).map((Y)=>[Y.type,Y.command,Y.secretRef??"—"]))},A))),D.command("data:action <service> <action>","Preview or enqueue a durable lifecycle action").option("--env <environment>","Target environment").option("--execute","Enqueue after validation; default is preview only").option("--plan <plan>","New provider plan").option("--storage <gb>","New storage allocation").option("--engine-version <version>","Target engine version").option("--min-capacity <acu>","Aurora minimum capacity").option("--max-capacity <acu>","Aurora maximum capacity").option("--public","Enable public exposure").option("--cidrs <ranges>","Comma-separated narrow public CIDRs").option("--retention <choice>","final_backup or retain for deletion").option("--confirm <name>","Exact service name for destructive actions").option("--backup-id <id>","Required backup for a major version change").option("--compatibility-reviewed","Confirm major-version compatibility review").action((A,_,z)=>Tn(()=>Ir(z.env,async(U)=>{if(!fd.includes(_))throw Error(`Action must be ${fd.join(", ")}.`);let Y=po(U,A),X=_,ne=yp(z),re=U.lifecycle.plan(Y,X,ne);if(e(JSON.stringify({...re,productionExecutionCreated:!1},null,2)),!z.execute)return;let se=U.lifecycle.enqueue(Y,X,ne,U.actor.id);n(`Queued ${X} as ${se}.`)},A))),D.command("data:management <service>","Enable reviewed management of an adopted service").option("--env <environment>","Target environment").option("--confirm <name>","Exact service name").action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=po(z,A);if(U.origin!=="adopted"||_.confirm!==U.name)throw Error(`Enabling adopted management requires --confirm ${U.name}.`);let Y=z.store.update(U.id,U.version,{managementEnabled:!0});n(`${Y.name} management is enabled.`)},A))),D.command("data:dependency <service> <resource>","Register or remove an application credential dependency").option("--env <environment>","Target environment").option("--dynamic","Dependency consumes a dynamic reference and does not need redeploy").option("--remove","Remove the dependency").action((A,_,z)=>Tn(()=>Ir(z.env,async(U)=>{let Y=po(U,A),X=U.controlPlane.store.listResources(U.controlPlane.project.id,U.environmentRecord.id).find((ne)=>ne.id===_||ne.slug===_);if(!X)throw Error(`Resource ${_} was not found in ${U.env}.`);if(z.remove){e(U.store.removeDependency(Y.id,X.id)?"Dependency removed.":"Dependency was not registered.");return}U.store.addDependency({serviceId:Y.id,resourceId:X.id,secretRef:Y.credentialRef??`secret://data-services/${Y.projectId}/${Y.name}`,requiresRedeploy:!z.dynamic}),n(`Registered dependency ${X.slug} -> ${Y.name}.`)},A))),D.command("db:list","Alias for data:list").option("--env <environment>","Target environment").action((A)=>Tn(()=>Ir(A.env,async(_)=>c(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],gd(_.store.list(_.controlPlane.project.id,_.environmentRecord.id)))))),D.command("db:connect <service>","Alias for data:connect").option("--env <environment>","Target environment").action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=po(z,A);c(["Type","Command","Credential reference"],Bn(U,hd(U)).map((Y)=>[Y.type,Y.command,Y.secretRef??"—"]))},A)));for(let A of["backup","snapshot"])D.command(`db:${A} <service>`,`Alias for data:action ${A==="backup"?"backup":"backup"}`).option("--env <environment>","Target environment").option("--execute","Enqueue the backup; default previews").action((_,z)=>Tn(()=>Ir(z.env,async(U)=>{let Y=po(U,_),X=U.lifecycle.plan(Y,"backup");if(e(JSON.stringify({...X,productionExecutionCreated:!1},null,2)),z.execute)n(`Queued backup as ${U.lifecycle.enqueue(Y,"backup",{},U.actor.id)}.`)},_)))}function vd(D,t=15){if(!D)return t*60000;let m=/^(\d+)\s*([smhd])?$/.exec(D.trim());if(!m)return t*60000;let A=Number(m[1]),_=m[2]??"m";return A*({s:1000,m:60000,h:3600000,d:86400000}[_]??60000)}function vp(D,t){for(let m of D){let A=m.timestamp?new Date(m.timestamp).toISOString():"",_=t?`[${t}] `:"";e(`${A} ${_}${(m.message??"").trimEnd()}`)}}function rc(D){D.command("logs","Stream serverless application logs (http + queue + cli)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli | all",{default:"all"}).option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <duration>","Look back (e.g. 1h, 30m, 15)",{default:"15m"}).action(async(t)=>{try{let m=await je(),A=t?.env||"production",{region:_,functions:z}=bt(m,A),U=new oe(_),Y=(t?.function??"all").toLowerCase();if(Y!=="all"&&!["http","queue","cli"].includes(Y)){r("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let ne=(Y==="all"?["http","queue","cli"]:[Y]).map((fe)=>({mode:fe,group:`/aws/lambda/${z[fe]}`}));i(`Logs — ${m.project.slug} (${A})`);let re=Date.now()-vd(t?.since),se=async()=>{let fe=re;for(let{mode:Ae,group:Pe}of ne)try{let{events:he=[]}=await U.filterLogEvents({logGroupName:Pe,startTime:re,filterPattern:t?.filter,limit:200});vp(he,ne.length>1?Ae:void 0);for(let Ie of he)if((Ie.timestamp??0)>fe)fe=Ie.timestamp??fe}catch(he){if(!/ResourceNotFound/i.test(String(he?.message)))throw he}re=fe+1};if(await se(),t?.tail){e(`
|
|
335
|
-
(streaming — Ctrl+C to stop)`);for(;;)await new Promise((fe)=>setTimeout(fe,3000)),await se()}}catch(m){r(`Failed to read logs: ${m.message}`),process.exitCode=1}}),D.command("metrics","Show serverless function metrics (invocations, errors, duration, throttles)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--since <duration>","Window (e.g. 1h, 24h)",{default:"1h"}).action(async(t)=>{try{let m=await je(),A=t?.env||"production",{region:_,functions:z}=bt(m,A),U=new
|
|
334
|
+
Record deleted!`)}catch(_){r(`Failed to delete record: ${_ instanceof Error?_.message:String(_)}`)}})}function fp(){if(process.env.GODADDY_PAT)return"godaddy";if(process.env.GODADDY_API_KEY&&process.env.GODADDY_API_SECRET)return"godaddy";if(process.env.PORKBUN_API_KEY&&process.env.PORKBUN_SECRET_KEY)return"porkbun";return null}var md=["postgres","mysql","mariadb","redis","mongodb","libsql"],pd=["aws_rds","aws_aurora","aws_elasticache","server","container","external"],fd=["observe","connect","backup","restore","restart","resize","version","rotate","expose","delete","logs","slow_queries","users","databases"];async function gp(D,t){let m=await je(),A=y(process.cwd(),m),_=new er(A.store),z=!D&&t?_.get(t)??_.list(A.project.id).find((he)=>he.name===t):void 0,U=z?.environmentId?A.store.listEnvironments(A.project.id).find((he)=>he.id===z.environmentId)?.slug:void 0,Y=D??U??Object.keys(m.environments??{})[0]??"production",X=A.environments.get(Y);if(!X)throw A.store.close(),Error(`Environment ${Y} was not found.`);let ne=new S(A.store,{workerId:`cli:${process.pid}`}),re=new tr(A.store,x(process.cwd())),se=new jr(_,ne,re),fe=A.store.getActorByExternalId("system","cli")??A.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),Ae=m.project?.region??"us-east-1",Pe={aws_rds:new jn(new Gn(new me(Ae))),aws_aurora:new Vn(new Wn(new me(Ae))),aws_elasticache:new zn(new Kn(new ie(Ae))),server:new Hn(new ot),container:new qn(new ot)};return{config:m,controlPlane:A,environmentRecord:X,env:Y,store:_,queue:ne,secrets:re,lifecycle:se,actor:fe,resolveAdapter:(he)=>Pe[he.provider]}}async function Ir(D,t,m){let A=await gp(D,m);try{return await t(A)}finally{A.controlPlane.store.close()}}async function Tn(D){try{await D()}catch(t){r(t instanceof Error?t.message:String(t))}}function ec(D){if(!md.includes(D))throw Error(`--engine must be ${md.join(", ")}.`);return D}function tc(D){if(!pd.includes(D))throw Error(`--provider must be ${pd.join(", ")}.`);return D}function po(D,t){let m=D.store.list(D.controlPlane.project.id,D.environmentRecord.id),A=D.store.get(t)??m.find((_)=>_.name===t);if(!A||A.environmentId!==D.environmentRecord.id)throw Error(`Data service ${t} was not found in ${D.env}.`);return A}function gd(D){return D.map((t)=>[t.id,t.name,t.engine,t.provider,t.status,t.plan,t.publicExposure?"public allowlist":"private",t.managementEnabled?"managed":"read-only"])}function hp(D,t){return D!==null&&typeof D==="object"&&!Array.isArray(D)&&D.serviceId===t}function yp(D){let t={};if(D.plan)t.plan=D.plan;if(D.storage!=null)t.storageGb=Number(D.storage);if(D.engineVersion)t.engineVersion=D.engineVersion;if(D.retention)t.retention=D.retention;if(D.confirm)t.confirm=D.confirm;if(D.backupId)t.backupId=D.backupId;if(D.compatibilityReviewed)t.compatibilityReviewed=!0;if(D.minCapacity!=null)t.minCapacity=Number(D.minCapacity);if(D.maxCapacity!=null)t.maxCapacity=Number(D.maxCapacity);if(D.public!=null)t.publicExposure=D.public;if(D.cidrs)t.allowedCidrs=D.cidrs.split(",").map((m)=>m.trim()).filter(Boolean);return t}function hd(D){let t=typeof D.observedState.endpoint==="string"?D.observedState.endpoint:D.placement,m=Number(D.observedState.port)||(D.engine==="postgres"?5432:D.engine==="redis"?6379:D.engine==="mongodb"?27017:D.engine==="libsql"?8080:3306),A=typeof D.observedState.database==="string"?D.observedState.database:void 0;return[{type:D.publicExposure?"external":"internal",host:t,port:m,database:A,tls:typeof D.observedState.tls==="boolean"?D.observedState.tls:D.provider.startsWith("aws_")},{type:"tunnel",host:t,port:m,database:A,tls:typeof D.observedState.tls==="boolean"?D.observedState.tls:D.provider.startsWith("aws_")}]}function yd(D){D.option("--env <environment>","Target environment").option("--engine <engine>","postgres, mysql, mariadb, redis, mongodb, or libsql",{default:"postgres"}).option("--provider <provider>","aws_rds, aws_aurora, aws_elasticache, server, container, or external",{default:"aws_rds"}).option("--placement <id>","Provider identifier or runtime placement").option("--plan <plan>","Provider plan or instance type",{default:"db.t4g.micro"}).option("--engine-version <version>","Engine version").option("--storage <gb>","Allocated storage in GB",{default:"20"}).option("--username <name>","Generated credential username",{default:"app"}).option("--database <name>","Initial logical database").option("--subnet-group <name>","Private subnet group").option("--security-groups <ids>","Comma-separated security group IDs").option("--high-availability","Enable provider high availability").option("--public","Request a public endpoint; requires narrow --cidrs").option("--cidrs <ranges>","Comma-separated narrow public CIDRs")}function nc(D){D.command("data:list","List first-class databases and caches").option("--env <environment>","Target environment").option("--json","Print structured JSON").action((A)=>Tn(()=>Ir(A.env,async(_)=>{let z=_.store.list(_.controlPlane.project.id,_.environmentRecord.id);if(A.json)e(JSON.stringify(z,null,2));else c(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],gd(z))}))),D.command("data:capabilities <engine> <provider>","Explain lifecycle support before taking action").action((A,_)=>Tn(async()=>{e(JSON.stringify(Zt(ec(A),tc(_)),null,2))}));let t=D.command("data:create <name>","Provision an encrypted, private-by-default data service");yd(t),t.action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=ec(_.engine),Y=tc(_.provider),X=z.resolveAdapter({provider:Y});if(!X||!X.engines.includes(U))throw Error(`No ${Y}/${U} provisioning adapter is configured. Use data:adopt for read-only inventory.`);let ne={..._.database?{database:_.database}:{},..._.subnetGroup?{subnetGroup:_.subnetGroup}:{},..._.securityGroups?{securityGroupIds:_.securityGroups.split(",").map((se)=>se.trim()).filter(Boolean)}:{}},re=await z.lifecycle.create({organizationId:z.controlPlane.organization.id,projectId:z.controlPlane.project.id,environmentId:z.environmentRecord.id,name:A,engine:U,provider:Y,placement:_.placement??A,engineVersion:_.engineVersion,plan:_.plan??"db.t4g.micro",storageGb:Number(_.storage)||void 0,highAvailability:_.highAvailability===!0,publicExposure:_.public===!0,allowedCidrs:_.cidrs?_.cidrs.split(",").map((se)=>se.trim()).filter(Boolean):[],desiredState:ne,observedState:{},origin:"managed",managementEnabled:!0,ownerActorId:z.actor.id,username:_.username});if(n(`Queued ${re.service.name} as operation ${re.operationId}.`),re.credential)Rr("One-time credential — copy it now:"),e(`Username: ${re.credential.username}`),e(`Password: ${re.credential.password}`),e(`Reference: ${re.credential.secretRef}`)})));let m=D.command("data:adopt <name>","Adopt an existing provider resource as read-only inventory");yd(m),m.action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=z.store.create({organizationId:z.controlPlane.organization.id,projectId:z.controlPlane.project.id,environmentId:z.environmentRecord.id,name:A,engine:ec(_.engine),provider:tc(_.provider),placement:_.placement??A,engineVersion:_.engineVersion,plan:_.plan??"external",storageGb:Number(_.storage)||void 0,highAvailability:_.highAvailability===!0,publicExposure:_.public===!0,allowedCidrs:_.cidrs?_.cidrs.split(",").map((Y)=>Y.trim()).filter(Boolean):[],desiredState:{},observedState:{},credentialRef:void 0,status:"adopted",origin:"adopted",managementEnabled:!1,ownerActorId:z.actor.id});n(`Adopted ${U.name} (${U.id}) read-only.`)}))),D.command("data:show <service>","Show safe service, capability, dependency, and operation metadata").option("--env <environment>","Target environment").option("--reveal","Reveal the encrypted credential with exact confirmation").option("--confirm <name>","Exact service name required with --reveal").action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=po(z,A),Y=z.store.credential(U.id),X={...U,credentialRef:void 0,credential:Y?{configured:!0,username:Y.username,version:Y.version,rotatedAt:Y.rotatedAt}:void 0,dependencies:z.store.dependencies(U.id).map((ne)=>({resourceId:ne.resourceId,requiresRedeploy:ne.requiresRedeploy})),operations:z.queue.list({projectId:U.projectId,limit:250}).filter((ne)=>hp(ne.operation.input,U.id))};if(e(JSON.stringify(X,null,2)),_.reveal){if(_.confirm!==U.name)throw Error(`Credential reveal requires --confirm ${U.name}.`);if(!Y)throw Error("No managed credential is configured.");Rr("Credential value follows; it will not be logged or persisted by ts-cloud:"),e(`Username: ${Y.username}`),e(`Password: ${await z.secrets.resolve(Y.secretRef)}`)}},A))),D.command("data:connect <service>","Print secret-free connection and tunnel guidance").option("--env <environment>","Target environment").action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=po(z,A);c(["Type","Command","Credential reference"],Bn(U,hd(U)).map((Y)=>[Y.type,Y.command,Y.secretRef??"—"]))},A))),D.command("data:action <service> <action>","Preview or enqueue a durable lifecycle action").option("--env <environment>","Target environment").option("--execute","Enqueue after validation; default is preview only").option("--plan <plan>","New provider plan").option("--storage <gb>","New storage allocation").option("--engine-version <version>","Target engine version").option("--min-capacity <acu>","Aurora minimum capacity").option("--max-capacity <acu>","Aurora maximum capacity").option("--public","Enable public exposure").option("--cidrs <ranges>","Comma-separated narrow public CIDRs").option("--retention <choice>","final_backup or retain for deletion").option("--confirm <name>","Exact service name for destructive actions").option("--backup-id <id>","Required backup for a major version change").option("--compatibility-reviewed","Confirm major-version compatibility review").action((A,_,z)=>Tn(()=>Ir(z.env,async(U)=>{if(!fd.includes(_))throw Error(`Action must be ${fd.join(", ")}.`);let Y=po(U,A),X=_,ne=yp(z),re=U.lifecycle.plan(Y,X,ne);if(e(JSON.stringify({...re,productionExecutionCreated:!1},null,2)),!z.execute)return;let se=U.lifecycle.enqueue(Y,X,ne,U.actor.id);n(`Queued ${X} as ${se}.`)},A))),D.command("data:management <service>","Enable reviewed management of an adopted service").option("--env <environment>","Target environment").option("--confirm <name>","Exact service name").action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=po(z,A);if(U.origin!=="adopted"||_.confirm!==U.name)throw Error(`Enabling adopted management requires --confirm ${U.name}.`);let Y=z.store.update(U.id,U.version,{managementEnabled:!0});n(`${Y.name} management is enabled.`)},A))),D.command("data:dependency <service> <resource>","Register or remove an application credential dependency").option("--env <environment>","Target environment").option("--dynamic","Dependency consumes a dynamic reference and does not need redeploy").option("--remove","Remove the dependency").action((A,_,z)=>Tn(()=>Ir(z.env,async(U)=>{let Y=po(U,A),X=U.controlPlane.store.listResources(U.controlPlane.project.id,U.environmentRecord.id).find((ne)=>ne.id===_||ne.slug===_);if(!X)throw Error(`Resource ${_} was not found in ${U.env}.`);if(z.remove){e(U.store.removeDependency(Y.id,X.id)?"Dependency removed.":"Dependency was not registered.");return}U.store.addDependency({serviceId:Y.id,resourceId:X.id,secretRef:Y.credentialRef??`secret://data-services/${Y.projectId}/${Y.name}`,requiresRedeploy:!z.dynamic}),n(`Registered dependency ${X.slug} -> ${Y.name}.`)},A))),D.command("db:list","Alias for data:list").option("--env <environment>","Target environment").action((A)=>Tn(()=>Ir(A.env,async(_)=>c(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],gd(_.store.list(_.controlPlane.project.id,_.environmentRecord.id)))))),D.command("db:connect <service>","Alias for data:connect").option("--env <environment>","Target environment").action((A,_)=>Tn(()=>Ir(_.env,async(z)=>{let U=po(z,A);c(["Type","Command","Credential reference"],Bn(U,hd(U)).map((Y)=>[Y.type,Y.command,Y.secretRef??"—"]))},A)));for(let A of["backup","snapshot"])D.command(`db:${A} <service>`,`Alias for data:action ${A==="backup"?"backup":"backup"}`).option("--env <environment>","Target environment").option("--execute","Enqueue the backup; default previews").action((_,z)=>Tn(()=>Ir(z.env,async(U)=>{let Y=po(U,_),X=U.lifecycle.plan(Y,"backup");if(e(JSON.stringify({...X,productionExecutionCreated:!1},null,2)),z.execute)n(`Queued backup as ${U.lifecycle.enqueue(Y,"backup",{},U.actor.id)}.`)},_)))}function vd(D,t=15){if(!D)return t*60000;let m=/^(\d+)\s*([smhd])?$/.exec(D.trim());if(!m)return t*60000;let A=Number(m[1]),_=m[2]??"m";return A*({s:1000,m:60000,h:3600000,d:86400000}[_]??60000)}function vp(D,t){for(let m of D){let A=m.timestamp?new Date(m.timestamp).toISOString():"",_=t?`[${t}] `:"";e(`${A} ${_}${(m.message??"").trimEnd()}`)}}function rc(D){D.command("logs","Stream serverless application logs (http + queue + cli)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli | all",{default:"all"}).option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <duration>","Look back (e.g. 1h, 30m, 15)",{default:"15m"}).action(async(t)=>{try{let m=await je(),A=t?.env||"production",{region:_,functions:z}=bt(m,A),U=new oe(_),Y=(t?.function??"all").toLowerCase();if(Y!=="all"&&!["http","queue","cli"].includes(Y)){r("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let ne=(Y==="all"?["http","queue","cli"]:[Y]).map((fe)=>({mode:fe,group:`/aws/lambda/${z[fe]}`}));i(`Logs — ${m.project.slug} (${A})`);let re=Date.now()-vd(t?.since),se=async()=>{let fe=re;for(let{mode:Ae,group:Pe}of ne)try{let{events:he=[]}=await U.filterLogEvents({logGroupName:Pe,startTime:re,filterPattern:t?.filter,limit:200});vp(he,ne.length>1?Ae:void 0);for(let Ie of he)if((Ie.timestamp??0)>fe)fe=Ie.timestamp??fe}catch(he){if(!/ResourceNotFound/i.test(String(he?.message)))throw he}re=fe+1};if(await se(),t?.tail){e(`
|
|
335
|
+
(streaming — Ctrl+C to stop)`);for(;;)await new Promise((fe)=>setTimeout(fe,3000)),await se()}}catch(m){r(`Failed to read logs: ${m.message}`),process.exitCode=1}}),D.command("metrics","Show serverless function metrics (invocations, errors, duration, throttles)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--since <duration>","Window (e.g. 1h, 24h)",{default:"1h"}).action(async(t)=>{try{let m=await je(),A=t?.env||"production",{region:_,functions:z}=bt(m,A),U=new G(_),Y=vd(t?.since,60),X=new Date,ne=new Date(X.getTime()-Y),re=Math.max(60,Math.round(Y/1000));i(`Metrics — ${m.project.slug} (${A}), last ${t?.since??"1h"}`);let se=[];for(let fe of["http","queue","cli"]){let Ae=[{Name:"FunctionName",Value:z[fe]}],Pe=async(Ke,Be)=>{let _e=await U.getMetricStatistics({Namespace:"AWS/Lambda",MetricName:Ke,Dimensions:Ae,StartTime:ne,EndTime:X,Period:re,Statistics:[Be]});if(!_e.length)return 0;if(Be==="Sum")return _e.reduce((et,Rt)=>et+(Rt.Sum??0),0);if(Be==="Maximum")return Math.max(..._e.map((et)=>et.Maximum??0));return _e.reduce((et,Rt)=>et+(Rt.Average??0),0)/_e.length},[he,Ie,Le,Re,Ee]=await Promise.all([Pe("Invocations","Sum"),Pe("Errors","Sum"),Pe("Throttles","Sum"),Pe("Duration","Average"),Pe("Duration","Maximum")]),Te=he?`${(Ie/he*100).toFixed(2)}%`:"—";se.push([fe,String(he),String(Ie),Te,String(Le),`${Re.toFixed(0)}ms`,`${Ee.toFixed(0)}ms`])}c(["Function","Invocations","Errors","Error rate","Throttles","Avg","Max"],se)}catch(m){r(`Failed to read metrics: ${m.message}`),process.exitCode=1}}),D.command("metrics:dashboard","Print the CloudWatch console URL for this app").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).action(async(t)=>{try{let m=await je(),A=t?.env||"production",{region:_,functions:z}=bt(m,A),U=encodeURIComponent(JSON.stringify({metrics:["http","queue","cli"].flatMap((Y)=>[["AWS/Lambda","Invocations","FunctionName",z[Y]],["AWS/Lambda","Errors","FunctionName",z[Y]]]),view:"timeSeries",region:_}));i("CloudWatch dashboard"),e(`https://console.aws.amazon.com/cloudwatch/home?region=${_}#metricsV2:graph=${U}`)}catch(m){r(m.message),process.exitCode=1}}),D.command("alarms","List CloudWatch alarms").option("--prefix <prefix>","Filter by alarm name prefix").option("--region <region>","AWS region").action(async(t)=>{i("CloudWatch Alarms");let m=t?.region;if(!m)try{m=(await je()).project.region}catch{}try{let _=await new G(m||process.env.AWS_REGION||"us-east-1").describeAlarms({AlarmNamePrefix:t?.prefix,MaxRecords:100});if(!_.length){e("No alarms found");return}c(["Name","State","Metric","Condition"],_.map((z)=>[z.AlarmName??"-",z.StateValue??"-",z.MetricName??"-",`${z.ComparisonOperator??""} ${z.Threshold??""}`.trim()]))}catch(A){r(`Failed to list alarms: ${A.message}`),process.exitCode=1}}),D.command("alarms:create","Create a CloudWatch alarm on a Lambda metric").option("--name <name>","Alarm name").option("--env <environment>","Environment",{default:"production"}).option("--function <which>","Function: http | queue | cli",{default:"http"}).option("--metric <metric>","Lambda metric (Errors, Throttles, Duration, Invocations)",{default:"Errors"}).option("--threshold <value>","Threshold value").option("--comparison <op>","GreaterThanThreshold | GreaterThanOrEqualToThreshold | LessThanThreshold",{default:"GreaterThanThreshold"}).option("--statistic <stat>","Sum | Average | Maximum | Minimum",{default:"Sum"}).action(async(t)=>{if(i("Creating CloudWatch Alarm"),!t?.name||t?.threshold==null){r("Missing required options: --name and --threshold"),process.exitCode=1;return}try{let m=await je(),A=t.env||"production",{region:_,functions:z}=bt(m,A),U=t.function??"http";await new G(_).putMetricAlarm({AlarmName:t.name,Namespace:"AWS/Lambda",MetricName:t.metric??"Errors",ComparisonOperator:t.comparison??"GreaterThanThreshold",Threshold:Number(t.threshold),EvaluationPeriods:1,Period:300,Statistic:t.statistic??"Sum",Dimensions:[{Name:"FunctionName",Value:z[U]}],AlarmDescription:`ts-cloud: ${z[U]} ${t.metric} ${t.comparison} ${t.threshold}`}),n(`Alarm '${t.name}' created on ${z[U]} ${t.metric}`)}catch(m){r(`Failed to create alarm: ${m.message}`),process.exitCode=1}})}async function _s(D){let t=await je(),m=D||"production";return{region:t.environments?.[m]?.region||t.project.region||"us-east-1",prefix:`${t.project.slug}/${m}`}}function ic(D){D.command("secrets:list","List secrets for an environment").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{i("Secrets");try{let{region:m,prefix:A}=await _s(t?.env),_=new K(m),{SecretList:z=[]}=await _.listSecrets({Filters:[{Key:"name",Values:[`${A}/`]}],MaxResults:100});if(!z.length){e(`No secrets found under ${A}/`);return}c(["Key","Last Changed"],z.map((U)=>[(U.Name??"").replace(`${A}/`,""),U.LastChangedDate??U.LastAccessedDate??"-"]))}catch(m){r(`Failed to list secrets: ${m.message}`),process.exitCode=1}}),D.command("secrets:set <key> <value>","Set (create or update) a secret").option("--env <environment>","Environment (production, staging, development)").action(async(t,m,A)=>{i(`Setting secret ${t}`);let{region:_,prefix:z}=await _s(A?.env),U=new K(_),Y=`${z}/${t}`,X=new s("Storing secret...");X.start();try{try{await U.putSecretValue({SecretId:Y,SecretString:m})}catch{await U.createSecret({Name:Y,SecretString:m})}X.succeed(`Secret ${t} stored (${Y})`),a("Redeploy for the new value to take effect in running functions.")}catch(ne){X.fail(`Failed to store secret: ${ne.message}`),process.exitCode=1}}),D.command("secrets:get <key>","Reveal a secret value").option("--env <environment>","Environment (production, staging, development)").action(async(t,m)=>{try{let{region:A,prefix:_}=await _s(m?.env),U=await new K(A).getSecretValue({SecretId:`${_}/${t}`});e(U.SecretString??"")}catch(A){r(`Failed to read secret: ${A.message}`),process.exitCode=1}}),D.command("secrets:delete <key>","Delete a secret").option("--env <environment>","Environment (production, staging, development)").option("--force","Delete immediately without a recovery window").action(async(t,m)=>{try{let{region:A,prefix:_}=await _s(m?.env);await new K(A).deleteSecret({SecretId:`${_}/${t}`,ForceDeleteWithoutRecovery:m?.force}),n(`Secret ${t} deleted`)}catch(A){r(`Failed to delete secret: ${A.message}`),process.exitCode=1}})}function oc(D){for(let[t,m]of[["firewall:rules","List provider firewall policy"],["firewall:block <ip>","Block an address"],["firewall:unblock <ip>","Unblock an address"],["firewall:countries","Manage geographic policy"]])D.command(t,m).action(async(...A)=>En(t.split(" ")[0],{target:String(A[0]??""),message:"No reviewed WAF/IP-set adapter is configured for this legacy command.",nextAction:"Use server firewall configuration or install a provider adapter; no rule was changed."}))}function sc(D){D.command("ssl:list","List all SSL certificates").option("--region <region>","AWS region (default: us-east-1)").action(async(t)=>{i("SSL Certificates");let m=t?.region||"us-east-1",A=new s("Fetching certificates from ACM...");A.start();try{let _=new W(m),z=await _.listCertificates();if(z.CertificateSummaryList.length===0){A.succeed("No certificates found"),e(`
|
|
336
336
|
No SSL certificates found in region ${m}`),e("Use 'cloud domain:ssl <domain>' to request a new certificate");return}let U=await Promise.all(z.CertificateSummaryList.map(async(ne)=>await _.describeCertificate({CertificateArn:ne.CertificateArn})));A.succeed(`Found ${U.length} certificate(s)`);let Y=(ne)=>{if(!ne)return"N/A";let re=typeof ne==="string"?Number.parseFloat(ne):ne,se=re<1000000000000?re*1000:re;return new Date(se).toISOString().split("T")[0]},X=U.map((ne)=>{let re=Y(ne.NotAfter),se=ne.Type==="AMAZON_ISSUED"?"Amazon Issued":ne.Type||"Unknown",fe=ne.Status==="ISSUED"?"Available":ne.Status||"Unknown";return[ne.DomainName,ne.Status||"Unknown",re,se,fe]});c(["Domain","Status","Expiry","Type","State"],X),e(`
|
|
337
337
|
ACM certificates are automatically renewed by AWS`),e(`Region: ${m}`)}catch(_){A.fail("Failed to fetch certificates"),r(_.message)}}),D.command("ssl:renew <domain>","Renew SSL certificate").option("--region <region>","AWS region (default: us-east-1)").action(async(t,m)=>{i(`Checking SSL Certificate for ${t}`);let A=m?.region||"us-east-1";e(`Domain: ${t}`),e(`Region: ${A}`);let _=new s("Checking certificate status...");_.start();try{let U=await new W(A).findCertificateByDomain(t);if(!U){_.fail("Certificate not found"),r(`No certificate found for domain: ${t}`),e("Use 'cloud domain:ssl <domain>' to request a new certificate");return}_.succeed("Certificate found"),e(`
|
|
338
338
|
Certificate is managed by AWS Certificate Manager`),e("ACM certificates are automatically renewed 60 days before expiry"),a(`
|
|
339
339
|
No manual renewal needed for ACM certificates`);let Y=(re)=>{if(!re)return"N/A";let se=typeof re==="string"?Number.parseFloat(re):re,fe=se<1000000000000?se*1000:se;return new Date(fe).toISOString().split("T")[0]},X=Y(U.NotAfter),ne=Y(U.IssuedAt);if(e(`
|
|
340
|
-
Certificate details:`),e(` - Domain: ${U.DomainName}`),e(` - Status: ${U.Status}`),e(` - Issued: ${ne}`),e(` - Expiry: ${X}`),e(` - Type: ${U.Type||"Unknown"}`),e(` - ARN: ${U.CertificateArn}`),e(` - Auto-renewal: ${U.Type==="AMAZON_ISSUED"?"Enabled":"N/A (imported)"}`),U.SubjectAlternativeNames&&U.SubjectAlternativeNames.length>1)e(` - SANs: ${U.SubjectAlternativeNames.join(", ")}`)}catch(z){_.fail("Failed to check certificate"),r(z.message)}})}function Ui(D){return D.toISOString().slice(0,10)}function bd(D){return new Date(Date.UTC(D.getUTCFullYear(),D.getUTCMonth(),D.getUTCDate()))}function wd(D=new Date){let t=bd(D),m=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),1)),A=new Date(t.getTime()+86400000),_=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()+1,1)),z=m,U=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()-1,1));return{start:Ui(m),end:Ui(A),label:m.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"}),daysElapsed:t.getUTCDate(),daysInMonth:Math.round((_.getTime()-m.getTime())/86400000),previous:{start:Ui(U),end:Ui(z),label:U.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"})}}}function Eo(D,t=new Date){if(!Number.isInteger(D)||D<1||D>366)throw Error("days must be an integer from 1 to 366");let m=new Date(bd(t).getTime()+86400000),A=new Date(m.getTime()-D*86400000),_=new Date(A.getTime()-D*86400000);return{current:{start:Ui(A),end:Ui(m),label:`last ${D} days`},previous:{start:Ui(_),end:Ui(A),label:`previous ${D} days`}}}function mc(D,t,m){return t>0?D/t*m:0}function ps(D,t){if(t===0)return D===0?0:null;return(D-t)/t*100}function fc(D,t){let m=new Map(t.map((A)=>[A.service,A.amount]));return D.map((A)=>{let _=m.get(A.service)??0;return{...A,previousAmount:_,changePercent:ps(A.amount,_)}})}var bp=/(?:data.?transfer|natgateway.*bytes|aws-(?:in|out)-bytes|regional-bytes|inter.?az|internet)/i;function zs(D){return D.filter((t)=>bp.test(t.key)).sort((t,m)=>m.amount-t.amount)}function No(D){if(D==null)return[];return Array.isArray(D)?D:[D]}function fo(D,t={}){let[,,m="unknown",A="",_="",z=D]=D.split(":"),U=z.includes("/")?"/":":",[Y=m,...X]=z.split(U),ne=X.join(U)||z;return{arn:D,service:m,type:Y,id:ne,name:t.Name||t.name||ne,region:A||void 0,accountId:_||void 0,tags:t,metadata:{}}}function wp(D,t){if(!t)return!0;let m=t.toLowerCase().replace(/^aws[:/]/,"");return[D.service,D.type,`${D.service}:${D.type}`].some((A)=>A.toLowerCase()===m||A.toLowerCase().includes(m))}function Sp(D,t){let m=new Map;for(let A of D){let _=`${A.service}:${A.type}:${A.id}`,z=m.get(_);m.set(_,z?{...z,...A,arn:z.accountId?z.arn:A.arn,accountId:z.accountId??A.accountId,tags:{...z.tags,...A.tags},metadata:{...z.metadata,...A.metadata}}:A)}return[...m.values()].filter((A)=>wp(A,t)).sort((A,_)=>A.service.localeCompare(_.service)||A.name.localeCompare(_.name)||A.arn.localeCompare(_.arn))}class gs{client;profile;region;constructor(D,t=process.env.AWS_REGION||"us-east-1"){this.profile=D,this.region=t,this.client=new p(Ct(D))}async taggedResources(){let D=[],t;do{let m=await this.client.request({service:"tagging",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-amz-json-1.1","x-amz-target":"ResourceGroupsTaggingAPI_20170126.GetResources"},body:JSON.stringify(t?{PaginationToken:t}:{})});for(let A of m?.ResourceTagMappingList??[]){let _=Object.fromEntries((A.Tags??[]).map((z)=>[String(z.Key),String(z.Value)]));D.push(fo(String(A.ResourceARN),_))}t=m?.PaginationToken||void 0}while(t);return D}async ec2Resources(){let D=async(z)=>this.client.request({service:"ec2",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({Action:z,Version:"2016-11-15"}).toString()}),[t,m,A]=await Promise.all([D("DescribeInstances"),D("DescribeVolumes"),D("DescribeAddresses")]),_=[];for(let z of No(t?.reservationSet?.item))for(let U of No(z?.instancesSet?.item)){let Y=String(U.instanceId);_.push({...fo(`arn:aws:ec2:${this.region}::instance/${Y}`),state:U.instanceState?.name,metadata:{instanceType:U.instanceType}})}for(let z of No(m?.volumeSet?.item)){let U=String(z.volumeId);_.push({...fo(`arn:aws:ec2:${this.region}::volume/${U}`),state:z.status,metadata:{sizeGiB:Number(z.size??0),attached:No(z.attachmentSet?.item).length>0}})}for(let z of No(A?.addressesSet?.item)){let U=String(z.allocationId??z.publicIp);_.push({...fo(`arn:aws:ec2:${this.region}::elastic-ip/${U}`),state:z.associationId?"associated":"available",metadata:{associated:Boolean(z.associationId),publicIp:z.publicIp}})}return _}async rdsResources(){let D=new URLSearchParams({Action:"DescribeDBInstances",Version:"2014-10-31"}).toString(),t=await this.client.request({service:"rds",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-www-form-urlencoded"},body:D}),m=t?.DescribeDBInstancesResult??t;return No(m?.DBInstances?.DBInstance).map((A)=>({...fo(A.DBInstanceArn||`arn:aws:rds:${this.region}::db:${String(A.DBInstanceIdentifier)}`),state:A.DBInstanceStatus,metadata:{engine:A.Engine,instanceClass:A.DBInstanceClass}}))}async lambdaResources(){let D=[],t;do{let m=await this.client.request({service:"lambda",region:this.region,method:"GET",path:"/2015-03-31/functions",queryParams:t?{Marker:t}:void 0});for(let A of m?.Functions??[])D.push({...fo(A.FunctionArn),state:A.State,metadata:{runtime:A.Runtime,memoryMiB:Number(A.MemorySize??0)}});t=m?.NextMarker||void 0}while(t);return D}async s3Resources(){return(await new g("us-east-1",this.profile).listBuckets()).Buckets.map((t)=>({...fo(`arn:aws:s3:::${t.Name}`),metadata:{creationDate:t.CreationDate}}))}async discover(D){let t=[["tagging:GetResources",()=>this.taggedResources()],["ec2:Describe*",()=>this.ec2Resources()],["rds:DescribeDBInstances",()=>this.rdsResources()],["lambda:ListFunctions",()=>this.lambdaResources()],["s3:ListAllMyBuckets",()=>this.s3Resources()]],m=await Promise.allSettled(t.map(([,z])=>z())),A=[],_=[];return m.forEach((z,U)=>{if(z.status==="fulfilled")A.push(...z.value);else _.push(`${t[U][0]} unavailable: ${z.reason instanceof Error?z.reason.message:String(z.reason)}`)}),{resources:Sp(A,D?.type),warnings:_}}}function hs(D,t){if(D==null)return{currentCost:null,projectedCost:null,monthlySavings:null};let m=D*t;return{currentCost:D,projectedCost:D-m,monthlySavings:m}}function Sd(D,t,m,A=!1){let _=gc(D,t,m);if(_)return[{category:"unused",resource:D,recommendation:_.recommendation,sourceSignal:_.signal,...hs(m,1)}];if((D.service==="ec2"&&D.type==="instance"||D.service==="rds"&&D.type==="db")&&t.cpuAverage!=null&&t.cpuAverage<20)return[{category:"right-size",resource:D,recommendation:"Validate memory, network, and peak demand, then test one smaller instance class.",sourceSignal:`${D.service.toUpperCase()} 30-day average CPU ${t.cpuAverage.toFixed(1)}% (<20%)`,...hs(m,0.4)}];if(A&&(D.service==="ec2"&&D.type==="instance"&&D.state==="running"||D.service==="rds"&&D.type==="db"&&D.state==="available")&&t.cpuAverage!=null&&t.cpuAverage>=20)return[{category:"commitment",resource:D,recommendation:"Compare a one-year no-upfront commitment against this steady workload before purchasing.",sourceSignal:`${D.service.toUpperCase()} remained active with 30-day average CPU ${t.cpuAverage.toFixed(1)}%`,...hs(m,0.3)}];if(D.service==="s3"&&t.storageBytes!=null&&t.storageBytes>=5368709120&&t.requests!=null&&t.requests<9000){let z=t.storageBytes/1073741824;return[{category:"storage",resource:D,recommendation:"Review object age and retrieval needs, then add Intelligent-Tiering or Glacier lifecycle rules.",sourceSignal:`${z.toFixed(1)} GiB stored with ${t.requests.toFixed(0)} requests over 90 days`,...hs(m,0.25)}]}return[]}function Cp(D,t){if(D<=0||t.length===0)return null;return{category:"cdn",resource:{arn:"arn:aws:cloudfront::account:distribution/account-wide",service:"cloudfront",type:"distribution",id:"account-wide",name:"account-wide transfer",tags:{},metadata:{}},recommendation:"Enable compression and review cache policies, TTLs, and origin cache headers for high-transfer paths.",sourceSignal:`30-day Cost Explorer transfer line items: ${t.slice(0,3).join(", ")}`,...hs(D,0.2)}}function $p(D){let t=D.map((m)=>m.Average).filter((m)=>m!=null);return t.length>0?t.reduce((m,A)=>m+A,0)/t.length:void 0}function Ap(D){let t=D.map((m)=>m.Sum).filter((m)=>m!=null);return t.length>0?t.reduce((m,A)=>m+A,0):void 0}function gc(D,t,m=null){if(D.service==="ec2"&&D.type==="volume"&&D.metadata.attached===!1)return{resource:D,signal:"EBS volume is detached",recommendation:"Snapshot if needed, then delete the detached volume.",monthlySavings:m};if(D.service==="ec2"&&D.type==="elastic-ip"&&D.metadata.associated===!1)return{resource:D,signal:"Elastic IP is not associated",recommendation:"Release the unassociated Elastic IP.",monthlySavings:m};if(D.service==="ec2"&&D.type==="instance"&&t.cpuAverage!=null&&t.cpuAverage<5)return{resource:D,signal:`30-day average CPU ${t.cpuAverage.toFixed(1)}% (<5%)`,recommendation:"Stop/delete if unused, or right-size after validating memory and network demand.",monthlySavings:m};if(D.service==="rds"&&D.type==="db"&&t.connectionsAverage!=null&&t.iopsAverage!=null&&t.connectionsAverage===0&&t.iopsAverage<1)return{resource:D,signal:`14-day connections 0 and average IOPS ${t.iopsAverage.toFixed(2)} (<1)`,recommendation:"Take a final snapshot, validate ownership, then stop or delete the database.",monthlySavings:m};if(D.service==="lambda"&&D.type==="function"&&t.invocations===0)return{resource:D,signal:"30-day invocations 0",recommendation:"Confirm no event source depends on it, then remove the function.",monthlySavings:m};if(D.service==="s3"&&t.objectCount===0&&t.requests===0)return{resource:D,signal:"Bucket is empty with zero requests over 90 days",recommendation:"Confirm retention policy, then delete the empty bucket.",monthlySavings:m};if(D.service==="elasticache"&&t.connectionsAverage!=null&&t.cacheHitRate!=null&&t.connectionsAverage===0&&t.cacheHitRate<20)return{resource:D,signal:`14-day connections 0 and cache hit rate ${t.cacheHitRate.toFixed(1)}%`,recommendation:"Validate clients and snapshots, then delete the idle cache cluster.",monthlySavings:m};return null}class Vs{inventory;costs;profile;region;constructor(D,t=process.env.AWS_REGION||"us-east-1"){this.profile=D,this.region=t,this.inventory=new gs(D,t),this.costs=new He(D)}async metric(D,t,m,A,_,z,U,Y=[]){let X=new Date,ne=new Date(X.getTime()-z*24*60*60*1000),re=await D.getMetricStatistics({Namespace:m,MetricName:A,Dimensions:[{Name:_,Value:t.id},...Y],StartTime:ne,EndTime:X,Period:86400,Statistics:[U]});return U==="Sum"?Ap(re):$p(re)}async signals(D){let t=new q(D.region??this.region,this.profile);if(D.service==="ec2"&&D.type==="instance")return{cpuAverage:await this.metric(t,D,"AWS/EC2","CPUUtilization","InstanceId",30,"Average")};if(D.service==="rds"&&D.type==="db"){let[m,A,_,z]=await Promise.all([this.metric(t,D,"AWS/RDS","CPUUtilization","DBInstanceIdentifier",30,"Average"),this.metric(t,D,"AWS/RDS","DatabaseConnections","DBInstanceIdentifier",14,"Average"),this.metric(t,D,"AWS/RDS","ReadIOPS","DBInstanceIdentifier",14,"Average"),this.metric(t,D,"AWS/RDS","WriteIOPS","DBInstanceIdentifier",14,"Average")]);return{cpuAverage:m,connectionsAverage:A,iopsAverage:_!=null&&z!=null?_+z:void 0}}if(D.service==="lambda"&&D.type==="function")return{invocations:await this.metric(t,D,"AWS/Lambda","Invocations","FunctionName",30,"Sum")};if(D.service==="s3"){let[m,A,_]=await Promise.all([this.metric(t,D,"AWS/S3","NumberOfObjects","BucketName",90,"Average",[{Name:"StorageType",Value:"AllStorageTypes"}]),this.metric(t,D,"AWS/S3","AllRequests","BucketName",90,"Sum"),this.metric(t,D,"AWS/S3","BucketSizeBytes","BucketName",90,"Average",[{Name:"StorageType",Value:"StandardStorage"}])]);return{objectCount:m,requests:A,storageBytes:_}}if(D.service==="elasticache"&&D.type.includes("cluster")){let[m,A]=await Promise.all([this.metric(t,D,"AWS/ElastiCache","CurrConnections","CacheClusterId",14,"Average"),this.metric(t,D,"AWS/ElastiCache","CacheHitRate","CacheClusterId",14,"Average")]);return{connectionsAverage:m,cacheHitRate:A}}return{}}async resourceCosts(){try{let D=Eo(30).current,t=await this.costs.getCostByDimension({start:D.start,end:D.end,dimension:"RESOURCE_ID",granularity:"DAILY"});return new Map(t.map((m)=>[m.key,m.amount]))}catch{return new Map}}resourceCost(D,t){return D.get(t.id)??D.get(t.arn)??null}async cloudFrontTransfer(){try{let D=Eo(30).current,t=await this.costs.getCostByDimension({start:D.start,end:D.end,dimension:"USAGE_TYPE",granularity:"DAILY",filter:{Dimensions:{Key:"SERVICE",Values:["Amazon CloudFront"]}}}),m=zs(t);return{cost:m.reduce((A,_)=>A+_.amount,0),usageTypes:m.map((A)=>A.key)}}catch{return{cost:0,usageTypes:[]}}}async unused(D){let[t,m]=await Promise.all([this.inventory.discover(D),this.resourceCosts()]),A=(await Promise.all(t.resources.map(async(_)=>{let z=this.resourceCost(m,_),U=gc(_,{},z);if(U)return U;try{return gc(_,await this.signals(_),z)}catch{return null}}))).filter((_)=>_!==null);return{findings:A,inventory:t,savingsAvailable:A.some((_)=>_.monthlySavings!=null)}}async optimize(D){let t=!D?.type||["cloudfront","cdn"].includes(D.type.toLowerCase()),[m,A,_]=await Promise.all([this.inventory.discover({type:D?.type}),this.resourceCosts(),t?this.cloudFrontTransfer():Promise.resolve({cost:0,usageTypes:[]})]),z=[],U=[...m.warnings];for(let X of m.resources){let ne=this.resourceCost(A,X),re=Sd(X,{},ne,D?.includeCommitments);if(re.length>0){z.push(...re);continue}try{z.push(...Sd(X,await this.signals(X),ne,D?.includeCommitments))}catch(se){U.push(`CloudWatch metrics unavailable for ${X.service}:${X.type}/${X.name}: ${se instanceof Error?se.message:String(se)}`)}}let Y=Cp(_.cost,_.usageTypes);if(Y)z.push(Y);return z.sort((X,ne)=>(ne.monthlySavings??-1)-(X.monthlySavings??-1)||X.category.localeCompare(ne.category)||X.resource.name.localeCompare(ne.resource.name)),{recommendations:z,inventory:m,savingsAvailable:z.some((X)=>X.monthlySavings!=null),warnings:U}}}var hc="Amazon Simple Storage Service";function Rp(){let D=new Date,t=new Date(Date.UTC(D.getUTCFullYear(),D.getUTCMonth(),1)),m=new Date(t);m.setUTCMonth(m.getUTCMonth()-1);let A=(z)=>z.toISOString().slice(0,10),_=m.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"});return{start:A(m),end:A(t),label:_}}function ln(D){return`$${D.toFixed(2)}`}function Ws(D){if(D===null)return"new";if(Math.abs(D)<0.05)return"→ 0.0%";return`${D>0?"↑":"↓"} ${Math.abs(D).toFixed(1)}%`}function vc(D){r(`Cost Explorer request failed: ${D instanceof Error?D.message:String(D)}`),e(`
|
|
340
|
+
Certificate details:`),e(` - Domain: ${U.DomainName}`),e(` - Status: ${U.Status}`),e(` - Issued: ${ne}`),e(` - Expiry: ${X}`),e(` - Type: ${U.Type||"Unknown"}`),e(` - ARN: ${U.CertificateArn}`),e(` - Auto-renewal: ${U.Type==="AMAZON_ISSUED"?"Enabled":"N/A (imported)"}`),U.SubjectAlternativeNames&&U.SubjectAlternativeNames.length>1)e(` - SANs: ${U.SubjectAlternativeNames.join(", ")}`)}catch(z){_.fail("Failed to check certificate"),r(z.message)}})}function Ui(D){return D.toISOString().slice(0,10)}function bd(D){return new Date(Date.UTC(D.getUTCFullYear(),D.getUTCMonth(),D.getUTCDate()))}function wd(D=new Date){let t=bd(D),m=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),1)),A=new Date(t.getTime()+86400000),_=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()+1,1)),z=m,U=new Date(Date.UTC(t.getUTCFullYear(),t.getUTCMonth()-1,1));return{start:Ui(m),end:Ui(A),label:m.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"}),daysElapsed:t.getUTCDate(),daysInMonth:Math.round((_.getTime()-m.getTime())/86400000),previous:{start:Ui(U),end:Ui(z),label:U.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"})}}}function Eo(D,t=new Date){if(!Number.isInteger(D)||D<1||D>366)throw Error("days must be an integer from 1 to 366");let m=new Date(bd(t).getTime()+86400000),A=new Date(m.getTime()-D*86400000),_=new Date(A.getTime()-D*86400000);return{current:{start:Ui(A),end:Ui(m),label:`last ${D} days`},previous:{start:Ui(_),end:Ui(A),label:`previous ${D} days`}}}function mc(D,t,m){return t>0?D/t*m:0}function ps(D,t){if(t===0)return D===0?0:null;return(D-t)/t*100}function fc(D,t){let m=new Map(t.map((A)=>[A.service,A.amount]));return D.map((A)=>{let _=m.get(A.service)??0;return{...A,previousAmount:_,changePercent:ps(A.amount,_)}})}var bp=/(?:data.?transfer|natgateway.*bytes|aws-(?:in|out)-bytes|regional-bytes|inter.?az|internet)/i;function zs(D){return D.filter((t)=>bp.test(t.key)).sort((t,m)=>m.amount-t.amount)}function No(D){if(D==null)return[];return Array.isArray(D)?D:[D]}function fo(D,t={}){let[,,m="unknown",A="",_="",z=D]=D.split(":"),U=z.includes("/")?"/":":",[Y=m,...X]=z.split(U),ne=X.join(U)||z;return{arn:D,service:m,type:Y,id:ne,name:t.Name||t.name||ne,region:A||void 0,accountId:_||void 0,tags:t,metadata:{}}}function wp(D,t){if(!t)return!0;let m=t.toLowerCase().replace(/^aws[:/]/,"");return[D.service,D.type,`${D.service}:${D.type}`].some((A)=>A.toLowerCase()===m||A.toLowerCase().includes(m))}function Sp(D,t){let m=new Map;for(let A of D){let _=`${A.service}:${A.type}:${A.id}`,z=m.get(_);m.set(_,z?{...z,...A,arn:z.accountId?z.arn:A.arn,accountId:z.accountId??A.accountId,tags:{...z.tags,...A.tags},metadata:{...z.metadata,...A.metadata}}:A)}return[...m.values()].filter((A)=>wp(A,t)).sort((A,_)=>A.service.localeCompare(_.service)||A.name.localeCompare(_.name)||A.arn.localeCompare(_.arn))}class gs{client;profile;region;constructor(D,t=process.env.AWS_REGION||"us-east-1"){this.profile=D,this.region=t,this.client=new p(Ct(D))}async taggedResources(){let D=[],t;do{let m=await this.client.request({service:"tagging",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-amz-json-1.1","x-amz-target":"ResourceGroupsTaggingAPI_20170126.GetResources"},body:JSON.stringify(t?{PaginationToken:t}:{})});for(let A of m?.ResourceTagMappingList??[]){let _=Object.fromEntries((A.Tags??[]).map((z)=>[String(z.Key),String(z.Value)]));D.push(fo(String(A.ResourceARN),_))}t=m?.PaginationToken||void 0}while(t);return D}async ec2Resources(){let D=async(z)=>this.client.request({service:"ec2",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({Action:z,Version:"2016-11-15"}).toString()}),[t,m,A]=await Promise.all([D("DescribeInstances"),D("DescribeVolumes"),D("DescribeAddresses")]),_=[];for(let z of No(t?.reservationSet?.item))for(let U of No(z?.instancesSet?.item)){let Y=String(U.instanceId);_.push({...fo(`arn:aws:ec2:${this.region}::instance/${Y}`),state:U.instanceState?.name,metadata:{instanceType:U.instanceType}})}for(let z of No(m?.volumeSet?.item)){let U=String(z.volumeId);_.push({...fo(`arn:aws:ec2:${this.region}::volume/${U}`),state:z.status,metadata:{sizeGiB:Number(z.size??0),attached:No(z.attachmentSet?.item).length>0}})}for(let z of No(A?.addressesSet?.item)){let U=String(z.allocationId??z.publicIp);_.push({...fo(`arn:aws:ec2:${this.region}::elastic-ip/${U}`),state:z.associationId?"associated":"available",metadata:{associated:Boolean(z.associationId),publicIp:z.publicIp}})}return _}async rdsResources(){let D=new URLSearchParams({Action:"DescribeDBInstances",Version:"2014-10-31"}).toString(),t=await this.client.request({service:"rds",region:this.region,method:"POST",path:"/",headers:{"content-type":"application/x-www-form-urlencoded"},body:D}),m=t?.DescribeDBInstancesResult??t;return No(m?.DBInstances?.DBInstance).map((A)=>({...fo(A.DBInstanceArn||`arn:aws:rds:${this.region}::db:${String(A.DBInstanceIdentifier)}`),state:A.DBInstanceStatus,metadata:{engine:A.Engine,instanceClass:A.DBInstanceClass}}))}async lambdaResources(){let D=[],t;do{let m=await this.client.request({service:"lambda",region:this.region,method:"GET",path:"/2015-03-31/functions",queryParams:t?{Marker:t}:void 0});for(let A of m?.Functions??[])D.push({...fo(A.FunctionArn),state:A.State,metadata:{runtime:A.Runtime,memoryMiB:Number(A.MemorySize??0)}});t=m?.NextMarker||void 0}while(t);return D}async s3Resources(){return(await new g("us-east-1",this.profile).listBuckets()).Buckets.map((t)=>({...fo(`arn:aws:s3:::${t.Name}`),metadata:{creationDate:t.CreationDate}}))}async discover(D){let t=[["tagging:GetResources",()=>this.taggedResources()],["ec2:Describe*",()=>this.ec2Resources()],["rds:DescribeDBInstances",()=>this.rdsResources()],["lambda:ListFunctions",()=>this.lambdaResources()],["s3:ListAllMyBuckets",()=>this.s3Resources()]],m=await Promise.allSettled(t.map(([,z])=>z())),A=[],_=[];return m.forEach((z,U)=>{if(z.status==="fulfilled")A.push(...z.value);else _.push(`${t[U][0]} unavailable: ${z.reason instanceof Error?z.reason.message:String(z.reason)}`)}),{resources:Sp(A,D?.type),warnings:_}}}function hs(D,t){if(D==null)return{currentCost:null,projectedCost:null,monthlySavings:null};let m=D*t;return{currentCost:D,projectedCost:D-m,monthlySavings:m}}function Sd(D,t,m,A=!1){let _=gc(D,t,m);if(_)return[{category:"unused",resource:D,recommendation:_.recommendation,sourceSignal:_.signal,...hs(m,1)}];if((D.service==="ec2"&&D.type==="instance"||D.service==="rds"&&D.type==="db")&&t.cpuAverage!=null&&t.cpuAverage<20)return[{category:"right-size",resource:D,recommendation:"Validate memory, network, and peak demand, then test one smaller instance class.",sourceSignal:`${D.service.toUpperCase()} 30-day average CPU ${t.cpuAverage.toFixed(1)}% (<20%)`,...hs(m,0.4)}];if(A&&(D.service==="ec2"&&D.type==="instance"&&D.state==="running"||D.service==="rds"&&D.type==="db"&&D.state==="available")&&t.cpuAverage!=null&&t.cpuAverage>=20)return[{category:"commitment",resource:D,recommendation:"Compare a one-year no-upfront commitment against this steady workload before purchasing.",sourceSignal:`${D.service.toUpperCase()} remained active with 30-day average CPU ${t.cpuAverage.toFixed(1)}%`,...hs(m,0.3)}];if(D.service==="s3"&&t.storageBytes!=null&&t.storageBytes>=5368709120&&t.requests!=null&&t.requests<9000){let z=t.storageBytes/1073741824;return[{category:"storage",resource:D,recommendation:"Review object age and retrieval needs, then add Intelligent-Tiering or Glacier lifecycle rules.",sourceSignal:`${z.toFixed(1)} GiB stored with ${t.requests.toFixed(0)} requests over 90 days`,...hs(m,0.25)}]}return[]}function Cp(D,t){if(D<=0||t.length===0)return null;return{category:"cdn",resource:{arn:"arn:aws:cloudfront::account:distribution/account-wide",service:"cloudfront",type:"distribution",id:"account-wide",name:"account-wide transfer",tags:{},metadata:{}},recommendation:"Enable compression and review cache policies, TTLs, and origin cache headers for high-transfer paths.",sourceSignal:`30-day Cost Explorer transfer line items: ${t.slice(0,3).join(", ")}`,...hs(D,0.2)}}function $p(D){let t=D.map((m)=>m.Average).filter((m)=>m!=null);return t.length>0?t.reduce((m,A)=>m+A,0)/t.length:void 0}function Ap(D){let t=D.map((m)=>m.Sum).filter((m)=>m!=null);return t.length>0?t.reduce((m,A)=>m+A,0):void 0}function gc(D,t,m=null){if(D.service==="ec2"&&D.type==="volume"&&D.metadata.attached===!1)return{resource:D,signal:"EBS volume is detached",recommendation:"Snapshot if needed, then delete the detached volume.",monthlySavings:m};if(D.service==="ec2"&&D.type==="elastic-ip"&&D.metadata.associated===!1)return{resource:D,signal:"Elastic IP is not associated",recommendation:"Release the unassociated Elastic IP.",monthlySavings:m};if(D.service==="ec2"&&D.type==="instance"&&t.cpuAverage!=null&&t.cpuAverage<5)return{resource:D,signal:`30-day average CPU ${t.cpuAverage.toFixed(1)}% (<5%)`,recommendation:"Stop/delete if unused, or right-size after validating memory and network demand.",monthlySavings:m};if(D.service==="rds"&&D.type==="db"&&t.connectionsAverage!=null&&t.iopsAverage!=null&&t.connectionsAverage===0&&t.iopsAverage<1)return{resource:D,signal:`14-day connections 0 and average IOPS ${t.iopsAverage.toFixed(2)} (<1)`,recommendation:"Take a final snapshot, validate ownership, then stop or delete the database.",monthlySavings:m};if(D.service==="lambda"&&D.type==="function"&&t.invocations===0)return{resource:D,signal:"30-day invocations 0",recommendation:"Confirm no event source depends on it, then remove the function.",monthlySavings:m};if(D.service==="s3"&&t.objectCount===0&&t.requests===0)return{resource:D,signal:"Bucket is empty with zero requests over 90 days",recommendation:"Confirm retention policy, then delete the empty bucket.",monthlySavings:m};if(D.service==="elasticache"&&t.connectionsAverage!=null&&t.cacheHitRate!=null&&t.connectionsAverage===0&&t.cacheHitRate<20)return{resource:D,signal:`14-day connections 0 and cache hit rate ${t.cacheHitRate.toFixed(1)}%`,recommendation:"Validate clients and snapshots, then delete the idle cache cluster.",monthlySavings:m};return null}class Vs{inventory;costs;profile;region;constructor(D,t=process.env.AWS_REGION||"us-east-1"){this.profile=D,this.region=t,this.inventory=new gs(D,t),this.costs=new He(D)}async metric(D,t,m,A,_,z,U,Y=[]){let X=new Date,ne=new Date(X.getTime()-z*24*60*60*1000),re=await D.getMetricStatistics({Namespace:m,MetricName:A,Dimensions:[{Name:_,Value:t.id},...Y],StartTime:ne,EndTime:X,Period:86400,Statistics:[U]});return U==="Sum"?Ap(re):$p(re)}async signals(D){let t=new G(D.region??this.region,this.profile);if(D.service==="ec2"&&D.type==="instance")return{cpuAverage:await this.metric(t,D,"AWS/EC2","CPUUtilization","InstanceId",30,"Average")};if(D.service==="rds"&&D.type==="db"){let[m,A,_,z]=await Promise.all([this.metric(t,D,"AWS/RDS","CPUUtilization","DBInstanceIdentifier",30,"Average"),this.metric(t,D,"AWS/RDS","DatabaseConnections","DBInstanceIdentifier",14,"Average"),this.metric(t,D,"AWS/RDS","ReadIOPS","DBInstanceIdentifier",14,"Average"),this.metric(t,D,"AWS/RDS","WriteIOPS","DBInstanceIdentifier",14,"Average")]);return{cpuAverage:m,connectionsAverage:A,iopsAverage:_!=null&&z!=null?_+z:void 0}}if(D.service==="lambda"&&D.type==="function")return{invocations:await this.metric(t,D,"AWS/Lambda","Invocations","FunctionName",30,"Sum")};if(D.service==="s3"){let[m,A,_]=await Promise.all([this.metric(t,D,"AWS/S3","NumberOfObjects","BucketName",90,"Average",[{Name:"StorageType",Value:"AllStorageTypes"}]),this.metric(t,D,"AWS/S3","AllRequests","BucketName",90,"Sum"),this.metric(t,D,"AWS/S3","BucketSizeBytes","BucketName",90,"Average",[{Name:"StorageType",Value:"StandardStorage"}])]);return{objectCount:m,requests:A,storageBytes:_}}if(D.service==="elasticache"&&D.type.includes("cluster")){let[m,A]=await Promise.all([this.metric(t,D,"AWS/ElastiCache","CurrConnections","CacheClusterId",14,"Average"),this.metric(t,D,"AWS/ElastiCache","CacheHitRate","CacheClusterId",14,"Average")]);return{connectionsAverage:m,cacheHitRate:A}}return{}}async resourceCosts(){try{let D=Eo(30).current,t=await this.costs.getCostByDimension({start:D.start,end:D.end,dimension:"RESOURCE_ID",granularity:"DAILY"});return new Map(t.map((m)=>[m.key,m.amount]))}catch{return new Map}}resourceCost(D,t){return D.get(t.id)??D.get(t.arn)??null}async cloudFrontTransfer(){try{let D=Eo(30).current,t=await this.costs.getCostByDimension({start:D.start,end:D.end,dimension:"USAGE_TYPE",granularity:"DAILY",filter:{Dimensions:{Key:"SERVICE",Values:["Amazon CloudFront"]}}}),m=zs(t);return{cost:m.reduce((A,_)=>A+_.amount,0),usageTypes:m.map((A)=>A.key)}}catch{return{cost:0,usageTypes:[]}}}async unused(D){let[t,m]=await Promise.all([this.inventory.discover(D),this.resourceCosts()]),A=(await Promise.all(t.resources.map(async(_)=>{let z=this.resourceCost(m,_),U=gc(_,{},z);if(U)return U;try{return gc(_,await this.signals(_),z)}catch{return null}}))).filter((_)=>_!==null);return{findings:A,inventory:t,savingsAvailable:A.some((_)=>_.monthlySavings!=null)}}async optimize(D){let t=!D?.type||["cloudfront","cdn"].includes(D.type.toLowerCase()),[m,A,_]=await Promise.all([this.inventory.discover({type:D?.type}),this.resourceCosts(),t?this.cloudFrontTransfer():Promise.resolve({cost:0,usageTypes:[]})]),z=[],U=[...m.warnings];for(let X of m.resources){let ne=this.resourceCost(A,X),re=Sd(X,{},ne,D?.includeCommitments);if(re.length>0){z.push(...re);continue}try{z.push(...Sd(X,await this.signals(X),ne,D?.includeCommitments))}catch(se){U.push(`CloudWatch metrics unavailable for ${X.service}:${X.type}/${X.name}: ${se instanceof Error?se.message:String(se)}`)}}let Y=Cp(_.cost,_.usageTypes);if(Y)z.push(Y);return z.sort((X,ne)=>(ne.monthlySavings??-1)-(X.monthlySavings??-1)||X.category.localeCompare(ne.category)||X.resource.name.localeCompare(ne.resource.name)),{recommendations:z,inventory:m,savingsAvailable:z.some((X)=>X.monthlySavings!=null),warnings:U}}}var hc="Amazon Simple Storage Service";function Rp(){let D=new Date,t=new Date(Date.UTC(D.getUTCFullYear(),D.getUTCMonth(),1)),m=new Date(t);m.setUTCMonth(m.getUTCMonth()-1);let A=(z)=>z.toISOString().slice(0,10),_=m.toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"});return{start:A(m),end:A(t),label:_}}function ln(D){return`$${D.toFixed(2)}`}function Ws(D){if(D===null)return"new";if(Math.abs(D)<0.05)return"→ 0.0%";return`${D>0?"↑":"↓"} ${Math.abs(D).toFixed(1)}%`}function vc(D){r(`Cost Explorer request failed: ${D instanceof Error?D.message:String(D)}`),e(`
|
|
341
341
|
This command needs the ce:GetCostAndUsage IAM permission.`)}function kp(D){let{label:t,profile:m,rows:A,total:_,count:z}=D,U=`# AWS Cost Analysis — ${t}`,Y=m?`
|
|
342
342
|
_Profile: \`${m}\`_
|
|
343
343
|
`:`
|
|
@@ -360,9 +360,9 @@ Total: ${ln(ne)} (${Ws(ps(ne,re))})`)}catch(U){z.stop(),vc(U)}}),D.command("cost
|
|
|
360
360
|
Total ranked egress: ${ln(U.reduce((Y,X)=>Y+X.amount,0))}`),e("Usage types classify NAT, internet, inter-AZ, and inter-region transfer; destination correlation requires VPC Flow Logs.")}catch(U){z.stop(),vc(U)}}),D.command("resources","List all resources").option("--env <environment>","Environment (production, staging, development)").option("--type <type>","Resource type (ec2, rds, s3, lambda, etc.)").option("--region <region>","AWS region for regional resources").action(async(t)=>{i(`AWS Resources${t?.profile?` (profile: ${t.profile})`:""}`);let m=new s("Discovering tagged and untagged resources...");m.start();let A=new gs(t?.profile,t?.region);try{let _=await A.discover({type:t?.type});if(m.stop(),_.resources.length===0)e("No matching resources were discovered.");else c(["Type","Name","Region","State","Monthly cost"],_.resources.map((z)=>[`${z.service}:${z.type}`,z.name,z.region??"global",z.state??"unknown","— (requires CUR resource IDs)"])),e(`
|
|
361
361
|
${_.resources.length} resource${_.resources.length===1?"":"s"} discovered.`);for(let z of _.warnings)a(z)}catch(_){m.stop(),r(`Resource discovery failed: ${_ instanceof Error?_.message:String(_)}`)}}),D.command("resources:unused","Find unused resources").option("--env <environment>","Environment (production, staging, development)").option("--type <type>","Limit analysis to one resource type").option("--region <region>","AWS region for regional resources").action(async(t)=>{i(`Unused AWS Resources${t?.profile?` (profile: ${t.profile})`:""}`);let m=new s("Evaluating inventory and CloudWatch idle signals...");m.start();try{let A=await new Vs(t?.profile,t?.region).unused({type:t?.type});if(m.stop(),A.findings.length===0)e("No resources met the conservative unused-resource thresholds.");else{c(["Resource","Signal","Recommendation","Monthly savings"],A.findings.map((z)=>[`${z.resource.service}:${z.resource.type}/${z.resource.name}`,z.signal,z.recommendation,z.monthlySavings==null?"—":ln(z.monthlySavings)]));let _=A.findings.reduce((z,U)=>z+(U.monthlySavings??0),0);e(`
|
|
362
362
|
Known monthly savings: ${A.savingsAvailable?ln(_):"unavailable (enable CUR resource IDs)"}`)}for(let _ of A.inventory.warnings)a(_)}catch(A){m.stop(),r(`Unused-resource analysis failed: ${A instanceof Error?A.message:String(A)}`)}}),D.command("optimize","Suggest cost optimizations").option("--env <environment>","Environment (production, staging, development)").option("--type <type>","Limit analysis to one resource type").option("--region <region>","AWS region for regional resources").option("--include-commitments","Include estimated RI and Savings Plan candidates").action(async(t)=>{i(`AWS Cost Optimizations${t?.profile?` (profile: ${t.profile})`:""}`);let m=new s("Combining inventory, CloudWatch signals, and Cost Explorer line items...");m.start();try{let A=await new Vs(t?.profile,t?.region).optimize({type:t?.type,includeCommitments:t?.includeCommitments});if(m.stop(),A.recommendations.length===0)e("No resources met the conservative optimization thresholds.");else{c(["Category","Resource","Current","Projected","Savings","Source signal","Recommendation"],A.recommendations.map((z)=>[z.category,`${z.resource.service}:${z.resource.type}/${z.resource.name}`,z.currentCost==null?"—":ln(z.currentCost),z.projectedCost==null?"—":ln(z.projectedCost),z.monthlySavings==null?"—":ln(z.monthlySavings),z.sourceSignal,z.recommendation]));let _=A.recommendations.reduce((z,U)=>z+(U.monthlySavings??0),0);e(`
|
|
363
|
-
Estimated monthly savings: ${A.savingsAvailable?ln(_):"unavailable (enable CUR resource IDs)"}`)}for(let _ of A.warnings)a(_);e("Estimates are directional; validate workload peaks and current AWS pricing before applying changes.")}catch(A){m.stop(),r(`Optimization analysis failed: ${A instanceof Error?A.message:String(A)}`)}})}async function Oo(D){let t=await je(),m=D??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);return{config:t,env:m,controlPlane:A,controls:new Yt(A.store)}}function Lo(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}function Cd(D){let t=D?.infrastructure?.compute??{};return{ddos:t.ddos===!1?void 0:typeof t.ddos==="object"?t.ddos:{ports:[80,443]},waf:t.waf===!1?void 0:typeof t.waf==="object"?t.waf:{mode:"detection"}}}function Cc(D){D.command("protect:status","Show the current edge-protection posture").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Oo(t.env),A=m.controls.current(),_=Vi(A);if(t.json){e(JSON.stringify({posture:_,controls:m.controls.raw()},null,2));return}if(i("Edge protection"),_.mitigationPaused||_.attackMode)a(_.summary);else n(_.summary);let z=Cd(m.config);if(e(`L3/L4 mitigation: ${z.ddos?"enabled":"disabled in config"}`),e(`WAF: ${z.waf?`enabled (${z.waf.mode??"detection"})`:"disabled in config"}`),e(`Allowlist: ${A.ipRules.allow.length} entry(s)`),e(`Blocklist: ${A.ipRules.block.length} entry(s)`),A.ipRules.allow.length>0||A.ipRules.block.length>0)c(["List","CIDR"],[...A.ipRules.allow.map((U)=>["allow",U]),...A.ipRules.block.map((U)=>["block",U])])}catch(m){Lo(m)}}),D.command("protect:attack-mode <state>","Challenge every visitor for a bounded window (on|off)").option("--hours <hours>",`How long before it lifts itself (default ${Rn}, max ${Qt})`).option("--reason <reason>","Why, for the record").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Oo(m.env);if(t==="off"){n(A.controls.disableAttackMode()?"Attack mode is off.":"Attack mode was not on.");return}if(t!=="on")throw Error("State must be 'on' or 'off'.");let _=A.controls.enableAttackMode({hours:m.hours==null?void 0:Number(m.hours),reason:m.reason??"Enabled from the CLI."});a("Attack mode is ON. Every visitor is challenged, including legitimate ones."),e(`It lifts itself at ${_.expiresAt} — you do not need to remember.`)}catch(A){Lo(A)}}),D.command("protect:pause <state>","Pause or resume automatic mitigation (on|off)").option("--hours <hours>",`How long (max ${Qt})`).option("--reason <reason>","Why — required when pausing").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Oo(m.env);if(t==="off"){n(A.controls.resumeMitigations()?"Automatic mitigation resumed.":"Mitigation was not paused.");return}if(t!=="on")throw Error("State must be 'on' or 'off'.");if(!m.reason)throw Error("--reason is required: you are liable for the traffic this admits.");let _=A.controls.pauseMitigations({hours:m.hours==null?void 0:Number(m.hours),reason:m.reason});a("Automatic mitigation is PAUSED. All traffic is served, and billed, including attacks."),e(`It resumes automatically at ${_.expiresAt}.`)}catch(A){Lo(A)}}),D.command("protect:allow <cidr>","Never rate-limit or block this IP or CIDR").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Oo(m.env),_=m.remove?A.controls.removeIpRule("allow",t):A.controls.addIpRule("allow",t);n(`Allowlist now has ${_.allow.length} entry(s).`),e("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(A){Lo(A)}}),D.command("protect:block <cidr>","Drop traffic from this IP or CIDR at the kernel").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Oo(m.env),_=m.remove?A.controls.removeIpRule("block",t):A.controls.addIpRule("block",t);n(`Blocklist now has ${_.block.length} entry(s).`),e("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(A){Lo(A)}}),D.command("protect:apply","Print the ruleset the current controls produce").option("--env <environment>","Target environment").option("--waf","Print the WAF configuration instead of the firewall script").action(async(t)=>{try{let m=await Oo(t.env),A=Cd(m.config);if(t.waf){if(!A.waf)throw Error("The WAF is disabled in this configuration.");let _=ui(A.waf);for(let z of _.warnings)a(z);e(_.config);return}if(!A.ddos)throw Error("L3/L4 mitigation is disabled in this configuration.");e(Cr(In(A.ddos,m.controls.current())))}catch(m){Lo(m)}})}async function Fn(D){let t=await je(),m=D??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),_=A.environments.get(m),z=new vr(A.store),U=new ft(A.store,{encryptionKey:x(process.cwd())});return{config:t,env:m,controlPlane:A,environmentRecord:_,store:z,alerts:U,service:new ht(z),gate:new Mt(A.store),organizationId:A.organization.id,projectId:A.project.id}}function qs(D){if(D==null)return;let t=String(D).trim().replace(/[$,\s]/g,"");if(!t)return;if(/^\d+c$/i.test(t))return Number.parseInt(t,10);let m=Number(t);if(!Number.isFinite(m)||m<0)throw Error(`Invalid amount: ${D}`);return Math.round(m*100)}function $d(D){if(!D)return;let t=D.split(",").map((m)=>m.trim()).filter(Boolean);for(let m of t)if(!so.includes(m))throw Error(`Unknown enforcement action '${m}'. Valid: ${so.join(", ")}`);return t}function Ad(D){if(D==null)return"monthly";if(D==="daily"||D==="weekly"||D==="monthly")return D;throw Error(`Unknown period '${D}'. Valid: daily, weekly, monthly`)}function Ip(D){return an[D]?.label??D}function Pp(D,t=24){let m=Math.max(0,Math.min(t,Math.round(D/100*t)));return`${"█".repeat(m)}${"░".repeat(t-m)}`}function ur(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}function $c(D){D.command("usage","Show current usage, cost, and remaining budget headroom").option("--env <environment>","Target environment").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=m.service.usageReport({organizationId:m.organizationId,projectId:m.projectId,period:Ad(t.period),timezone:t.timezone});if(t.json){e(JSON.stringify(A,null,2));return}if(i(`Usage — ${A.window.label}`),e(`Total: ${ee(A.totalCents,A.currency)}`),A.byMeter.length===0)e("No metered usage in this window yet.");else c(["Meter","Provider","Quantity","Cost"],A.byMeter.map((_)=>[Ip(_.meter),_.provider||"-",_.quantity.toLocaleString(void 0,{maximumFractionDigits:3}),ee(_.costCents,A.currency)]));if(A.budgets.length===0){a("No budgets are configured. Nothing is capping this project."),e("Create one with: cloud budget:create --name 'Monthly' --hard 200");return}if(i("Budgets"),c(["Name","Period","Spent","Limit","Used","Projected","Time to cap","State"],A.budgets.map((_)=>[_.name+(_.dryRun?" (dry run)":""),_.period,ee(_.spentCents,A.currency),_.limitCents==null?"-":ee(_.limitCents,A.currency),`${Pp(_.usedPercent)} ${_.usedPercent.toFixed(0)}%`,`${ee(_.projectedCents,A.currency)} (${(_.projectionConfidence*100).toFixed(0)}% conf)`,_.timeToCap||"-",_.level])),A.enforcement.strongestAction)a(`Enforcement in force: ${A.enforcement.strongestAction}`)}catch(m){ur(m)}}),D.command("budget:list","List budgets governing this project").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=m.store.listBudgets({organizationId:m.organizationId,projectId:m.projectId});if(t.json){e(JSON.stringify(A,null,2));return}if(A.length===0){a("No budgets configured.");return}c(["ID","Name","Scope","Period","Soft","Hard","Mode","Enabled"],A.map((_)=>[_.id.slice(0,8),_.name,_.environmentId?"environment":_.projectId?"project":"organization",_.period,_.softLimitCents==null?"-":ee(_.softLimitCents,_.currency),_.hardLimitCents==null?"-":ee(_.hardLimitCents,_.currency),_.dryRun?"dry run":"enforcing",_.enabled?"yes":"no"]))}catch(m){ur(m)}}),D.command("budget:create","Create a spend budget").option("--name <name>","Budget name").option("--soft <amount>","Soft limit in dollars (warn only)").option("--hard <amount>","Hard limit in dollars (enforces)").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--scope <scope>","organization, project, or environment (default project)").option("--actions <list>","Comma-separated actions to run at 100% (default notify,block_builds,block_deployments)").option("--grace <seconds>","Seconds a breach must persist before enforcing").option("--dry-run","Evaluate and report but never enforce").option("--env <environment>","Target environment").action(async(t)=>{try{if(!t.name)throw Error("--name is required.");let m=await Fn(t.env),A=t.scope??"project";if(!["organization","project","environment"].includes(A))throw Error(`Unknown scope '${A}'. Valid: organization, project, environment`);let _=$d(t.actions),z=m.store.createBudget({organizationId:m.organizationId,projectId:A==="organization"?void 0:m.projectId,environmentId:A==="environment"?m.environmentRecord?.id:void 0,name:t.name,period:Ad(t.period),timezone:t.timezone,softLimitCents:qs(t.soft),hardLimitCents:qs(t.hard),graceSeconds:t.grace==null?void 0:Number(t.grace),thresholds:_?[{atPercent:80,actions:["notify"]},{atPercent:100,actions:_}]:[...co],dryRun:t.dryRun===!0});n(`Created budget '${z.name}' (${z.id}).`),e(`Mode: ${z.dryRun?"dry run — reports only, enforces nothing":"enforcing"}`),e(`Ladder: ${z.thresholds.map((U)=>`${U.atPercent}% → ${U.actions.join("+")}`).join(", ")}`)}catch(m){ur(m)}}),D.command("budget:update <budgetId>","Update a budget").option("--soft <amount>","Soft limit in dollars").option("--hard <amount>","Hard limit in dollars").option("--enable","Enable the budget").option("--disable","Disable the budget").option("--enforce","Leave dry run and start enforcing").option("--dry-run","Return to dry run").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Fn(m.env),_=A.store.getBudget(t);if(!_)throw Error(`Budget '${t}' was not found.`);let z=A.store.updateBudget(t,{softLimitCents:qs(m.soft)??_.softLimitCents,hardLimitCents:qs(m.hard)??_.hardLimitCents,enabled:m.disable?!1:m.enable?!0:_.enabled,dryRun:m.enforce?!1:m.dryRun?!0:_.dryRun});n(`Updated budget '${z.name}'.`),e(`Mode: ${z.dryRun?"dry run":"enforcing"} · Enabled: ${z.enabled?"yes":"no"}`)}catch(A){ur(A)}}),D.command("budget:delete <budgetId>","Delete a budget and lift anything it was enforcing").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Fn(m.env),_=A.store.getBudget(t);if(!_)throw Error(`Budget '${t}' was not found.`);let z=A.gate.closeBudget(t);if(A.store.deleteBudget(t),n(`Deleted budget '${_.name}'.`),z>0)e(`Lifted ${z} enforcement action(s) it had in force.`)}catch(A){ur(A)}}),D.command("spend:check","Run one spend cycle now and report what it would do").option("--env <environment>","Target environment").option("--apply","Actually apply enforcement (default is a preview)").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=new Si({controlPlane:m.controlPlane.store,store:m.store,alerts:t.apply?m.alerts:void 0});if(!t.apply){let U=m.store.budgetsForScope(m.organizationId,m.projectId).map((Y)=>m.service.status(Y));if(t.json){e(JSON.stringify(U,null,2));return}if(i("Spend check (preview — nothing was applied)"),U.length===0){a("No budgets govern this project.");return}c(["Budget","Used","Projected","Level","Would apply"],U.map((Y)=>[Y.budget.name,`${Y.decision.usedPercent.toFixed(1)}%`,`${Y.decision.projectedPercent.toFixed(1)}%`,Y.decision.level,Y.decision.actions.join(", ")||"-"])),e(`
|
|
363
|
+
Estimated monthly savings: ${A.savingsAvailable?ln(_):"unavailable (enable CUR resource IDs)"}`)}for(let _ of A.warnings)a(_);e("Estimates are directional; validate workload peaks and current AWS pricing before applying changes.")}catch(A){m.stop(),r(`Optimization analysis failed: ${A instanceof Error?A.message:String(A)}`)}})}async function Oo(D){let t=await je(),m=D??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);return{config:t,env:m,controlPlane:A,controls:new Yt(A.store)}}function Lo(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}function Cd(D){let t=D?.infrastructure?.compute??{};return{ddos:t.ddos===!1?void 0:typeof t.ddos==="object"?t.ddos:{ports:[80,443]},waf:t.waf===!1?void 0:typeof t.waf==="object"?t.waf:{mode:"detection"}}}function Cc(D){D.command("protect:status","Show the current edge-protection posture").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Oo(t.env),A=m.controls.current(),_=Vi(A);if(t.json){e(JSON.stringify({posture:_,controls:m.controls.raw()},null,2));return}if(i("Edge protection"),_.mitigationPaused||_.attackMode)a(_.summary);else n(_.summary);let z=Cd(m.config);if(e(`L3/L4 mitigation: ${z.ddos?"enabled":"disabled in config"}`),e(`WAF: ${z.waf?`enabled (${z.waf.mode??"detection"})`:"disabled in config"}`),e(`Allowlist: ${A.ipRules.allow.length} entry(s)`),e(`Blocklist: ${A.ipRules.block.length} entry(s)`),A.ipRules.allow.length>0||A.ipRules.block.length>0)c(["List","CIDR"],[...A.ipRules.allow.map((U)=>["allow",U]),...A.ipRules.block.map((U)=>["block",U])])}catch(m){Lo(m)}}),D.command("protect:attack-mode <state>","Challenge every visitor for a bounded window (on|off)").option("--hours <hours>",`How long before it lifts itself (default ${Rn}, max ${Qt})`).option("--reason <reason>","Why, for the record").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Oo(m.env);if(t==="off"){n(A.controls.disableAttackMode()?"Attack mode is off.":"Attack mode was not on.");return}if(t!=="on")throw Error("State must be 'on' or 'off'.");let _=A.controls.enableAttackMode({hours:m.hours==null?void 0:Number(m.hours),reason:m.reason??"Enabled from the CLI."});a("Attack mode is ON. Every visitor is challenged, including legitimate ones."),e(`It lifts itself at ${_.expiresAt} — you do not need to remember.`)}catch(A){Lo(A)}}),D.command("protect:pause <state>","Pause or resume automatic mitigation (on|off)").option("--hours <hours>",`How long (max ${Qt})`).option("--reason <reason>","Why — required when pausing").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Oo(m.env);if(t==="off"){n(A.controls.resumeMitigations()?"Automatic mitigation resumed.":"Mitigation was not paused.");return}if(t!=="on")throw Error("State must be 'on' or 'off'.");if(!m.reason)throw Error("--reason is required: you are liable for the traffic this admits.");let _=A.controls.pauseMitigations({hours:m.hours==null?void 0:Number(m.hours),reason:m.reason});a("Automatic mitigation is PAUSED. All traffic is served, and billed, including attacks."),e(`It resumes automatically at ${_.expiresAt}.`)}catch(A){Lo(A)}}),D.command("protect:allow <cidr>","Never rate-limit or block this IP or CIDR").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Oo(m.env),_=m.remove?A.controls.removeIpRule("allow",t):A.controls.addIpRule("allow",t);n(`Allowlist now has ${_.allow.length} entry(s).`),e("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(A){Lo(A)}}),D.command("protect:block <cidr>","Drop traffic from this IP or CIDR at the kernel").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Oo(m.env),_=m.remove?A.controls.removeIpRule("block",t):A.controls.addIpRule("block",t);n(`Blocklist now has ${_.block.length} entry(s).`),e("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(A){Lo(A)}}),D.command("protect:apply","Print the ruleset the current controls produce").option("--env <environment>","Target environment").option("--waf","Print the WAF configuration instead of the firewall script").action(async(t)=>{try{let m=await Oo(t.env),A=Cd(m.config);if(t.waf){if(!A.waf)throw Error("The WAF is disabled in this configuration.");let _=ui(A.waf);for(let z of _.warnings)a(z);e(_.config);return}if(!A.ddos)throw Error("L3/L4 mitigation is disabled in this configuration.");e(Cr(In(A.ddos,m.controls.current())))}catch(m){Lo(m)}})}async function Fn(D){let t=await je(),m=D??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),_=A.environments.get(m),z=new vr(A.store),U=new ft(A.store,{encryptionKey:x(process.cwd())});return{config:t,env:m,controlPlane:A,environmentRecord:_,store:z,alerts:U,service:new ht(z),gate:new Mt(A.store),organizationId:A.organization.id,projectId:A.project.id}}function Gs(D){if(D==null)return;let t=String(D).trim().replace(/[$,\s]/g,"");if(!t)return;if(/^\d+c$/i.test(t))return Number.parseInt(t,10);let m=Number(t);if(!Number.isFinite(m)||m<0)throw Error(`Invalid amount: ${D}`);return Math.round(m*100)}function $d(D){if(!D)return;let t=D.split(",").map((m)=>m.trim()).filter(Boolean);for(let m of t)if(!so.includes(m))throw Error(`Unknown enforcement action '${m}'. Valid: ${so.join(", ")}`);return t}function Ad(D){if(D==null)return"monthly";if(D==="daily"||D==="weekly"||D==="monthly")return D;throw Error(`Unknown period '${D}'. Valid: daily, weekly, monthly`)}function Ip(D){return an[D]?.label??D}function Pp(D,t=24){let m=Math.max(0,Math.min(t,Math.round(D/100*t)));return`${"█".repeat(m)}${"░".repeat(t-m)}`}function ur(D){r(D instanceof Error?D.message:String(D)),process.exitCode=1}function $c(D){D.command("usage","Show current usage, cost, and remaining budget headroom").option("--env <environment>","Target environment").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=m.service.usageReport({organizationId:m.organizationId,projectId:m.projectId,period:Ad(t.period),timezone:t.timezone});if(t.json){e(JSON.stringify(A,null,2));return}if(i(`Usage — ${A.window.label}`),e(`Total: ${ee(A.totalCents,A.currency)}`),A.byMeter.length===0)e("No metered usage in this window yet.");else c(["Meter","Provider","Quantity","Cost"],A.byMeter.map((_)=>[Ip(_.meter),_.provider||"-",_.quantity.toLocaleString(void 0,{maximumFractionDigits:3}),ee(_.costCents,A.currency)]));if(A.budgets.length===0){a("No budgets are configured. Nothing is capping this project."),e("Create one with: cloud budget:create --name 'Monthly' --hard 200");return}if(i("Budgets"),c(["Name","Period","Spent","Limit","Used","Projected","Time to cap","State"],A.budgets.map((_)=>[_.name+(_.dryRun?" (dry run)":""),_.period,ee(_.spentCents,A.currency),_.limitCents==null?"-":ee(_.limitCents,A.currency),`${Pp(_.usedPercent)} ${_.usedPercent.toFixed(0)}%`,`${ee(_.projectedCents,A.currency)} (${(_.projectionConfidence*100).toFixed(0)}% conf)`,_.timeToCap||"-",_.level])),A.enforcement.strongestAction)a(`Enforcement in force: ${A.enforcement.strongestAction}`)}catch(m){ur(m)}}),D.command("budget:list","List budgets governing this project").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=m.store.listBudgets({organizationId:m.organizationId,projectId:m.projectId});if(t.json){e(JSON.stringify(A,null,2));return}if(A.length===0){a("No budgets configured.");return}c(["ID","Name","Scope","Period","Soft","Hard","Mode","Enabled"],A.map((_)=>[_.id.slice(0,8),_.name,_.environmentId?"environment":_.projectId?"project":"organization",_.period,_.softLimitCents==null?"-":ee(_.softLimitCents,_.currency),_.hardLimitCents==null?"-":ee(_.hardLimitCents,_.currency),_.dryRun?"dry run":"enforcing",_.enabled?"yes":"no"]))}catch(m){ur(m)}}),D.command("budget:create","Create a spend budget").option("--name <name>","Budget name").option("--soft <amount>","Soft limit in dollars (warn only)").option("--hard <amount>","Hard limit in dollars (enforces)").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--scope <scope>","organization, project, or environment (default project)").option("--actions <list>","Comma-separated actions to run at 100% (default notify,block_builds,block_deployments)").option("--grace <seconds>","Seconds a breach must persist before enforcing").option("--dry-run","Evaluate and report but never enforce").option("--env <environment>","Target environment").action(async(t)=>{try{if(!t.name)throw Error("--name is required.");let m=await Fn(t.env),A=t.scope??"project";if(!["organization","project","environment"].includes(A))throw Error(`Unknown scope '${A}'. Valid: organization, project, environment`);let _=$d(t.actions),z=m.store.createBudget({organizationId:m.organizationId,projectId:A==="organization"?void 0:m.projectId,environmentId:A==="environment"?m.environmentRecord?.id:void 0,name:t.name,period:Ad(t.period),timezone:t.timezone,softLimitCents:Gs(t.soft),hardLimitCents:Gs(t.hard),graceSeconds:t.grace==null?void 0:Number(t.grace),thresholds:_?[{atPercent:80,actions:["notify"]},{atPercent:100,actions:_}]:[...co],dryRun:t.dryRun===!0});n(`Created budget '${z.name}' (${z.id}).`),e(`Mode: ${z.dryRun?"dry run — reports only, enforces nothing":"enforcing"}`),e(`Ladder: ${z.thresholds.map((U)=>`${U.atPercent}% → ${U.actions.join("+")}`).join(", ")}`)}catch(m){ur(m)}}),D.command("budget:update <budgetId>","Update a budget").option("--soft <amount>","Soft limit in dollars").option("--hard <amount>","Hard limit in dollars").option("--enable","Enable the budget").option("--disable","Disable the budget").option("--enforce","Leave dry run and start enforcing").option("--dry-run","Return to dry run").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Fn(m.env),_=A.store.getBudget(t);if(!_)throw Error(`Budget '${t}' was not found.`);let z=A.store.updateBudget(t,{softLimitCents:Gs(m.soft)??_.softLimitCents,hardLimitCents:Gs(m.hard)??_.hardLimitCents,enabled:m.disable?!1:m.enable?!0:_.enabled,dryRun:m.enforce?!1:m.dryRun?!0:_.dryRun});n(`Updated budget '${z.name}'.`),e(`Mode: ${z.dryRun?"dry run":"enforcing"} · Enabled: ${z.enabled?"yes":"no"}`)}catch(A){ur(A)}}),D.command("budget:delete <budgetId>","Delete a budget and lift anything it was enforcing").option("--env <environment>","Target environment").action(async(t,m)=>{try{let A=await Fn(m.env),_=A.store.getBudget(t);if(!_)throw Error(`Budget '${t}' was not found.`);let z=A.gate.closeBudget(t);if(A.store.deleteBudget(t),n(`Deleted budget '${_.name}'.`),z>0)e(`Lifted ${z} enforcement action(s) it had in force.`)}catch(A){ur(A)}}),D.command("spend:check","Run one spend cycle now and report what it would do").option("--env <environment>","Target environment").option("--apply","Actually apply enforcement (default is a preview)").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=new Si({controlPlane:m.controlPlane.store,store:m.store,alerts:t.apply?m.alerts:void 0});if(!t.apply){let U=m.store.budgetsForScope(m.organizationId,m.projectId).map((Y)=>m.service.status(Y));if(t.json){e(JSON.stringify(U,null,2));return}if(i("Spend check (preview — nothing was applied)"),U.length===0){a("No budgets govern this project.");return}c(["Budget","Used","Projected","Level","Would apply"],U.map((Y)=>[Y.budget.name,`${Y.decision.usedPercent.toFixed(1)}%`,`${Y.decision.projectedPercent.toFixed(1)}%`,Y.decision.level,Y.decision.actions.join(", ")||"-"])),e(`
|
|
364
364
|
Run with --apply to enforce.`);return}let _=await A.run({organizationId:m.organizationId,projectId:m.projectId,environmentId:m.environmentRecord?.id,environmentKind:m.environmentRecord?.kind});if(t.json){e(JSON.stringify(_,null,2));return}if(i("Spend cycle"),e(`Evaluated ${_.decisions.length} budget(s) in ${_.durationMs}ms.`),_.applied.length>0)a(`Applied: ${_.applied.join(", ")}`);if(_.released.length>0)n(`Released: ${_.released.join(", ")}`);if(_.withheld.length>0)a(`Withheld pending approval: ${_.withheld.join(", ")}`);if(_.anomalies.length>0)a(`${_.anomalies.length} anomaly(ies) detected.`);e(`Notifications queued: ${_.notificationsSent}`);for(let z of _.warnings)a(z);if(_.applied.length===0&&_.released.length===0)n("Nothing to change.")}catch(m){ur(m)}}),D.command("spend:work","Run the spend loop continuously: meter, evaluate, enforce, notify").option("--env <environment>","Target environment").option("--interval <seconds>",`Seconds between cycles (default ${vi})`).option("--once","Run a single cycle and exit").action(async(t)=>{try{let m=await Fn(t.env),A=new Si({controlPlane:m.controlPlane.store,store:m.store,alerts:m.alerts}),_={organizationId:m.organizationId,projectId:m.projectId,environmentId:m.environmentRecord?.id,environmentKind:m.environmentRecord?.kind};if(t.once){let ne=await A.run(_);n(`Cycle finished in ${ne.durationMs}ms.`);for(let re of ne.warnings)a(re);return}let z=Math.max(10,Number(t.interval??vi)),U=new mo(m.controlPlane.store,{owner:`cli:${process.pid}`});i(`Spend worker (every ${z}s)`),e("Press Ctrl+C to stop.");let Y=Is(A,{intervalSeconds:z,lease:U,immediate:!0,onResult:(ne)=>{let re=ne.flatMap((Ae)=>Ae.applied),se=ne.flatMap((Ae)=>Ae.released),fe=ne.flatMap((Ae)=>Ae.warnings);if(re.length>0)a(`Applied: ${re.join(", ")}`);if(se.length>0)n(`Released: ${se.join(", ")}`);for(let Ae of fe)a(Ae)},onSkip:(ne)=>e(`Skipped: ${ne} holds the loop lease.`),onError:(ne)=>r(ne instanceof Error?ne.message:String(ne))}),X=()=>{Y(),e(`
|
|
365
|
-
Spend worker stopped; the loop lease was released.`),process.exit(0)};process.on("SIGINT",X),process.on("SIGTERM",X),await new Promise(()=>{})}catch(m){ur(m)}}),D.command("spend:status","Show enforcement currently in force").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=m.gate.listUnder({organizationId:m.organizationId});if(t.json){e(JSON.stringify(A,null,2));return}if(A.length===0){n("No spend enforcement is in force.");return}c(["Action","Budget","Scope","Mode","Since","Reason"],A.map((_)=>[_.action,_.budgetId.slice(0,8),_.environmentId?"environment":_.projectId?"project":"organization",_.simulated?"dry run":"enforcing",_.appliedAt,_.reason]))}catch(m){ur(m)}}),D.command("spend:release <budgetId> <action>","Manually lift one enforcement action").option("--env <environment>","Target environment").action(async(t,m,A)=>{try{let _=await Fn(A.env),z=$d(m)?.[0];if(!z)throw Error("An action is required.");if(!_.gate.close(t,z))throw Error(`${z} was not in force for budget ${t}.`);n(`Lifted ${z} for budget ${t}.`),a("The next spend cycle will re-apply this if spend is still over the threshold.")}catch(_){ur(_)}}),D.command("anomaly:config","Tune anomaly detection for a signal").option("--signal <signal>","Signal to configure (default cost)").option("--sensitivity <level>","low, medium, or high").option("--season <points>","Points per season: 24 for daily, 168 for weekly").option("--min-delta <amount>","Absolute change below which nothing is reported, in the signal's units").option("--severity <severity>","info, warning, or critical").option("--disable","Stop detecting this signal").option("--enable","Resume detecting this signal").option("--list","List configured signals instead of changing one").option("--signals","List every detectable signal and its data rules").option("--env <environment>","Target environment").action(async(t)=>{try{if(t.signals){c(["Signal","Source","Empty hour","Min samples","Unit"],Lt.map((U)=>[U.key,U.source,U.gapPolicy==="zero"?"counts as zero":"ignored",String(U.minSamples),U.unit]));return}let m=await Fn(t.env),A=m.service.anomalyConfigs;if(t.list){let U=A.list({organizationId:m.organizationId,projectId:m.projectId});if(U.length===0){e("No signals are configured; every one uses its shipped defaults.");return}c(["Signal","Enabled","Sensitivity","Season","Min delta","Severity"],U.map((Y)=>[Y.signal,Y.enabled?"yes":"no",Y.sensitivity,String(Y.seasonLength),String(Y.minAbsoluteDelta),Y.severity]));return}let _=t.sensitivity;if(_&&!["low","medium","high"].includes(_))throw Error("--sensitivity must be low, medium, or high.");let z=A.upsert({organizationId:m.organizationId,projectId:m.projectId,signal:t.signal??"cost",sensitivity:_,seasonLength:t.season==null?void 0:Number(t.season),minAbsoluteDelta:t.minDelta==null?void 0:Number(t.minDelta),severity:t.severity,enabled:t.disable?!1:t.enable?!0:void 0});n(`Anomaly detection for '${z.signal}' updated.`),e(`${z.enabled?"Enabled":"Disabled"} · ${z.sensitivity} sensitivity · season ${z.seasonLength} · floor ${z.minAbsoluteDelta} · ${z.severity}`)}catch(m){ur(m)}}),D.command("anomaly:silence","Skip detection for a signal, route, or status code").option("--signal <signal>","Signal to silence").option("--route <pattern>","Route glob, e.g. /webhooks/**").option("--status <code>","HTTP status code").option("--reason <reason>","Why — required").option("--until <timestamp>","ISO timestamp the silence expires").option("--list","List active silences").option("--remove <id>","Remove a silence").option("--env <environment>","Target environment").action(async(t)=>{try{let m=await Fn(t.env),A=m.service.anomalyConfigs;if(t.remove){n(A.removeSilence(t.remove)?"Silence removed.":"That silence was not found.");return}if(t.list){let z=A.listSilences({organizationId:m.organizationId,projectId:m.projectId});if(z.length===0){n("No active silences.");return}c(["ID","Signal","Route","Status","Expires","Reason"],z.map((U)=>[U.id.slice(0,8),U.signal??"-",U.routePattern??"-",U.statusCode==null?"-":String(U.statusCode),U.expiresAt??"never",U.reason]));return}if(!t.reason)throw Error("--reason is required: someone will ask why this stopped alerting.");let _=A.silence({organizationId:m.organizationId,projectId:m.projectId,signal:t.signal,routePattern:t.route,statusCode:t.status==null?void 0:Number(t.status),reason:t.reason,expiresAt:t.until});if(n(`Silenced (${_.id.slice(0,8)}).`),!_.expiresAt)a("This silence never expires. Pass --until to bound it, or it will outlive the reason for it.")}catch(m){ur(m)}}),D.command("spend:anomalies","List detected spend anomalies").option("--env <environment>","Target environment").option("--unacknowledged","Only anomalies nobody has acknowledged").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=m.store.listAnomalies({organizationId:m.organizationId,projectId:m.projectId,unacknowledgedOnly:t.unacknowledged===!0});if(t.json){e(JSON.stringify(A,null,2));return}if(A.length===0){n("No anomalies detected.");return}c(["Hour","Signal","Direction","Observed","Expected","Score","Severity"],A.map((_)=>[_.bucketStart,_.signal,_.direction,ee(_.observed),ee(_.expected),_.score.toFixed(1),_.severity]))}catch(m){ur(m)}})}import{readFileSync as xp}from"node:fs";var Dp={github:"GITHUB_TOKEN",gitlab:"GITLAB_TOKEN",bitbucket:"BITBUCKET_TOKEN",gitea:"GITEA_TOKEN",generic_https:"GIT_TOKEN"},Ep={github:"https://github.com",gitlab:"https://gitlab.com",bitbucket:"https://bitbucket.org",gitea:"https://gitea.com"};function Ac(D,t={}){if(t.provider&&!["github","gitlab","bitbucket","gitea","generic_https","generic_ssh"].includes(t.provider))throw Error(`Unsupported Git provider: ${t.provider}`);let m=t.cloneUrl??(/^(?:https?:\/\/|ssh:\/\/|git@)/.test(D)?D:void 0),A="",_=m??D;if(m?.startsWith("git@")){let Y=/^git@([^:]+):(.+)$/.exec(m);if(!Y)throw Error("SSH repository URL must use git@host:owner/name.git");A=Y[1],_=Y[2]}else if(m){let Y=new URL(m);A=Y.hostname,_=Y.pathname}if(_=_.replace(/^\/+|\/+$/g,"").replace(/\.git$/,""),!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(_))throw Error("Repository must use owner/name or a supported clone URL");let z=t.provider??(A.includes("github")?"github":A.includes("gitlab")?"gitlab":A.includes("bitbucket")?"bitbucket":A.includes("gitea")?"gitea":m?.startsWith("git@")||m?.startsWith("ssh://")?"generic_ssh":m?"generic_https":"github"),U=t.host??Ep[z]??(A?`https://${A}`:"");if(!U)throw Error("A provider host is required");return{provider:z,host:U,fullName:_,cloneUrl:m}}async function Tp(){let D=await je(),t=y(process.cwd(),D);return{config:D,controlPlane:t,sources:new rr(t.store,{encryptionKey:x(process.cwd())})}}async function Pi(D){let t=await Tp();try{return await D(t)}finally{t.controlPlane.store.close()}}function ys(D,t,m){let A=D.listConnections(t);if(!m)return A.length===1?A[0]:void 0;return A.find((_)=>_.id===m||_.name.toLowerCase()===m.toLowerCase())}function Rd(D,t){if(!D)throw Error(`${t} file is required`);return xp(D,"utf8")}async function Np(D,t,m,A){if(["github","gitlab","bitbucket","gitea"].includes(t.provider)){let z=D.listRepositories(t.id).find((X)=>X.fullName.toLowerCase()===m.fullName.toLowerCase());if(z)return z;let Y=(await Vr(D,t.id,{search:m.fullName,maxPages:20})).find((X)=>X.fullName.toLowerCase()===m.fullName.toLowerCase());if(!Y)throw Error(`Repository ${m.fullName} is not granted to this connection`);return Y}let _=A.url??m.cloneUrl;if(!_)throw Error("Generic Git connections require --url <clone-url>");return D.upsertRepository({connectionId:t.id,providerRepositoryId:`manual:${m.fullName}`,fullName:m.fullName,cloneUrl:_,defaultBranch:A.branch??"main",visibility:"unknown",archived:!1,metadata:{source:"cli"}})}async function kd(D,t,m){let A=Ac(t,{provider:m.provider,host:m.host,cloneUrl:m.url}),_=ys(D.sources,D.controlPlane.organization.id,m.connection);if(!_){if(m.connection)throw Error(`Source connection ${m.connection} was not found`);let U=m.tokenEnv??Dp[A.provider],Y=U?process.env[U]:void 0;if(["github","gitlab","bitbucket","gitea"].includes(A.provider)&&!Y)throw Error(`Set ${U} or pass --token-env <name>; secrets are never accepted as command arguments`);let X=A.provider==="generic_ssh"?{publicKey:Rd(m.publicKey,"Public key"),privateKey:Rd(m.privateKey,"Private key"),hostKey:m.hostKey??""}:void 0;D.controlPlane.store.database.transaction(()=>{if(_=D.sources.createConnection({organizationId:D.controlPlane.organization.id,provider:A.provider,name:m.name??`${A.provider.replace("_"," ")} · ${new URL(A.host).host}`,host:A.host,owner:A.fullName.split("/")[0],authKind:A.provider==="generic_ssh"?"deploy_key":Y?"access_token":"none",credential:Y?{token:Y}:void 0}),X)D.sources.createDeployKey({connectionId:_.id,name:m.deployKey??`${A.fullName} deploy key`,...X,host:m.sshHost??new URL(A.host).hostname,actorId:void 0})})()}if(!_)throw Error("Source connection could not be created");if(_.status==="disconnected")throw Error("The selected source connection is disconnected");let z=await Np(D.sources,_,A,m);if(m.site){let U=D.controlPlane.environments.get("production")??[...D.controlPlane.environments.values()][0],Y=D.controlPlane.store.listResources(D.controlPlane.project.id,U?.id).find((ne)=>ne.kind==="application"&&ne.slug===m.site);if(!Y)throw Error(`Application ${m.site} was not found`);let X=_.provider==="generic_ssh"?D.sources.listDeployKeys(_.id)[0]:void 0;D.sources.createBinding({projectId:D.controlPlane.project.id,environmentId:U?.id,resourceId:Y.id,connectionId:_.id,repositoryId:z.id,repositoryFullName:z.fullName,defaultBranch:m.branch??z.defaultBranch,branchRule:m.tag?void 0:m.branch??z.defaultBranch,tagRule:m.tag,monorepoRoot:m.root??".",includePaths:m.include?.split(",").map((ne)=>ne.trim()).filter(Boolean),excludePaths:m.exclude?.split(",").map((ne)=>ne.trim()).filter(Boolean),submodules:m.submodules,cloneDepth:m.fullClone?void 0:20,deployKeyId:X?.id})}return{connection:_,repository:z}}function Di(D){r(D instanceof Error?D.message:"Git integration command failed")}function Rc(D){D.command("git:connections","List encrypted Git provider connections").action(async()=>{try{await Pi(async({sources:t,controlPlane:m})=>{let A=t.listConnections(m.organization.id);c(["ID","Name","Provider","Status","Credential","Last sync"],A.map((_)=>[_.id,_.name,_.provider,_.status,_.credentialConfigured?"encrypted":"none",_.lastSyncedAt??"-"]))})}catch(t){Di(t)}}),D.command("git:repositories","List repositories granted to a connection").option("--connection <id>","Connection ID or name").action(async(t)=>{try{await Pi(async({sources:m,controlPlane:A})=>{let _=ys(m,A.organization.id,t.connection);if(!_)throw Error("Choose a connection with --connection <id>");let z=m.listRepositories(_.id);c(["ID","Repository","Default branch","Visibility","Archived"],z.map((U)=>[U.id,U.fullName,U.defaultBranch,U.visibility,U.archived?"yes":"no"]))})}catch(m){Di(m)}}),D.command("git:add <repo>","Connect and optionally bind a Git repository").option("--branch <branch>","Default branch to deploy",{default:"main"}).option("--connection <id>","Reuse a connection ID or name").option("--provider <provider>","github, gitlab, bitbucket, gitea, generic_https, or generic_ssh").option("--host <url>","Provider API host").option("--name <name>","Connection name").option("--token-env <name>","Environment variable containing the access token").option("--url <clone-url>","Credential-free clone URL").option("--site <slug>","Bind the repository to an application").option("--root <path>","Monorepo root",{default:"."}).option("--include <globs>","Comma-separated watched paths").option("--exclude <globs>","Comma-separated ignored paths").option("--tag <glob>","Deploy matching tags instead of branch pushes").option("--full-clone","Use full history instead of a shallow clone").option("--submodules","Initialize repository submodules").option("--deploy-key <name>","Deploy-key display name").option("--public-key <path>","SSH public-key file").option("--private-key <path>","SSH private-key file").option("--host-key <key>","Pinned SSH host key").option("--ssh-host <host>","SSH clone host").option("--yes","Skip confirmation").action(async(t,m)=>{try{if(!m.yes&&!await d(`Connect ${t}?`,!0))return;let A=await Pi((_)=>kd(_,t,m));if(n(`Connected ${A.repository.fullName}`),e(`Connection: ${A.connection.name} (${A.connection.id})`),m.site)e(`Application binding: ${m.site}`)}catch(A){Di(A)}}),D.command("git:import","Import site.repository entries from cloud.config.ts").option("--connection <id>","Reuse a connection ID or name").option("--token-env <name>","Environment variable containing provider token").option("--yes","Skip confirmation").action(async(t)=>{try{await Pi(async(m)=>{let A=Object.entries(m.config.sites??{}).filter(([,_])=>_.repository?.url);if(!A.length)throw Error("No site.repository entries were found");if(!t.yes&&!await d(`Import ${A.length} repository configuration${A.length===1?"":"s"}?`,!0))return;for(let[_,z]of A){let U=z.repository,Y=U.provider==="custom"?void 0:U.provider;await kd(m,U.url,{connection:t.connection,tokenEnv:t.tokenEnv,site:_,provider:Y,branch:U.branch??"main",tag:U.strategy==="tag"?U.tag??U.tagPattern??"v*":void 0,url:U.url}),n(`Imported ${_} · ${U.url}`)}})}catch(m){Di(m)}}),D.command("git:branches <repo>","List branches or tags visible to a connection").option("--connection <id>","Connection ID or name").option("--tags","List tags instead of branches").option("--deploy-key <id>","SSH deploy-key ID").action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=ys(A,_.organization.id,m.connection);if(!z)throw Error("Choose a connection with --connection <id>");let U=A.listRepositories(z.id).find((X)=>X.id===t||X.fullName.toLowerCase()===t.toLowerCase());if(!U)throw Error(`Repository ${t} was not found`);let Y=await nr(A,{connectionId:z.id,repository:U.fullName,repositoryId:U.id,type:m.tags?"tags":"branches",deployKeyId:m.deployKey??A.listDeployKeys(z.id)[0]?.id});c([m.tags?"Tag":"Branch","Commit","Protected"],Y.refs.map((X)=>[X.name,X.commitSha,X.protected?"yes":"no"]))})}catch(A){Di(A)}}),D.command("git:deploy <branch>","Queue an idempotent deployment from a Git branch").option("--binding <id>","Source binding ID").option("--repo <owner/name>","Bound repository").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=_.environments.get(m.env??"production");if(!z)throw Error(`Environment ${m.env} was not found`);let U=A.listBindings({projectId:_.project.id,status:"active"}).find((re)=>(!m.binding||re.id===m.binding)&&(!m.repo||re.repositoryFullName.toLowerCase()===m.repo.toLowerCase())&&(!re.environmentId||re.environmentId===z.id));if(!U)throw Error("No active source binding matched; use --binding or --repo");let X=(await nr(A,{connectionId:U.connectionId,repository:U.repositoryFullName,repositoryId:U.repositoryId,type:"branches",deployKeyId:U.deployKeyId})).refs.find((re)=>re.name===t);if(!X)throw Error(`Branch ${t} was not found`);let ne=_.store.createOperation({projectId:U.projectId,environmentId:z.id,resourceId:U.resourceId,kind:"deploy.source",idempotencyKey:`cli-source:${U.id}:${X.commitSha}`,correlationId:`cli-source:${U.id}:${X.commitSha}`,input:{source:{connectionId:U.connectionId,bindingId:U.id,repository:U.repositoryFullName,commitSha:X.commitSha,branch:t,monorepoRoot:U.monorepoRoot,submodules:U.submodules,cloneDepth:U.cloneDepth??null}}});n(`Deployment queued: ${ne.id}`),e(`${U.repositoryFullName}@${t} · ${X.commitSha}`)})}catch(A){Di(A)}}),D.command("git:webhook:add <repo>","Create and reconcile a signed auto-deploy webhook").option("--connection <id>","Connection ID or name").option("--base-url <url>","Public HTTPS dashboard URL").action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=ys(A,_.organization.id,m.connection);if(!z)throw Error("Choose a connection with --connection <id>");let U=A.listRepositories(z.id).find((se)=>se.id===t||se.fullName.toLowerCase()===t.toLowerCase());if(!U)throw Error(`Repository ${t} was not found`);let Y=m.baseUrl??process.env.TS_CLOUD_WEBHOOK_BASE_URL;if(!Y)throw Error("Pass --base-url or set TS_CLOUD_WEBHOOK_BASE_URL");let X=A.createWebhook({connectionId:z.id,repositoryId:U.id,repositoryFullName:U.fullName}),ne=Dt(Y,X.webhook),re=await ir(A,X.webhook.id,Y);n(`Webhook ${re.status}: ${ne}`),e("The signing secret is encrypted and is not printed.")})}catch(A){Di(A)}}),D.command("git:webhook:remove <webhook>","Disable and remove a provider webhook").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=A.listConnections(_.organization.id).flatMap((Y)=>A.listWebhooks(Y.id)).find((Y)=>Y.id===t||Y.repositoryFullName.toLowerCase()===t.toLowerCase());if(!z)throw Error(`Webhook ${t} was not found`);if(!m.yes&&!await d(`Remove webhook for ${z.repositoryFullName}?`,!0))return;let U=await Gi(A,z.id);n(`Webhook disabled: ${U.id}`)})}catch(A){Di(A)}}),D.command("git:disconnect <connection>","Preview and disconnect a Git provider connection").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=ys(A,_.organization.id,t);if(!z)throw Error(`Source connection ${t} was not found`);let U=A.listBindings({connectionId:z.id,status:"active"});e(`${U.length} active binding${U.length===1?"":"s"} will be disabled.`);for(let Y of U)e(` ${Y.repositoryFullName} · ${Y.branchRule??Y.defaultBranch} · ${Y.monorepoRoot}`);if(!m.yes&&!await d(`Disconnect ${z.name}?`,!1))return;A.disconnectConnection(z.id),n(`Disconnected ${z.name}`)})}catch(A){Di(A)}})}import{readFileSync as Pd,writeFileSync as Op}from"node:fs";import{resolve as Ks}from"node:path";async function Lp(){let D=await je(),t=y(process.cwd(),D),m=x(process.cwd());return{config:D,controlPlane:t,drafts:new Xn(t.store),artifacts:new Yn(t.store,{cwd:process.cwd()}),registries:new Qn(t.store,{encryptionKey:m}),sources:new rr(t.store,{encryptionKey:m})}}async function Pr(D){let t=await Lp();try{return await D(t)}finally{t.controlPlane.store.close()}}function hn(D){r(D instanceof Error?D.message:"Application onboarding command failed")}function Id(D){return JSON.parse(Pd(Ks(D),"utf8"))}function jp(D){let t=D.site,m=["laravel","php","statamic","wordpress"].includes(t.type),A=["static","spa"].includes(t.type)||!t.start&&!m,_=A?{kind:"static",publishDirectory:t.root??t.webDirectory??"dist",buildCommand:t.build}:{kind:"server",runtime:m?"php":"bun",runtimeVersion:m?t.phpVersion:void 0,installCommand:m?"composer install --no-dev --prefer-dist --optimize-autoloader":void 0,buildCommand:t.build,startCommand:t.start??(m?"php -S 0.0.0.0:$PORT -t public":"bun run start")};return{schemaVersion:1,name:t.name??D.siteSlug,slug:D.siteSlug,projectId:D.projectId,environmentId:D.environmentId,source:D.source??{kind:"local",root:t.root??"."},build:_,runtime:{target:"server",architecture:t.architecture==="arm64"?"arm64":"x86_64",port:A?void 0:Number(t.port??3000),healthCheck:A?void 0:{protocol:"http",path:t.healthCheck?.path??"/health"}},domain:t.domain?{hostname:t.domain,tls:t.ssl?.provider!=="none"}:void 0}}function kc(D){D.command("app:detect [root]","Detect application frameworks without executing project code").action(async(t=".")=>{try{let m=kt(Jn(Ks(t)));c(["Framework","Strategy","Confidence","Evidence"],m.map((A)=>[A.framework,A.strategy,`${Math.round(A.confidence*100)}%`,A.evidence.map((_)=>_.path).join(", ")||"manual"]));for(let A of m)e(`${A.framework}: ${A.description}`)}catch(m){hn(m)}}),D.command("app:plan <draft>","Validate and preview a secret-free application draft").option("--secrets <names>","Comma-separated configured secret names").action(async(t,m)=>{try{let A=ce(Id(t),m.secrets?.split(",").map((_)=>_.trim()).filter(Boolean));if(e(JSON.stringify(A,null,2)),!A.valid)hn(Error("Plan is not deployable; resolve the reported issues and missing secrets."))}catch(A){hn(A)}}),D.command("app:draft:save <draft>","Persist or update a resumable application draft").option("--id <id>","Existing draft ID").option("--version <version>","Expected draft version").option("--step <step>","Wizard step",{default:"source"}).option("--secrets <names>","Comma-separated configured secret names").action(async(t,m)=>{try{await Pr(async({drafts:A,controlPlane:_})=>{let z=Id(t),U=m.secrets?.split(",").map((X)=>X.trim()).filter(Boolean)??[],Y=m.id?A.update(m.id,Number(m.version),{draft:z,step:m.step,suppliedSecretNames:U}):A.create({organizationId:_.organization.id,projectId:_.project.id,name:z.name,draft:{...z,projectId:_.project.id},step:m.step,suppliedSecretNames:U});n(`Draft saved: ${Y.id} · v${Y.version} · ${Y.status}`)})}catch(A){hn(A)}}),D.command("app:drafts","List resumable application drafts").action(async()=>{try{await Pr(async({drafts:t,controlPlane:m})=>{let A=t.list(m.project.id);c(["ID","Application","Step","Status","Version","Updated"],A.map((_)=>[_.id,_.input.name,_.step,_.status,String(_.version),_.updatedAt]))})}catch(t){hn(t)}}),D.command("app:export <draft>","Export a deterministic application manifest").option("--output <path>","Output path",{default:"application.manifest.json"}).action(async(t,m)=>{try{await Pr(async({drafts:A})=>{let _=A.get(t);if(!_)throw Error(`Draft ${t} was not found`);let z=ce(_.input,_.suppliedSecretNames),U=Ks(m.output??"application.manifest.json");Op(U,z.serializedManifest,{mode:384}),n(`Manifest exported: ${U}`)})}catch(A){hn(A)}}),D.command("app:deploy <draft>","Confirm and queue a validated application plan").option("--confirm <environment>","Exact target environment slug").action(async(t,m)=>{try{await Pr(async({drafts:A,controlPlane:_})=>{let z=A.get(t);if(!z)throw Error(`Draft ${t} was not found`);if(!m.confirm)throw Error("Pass --confirm <environment> to acknowledge the target and recurring cost drivers");let U=zr({controlPlane:_.store,drafts:A,draftId:t,expectedVersion:z.version,confirmEnvironment:m.confirm});n(`Application queued: ${U.operation.id}`),e(`Cost drivers: ${U.plan.costDrivers.join(", ")}`)})}catch(A){hn(A)}}),D.command("app:import <site>","Import an existing cloud.config.ts site as a draft").option("--connection <id>","Git connection ID for repository-backed sites").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{try{await Pr(async({config:A,controlPlane:_,sources:z,drafts:U})=>{let Y=A.sites?.[t];if(!Y)throw Error(`Site ${t} was not found`);let X=_.environments.get(m.env??"production");if(!X)throw Error(`Environment ${m.env} was not found`);let ne;if(Y.repository?.url){if(!m.connection)throw Error("Repository-backed imports require --connection <id>");let fe=z.getConnection(m.connection);if(!fe)throw Error("Git connection was not found");let Ae=Ac(Y.repository.url),Pe=z.listRepositories(fe.id).find((he)=>he.fullName.toLowerCase()===Ae.fullName.toLowerCase());if(!Pe)throw Error(`Repository ${Ae.fullName} is not synchronized`);ne={kind:"git",connectionId:fe.id,repositoryId:Pe.id,repositoryFullName:Pe.fullName,ref:Y.repository.branch??"main"}}let re=jp({site:Y,siteSlug:t,projectId:_.project.id,environmentId:X.id,source:ne}),se=U.create({organizationId:_.organization.id,projectId:_.project.id,name:`Import ${t}`,draft:re,step:"review"});n(`Imported draft: ${se.id}`)})}catch(A){hn(A)}}),D.command("app:artifact:add <file>","Inspect and store a bounded ZIP or TAR artifact").action(async(t)=>{try{await Pr(async({artifacts:m,controlPlane:A})=>{let _=Ks(t),z=m.create({organizationId:A.organization.id,projectId:A.project.id,filename:_.split("/").pop(),bytes:Pd(_)});n(`Artifact stored: ${z.id} · ${z.entryCount} entries · ${z.sha256}`)})}catch(m){hn(m)}}),D.command("app:registry:add <host>","Create an encrypted private-registry connection").option("--name <name>","Connection name",{default:"Container registry"}).option("--provider <provider>","docker_hub, ghcr, ecr, gcr, or generic",{default:"generic"}).option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).action(async(t,m)=>{try{await Pr(async({registries:A,controlPlane:_})=>{let z={username:process.env[m.usernameEnv],password:process.env[m.passwordEnv],token:process.env[m.tokenEnv]},U=A.create({organizationId:_.organization.id,provider:m.provider,name:m.name,host:t,credential:Object.values(z).some(Boolean)?z:void 0});n(`Registry connection created: ${U.id}`)})}catch(A){hn(A)}}),D.command("app:registries","List safe registry connection metadata").action(async()=>{try{await Pr(async({registries:t,controlPlane:m})=>c(["ID","Name","Provider","Host","Status"],t.list(m.organization.id).map((A)=>[A.id,A.name,A.provider,A.host,A.status])))}catch(t){hn(t)}}),D.command("app:registry:test <id>","Test registry and optional image pull access").option("--image <image>","Image reference").action(async(t,m)=>{try{await Pr(async({registries:A})=>{let _=await A.test(t,{image:m.image});e(`${_.status}: ${_.healthMessage}`)})}catch(A){hn(A)}}),D.command("app:registry:rotate <id>","Rotate write-only registry credentials").option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).option("--expires <date>","Credential expiry timestamp").action(async(t,m)=>{try{await Pr(async({registries:A})=>{let _={username:process.env[m.usernameEnv],password:process.env[m.passwordEnv],token:process.env[m.tokenEnv]};if(!Object.values(_).some(Boolean))throw Error("Set at least one configured registry credential environment variable");let z=A.rotate(t,_,{expiresAt:m.expires?new Date(m.expires).toISOString():void 0});n(`Registry credential rotated: ${z.id} · ${z.status}`)})}catch(A){hn(A)}}),D.command("app:registry:disconnect <id>","Disconnect and erase registry credentials").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Pr(async({registries:A,drafts:_,controlPlane:z})=>{let U=A.get(t);if(!U)throw Error(`Registry ${t} was not found`);let Y=_.list(z.project.id).filter((X)=>X.input.source.kind==="image"&&X.input.source.registryConnectionId===t||X.input.build.kind==="prebuilt_image"&&X.input.build.registryConnectionId===t);if(e(`${Y.length} application draft${Y.length===1?"":"s"} reference this registry.`),!m.yes&&!await d(`Disconnect ${U.name}?`,!1))return;A.disconnect(t),n(`Disconnected ${U.name}`)})}catch(A){hn(A)}})}import{existsSync as Fp,readFileSync as Bp,writeFileSync as Mp}from"node:fs";function Ic(D,t){r(`'${D}' is not implemented yet.`),e(t),process.exitCode=1}function _p(D){let t={};for(let m of D.split(`
|
|
365
|
+
Spend worker stopped; the loop lease was released.`),process.exit(0)};process.on("SIGINT",X),process.on("SIGTERM",X),await new Promise(()=>{})}catch(m){ur(m)}}),D.command("spend:status","Show enforcement currently in force").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=m.gate.listUnder({organizationId:m.organizationId});if(t.json){e(JSON.stringify(A,null,2));return}if(A.length===0){n("No spend enforcement is in force.");return}c(["Action","Budget","Scope","Mode","Since","Reason"],A.map((_)=>[_.action,_.budgetId.slice(0,8),_.environmentId?"environment":_.projectId?"project":"organization",_.simulated?"dry run":"enforcing",_.appliedAt,_.reason]))}catch(m){ur(m)}}),D.command("spend:release <budgetId> <action>","Manually lift one enforcement action").option("--env <environment>","Target environment").action(async(t,m,A)=>{try{let _=await Fn(A.env),z=$d(m)?.[0];if(!z)throw Error("An action is required.");if(!_.gate.close(t,z))throw Error(`${z} was not in force for budget ${t}.`);n(`Lifted ${z} for budget ${t}.`),a("The next spend cycle will re-apply this if spend is still over the threshold.")}catch(_){ur(_)}}),D.command("anomaly:config","Tune anomaly detection for a signal").option("--signal <signal>","Signal to configure (default cost)").option("--sensitivity <level>","low, medium, or high").option("--season <points>","Points per season: 24 for daily, 168 for weekly").option("--min-delta <amount>","Absolute change below which nothing is reported, in the signal's units").option("--severity <severity>","info, warning, or critical").option("--disable","Stop detecting this signal").option("--enable","Resume detecting this signal").option("--list","List configured signals instead of changing one").option("--signals","List every detectable signal and its data rules").option("--env <environment>","Target environment").action(async(t)=>{try{if(t.signals){c(["Signal","Source","Empty hour","Min samples","Unit"],Lt.map((U)=>[U.key,U.source,U.gapPolicy==="zero"?"counts as zero":"ignored",String(U.minSamples),U.unit]));return}let m=await Fn(t.env),A=m.service.anomalyConfigs;if(t.list){let U=A.list({organizationId:m.organizationId,projectId:m.projectId});if(U.length===0){e("No signals are configured; every one uses its shipped defaults.");return}c(["Signal","Enabled","Sensitivity","Season","Min delta","Severity"],U.map((Y)=>[Y.signal,Y.enabled?"yes":"no",Y.sensitivity,String(Y.seasonLength),String(Y.minAbsoluteDelta),Y.severity]));return}let _=t.sensitivity;if(_&&!["low","medium","high"].includes(_))throw Error("--sensitivity must be low, medium, or high.");let z=A.upsert({organizationId:m.organizationId,projectId:m.projectId,signal:t.signal??"cost",sensitivity:_,seasonLength:t.season==null?void 0:Number(t.season),minAbsoluteDelta:t.minDelta==null?void 0:Number(t.minDelta),severity:t.severity,enabled:t.disable?!1:t.enable?!0:void 0});n(`Anomaly detection for '${z.signal}' updated.`),e(`${z.enabled?"Enabled":"Disabled"} · ${z.sensitivity} sensitivity · season ${z.seasonLength} · floor ${z.minAbsoluteDelta} · ${z.severity}`)}catch(m){ur(m)}}),D.command("anomaly:silence","Skip detection for a signal, route, or status code").option("--signal <signal>","Signal to silence").option("--route <pattern>","Route glob, e.g. /webhooks/**").option("--status <code>","HTTP status code").option("--reason <reason>","Why — required").option("--until <timestamp>","ISO timestamp the silence expires").option("--list","List active silences").option("--remove <id>","Remove a silence").option("--env <environment>","Target environment").action(async(t)=>{try{let m=await Fn(t.env),A=m.service.anomalyConfigs;if(t.remove){n(A.removeSilence(t.remove)?"Silence removed.":"That silence was not found.");return}if(t.list){let z=A.listSilences({organizationId:m.organizationId,projectId:m.projectId});if(z.length===0){n("No active silences.");return}c(["ID","Signal","Route","Status","Expires","Reason"],z.map((U)=>[U.id.slice(0,8),U.signal??"-",U.routePattern??"-",U.statusCode==null?"-":String(U.statusCode),U.expiresAt??"never",U.reason]));return}if(!t.reason)throw Error("--reason is required: someone will ask why this stopped alerting.");let _=A.silence({organizationId:m.organizationId,projectId:m.projectId,signal:t.signal,routePattern:t.route,statusCode:t.status==null?void 0:Number(t.status),reason:t.reason,expiresAt:t.until});if(n(`Silenced (${_.id.slice(0,8)}).`),!_.expiresAt)a("This silence never expires. Pass --until to bound it, or it will outlive the reason for it.")}catch(m){ur(m)}}),D.command("spend:anomalies","List detected spend anomalies").option("--env <environment>","Target environment").option("--unacknowledged","Only anomalies nobody has acknowledged").option("--json","Print structured JSON").action(async(t)=>{try{let m=await Fn(t.env),A=m.store.listAnomalies({organizationId:m.organizationId,projectId:m.projectId,unacknowledgedOnly:t.unacknowledged===!0});if(t.json){e(JSON.stringify(A,null,2));return}if(A.length===0){n("No anomalies detected.");return}c(["Hour","Signal","Direction","Observed","Expected","Score","Severity"],A.map((_)=>[_.bucketStart,_.signal,_.direction,ee(_.observed),ee(_.expected),_.score.toFixed(1),_.severity]))}catch(m){ur(m)}})}import{readFileSync as xp}from"node:fs";var Dp={github:"GITHUB_TOKEN",gitlab:"GITLAB_TOKEN",bitbucket:"BITBUCKET_TOKEN",gitea:"GITEA_TOKEN",generic_https:"GIT_TOKEN"},Ep={github:"https://github.com",gitlab:"https://gitlab.com",bitbucket:"https://bitbucket.org",gitea:"https://gitea.com"};function Ac(D,t={}){if(t.provider&&!["github","gitlab","bitbucket","gitea","generic_https","generic_ssh"].includes(t.provider))throw Error(`Unsupported Git provider: ${t.provider}`);let m=t.cloneUrl??(/^(?:https?:\/\/|ssh:\/\/|git@)/.test(D)?D:void 0),A="",_=m??D;if(m?.startsWith("git@")){let Y=/^git@([^:]+):(.+)$/.exec(m);if(!Y)throw Error("SSH repository URL must use git@host:owner/name.git");A=Y[1],_=Y[2]}else if(m){let Y=new URL(m);A=Y.hostname,_=Y.pathname}if(_=_.replace(/^\/+|\/+$/g,"").replace(/\.git$/,""),!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(_))throw Error("Repository must use owner/name or a supported clone URL");let z=t.provider??(A.includes("github")?"github":A.includes("gitlab")?"gitlab":A.includes("bitbucket")?"bitbucket":A.includes("gitea")?"gitea":m?.startsWith("git@")||m?.startsWith("ssh://")?"generic_ssh":m?"generic_https":"github"),U=t.host??Ep[z]??(A?`https://${A}`:"");if(!U)throw Error("A provider host is required");return{provider:z,host:U,fullName:_,cloneUrl:m}}async function Tp(){let D=await je(),t=y(process.cwd(),D);return{config:D,controlPlane:t,sources:new rr(t.store,{encryptionKey:x(process.cwd())})}}async function Pi(D){let t=await Tp();try{return await D(t)}finally{t.controlPlane.store.close()}}function ys(D,t,m){let A=D.listConnections(t);if(!m)return A.length===1?A[0]:void 0;return A.find((_)=>_.id===m||_.name.toLowerCase()===m.toLowerCase())}function Rd(D,t){if(!D)throw Error(`${t} file is required`);return xp(D,"utf8")}async function Np(D,t,m,A){if(["github","gitlab","bitbucket","gitea"].includes(t.provider)){let z=D.listRepositories(t.id).find((X)=>X.fullName.toLowerCase()===m.fullName.toLowerCase());if(z)return z;let Y=(await Vr(D,t.id,{search:m.fullName,maxPages:20})).find((X)=>X.fullName.toLowerCase()===m.fullName.toLowerCase());if(!Y)throw Error(`Repository ${m.fullName} is not granted to this connection`);return Y}let _=A.url??m.cloneUrl;if(!_)throw Error("Generic Git connections require --url <clone-url>");return D.upsertRepository({connectionId:t.id,providerRepositoryId:`manual:${m.fullName}`,fullName:m.fullName,cloneUrl:_,defaultBranch:A.branch??"main",visibility:"unknown",archived:!1,metadata:{source:"cli"}})}async function kd(D,t,m){let A=Ac(t,{provider:m.provider,host:m.host,cloneUrl:m.url}),_=ys(D.sources,D.controlPlane.organization.id,m.connection);if(!_){if(m.connection)throw Error(`Source connection ${m.connection} was not found`);let U=m.tokenEnv??Dp[A.provider],Y=U?process.env[U]:void 0;if(["github","gitlab","bitbucket","gitea"].includes(A.provider)&&!Y)throw Error(`Set ${U} or pass --token-env <name>; secrets are never accepted as command arguments`);let X=A.provider==="generic_ssh"?{publicKey:Rd(m.publicKey,"Public key"),privateKey:Rd(m.privateKey,"Private key"),hostKey:m.hostKey??""}:void 0;D.controlPlane.store.database.transaction(()=>{if(_=D.sources.createConnection({organizationId:D.controlPlane.organization.id,provider:A.provider,name:m.name??`${A.provider.replace("_"," ")} · ${new URL(A.host).host}`,host:A.host,owner:A.fullName.split("/")[0],authKind:A.provider==="generic_ssh"?"deploy_key":Y?"access_token":"none",credential:Y?{token:Y}:void 0}),X)D.sources.createDeployKey({connectionId:_.id,name:m.deployKey??`${A.fullName} deploy key`,...X,host:m.sshHost??new URL(A.host).hostname,actorId:void 0})})()}if(!_)throw Error("Source connection could not be created");if(_.status==="disconnected")throw Error("The selected source connection is disconnected");let z=await Np(D.sources,_,A,m);if(m.site){let U=D.controlPlane.environments.get("production")??[...D.controlPlane.environments.values()][0],Y=D.controlPlane.store.listResources(D.controlPlane.project.id,U?.id).find((ne)=>ne.kind==="application"&&ne.slug===m.site);if(!Y)throw Error(`Application ${m.site} was not found`);let X=_.provider==="generic_ssh"?D.sources.listDeployKeys(_.id)[0]:void 0;D.sources.createBinding({projectId:D.controlPlane.project.id,environmentId:U?.id,resourceId:Y.id,connectionId:_.id,repositoryId:z.id,repositoryFullName:z.fullName,defaultBranch:m.branch??z.defaultBranch,branchRule:m.tag?void 0:m.branch??z.defaultBranch,tagRule:m.tag,monorepoRoot:m.root??".",includePaths:m.include?.split(",").map((ne)=>ne.trim()).filter(Boolean),excludePaths:m.exclude?.split(",").map((ne)=>ne.trim()).filter(Boolean),submodules:m.submodules,cloneDepth:m.fullClone?void 0:20,deployKeyId:X?.id})}return{connection:_,repository:z}}function Di(D){r(D instanceof Error?D.message:"Git integration command failed")}function Rc(D){D.command("git:connections","List encrypted Git provider connections").action(async()=>{try{await Pi(async({sources:t,controlPlane:m})=>{let A=t.listConnections(m.organization.id);c(["ID","Name","Provider","Status","Credential","Last sync"],A.map((_)=>[_.id,_.name,_.provider,_.status,_.credentialConfigured?"encrypted":"none",_.lastSyncedAt??"-"]))})}catch(t){Di(t)}}),D.command("git:repositories","List repositories granted to a connection").option("--connection <id>","Connection ID or name").action(async(t)=>{try{await Pi(async({sources:m,controlPlane:A})=>{let _=ys(m,A.organization.id,t.connection);if(!_)throw Error("Choose a connection with --connection <id>");let z=m.listRepositories(_.id);c(["ID","Repository","Default branch","Visibility","Archived"],z.map((U)=>[U.id,U.fullName,U.defaultBranch,U.visibility,U.archived?"yes":"no"]))})}catch(m){Di(m)}}),D.command("git:add <repo>","Connect and optionally bind a Git repository").option("--branch <branch>","Default branch to deploy",{default:"main"}).option("--connection <id>","Reuse a connection ID or name").option("--provider <provider>","github, gitlab, bitbucket, gitea, generic_https, or generic_ssh").option("--host <url>","Provider API host").option("--name <name>","Connection name").option("--token-env <name>","Environment variable containing the access token").option("--url <clone-url>","Credential-free clone URL").option("--site <slug>","Bind the repository to an application").option("--root <path>","Monorepo root",{default:"."}).option("--include <globs>","Comma-separated watched paths").option("--exclude <globs>","Comma-separated ignored paths").option("--tag <glob>","Deploy matching tags instead of branch pushes").option("--full-clone","Use full history instead of a shallow clone").option("--submodules","Initialize repository submodules").option("--deploy-key <name>","Deploy-key display name").option("--public-key <path>","SSH public-key file").option("--private-key <path>","SSH private-key file").option("--host-key <key>","Pinned SSH host key").option("--ssh-host <host>","SSH clone host").option("--yes","Skip confirmation").action(async(t,m)=>{try{if(!m.yes&&!await d(`Connect ${t}?`,!0))return;let A=await Pi((_)=>kd(_,t,m));if(n(`Connected ${A.repository.fullName}`),e(`Connection: ${A.connection.name} (${A.connection.id})`),m.site)e(`Application binding: ${m.site}`)}catch(A){Di(A)}}),D.command("git:import","Import site.repository entries from cloud.config.ts").option("--connection <id>","Reuse a connection ID or name").option("--token-env <name>","Environment variable containing provider token").option("--yes","Skip confirmation").action(async(t)=>{try{await Pi(async(m)=>{let A=Object.entries(m.config.sites??{}).filter(([,_])=>_.repository?.url);if(!A.length)throw Error("No site.repository entries were found");if(!t.yes&&!await d(`Import ${A.length} repository configuration${A.length===1?"":"s"}?`,!0))return;for(let[_,z]of A){let U=z.repository,Y=U.provider==="custom"?void 0:U.provider;await kd(m,U.url,{connection:t.connection,tokenEnv:t.tokenEnv,site:_,provider:Y,branch:U.branch??"main",tag:U.strategy==="tag"?U.tag??U.tagPattern??"v*":void 0,url:U.url}),n(`Imported ${_} · ${U.url}`)}})}catch(m){Di(m)}}),D.command("git:branches <repo>","List branches or tags visible to a connection").option("--connection <id>","Connection ID or name").option("--tags","List tags instead of branches").option("--deploy-key <id>","SSH deploy-key ID").action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=ys(A,_.organization.id,m.connection);if(!z)throw Error("Choose a connection with --connection <id>");let U=A.listRepositories(z.id).find((X)=>X.id===t||X.fullName.toLowerCase()===t.toLowerCase());if(!U)throw Error(`Repository ${t} was not found`);let Y=await nr(A,{connectionId:z.id,repository:U.fullName,repositoryId:U.id,type:m.tags?"tags":"branches",deployKeyId:m.deployKey??A.listDeployKeys(z.id)[0]?.id});c([m.tags?"Tag":"Branch","Commit","Protected"],Y.refs.map((X)=>[X.name,X.commitSha,X.protected?"yes":"no"]))})}catch(A){Di(A)}}),D.command("git:deploy <branch>","Queue an idempotent deployment from a Git branch").option("--binding <id>","Source binding ID").option("--repo <owner/name>","Bound repository").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=_.environments.get(m.env??"production");if(!z)throw Error(`Environment ${m.env} was not found`);let U=A.listBindings({projectId:_.project.id,status:"active"}).find((re)=>(!m.binding||re.id===m.binding)&&(!m.repo||re.repositoryFullName.toLowerCase()===m.repo.toLowerCase())&&(!re.environmentId||re.environmentId===z.id));if(!U)throw Error("No active source binding matched; use --binding or --repo");let X=(await nr(A,{connectionId:U.connectionId,repository:U.repositoryFullName,repositoryId:U.repositoryId,type:"branches",deployKeyId:U.deployKeyId})).refs.find((re)=>re.name===t);if(!X)throw Error(`Branch ${t} was not found`);let ne=_.store.createOperation({projectId:U.projectId,environmentId:z.id,resourceId:U.resourceId,kind:"deploy.source",idempotencyKey:`cli-source:${U.id}:${X.commitSha}`,correlationId:`cli-source:${U.id}:${X.commitSha}`,input:{source:{connectionId:U.connectionId,bindingId:U.id,repository:U.repositoryFullName,commitSha:X.commitSha,branch:t,monorepoRoot:U.monorepoRoot,submodules:U.submodules,cloneDepth:U.cloneDepth??null}}});n(`Deployment queued: ${ne.id}`),e(`${U.repositoryFullName}@${t} · ${X.commitSha}`)})}catch(A){Di(A)}}),D.command("git:webhook:add <repo>","Create and reconcile a signed auto-deploy webhook").option("--connection <id>","Connection ID or name").option("--base-url <url>","Public HTTPS dashboard URL").action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=ys(A,_.organization.id,m.connection);if(!z)throw Error("Choose a connection with --connection <id>");let U=A.listRepositories(z.id).find((se)=>se.id===t||se.fullName.toLowerCase()===t.toLowerCase());if(!U)throw Error(`Repository ${t} was not found`);let Y=m.baseUrl??process.env.TS_CLOUD_WEBHOOK_BASE_URL;if(!Y)throw Error("Pass --base-url or set TS_CLOUD_WEBHOOK_BASE_URL");let X=A.createWebhook({connectionId:z.id,repositoryId:U.id,repositoryFullName:U.fullName}),ne=Dt(Y,X.webhook),re=await ir(A,X.webhook.id,Y);n(`Webhook ${re.status}: ${ne}`),e("The signing secret is encrypted and is not printed.")})}catch(A){Di(A)}}),D.command("git:webhook:remove <webhook>","Disable and remove a provider webhook").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=A.listConnections(_.organization.id).flatMap((Y)=>A.listWebhooks(Y.id)).find((Y)=>Y.id===t||Y.repositoryFullName.toLowerCase()===t.toLowerCase());if(!z)throw Error(`Webhook ${t} was not found`);if(!m.yes&&!await d(`Remove webhook for ${z.repositoryFullName}?`,!0))return;let U=await qi(A,z.id);n(`Webhook disabled: ${U.id}`)})}catch(A){Di(A)}}),D.command("git:disconnect <connection>","Preview and disconnect a Git provider connection").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Pi(async({sources:A,controlPlane:_})=>{let z=ys(A,_.organization.id,t);if(!z)throw Error(`Source connection ${t} was not found`);let U=A.listBindings({connectionId:z.id,status:"active"});e(`${U.length} active binding${U.length===1?"":"s"} will be disabled.`);for(let Y of U)e(` ${Y.repositoryFullName} · ${Y.branchRule??Y.defaultBranch} · ${Y.monorepoRoot}`);if(!m.yes&&!await d(`Disconnect ${z.name}?`,!1))return;A.disconnectConnection(z.id),n(`Disconnected ${z.name}`)})}catch(A){Di(A)}})}import{readFileSync as Pd,writeFileSync as Op}from"node:fs";import{resolve as Ks}from"node:path";async function Lp(){let D=await je(),t=y(process.cwd(),D),m=x(process.cwd());return{config:D,controlPlane:t,drafts:new Xn(t.store),artifacts:new Yn(t.store,{cwd:process.cwd()}),registries:new Qn(t.store,{encryptionKey:m}),sources:new rr(t.store,{encryptionKey:m})}}async function Pr(D){let t=await Lp();try{return await D(t)}finally{t.controlPlane.store.close()}}function hn(D){r(D instanceof Error?D.message:"Application onboarding command failed")}function Id(D){return JSON.parse(Pd(Ks(D),"utf8"))}function jp(D){let t=D.site,m=["laravel","php","statamic","wordpress"].includes(t.type),A=["static","spa"].includes(t.type)||!t.start&&!m,_=A?{kind:"static",publishDirectory:t.root??t.webDirectory??"dist",buildCommand:t.build}:{kind:"server",runtime:m?"php":"bun",runtimeVersion:m?t.phpVersion:void 0,installCommand:m?"composer install --no-dev --prefer-dist --optimize-autoloader":void 0,buildCommand:t.build,startCommand:t.start??(m?"php -S 0.0.0.0:$PORT -t public":"bun run start")};return{schemaVersion:1,name:t.name??D.siteSlug,slug:D.siteSlug,projectId:D.projectId,environmentId:D.environmentId,source:D.source??{kind:"local",root:t.root??"."},build:_,runtime:{target:"server",architecture:t.architecture==="arm64"?"arm64":"x86_64",port:A?void 0:Number(t.port??3000),healthCheck:A?void 0:{protocol:"http",path:t.healthCheck?.path??"/health"}},domain:t.domain?{hostname:t.domain,tls:t.ssl?.provider!=="none"}:void 0}}function kc(D){D.command("app:detect [root]","Detect application frameworks without executing project code").action(async(t=".")=>{try{let m=kt(Jn(Ks(t)));c(["Framework","Strategy","Confidence","Evidence"],m.map((A)=>[A.framework,A.strategy,`${Math.round(A.confidence*100)}%`,A.evidence.map((_)=>_.path).join(", ")||"manual"]));for(let A of m)e(`${A.framework}: ${A.description}`)}catch(m){hn(m)}}),D.command("app:plan <draft>","Validate and preview a secret-free application draft").option("--secrets <names>","Comma-separated configured secret names").action(async(t,m)=>{try{let A=ce(Id(t),m.secrets?.split(",").map((_)=>_.trim()).filter(Boolean));if(e(JSON.stringify(A,null,2)),!A.valid)hn(Error("Plan is not deployable; resolve the reported issues and missing secrets."))}catch(A){hn(A)}}),D.command("app:draft:save <draft>","Persist or update a resumable application draft").option("--id <id>","Existing draft ID").option("--version <version>","Expected draft version").option("--step <step>","Wizard step",{default:"source"}).option("--secrets <names>","Comma-separated configured secret names").action(async(t,m)=>{try{await Pr(async({drafts:A,controlPlane:_})=>{let z=Id(t),U=m.secrets?.split(",").map((X)=>X.trim()).filter(Boolean)??[],Y=m.id?A.update(m.id,Number(m.version),{draft:z,step:m.step,suppliedSecretNames:U}):A.create({organizationId:_.organization.id,projectId:_.project.id,name:z.name,draft:{...z,projectId:_.project.id},step:m.step,suppliedSecretNames:U});n(`Draft saved: ${Y.id} · v${Y.version} · ${Y.status}`)})}catch(A){hn(A)}}),D.command("app:drafts","List resumable application drafts").action(async()=>{try{await Pr(async({drafts:t,controlPlane:m})=>{let A=t.list(m.project.id);c(["ID","Application","Step","Status","Version","Updated"],A.map((_)=>[_.id,_.input.name,_.step,_.status,String(_.version),_.updatedAt]))})}catch(t){hn(t)}}),D.command("app:export <draft>","Export a deterministic application manifest").option("--output <path>","Output path",{default:"application.manifest.json"}).action(async(t,m)=>{try{await Pr(async({drafts:A})=>{let _=A.get(t);if(!_)throw Error(`Draft ${t} was not found`);let z=ce(_.input,_.suppliedSecretNames),U=Ks(m.output??"application.manifest.json");Op(U,z.serializedManifest,{mode:384}),n(`Manifest exported: ${U}`)})}catch(A){hn(A)}}),D.command("app:deploy <draft>","Confirm and queue a validated application plan").option("--confirm <environment>","Exact target environment slug").action(async(t,m)=>{try{await Pr(async({drafts:A,controlPlane:_})=>{let z=A.get(t);if(!z)throw Error(`Draft ${t} was not found`);if(!m.confirm)throw Error("Pass --confirm <environment> to acknowledge the target and recurring cost drivers");let U=zr({controlPlane:_.store,drafts:A,draftId:t,expectedVersion:z.version,confirmEnvironment:m.confirm});n(`Application queued: ${U.operation.id}`),e(`Cost drivers: ${U.plan.costDrivers.join(", ")}`)})}catch(A){hn(A)}}),D.command("app:import <site>","Import an existing cloud.config.ts site as a draft").option("--connection <id>","Git connection ID for repository-backed sites").option("--env <environment>","Target environment",{default:"production"}).action(async(t,m)=>{try{await Pr(async({config:A,controlPlane:_,sources:z,drafts:U})=>{let Y=A.sites?.[t];if(!Y)throw Error(`Site ${t} was not found`);let X=_.environments.get(m.env??"production");if(!X)throw Error(`Environment ${m.env} was not found`);let ne;if(Y.repository?.url){if(!m.connection)throw Error("Repository-backed imports require --connection <id>");let fe=z.getConnection(m.connection);if(!fe)throw Error("Git connection was not found");let Ae=Ac(Y.repository.url),Pe=z.listRepositories(fe.id).find((he)=>he.fullName.toLowerCase()===Ae.fullName.toLowerCase());if(!Pe)throw Error(`Repository ${Ae.fullName} is not synchronized`);ne={kind:"git",connectionId:fe.id,repositoryId:Pe.id,repositoryFullName:Pe.fullName,ref:Y.repository.branch??"main"}}let re=jp({site:Y,siteSlug:t,projectId:_.project.id,environmentId:X.id,source:ne}),se=U.create({organizationId:_.organization.id,projectId:_.project.id,name:`Import ${t}`,draft:re,step:"review"});n(`Imported draft: ${se.id}`)})}catch(A){hn(A)}}),D.command("app:artifact:add <file>","Inspect and store a bounded ZIP or TAR artifact").action(async(t)=>{try{await Pr(async({artifacts:m,controlPlane:A})=>{let _=Ks(t),z=m.create({organizationId:A.organization.id,projectId:A.project.id,filename:_.split("/").pop(),bytes:Pd(_)});n(`Artifact stored: ${z.id} · ${z.entryCount} entries · ${z.sha256}`)})}catch(m){hn(m)}}),D.command("app:registry:add <host>","Create an encrypted private-registry connection").option("--name <name>","Connection name",{default:"Container registry"}).option("--provider <provider>","docker_hub, ghcr, ecr, gcr, or generic",{default:"generic"}).option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).action(async(t,m)=>{try{await Pr(async({registries:A,controlPlane:_})=>{let z={username:process.env[m.usernameEnv],password:process.env[m.passwordEnv],token:process.env[m.tokenEnv]},U=A.create({organizationId:_.organization.id,provider:m.provider,name:m.name,host:t,credential:Object.values(z).some(Boolean)?z:void 0});n(`Registry connection created: ${U.id}`)})}catch(A){hn(A)}}),D.command("app:registries","List safe registry connection metadata").action(async()=>{try{await Pr(async({registries:t,controlPlane:m})=>c(["ID","Name","Provider","Host","Status"],t.list(m.organization.id).map((A)=>[A.id,A.name,A.provider,A.host,A.status])))}catch(t){hn(t)}}),D.command("app:registry:test <id>","Test registry and optional image pull access").option("--image <image>","Image reference").action(async(t,m)=>{try{await Pr(async({registries:A})=>{let _=await A.test(t,{image:m.image});e(`${_.status}: ${_.healthMessage}`)})}catch(A){hn(A)}}),D.command("app:registry:rotate <id>","Rotate write-only registry credentials").option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).option("--expires <date>","Credential expiry timestamp").action(async(t,m)=>{try{await Pr(async({registries:A})=>{let _={username:process.env[m.usernameEnv],password:process.env[m.passwordEnv],token:process.env[m.tokenEnv]};if(!Object.values(_).some(Boolean))throw Error("Set at least one configured registry credential environment variable");let z=A.rotate(t,_,{expiresAt:m.expires?new Date(m.expires).toISOString():void 0});n(`Registry credential rotated: ${z.id} · ${z.status}`)})}catch(A){hn(A)}}),D.command("app:registry:disconnect <id>","Disconnect and erase registry credentials").option("--yes","Skip confirmation").action(async(t,m)=>{try{await Pr(async({registries:A,drafts:_,controlPlane:z})=>{let U=A.get(t);if(!U)throw Error(`Registry ${t} was not found`);let Y=_.list(z.project.id).filter((X)=>X.input.source.kind==="image"&&X.input.source.registryConnectionId===t||X.input.build.kind==="prebuilt_image"&&X.input.build.registryConnectionId===t);if(e(`${Y.length} application draft${Y.length===1?"":"s"} reference this registry.`),!m.yes&&!await d(`Disconnect ${U.name}?`,!1))return;A.disconnect(t),n(`Disconnected ${U.name}`)})}catch(A){hn(A)}})}import{existsSync as Fp,readFileSync as Bp,writeFileSync as Mp}from"node:fs";function Ic(D,t){r(`'${D}' is not implemented yet.`),e(t),process.exitCode=1}function _p(D){let t={};for(let m of D.split(`
|
|
366
366
|
`)){let A=m.trim();if(!A||A.startsWith("#"))continue;let _=A.indexOf("=");if(_===-1)continue;let z=A.slice(0,_).trim(),U=A.slice(_+1).trim();if(U.startsWith('"')&&U.endsWith('"')||U.startsWith("'")&&U.endsWith("'"))U=U.slice(1,-1);t[z]=U}return t}function zp(D){return Object.entries(D).sort(([m],[A])=>m.localeCompare(A)).map(([m,A])=>{if(!/[\s#"']/.test(A))return`${m}=${A}`;let _='"'+A.replace(/"/g,"\\\"")+'"';return`${m}=${_}`}).join(`
|
|
367
367
|
`)+`
|
|
368
368
|
`}function Pc(D){D.command("env:list","List configured environments and their deployed stack status").action(async()=>{i("Environments");try{let t=await je(),m=Object.keys(t.environments??{});if(!m.length){e("No environments configured. Add environments.<env> to your cloud.config.");return}let A=[];for(let _ of m){let z=t.environments[_],U=z?.region||t.project.region||"us-east-1",Y=z?.app?`serverless (${z.app.kind})`:"server/static",X="not deployed";if(z?.app)try{let ne=new v(U),{Stacks:re}=await ne.describeStacks({stackName:xe(t,_)});X=re?.[0]?.StackStatus??"not deployed"}catch{X="not deployed"}A.push([_,z?.type??_,Y,U,X])}c(["Environment","Type","Kind","Region","Stack status"],A)}catch(t){r(`Failed to list environments: ${t.message}`),process.exitCode=1}}),D.command("env:compare <env1> <env2>","Compare two environments' app configuration").action(async(t,m)=>{i(`Comparing ${t} ↔ ${m}`);try{let A=await je(),_=A.environments?.[t],z=A.environments?.[m];if(!_||!z){r(`Both environments must exist in config (have: ${Object.keys(A.environments??{}).join(", ")||"none"}).`),process.exitCode=1;return}let U=(se,fe="")=>{let Ae={};for(let[Pe,he]of Object.entries(se??{})){let Ie=fe?`${fe}.${Pe}`:Pe;if(he&&typeof he==="object"&&!Array.isArray(he))Object.assign(Ae,U(he,Ie));else Ae[Ie]=Array.isArray(he)?JSON.stringify(he):String(he)}return Ae},Y=U(_),X=U(z),re=[...new Set([...Object.keys(Y),...Object.keys(X)])].sort().filter((se)=>Y[se]!==X[se]).map((se)=>[se,Y[se]??"—",X[se]??"—"]);if(!re.length){n("Identical — no configuration differences.");return}c(["Setting",t,m],re),e(`
|
|
@@ -372,7 +372,7 @@ Deployment complete!`),e(`Total size: ${Ae} MB`),e(`
|
|
|
372
372
|
Assets URL: https://${z}.s3.${A}.amazonaws.com${U?`/${U}`:""}`)}catch(m){r(`Deployment failed: ${m.message}`)}}),D.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 _=(t?.paths||"/*").split(",").map((re)=>re.trim()),z=t?.wait||!1;if(e(`Distribution: ${m}`),e(`Paths: ${_.join(", ")}`),!await d(`
|
|
373
373
|
Invalidate cache now?`,!0)){e("Invalidation cancelled");return}let Y=new E,X=new s("Creating invalidation...");X.start();let ne=await Y.invalidatePaths(m,_);if(X.succeed("Invalidation created"),n(`
|
|
374
374
|
Invalidation ID: ${ne.Id}`),e(`Status: ${ne.Status}`),e(`Created: ${new Date(ne.CreateTime).toLocaleString()}`),z){let re=new s("Waiting for invalidation to complete...");re.start(),await Y.waitForInvalidation(m,ne.Id),re.succeed("Invalidation completed!")}else e(`
|
|
375
|
-
Invalidation is in progress. Use --wait to wait for completion.`)}catch(m){r(`Invalidation failed: ${m.message}`)}})}function Oc(D){for(let[t,m]of[["team:add <email> <role>","Invite an organization member"],["team:list","List organization members"],["team:remove <email>","Revoke organization membership"]])D.command(t,m).action(async(...A)=>En(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 af,execSync as Yc}from"node:child_process";import{copyFileSync as Cs,existsSync as vn,mkdirSync as _d,readFileSync as Jc,writeFileSync as cf}from"node:fs";import{tmpdir as zd}from"node:os";import{dirname as lf,join as Ud,resolve as df}from"node:path";import{existsSync as Lc,readdirSync as xd,statSync as jc}from"node:fs";import{join as Ys}from"node:path";var Wp=new Set([".DS_Store","Thumbs.db",".gitkeep",".keep"]);function Dd(D){return Wp.has(D)}function Ed(D,t=0){if(t>8)return!0;let m;try{m=xd(D)}catch{return!1}for(let A of m){if(Dd(A))continue;let _=Ys(D,A),z;try{z=jc(_)}catch{continue}if(z.isFile())return!0;if(z.isDirectory()&&Ed(_,t+1))return!0}return!1}function qp(D){let t;try{t=xd(D)}catch{return[]}let m=[];for(let A of t){if(Dd(A))continue;let _=Ys(D,A);try{if(jc(_).isDirectory()&&Lc(Ys(_,"index.html")))m.push(A)}catch{}}return m.sort((A,_)=>A.localeCompare(_))}function Td(D){let{root:t,siteName:m,expectIndex:A}=D,_=[];if(!Lc(t))return _.push({level:"error",message:`Build output not found at ${t} for site '${m}'`}),_;if(!jc(t).isDirectory())return _.push({level:"error",message:`Site '${m}' has root ${t}, which is a file, not a directory`}),_;if(!Ed(t))return _.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.`}),_;if(!A)return _;if(Lc(Ys(t,"index.html")))return _;let z=qp(t);if(z.length===1){let U=`${t.replace(/\/+$/,"")}/${z[0]}`;return _.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}'.`}),_}if(z.length>1)return _.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.`}),_;return _.push({level:"warning",message:`Site '${m}' has no index.html anywhere under ${t}. The domain root will 404.`}),_}function Gp(D){if(!D)return[];if(Array.isArray(D))return D.map(String);let t=D,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 _=A.Item;if(!_)return[];return Array.isArray(_)?_.map(String):[String(_)]}if("CNAME"in A){let _=A.CNAME;if(!_)return[];return Array.isArray(_)?_.map(String):[String(_)]}return Object.values(A).flatMap((_)=>{if(Array.isArray(_))return _.map(String);return typeof _==="string"?[_]:[]})}return[String(m)]}async function Zs(D){if(D.length===0)return;let t=new E,m=await t.listDistributions(),A=[...new Set(D.map((_)=>_.toLowerCase()))];for(let _ of A){let z=m.find((Y)=>Gp(Y.Aliases).some((ne)=>ne.toLowerCase()===_));if(!z?.Id){a(`No CloudFront distribution found for ${_} — skipping dynamic HTTP method sync`);continue}if(await t.ensureDynamicHttpMethods(z.Id))n(`CloudFront ${z.Id} (${_}): enabled POST/PUT/PATCH/DELETE on dynamic paths`);else e(`CloudFront ${z.Id} (${_}): already allows dynamic HTTP methods`)}}import{execSync as Hp}from"node:child_process";async function Kp(D,t,m,A={},_=process.cwd()){if(!D)return;if(A.step?.(`Running ${m} hook`),typeof D==="string"){Hp(D,{stdio:"inherit",cwd:_});return}await D(t)}async function vs(D,t,m={},A){try{return await Kp(D.hooks?.[t],D,t,m,A),!0}catch(_){return m.error?.(`${t} hook failed: ${_?.message||_}`),!1}}var wi=(D)=>D===void 0?void 0:D?"on":"off";function Fc(D){let t={ssl:D.ssl,always_use_https:wi(D.alwaysUseHttps),automatic_https_rewrites:wi(D.automaticHttpsRewrites),min_tls_version:D.minTlsVersion,tls_1_3:wi(D.tls13),brotli:wi(D.brotli),http3:wi(D.http3),"0rtt":wi(D.zeroRtt),early_hints:wi(D.earlyHints),browser_cache_ttl:D.browserCacheTtl,always_online:wi(D.alwaysOnline),websockets:wi(D.websockets),email_obfuscation:wi(D.emailObfuscation)};if(D.hsts)t.security_header={strict_transport_security:{enabled:D.hsts.enabled,max_age:D.hsts.maxAge??31536000,include_subdomains:D.hsts.includeSubdomains??!1,preload:D.hsts.preload??!1,nosniff:D.hsts.noSniff??!0}};for(let m of Object.keys(t))if(t[m]===void 0)delete t[m];return{...t,...D.raw??{}}}var bs={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 Jp}from"node:tls";async function Bc(D){let{address:t,serverName:m,port:A=443,timeoutMs:_=5000}=D;return new Promise((z)=>{let U=!1,Y=(ne)=>{if(U)return;U=!0;try{X.destroy()}catch{}z(ne)},X=Jp({host:t,port:A,servername:m,rejectUnauthorized:!1,timeout:_});X.once("secureConnect",()=>{if(X.authorized){Y({reachable:!0,trusted:!0});return}Y({reachable:!0,trusted:!1,reason:X.authorizationError?String(X.authorizationError):"origin certificate is not trusted for this hostname"})}),X.once("timeout",()=>{Y({reachable:!1,trusted:!1,reason:`no TLS handshake within ${_}ms`})}),X.once("error",(ne)=>{Y({reachable:!1,trusted:!1,reason:ne.message})})})}function Yp(D,t){let m=t.replace(/\.$/,"").toLowerCase();return D.some((A)=>{let _=A.replace(/\.$/,"").toLowerCase();if(_===m)return!0;if(!_.startsWith("*."))return!1;let z=_.slice(2);if(!m.endsWith(`.${z}`))return!1;return!m.slice(0,m.length-z.length-1).includes(".")})}async function Mc(D){let{provider:t,zone:m,hosts:A,ipv4:_,ipv6:z,proxied:U=!0,settings:Y=bs,cache:X,originGuard:ne,purge:re=!0,ttl:se=300,skipOriginProbe:fe=!1,skipEdgeCertificateCheck:Ae=!1}=D,Pe={records:[],settingsChanged:[],cacheRules:0,originGuard:!1,purged:!1,deferredProxy:[],warnings:[]};if(A.length===0)return Pe;let he=[],Ie=U&&!Ae?await t.listEdgeCertificateHosts(m):null;for(let Re of A){let Ee=U;if(Ee&&Ie&&!Yp(Ie,Re)){Ee=!1;let Ke=Ie.length===0?"the zone has no active edge certificate yet":`no active edge certificate covers it (${Ie.join(", ")})`;Pe.deferredProxy.push({host:Re,reason:Ke}),Pe.warnings.push(`${Re}: publishing DNS-only — ${Ke}. 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(Ee&&!fe){let Ke=await Bc({address:_,serverName:Re});if(!Ke.trusted){Ee=!1;let Be=Ke.reason||"origin is not serving a trusted certificate";Pe.deferredProxy.push({host:Re,reason:Be}),Pe.warnings.push(`${Re}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${Be}). 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(Ee)he.push(Re);let Te=[{type:"A",content:_}];if(z&&Wi(Re))Te.push({type:"AAAA",content:z});for(let{type:Ke,content:Be}of Te){let _e=await t.upsertRecord(m,{name:Re,type:Ke,content:Be,ttl:se,proxied:Ee});if(_e.success)Pe.records.push({host:Re,type:Ke,content:Be,proxied:Ee});else Pe.warnings.push(`${Re} ${Ke} → ${Be} failed: ${_e.message||"unknown provider error"}`)}}if(he.length===0){if(U)Pe.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");return Pe}try{let Re=Fc(Y),Ee=await t.applyZoneSettings(m,Re);Pe.settingsChanged=Ee.changed;for(let Te of Ee.failed)Pe.warnings.push(`zone setting '${Te.id}' could not be applied: ${Te.error}`)}catch(Re){Pe.warnings.push(`zone settings could not be read: ${Re instanceof Error?Re.message:String(Re)}`)}let Le=ks(he,X);if(Le.length>0){let Re=await t.putManagedPhaseRules(m,"http_request_cache_settings",Le);if(Re.success)Pe.cacheRules=Le.length;else Pe.warnings.push(`cache rules could not be written: ${Re.message}`)}if(ne){let Re=Ns(he,ne.header,ne.value);if(Re.length>0){let Ee=await t.putManagedPhaseRules(m,"http_request_late_transform",Re);if(Ee.success)Pe.originGuard=!0;else Pe.warnings.push(`origin guard header could not be written: ${Ee.message}. `+"The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.")}}if(re){if(Pe.purged=await t.purgeCache(m,{hosts:he}),!Pe.purged)Pe.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.")}return Pe}var Zp=(D)=>process.env[D];function Nd(D,t){let m=D.replace(/\.$/,"").toLowerCase();if(t){let A=t.replace(/\.$/,"").toLowerCase();if(m===A||m.endsWith(`.${A}`))return A}return m.split(".").slice(-2).join(".")}function _c(D,t={}){let m=t.env??Zp,A=D.infrastructure?.compute?.proxy?.cdn,_=[];if(!A||A.provider!=="cloudflare")return{plan:null,errors:_};if(A.originDomain)_.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 _.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled."),{plan:null,errors:_};let U=D.sites??{},Y=A.frontedHosts?.length?[...new Set(A.frontedHosts.map((fe)=>fe.toLowerCase()))]:Xi(U,{autoWww:D.infrastructure?.compute?.proxy?.autoWww!==!1});if(Y.length===0)return _.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain."),{plan:null,errors:_};let X=D.infrastructure?.dns?.domain,ne=new Set(Y.map((fe)=>Nd(fe,X)));if(ne.size>1)return _.push(`Fronted hosts span more than one zone (${[...ne].join(", ")}). A Cloudflare zone is configured as a unit, so run one CDN config per zone.`),{plan:null,errors:_};let re=A.cloudflare??{},se=A.secret?{header:A.secretHeader??"X-Origin-Verify",value:A.secret}:void 0;return{plan:{zone:[...ne][0],hosts:Y,apiToken:z,zoneId:re.zoneId??m("CLOUDFLARE_ZONE_ID"),accountId:re.accountId??m("CLOUDFLARE_ACCOUNT_ID"),proxied:re.proxied!==!1,settings:re.settings??bs,cache:re.cache,originGuard:se,purge:re.purgeOnDeploy!==!1,skipOriginProbe:re.skipOriginProbe===!0},errors:_}}var Qp="https://api.cloudflare.com/client/v4",Ld=10042,Xp=10006,ef=10059;class go extends Error{status;errors;constructor(D,t,m=[]){super(D);this.name="R2ApiError",this.status=t,this.errors=m}hasCode(D){return this.errors.some((t)=>t.code===D)}}class Yi extends go{accountId;constructor(D,t,m=[]){super(`R2 is not enabled on Cloudflare account ${D}. Enable it once in the Cloudflare dashboard (R2 Object Storage, then follow the prompt to activate it), then re-run the deploy.`,t,m);this.name="R2NotEnabledError",this.accountId=D}}class Qs{name="cloudflare-r2";apiToken;accountId;fetchImpl;constructor(D){if(!D.apiToken)throw Error("Cloudflare R2 needs an API token");if(!D.accountId)throw Error("Cloudflare R2 needs an account id");this.apiToken=D.apiToken,this.accountId=D.accountId,this.fetchImpl=D.fetch??((t,m)=>fetch(t,m))}async requestEnvelope(D,t,m,A={}){let _={Authorization:`Bearer ${this.apiToken}`};if(m!==void 0)_["Content-Type"]="application/json";if(A.jurisdiction&&A.jurisdiction!=="default")_["cf-r2-jurisdiction"]=A.jurisdiction;let z=await this.fetchImpl(`${Qp}/accounts/${this.accountId}/r2${t}`,{method:D,headers:_,body:m===void 0?void 0:JSON.stringify(m)}),U=await z.text(),Y;try{Y=JSON.parse(U)}catch{throw new go(`Cloudflare R2 API returned ${z.status} with a non-JSON body: ${U.slice(0,200)}`,z.status)}if(!Y.success){let X=Y.errors??[];if(X.some((re)=>re.code===Ld))throw new Yi(this.accountId,z.status,X);let ne=X.map((re)=>`${re.code}: ${re.message}`).join(", ");throw new go(`Cloudflare R2 API error (${z.status}) on ${D} ${t}: ${ne||U.slice(0,200)}`,z.status,X)}return Y}async request(D,t,m,A){return(await this.requestEnvelope(D,t,m,A)).result}bucketPath(D){return`/buckets/${encodeURIComponent(D)}`}async listBuckets(D={}){let t=[],m;do{let A=new URLSearchParams({per_page:"1000"});if(m)A.set("cursor",m);let _=await this.requestEnvelope("GET",`/buckets?${A}`,void 0,D),z=_.result?.buckets??[];t.push(...z),m=z.length>0?_.result_info?.cursor||void 0:void 0}while(m);return t}async getBucket(D,t={}){try{return await this.request("GET",this.bucketPath(D),void 0,t)}catch(m){if(m instanceof Yi)throw m;if(m instanceof go&&(m.hasCode(Xp)||m.status===404))return null;throw m}}async ensureBucket(D,t={}){let m={jurisdiction:t.jurisdiction},A=await this.getBucket(D,m);if(A)return{bucket:A,created:!1};let _={name:D};if(t.locationHint)_.locationHint=t.locationHint;let z=await this.request("POST","/buckets",_,m);return{bucket:{...z,name:z?.name||D},created:!0}}async getCors(D,t={}){try{return(await this.request("GET",`${this.bucketPath(D)}/cors`,void 0,t))?.rules??[]}catch(m){if(m instanceof go&&!(m instanceof Yi)&&(m.hasCode(ef)||m.status===404))return[];throw m}}async putCors(D,t,m={}){if(t.length===0){await this.request("DELETE",`${this.bucketPath(D)}/cors`,void 0,m);return}await this.request("PUT",`${this.bucketPath(D)}/cors`,{rules:t},m)}async ensureCors(D,t,m={}){let A=await this.getCors(D,m);if(Xs(A,t))return!1;return await this.putCors(D,t,m),!0}async getLifecycle(D,t={}){return(await this.request("GET",`${this.bucketPath(D)}/lifecycle`,void 0,t))?.rules??[]}async putLifecycle(D,t,m={}){await this.request("PUT",`${this.bucketPath(D)}/lifecycle`,{rules:t},m)}async ensureLifecycle(D,t,m={}){let A=await this.getLifecycle(D,m);if(ea(A,t))return!1;return await this.putLifecycle(D,t,m),!0}async listCustomDomains(D,t={}){return(await this.request("GET",`${this.bucketPath(D)}/domains/custom`,void 0,t))?.domains??[]}async attachCustomDomain(D,t,m={}){return this.request("POST",`${this.bucketPath(D)}/domains/custom`,{domain:t.domain,zoneId:t.zoneId,enabled:t.enabled??!0,minTLS:t.minTLS??"1.2"},m)}async updateCustomDomain(D,t,m,A={}){return this.request("PUT",`${this.bucketPath(D)}/domains/custom/${encodeURIComponent(t)}`,m,A)}async ensureCustomDomain(D,t,m={},A){let _=t.minTLS??"1.2",U=(A??await this.listCustomDomains(D,m)).find((ne)=>ne.domain.toLowerCase()===t.domain.toLowerCase());if(!U){let ne=await this.attachCustomDomain(D,{...t,minTLS:_,enabled:!0},m);return{action:"attached",domain:{...ne,domain:ne?.domain||t.domain}}}let Y=U.minTLS??"1.0";if(U.enabled&&Y===_)return{action:"unchanged",domain:U};let X=await this.updateCustomDomain(D,U.domain,{enabled:!0,minTLS:_},m);return{action:"updated",domain:{...U,...X}}}async getManagedDomain(D,t={}){return this.request("GET",`${this.bucketPath(D)}/domains/managed`,void 0,t)}async setManagedDomain(D,t,m={}){let A=await this.getManagedDomain(D,m);if(A?.enabled===t)return{changed:!1,domain:A};return{changed:!0,domain:await this.request("PUT",`${this.bucketPath(D)}/domains/managed`,{enabled:t},m)}}}function ws(D){if(Array.isArray(D))return`[${D.map(ws).join(",")}]`;if(D&&typeof D==="object")return`{${Object.entries(D).filter(([,m])=>m!==void 0).sort(([m],[A])=>m.localeCompare(A)).map(([m,A])=>`${JSON.stringify(m)}:${ws(A)}`).join(",")}}`;return JSON.stringify(D)}function Od(D,t){let m=(A)=>A&&A.length>0?[...A].map(String).sort():void 0;return{id:t?D.id:void 0,allowed:{origins:m(D.allowed?.origins),methods:m(D.allowed?.methods?.map((A)=>A.toUpperCase())),headers:m(D.allowed?.headers?.map((A)=>A.toLowerCase()))},exposeHeaders:m(D.exposeHeaders?.map((A)=>A.toLowerCase())),maxAgeSeconds:D.maxAgeSeconds}}function Xs(D,t){if(D.length!==t.length)return!1;return t.every((m,A)=>{let _=m.id!==void 0;return ws(Od(D[A],_))===ws(Od(m,_))})}function ea(D,t){if(D.length!==t.length)return!1;let m=(A)=>ws([...A].map((_)=>({..._,storageClassTransitions:_.storageClassTransitions?.length?_.storageClassTransitions:void 0})).sort((_,z)=>_.id.localeCompare(z.id)));return m(D)===m(t)}var tf="r2 cache",nf=/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;async function qc(D,t={}){let m=t.log??(()=>{}),A={buckets:[],warnings:[]},_=D.infrastructure?.r2??D.r2,z=Object.entries(_?.buckets??{});if(z.length===0)return m("R2: no buckets declared, skipping."),A;if(!t.apiToken||!t.accountId){let re=[!t.apiToken&&"CLOUDFLARE_API_TOKEN",!t.accountId&&"CLOUDFLARE_ACCOUNT_ID"].filter(Boolean).join(" and "),se=`R2: ${z.length} bucket(s) declared but ${re} is not set, skipping.`;return m(se),A.warnings.push(se),A}let U=t.dryRun===!0,Y=new Qs({apiToken:t.apiToken,accountId:t.accountId,fetch:t.fetch}),X=new Ne(t.apiToken,{zoneId:t.zoneId,accountId:t.accountId}),ne=new Map;for(let[re,se]of z){let fe=se?.name;if(!fe||!nf.test(fe)){A.warnings.push(`R2: bucket '${re}' has an invalid name '${fe??""}' (3-63 chars: lowercase letters, digits, hyphens), skipping.`);continue}m(`R2: ${U?"checking":"reconciling"} bucket ${fe}...`);let Ae={name:fe,changes:[],domains:[]};if(A.buckets.push(Ae),await rf(se,{r2:Y,cloudflare:X,dryRun:U,summary:Ae,warnings:A.warnings,claimed:ne,log:m}),Ae.changes.length===0)m(`R2: ${fe} is in sync.`);else for(let Pe of Ae.changes)m(`R2: ${fe}: ${Pe}`)}return A}async function rf(D,t){let{r2:m,dryRun:A,summary:_,warnings:z}=t,U=D.name,Y={jurisdiction:D.jurisdiction},X=(Ae,Pe)=>{let he=A?Pe&&`would ${Pe}`:Ae;if(he)_.changes.push(he)},ne;if(A)ne=await m.getBucket(U,Y)!==null;else{let{created:Ae}=await m.ensureBucket(U,{locationHint:D.locationHint,jurisdiction:D.jurisdiction});if(ne=!0,Ae)X("created","")}if(!ne)X("","create bucket");let re=sf(D.customDomains);await Ss(t,`cors on ${U}`,async()=>{if(!D.cors)return;if(!ne){X("cors updated","set cors");return}let Ae=await m.getCors(U,Y);if(Xs(Ae,D.cors))return;if(!A)await m.putCors(U,D.cors,Y);X(D.cors.length===0?"cors removed":"cors updated",D.cors.length===0?"remove cors":"update cors")}),await Ss(t,`lifecycle on ${U}`,async()=>{if(!D.lifecycle)return;let Ae=D.lifecycle.map(Bd);if(ne){let Pe=await m.getLifecycle(U,Y);if(ea(Pe,Ae))return}if(!A)await m.putLifecycle(U,Ae,Y);X("lifecycle updated","update lifecycle")}),await Ss(t,`r2.dev URL on ${U}`,async()=>{let Ae=D.publicDevUrl===!0;if(!ne){if(Ae)X("r2.dev URL enabled","enable r2.dev URL");return}if((await m.getManagedDomain(U,Y))?.enabled===Ae)return;if(!A)await m.setManagedDomain(U,Ae,Y);X(Ae?"r2.dev URL enabled":"r2.dev URL disabled",Ae?"enable r2.dev URL":"disable r2.dev URL")});let se=[];if(re.length>0&&ne)try{se=await m.listCustomDomains(U,Y)}catch(Ae){if(Ae instanceof Yi)throw Ae;z.push(`R2: could not list custom domains on ${U}: ${Gc(Ae)}`)}let fe=new Map;for(let{domain:Ae,minTLS:Pe}of re){let he=t.claimed.get(Ae);if(he&&he!==U){z.push(`R2: ${Ae} is declared on both ${he} and ${U}; leaving it on ${he}.`);continue}t.claimed.set(Ae,U);let Ie=se.find((Re)=>Re.domain.toLowerCase()===Ae),Le=Pe??"1.2";if(Ie&&Ie.enabled&&(Ie.minTLS??"1.0")===Le){if(_.domains.push({domain:Ae,status:Wc(Ie)}),Ie.zoneId)fe.set(Ae,Ie.zoneId);continue}await Ss(t,`custom domain ${Ae} on ${U}`,async()=>{if(Ie){if(!A)await m.updateCustomDomain(U,Ie.domain,{enabled:!0,minTLS:Le},Y);if(X(`custom domain ${Ae} updated`,`update custom domain ${Ae}`),_.domains.push({domain:Ae,status:Wc({...Ie,enabled:!0})}),Ie.zoneId)fe.set(Ae,Ie.zoneId);return}let Re=await t.cloudflare.zoneIdFor(Ae);if(fe.set(Ae,Re),!A)await m.ensureCustomDomain(U,{domain:Ae,zoneId:Re,minTLS:Le},Y,se);X(`custom domain ${Ae} attached`,`attach custom domain ${Ae}`),_.domains.push({domain:Ae,status:A?"not attached":"pending"})},()=>_.domains.push({domain:Ae,status:"error"}))}if(D.cache&&re.length>0)await of(D,re.map((Ae)=>Ae.domain).filter((Ae)=>t.claimed.get(Ae)===U),fe,t,X)}async function of(D,t,m,A,_){let z=jd(D.cache);if(!z||t.length===0)return;let U=new Map;for(let Y of t){let X=m.get(Y);if(!X)try{X=await A.cloudflare.zoneIdFor(Y)}catch(ne){A.warnings.push(`R2: could not resolve the zone for ${Y}, cache rule skipped: ${Gc(ne)}`);continue}U.set(X,[...U.get(X)??[],Y])}for(let Y of U.values()){let X={action:"set_cache_settings",description:`${Er} ${tf}`,expression:`(${Tr(Y)})`,enabled:!0,action_parameters:z};await Ss(A,`cache rule for ${Y.join(", ")}`,async()=>{let ne=await A.cloudflare.getPhaseRules(Y[0],"http_request_cache_settings");if(Fd(ne,X))return;if(!A.dryRun){let re=await A.cloudflare.putManagedPhaseRules(Y[0],"http_request_cache_settings",[X]);if(!re.success)throw Error(re.message||"cache rule write failed")}_(`cache rule updated for ${Y.join(", ")}`,`update cache rule for ${Y.join(", ")}`)})}}function jd(D){if(D.edgeTtl===void 0&&D.browserTtl===void 0)return null;let t={cache:!0};if(D.edgeTtl!==void 0)t.edge_ttl={mode:"override_origin",default:D.edgeTtl};if(D.browserTtl!==void 0)t.browser_ttl={mode:"override_origin",default:D.browserTtl};return t}function Fd(D,t){let m=wr(t.expression),A=Ci(t.expression),_=(Y)=>(Y.description||"").startsWith(Er),z=(Y)=>Y.action===t.action&&Y.description===t.description&&(Y.enabled??!0)===(t.enabled??!0)&&Ci(Y.expression)===A&&na(Y.action_parameters)===na(t.action_parameters),U=!1;for(let Y of D){if(!_(Y))continue;let X=wr(Y.expression);if(!m.some((re)=>X.includes(re)))continue;if(z(Y)&&m.every((re)=>X.includes(re))&&!U){U=!0;continue}return!1}return U}function Bd(D){let t={id:D.id,enabled:D.enabled??!0,conditions:{prefix:D.prefix??""}};if(D.expireAfterDays!==void 0)t.deleteObjectsTransition={condition:{type:"Age",maxAge:Uc(D.expireAfterDays)}};else if(D.expireOn!==void 0)t.deleteObjectsTransition={condition:{type:"Date",date:D.expireOn}};if(D.abortMultipartUploadsAfterDays!==void 0)t.abortMultipartUploadsTransition={condition:{type:"Age",maxAge:Uc(D.abortMultipartUploadsAfterDays)}};if(D.infrequentAccessAfterDays!==void 0)t.storageClassTransitions=[{storageClass:"InfrequentAccess",condition:{type:"Age",maxAge:Uc(D.infrequentAccessAfterDays)}}];return t}function Uc(D){return Math.round(D*86400)}function sf(D){let t=new Set,m=[];for(let A of D??[]){let _=typeof A==="string"?{domain:A}:A,z=_.domain?.trim().replace(/\.$/,"").toLowerCase();if(!z||t.has(z))continue;t.add(z),m.push({domain:z,minTLS:_.minTLS})}return m}function Wc(D){if(!D.enabled)return"disabled";let t=D.status?.ownership,m=D.status?.ssl;if(t==="active"&&m==="active")return"active";for(let A of[t,m])if(A==="error"||A==="blocked"||A==="deactivated")return A;return"pending"}async function Ss(D,t,m,A){try{await m()}catch(_){if(_ instanceof Yi)throw _;D.warnings.push(`R2: ${t} failed: ${Gc(_)}`),A?.()}}function Gc(D){return D instanceof Error?D.message:String(D)}function na(D){if(Array.isArray(D))return`[${D.map(na).join(",")}]`;if(D&&typeof D==="object")return`{${Object.entries(D).filter(([,m])=>m!==void 0).sort(([m],[A])=>m.localeCompare(A)).map(([m,A])=>`${JSON.stringify(m)}:${na(A)}`).join(",")}}`;return JSON.stringify(D)}async function uf(D){let t=ta();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 lt(D).getCallerIdentity();if(A.Arn)e(`AWS identity: ${A.Arn}`)}catch(m){a(`Could not verify AWS identity: ${m.message}`)}}function mf(){try{return Yc("git rev-parse --short HEAD",{stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return Date.now().toString(36)}}async function Hc(D,t,m,A){let{ensureManagementDashboard:_}=await import("./chunk-h0s69e9p.js");_(D,{logger:{info:e,warn:a}});let z=D.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(([Le])=>Le).join(", ")}`),!1;let Y=U.filter(([Le,Re])=>{if(!Re)return!1;if(A&&Le!==A)return!1;if(P(Re)==="bucket")return a(`Site '${Le}' targets a bucket — skipping compute deploy (handled by the static-site path).`),!1;return Gs(Re)});if(A)e(`Deploying only '${A}'. Other sites keep their current release; proxy routes are still regenerated in full.`);let X=D.project.slug,re=(D.infrastructure?.compute||{}).runtime||"bun",se=O({config:D}),fe=mf(),Ae=new Map,Pe=new Map,he={step:(Le)=>u(Le),error:(Le)=>r(Le)};if(!await vs(D,"beforeDeploy",he))return!1;if(!await vs(D,"beforeBuild",he))return!1;for(let[Le,Re]of Y){let Ee=P(Re);if(i(`Preparing site: ${Le}`),e(`Domain: ${Re.domain||"(none)"}`),e(`Source: ${Re.root}`),e(`Kind: ${Ee==="server-static"?"static (shipped to /var/www)":"app (systemd service)"}`),Ee==="server-app")e(`Start: ${Re.start}`),e(`Port: ${Re.port??"(unset)"}`);if(Re.build){u(`Running build: ${Re.build}`);try{Yc(Re.build,{stdio:"inherit",cwd:process.cwd()})}catch(et){return r(`Build failed for site '${Le}': ${et.message}`),!1}}let Te=Td({root:Re.root,siteName:Le,expectIndex:Ee==="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 Ke=Re.build?void 0:JSON.stringify([df(Re.root),[...Re.exclude||[]].sort()]),Be=Ke?Pe.get(Ke):void 0;if(Be){u(`Reusing package for ${Re.root} → ${Be}`),Ae.set(Le,Be);continue}let _e=Ud(zd(),`${X}-${Le}-${fe}.tar.gz`);u(`Packaging ${Re.root} → ${_e}`);try{let et=(Re.exclude||[]).map((Rt)=>`--exclude=${Rt}`);af("tar",["czf",_e,"-C",Re.root,...et,"."],{stdio:"inherit",env:{...process.env,COPYFILE_DISABLE:"1"}})}catch(et){return r(`Failed to package '${Le}': ${et.message}`),!1}if(Ae.set(Le,_e),Ke)Pe.set(Ke,_e)}if(!await vs(D,"afterBuild",he))return!1;let Ie=await ro({config:A?{...D,sites:Object.fromEntries(Y)}:D,rpxConfig:A?D:void 0,environment:t,driver:se,sha:fe,runtime:re,tarballForSite:(Le)=>{let Re=Ae.get(Le);if(!Re)throw Error(`Missing tarball for site '${Le}'`);return Re},logger:{info:(Le)=>e(Le),warn:(Le)=>a(Le),error:(Le)=>r(Le),step:(Le)=>u(Le),success:(Le)=>n(Le)}});if(Ie&&!await vs(D,"afterDeploy",he))return!1;return Ie}async function ra(D){let t=new qr;if(u("Running pre-deployment security scan..."),D.exclude?.length)e(`Excluding from the scan: ${D.exclude.join(", ")}`);let m=await t.scan({directory:D.sourceDir,failOnSeverity:D.failOnSeverity||"critical",skipPatterns:D.skipPatterns,exclude:D.exclude});return{passed:m.passed,result:m}}function pf(){let D=Ud(zd(),`ts-cloud-empty-scan-${process.pid}`);return _d(D,{recursive:!0}),D}function ia(D){let{summary:t,findings:m,scannedFiles:A,duration:_}=D;if(e(`
|
|
375
|
+
Invalidation is in progress. Use --wait to wait for completion.`)}catch(m){r(`Invalidation failed: ${m.message}`)}})}function Oc(D){for(let[t,m]of[["team:add <email> <role>","Invite an organization member"],["team:list","List organization members"],["team:remove <email>","Revoke organization membership"]])D.command(t,m).action(async(...A)=>En(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 af,execSync as Yc}from"node:child_process";import{copyFileSync as Cs,existsSync as vn,mkdirSync as _d,readFileSync as Jc,writeFileSync as cf}from"node:fs";import{tmpdir as zd}from"node:os";import{dirname as lf,join as Ud,resolve as df}from"node:path";import{existsSync as Lc,readdirSync as xd,statSync as jc}from"node:fs";import{join as Ys}from"node:path";var Wp=new Set([".DS_Store","Thumbs.db",".gitkeep",".keep"]);function Dd(D){return Wp.has(D)}function Ed(D,t=0){if(t>8)return!0;let m;try{m=xd(D)}catch{return!1}for(let A of m){if(Dd(A))continue;let _=Ys(D,A),z;try{z=jc(_)}catch{continue}if(z.isFile())return!0;if(z.isDirectory()&&Ed(_,t+1))return!0}return!1}function qp(D){let t;try{t=xd(D)}catch{return[]}let m=[];for(let A of t){if(Dd(A))continue;let _=Ys(D,A);try{if(jc(_).isDirectory()&&Lc(Ys(_,"index.html")))m.push(A)}catch{}}return m.sort((A,_)=>A.localeCompare(_))}function Td(D){let{root:t,siteName:m,expectIndex:A}=D,_=[];if(!Lc(t))return _.push({level:"error",message:`Build output not found at ${t} for site '${m}'`}),_;if(!jc(t).isDirectory())return _.push({level:"error",message:`Site '${m}' has root ${t}, which is a file, not a directory`}),_;if(!Ed(t))return _.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.`}),_;if(!A)return _;if(Lc(Ys(t,"index.html")))return _;let z=qp(t);if(z.length===1){let U=`${t.replace(/\/+$/,"")}/${z[0]}`;return _.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}'.`}),_}if(z.length>1)return _.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.`}),_;return _.push({level:"warning",message:`Site '${m}' has no index.html anywhere under ${t}. The domain root will 404.`}),_}function Gp(D){if(!D)return[];if(Array.isArray(D))return D.map(String);let t=D,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 _=A.Item;if(!_)return[];return Array.isArray(_)?_.map(String):[String(_)]}if("CNAME"in A){let _=A.CNAME;if(!_)return[];return Array.isArray(_)?_.map(String):[String(_)]}return Object.values(A).flatMap((_)=>{if(Array.isArray(_))return _.map(String);return typeof _==="string"?[_]:[]})}return[String(m)]}async function Zs(D){if(D.length===0)return;let t=new E,m=await t.listDistributions(),A=[...new Set(D.map((_)=>_.toLowerCase()))];for(let _ of A){let z=m.find((Y)=>Gp(Y.Aliases).some((ne)=>ne.toLowerCase()===_));if(!z?.Id){a(`No CloudFront distribution found for ${_} — skipping dynamic HTTP method sync`);continue}if(await t.ensureDynamicHttpMethods(z.Id))n(`CloudFront ${z.Id} (${_}): enabled POST/PUT/PATCH/DELETE on dynamic paths`);else e(`CloudFront ${z.Id} (${_}): already allows dynamic HTTP methods`)}}import{execSync as Hp}from"node:child_process";async function Kp(D,t,m,A={},_=process.cwd()){if(!D)return;if(A.step?.(`Running ${m} hook`),typeof D==="string"){Hp(D,{stdio:"inherit",cwd:_});return}await D(t)}async function vs(D,t,m={},A){try{return await Kp(D.hooks?.[t],D,t,m,A),!0}catch(_){return m.error?.(`${t} hook failed: ${_?.message||_}`),!1}}var wi=(D)=>D===void 0?void 0:D?"on":"off";function Fc(D){let t={ssl:D.ssl,always_use_https:wi(D.alwaysUseHttps),automatic_https_rewrites:wi(D.automaticHttpsRewrites),min_tls_version:D.minTlsVersion,tls_1_3:wi(D.tls13),brotli:wi(D.brotli),http3:wi(D.http3),"0rtt":wi(D.zeroRtt),early_hints:wi(D.earlyHints),browser_cache_ttl:D.browserCacheTtl,always_online:wi(D.alwaysOnline),websockets:wi(D.websockets),email_obfuscation:wi(D.emailObfuscation)};if(D.hsts)t.security_header={strict_transport_security:{enabled:D.hsts.enabled,max_age:D.hsts.maxAge??31536000,include_subdomains:D.hsts.includeSubdomains??!1,preload:D.hsts.preload??!1,nosniff:D.hsts.noSniff??!0}};for(let m of Object.keys(t))if(t[m]===void 0)delete t[m];return{...t,...D.raw??{}}}var bs={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 Jp}from"node:tls";async function Bc(D){let{address:t,serverName:m,port:A=443,timeoutMs:_=5000}=D;return new Promise((z)=>{let U=!1,Y=(ne)=>{if(U)return;U=!0;try{X.destroy()}catch{}z(ne)},X=Jp({host:t,port:A,servername:m,rejectUnauthorized:!1,timeout:_});X.once("secureConnect",()=>{if(X.authorized){Y({reachable:!0,trusted:!0});return}Y({reachable:!0,trusted:!1,reason:X.authorizationError?String(X.authorizationError):"origin certificate is not trusted for this hostname"})}),X.once("timeout",()=>{Y({reachable:!1,trusted:!1,reason:`no TLS handshake within ${_}ms`})}),X.once("error",(ne)=>{Y({reachable:!1,trusted:!1,reason:ne.message})})})}function Yp(D,t){let m=t.replace(/\.$/,"").toLowerCase();return D.some((A)=>{let _=A.replace(/\.$/,"").toLowerCase();if(_===m)return!0;if(!_.startsWith("*."))return!1;let z=_.slice(2);if(!m.endsWith(`.${z}`))return!1;return!m.slice(0,m.length-z.length-1).includes(".")})}async function Mc(D){let{provider:t,zone:m,hosts:A,ipv4:_,ipv6:z,proxied:U=!0,settings:Y=bs,cache:X,originGuard:ne,purge:re=!0,ttl:se=300,skipOriginProbe:fe=!1,skipEdgeCertificateCheck:Ae=!1}=D,Pe={records:[],settingsChanged:[],cacheRules:0,originGuard:!1,purged:!1,deferredProxy:[],warnings:[]};if(A.length===0)return Pe;let he=[],Ie=U&&!Ae?await t.listEdgeCertificateHosts(m):null;for(let Re of A){let Ee=U;if(Ee&&Ie&&!Yp(Ie,Re)){Ee=!1;let Ke=Ie.length===0?"the zone has no active edge certificate yet":`no active edge certificate covers it (${Ie.join(", ")})`;Pe.deferredProxy.push({host:Re,reason:Ke}),Pe.warnings.push(`${Re}: publishing DNS-only — ${Ke}. 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(Ee&&!fe){let Ke=await Bc({address:_,serverName:Re});if(!Ke.trusted){Ee=!1;let Be=Ke.reason||"origin is not serving a trusted certificate";Pe.deferredProxy.push({host:Re,reason:Be}),Pe.warnings.push(`${Re}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${Be}). 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(Ee)he.push(Re);let Te=[{type:"A",content:_}];if(z&&Wi(Re))Te.push({type:"AAAA",content:z});for(let{type:Ke,content:Be}of Te){let _e=await t.upsertRecord(m,{name:Re,type:Ke,content:Be,ttl:se,proxied:Ee});if(_e.success)Pe.records.push({host:Re,type:Ke,content:Be,proxied:Ee});else Pe.warnings.push(`${Re} ${Ke} → ${Be} failed: ${_e.message||"unknown provider error"}`)}}if(he.length===0){if(U)Pe.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");return Pe}try{let Re=Fc(Y),Ee=await t.applyZoneSettings(m,Re);Pe.settingsChanged=Ee.changed;for(let Te of Ee.failed)Pe.warnings.push(`zone setting '${Te.id}' could not be applied: ${Te.error}`)}catch(Re){Pe.warnings.push(`zone settings could not be read: ${Re instanceof Error?Re.message:String(Re)}`)}let Le=ks(he,X);if(Le.length>0){let Re=await t.putManagedPhaseRules(m,"http_request_cache_settings",Le);if(Re.success)Pe.cacheRules=Le.length;else Pe.warnings.push(`cache rules could not be written: ${Re.message}`)}if(ne){let Re=Ns(he,ne.header,ne.value);if(Re.length>0){let Ee=await t.putManagedPhaseRules(m,"http_request_late_transform",Re);if(Ee.success)Pe.originGuard=!0;else Pe.warnings.push(`origin guard header could not be written: ${Ee.message}. `+"The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.")}}if(re){if(Pe.purged=await t.purgeCache(m,{hosts:he}),!Pe.purged)Pe.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.")}return Pe}var Zp=(D)=>process.env[D];function Nd(D,t){let m=D.replace(/\.$/,"").toLowerCase();if(t){let A=t.replace(/\.$/,"").toLowerCase();if(m===A||m.endsWith(`.${A}`))return A}return m.split(".").slice(-2).join(".")}function _c(D,t={}){let m=t.env??Zp,A=D.infrastructure?.compute?.proxy?.cdn,_=[];if(!A||A.provider!=="cloudflare")return{plan:null,errors:_};if(A.originDomain)_.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 _.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled."),{plan:null,errors:_};let U=D.sites??{},Y=A.frontedHosts?.length?[...new Set(A.frontedHosts.map((fe)=>fe.toLowerCase()))]:Xi(U,{autoWww:D.infrastructure?.compute?.proxy?.autoWww!==!1});if(Y.length===0)return _.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain."),{plan:null,errors:_};let X=D.infrastructure?.dns?.domain,ne=new Set(Y.map((fe)=>Nd(fe,X)));if(ne.size>1)return _.push(`Fronted hosts span more than one zone (${[...ne].join(", ")}). A Cloudflare zone is configured as a unit, so run one CDN config per zone.`),{plan:null,errors:_};let re=A.cloudflare??{},se=A.secret?{header:A.secretHeader??"X-Origin-Verify",value:A.secret}:void 0;return{plan:{zone:[...ne][0],hosts:Y,apiToken:z,zoneId:re.zoneId??m("CLOUDFLARE_ZONE_ID"),accountId:re.accountId??m("CLOUDFLARE_ACCOUNT_ID"),proxied:re.proxied!==!1,settings:re.settings??bs,cache:re.cache,originGuard:se,purge:re.purgeOnDeploy!==!1,skipOriginProbe:re.skipOriginProbe===!0},errors:_}}var Qp="https://api.cloudflare.com/client/v4",Ld=10042,Xp=10006,ef=10059;class go extends Error{status;errors;constructor(D,t,m=[]){super(D);this.name="R2ApiError",this.status=t,this.errors=m}hasCode(D){return this.errors.some((t)=>t.code===D)}}class Yi extends go{accountId;constructor(D,t,m=[]){super(`R2 is not enabled on Cloudflare account ${D}. Enable it once in the Cloudflare dashboard (R2 Object Storage, then follow the prompt to activate it), then re-run the deploy.`,t,m);this.name="R2NotEnabledError",this.accountId=D}}class Qs{name="cloudflare-r2";apiToken;accountId;fetchImpl;constructor(D){if(!D.apiToken)throw Error("Cloudflare R2 needs an API token");if(!D.accountId)throw Error("Cloudflare R2 needs an account id");this.apiToken=D.apiToken,this.accountId=D.accountId,this.fetchImpl=D.fetch??((t,m)=>fetch(t,m))}async requestEnvelope(D,t,m,A={}){let _={Authorization:`Bearer ${this.apiToken}`};if(m!==void 0)_["Content-Type"]="application/json";if(A.jurisdiction&&A.jurisdiction!=="default")_["cf-r2-jurisdiction"]=A.jurisdiction;let z=await this.fetchImpl(`${Qp}/accounts/${this.accountId}/r2${t}`,{method:D,headers:_,body:m===void 0?void 0:JSON.stringify(m)}),U=await z.text(),Y;try{Y=JSON.parse(U)}catch{throw new go(`Cloudflare R2 API returned ${z.status} with a non-JSON body: ${U.slice(0,200)}`,z.status)}if(!Y.success){let X=Y.errors??[];if(X.some((re)=>re.code===Ld))throw new Yi(this.accountId,z.status,X);let ne=X.map((re)=>`${re.code}: ${re.message}`).join(", ");throw new go(`Cloudflare R2 API error (${z.status}) on ${D} ${t}: ${ne||U.slice(0,200)}`,z.status,X)}return Y}async request(D,t,m,A){return(await this.requestEnvelope(D,t,m,A)).result}bucketPath(D){return`/buckets/${encodeURIComponent(D)}`}async listBuckets(D={}){let t=[],m;do{let A=new URLSearchParams({per_page:"1000"});if(m)A.set("cursor",m);let _=await this.requestEnvelope("GET",`/buckets?${A}`,void 0,D),z=_.result?.buckets??[];t.push(...z),m=z.length>0?_.result_info?.cursor||void 0:void 0}while(m);return t}async getBucket(D,t={}){try{return await this.request("GET",this.bucketPath(D),void 0,t)}catch(m){if(m instanceof Yi)throw m;if(m instanceof go&&(m.hasCode(Xp)||m.status===404))return null;throw m}}async ensureBucket(D,t={}){let m={jurisdiction:t.jurisdiction},A=await this.getBucket(D,m);if(A)return{bucket:A,created:!1};let _={name:D};if(t.locationHint)_.locationHint=t.locationHint;let z=await this.request("POST","/buckets",_,m);return{bucket:{...z,name:z?.name||D},created:!0}}async getCors(D,t={}){try{return(await this.request("GET",`${this.bucketPath(D)}/cors`,void 0,t))?.rules??[]}catch(m){if(m instanceof go&&!(m instanceof Yi)&&(m.hasCode(ef)||m.status===404))return[];throw m}}async putCors(D,t,m={}){if(t.length===0){await this.request("DELETE",`${this.bucketPath(D)}/cors`,void 0,m);return}await this.request("PUT",`${this.bucketPath(D)}/cors`,{rules:t},m)}async ensureCors(D,t,m={}){let A=await this.getCors(D,m);if(Xs(A,t))return!1;return await this.putCors(D,t,m),!0}async getLifecycle(D,t={}){return(await this.request("GET",`${this.bucketPath(D)}/lifecycle`,void 0,t))?.rules??[]}async putLifecycle(D,t,m={}){await this.request("PUT",`${this.bucketPath(D)}/lifecycle`,{rules:t},m)}async ensureLifecycle(D,t,m={}){let A=await this.getLifecycle(D,m);if(ea(A,t))return!1;return await this.putLifecycle(D,t,m),!0}async listCustomDomains(D,t={}){return(await this.request("GET",`${this.bucketPath(D)}/domains/custom`,void 0,t))?.domains??[]}async attachCustomDomain(D,t,m={}){return this.request("POST",`${this.bucketPath(D)}/domains/custom`,{domain:t.domain,zoneId:t.zoneId,enabled:t.enabled??!0,minTLS:t.minTLS??"1.2"},m)}async updateCustomDomain(D,t,m,A={}){return this.request("PUT",`${this.bucketPath(D)}/domains/custom/${encodeURIComponent(t)}`,m,A)}async ensureCustomDomain(D,t,m={},A){let _=t.minTLS??"1.2",U=(A??await this.listCustomDomains(D,m)).find((ne)=>ne.domain.toLowerCase()===t.domain.toLowerCase());if(!U){let ne=await this.attachCustomDomain(D,{...t,minTLS:_,enabled:!0},m);return{action:"attached",domain:{...ne,domain:ne?.domain||t.domain}}}let Y=U.minTLS??"1.0";if(U.enabled&&Y===_)return{action:"unchanged",domain:U};let X=await this.updateCustomDomain(D,U.domain,{enabled:!0,minTLS:_},m);return{action:"updated",domain:{...U,...X}}}async getManagedDomain(D,t={}){return this.request("GET",`${this.bucketPath(D)}/domains/managed`,void 0,t)}async setManagedDomain(D,t,m={}){let A=await this.getManagedDomain(D,m);if(A?.enabled===t)return{changed:!1,domain:A};return{changed:!0,domain:await this.request("PUT",`${this.bucketPath(D)}/domains/managed`,{enabled:t},m)}}}function ws(D){if(Array.isArray(D))return`[${D.map(ws).join(",")}]`;if(D&&typeof D==="object")return`{${Object.entries(D).filter(([,m])=>m!==void 0).sort(([m],[A])=>m.localeCompare(A)).map(([m,A])=>`${JSON.stringify(m)}:${ws(A)}`).join(",")}}`;return JSON.stringify(D)}function Od(D,t){let m=(A)=>A&&A.length>0?[...A].map(String).sort():void 0;return{id:t?D.id:void 0,allowed:{origins:m(D.allowed?.origins),methods:m(D.allowed?.methods?.map((A)=>A.toUpperCase())),headers:m(D.allowed?.headers?.map((A)=>A.toLowerCase()))},exposeHeaders:m(D.exposeHeaders?.map((A)=>A.toLowerCase())),maxAgeSeconds:D.maxAgeSeconds}}function Xs(D,t){if(D.length!==t.length)return!1;return t.every((m,A)=>{let _=m.id!==void 0;return ws(Od(D[A],_))===ws(Od(m,_))})}function ea(D,t){if(D.length!==t.length)return!1;let m=(A)=>ws([...A].map((_)=>({..._,storageClassTransitions:_.storageClassTransitions?.length?_.storageClassTransitions:void 0})).sort((_,z)=>_.id.localeCompare(z.id)));return m(D)===m(t)}var tf="r2 cache",nf=/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;async function qc(D,t={}){let m=t.log??(()=>{}),A={buckets:[],warnings:[]},_=D.infrastructure?.r2??D.r2,z=Object.entries(_?.buckets??{});if(z.length===0)return m("R2: no buckets declared, skipping."),A;if(!t.apiToken||!t.accountId){let re=[!t.apiToken&&"CLOUDFLARE_API_TOKEN",!t.accountId&&"CLOUDFLARE_ACCOUNT_ID"].filter(Boolean).join(" and "),se=`R2: ${z.length} bucket(s) declared but ${re} is not set, skipping.`;return m(se),A.warnings.push(se),A}let U=t.dryRun===!0,Y=new Qs({apiToken:t.apiToken,accountId:t.accountId,fetch:t.fetch}),X=new Ne(t.apiToken,{zoneId:t.zoneId,accountId:t.accountId}),ne=new Map;for(let[re,se]of z){let fe=se?.name;if(!fe||!nf.test(fe)){A.warnings.push(`R2: bucket '${re}' has an invalid name '${fe??""}' (3-63 chars: lowercase letters, digits, hyphens), skipping.`);continue}m(`R2: ${U?"checking":"reconciling"} bucket ${fe}...`);let Ae={name:fe,changes:[],domains:[]};if(A.buckets.push(Ae),await rf(se,{r2:Y,cloudflare:X,dryRun:U,summary:Ae,warnings:A.warnings,claimed:ne,log:m}),Ae.changes.length===0)m(`R2: ${fe} is in sync.`);else for(let Pe of Ae.changes)m(`R2: ${fe}: ${Pe}`)}return A}async function rf(D,t){let{r2:m,dryRun:A,summary:_,warnings:z}=t,U=D.name,Y={jurisdiction:D.jurisdiction},X=(Ae,Pe)=>{let he=A?Pe&&`would ${Pe}`:Ae;if(he)_.changes.push(he)},ne;if(A)ne=await m.getBucket(U,Y)!==null;else{let{created:Ae}=await m.ensureBucket(U,{locationHint:D.locationHint,jurisdiction:D.jurisdiction});if(ne=!0,Ae)X("created","")}if(!ne)X("","create bucket");let re=sf(D.customDomains);await Ss(t,`cors on ${U}`,async()=>{if(!D.cors)return;if(!ne){X("cors updated","set cors");return}let Ae=await m.getCors(U,Y);if(Xs(Ae,D.cors))return;if(!A)await m.putCors(U,D.cors,Y);X(D.cors.length===0?"cors removed":"cors updated",D.cors.length===0?"remove cors":"update cors")}),await Ss(t,`lifecycle on ${U}`,async()=>{if(!D.lifecycle)return;let Ae=D.lifecycle.map(Bd);if(ne){let Pe=await m.getLifecycle(U,Y);if(ea(Pe,Ae))return}if(!A)await m.putLifecycle(U,Ae,Y);X("lifecycle updated","update lifecycle")}),await Ss(t,`r2.dev URL on ${U}`,async()=>{let Ae=D.publicDevUrl===!0;if(!ne){if(Ae)X("r2.dev URL enabled","enable r2.dev URL");return}if((await m.getManagedDomain(U,Y))?.enabled===Ae)return;if(!A)await m.setManagedDomain(U,Ae,Y);X(Ae?"r2.dev URL enabled":"r2.dev URL disabled",Ae?"enable r2.dev URL":"disable r2.dev URL")});let se=[];if(re.length>0&&ne)try{se=await m.listCustomDomains(U,Y)}catch(Ae){if(Ae instanceof Yi)throw Ae;z.push(`R2: could not list custom domains on ${U}: ${Gc(Ae)}`)}let fe=new Map;for(let{domain:Ae,minTLS:Pe}of re){let he=t.claimed.get(Ae);if(he&&he!==U){z.push(`R2: ${Ae} is declared on both ${he} and ${U}; leaving it on ${he}.`);continue}t.claimed.set(Ae,U);let Ie=se.find((Re)=>Re.domain.toLowerCase()===Ae),Le=Pe??"1.2";if(Ie&&Ie.enabled&&(Ie.minTLS??"1.0")===Le){if(_.domains.push({domain:Ae,status:Wc(Ie)}),Ie.zoneId)fe.set(Ae,Ie.zoneId);continue}await Ss(t,`custom domain ${Ae} on ${U}`,async()=>{if(Ie){if(!A)await m.updateCustomDomain(U,Ie.domain,{enabled:!0,minTLS:Le},Y);if(X(`custom domain ${Ae} updated`,`update custom domain ${Ae}`),_.domains.push({domain:Ae,status:Wc({...Ie,enabled:!0})}),Ie.zoneId)fe.set(Ae,Ie.zoneId);return}let Re=await t.cloudflare.zoneIdFor(Ae);if(fe.set(Ae,Re),!A)await m.ensureCustomDomain(U,{domain:Ae,zoneId:Re,minTLS:Le},Y,se);X(`custom domain ${Ae} attached`,`attach custom domain ${Ae}`),_.domains.push({domain:Ae,status:A?"not attached":"pending"})},()=>_.domains.push({domain:Ae,status:"error"}))}if(D.cache&&re.length>0)await of(D,re.map((Ae)=>Ae.domain).filter((Ae)=>t.claimed.get(Ae)===U),fe,t,X)}async function of(D,t,m,A,_){let z=jd(D.cache);if(!z||t.length===0)return;let U=new Map;for(let Y of t){let X=m.get(Y);if(!X)try{X=await A.cloudflare.zoneIdFor(Y)}catch(ne){A.warnings.push(`R2: could not resolve the zone for ${Y}, cache rule skipped: ${Gc(ne)}`);continue}U.set(X,[...U.get(X)??[],Y])}for(let Y of U.values()){let X={action:"set_cache_settings",description:`${Er} ${tf}`,expression:`(${Tr(Y)})`,enabled:!0,action_parameters:z};await Ss(A,`cache rule for ${Y.join(", ")}`,async()=>{let ne=await A.cloudflare.getPhaseRules(Y[0],"http_request_cache_settings");if(Fd(ne,X))return;if(!A.dryRun){let re=await A.cloudflare.putManagedPhaseRules(Y[0],"http_request_cache_settings",[X]);if(!re.success)throw Error(re.message||"cache rule write failed")}_(`cache rule updated for ${Y.join(", ")}`,`update cache rule for ${Y.join(", ")}`)})}}function jd(D){if(D.edgeTtl===void 0&&D.browserTtl===void 0)return null;let t={cache:!0};if(D.edgeTtl!==void 0)t.edge_ttl={mode:"override_origin",default:D.edgeTtl};if(D.browserTtl!==void 0)t.browser_ttl={mode:"override_origin",default:D.browserTtl};return t}function Fd(D,t){let m=wr(t.expression),A=Ci(t.expression),_=(Y)=>(Y.description||"").startsWith(Er),z=(Y)=>Y.action===t.action&&Y.description===t.description&&(Y.enabled??!0)===(t.enabled??!0)&&Ci(Y.expression)===A&&na(Y.action_parameters)===na(t.action_parameters),U=!1;for(let Y of D){if(!_(Y))continue;let X=wr(Y.expression);if(!m.some((re)=>X.includes(re)))continue;if(z(Y)&&m.every((re)=>X.includes(re))&&!U){U=!0;continue}return!1}return U}function Bd(D){let t={id:D.id,enabled:D.enabled??!0,conditions:{prefix:D.prefix??""}};if(D.expireAfterDays!==void 0)t.deleteObjectsTransition={condition:{type:"Age",maxAge:Uc(D.expireAfterDays)}};else if(D.expireOn!==void 0)t.deleteObjectsTransition={condition:{type:"Date",date:D.expireOn}};if(D.abortMultipartUploadsAfterDays!==void 0)t.abortMultipartUploadsTransition={condition:{type:"Age",maxAge:Uc(D.abortMultipartUploadsAfterDays)}};if(D.infrequentAccessAfterDays!==void 0)t.storageClassTransitions=[{storageClass:"InfrequentAccess",condition:{type:"Age",maxAge:Uc(D.infrequentAccessAfterDays)}}];return t}function Uc(D){return Math.round(D*86400)}function sf(D){let t=new Set,m=[];for(let A of D??[]){let _=typeof A==="string"?{domain:A}:A,z=_.domain?.trim().replace(/\.$/,"").toLowerCase();if(!z||t.has(z))continue;t.add(z),m.push({domain:z,minTLS:_.minTLS})}return m}function Wc(D){if(!D.enabled)return"disabled";let t=D.status?.ownership,m=D.status?.ssl;if(t==="active"&&m==="active")return"active";for(let A of[t,m])if(A==="error"||A==="blocked"||A==="deactivated")return A;return"pending"}async function Ss(D,t,m,A){try{await m()}catch(_){if(_ instanceof Yi)throw _;D.warnings.push(`R2: ${t} failed: ${Gc(_)}`),A?.()}}function Gc(D){return D instanceof Error?D.message:String(D)}function na(D){if(Array.isArray(D))return`[${D.map(na).join(",")}]`;if(D&&typeof D==="object")return`{${Object.entries(D).filter(([,m])=>m!==void 0).sort(([m],[A])=>m.localeCompare(A)).map(([m,A])=>`${JSON.stringify(m)}:${na(A)}`).join(",")}}`;return JSON.stringify(D)}async function uf(D){let t=ta();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 lt(D).getCallerIdentity();if(A.Arn)e(`AWS identity: ${A.Arn}`)}catch(m){a(`Could not verify AWS identity: ${m.message}`)}}function mf(){try{return Yc("git rev-parse --short HEAD",{stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return Date.now().toString(36)}}async function Hc(D,t,m,A){let{ensureManagementDashboard:_}=await import("./chunk-ky4xdskt.js");_(D,{logger:{info:e,warn:a}});let z=D.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(([Le])=>Le).join(", ")}`),!1;let Y=U.filter(([Le,Re])=>{if(!Re)return!1;if(A&&Le!==A)return!1;if(P(Re)==="bucket")return a(`Site '${Le}' targets a bucket — skipping compute deploy (handled by the static-site path).`),!1;return qs(Re)});if(A)e(`Deploying only '${A}'. Other sites keep their current release; proxy routes are still regenerated in full.`);let X=D.project.slug,re=(D.infrastructure?.compute||{}).runtime||"bun",se=O({config:D}),fe=mf(),Ae=new Map,Pe=new Map,he={step:(Le)=>u(Le),error:(Le)=>r(Le)};if(!await vs(D,"beforeDeploy",he))return!1;if(!await vs(D,"beforeBuild",he))return!1;for(let[Le,Re]of Y){let Ee=P(Re);if(i(`Preparing site: ${Le}`),e(`Domain: ${Re.domain||"(none)"}`),e(`Source: ${Re.root}`),e(`Kind: ${Ee==="server-static"?"static (shipped to /var/www)":"app (systemd service)"}`),Ee==="server-app")e(`Start: ${Re.start}`),e(`Port: ${Re.port??"(unset)"}`);if(Re.build){u(`Running build: ${Re.build}`);try{Yc(Re.build,{stdio:"inherit",cwd:process.cwd()})}catch(et){return r(`Build failed for site '${Le}': ${et.message}`),!1}}let Te=Td({root:Re.root,siteName:Le,expectIndex:Ee==="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 Ke=Re.build?void 0:JSON.stringify([df(Re.root),[...Re.exclude||[]].sort()]),Be=Ke?Pe.get(Ke):void 0;if(Be){u(`Reusing package for ${Re.root} → ${Be}`),Ae.set(Le,Be);continue}let _e=Ud(zd(),`${X}-${Le}-${fe}.tar.gz`);u(`Packaging ${Re.root} → ${_e}`);try{let et=(Re.exclude||[]).map((Rt)=>`--exclude=${Rt}`);af("tar",["czf",_e,"-C",Re.root,...et,"."],{stdio:"inherit",env:{...process.env,COPYFILE_DISABLE:"1"}})}catch(et){return r(`Failed to package '${Le}': ${et.message}`),!1}if(Ae.set(Le,_e),Ke)Pe.set(Ke,_e)}if(!await vs(D,"afterBuild",he))return!1;let Ie=await ro({config:A?{...D,sites:Object.fromEntries(Y)}:D,rpxConfig:A?D:void 0,environment:t,driver:se,sha:fe,runtime:re,tarballForSite:(Le)=>{let Re=Ae.get(Le);if(!Re)throw Error(`Missing tarball for site '${Le}'`);return Re},logger:{info:(Le)=>e(Le),warn:(Le)=>a(Le),error:(Le)=>r(Le),step:(Le)=>u(Le),success:(Le)=>n(Le)}});if(Ie&&!await vs(D,"afterDeploy",he))return!1;return Ie}async function ra(D){let t=new Gr;if(u("Running pre-deployment security scan..."),D.exclude?.length)e(`Excluding from the scan: ${D.exclude.join(", ")}`);let m=await t.scan({directory:D.sourceDir,failOnSeverity:D.failOnSeverity||"critical",skipPatterns:D.skipPatterns,exclude:D.exclude});return{passed:m.passed,result:m}}function pf(){let D=Ud(zd(),`ts-cloud-empty-scan-${process.pid}`);return _d(D,{recursive:!0}),D}function ia(D){let{summary:t,findings:m,scannedFiles:A,duration:_}=D;if(e(`
|
|
376
376
|
Scanned ${A} files in ${_}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((re)=>re.pattern.severity==="critical"),U=m.filter((re)=>re.pattern.severity==="high"),Y=m.filter((re)=>re.pattern.severity==="medium"),X=m.filter((re)=>re.pattern.severity==="low"),ne=(re,se,fe)=>{if(re.length>0){console.log(`
|
|
378
378
|
${jt(`[${se}]`,fe)}`);for(let Ae of re.slice(0,10))e(` ${Ae.pattern.name}`),e(` File: ${Ae.file}:${Ae.line}`),e(` Match: ${Ae.match}`);if(re.length>10)e(` ... and ${re.length-10} more ${se.toLowerCase()} findings`)}};ne(z,"CRITICAL","red"),ne(U,"HIGH","yellow"),ne(Y,"MEDIUM","blue"),ne(X,"LOW","gray")}}function Vd(D,t){let m=Object.keys(D.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 Kc(D,t,m){let A=Vd(D,t),_=y(process.cwd(),D);try{let z=new xt(_.store);ei(_);let U=le(_,A);if(m)ac(z,U,m);else cc(z,U);let Y=ar(z,{scope:U,desiredConfigHash:_.project.desiredConfigHash});return e(`Security policy: ${Y.decision.policyId} v${Y.decision.policyVersion} — ${Y.decision.outcome.toUpperCase()}`),e(`Security decision: ${Y.decision.explanation}`),{allowed:Y.decision.outcome!=="block",explanation:Y.decision.explanation}}finally{_.store.close()}}async function ff(D,t){let m=Vd(D),A=y(process.cwd(),D);try{let _=new xt(A.store);ei(A);let z=le(A,m),U=t.imageSha256?`${t.imageRef}@sha256:${t.imageSha256.replace(/^sha256:/,"")}`:t.imageRef;u("Scanning the built image and producing release security artifacts...");let Y=await lc(_,{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: ${Y.scan.run.status} (${Y.scan.findings.length} findings)`),e(`Release SBOM: ${Y.sbomSource==="image"?"full image inventory":Y.sbomSource==="manifest"?"package-manifest fallback":"unavailable"}`);let X=ar(_,{scope:z,operationId:t.invocationId,desiredConfigHash:A.project.desiredConfigHash});return e(`Release policy: ${X.decision.policyId} v${X.decision.policyVersion} — ${X.decision.outcome.toUpperCase()}`),e(`Release decision: ${X.decision.explanation}`),{allowed:X.decision.outcome!=="block",explanation:X.decision.explanation}}finally{A.store.close()}}function gf(D){try{let t=y(process.cwd(),D),m=new Yt(t.store).current();if(!m.attackMode&&!m.mitigationPause&&m.ipRules.allow.length===0&&m.ipRules.block.length===0)return D;let A=Zo(D,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 D}}function Zc(D){D.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",_=t?.skipPatterns?.split(",").map((Y)=>Y.trim())||[];if(!vn(m)){r(`Source directory not found: ${m}`);return}e(`Source: ${m}`),e(`Fail on: ${A} or higher severity`);let{passed:z,result:U}=await ra({sourceDir:m,failOnSeverity:A,skipPatterns:_});if(ia(U),z)n(`
|
|
@@ -382,9 +382,9 @@ Recommendations:`),e(" 1. Remove any hardcoded credentials from your code"),e("
|
|
|
382
382
|
✗ Deployment configuration is invalid:`);for(let St of Y)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),Ft=t.stack||T(U,A),un=F(U),mn=U.project.region||"us-east-1",Sr=t.site?[t.site]:Object.keys(U.sites||{});i("Deployment Plan"),e(`Cloud provider: ${un}`),e(`Mode: ${St}`),e(`Stack: ${Ft}`),e(`Region: ${mn}`),e(`Environment: ${A}`),e(`Sites: ${Sr.length>0?Sr.join(", "):"none"}`),await Md(U,{dryRun:!0}),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 uf(St)}let ne;if(!t?.skipSecurityScan){let St=zc(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:Ft}=await ra({sourceDir:St?process.cwd():pf(),failOnSeverity:t?.securityFailOn||"critical",exclude:U.infrastructure?.security?.scan?.exclude});ia(Ft),ne=Ft}else a(`Security scan skipped (--skip-security-scan)
|
|
383
383
|
`);let re=Kc(U,A,ne);if(!re.allowed){r(`
|
|
384
384
|
✗ Deployment blocked by security policy: ${re.explanation}`),e("Remediate the findings or create an authorized, time-limited waiver in the Security posture center."),process.exitCode=1;return}if(n(`✓ Security policy allows this deployment
|
|
385
|
-
`),!await Md(U)){process.exitCode=1;return}if(H(U)==="serverless"){u("Serverless project detected: deploying the Lambda application");let{deployServerlessApp:St}=await import("./chunk-
|
|
385
|
+
`),!await Md(U)){process.exitCode=1;return}if(H(U)==="serverless"){u("Serverless project detected: deploying the Lambda application");let{deployServerlessApp:St}=await import("./chunk-aptdpa3m.js");await St(U,A,{}),n("Serverless deployment complete");return}let se=t?.stack||T(U,A),fe=U.project.region||"us-east-1",Ae=U.infrastructure?.deployStack!==!1,Pe=U.sites&&Object.keys(U.sites).length>0,he=U.infrastructure?.dns?.provider;if(Pe&&he){if(await wf(U,t?.site,he,fe,t?.skipDnsVerification,A,m),!Ae){if(U.infrastructure?.compute){let St=Object.values(U.sites).map((Ft)=>Ft.domain).filter((Ft)=>!!Ft);if(St.length>0)u("Syncing CloudFront dynamic HTTP methods for app domains..."),await Zs(St)}return}}if(!Ae){e("Infrastructure stack deployment disabled (infrastructure.deployStack: false)");return}let Ie=F(U);if(e(`Cloud provider: ${Ie}`),e(`Stack: ${se}`),e(`Region: ${fe}`),e(`Environment: ${A}`),(Ie==="hetzner"||U.infrastructure?.compute?.mode==="server")&&ko(U)){u(`Provisioning ${Ie} compute infrastructure...`);let St=O({config:U,provider:Ie});if(!St.provisionComputeInfrastructure){r(`${Ie} driver does not support compute provisioning`),process.exitCode=1;return}let Ft=await St.provisionComputeInfrastructure({config:U,environment:A});if(n(`${Ie} compute infrastructure ready`),Ft.appPublicIp)e(`App server: ${Ft.appPublicIp}`);if(Ft.appInstanceId)e(`Server ID: ${Ft.appInstanceId}`);if(!await Hc(U,A,fe,t?.site))r(`App deploy to ${Ie} compute reported a failure`),process.exitCode=1;else{if(n(`App deployed to ${Ie} compute`),t?.skipDnsVerification)e("DNS verification and record reconciliation skipped (--skip-dns-verification)");else await yf(U,Ft.appPublicIp,he,Ft.appPublicIpv6),await vf(U,he);let mn=await no({config:U,environment:A,driver:St,logger:{info:e,warn:a,error:r,step:u,success:n}});if(!mn)r("App deployed, but rpx TLS certificate reconciliation failed");if(!mn)process.exitCode=1;if(!t?.skipDnsVerification)await bf(U,Ft.appPublicIp,Ft.appPublicIpv6)}return}u("Generating CloudFormation template...");let Re=new xo({config:U,environment:A});Re.generate();let Ee=Re.toJSON(),Te=JSON.parse(Ee);if(Object.keys(Te.Resources||{}).length===0){e(`No infrastructure resources to deploy for environment '${A}' — skipping stack ${se}.`);return}u("Validating template...");let Be=Ko(Te),_e=Jo(Ee),et=Yo(Te),Rt=[...Be.errors,..._e.errors,...et.errors];if(Rt.length>0){r("Template validation failed:");for(let St of Rt)r(` - ${St.path}: ${St.message}`);return}let Et=[...Be.warnings,..._e.warnings,...et.warnings];if(Et.length>0)for(let St of Et)a(` - ${St.path}: ${St.message}`);n("Template validated successfully");let wt=Object.keys(Te.Resources).length;e(`
|
|
386
386
|
Resources to deploy: ${wt}`);let Pt={};for(let St of Object.values(Te.Resources)){let Ft=St.Type;Pt[Ft]=(Pt[Ft]||0)+1}for(let[St,Ft]of Object.entries(Pt).sort((un,mn)=>mn[1]-un[1]).slice(0,5))e(` - ${St}: ${Ft}`);if(!(m||await d(`
|
|
387
|
-
Deploy now?`,!0))){e("Deployment cancelled");return}let _t=new v(fe),nt=va(Te);if(nt.length>0){let{ensureFailoverReplicaBuckets:St}=await import("./chunk-
|
|
387
|
+
Deploy now?`,!0))){e("Deployment cancelled");return}let _t=new v(fe),nt=va(Te);if(nt.length>0){let{ensureFailoverReplicaBuckets:St}=await import("./chunk-5dqnj5n9.js");u("Ensuring failover replica buckets..."),await St(nt,(Ft)=>e(` ${Ft}`))}u("Checking stack status...");let zt=!1;try{let St=await _t.describeStacks({stackName:se});zt=St.Stacks&&St.Stacks.length>0}catch(St){zt=!1}if(zt){e("Stack exists, updating...");let St=new s("Updating CloudFormation stack...");St.start();try{await _t.updateStack({stackName:se,templateBody:Ee,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"),u("Waiting for stack update to complete..."),await _t.waitForStack(se,"stack-update-complete"),n("Stack updated successfully!")}catch(Ft){if(Ft.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 Ft}}else{e("Creating new stack...");let St=new s("Creating CloudFormation stack...");St.start(),await _t.createStack({stackName:se,templateBody:Ee,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"),u("Waiting for stack creation to complete..."),await _t.waitForStack(se,"stack-create-complete"),n("Stack created successfully!")}let Wt=await _t.getStackOutputs(se);if(ve(`Deployment Complete!
|
|
388
388
|
|
|
389
389
|
Stack: ${se}
|
|
390
390
|
Region: ${fe}
|
|
@@ -393,16 +393,16 @@ Resources: ${wt}
|
|
|
393
393
|
|
|
394
394
|
View in console:
|
|
395
395
|
https://console.aws.amazon.com/cloudformation/home?region=${fe}#/stacks/stackinfo?stackId=${encodeURIComponent(se)}`,"green"),Object.keys(Wt).length>0){e(`
|
|
396
|
-
Stack Outputs:`);for(let[St,Ft]of Object.entries(Wt))e(` - ${St}: ${Ft}`)}if(nt.length>0){let{grantFailoverReplicaAccess:St}=await import("./chunk-
|
|
396
|
+
Stack Outputs:`);for(let[St,Ft]of Object.entries(Wt))e(` - ${St}: ${Ft}`)}if(nt.length>0){let{grantFailoverReplicaAccess:St}=await import("./chunk-5dqnj5n9.js");u("Granting the distribution read access to its failover replicas..."),await St(nt,Wt,(Ft)=>e(` ${Ft}`))}if(U.infrastructure?.storage)for(let[St,Ft]of Object.entries(U.infrastructure.storage)){if(!Ft.website||!Ft.root)continue;let un=Wt[`${St}BucketName`]||Wt.FrontendBucketName,mn=Wt[`${St}CloudFrontDistributionId`];if(!un){a(`Could not find bucket name for storage '${St}' — skipping file upload`);continue}if(!vn(Ft.root)){a(`Source directory not found: ${Ft.root} — skipping file upload for '${St}'`);continue}u(`Uploading files from ${Ft.root} to s3://${un}...`);let{uploadStaticFiles:Sr,invalidateCache:Li}=await import("./chunk-c23r6gjb.js"),fn=await Sr({sourceDir:Ft.root,bucket:un,region:fe,onProgress:(Ht,wn,Cn)=>{if(Ht%10===0||Ht===wn)e(` ${Ht}/${wn}: ${Cn}`)}});if(fn.errors.length>0)a(`Upload completed with errors: ${fn.errors.join(", ")}`);else{let Ht=fn.skipped>0?`Uploaded ${fn.uploaded} files (${fn.skipped} unchanged)`:`Uploaded ${fn.uploaded} files`;n(Ht)}if(mn&&fn.uploaded>0){u("Invalidating CloudFront cache...");let{invalidationId:Ht}=await Li(mn);n(`Cache invalidation created: ${Ht}`)}}if(nt.length>0){let{seedFailoverReplicas:St}=await import("./chunk-5dqnj5n9.js");u("Syncing failover replicas..."),await St(nt,(Ft)=>e(` ${Ft}`))}if(U.infrastructure?.compute)await Hc(U,A,fe,t?.site);if(U.infrastructure?.compute&&U.sites){let St=Object.values(U.sites).map((Ft)=>Ft.domain).filter((Ft)=>!!Ft);if(St.length>0)u("Syncing CloudFront dynamic HTTP methods for app domains..."),await Zs(St)}}catch(z){if(r(`Deployment failed: ${z.message}`),process.exitCode=1,z.stack)e(`
|
|
397
397
|
Stack trace:`),console.error(z.stack)}finally{if(_)await _()}}),D.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 je(),A=t?.env||"production",_=m.project.region||"us-east-1",z=$r(m);if(z){r(z),process.exitCode=1;return}if(!ko(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: ${_}`),e(`Environment: ${A}`),!await Hc(m,A,_,t?.site)){process.exitCode=1;return}if(m.sites){let Y=Object.values(m.sites).map((X)=>X.domain).filter((X)=>!!X);if(Y.length>0)u("Syncing CloudFront dynamic HTTP methods for app domains..."),await Zs(Y)}n(`
|
|
398
|
-
Server deployment complete!`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),D.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 je(),A=t?.env||"production",_=$r(m);if(_){r(_),process.exitCode=1;return}let{deployServerlessApp:z,redeployServerlessApp:U}=await import("./chunk-
|
|
398
|
+
Server deployment complete!`)}catch(m){r(`Deployment failed: ${m.message}`),process.exitCode=1}}),D.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 je(),A=t?.env||"production",_=$r(m);if(_){r(_),process.exitCode=1;return}let{deployServerlessApp:z,redeployServerlessApp:U}=await import("./chunk-aptdpa3m.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}}),D.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 je(),A=t?.env||"production",{rollbackServerlessApp:_}=await import("./chunk-aptdpa3m.js");await _(m,A)}catch(m){r(`Rollback failed: ${m.message}`),process.exitCode=1}}),D.command("deploy:rollback [site]","Roll a compute site back to a previous release").option("--env <environment>","Environment (production, staging, development)").option("--to <release>","Release id to roll back to (default: the previous release)").option("--dry-run","Say which release it would roll back to, and change nothing").option("--all","Every configured site, instead of one").option("--only-if-live <release>","Only roll back a site whose live release is this commit (what a failed deploy switched)").action(async(t,m)=>{i("Rolling Back Release");try{let A=await je(),_=m?.env||"production",z=m?.all?Object.keys(A.sites||{}):[t||Object.keys(A.sites||{})[0]].filter(Boolean);if(z.length===0){r("No site configured to roll back."),process.exitCode=1;return}let{rollbackComputeSite:U}=await import("./chunk-gj26v5hg.js"),Y=O({config:A});for(let X of z){let ne=await U({driver:Y,slug:A.project.slug,environment:_,logger:f},{siteName:X,to:m?.to,dryRun:m?.dryRun,onlyIfLive:m?.onlyIfLive});for(let re of ne.perInstance||[])e(` ${X} ${re.instanceId}: ${re.output?.trim()||re.status}`);if(!ne.success)r(`Rollback of ${X} failed: ${ne.error||"unknown error"}`),process.exitCode=1}}catch(A){r(`Rollback failed: ${A.message}`),process.exitCode=1}}),D.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 je(),_=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-gj26v5hg.js"),Y=await U({driver:O({config:A}),slug:A.project.slug,environment:_,logger:f},{siteName:z,limit:Number.parseInt(m?.limit||"20",10)||20});if(!Y.success){r(`Could not read history: ${Y.error||"unknown error"}`),process.exitCode=1;return}for(let X of Y.perInstance||[])e(`
|
|
399
399
|
${X.instanceId}:`),e(X.output?.trimEnd()||"(no output)")}catch(A){r(`History lookup failed: ${A.message}`),process.exitCode=1}}),D.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(!vn(m)){r(`Recipe script not found: ${m}`),process.exitCode=1;return}let _=await je(),z=A?.env||"production",U=Jc(m,"utf8").split(`
|
|
400
|
-
`),{runComputeRecipe:Y}=await import("./chunk-
|
|
401
|
-
${ne.instanceId}:`),e(ne.output?.trimEnd()||"(no output)");if(!X.success)process.exitCode=1}catch(_){r(`Recipe failed: ${_.message}`),process.exitCode=1}}),D.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 je(),_=m?.env||"production",{restoreDatabaseBackup:z}=await import("./chunk-
|
|
402
|
-
(recorded as #${U} — re-run with \`cloud command:again ${U}\`)`)}catch(A){r(`Command failed: ${A.message}`),process.exitCode=1}}),D.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 je(),A=t?.env||"production",{listCommandHistory:_}=await import("./chunk-
|
|
403
|
-
(recorded as #${X.id})`)}catch(A){r(`Command failed: ${A.message}`),process.exitCode=1}}),D.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 je(),_=m?.env||"production",{runDbQuery:z}=await import("./chunk-
|
|
404
|
-
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),D.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 je().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",_=t?.arch||"x86_64",z=t?.node||"24",{buildNodeRuntimeLayerZip:U}=await import("./chunk-
|
|
405
|
-
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),D.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 je().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",_=t?.arch||"x86_64",z=t?.bun||"latest",{buildBunRuntimeLayerZip:U}=await import("./chunk-
|
|
400
|
+
`),{runComputeRecipe:Y}=await import("./chunk-gj26v5hg.js"),X=await Y({driver:O({config:_}),slug:_.project.slug,environment:z,logger:f},{name:t,script:U,user:A?.user});for(let ne of X.perInstance||[])e(`
|
|
401
|
+
${ne.instanceId}:`),e(ne.output?.trimEnd()||"(no output)");if(!X.success)process.exitCode=1}catch(_){r(`Recipe failed: ${_.message}`),process.exitCode=1}}),D.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 je(),_=m?.env||"production",{restoreDatabaseBackup:z}=await import("./chunk-gj26v5hg.js"),U=await z({driver:O({config:A}),slug:A.project.slug,environment:_,logger:f},{database:w(A),from:t});for(let Y of U.perInstance||[])e(` ${Y.instanceId}: ${Y.output?.trim()||Y.status}`);if(!U.success)r(`Restore failed: ${U.error||"unknown error"}`),process.exitCode=1}catch(A){r(`Restore failed: ${A.message}`),process.exitCode=1}}),D.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 je(),{buildQuickDeployCi:A,inferQuickDeployProvider:_}=await import("./chunk-eaet154r.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 X=Yc("git remote get-url origin",{stdio:["ignore","pipe","ignore"]}).toString().trim();U=_(X)}catch{}let Y=A(m,t?.env||"production",{provider:U,site:t?.site,skipDnsVerification:t?.skipDnsVerification,setup:vn("pantry.lock")?"pantry":"bun",sshPrivateKeySecret:t?.sshKeySecret});if(!Y){a("Could not resolve a GitHub, GitLab, or Bitbucket provider from --provider, the origin remote, or a configured site repository."),process.exitCode=1;return}if(vn(Y.path)&&!t?.force){a(`${Y.path} already exists — re-run with --force to overwrite.`);return}_d(lf(Y.path),{recursive:!0}),cf(Y.path,Y.content),n(`Wrote ${Y.path} (${Y.provider}, deploys on push to '${Y.branch}').`),e("Next: commit it and add your provider credentials as CI secrets/variables.")}catch(m){r(`Quick deploy setup failed: ${m.message}`),process.exitCode=1}}),D.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 je(),A=t?.env||"production",{setMaintenance:_}=await import("./chunk-aptdpa3m.js");await _(m,A,!0,t?.secret)}catch(m){r(`Failed to enable maintenance mode: ${m.message}`),process.exitCode=1}}),D.command("up","Bring the serverless app out of maintenance mode").option("--env <environment>","Environment (production, staging, development)").action(async(t)=>{try{let m=await je(),A=t?.env||"production",{setMaintenance:_}=await import("./chunk-aptdpa3m.js");await _(m,A,!1)}catch(m){r(`Failed to disable maintenance mode: ${m.message}`),process.exitCode=1}}),D.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 je(),_=m?.env||"production",{runAndRecordCommand:z}=await import("./chunk-aptdpa3m.js"),{id:U,output:Y}=await z(A,_,t);if(e(Y),U>0)e(`
|
|
402
|
+
(recorded as #${U} — re-run with \`cloud command:again ${U}\`)`)}catch(A){r(`Command failed: ${A.message}`),process.exitCode=1}}),D.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 je(),A=t?.env||"production",{listCommandHistory:_}=await import("./chunk-aptdpa3m.js"),z=await _(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((Y)=>[String(Y.id),Y.timestamp,Y.status,Y.command]))}catch(m){r(`Failed to read history: ${m.message}`),process.exitCode=1}}),D.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 je(),_=m?.env||"production",{getCommandRecord:z,runAndRecordCommand:U}=await import("./chunk-aptdpa3m.js"),Y=await z(A,_,t?Number(t):void 0);if(!Y){r(t?`No command #${t} in history.`:"No command history to re-run."),process.exitCode=1;return}e(`Re-running #${Y.id}: ${Y.command}`);let X=await U(A,_,Y.command);if(e(X.output),X.id>0)e(`
|
|
403
|
+
(recorded as #${X.id})`)}catch(A){r(`Command failed: ${A.message}`),process.exitCode=1}}),D.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 je(),_=m?.env||"production",{runDbQuery:z}=await import("./chunk-aptdpa3m.js"),U=await z(A,_,t);e(U)}catch(A){r(`Query failed: ${A.message}`),process.exitCode=1}}),D.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 _=await je(),z=A?.env||"production",U=Number(t),Y=Number(m);if(!(U>0)||!(Y>=U)){r("Provide valid capacities: min > 0 and max >= min (e.g. `serverless:db-scale 0.5 8`)."),process.exitCode=1;return}let{scaleServerlessDatabase:X}=await import("./chunk-aptdpa3m.js");await X(_,z,U,Y)}catch(_){r(`Scale failed: ${_.message}`),process.exitCode=1}}),D.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 je(),A=t?.env||"production",_;if(t?.to){if(_=new Date(t.to),Number.isNaN(_.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-aptdpa3m.js");await z(m,A,{toTime:_,latest:t?.latest,target:t?.target})}catch(m){r(`Restore failed: ${m.message}`),process.exitCode=1}}),D.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 je(),A=t?.env||"production",{serverlessInfo:_}=await import("./chunk-aptdpa3m.js"),z=await _(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}}),D.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 _=await je(),z=t?.env||"production";u("Gathering live data from AWS");let U=null;if(_.environments?.[z]?.app){let{resolveDashboardData:re}=await import("./chunk-6kt12934.js"),se=await re(_,z).catch((fe)=>(a(`Serverless data unavailable (${fe.message}) — that view uses sample data.`),null));if(se)U={...U??{},...se}}if(_.infrastructure?.compute){let{resolveServerDashboardData:re}=await import("./chunk-2tvtr5fy.js"),se=await re(_,z).catch(()=>null);if(se){if(se._serverReachable===!1)a("Server box not reachable over SSM — server view uses config + sample data.");U={...U??{},...se}}}u("Building dashboard");let{encodeDashboardPayload:Y}=await import("./chunk-jrztbsnb.js"),{execSync:X}=await import("node:child_process"),{rmSync:ne}=await import("node:fs");for(let re of[".stx/ssg-cache",".stx/cache"])ne(`${A}/${re}`,{recursive:!0,force:!0});X("bun run build",{cwd:A,stdio:"inherit",env:{...process.env,...U?{TSCLOUD_DASHBOARD_DATA:Y(U)}:{}}}),n(`Dashboard built → ${A}/dist${U?" (live data)":" (sample data)"}`)}catch(m){r(`Dashboard build failed: ${m.message}`),process.exitCode=1}}),D.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 je().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",_=t?.arch||"x86_64",z=t?.php||"8.3",U=`${t?.name||"tscloud-php"}-${z.replace(".","")}-${_}`,Y=t?.bucket||`${m?.project.slug||"tscloud"}-layers`,{buildPhpRuntimeLayerZip:X}=await import("./chunk-kzgt05w5.js"),{S3Client:ne}=await import("./chunk-k9ate69x.js"),{LambdaClient:re}=await import("./chunk-5khcwxhn.js"),se=X({architecture:_,phpVersion:z,onStep:(Ie)=>u(Ie)});e(`Layer: ${se.fileCount} files, ${(se.zip.length/1024/1024).toFixed(1)} MB`);let fe=new ne(A);if(!await fe.bucketExists(Y))u(`Creating layer bucket ${Y}`),await fe.createBucket(Y);let Ae=`layers/${U}.zip`;u("Uploading layer zip"),await fe.putObject({bucket:Y,key:Ae,body:se.zip,contentType:"application/zip"}),u("Publishing layer version");let he=await new re(A).publishLayerVersion({LayerName:U,Description:`ts-cloud PHP ${z} runtime (${_})`,Content:{S3Bucket:Y,S3Key:Ae},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[_]});ve(["PHP runtime layer published","",`ARN: ${he.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'php', layers: ['${he.LayerVersionArn}'] }`,"or set TSCLOUD_PHP_LAYER_ARN before deploying."].join(`
|
|
404
|
+
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),D.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 je().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",_=t?.arch||"x86_64",z=t?.node||"24",{buildNodeRuntimeLayerZip:U}=await import("./chunk-kzgt05w5.js"),{S3Client:Y}=await import("./chunk-k9ate69x.js"),{LambdaClient:X}=await import("./chunk-5khcwxhn.js"),ne=U({architecture:_,version:z,onStep:(Ie)=>u(Ie)}),re=`${t?.name||"tscloud-node"}-${ne.version.split(".")[0]}-${_}`,se=t?.bucket||`${m?.project.slug||"tscloud"}-layers`;e(`Layer: Node ${ne.version}, ${(ne.zip.length/1024/1024).toFixed(1)} MB`);let fe=new Y(A);if(!await fe.bucketExists(se))u(`Creating layer bucket ${se}`),await fe.createBucket(se);let Ae=`layers/${re}.zip`;u("Uploading layer zip"),await fe.putObject({bucket:se,key:Ae,body:ne.zip,contentType:"application/zip"}),u("Publishing layer version");let he=await new X(A).publishLayerVersion({LayerName:re,Description:`ts-cloud Node ${ne.version} runtime (${_})`,Content:{S3Bucket:se,S3Key:Ae},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[_]});ve(["Node runtime layer published","",`ARN: ${he.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'node', runtimeVersion: '${ne.version.split(".")[0]}', layers: ['${he.LayerVersionArn}'] }`,"or set TSCLOUD_NODE_LAYER_ARN before deploying."].join(`
|
|
405
|
+
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),D.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 je().catch(()=>null),A=t?.region||m?.project.region||"us-east-1",_=t?.arch||"x86_64",z=t?.bun||"latest",{buildBunRuntimeLayerZip:U}=await import("./chunk-kzgt05w5.js"),{S3Client:Y}=await import("./chunk-k9ate69x.js"),{LambdaClient:X}=await import("./chunk-5khcwxhn.js"),ne=U({architecture:_,version:z,onStep:(Ie)=>u(Ie)}),re=`${t?.name||"tscloud-bun"}-${ne.version.replace(/\./g,"")}-${_}`,se=t?.bucket||`${m?.project.slug||"tscloud"}-layers`;e(`Layer: Bun ${ne.version}, ${(ne.zip.length/1024/1024).toFixed(1)} MB`);let fe=new Y(A);if(!await fe.bucketExists(se))u(`Creating layer bucket ${se}`),await fe.createBucket(se);let Ae=`layers/${re}.zip`;u("Uploading layer zip"),await fe.putObject({bucket:se,key:Ae,body:ne.zip,contentType:"application/zip"}),u("Publishing layer version");let he=await new X(A).publishLayerVersion({LayerName:re,Description:`ts-cloud Bun ${ne.version} runtime (${_})`,Content:{S3Bucket:se,S3Key:Ae},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[_]});ve(["Bun runtime layer published","",`ARN: ${he.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'bun', layers: ['${he.LayerVersionArn}'] }`,"or set TSCLOUD_BUN_LAYER_ARN before deploying."].join(`
|
|
406
406
|
`),"green")}catch(m){r(`Layer build failed: ${m.message}`),process.exitCode=1}}),D.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 je(),A=t?.env||"production",_=t?.stack||T(m,A),z=m.project.region||"us-east-1";e(`Stack: ${_}`),e(`Region: ${z}`);let U=new s("Checking deployment status...");U.start();let X=await new v(z).describeStacks({stackName:_});if(X.Stacks.length===0){U.fail("Stack not found"),Rr("No deployment found for this environment");return}let ne=X.Stacks[0];if(U.succeed("Status retrieved"),e(`
|
|
407
407
|
Status: ${ne.StackStatus}`),e(`Created: ${ne.CreationTime}`),ne.LastUpdatedTime)e(`Last Updated: ${ne.LastUpdatedTime}`);if(ne.Outputs&&ne.Outputs.length>0){e(`
|
|
408
408
|
Outputs:`);for(let re of ne.Outputs)e(` ${re.OutputKey}: ${re.OutputValue}`)}}catch(m){r(`Failed to get status: ${m.message}`)}}),D.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 je(),A=t?.env||"production",_=t?.stack||T(m,A),z=m.project.region||"us-east-1";if(e(`Stack: ${_}`),e(`Region: ${z}`),!await d(`
|
|
@@ -457,11 +457,11 @@ Resources (${X.StackResourceSummaries.length}):`);let ne=["Logical ID","Type","S
|
|
|
457
457
|
... and ${X.StackResourceSummaries.length-10} more resources`)}}catch(m){r(`Failed to describe stack: ${m.message}`)}}),D.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 _=(await je()).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(_),Y=new s("Deleting stack...");Y.start(),await U.deleteStack(t),Y.succeed("Stack deletion initiated"),u("Waiting for stack deletion..."),await U.waitForStack(t,"stack-delete-complete"),n("Stack deleted successfully!")}catch(A){r(`Failed to delete stack: ${A.message}`)}}),D.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 _=(await je()).project.region||"us-east-1",z=new v(_),U=new s("Loading events...");U.start();let Y=await z.describeStackEvents(t);U.succeed(`Found ${Y.StackEvents.length} events`);let X=m?.limit?Number.parseInt(m.limit):20,ne=Y.StackEvents.slice(0,X);if(ne.length===0){e("No events found");return}let re=["Time","Resource","Status","Reason"],se=ne.map((fe)=>[new Date(fe.Timestamp).toLocaleString(),fe.LogicalResourceId,fe.ResourceStatus,fe.ResourceStatusReason||""]);c(re,se)}catch(A){r(`Failed to load events: ${A.message}`)}}),D.command("stack:outputs STACK_NAME","Show stack outputs").action(async(t)=>{i(`Stack Outputs: ${t}`);try{let A=(await je()).project.region||"us-east-1",_=new v(A),z=new s("Loading stack outputs...");z.start();let U=await _.describeStacks({stackName:t});if(!U.Stacks||U.Stacks.length===0){z.fail("Stack not found");return}let Y=U.Stacks[0];if(z.succeed("Stack outputs loaded"),!Y.Outputs||Y.Outputs.length===0){e("No outputs found for this stack");return}let X=["Key","Value","Description","Export Name"],ne=Y.Outputs.map((re)=>[re.OutputKey||"",re.OutputValue||"",re.Description||"",re.ExportName||""]);c(X,ne),e(`
|
|
459
459
|
Copy-friendly format:`);for(let re of Y.Outputs)e(`${re.OutputKey}=${re.OutputValue}`)}catch(m){r(`Failed to load outputs: ${m.message}`)}}),D.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 _=(await je()).project.region||"us-east-1",z=new v(_),U=new s("Fetching stack template...");U.start();let Y=await z.getTemplate(t);if(!Y.TemplateBody){U.fail("Template not found");return}U.succeed("Template fetched");let X=m?.format||"json",ne=Y.TemplateBody;if(X==="json"){let re=JSON.parse(ne);ne=JSON.stringify(re,null,2)}if(m?.output){let re=m.output;Gf(re,ne,"utf-8"),n(`Template exported to: ${re}`);let fe=(qf(re).size/1024).toFixed(2);e(`File size: ${fe} KB`)}else e(`
|
|
460
|
-
Template:`),console.log(ne)}catch(A){r(`Failed to export template: ${A.message}`)}})}import{existsSync as Hf}from"node:fs";function rl(D,t){D.command("upgrade","Upgrade CLI to latest version").action(async()=>{En("upgrade",{message:`Self-update is disabled for this ${t} build because no signed update-manifest source is configured.`,nextAction:"Install a verified release with your package manager or configure platform maintenance updates."})}),D.command("doctor","Check system requirements and AWS credentials").action(async()=>{if(i("System Diagnostics"),u("Checking Bun..."),n(`Bun ${process.versions.bun}`),u("Checking AWS CLI..."),await Ms())n("AWS CLI is installed");else r("AWS CLI is not installed"),e("Install: https://aws.amazon.com/cli/");if(u("Checking AWS credentials..."),await Us()){n("AWS credentials are configured");let z=await Bs();if(z)e(`Account ID: ${z}`)}else r("AWS credentials are not configured"),e("Run: aws configure");u("Checking CloudFront list access...");let _=!1;try{let{CloudFrontClient:z}=await import("./chunk-
|
|
460
|
+
Template:`),console.log(ne)}catch(A){r(`Failed to export template: ${A.message}`)}})}import{existsSync as Hf}from"node:fs";function rl(D,t){D.command("upgrade","Upgrade CLI to latest version").action(async()=>{En("upgrade",{message:`Self-update is disabled for this ${t} build because no signed update-manifest source is configured.`,nextAction:"Install a verified release with your package manager or configure platform maintenance updates."})}),D.command("doctor","Check system requirements and AWS credentials").action(async()=>{if(i("System Diagnostics"),u("Checking Bun..."),n(`Bun ${process.versions.bun}`),u("Checking AWS CLI..."),await Ms())n("AWS CLI is installed");else r("AWS CLI is not installed"),e("Install: https://aws.amazon.com/cli/");if(u("Checking AWS credentials..."),await Us()){n("AWS credentials are configured");let z=await Bs();if(z)e(`Account ID: ${z}`)}else r("AWS credentials are not configured"),e("Run: aws configure");u("Checking CloudFront list access...");let _=!1;try{let{CloudFrontClient:z}=await import("./chunk-s64d5nw2.js");await new z().listDistributions(),n("CloudFront list access is enabled"),_=!0}catch(z){a(`CloudFront list check failed: ${z.message}`)}if(_){u("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)")}}u("Checking S3 access...");try{let{S3Client:z}=await import("./chunk-k9ate69x.js");await new z("us-east-1").listBuckets(),n("S3 access is enabled")}catch(z){a(`S3 check failed: ${z.message}`)}u("Checking ACM (SSL certificates) access...");try{await new W("us-east-1").listCertificates(),n("ACM access is enabled")}catch(z){a(`ACM check failed: ${z.message}`)}if(u("Checking configuration..."),Hf("cloud.config.ts"))n("cloud.config.ts found");else a("cloud.config.ts not found"),e("Run: cloud init")}),D.command("regions","List available AWS regions").action(async()=>{i("AWS Regions");let m=new s("Fetching regions...");m.start();let A=await Fs();m.stop(),A.forEach((_)=>{console.log(` ${_}`)})}),D.command("version","Show the version of the CLI").alias("v").action(()=>{console.log(`ts-cloud v${t}`)})}function il(D){D.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 s("Fetching sites...");A.start();let _=await m.scan({TableName:t.table,FilterExpression:"begins_with(pk, :pk)",ExpressionAttributeValues:{":pk":{S:"SITE#"}}});if(A.succeed(`Found ${_.Items?.length||0} site(s)`),!_.Items||_.Items.length===0){e("No analytics sites found"),e("Use `cloud analytics:sites:create` to create a new site");return}let z=_.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)}}),D.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 R("Site name","My Site"),_=[];if(t.domain)_=Array.isArray(t.domain)?t.domain:[t.domain];else _=(await R("Site domains (comma-separated)","example.com")).split(",").map((re)=>re.trim()).filter(Boolean);if(e(`
|
|
461
461
|
Creating site: ${A}`),e(`Domains: ${_.join(", ")}`),!await d(`
|
|
462
462
|
Create this site?`,!0)){e("Operation cancelled");return}let U=new s("Creating site...");U.start();let Y=`site_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,X=new Date().toISOString();await m.putItem({TableName:t.table,Item:{pk:{S:`SITE#${Y}`},sk:{S:`SITE#${Y}`},siteId:{S:Y},name:{S:A},domains:{L:_.map((ne)=>({S:ne}))},isActive:{BOOL:!0},createdAt:{S:X},updatedAt:{S:X}}}),U.succeed("Site created successfully"),n(`
|
|
463
463
|
Site ID: ${Y}`),e(`
|
|
464
|
-
Add the tracking script to your website:`),e(` <script src="https://analytics.stacksjs.com/track.js" data-site-id="${Y}"></script>`)}catch(m){r(`Failed to create site: ${m.message}`),process.exit(1)}}),D.command("analytics:sites:update <siteId>","Update an analytics site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--name <name>","New site name").option("--domain <domain>","Set domains (replaces existing)").option("--active <boolean>","Set site active/inactive status").action(async(t,m)=>{i("Update Analytics Site");try{let A=new
|
|
464
|
+
Add the tracking script to your website:`),e(` <script src="https://analytics.stacksjs.com/track.js" data-site-id="${Y}"></script>`)}catch(m){r(`Failed to create site: ${m.message}`),process.exit(1)}}),D.command("analytics:sites:update <siteId>","Update an analytics site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--name <name>","New site name").option("--domain <domain>","Set domains (replaces existing)").option("--active <boolean>","Set site active/inactive status").action(async(t,m)=>{i("Update Analytics Site");try{let A=new q(m.region),_=await A.getItem({TableName:m.table,Key:{pk:{S:`SITE#${t}`},sk:{S:`SITE#${t}`}}});if(!_.Item)r(`Site not found: ${t}`),process.exit(1);let z=q.unmarshal(_.Item);e(`Updating site: ${z.name||"Unnamed"} (${t})`),e("");let U=[],Y={},X={};if(m.name)U.push("#n = :name"),Y["#n"]="name",X[":name"]={S:m.name},e(` Name: ${z.name} -> ${m.name}`);if(m.domain!==void 0){let ne=Array.isArray(m.domain)?m.domain:[m.domain];U.push("domains = :domains"),X[":domains"]={L:ne.map((re)=>({S:re}))},e(` Domains: ${JSON.stringify(z.domains||[])} -> ${JSON.stringify(ne)}`)}if(m.active!==void 0){let ne=m.active==="true"||m.active==="1";U.push("isActive = :active"),X[":active"]={BOOL:ne},e(` Active: ${z.isActive} -> ${ne}`)}if(U.length===0){a("No updates specified. Use --name, --domain, or --active options.");return}U.push("updatedAt = :updatedAt"),X[":updatedAt"]={S:new Date().toISOString()},await A.updateItem({TableName:m.table,Key:{pk:{S:`SITE#${t}`},sk:{S:`SITE#${t}`}},UpdateExpression:`SET ${U.join(", ")}`,ExpressionAttributeNames:Object.keys(Y).length>0?Y:void 0,ExpressionAttributeValues:X}),e(""),n("Site updated successfully")}catch(A){r(`Failed to update site: ${A.message}`),process.exit(1)}}),D.command("analytics:query <siteId>","Query analytics data for a site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--limit <n>","Max items to return",{default:"10"}).option("--type <type>","Filter by type (pageview, event, etc.)").action(async(t,m)=>{i(`Analytics Data: ${t}`);try{let A=new q(m.region),_=new s("Querying data...");_.start();let z=await A.query({TableName:m.table,KeyConditionExpression:"pk = :pk",ExpressionAttributeValues:{":pk":{S:`SITE#${t}`}},ScanIndexForward:!1,Limit:parseInt(m.limit,10)});if(_.succeed(`Found ${z.Items?.length||0} items`),!z.Items||z.Items.length===0){e("No data found for this site");return}let U=z.Items.map((Y)=>q.unmarshal(Y));for(let Y of U){if(e(""),e(`SK: ${Y.sk}`),Y.path)e(` Path: ${Y.path}`);if(Y.timestamp)e(` Time: ${Y.timestamp}`);if(Y.visitorId)e(` Visitor: ${Y.visitorId}`);if(Y.browser)e(` Browser: ${Y.browser}`);if(Y.country)e(` Country: ${Y.country}`)}}catch(A){r(`Failed to query data: ${A.message}`),process.exit(1)}}),D.command("analytics:realtime <siteId>","Check realtime visitors for a site").option("--table <name>","DynamoDB table name",{default:"ts-analytics"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--minutes <n>","Minutes to look back",{default:"5"}).action(async(t,m)=>{i(`Realtime: ${t}`);try{let A=new q(m.region),_=new s("Checking realtime...");_.start();let z=parseInt(m.minutes,10),U=new Date(Date.now()-z*60*1000),Y=await A.query({TableName:m.table,KeyConditionExpression:"pk = :pk AND sk BETWEEN :start AND :end",ExpressionAttributeValues:{":pk":{S:`SITE#${t}`},":start":{S:`PAGEVIEW#${U.toISOString()}`},":end":{S:"PAGEVIEW#Z"}},ScanIndexForward:!1});if(_.succeed(`Found ${Y.Items?.length||0} pageviews in last ${z} minutes`),!Y.Items||Y.Items.length===0){a("No recent pageviews found"),e(""),e("Possible issues:"),e(" 1. Tracking script not installed correctly"),e(" 2. Site ID mismatch"),e(" 3. CORS or network issues");return}let X=Y.Items.map((se)=>q.unmarshal(se)),ne=new Set(X.map((se)=>se.visitorId)).size;e(""),n(`${ne} unique visitor(s) online`),e("");let re={};for(let se of X){let fe=se.path||"/";re[fe]=(re[fe]||0)+1}e("Active pages:");for(let[se,fe]of Object.entries(re).sort((Ae,Pe)=>Pe[1]-Ae[1]).slice(0,5))e(` ${se}: ${fe} view(s)`);e(""),e("Recent pageviews:");for(let se of X.slice(0,5))e(` ${se.timestamp} - ${se.path} (${se.browser||"Unknown"})`)}catch(A){r(`Failed to check realtime: ${A.message}`),process.exit(1)}})}import{chmodSync as Kf,mkdirSync as Jf,readFileSync as Yf,writeFileSync as Zf}from"node:fs";import{dirname as Qf,resolve as ma}from"node:path";function Jt(D){return new Or(D?{path:ma(D)}:{})}function Xf(D){let t=D.health();i("Control-plane diagnostics"),e(`Database: ${t.path}`),e(`Schema: ${t.schemaVersion}/${t.supportedSchemaVersion}`),e(`Integrity: ${t.integrity}`),e(`Journal: ${t.journalMode}`),e(`Size: ${t.databaseBytes.toLocaleString()} bytes`),e(`Last backup: ${t.lastBackupAt??"never"}`),e(`Operations: ${Object.entries(t.operations).map(([m,A])=>`${m}=${A}`).join(", ")}`),e(`Pending/retryable: ${t.pendingRetryableOperations}`)}function Ei(D,t){let m=D.getOrganization(t)??D.getOrganizationBySlug(t);if(!m)throw Error(`Organization '${t}' was not found.`);return m}function oa(D,t){let m=D??"organization";if(m==="organization")return{type:"organization"};if(!["project","environment","resource"].includes(m)||!t)throw Error("Scoped access requires --scope project|environment|resource and --scope-id <id>.");return{type:m,id:t}}function aa(D,t){let m=t.includes("@")?D.getIdentityByEmail(t):D.getIdentityByUsername(t);if(!m)throw Error(`Authentication identity '${t}' was not found.`);return m}function ca(D,t){return new Oe(D,{encryptionKey:x(ma(t??process.cwd()))})}function eu(D,t="TS_CLOUD_API_TOKEN"){let m=process.env[t]?.trim();if(!m)throw Error(`Set ${t} to a one-time-issued API token.`);return new Gi({baseUrl:D??process.env.TS_CLOUD_API_URL??"http://127.0.0.1:7676",token:m})}function ol(D){D.command("dashboard:serve","Run the local Forge-style cloud management UI").option("--host <host>","Host to bind",{default:"127.0.0.1"}).option("--port <port>","Port to bind",{default:"7676"}).option("--env <environment>","Environment to manage").option("--box","Box mode: run on the provisioned server (operate on localhost)").option("--open","Print the URL for opening in a browser").option("--verbose","Print server errors").action(async(t)=>{let m=await cr({host:t?.host,port:Number(t?.port??7676),environment:t?.env,box:t?.box,verbose:t?.verbose});i("ts-cloud Local Dashboard"),n(`Serving ${m.url}`),e("Use Ctrl+C to stop."),await new Promise((A)=>{let _=()=>{m.server.stop(!0),A()};process.once("SIGINT",_),process.once("SIGTERM",_)})}),D.command("control-plane:status","Inspect local control-plane storage health").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{Xf(m)}finally{m.close()}}),D.command("control-plane:backup","Create a consistent local control-plane backup").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{let A=m.createBackup("cli");n(`Backup written to ${A}`)}finally{m.close()}}),D.command("control-plane:export <file>","Export portable control-plane metadata and history").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let A=ma(t),_=Jt(m?.path);try{Jf(Qf(A),{recursive:!0}),Zf(A,`${JSON.stringify(_.exportSnapshot(),null,2)}
|
|
465
465
|
`,{mode:384}),Kf(A,384),n(`Control-plane snapshot exported to ${A}`)}finally{_.close()}}),D.command("control-plane:import <file>","Import a portable control-plane snapshot").option("--path <path>","Use a non-default control-plane database").option("--replace","Replace existing control-plane records").action((t,m)=>{let A=ma(t),_=JSON.parse(Yf(A,"utf8")),z=Jt(m?.path);try{let U=m?.replace?z.createBackup("pre-import"):void 0;if(z.importSnapshot(_,{replace:m?.replace}),n(`Imported control-plane snapshot from ${A}`),U)e(`Previous state backed up to ${U}`)}finally{z.close()}}),D.command("control-plane:compact","Apply history retention and compact control-plane storage").option("--path <path>","Use a non-default control-plane database").option("--event-days <days>","Retain event history for this many days",{default:"90"}).option("--operation-days <days>","Retain terminal operations for this many days",{default:"365"}).option("--no-vacuum","Delete expired records without reclaiming file space").action((t)=>{let m=Jt(t?.path);try{let A=m.compact({eventRetentionDays:Number(t?.eventDays??90),operationRetentionDays:Number(t?.operationDays??365),vacuum:t?.vacuum!==!1});if(n(`Removed ${A.deletedEvents} event(s) and ${A.deletedOperations} terminal operation(s).`),A.vacuumed)e("Database file compacted.")}finally{m.close()}}),D.command("organization:list","List organizations in the local control plane").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{i("Organizations");for(let A of m.listOrganizations())e(`${A.slug} ${A.name} ${A.id}`)}finally{m.close()}}),D.command("organization:members <organization>","List memberships and scoped grants").option("--path <path>","Use a non-default control-plane database").option("--all","Include revoked memberships").action((t,m)=>{let A=Jt(m?.path);try{let _=Ei(A,t);i(`${_.name} members`);for(let z of A.listMemberships(_.id,{includeRevoked:m?.all})){let U=A.getActor(z.actorId);e(`${U?.displayName??z.actorId} ${z.roleTemplate} ${z.scope.type}:${z.scope.id??_.slug} ${z.status} ${z.id}`);for(let Y of A.listGrants(z.id))e(` ${Y.effect} ${Y.capability} @ ${Y.scope.type}:${Y.scope.id??_.slug}`)}}finally{A.close()}}),D.command("organization:invite <organization> <email>","Create a hashed, expiring organization invitation").option("--path <path>","Use a non-default control-plane database").option("--role <role>","owner|admin|deployer|operator|viewer|auditor",{default:"viewer"}).option("--scope <scope>","organization|project|environment|resource",{default:"organization"}).option("--scope-id <id>","Project, environment, or resource ID").option("--days <days>","Invitation lifetime in days",{default:"7"}).option("--base-url <url>","Dashboard URL used to print the acceptance link",{default:"http://127.0.0.1:7676"}).action((t,m,A)=>{let _=Jt(A?.path);try{let z=Ei(_,t);if(!["owner","admin","deployer","operator","viewer","auditor"].includes(A?.role??"viewer"))throw Error("Unknown role template.");let U=_.createInvitation({organizationId:z.id,email:m,roleTemplate:A?.role??"viewer",scope:oa(A?.scope,A?.scopeId),expiresInMs:Number(A?.days??7)*86400000});n(`Invitation created for ${U.invitation.email}; expires ${U.invitation.expiresAt}`),e(`${String(A?.baseUrl??"http://127.0.0.1:7676").replace(/\/$/,"")}/accept-invitation?token=${encodeURIComponent(U.token)}`),e("This token is shown once. Resending revokes it and creates a new token.")}finally{_.close()}}),D.command("organization:invitations <organization>","List invitation states without exposing tokens").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let A=Jt(m?.path);try{let _=Ei(A,t);i(`${_.name} invitations`);for(let z of A.listInvitations(_.id))e(`${z.email} ${z.roleTemplate} ${z.scope.type}:${z.scope.id??_.slug} ${z.state} ${z.id}`)}finally{A.close()}}),D.command("organization:revoke-invitation <id>","Revoke a pending organization invitation").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let A=Jt(m?.path);try{let _=A.revokeInvitation(t);n(`Invitation ${_.id} is ${_.state}.`)}finally{A.close()}}),D.command("organization:grant <organization> <membership> <capability>","Add an explicit scoped allow or deny").option("--path <path>","Use a non-default control-plane database").option("--effect <effect>","allow|deny",{default:"allow"}).option("--scope <scope>","organization|project|environment|resource",{default:"organization"}).option("--scope-id <id>","Project, environment, or resource ID").action((t,m,A,_)=>{let z=Jt(_?.path);try{let U=Ei(z,t);if(!ae.includes(A))throw Error(`Unknown capability '${A}'.`);if(_?.effect!==void 0&&!["allow","deny"].includes(_.effect))throw Error("Effect must be allow or deny.");let Y=z.upsertGrant({organizationId:U.id,membershipId:m,effect:_?.effect??"allow",capability:A,scope:oa(_?.scope,_?.scopeId)});n(`${Y.effect} ${Y.capability} added at ${Y.scope.type}:${Y.scope.id??U.slug}.`)}finally{z.close()}}),D.command("organization:revoke-member <membership>","Revoke a membership and invalidate its sessions").option("--path <path>","Use a non-default control-plane database").option("--confirm <id>","Type the membership ID to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke this membership.`);let A=Jt(m?.path);try{let _=A.revokeMembership(t);n(`Membership ${_.id} revoked; session version is now ${_.sessionVersion}.`)}finally{A.close()}}),D.command("auth:identities","List local authentication identities without credential material").option("--path <path>","Use a non-default control-plane database").action((t)=>{let m=Jt(t?.path);try{let A=new Oe(m);i("Authentication identities");for(let _ of A.listIdentities())e(`${_.username} ${_.email??"no-email"} ${_.disabledAt?"disabled":"active"} ${_.id}`)}finally{m.close()}}),D.command("auth:reset-link <identity>","Create a one-time password reset link for offline recovery").option("--path <path>","Use a non-default control-plane database").option("--base-url <url>","Dashboard URL used to print the reset link",{default:"http://127.0.0.1:7676"}).action((t,m)=>{let A=Jt(m?.path);try{let _=new Oe(A),z=aa(_,t);_.revokeActionTokens(z.id,"password_reset");let U=_.createActionToken(z.id,"password_reset");n(`Created a one-time reset link for ${z.username}; it expires ${U.actionToken.expiresAt}.`),e(`${String(m?.baseUrl??"http://127.0.0.1:7676").replace(/\/$/,"")}/reset-password?token=${encodeURIComponent(U.token)}`)}finally{A.close()}}),D.command("auth:sessions <identity>","List active and expired sessions for an identity").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let A=Jt(m?.path);try{let _=new Oe(A),z=aa(_,t);i(`${z.username} sessions`);for(let U of _.listSessions(z.id,{includeInactive:!0}))e(`${U.state} ${U.authMethod} ${U.userAgent??"unknown-device"} ${U.lastUsedAt} ${U.id}`)}finally{A.close()}}),D.command("auth:revoke-sessions <identity>","Revoke every dashboard session for an identity").option("--path <path>","Use a non-default control-plane database").option("--confirm <identity>","Type the username or email to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke these sessions.`);let A=Jt(m?.path);try{let _=new Oe(A),z=aa(_,t),U=_.listSessions(z.id);for(let Y of U)_.revokeSession(z.id,Y.id);n(`Revoked ${U.length} session(s) for ${z.username}.`)}finally{A.close()}}),D.command("auth:disable-mfa <identity>","Disable MFA through the offline administrative recovery path").option("--path <path>","Use a non-default control-plane database").option("--confirm <identity>","Type the username or email to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to disable MFA.`);let A=Jt(m?.path);try{let _=new Oe(A),z=aa(_,t);_.disableMfa(z.id);for(let U of _.listSessions(z.id))_.revokeSession(z.id,U.id);n(`Disabled MFA and revoked active sessions for ${z.username}.`)}finally{A.close()}}),D.command("auth:oidc:list <organization>","List OIDC providers without credential material").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let A=Jt(m?.path);try{let _=Ei(A,t),z=ca(A);i(`${_.name} OIDC providers`);let U=z.listOidcProviders(_.id,{includeDisabled:!0});if(U.length===0)e("No providers configured.");for(let Y of U)e(`${Y.enabled?"enabled":"disabled"} ${Y.slug} ${Y.issuer} role=${Y.defaultRole} enforce=${Y.enforceSso}`)}finally{A.close()}}),D.command("auth:oidc:configure <organization> <slug>","Create or update an encrypted OIDC provider configuration").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").option("--name <name>","Provider label shown on the sign-in page").option("--issuer <url>","Exact OIDC issuer URL").option("--client-id <id>","OIDC client ID").option("--secret-env <name>","Environment variable containing the client secret",{default:"TS_CLOUD_OIDC_CLIENT_SECRET"}).option("--domains <domains>","Comma-separated verified email domains").option("--scopes <scopes>","Comma-separated scopes (openid is always included)",{default:"email,profile"}).option("--default-role <role>","Role for provisioned users: admin, deployer, operator, viewer, or auditor",{default:"viewer"}).option("--enforce-sso","Require SSO for matching non-owner local accounts").option("--disabled","Save the provider disabled").action((t,m,A)=>{let _=Jt(A?.path);try{let z=Ei(_,t),U=ca(_,A?.authRoot),Y=U.getOidcProviderBySlug(m),X=A?.secretEnv??"TS_CLOUD_OIDC_CLIENT_SECRET",ne=process.env[X]?.trim();if(!Y&&!ne)throw Error(`Set ${X} to the client secret before creating this provider.`);let re=(A?.domains??Y?.allowedDomains.join(",")??"").split(",").map((Ae)=>Ae.trim()).filter(Boolean),se=A?.defaultRole??Y?.defaultRole??"viewer";if(!["admin","deployer","operator","viewer","auditor"].includes(se))throw Error("Default role must be admin, deployer, operator, viewer, or auditor.");let fe=U.upsertOidcProvider({id:Y?.id,organizationId:z.id,slug:m,name:A?.name??Y?.name??m,issuer:A?.issuer??Y?.issuer??"",clientId:A?.clientId??Y?.clientId??"",clientSecret:ne,allowedDomains:re,scopes:(A?.scopes??Y?.scopes.join(",")??"email,profile").split(",").map((Ae)=>Ae.trim()).filter(Boolean),defaultRole:se,enforceSso:A?.enforceSso??Y?.enforceSso??!1,enabled:A?.disabled?!1:Y?.enabled??!0});if(n(`${fe.name} saved as ${fe.enabled?"enabled":"disabled"} with encrypted credentials.`),e(`Callback path: /auth/oidc/${fe.slug}/callback`),fe.enforceSso)e(`Offline recovery: cloud auth:oidc:disable ${fe.slug} --confirm ${fe.slug}`)}finally{_.close()}}),D.command("auth:oidc:test <slug>","Validate OIDC discovery for a configured provider").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").action(async(t,m)=>{let A=Jt(m?.path);try{let z=ca(A,m?.authRoot).getOidcProviderBySlug(t);if(!z)throw Error(`OIDC provider '${t}' was not found.`);let U=await Pn(z);n(`${z.name} discovery is valid and issuer-bound.`),e(`Authorization: ${U.authorization_endpoint}`),e(`Token: ${U.token_endpoint}`),e(`JWKS: ${U.jwks_uri}`)}finally{A.close()}}),D.command("auth:oidc:disable <slug>","Disable an OIDC provider through the offline recovery path").option("--path <path>","Use a non-default control-plane database").option("--auth-root <path>","Directory containing .ts-cloud/auth-encryption-key").option("--confirm <slug>","Type the provider slug to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to disable this provider.`);let A=Jt(m?.path);try{let _=ca(A,m?.authRoot),z=_.getOidcProviderBySlug(t);if(!z)throw Error(`OIDC provider '${t}' was not found.`);_.setOidcProviderEnabled(z.id,!1),n(`Disabled ${z.name}, cancelled pending sign-ins, and removed SSO enforcement.`)}finally{A.close()}}),D.command("api:service-accounts <organization>","List service accounts and safe token metadata").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let A=Jt(m?.path);try{let _=Ei(A,t),z=new ze(A);i(`${_.name} service accounts`);for(let U of z.listServiceAccounts(_.id,{includeDisabled:!0})){e(`${U.state} ${U.slug} ${U.name} ${U.id}`);for(let Y of z.listTokens(U.id,{includeInactive:!0}))e(` ${Y.state} ${Y.prefix} ${Y.name} expires=${Y.expiresAt} last-used=${Y.lastUsedAt??"never"}`)}}finally{A.close()}}),D.command("api:service-account:create <organization> <slug>","Create a scoped service account").option("--path <path>","Use a non-default control-plane database").option("--name <name>","Display name").option("--role <role>","admin, deployer, operator, viewer, or auditor",{default:"deployer"}).option("--scope <scope>","organization, project, environment, or resource",{default:"organization"}).option("--scope-id <id>","Required for non-organization scope").action((t,m,A)=>{let _=Jt(A?.path);try{let z=Ei(_,t),U=A?.role??"deployer";if(!["admin","deployer","operator","viewer","auditor"].includes(U))throw Error("Service-account role cannot be owner and must be a supported role.");let X=new ze(_).createServiceAccount({organizationId:z.id,slug:m,name:A?.name??m,roleTemplate:U,scope:oa(A?.scope,A?.scopeId)});n(`Created ${X.serviceAccount.name} (${X.serviceAccount.id}) with ${X.membership.roleTemplate} access.`)}finally{_.close()}}),D.command("api:token:create <organization> <account>","Create and reveal a scoped API token exactly once").option("--path <path>","Use a non-default control-plane database").option("--name <name>","Token label",{default:"CLI token"}).option("--capabilities <list>","Comma-separated capabilities").option("--scope <scope>","organization, project, environment, or resource",{default:"organization"}).option("--scope-id <id>","Required for non-organization scope").option("--expires-days <days>","Expiry in days, maximum 365",{default:"90"}).action((t,m,A)=>{let _=Jt(A?.path);try{let z=Ei(_,t),U=new ze(_),Y=U.getServiceAccount(m)??U.getServiceAccountBySlug(z.id,m);if(!Y||Y.organizationId!==z.id)throw Error(`Service account '${m}' was not found.`);let X=(A?.capabilities??"").split(",").map((se)=>se.trim()).filter((se)=>ae.includes(se));if(!X.length)throw Error(`Pass --capabilities with one or more of: ${ae.join(", ")}`);let ne=Math.min(365,Math.max(1,Number(A?.expiresDays??90))),re=U.createToken({serviceAccountId:Y.id,name:A?.name??"CLI token",capabilities:X,scope:oa(A?.scope,A?.scopeId),expiresAt:new Date(Date.now()+ne*24*60*60*1000).toISOString()});n(`Created ${re.token.prefix}; copy this secret now. It will not be shown again.`),e(re.secret)}finally{_.close()}}),D.command("api:token:rotate <token>","Create an overlapping replacement for an API token").option("--path <path>","Use a non-default control-plane database").action((t,m)=>{let A=Jt(m?.path);try{let _=new ze(A).rotateToken(t);n(`Created overlapping replacement ${_.token.prefix}; revoke the old token after rollout.`),e(_.secret)}finally{A.close()}}),D.command("api:token:revoke <token>","Immediately revoke an API token").option("--path <path>","Use a non-default control-plane database").option("--confirm <token>","Type the token ID to confirm").action((t,m)=>{if(m?.confirm!==t)throw Error(`Pass --confirm ${t} to revoke this token.`);let A=Jt(m?.path);try{let _=new ze(A).revokeToken(t);n(`Revoked ${_.prefix}; bearer requests stop immediately.`)}finally{A.close()}}),D.command("api:projects","List projects through the public v1 API").option("--base-url <url>","Dashboard base URL").option("--token-env <name>","Environment variable containing the API token",{default:"TS_CLOUD_API_TOKEN"}).action(async(t)=>{let m=await eu(t?.baseUrl,t?.tokenEnv).listProjects();for(let A of m.data)e(`${A.slug} ${A.name} ${A.id}`)}),D.command("api:deploy <project> <environment>","Queue a deployment through the public v1 API").option("--base-url <url>","Dashboard base URL").option("--token-env <name>","Environment variable containing the API token",{default:"TS_CLOUD_API_TOKEN"}).option("--service <id>","Optional service resource ID").option("--revision <revision>","Revision or release identifier").option("--idempotency-key <key>","Stable retry key").action(async(t,m,A)=>{let _=A?.idempotencyKey??`cli-${Date.now()}-${crypto.randomUUID().slice(0,8)}`,z=await eu(A?.baseUrl,A?.tokenEnv).createDeployment({projectId:t,environmentId:m,serviceId:A?.service,revision:A?.revision},_);n(`${z.idempotentReplay?"Replayed":"Queued"} operation ${z.operation.id} (${z.operation.state}).`)})}import{createHash as eg}from"node:crypto";var tg=`let coldStart = true
|
|
466
466
|
const initializedAt = new Date().toISOString()
|
|
467
467
|
export const handler = async (event) => {
|
|
@@ -629,7 +629,7 @@ Full Event Record:`);let ne=JSON.parse(X.CloudTrailEvent);console.log(JSON.strin
|
|
|
629
629
|
Create this trail?`,!0)){e("Operation cancelled");return}let z=new s("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)}}),D.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 io(m.region),_=new s("Starting logging...");_.start(),await A.startLogging(t),_.succeed("Logging started")}catch(A){r(`Failed to start logging: ${A.message}`),process.exit(1)}}),D.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 io(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 s("Stopping logging...");z.start(),await A.stopLogging(t),z.succeed("Logging stopped")}catch(A){r(`Failed to stop logging: ${A.message}`),process.exit(1)}}),D.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 io(m.region);if(a(`This will permanently delete trail: ${t}`),!await d(`
|
|
632
|
-
Delete this trail?`,!1)){e("Operation cancelled");return}let z=new s("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 bl(D){D.command("status","Show overall infrastructure health dashboard").option("--region <region>","AWS region").action(async(t)=>{i("Infrastructure Status Dashboard");try{let m=await je(),A=t.region||m.project.region||"us-east-1";e(`Region: ${A}`),e(`Project: ${m.project.name||"Unknown"}`),e("");let _=[],z=new s("Checking EC2 instances...");z.start();try{let{EC2Client:he}=await import("./chunk-
|
|
632
|
+
Delete this trail?`,!1)){e("Operation cancelled");return}let z=new s("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 bl(D){D.command("status","Show overall infrastructure health dashboard").option("--region <region>","AWS region").action(async(t)=>{i("Infrastructure Status Dashboard");try{let m=await je(),A=t.region||m.project.region||"us-east-1";e(`Region: ${A}`),e(`Project: ${m.project.name||"Unknown"}`),e("");let _=[],z=new s("Checking EC2 instances...");z.start();try{let{EC2Client:he}=await import("./chunk-7nsjehvx.js"),Le=await new he(A).describeInstances({Filters:[{Name:"instance-state-name",Values:["running","pending","stopping","stopped"]}]}),Re=0,Ee=0,Te=0;for(let Ke of Le.Reservations||[])for(let Be of Ke.Instances||[])if(Te++,Be.State?.Name==="running")Re++;else if(Be.State?.Name==="stopped")Ee++;z.succeed("EC2 instances checked"),_.push({name:"EC2 Instances",status:Re>0?"OK":Te>0?"WARN":"INFO",details:`${Re} running, ${Ee} stopped, ${Te} total`})}catch(he){z.fail("EC2 check failed"),_.push({name:"EC2 Instances",status:"ERROR",details:he.message})}let U=new s("Checking RDS instances...");U.start();try{let{RDSClient:he}=await import("./chunk-mh3rgn0e.js"),Re=(await new he(A).describeDBInstances()).DBInstances||[],Ee=Re.filter((Te)=>Te.DBInstanceStatus==="available").length;U.succeed("RDS instances checked"),_.push({name:"RDS Databases",status:Re.length>0&&Ee===Re.length?"OK":Re.length>0?"WARN":"INFO",details:`${Ee} available, ${Re.length} total`})}catch(he){U.fail("RDS check failed"),_.push({name:"RDS Databases",status:"ERROR",details:he.message})}let Y=new s("Checking Lambda functions...");Y.start();try{let{LambdaClient:he}=await import("./chunk-5khcwxhn.js"),Re=(await new he(A).listFunctions()).Functions||[];Y.succeed("Lambda functions checked"),_.push({name:"Lambda Functions",status:"OK",details:`${Re.length} function(s)`})}catch(he){Y.fail("Lambda check failed"),_.push({name:"Lambda Functions",status:"ERROR",details:he.message})}let X=new s("Checking S3 buckets...");X.start();try{let{S3Client:he}=await import("./chunk-k9ate69x.js"),Re=(await new he(A).listBuckets()).Buckets||[];X.succeed("S3 buckets checked"),_.push({name:"S3 Buckets",status:"OK",details:`${Re.length} bucket(s)`})}catch(he){X.fail("S3 check failed"),_.push({name:"S3 Buckets",status:"ERROR",details:he.message})}let ne=new s("Checking CloudFront distributions...");ne.start();try{let{CloudFrontClient:he}=await import("./chunk-s64d5nw2.js"),Le=await new he().listDistributions(),Re=Le.filter((Ee)=>Ee.Status==="Deployed").length;ne.succeed("CloudFront checked"),_.push({name:"CloudFront",status:Le.length>0&&Re===Le.length?"OK":Le.length>0?"WARN":"INFO",details:`${Re} deployed, ${Le.length} total`})}catch(he){ne.fail("CloudFront check failed"),_.push({name:"CloudFront",status:"ERROR",details:he.message})}let re=new s("Checking SQS queues...");re.start();try{let{SQSClient:he}=await import("./chunk-jv9qp13h.js"),Re=(await new he(A).listQueues()).QueueUrls||[];re.succeed("SQS queues checked"),_.push({name:"SQS Queues",status:"OK",details:`${Re.length} queue(s)`})}catch(he){re.fail("SQS check failed"),_.push({name:"SQS Queues",status:"ERROR",details:he.message})}let se=new s("Checking CloudFormation stacks...");se.start();try{let{CloudFormationClient:he}=await import("./chunk-qgqgdkg0.js"),Re=(await new he(A).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE","CREATE_IN_PROGRESS","UPDATE_IN_PROGRESS","ROLLBACK_COMPLETE","UPDATE_ROLLBACK_COMPLETE"])).StackSummaries||[],Ee=Re.filter((Be)=>Be.StackStatus==="CREATE_COMPLETE"||Be.StackStatus==="UPDATE_COMPLETE").length,Te=Re.filter((Be)=>Be.StackStatus?.includes("IN_PROGRESS")).length,Ke=Re.filter((Be)=>Be.StackStatus?.includes("ROLLBACK")).length;se.succeed("CloudFormation checked"),_.push({name:"CloudFormation",status:Ke>0?"WARN":Te>0?"INFO":"OK",details:`${Ee} healthy, ${Te} in progress, ${Ke} rolled back`})}catch(he){se.fail("CloudFormation check failed"),_.push({name:"CloudFormation",status:"ERROR",details:he.message})}let fe=new s("Checking SSL certificates...");fe.start();try{let{ACMClient:he}=await import("./chunk-cxcgqqdb.js"),Ie=new he("us-east-1"),Re=(await Ie.listCertificates()).CertificateSummaryList||[],Ee=await Promise.all(Re.map((_e)=>Ie.describeCertificate({CertificateArn:_e.CertificateArn}))),Te=Ee.filter((_e)=>_e.Status==="ISSUED").length,Ke=Ee.filter((_e)=>_e.Status==="PENDING_VALIDATION").length,Be=Ee.filter((_e)=>{if(_e.NotAfter)return(new Date(_e.NotAfter).getTime()-Date.now())/86400000<30;return!1}).length;fe.succeed("SSL certificates checked"),_.push({name:"SSL Certificates",status:Be>0?"WARN":Ke>0?"INFO":"OK",details:`${Te} issued, ${Ke} pending${Be>0?`, ${Be} expiring soon`:""}`})}catch(he){fe.fail("ACM check failed"),_.push({name:"SSL Certificates",status:"ERROR",details:he.message})}e(`
|
|
633
633
|
`+"=".repeat(60)),e("HEALTH SUMMARY"),e("=".repeat(60)+`
|
|
634
634
|
`);for(let he of _){let Ie="";if(he.status==="OK")Ie=`${C.green}[OK]${C.reset}`;else if(he.status==="WARN")Ie=`${C.yellow}[WARN]${C.reset}`;else if(he.status==="ERROR")Ie=`${C.red}[ERROR]${C.reset}`;else Ie=`${C.blue}[INFO]${C.reset}`;console.log(`${Ie} ${he.name.padEnd(20)} ${he.details}`)}let Ae=_.some((he)=>he.status==="ERROR"),Pe=_.some((he)=>he.status==="WARN");if(e(""),Ae)r("Some services have errors. Check the details above.");else if(Pe)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)}}),D.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),_=new Date(m.getFullYear(),m.getMonth()+1,0);e(`Period: ${A.toISOString().split("T")[0]} to ${_.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)}}),D.command("status:alarms","Show CloudWatch alarm status").option("--region <region>","AWS region").action(async(t)=>{i("CloudWatch Alarms");try{let m=await je(),A=t.region||m.project.region||"us-east-1",{AWSClient:_}=await import("./chunk-bb08ms5q.js");class z{client;region;constructor(Ae){this.region=Ae,this.client=new _}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),Y=new s("Fetching alarms...");Y.start();let ne=(await U.describeAlarms()).MetricAlarms||[];if(Y.succeed(`Found ${ne.length} alarm(s)`),ne.length===0){e("No CloudWatch alarms configured");return}let re=ne.filter((Ae)=>Ae.StateValue==="ALARM"),se=ne.filter((Ae)=>Ae.StateValue==="OK"),fe=ne.filter((Ae)=>Ae.StateValue==="INSUFFICIENT_DATA");if(e(""),e(`Alarming: ${re.length}`),e(`OK: ${se.length}`),e(`Insufficient Data: ${fe.length}`),re.length>0){e(`
|
|
635
635
|
Alarms in ALARM state:`);for(let Ae of re)r(` - ${Ae.AlarmName}: ${Ae.AlarmDescription||"No description"}`)}}catch(m){r(`Failed to get alarms: ${m.message}`),process.exit(1)}})}async function Ur(D){let{AWSClient:t}=await import("./chunk-bb08ms5q.js");class m{client;region;constructor(A){this.region=A,this.client=new t}async jsonRpcRequest(A,_){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(_)})}async listBackupVaults(){return this.jsonRpcRequest("ListBackupVaults",{})}async listBackupPlans(){return this.jsonRpcRequest("ListBackupPlans",{})}async listRecoveryPointsByBackupVault(A,_){return this.jsonRpcRequest("ListRecoveryPointsByBackupVault",{BackupVaultName:A,..._})}async describeBackupVault(A){return this.jsonRpcRequest("DescribeBackupVault",{BackupVaultName:A})}async getBackupPlan(A){return this.jsonRpcRequest("GetBackupPlan",{BackupPlanId:A})}async createBackupVault(A,_){return this.jsonRpcRequest("CreateBackupVault",{BackupVaultName:A,..._})}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,_){return this.jsonRpcRequest("CreateBackupSelection",{BackupPlanId:A,BackupSelection:_})}async listBackupSelections(A){return this.jsonRpcRequest("ListBackupSelections",{BackupPlanId:A})}}return new m(D)}function wl(D){D.command("backup:vaults","List backup vaults").option("--region <region>","AWS region").action(async(t)=>{i("Backup Vaults");try{let m=await je(),A=t.region||m.project.region||"us-east-1",_=await Ur(A),z=new s("Fetching vaults...");z.start();let Y=(await _.listBackupVaults()).BackupVaultList||[];if(z.succeed(`Found ${Y.length} vault(s)`),Y.length===0){e("No backup vaults found"),e("Use `cloud backup:create-vault` to create a new vault");return}c(["Vault Name","Recovery Points","Created","Encrypted"],Y.map((X)=>[X.BackupVaultName||"N/A",(X.NumberOfRecoveryPoints||0).toString(),X.CreationDate?new Date(X.CreationDate).toLocaleDateString():"N/A",X.EncryptionKeyArn?"Yes":"Default"]))}catch(m){r(`Failed to list vaults: ${m.message}`),process.exit(1)}}),D.command("backup:list","List backup plans").option("--region <region>","AWS region").action(async(t)=>{i("Backup Plans");try{let m=await je(),A=t.region||m.project.region||"us-east-1",_=await Ur(A),z=new s("Fetching backup plans...");z.start();let Y=(await _.listBackupPlans()).BackupPlansList||[];if(z.succeed(`Found ${Y.length} backup plan(s)`),Y.length===0){e("No backup plans found"),e("Use `cloud backup:create` to create a backup plan");return}c(["Plan Name","Plan ID","Version","Created"],Y.map((X)=>[X.BackupPlanName||"N/A",X.BackupPlanId||"N/A",X.VersionId?.substring(0,8)||"N/A",X.CreationDate?new Date(X.CreationDate).toLocaleDateString():"N/A"]))}catch(m){r(`Failed to list backup plans: ${m.message}`),process.exit(1)}}),D.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 je(),_=m.region||A.project.region||"us-east-1",z=await Ur(_),U=new s("Fetching recovery points...");U.start();let X=(await z.listRecoveryPointsByBackupVault(t,{MaxResults:Number.parseInt(m.limit)})).RecoveryPoints||[];if(U.succeed(`Found ${X.length} recovery point(s)`),X.length===0){e("No recovery points found");return}c(["Resource","Status","Created","Size","Lifecycle"],X.map((ne)=>[ne.ResourceArn?.split(":").pop()||"N/A",ne.Status||"N/A",ne.CreationDate?new Date(ne.CreationDate).toLocaleString():"N/A",ne.BackupSizeInBytes?uu(ne.BackupSizeInBytes):"N/A",ne.Lifecycle?.DeleteAfterDays?`${ne.Lifecycle.DeleteAfterDays} days`:"Indefinite"]))}catch(A){r(`Failed to list recovery points: ${A.message}`),process.exit(1)}}),D.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 Ur(m.region);if(e(`Vault Name: ${t}`),e(`Region: ${m.region}`),e(`Encryption: ${m.kmsKey?"Custom KMS":"AWS Managed"}`),!await d(`
|
|
@@ -652,7 +652,7 @@ Send request?`,!0)){e("Operation cancelled");return}let ne=new s("Sending reques
|
|
|
652
652
|
Status: ${se.status} ${se.statusText}`),e(`
|
|
653
653
|
Response Headers:`),se.headers.forEach((Pe,he)=>{e(` ${he}: ${Pe}`)}),e(`
|
|
654
654
|
Response Body:`);try{let Pe=JSON.parse(Ae);console.log(JSON.stringify(Pe,null,2))}catch{console.log(Ae)}}catch(_){r(`Failed to test API: ${_.message}`),process.exit(1)}})}async function ho(D){let t=await je(),m=y(process.cwd(),t),A=new S(m.store,{workerId:`cli:${process.pid}`});try{return await D(A,m.project.id,m.organization.id)}finally{m.store.close()}}function jg(D){if(!D)return;if(!["queued","running","succeeded","failed","cancelled","timed_out"].includes(D))throw Error("State must be queued, running, succeeded, failed, cancelled, or timed_out");return D}function Fg(D,t){if(!D)return"—";let m=Math.max(0,new Date(t??Date.now()).getTime()-new Date(D).getTime());return m<1000?`${m}ms`:m<60000?`${Math.round(m/1000)}s`:`${Math.round(m/60000)}m`}function $l(D){D.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 ho(async(m,A)=>{let _=m.list({projectId:t.allProjects?void 0:A,state:jg(t.state),limit:500});c(["ID","State","Operation","Target","Attempt","Step / blocked","Queued","Duration"],_.map(({operation:z,job:U,approximatePosition:Y})=>[z.id,z.state,z.kind,z.resourceId??z.environmentId??z.projectId??"global",`${z.attempt}/${U.maxAttempts}`,U.blockedReason??U.currentStep??(Y?`${Y.ahead} ahead`:"—"),z.createdAt,Fg(z.startedAt,z.finishedAt)]))})}catch(m){r(m instanceof Error?m.message:String(m))}}),D.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 ho(async(A)=>{let _=A.view(t);if(!_)throw Error(`Operation ${t} was not found`);e(JSON.stringify(_,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))}}),D.command("ops:cancel <id>","Cancel queued work or request running cancellation").option("--yes","Skip confirmation").action(async(t,m)=>{try{await ho(async(A)=>{let _=A.view(t);if(!_)throw Error(`Operation ${t} was not found`);if(!m.yes&&!await d(`Cancel ${_.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))}}),D.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 ho(async(A)=>{let _=A.retry(t,m.class,{delayMs:Number(m.delay)||0});n(`Retry queued: ${_.id} · attempt ${_.attempt+1}`)})}catch(A){r(A instanceof Error?A.message:String(A))}}),D.command("ops:concurrency","Show effective durable queue concurrency limits").action(async()=>{try{await ho(async(t)=>e(JSON.stringify(t.limits,null,2)))}catch(t){r(t instanceof Error?t.message:String(t))}}),D.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 ho(async(A,_,z)=>n(`Queue concurrency updated: ${JSON.stringify(A.configureConcurrency(m,{organizationId:z}))}`))}catch(m){r(m instanceof Error?m.message:String(m))}}),D.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 ho(async(m,A)=>{if(!t.yes&&!await d("Clear completed queue history whose retention has elapsed?",!1))return;let _=m.clearCompleted({projectId:A,before:t.before});n(`Cleared ${_} retained operation${_===1?"":"s"}.`)})}catch(m){r(m instanceof Error?m.message:String(m))}})}import{readFile as Bg,writeFile as Mg}from"node:fs/promises";function _g(D){if(D==null)return"—";if(D<60)return`${D}s`;if(D<3600)return`${Math.floor(D/60)}m`;if(D<86400)return`${Math.floor(D/3600)}h`;return`${Math.floor(D/86400)}d`}function zg(D){return D==null?"—":dn(D)}function Ug(D){return D.map((t)=>[t.id,t.name,t.provider,t.kind,t.status,t.desiredReplicas==null?"—":`${t.runningReplicas??0}/${t.desiredReplicas}`,t.image??t.runtime??"—",zg(t.resources?.memoryBytes??t.resources?.memoryLimitBytes),_g(t.ageSeconds)])}function Vg(D,t){let m=t.search?.toLowerCase();return{...D,workloads:D.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 wo(D){let t=await je(),m=D??Object.keys(t.environments??{})[0]??"production";if(!Object.hasOwn(t.environments??{},m))throw Error(`Environment ${m} was not found`);let A=new be(t,m);return{config:t,env:m,service:A}}async function ga(D,t){if(t!==void 0)return t;return R(`Type ${D.name} to confirm`,"")}async function Wg(D,t,m,A,_,z){return wa(D,`runtime.${t}`,{workloadId:D.id,provider:D.provider,action:t,replicas:m??null},_,()=>z.run({workloadId:D.id,action:t,replicas:m,confirm:A,recentAuth:!0}))}async function wa(D,t,m,A,_){let z=await je(),U=y(process.cwd(),z),Y=new S(U.store,{workerId:`cli:${process.pid}`});try{let X=U.environments.get(A),ne=U.store.listResources(U.project.id,X?.id).find((fe)=>fe.slug===D.links.service),re=U.store.getActorByExternalId("system","cli")??U.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI",metadata:{source:"cli"}}),se=Y.enqueue({projectId:U.project.id,environmentId:X?.id,resourceId:ne?.id,actorId:re.id,kind:t,input:h(m),lockKey:ne?`resource:${ne.id}`:`runtime:${D.id}`,providerKey:D.provider,maxAttempts:1,timeoutSeconds:3600,resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:90}).operation;if(!Y.claim(se.id))throw Error(`Workload is locked by another operation. Inspect ${se.id} with cloud ops:show.`);try{let fe=await _();if(fe.stdout)Y.appendLog(se.id,fe.stdout,{stream:"stdout"});if(fe.stderr)Y.appendLog(se.id,fe.stderr,{stream:"stderr"});if(fe.ok)Y.complete(se.id,{ok:!0,command:fe.command??null});else Y.fail(se.id,fe.error??fe.stderr??"Runtime operation failed",{ok:!1});return{result:fe,operationId:se.id}}catch(fe){throw Y.fail(se.id,fe instanceof Error?fe.message:String(fe),{ok:!1,threw:!0}),fe}}finally{U.store.close()}}function Al(D){D.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 wo(t.env),A=Vg(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"],Ug(A.workloads));for(let _ of A.sources.filter((z)=>z.status!=="fresh"))a(`${_.id}: ${_.status}${_.message?` — ${_.message}`:""}`)}}catch(m){r(m instanceof Error?m.message:String(m)),process.exitCode=1}}),D.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 wo(m.env),_=await A.workload(t);if(!_)throw Error("Workload was not found in this project and environment");e(JSON.stringify(_,null,2))}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),D.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 wo(m.env),_=await A.logs(t,{limit:Number(m.lines)||200});if(m.json)e(JSON.stringify(_,null,2));else for(let z of _.lines)console.log(`${z.timestamp??"—"} ${z.message}`)}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),D.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 _=m;if(!["start","stop","restart","redeploy","scale"].includes(_))throw Error("Action must be start, stop, restart, redeploy, or scale");let z=A.replicas==null?void 0:Number(A.replicas);if(_==="scale"&&(!Number.isInteger(z)||z<0))throw Error("Scale requires a non-negative integer --replicas value");let{env:U,service:Y}=await wo(A.env),X=await Y.inventory(),ne=X.workloads.find((fe)=>fe.id===t);if(!ne)throw Error("Workload was not found in this project and environment");let re=new be(await je(),U,{inventory:async()=>X}),se=await Wg(ne,_,z,await ga(ne,A.confirm),U,re);if(!se.result.ok)throw Error(se.result.error??se.result.stderr??"Runtime operation failed");if(n(`${_} completed for ${ne.name} · operation ${se.operationId}`),se.result.stdout)e(se.result.stdout)}catch(_){r(_ instanceof Error?_.message:String(_)),process.exitCode=1}}),D.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:_}=await wo(m.env),U=(await _.inventory()).workloads.find((re)=>re.id===t);if(!U)throw Error("Workload was not found in this project and environment");let Y=["process","sockets","filesystem"].includes(String(m.preset))?m.preset:void 0;if(m.preset&&!Y)throw Error("Preset must be process, sockets, or filesystem");if(!Y&&!m.command)throw Error("Pass --preset or --command");let X=await ga(U,m.confirm),ne=await wa(U,"runtime.exec",{workloadId:t,provider:U.provider,preset:Y??null,freeForm:!!m.command},A,()=>_.exec({workloadId:t,preset:Y,command:m.command,confirm:X,recentAuth:!0}));if(!ne.result.ok)throw Error(ne.result.error??ne.result.stderr??"Exec failed");if(ne.result.stdout)e(ne.result.stdout);if(ne.result.stderr)a(ne.result.stderr);n(`Exec completed for ${U.name} · operation ${ne.operationId}`)}catch(A){r(A instanceof Error?A.message:String(A)),process.exitCode=1}}),D.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,_)=>{try{let{env:z,service:U}=await wo(_.env),X=(await U.inventory()).workloads.find((se)=>se.id===t);if(!X)throw Error("Workload was not found in this project and environment");let ne=await ga(X,_.confirm),re=await wa(X,"runtime.file.read",{workloadId:t,provider:X.provider,path:m},z,()=>U.readFile({workloadId:t,path:m,confirm:ne,recentAuth:!0}));if(!re.result.ok||!re.result.contentBase64)throw Error(re.result.error??"File download failed");await Mg(A,Buffer.from(re.result.contentBase64,"base64")),n(`Downloaded ${re.result.size??0} bytes to ${A} · operation ${re.operationId}${re.result.truncated?" (truncated)":""}`)}catch(z){r(z instanceof Error?z.message:String(z)),process.exitCode=1}}),D.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,_)=>{try{let{env:z,service:U}=await wo(_.env),X=(await U.inventory()).workloads.find((fe)=>fe.id===t);if(!X)throw Error("Workload was not found in this project and environment");let ne=await ga(X,_.confirm),re=(await Bg(m)).toString("base64"),se=await wa(X,"runtime.file.write",{workloadId:t,provider:X.provider,path:A,bytes:Buffer.from(re,"base64").byteLength},z,()=>U.writeFile({workloadId:t,path:A,contentBase64:re,confirm:ne,recentAuth:!0}));if(!se.result.ok)throw Error(se.result.error??"File upload failed");n(`Uploaded ${se.result.size??0} bytes to ${A} · operation ${se.operationId}`)}catch(z){r(z instanceof Error?z.message:String(z)),process.exitCode=1}})}function qg(D,t=new Date){let m=D.to?new Date(D.to):t;if(!Number.isFinite(m.getTime()))throw Error("--to must be a valid ISO-8601 instant");if(D.from){let z=new Date(D.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(D.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 _=A[2]==="m"?60000:A[2]==="h"?3600000:86400000;return{from:new Date(m.getTime()-Number(A[1])*_).toISOString(),to:m.toISOString()}}async function As(D){let t=await je(),m=D??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),_=A.environments.get(m),z=new Ye(A.store);return{config:t,env:m,controlPlane:A,environmentRecord:_,telemetry:z}}function pu(D,t,m){let A=(z)=>z?.split(",").map((U)=>U.trim()).filter(Boolean)||void 0,_=A(D.kind)?.filter((z)=>["metric","log","trace","request","event"].includes(z));return{projectId:t,environmentId:m,...qg(D),kinds:_,names:A(D.name),sources:A(D.source),text:D.text,limit:Math.min(5000,Math.max(1,Number(D.limit)||200))}}function Gg(D){let t=["timestamp","kind","source","name","level","value","unit","message","traceId","requestId","deploymentId","releaseId","workloadId"];return[t.join(","),...D.map((m)=>t.map((A)=>`"${String(m[A]??"").replaceAll('"','""')}"`).join(","))].join(`
|
|
655
|
-
`)}function Rl(D){D.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 As(t.env);try{let A=await oi({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((_)=>[_.source,_.freshness,_.lagSeconds==null?"—":`${_.lagSeconds}s`,`${Math.round(_.samplingRate*100)}%`,`${_.retentionDays}d`]))}finally{m.controlPlane.store.close()}}),D.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 As(t.env);try{let A=m.telemetry.query(pu(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((_)=>[_.timestamp,_.kind,_.source,_.name,_.message??(_.value==null?"—":`${_.value} ${_.unit??""}`),_.requestId??_.traceId??_.releaseId??_.deploymentId??"—"]));if(A.truncated)a("More records are available; narrow the window or use the dashboard cursor explorer.")}finally{m.controlPlane.store.close()}}),D.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 As(m.env);try{let _=pu(m,A.controlPlane.project.id,A.environmentRecord?.id),z=A.telemetry.query(_);await Bun.write(t,t.toLowerCase().endsWith(".csv")?Gg(z.records):JSON.stringify({exportedAt:new Date().toISOString(),query:_,...z},null,2)),n(`Exported ${z.records.length} records to ${t}${z.truncated?" (bounded result)":""}.`)}finally{A.controlPlane.store.close()}}),D.command("telemetry:compact","Downsample old metrics and enforce configured retention").option("--env <environment>","Target environment").action(async(t)=>{let m=await As(t.env);try{let A=Qe(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()}}),D.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 As(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 _=Object.keys(A).length?fi(m.controlPlane.store,m.controlPlane.project.id,A):Qe(m.controlPlane.store,m.controlPlane.project.id),U=m.telemetry.status(m.controlPlane.project.id,m.environmentRecord?.id,_.rawDays,_.samplingRate).reduce((X,ne)=>X+ne.estimatedDailyBytes*30,0),Y={policy:_,estimatedMonthlyBytes:U,estimatedMonthlyCostUsd:hi(U,_),costEstimateConfigured:_.estimatedStorageUsdPerGbMonth>0};if(t.json)e(JSON.stringify(Y,null,2));else e(JSON.stringify(_,null,2)),e(Y.costEstimateConfigured?`Estimated local storage: $${Y.estimatedMonthlyCostUsd}/month`:"Storage cost remains unavailable until --storage-usd-gb-month is set.")}finally{m.controlPlane.store.close()}})}async function oo(D){let t=await je(),m=D??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),_=A.environments.get(m),z=new ft(A.store,{encryptionKey:x(process.cwd())});return{config:t,env:m,controlPlane:A,environmentRecord:_,alerts:z}}function fu(D){return new yr(D,{emailImpl:async(t)=>{let{email:m}=await import("./chunk-p9baztvr.js");return m.send(t)}})}function kl(D){D.command("health:list","List configured health checks and latest results").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let A=m.alerts.listHealthChecks(m.controlPlane.project.id,m.environmentRecord?.id).map((_)=>({..._,latest:m.alerts.listHealthResults(_.id,1)[0]}));if(t.json)e(JSON.stringify(A,null,2));else c(["ID","Name","Kind","Target","Status","Interval","Enabled"],A.map((_)=>[_.id,_.name,_.kind,_.target,_.latest?.status??"no data",`${_.intervalSeconds}s`,_.enabled?"yes":"no"]))}finally{m.controlPlane.store.close()}}),D.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 oo(m.env);try{let _=A.alerts.getHealthCheck(t)??A.alerts.listHealthChecks(A.controlPlane.project.id,A.environmentRecord?.id).find((Y)=>Y.name===t);if(!_)throw Error("Health check was not found");let z=new Ue(A.alerts);if(m.test){let Y=await z.probe(_);e(JSON.stringify({...Y,productionIncidentCreated:!1},null,2));return}let U=await z.runAndEvaluate(_);if(e(JSON.stringify(U,null,2)),U.result.status==="healthy")n(`${_.name} is healthy.`);else a(`${_.name} is ${U.result.status}.`)}finally{A.controlPlane.store.close()}}),D.command("alerts:list","List pending, firing, silenced, and resolved alerts").option("--env <environment>","Target environment").option("--state <states>","Comma-separated states").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let 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((_)=>[_.id,_.state,_.severity,_.title,_.ownerActorId??"unassigned",_.firstSeenAt,_.lastSeenAt]))}finally{m.controlPlane.store.close()}}),D.command("alerts:evaluate","Evaluate persisted telemetry rules now").option("--env <environment>","Target environment").action(async(t)=>{let m=await oo(t.env);try{let A=sn(m.alerts,m.controlPlane.project.id,m.environmentRecord?.id);e(JSON.stringify(A,null,2))}finally{m.controlPlane.store.close()}}),D.command("alerts:ack <alert>","Acknowledge an alert as the CLI actor").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await oo(m.env);try{let _=A.controlPlane.store.getActorByExternalId("system","cli")??A.controlPlane.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});n(`Acknowledged ${A.alerts.acknowledge(t,_.id).title}.`)}finally{A.controlPlane.store.close()}}),D.command("notifications:list","List notification channels, routes, and recent delivery diagnostics").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let 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((_)=>[_.name,_.kind,_.status,_.lastTestedAt??"never",_.lastError??"—"])),c(["Event","State","Attempts","Status","Error"],A.deliveries.map((_)=>[_.eventType,_.state,`${_.attempt}/${_.maxAttempts}`,String(_.responseStatus??"—"),_.error??"—"]))}finally{m.controlPlane.store.close()}}),D.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 oo(m.env);try{let _=A.alerts.getChannel(t)??A.alerts.listChannels(A.controlPlane.organization.id).find((U)=>U.name===t);if(!_)throw Error("Notification channel was not found");let z=await fu(A.alerts).testChannel(_.id);if(z.ok)n("Test delivered; no production incident was created.");else throw Error(z.error)}finally{A.controlPlane.store.close()}}),D.command("notifications:retry <delivery>","Retry one failed notification delivery").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await oo(m.env);try{let _=A.alerts.getDelivery(t);if(!_)throw Error("Notification delivery was not found");A.alerts.updateDelivery(t,{state:"retrying",attempt:Math.max(0,_.attempt-1),nextAttemptAt:new Date().toISOString()}),e(JSON.stringify(await fu(A.alerts).deliver(t),null,2))}finally{A.controlPlane.store.close()}})}async function Hg(D,t){let m=await je(),A=y(process.cwd(),m),_=!D&&t?new Wr(A.store).get(t):void 0,z=_?.environmentId?A.store.listEnvironments(A.project.id).find((Ae)=>Ae.id===_.environmentId)?.slug:void 0,U=D??z??Object.keys(m.environments??{})[0]??"production";if(!Object.hasOwn(m.environments??{},U))throw A.store.close(),Error(`Environment ${U} was not found`);let Y=A.environments.get(U);if(!Y)throw A.store.close(),Error(`Environment ${U} was not found in the control plane`);let X=new Wr(A.store),ne=new S(A.store,{workerId:`cli:${process.pid}`}),re=new ri(X,{queue:ne}),se=A.store.listResources(A.project.id,Y.id);Kr(X,m,{organization:A.organization,project:A.project,environment:Y,resources:se});let fe=A.store.getActorByExternalId("system","cli")??A.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:m,env:U,controlPlane:A,environmentRecord:Y,resources:se,store:X,queue:ne,service:re,actor:fe}}async function si(D,t,m){let A=await Hg(D,m);try{return await t(A)}finally{A.controlPlane.store.close()}}async function br(D){try{await D()}catch(t){r(t instanceof Error?t.message:String(t))}}function Bo(D,t){let m=D.store.list(D.controlPlane.project.id,{environmentId:D.environmentRecord.id}),A=D.store.get(t)??m.find((_)=>_.name===t);if(!A||A.environmentId!==D.environmentRecord.id)throw Error(`Scheduled job ${t} was not found in ${D.env}`);return A}function $o(D,t,m,A){D.controlPlane.store.appendEvent({organizationId:D.controlPlane.organization.id,projectId:D.controlPlane.project.id,resourceId:A,actorId:D.actor.id,type:`job.${t}`,payload:m})}function gu(D,t,m){if(D.environmentRecord.kind==="production"&&m!==t)throw Error(`Production ${t} requires --confirm ${t}`)}function Kg(D){if(!D)return{};if(!/^(?:secret|ssm|arn|env):\/\//.test(D))throw Error("Payloads must use a secret://, ssm://, arn://, or env:// reference");return{input:D}}function Jg(D){return D.map((t)=>[t.id,t.enabled?"enabled":"disabled",t.provider,t.name,t.expression,t.timezone,t.nextRunAt??"—",t.reconciliationStatus])}function Il(D){D.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)=>br(async()=>si(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"],Jg(A))}))),D.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)=>br(async()=>{let A=ct(t,m.timezone??"UTC",m.from?new Date(m.from):new Date,Number(m.count)||5);e(JSON.stringify({...A,productionExecutionCreated:!1},null,2))})),D.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)=>br(async()=>si(A.env,async(_)=>{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(_.config),Y={workers:_.store.listWorkers(_.controlPlane.project.id,_.environmentRecord.id).map((Ae)=>({name:Ae.name}))},X;if(z==="server"){let Ae=Hr(_.config,Y).filter((he)=>!he.danger&&["scheduler","backup","worker"].includes(he.group)),Pe=A.operation?Ae.find((he)=>he.id===A.operation):Ae.length===1?Ae[0]:void 0;if(!Pe)throw Error(`Choose a safe target with --operation. Available: ${Ae.map((he)=>he.id).join(", ")||"none"}`);X={kind:"dashboard_operation",operationId:Pe.id}}else X={kind:"serverless_scheduler",action:"run"};let ne=z==="server"?"server":"eventbridge",re=ti({provider:ne,expression:m,flexibleMinutes:Number(A.flexibleMinutes)||0,missedRunPolicy:A.missed??"skip",overlapPolicy:A.overlap??"forbid"});if(!re.supported)throw Error(re.notes.join(" "));let se=A.resource?_.resources.find((Ae)=>Ae.id===A.resource||Ae.slug===A.resource):void 0;if(A.resource&&!se)throw Error(`Resource ${A.resource} was not found in ${_.env}`);let fe=_.store.create({organizationId:_.controlPlane.organization.id,projectId:_.controlPlane.project.id,environmentId:_.environmentRecord.id,resourceId:se?.id,name:t,provider:ne,expression:m,timezone:A.timezone??"UTC",startsAt:A.startsAt,endsAt:A.endsAt,flexibleMinutes:Number(A.flexibleMinutes)||0,target:X,payloadRefs:Kg(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:_.actor.id,observedState:{},reconciliationStatus:"pending"});$o(_,"created",{jobId:fe.id,provider:fe.provider,expression:fe.normalizedExpression},fe.resourceId),n(`Created ${fe.name} (${fe.id}); next run ${fe.nextRunAt??"outside the active window"}`)}))),D.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)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t);if(_.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(_.id,{expression:m.expression,timezone:m.timezone,missedRunPolicy:m.missed,overlapPolicy:m.overlap,timeoutSeconds:m.timeout==null?void 0:Number(m.timeout)});$o(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"}`)}))),D.command("jobs:run <job>","Queue a manual run through the durable operation worker").option("--env <environment>","Target environment").action(async(t,m)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t),z=A.service.enqueue(_,{trigger:"manual",actorId:A.actor.id});$o(A,"run_queued",{jobId:_.id,executionId:z.id},_.resourceId),n(`Queued execution ${z.id}; inspect it with cloud jobs:history "${_.name}"`)}))),D.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)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t);if(!_.enabled)throw Error(`${_.name} is disabled`);let z=m.scheduledFor?new Date(m.scheduledFor):new Date;if(!m.scheduledFor)z.setUTCSeconds(0,0);let U=z.toISOString(),Y=A.service.enqueue(_,{trigger:m.scheduled?"scheduled":"external",scheduledFor:U,actorId:A.actor.id}),X=at(_.normalizedExpression,_.timezone,z,1)[0];A.store.markScheduled(_.id,U,X),n(`Accepted ${_.name} for ${U} as ${Y.id}`)},t)));for(let t of["enable","disable"])D.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)=>br(async()=>si(A.env,async(_)=>{let z=Bo(_,m);if(t==="disable")gu(_,t,A.confirm);let U=_.store.setEnabled(z.id,t==="enable");$o(_,t==="enable"?"enabled":"disabled",{jobId:U.id},U.resourceId),n(`${U.name} is ${U.enabled?"enabled":"disabled"}`)})));D.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)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t);gu(A,"delete",m.confirm),A.store.remove(_.id),$o(A,"deleted",{jobId:_.id,name:_.name},_.resourceId),n(`Deleted ${_.name}`)}))),D.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)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t),z=A.store.listExecutions(_.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 Y of U.logs)e(`${U.id} ${Y.sequence} ${Y.stream.toUpperCase()} ${Y.message}`)}))),D.command("jobs:reconcile","Import declarative schedules and workers without destructive deletion").option("--env <environment>","Target environment").action(async(t)=>br(async()=>si(t.env,async(m)=>{let A=Kr(m.store,m.config,{organization:m.controlPlane.organization,project:m.controlPlane.project,environment:m.environmentRecord,resources:m.resources});$o(m,"reconciled",A),n(`Reconciled ${A.jobs} schedules and ${A.workers} workers; retained ${A.drifted} missing definitions as drifted`)}))),D.command("workers:list","List configured systemd, ECS, and Lambda workers").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>br(async()=>si(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((_)=>[_.id,_.provider,_.name,_.queue,String(_.processes),`${_.timeoutSeconds}s`,_.restartPolicy,_.reconciliationStatus]))}))),D.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)=>br(async()=>si(m.env,async(A)=>{let _=A.store.listWorkers(A.controlPlane.project.id,A.environmentRecord.id),z=A.store.getWorker(t)??_.find((X)=>X.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=Zn(String(z.target.operationId??""),A.config,{workers:_.map((X)=>({name:X.name}))});if(!U||U.danger||U.group!=="worker")throw Error("The allowlisted worker restart operation is unavailable");let Y=await Gr(A.config,A.env,U);if(A.store.reconcileWorker(z.id,Y.ok?"in_sync":"unavailable",{...z.observedState,lastRestartAt:new Date().toISOString(),command:Y.command??null,error:Y.error??Y.stderr??null}),$o(A,"worker_restarted",{workerId:z.id,ok:Y.ok},z.resourceId),!Y.ok)throw Error(Y.error??Y.stderr??"Worker restart failed");n(`Restarted ${z.name}`)})))}function Pl(D){D.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 s("Connecting to tunnel server...");A.start();try{let _;try{let ne=await import("localtunnels");_=ne.TunnelClient}catch{A.fail("localtunnels package not found"),e(`
|
|
655
|
+
`)}function Rl(D){D.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 As(t.env);try{let A=await oi({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((_)=>[_.source,_.freshness,_.lagSeconds==null?"—":`${_.lagSeconds}s`,`${Math.round(_.samplingRate*100)}%`,`${_.retentionDays}d`]))}finally{m.controlPlane.store.close()}}),D.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 As(t.env);try{let A=m.telemetry.query(pu(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((_)=>[_.timestamp,_.kind,_.source,_.name,_.message??(_.value==null?"—":`${_.value} ${_.unit??""}`),_.requestId??_.traceId??_.releaseId??_.deploymentId??"—"]));if(A.truncated)a("More records are available; narrow the window or use the dashboard cursor explorer.")}finally{m.controlPlane.store.close()}}),D.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 As(m.env);try{let _=pu(m,A.controlPlane.project.id,A.environmentRecord?.id),z=A.telemetry.query(_);await Bun.write(t,t.toLowerCase().endsWith(".csv")?Gg(z.records):JSON.stringify({exportedAt:new Date().toISOString(),query:_,...z},null,2)),n(`Exported ${z.records.length} records to ${t}${z.truncated?" (bounded result)":""}.`)}finally{A.controlPlane.store.close()}}),D.command("telemetry:compact","Downsample old metrics and enforce configured retention").option("--env <environment>","Target environment").action(async(t)=>{let m=await As(t.env);try{let A=Qe(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()}}),D.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 As(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 _=Object.keys(A).length?fi(m.controlPlane.store,m.controlPlane.project.id,A):Qe(m.controlPlane.store,m.controlPlane.project.id),U=m.telemetry.status(m.controlPlane.project.id,m.environmentRecord?.id,_.rawDays,_.samplingRate).reduce((X,ne)=>X+ne.estimatedDailyBytes*30,0),Y={policy:_,estimatedMonthlyBytes:U,estimatedMonthlyCostUsd:hi(U,_),costEstimateConfigured:_.estimatedStorageUsdPerGbMonth>0};if(t.json)e(JSON.stringify(Y,null,2));else e(JSON.stringify(_,null,2)),e(Y.costEstimateConfigured?`Estimated local storage: $${Y.estimatedMonthlyCostUsd}/month`:"Storage cost remains unavailable until --storage-usd-gb-month is set.")}finally{m.controlPlane.store.close()}})}async function oo(D){let t=await je(),m=D??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),_=A.environments.get(m),z=new ft(A.store,{encryptionKey:x(process.cwd())});return{config:t,env:m,controlPlane:A,environmentRecord:_,alerts:z}}function fu(D){return new yr(D,{emailImpl:async(t)=>{let{email:m}=await import("./chunk-p9baztvr.js");return m.send(t)}})}function kl(D){D.command("health:list","List configured health checks and latest results").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let A=m.alerts.listHealthChecks(m.controlPlane.project.id,m.environmentRecord?.id).map((_)=>({..._,latest:m.alerts.listHealthResults(_.id,1)[0]}));if(t.json)e(JSON.stringify(A,null,2));else c(["ID","Name","Kind","Target","Status","Interval","Enabled"],A.map((_)=>[_.id,_.name,_.kind,_.target,_.latest?.status??"no data",`${_.intervalSeconds}s`,_.enabled?"yes":"no"]))}finally{m.controlPlane.store.close()}}),D.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 oo(m.env);try{let _=A.alerts.getHealthCheck(t)??A.alerts.listHealthChecks(A.controlPlane.project.id,A.environmentRecord?.id).find((Y)=>Y.name===t);if(!_)throw Error("Health check was not found");let z=new Ue(A.alerts);if(m.test){let Y=await z.probe(_);e(JSON.stringify({...Y,productionIncidentCreated:!1},null,2));return}let U=await z.runAndEvaluate(_);if(e(JSON.stringify(U,null,2)),U.result.status==="healthy")n(`${_.name} is healthy.`);else a(`${_.name} is ${U.result.status}.`)}finally{A.controlPlane.store.close()}}),D.command("alerts:list","List pending, firing, silenced, and resolved alerts").option("--env <environment>","Target environment").option("--state <states>","Comma-separated states").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let 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((_)=>[_.id,_.state,_.severity,_.title,_.ownerActorId??"unassigned",_.firstSeenAt,_.lastSeenAt]))}finally{m.controlPlane.store.close()}}),D.command("alerts:evaluate","Evaluate persisted telemetry rules now").option("--env <environment>","Target environment").action(async(t)=>{let m=await oo(t.env);try{let A=sn(m.alerts,m.controlPlane.project.id,m.environmentRecord?.id);e(JSON.stringify(A,null,2))}finally{m.controlPlane.store.close()}}),D.command("alerts:ack <alert>","Acknowledge an alert as the CLI actor").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await oo(m.env);try{let _=A.controlPlane.store.getActorByExternalId("system","cli")??A.controlPlane.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});n(`Acknowledged ${A.alerts.acknowledge(t,_.id).title}.`)}finally{A.controlPlane.store.close()}}),D.command("notifications:list","List notification channels, routes, and recent delivery diagnostics").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>{let m=await oo(t.env);try{let 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((_)=>[_.name,_.kind,_.status,_.lastTestedAt??"never",_.lastError??"—"])),c(["Event","State","Attempts","Status","Error"],A.deliveries.map((_)=>[_.eventType,_.state,`${_.attempt}/${_.maxAttempts}`,String(_.responseStatus??"—"),_.error??"—"]))}finally{m.controlPlane.store.close()}}),D.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 oo(m.env);try{let _=A.alerts.getChannel(t)??A.alerts.listChannels(A.controlPlane.organization.id).find((U)=>U.name===t);if(!_)throw Error("Notification channel was not found");let z=await fu(A.alerts).testChannel(_.id);if(z.ok)n("Test delivered; no production incident was created.");else throw Error(z.error)}finally{A.controlPlane.store.close()}}),D.command("notifications:retry <delivery>","Retry one failed notification delivery").option("--env <environment>","Target environment").action(async(t,m)=>{let A=await oo(m.env);try{let _=A.alerts.getDelivery(t);if(!_)throw Error("Notification delivery was not found");A.alerts.updateDelivery(t,{state:"retrying",attempt:Math.max(0,_.attempt-1),nextAttemptAt:new Date().toISOString()}),e(JSON.stringify(await fu(A.alerts).deliver(t),null,2))}finally{A.controlPlane.store.close()}})}async function Hg(D,t){let m=await je(),A=y(process.cwd(),m),_=!D&&t?new Wr(A.store).get(t):void 0,z=_?.environmentId?A.store.listEnvironments(A.project.id).find((Ae)=>Ae.id===_.environmentId)?.slug:void 0,U=D??z??Object.keys(m.environments??{})[0]??"production";if(!Object.hasOwn(m.environments??{},U))throw A.store.close(),Error(`Environment ${U} was not found`);let Y=A.environments.get(U);if(!Y)throw A.store.close(),Error(`Environment ${U} was not found in the control plane`);let X=new Wr(A.store),ne=new S(A.store,{workerId:`cli:${process.pid}`}),re=new ri(X,{queue:ne}),se=A.store.listResources(A.project.id,Y.id);Kr(X,m,{organization:A.organization,project:A.project,environment:Y,resources:se});let fe=A.store.getActorByExternalId("system","cli")??A.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:m,env:U,controlPlane:A,environmentRecord:Y,resources:se,store:X,queue:ne,service:re,actor:fe}}async function si(D,t,m){let A=await Hg(D,m);try{return await t(A)}finally{A.controlPlane.store.close()}}async function br(D){try{await D()}catch(t){r(t instanceof Error?t.message:String(t))}}function Bo(D,t){let m=D.store.list(D.controlPlane.project.id,{environmentId:D.environmentRecord.id}),A=D.store.get(t)??m.find((_)=>_.name===t);if(!A||A.environmentId!==D.environmentRecord.id)throw Error(`Scheduled job ${t} was not found in ${D.env}`);return A}function $o(D,t,m,A){D.controlPlane.store.appendEvent({organizationId:D.controlPlane.organization.id,projectId:D.controlPlane.project.id,resourceId:A,actorId:D.actor.id,type:`job.${t}`,payload:m})}function gu(D,t,m){if(D.environmentRecord.kind==="production"&&m!==t)throw Error(`Production ${t} requires --confirm ${t}`)}function Kg(D){if(!D)return{};if(!/^(?:secret|ssm|arn|env):\/\//.test(D))throw Error("Payloads must use a secret://, ssm://, arn://, or env:// reference");return{input:D}}function Jg(D){return D.map((t)=>[t.id,t.enabled?"enabled":"disabled",t.provider,t.name,t.expression,t.timezone,t.nextRunAt??"—",t.reconciliationStatus])}function Il(D){D.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)=>br(async()=>si(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"],Jg(A))}))),D.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)=>br(async()=>{let A=ct(t,m.timezone??"UTC",m.from?new Date(m.from):new Date,Number(m.count)||5);e(JSON.stringify({...A,productionExecutionCreated:!1},null,2))})),D.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)=>br(async()=>si(A.env,async(_)=>{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(_.config),Y={workers:_.store.listWorkers(_.controlPlane.project.id,_.environmentRecord.id).map((Ae)=>({name:Ae.name}))},X;if(z==="server"){let Ae=Hr(_.config,Y).filter((he)=>!he.danger&&["scheduler","backup","worker"].includes(he.group)),Pe=A.operation?Ae.find((he)=>he.id===A.operation):Ae.length===1?Ae[0]:void 0;if(!Pe)throw Error(`Choose a safe target with --operation. Available: ${Ae.map((he)=>he.id).join(", ")||"none"}`);X={kind:"dashboard_operation",operationId:Pe.id}}else X={kind:"serverless_scheduler",action:"run"};let ne=z==="server"?"server":"eventbridge",re=ti({provider:ne,expression:m,flexibleMinutes:Number(A.flexibleMinutes)||0,missedRunPolicy:A.missed??"skip",overlapPolicy:A.overlap??"forbid"});if(!re.supported)throw Error(re.notes.join(" "));let se=A.resource?_.resources.find((Ae)=>Ae.id===A.resource||Ae.slug===A.resource):void 0;if(A.resource&&!se)throw Error(`Resource ${A.resource} was not found in ${_.env}`);let fe=_.store.create({organizationId:_.controlPlane.organization.id,projectId:_.controlPlane.project.id,environmentId:_.environmentRecord.id,resourceId:se?.id,name:t,provider:ne,expression:m,timezone:A.timezone??"UTC",startsAt:A.startsAt,endsAt:A.endsAt,flexibleMinutes:Number(A.flexibleMinutes)||0,target:X,payloadRefs:Kg(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:_.actor.id,observedState:{},reconciliationStatus:"pending"});$o(_,"created",{jobId:fe.id,provider:fe.provider,expression:fe.normalizedExpression},fe.resourceId),n(`Created ${fe.name} (${fe.id}); next run ${fe.nextRunAt??"outside the active window"}`)}))),D.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)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t);if(_.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(_.id,{expression:m.expression,timezone:m.timezone,missedRunPolicy:m.missed,overlapPolicy:m.overlap,timeoutSeconds:m.timeout==null?void 0:Number(m.timeout)});$o(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"}`)}))),D.command("jobs:run <job>","Queue a manual run through the durable operation worker").option("--env <environment>","Target environment").action(async(t,m)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t),z=A.service.enqueue(_,{trigger:"manual",actorId:A.actor.id});$o(A,"run_queued",{jobId:_.id,executionId:z.id},_.resourceId),n(`Queued execution ${z.id}; inspect it with cloud jobs:history "${_.name}"`)}))),D.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)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t);if(!_.enabled)throw Error(`${_.name} is disabled`);let z=m.scheduledFor?new Date(m.scheduledFor):new Date;if(!m.scheduledFor)z.setUTCSeconds(0,0);let U=z.toISOString(),Y=A.service.enqueue(_,{trigger:m.scheduled?"scheduled":"external",scheduledFor:U,actorId:A.actor.id}),X=at(_.normalizedExpression,_.timezone,z,1)[0];A.store.markScheduled(_.id,U,X),n(`Accepted ${_.name} for ${U} as ${Y.id}`)},t)));for(let t of["enable","disable"])D.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)=>br(async()=>si(A.env,async(_)=>{let z=Bo(_,m);if(t==="disable")gu(_,t,A.confirm);let U=_.store.setEnabled(z.id,t==="enable");$o(_,t==="enable"?"enabled":"disabled",{jobId:U.id},U.resourceId),n(`${U.name} is ${U.enabled?"enabled":"disabled"}`)})));D.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)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t);gu(A,"delete",m.confirm),A.store.remove(_.id),$o(A,"deleted",{jobId:_.id,name:_.name},_.resourceId),n(`Deleted ${_.name}`)}))),D.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)=>br(async()=>si(m.env,async(A)=>{let _=Bo(A,t),z=A.store.listExecutions(_.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 Y of U.logs)e(`${U.id} ${Y.sequence} ${Y.stream.toUpperCase()} ${Y.message}`)}))),D.command("jobs:reconcile","Import declarative schedules and workers without destructive deletion").option("--env <environment>","Target environment").action(async(t)=>br(async()=>si(t.env,async(m)=>{let A=Kr(m.store,m.config,{organization:m.controlPlane.organization,project:m.controlPlane.project,environment:m.environmentRecord,resources:m.resources});$o(m,"reconciled",A),n(`Reconciled ${A.jobs} schedules and ${A.workers} workers; retained ${A.drifted} missing definitions as drifted`)}))),D.command("workers:list","List configured systemd, ECS, and Lambda workers").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(t)=>br(async()=>si(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((_)=>[_.id,_.provider,_.name,_.queue,String(_.processes),`${_.timeoutSeconds}s`,_.restartPolicy,_.reconciliationStatus]))}))),D.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)=>br(async()=>si(m.env,async(A)=>{let _=A.store.listWorkers(A.controlPlane.project.id,A.environmentRecord.id),z=A.store.getWorker(t)??_.find((X)=>X.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=Zn(String(z.target.operationId??""),A.config,{workers:_.map((X)=>({name:X.name}))});if(!U||U.danger||U.group!=="worker")throw Error("The allowlisted worker restart operation is unavailable");let Y=await qr(A.config,A.env,U);if(A.store.reconcileWorker(z.id,Y.ok?"in_sync":"unavailable",{...z.observedState,lastRestartAt:new Date().toISOString(),command:Y.command??null,error:Y.error??Y.stderr??null}),$o(A,"worker_restarted",{workerId:z.id,ok:Y.ok},z.resourceId),!Y.ok)throw Error(Y.error??Y.stderr??"Worker restart failed");n(`Restarted ${z.name}`)})))}function Pl(D){D.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 s("Connecting to tunnel server...");A.start();try{let _;try{let ne=await import("localtunnels");_=ne.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",Y=new _({localPort:m,localHost:t.host,subdomain:t.subdomain,host:z,port:U?443:80,secure:U,verbose:t.verbose});Y.on("connected",(ne)=>{A.succeed("Connected!"),n(`
|
|
658
658
|
Your tunnel URL: ${ne.url}`),e(`Subdomain: ${ne.subdomain}`),e(`
|