@stacksjs/ts-cloud 0.11.2 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +34 -34
- package/dist/cdn/cloudflare-settings.d.ts +16 -0
- package/dist/index.js +3 -3
- package/dist/ui/404.html +1 -1
- package/dist/ui/accept-invitation.html +1 -1
- package/dist/ui/access-denied.html +1 -1
- package/dist/ui/account/automation.html +5 -5
- package/dist/ui/account/security.html +2 -2
- package/dist/ui/applications/compose.html +6 -6
- package/dist/ui/applications/new.html +1 -1
- package/dist/ui/data/backups.html +6 -6
- package/dist/ui/data/services.html +6 -6
- package/dist/ui/data/volumes.html +6 -6
- package/dist/ui/index.html +6 -6
- package/dist/ui/infrastructure/topology.html +5 -5
- package/dist/ui/integrations.html +2 -2
- package/dist/ui/operations/alerts.html +6 -6
- package/dist/ui/operations/configuration.html +6 -6
- package/dist/ui/operations/jobs.html +6 -6
- package/dist/ui/operations/maintenance.html +5 -5
- package/dist/ui/operations/observability.html +6 -6
- package/dist/ui/operations/previews.html +6 -6
- package/dist/ui/operations/queue.html +6 -6
- package/dist/ui/operations/regions.html +5 -5
- package/dist/ui/operations/releases.html +6 -6
- package/dist/ui/operations/spend.html +6 -6
- package/dist/ui/operations/workloads.html +6 -6
- package/dist/ui/security.html +2 -2
- package/dist/ui/server/actions.html +6 -6
- package/dist/ui/server/activity.html +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 +5 -5
- package/dist/ui/server/fleet.html +5 -5
- package/dist/ui/server/logs.html +5 -5
- package/dist/ui/server/metrics.html +5 -5
- package/dist/ui/server/security.html +1 -1
- package/dist/ui/server/services.html +1 -1
- package/dist/ui/server/sites.html +5 -5
- 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 +1 -1
- 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 +5 -5
- 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 +5 -5
- package/dist/ui/serverless/traces.html +6 -6
- package/dist/ui/serverless.html +5 -5
- package/package.json +3 -3
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import{i as Qn,j as Di,k as vn,l as Ti,n as Ni,o as ml}from"./chunk-g0fm1xcs.js";import{v as lr}from"./chunk-b4wt9jcg.js";import{$ as ic,A as Vr,Aa as fl,B as Ps,Ba as gl,C as Ys,Ca as Io,D as ia,Da as hl,E as Mn,Ea as Te,F as Un,Fa as Ml,G as
|
|
2
|
+
import{i as Qn,j as Di,k as vn,l as Ti,n as Ni,o as ml}from"./chunk-g0fm1xcs.js";import{v as lr}from"./chunk-b4wt9jcg.js";import{$ as ic,A as Vr,Aa as fl,B as Ps,Ba as gl,C as Ys,Ca as Io,D as ia,Da as hl,E as Mn,Ea as Te,F as Un,Fa as Ml,G as et,Ga as Qt,H as Ta,Ha as gd,I as La,Ia as hd,J as ja,Ja as yd,K as Fa,Ka as us,L as Ma,La as ms,M as Ba,Ma as vd,N as _a,Na as td,O as za,Oa as nd,P as qe,Pa as rd,Q as Ja,Qa as Wo,R as Gn,Ra as Oe,S as gi,Sa as Ye,T as Ya,Ta as ct,U as Za,Ua as ss,V as Qa,Va as ld,W as Xa,Wa as gr,X as ec,Y as tc,Z as nc,_ as rc,aa as hi,ba as Kn,ca as er,da as kc,ea as Rc,fa as Bi,ga as Ic,ha as Pc,ia as Ec,ja as Dc,ka as Ui,la as Tc,ma as Nc,na as Oc,oa as Lc,pa as md,qa as pd,ra as fd,sa as Jc,ta as Yc,ua as Zc,va as Co,w as Tt,wa as Ao,x as le,xa as pl,y as jl,ya as ko,z as q,za as Ro}from"./chunk-b0e39tpv.js";import"./chunk-sj5csg49.js";import{$a as Bc,Ab as cd,Ac as vc,Bb as Vo,Db as gc,Eb as Li,Jc as bc,Mc as wc,Sc as Ge,Wc as Sc,Xc as ji,Yc as Fi,Za as We,Zc as $c,ab as _c,bb as zc,bc as hc,cb as Uc,cc as yc,db as Vc,eb as Wc,fb as qc,gb as Gc,hb as Kc,ib as ql,jb as Gl,lb as Kl,mb as Hl,nb as Hc,ob as ir,pb as Fs,qb as nr,sb as bn,tb as zo,ub as Uo,vb as cs,wb as sd,xb as ad}from"./chunk-cbsys5v8.js";import{fd as Dt}from"./chunk-s4yzes15.js";import{gd as qn}from"./chunk-3aph4c1w.js";import{hd as oi,id as si,jd as pn,kd as ho,ld as yo,nd as ai,od as ci,pd as li,qd as di,rd as vt,td as ui,ud as Re,vd as Dr}from"./chunk-mfrf9q4b.js";import{wd as ne}from"./chunk-82mj52xr.js";import"./chunk-81hmza39.js";import{Bd as rt,Cd as qt,Dd as dc,Ed as uc,Fd as Et}from"./chunk-e2bybahr.js";import{Hd as at}from"./chunk-d7ke90zt.js";import{Id as pt,Jd as bt,Nd as Mc}from"./chunk-kg61p5hw.js";import{_d as nt}from"./chunk-c0bdafbx.js";import{$d as Nr,Ae as Ra,Be as Ia,Ce as Pa,De as xa,Ee as Ea,Fe as ht,Ge as Da,Ie as So,Je as $o,ae as vo,be as _t,ce as so,de as rn,ee as jr,fe as fa,ge as ta,he as co,ie as na,je as Hr,ke as ra,le as ln,me as Vt,ne as Wt,oe as ba,pe as wa,qe as Rt,re as Sa,se as He,te as Jr,ue as $a,ve as Kr,we as Yr,xe as Ca,ye as Aa,ze as ka}from"./chunk-cq9mca3h.js";import{$e as Ei}from"./chunk-4xhj37nj.js";import{hf as xi}from"./chunk-8khgprf6.js";import{kf as Ce}from"./chunk-r6b3pq31.js";import"./chunk-xk08030d.js";import{Af as bo,Bf as wo,Cf as dl,Df as ul,Kf as Qs,Nf as Xs,Rf as ea,vf as Zs,yf as kn,zf as fo}from"./chunk-56wvjysz.js";import"./chunk-xhwqpgwq.js";import{jg as ie}from"./chunk-a7w13qfm.js";import{kg as he,lg as Je}from"./chunk-my3ewh3a.js";import{og as Wn}from"./chunk-e5ab5twy.js";import{pg as cc,qg as de}from"./chunk-btsyjhk7.js";import{rg as Es,ug as Ds,vg as Ts,wg as Ns,xg as Os,yg as Ls,zg as js}from"./chunk-w32xv8n4.js";import{Ag as pr,Eg as id,Fg as Fl}from"./chunk-dz1ec589.js";import"./chunk-mcr0r4at.js";import{Ig as Rn}from"./chunk-9a88fp00.js";import{$g as Nl,Jg as lt,Kg as sl,Lg as h,Mg as p,Ng as R,Og as fn,Pg as a,Qg as M,Rg as w,Sg as A,Tg as Z,Ug as O,Vg as xr,Wg as x,Xg as Pn,Yg as Xe,Zg as Dl,_g as Tl,ah as Ol,bh as z}from"./chunk-6tfwm33r.js";import{dh as Ai,fh as ol}from"./chunk-ja1vjdn1.js";import{$h as Fc,Ai as ua,Ch as Sl,Di as ma,Eh as An,Fh as po,Gh as Fn,Ji as pa,Ki as zn,Lh as Yt,Mh as oa,Qi as Rl,Rh as sa,Sh as aa,Vh as Ve,Vi as Il,Wh as al,Xh as cl,Yh as ll,fi as Mr,ji as Br,ki as me,li as ca,mi as Bn,ni as ft,qi as _r,vi as la,wi as zt,xi as _n,yi as zr,zi as da}from"./chunk-4q6tad44.js";import{op as F}from"./chunk-ehc63fvs.js";import{existsSync as bd}from"node:fs";import br from"node:fs/promises";import gs from"node:os";import wr from"node:path";import At from"node:process";import{EventEmitter as wd}from"node:events";import ut from"node:process";import hs from"node:process";import Cr from"node:process";import Dn from"node:process";import nn from"node:process";import ws from"node:tty";import cu,{stdin as Hf,stdout as Jf}from"node:process";class Ss{configPath;config=null;events=[];retryCount=0;maxRetries=3;retryDelayMs=1000;constructor(){let r=gs.homedir(),e=wr.join(r,".config","clapp");this.configPath=wr.join(e,"telemetry.json")}async isEnabled(){if(At.env.DO_NOT_TRACK==="1"||At.env.DO_NOT_TRACK==="true")return!1;if(At.env.NO_TELEMETRY==="1"||At.env.NO_TELEMETRY==="true")return!1;return(await this.loadConfig()).enabled}async enable(){let r=await this.loadConfig();if(r.enabled=!0,!r.userId)r.userId=this.generateUserId();await this.saveConfig(r)}async disable(){let r=await this.loadConfig();r.enabled=!1,await this.saveConfig(r)}async track(r,e){if(!await this.isEnabled())return;let n={event:r,...e,timestamp:Date.now(),platform:gs.platform(),nodeVersion:At.version};if(this.events.push(n),this.events.length>=10)await this.send()}async trackCommand(r,e){await this.track("command",{command:r,duration:e})}async trackError(r,e){await this.track("error",{error:r,command:e})}async send(){if(!await this.isEnabled()||this.events.length===0)return!0;try{this.events=[],this.retryCount=0;let e=await this.loadConfig();return e.lastSent=Date.now(),await this.saveConfig(e),!0}catch{if(this.retryCount<this.maxRetries){this.retryCount++;let e=this.retryDelayMs*2**(this.retryCount-1);return await this.sleep(e),this.send()}return this.events=[],this.retryCount=0,!1}}async flush(){if(this.events.length===0)return!0;return this.send()}sleep(r){return new Promise((e)=>setTimeout(e,r))}async status(){let r=await this.loadConfig();return{enabled:r.enabled,doNotTrack:At.env.DO_NOT_TRACK==="1"||At.env.DO_NOT_TRACK==="true",eventsQueued:this.events.length,lastSent:r.lastSent}}async loadConfig(){if(this.config)return this.config;try{if(bd(this.configPath)){let r=await br.readFile(this.configPath,"utf-8");return this.config=JSON.parse(r),this.config}}catch{}return this.config={enabled:!1},this.config}async saveConfig(r){this.config=r;try{let e=wr.dirname(this.configPath);await br.mkdir(e,{recursive:!0}),await br.writeFile(this.configPath,JSON.stringify(r,null,2),"utf-8")}catch{}}generateUserId(){let r=Math.random().toString(36).substring(2,15),e=Date.now().toString(36);return`${r}-${e}`}}var Lf=new Ss;function Sd(r,e={}){let t={_:[]},n=e.alias||{},i=new Set(e.boolean||[]),o={};for(let c of Object.keys(n))for(let l of n[c])o[l]=c;for(let c of i)if(n[c])for(let l of n[c])i.add(l);function s(c,l){let d=o[c]||c;if(t[d]=l,n[d])for(let u of n[d])t[u]=l;if(o[c]&&n[o[c]])for(let u of n[o[c]])t[u]=l;t[c]=l}for(let c=0;c<r.length;c++){let l=r[c];if(l==="--"){t._.push(...r.slice(c+1));break}if(l.startsWith("--")){let d=l.indexOf("=");if(d!==-1){let u=l.slice(2,d),m=l.slice(d+1);s(u,m)}else{let u=l.slice(2);if(u.startsWith("no-")){let f=u.slice(3);s(f,!1);continue}let m=o[u]||u;if(i.has(m)||i.has(u))s(u,!0);else{let f=r[c+1];if(f!==void 0&&!f.startsWith("-"))s(u,f),c++;else s(u,!0)}}}else if(l.startsWith("-")&&l.length>1){let d=l.slice(1);for(let u=0;u<d.length;u++){let m=d[u],f=o[m]||m;if(u===d.length-1&&!i.has(f)&&!i.has(m)){let y=r[c+1];if(y!==void 0&&!y.startsWith("-"))s(m,y),c++;else s(m,!0)}else s(m,!0)}}else t._.push(l)}return t}function $r(r){return r.replace(/[<[].+/,"").trim()}function $d(r){let e=/<([^>]+)>/g,t=/\[([^\]]+)\]/g,n=[],i=(c)=>{let l=!1,d=c[1];if(d.startsWith("..."))d=d.slice(3),l=!0;return{required:c[0].startsWith("<"),value:d,variadic:l}},o;while(o=e.exec(r))n.push(i(o));let s;while(s=t.exec(r))n.push(i(s));return n}function Cd(r){let e={alias:{},boolean:[]};for(let[t,n]of r.entries()){if(n.names.length>1)e.alias[n.names[0]]=n.names.slice(1);if(n.isBoolean)if(n.negated){if(!r.some((o,s)=>s!==t&&o.names.some((c)=>n.names.includes(c))&&typeof o.required==="boolean"))e.boolean.push(n.names[0])}else e.boolean.push(n.names[0])}return e}function ys(r){return r.reduce((e,t)=>e.length>=t.length?e:t,"")}function Sr(r,e){return r.length>=e?r:`${r}${" ".repeat(e-r.length)}`}function Ad(r){return r.replace(/([a-z])-([a-z])/g,(e,t,n)=>t+n.toUpperCase())}function kd(r,e,t){let n=0,i=e.length,o=r,s;for(;n<i;++n)s=o[e[n]],o=o[e[n]]=n===i-1?t:s!=null?s:!!~e[n+1].indexOf(".")||!(+e[n+1]>-1)?{}:[]}function Rd(r,e){for(let t of Object.keys(e)){let n=e[t];if(n.shouldTransform){if(r[t]=Array.prototype.concat.call([],r[t]),typeof n.transformFunction==="function")r[t]=r[t].map(n.transformFunction)}}}function Id(r){let e=/([^\\/]+)$/.exec(r);return e?e[1]:""}function $s(r){return r.split(".").map((e,t)=>t===0?Ad(e):e).join(".")}class En extends Error{exitCode=2;isUsageError=!0;constructor(r){super(r);if(this.name=this.constructor.name,typeof Error.captureStackTrace==="function")Error.captureStackTrace(this,this.constructor);else this.stack=Error(r).stack}format(r=!1){if(r&&this.stack)return`${this.message}
|
|
3
3
|
|
|
4
4
|
Stack trace:
|
|
5
5
|
${this.stack}`;return this.message}}function Pd(){let{env:r}=hs,{TERM:e,TERM_PROGRAM:t}=r;if(hs.platform!=="win32")return e!=="linux";return Boolean(r.WT_SESSION)||Boolean(r.TERMINUS_SUBLIME)||r.ConEmuTask==="{cmd::Cmder}"||t==="Terminus-Sublime"||t==="vscode"||e==="xterm-256color"||e==="alacritty"||e==="rxvt-unicode"||e==="rxvt-unicode-256color"||r.TERMINAL_EMULATOR==="JetBrains-JediTerm"}var Tn="\x1B",re=`${Tn}[`;var vs={to(r,e){if(!e)return`${re}${r+1}G`;return`${re}${e+1};${r+1}H`},move(r,e){let t="";if(r<0)t+=`${re}${-r}D`;else if(r>0)t+=`${re}${r}C`;if(e<0)t+=`${re}${-e}A`;else if(e>0)t+=`${re}${e}B`;return t},up:(r=1)=>`${re}${r}A`,down:(r=1)=>`${re}${r}B`,forward:(r=1)=>`${re}${r}C`,backward:(r=1)=>`${re}${r}D`,nextLine:(r=1)=>`${re}E`.repeat(r),prevLine:(r=1)=>`${re}F`.repeat(r),left:`${re}G`,hide:`${re}?25l`,show:`${re}?25h`,save:`${Tn}7`,restore:`${Tn}8`};var Bf={screen:`${re}2J`,up:(r=1)=>`${re}1J`.repeat(r),down:(r=1)=>`${re}J`.repeat(r),line:`${re}2K`,lineEnd:`${re}K`,lineStart:`${re}1K`,lines(r){let e="";for(let t=0;t<r;t++)e+=this.line+(t<r-1?vs.up():"");if(r)e+=vs.left;return e}},_f={screen:`${Tn}c`};function xd(r,e){let t=[];for(let n=0;n<=e.length;n++)t[n]=[n];for(let n=0;n<=r.length;n++)t[0][n]=n;for(let n=1;n<=e.length;n++)for(let i=1;i<=r.length;i++)if(e.charAt(n-1)===r.charAt(i-1))t[n][i]=t[n-1][i-1];else t[n][i]=Math.min(t[n-1][i-1]+1,t[n][i-1]+1,t[n-1][i]+1);return t[e.length][r.length]}function Cs(r,e,t=2,n=3){return e.map((i)=>({cmd:i,distance:xd(r,i)})).filter(({distance:i})=>i<=t).sort((i,o)=>i.distance-o.distance).slice(0,n).map(({cmd:i})=>i)}class As{rawName;description;name;names;isBoolean;required;config;negated;constructor(r,e,t){if(this.rawName=r,this.description=e,this.config=Object.assign({},t),r=r.replace(/\.\*/g,""),this.negated=!1,this.names=$r(r).split(",").map((n)=>{let i=n.trim().replace(/^-{1,2}/,"");if(i.startsWith("no-"))this.negated=!0,i=i.replace(/^no-/,"");return $s(i)}).sort((n,i)=>n.length>i.length?1:-1),this.name=this.names[this.names.length-1],this.negated&&this.config.default==null)this.config.default=!0;if(r.includes("<"))this.required=!0;else if(r.includes("["))this.required=!1;else this.isBoolean=!0}}var Uf=Cr.argv,Ed=`${Cr.platform}-${Cr.arch} bun-v${typeof Bun<"u"?Bun.version:"unknown"}`,bs=Dn.argv,Dd=`${Dn.platform}-${Dn.arch} node-${Dn.version}`;class Ar{rawName;description;config;cli;options;aliasNames;name;namespace;args;commandAction;usageText;versionNumber;examples;helpCallback;globalCommand;beforeHooks;afterHooks;middleware;constructor(r,e,t,n){this.rawName=r,this.description=e,this.config=t,this.cli=n,this.options=[],this.aliasNames=[],this.name=$r(r);let i=$r(r),o=i.indexOf(":");if(o>0)this.namespace=i.substring(0,o),this.name=i.substring(o+1);if(this.args=$d(r),this.examples=[],this.beforeHooks=[],this.afterHooks=[],this.middleware=[],!t)this.config={}}usage(r){return this.usageText=r,this}allowUnknownOptions(){return this.config.allowUnknownOptions=!0,this}ignoreOptionDefaultValue(){return this.config.ignoreOptionDefaultValue=!0,this}version(r,e="-v, --version"){return this.versionNumber=r,this.option(e,"Display version number"),this}example(r){return this.examples.push(r),this}option(r,e,t){let n=new As(r,e,t);return this.options.push(n),this}alias(r){return this.aliasNames.push(r),this}action(r){return this.commandAction=r,this}before(r){return this.beforeHooks.push(r),this}after(r){return this.afterHooks.push(r),this}use(r){return this.middleware.push(r),this}isMatched(r){if(this.aliasNames.includes(r))return!0;if(this.namespace)return`${this.namespace}:${this.name}`===r;return this.name===r}get isDefaultCommand(){return this.name===""||this.aliasNames.includes("!")}get isGlobalCommand(){return this instanceof kr}get displayName(){return this.namespace?`${this.namespace}:${this.name}`:this.name}hasOption(r){return r=r.split(".")[0],!!this.options.find((e)=>e.names.includes(r))}outputHelp(){let{name:r,commands:e}=this.cli,{versionNumber:t,options:n,helpCallback:i}=this.cli.globalCommand,o=[{body:`${r}${t?`/${t}`:""}`}];if(o.push({title:"Usage",body:` $ ${r} ${this.usageText||this.rawName}`}),(this.isGlobalCommand||this.isDefaultCommand)&&e.length>0){let l=ys(e.map((y)=>y.rawName)),d=new Map,u=[];for(let y of e)if(y.namespace){if(!d.has(y.namespace))d.set(y.namespace,[]);d.get(y.namespace).push(y)}else u.push(y);let m="";if(u.length>0)m+=u.map((y)=>` ${Sr(y.rawName,l.length)} ${y.description}`).join(`
|
|
@@ -28,7 +28,7 @@ Received ${t}, cleaning up...`),r)try{await r()}catch(n){console.error("Error du
|
|
|
28
28
|
✗ Command "${r}" not found.
|
|
29
29
|
`)),this.enableDidYouMean){let e=[];for(let n of this.commands){if(n.displayName)e.push(n.displayName);if(n.aliasNames)e.push(...n.aliasNames)}let t=Cs(r,e);if(t.length>0)console.log(tn.yellow("Did you mean one of these?")),t.forEach((n)=>console.log(` ${tn.dim("•")} ${this.name} ${n}`)),console.log("")}console.log(tn.dim("Run"),`${this.name} --help`,tn.dim("to see all available commands")),ut.exit(1)}async parse(r=bs,e={}){let{run:t=!0,exitOnError:n=!1}=e;if(n)try{return await this.parse(r,{run:t})}catch(l){throw this.handleUsageError(l),l}if(this.rawArgs=r,!this.name)this.name=r[1]?Id(r[1]):"cli";let i=!0,o=r.slice(2),s=o[0];if(s&&!s.startsWith("-")){for(let l of this.commands)if(l.isMatched(s)){let d=this.mri(o,l);i=!1;let u={...d,args:d.args.slice(1)};this.setParsedInfo(u,l,s),this.emit(`command:${s}`,l);break}}if(i){for(let l of this.commands)if(l.name===""){i=!1;let d=this.mri(o,l);this.setParsedInfo(d,l),this.emit("command:!",l);break}}if(i){let l=this.mri(r.slice(2));this.setParsedInfo(l)}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.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!==void 0)this.useEmoji=!this.options.noEmoji;if(this.options.theme)this.theme=String(this.options.theme);if(this.options.noCache!==void 0)this.isNoCache=Boolean(this.options.noCache);if(this.options.help&&this.showHelpOnExit)this.outputHelp(),t=!1,this.unsetMatchedCommand();if(this.options.version&&this.showVersionOnExit&&this.matchedCommandName==null)this.outputVersion(),t=!1,this.unsetMatchedCommand();let c={args:this.args,options:this.options};if(t)await this.runMatchedCommand();if(!this.matchedCommand&&this.args[0]){if(this.emit("command:*"),!(this.listenerCount("command:*")>0))this.showCommandNotFound(this.args[0])}return c}mri(r,e){let t=[...this.globalCommand.options,...e?e.options:[]],n=Cd(t),i=[],o=r.indexOf("--");if(o>-1)i=r.slice(o+1),r=r.slice(0,o);let s=Sd(r,n),c={_:s._};for(let f of Object.keys(s))if(f!=="_")c[$s(f)]=s[f];let l=c._,d={"--":i},u=e&&e.config.ignoreOptionDefaultValue?e.config.ignoreOptionDefaultValue:this.globalCommand.config.ignoreOptionDefaultValue,m=Object.create(null);for(let f of t){if(!u&&f.config.default!==void 0)for(let y of f.names)d[y]=f.config.default;if(Array.isArray(f.config.type)){if(m[f.name]===void 0)m[f.name]={shouldTransform:!0,transformFunction:f.config.type[0]}}}for(let f of Object.keys(c))if(f!=="_"){let y=f.split(".");kd(d,y,c[f]),Rd(d,m)}return{args:l,options:d}}async run(r=bs){return this.parse(r,{run:!0,exitOnError:!0})}handleUsageError(r){if(!(!!r&&typeof r==="object"&&r.name==="ClappError"&&r.isUsageError!==!1))return;let n=r,i=n.message??"command-line error",o=this.name?`${this.name}: `:"",s=/--help/.test(i)?"":`
|
|
30
30
|
Run \`${this.name??"cli"} --help\` for usage.`;ut.stderr.write(`${o}${i}${s}
|
|
31
|
-
`),ut.exit(n.exitCode??2)}async runMatchedCommand(){let{args:r,options:e,matchedCommand:t}=this;if(!t||!t.commandAction)return;t.checkUnknownOptions(),t.checkOptionValue(),t.checkRequiredArgs();let n=[];t.args.forEach((c,l)=>{if(c.variadic)n.push(r.slice(l));else n.push(r[l])}),n.push(e);let i={command:t,args:n,options:e};for(let c of t.beforeHooks)await c(i);let o,s=async()=>{let c=t.commandAction.apply(this,n);if(c instanceof Promise)o=await c;else o=c;return o};if(t.middleware.length>0){let c=0,l=async()=>{if(c<t.middleware.length){let d=t.middleware[c++];await d({...i,next:l})}else await s()};await l()}else await s();for(let c of t.afterHooks)await c(i);return o}removeSignalHandlers(){if(!this.signalHandlersSet)return this;return ut.removeAllListeners("SIGINT"),ut.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 ks{cache;enabled;cleanupInterval=null;hits=0;misses=0;constructor(){this.cache=new Map,this.enabled=!0,this.startCleanupInterval()}startCleanupInterval(){if(this.cleanupInterval)return;this.cleanupInterval=setInterval(()=>{this.cleanup()},30000),this.cleanupInterval.unref()}stopCleanup(){if(this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}isEnabled(){return this.enabled}get(r){if(!this.enabled){this.misses++;return}let e=this.cache.get(r);if(!e){this.misses++;return}if(Date.now()-e.timestamp>e.ttl){this.cache.delete(r),this.misses++;return}return this.hits++,e.value}set(r,e,t=5000){if(!this.enabled)return;this.cache.set(r,{value:e,timestamp:Date.now(),ttl:t})}has(r){if(!this.enabled)return!1;let e=this.cache.get(r);if(!e)return!1;if(Date.now()-e.timestamp>e.ttl)return this.cache.delete(r),!1;return!0}delete(r){this.cache.delete(r)}clear(){this.cache.clear()}disable(){this.enabled=!1,this.clear()}enable(){this.enabled=!0}stats(){return{size:this.cache.size,enabled:this.enabled,hits:this.hits,misses:this.misses}}resetStats(){this.hits=0,this.misses=0}keys(){return Array.from(this.cache.keys())}cleanup(){let r=Date.now();for(let[e,t]of this.cache.entries())if(r-t.timestamp>t.ttl)this.cache.delete(e)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var Wf=new ks;function Ld(){if("FORCE_COLOR"in nn.env)return nn.env.FORCE_COLOR!=="0";if("NO_COLOR"in nn.env||nn.env.TERM==="dumb")return!1;if(nn.platform==="win32")return!0;return ws.isatty(1)&&ws.isatty(2)}var Rs=Ld();function te(r,e){if(!Rs)return(t)=>t;return(t)=>r+t+e}var jd=te("\x1B[0m","\x1B[0m"),Fd=te("\x1B[31m","\x1B[39m"),Md=te("\x1B[32m","\x1B[39m"),Bd=te("\x1B[33m","\x1B[39m"),_d=te("\x1B[34m","\x1B[39m"),zd=te("\x1B[35m","\x1B[39m"),Ud=te("\x1B[36m","\x1B[39m"),Vd=te("\x1B[37m","\x1B[39m"),Wd=te("\x1B[90m","\x1B[39m"),qd=te("\x1B[1m","\x1B[22m"),Gd=te("\x1B[3m","\x1B[23m"),Kd=te("\x1B[4m","\x1B[24m"),Hd=te("\x1B[2m","\x1B[22m"),Jd=te("\x1B[7m","\x1B[27m"),Yd=te("\x1B[8m","\x1B[28m"),Zd=te("\x1B[9m","\x1B[29m"),Qd=te("\x1B[41m","\x1B[49m"),Xd=te("\x1B[42m","\x1B[49m"),eu=te("\x1B[43m","\x1B[49m"),tu=te("\x1B[44m","\x1B[49m"),nu=te("\x1B[45m","\x1B[49m"),ru=te("\x1B[46m","\x1B[49m"),iu=te("\x1B[47m","\x1B[49m"),ou=Rs,su={reset:jd,red:Fd,green:Md,yellow:Bd,blue:_d,magenta:zd,cyan:Ud,white:Vd,gray:Wd,bold:qd,italic:Gd,underline:Kd,dim:Hd,inverse:Jd,hidden:Yd,strikethrough:Zd,bgRed:Qd,bgGreen:Xd,bgYellow:eu,bgBlue:tu,bgMagenta:nu,bgCyan:ru,bgWhite:iu,isColorSupported:ou},au=su,lu=["up","down","left","right","space","enter","cancel"],Yf={actions:new Set(lu),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};var Zf=cu.platform.startsWith("win"),Qf=Symbol("clapp:cancel");var du=Pd();var ee=(r,e)=>du?r:e,Xf=ee("◆","*"),eg=ee("■","x"),tg=ee("▲","x"),ng=ee("◇","o"),rg=ee("┌","T"),uu=ee("│","|"),ig=ee("└","—"),og=ee("●",">"),sg=ee("○"," "),ag=ee("◻","[•]"),cg=ee("◼","[+]"),lg=ee("◻","[ ]"),dg=ee("▪","•"),ug=ee("─","-"),mg=ee("╮","+"),pg=ee("├","+"),fg=ee("╯","+"),gg=ee("●","•"),hg=ee("◆","*"),yg=ee("▲","!"),vg=ee("■","x");var bg={light:ee("─","-"),heavy:ee("━","="),block:ee("█","#")};function mu(){return`${au.gray(uu)} `}var wg=mu();var Ir="0.11.
|
|
31
|
+
`),ut.exit(n.exitCode??2)}async runMatchedCommand(){let{args:r,options:e,matchedCommand:t}=this;if(!t||!t.commandAction)return;t.checkUnknownOptions(),t.checkOptionValue(),t.checkRequiredArgs();let n=[];t.args.forEach((c,l)=>{if(c.variadic)n.push(r.slice(l));else n.push(r[l])}),n.push(e);let i={command:t,args:n,options:e};for(let c of t.beforeHooks)await c(i);let o,s=async()=>{let c=t.commandAction.apply(this,n);if(c instanceof Promise)o=await c;else o=c;return o};if(t.middleware.length>0){let c=0,l=async()=>{if(c<t.middleware.length){let d=t.middleware[c++];await d({...i,next:l})}else await s()};await l()}else await s();for(let c of t.afterHooks)await c(i);return o}removeSignalHandlers(){if(!this.signalHandlersSet)return this;return ut.removeAllListeners("SIGINT"),ut.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 ks{cache;enabled;cleanupInterval=null;hits=0;misses=0;constructor(){this.cache=new Map,this.enabled=!0,this.startCleanupInterval()}startCleanupInterval(){if(this.cleanupInterval)return;this.cleanupInterval=setInterval(()=>{this.cleanup()},30000),this.cleanupInterval.unref()}stopCleanup(){if(this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}isEnabled(){return this.enabled}get(r){if(!this.enabled){this.misses++;return}let e=this.cache.get(r);if(!e){this.misses++;return}if(Date.now()-e.timestamp>e.ttl){this.cache.delete(r),this.misses++;return}return this.hits++,e.value}set(r,e,t=5000){if(!this.enabled)return;this.cache.set(r,{value:e,timestamp:Date.now(),ttl:t})}has(r){if(!this.enabled)return!1;let e=this.cache.get(r);if(!e)return!1;if(Date.now()-e.timestamp>e.ttl)return this.cache.delete(r),!1;return!0}delete(r){this.cache.delete(r)}clear(){this.cache.clear()}disable(){this.enabled=!1,this.clear()}enable(){this.enabled=!0}stats(){return{size:this.cache.size,enabled:this.enabled,hits:this.hits,misses:this.misses}}resetStats(){this.hits=0,this.misses=0}keys(){return Array.from(this.cache.keys())}cleanup(){let r=Date.now();for(let[e,t]of this.cache.entries())if(r-t.timestamp>t.ttl)this.cache.delete(e)}destroy(){this.stopCleanup(),this.clear(),this.resetStats()}}var Wf=new ks;function Ld(){if("FORCE_COLOR"in nn.env)return nn.env.FORCE_COLOR!=="0";if("NO_COLOR"in nn.env||nn.env.TERM==="dumb")return!1;if(nn.platform==="win32")return!0;return ws.isatty(1)&&ws.isatty(2)}var Rs=Ld();function te(r,e){if(!Rs)return(t)=>t;return(t)=>r+t+e}var jd=te("\x1B[0m","\x1B[0m"),Fd=te("\x1B[31m","\x1B[39m"),Md=te("\x1B[32m","\x1B[39m"),Bd=te("\x1B[33m","\x1B[39m"),_d=te("\x1B[34m","\x1B[39m"),zd=te("\x1B[35m","\x1B[39m"),Ud=te("\x1B[36m","\x1B[39m"),Vd=te("\x1B[37m","\x1B[39m"),Wd=te("\x1B[90m","\x1B[39m"),qd=te("\x1B[1m","\x1B[22m"),Gd=te("\x1B[3m","\x1B[23m"),Kd=te("\x1B[4m","\x1B[24m"),Hd=te("\x1B[2m","\x1B[22m"),Jd=te("\x1B[7m","\x1B[27m"),Yd=te("\x1B[8m","\x1B[28m"),Zd=te("\x1B[9m","\x1B[29m"),Qd=te("\x1B[41m","\x1B[49m"),Xd=te("\x1B[42m","\x1B[49m"),eu=te("\x1B[43m","\x1B[49m"),tu=te("\x1B[44m","\x1B[49m"),nu=te("\x1B[45m","\x1B[49m"),ru=te("\x1B[46m","\x1B[49m"),iu=te("\x1B[47m","\x1B[49m"),ou=Rs,su={reset:jd,red:Fd,green:Md,yellow:Bd,blue:_d,magenta:zd,cyan:Ud,white:Vd,gray:Wd,bold:qd,italic:Gd,underline:Kd,dim:Hd,inverse:Jd,hidden:Yd,strikethrough:Zd,bgRed:Qd,bgGreen:Xd,bgYellow:eu,bgBlue:tu,bgMagenta:nu,bgCyan:ru,bgWhite:iu,isColorSupported:ou},au=su,lu=["up","down","left","right","space","enter","cancel"],Yf={actions:new Set(lu),aliases:new Map([["k","up"],["j","down"],["h","left"],["l","right"],["\x03","cancel"],["escape","cancel"]]),messages:{cancel:"Canceled",error:"Something went wrong"}};var Zf=cu.platform.startsWith("win"),Qf=Symbol("clapp:cancel");var du=Pd();var ee=(r,e)=>du?r:e,Xf=ee("◆","*"),eg=ee("■","x"),tg=ee("▲","x"),ng=ee("◇","o"),rg=ee("┌","T"),uu=ee("│","|"),ig=ee("└","—"),og=ee("●",">"),sg=ee("○"," "),ag=ee("◻","[•]"),cg=ee("◼","[+]"),lg=ee("◻","[ ]"),dg=ee("▪","•"),ug=ee("─","-"),mg=ee("╮","+"),pg=ee("├","+"),fg=ee("╯","+"),gg=ee("●","•"),hg=ee("◆","*"),yg=ee("▲","!"),vg=ee("■","x");var bg={light:ee("─","-"),heavy:ee("━","="),block:ee("█","#")};function mu(){return`${au.gray(uu)} `}var wg=mu();var Ir="0.11.3";import{existsSync as Pr}from"node:fs";import{mkdir as fu,writeFile as Is}from"node:fs/promises";function Er(r){r.command("init","Initialize a new ts-cloud project").option("--mode <mode>","Deployment mode: server or serverless").option("--name <name>","Project name").option("--region <region>","AWS Region").action(async(e)=>{if(w("Initializing ts-cloud Project"),Pr("cloud.config.ts")){if(!await O("cloud.config.ts already exists. Overwrite?",!1)){a("Initialization cancelled");return}}let t=e?.name||await Z("Project name","my-app"),n=e?.mode||await xr("Select deployment mode",["serverless","server"]),i=e?.region||await xr("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),o=new A("Creating configuration file...");o.start();let s=`import { defineConfig } from '@ts-cloud/core'
|
|
32
32
|
|
|
33
33
|
export default defineConfig({
|
|
34
34
|
project: {
|
|
@@ -59,7 +59,7 @@ dist/
|
|
|
59
59
|
cloudformation/
|
|
60
60
|
*.log
|
|
61
61
|
.DS_Store
|
|
62
|
-
`),h("Created .gitignore");if(!Pr("cloudformation"))await fu("cloudformation",{recursive:!0}),h("Created cloudformation/ directory");
|
|
62
|
+
`),h("Created .gitignore");if(!Pr("cloudformation"))await fu("cloudformation",{recursive:!0}),h("Created cloudformation/ directory");Xe(`ts-cloud project initialized!
|
|
63
63
|
|
|
64
64
|
Next steps:
|
|
65
65
|
1. Edit cloud.config.ts to configure your infrastructure
|
|
@@ -79,7 +79,7 @@ Next steps:
|
|
|
79
79
|
`)}function Lr(r){let e=new Map;for(let t of r.split(`
|
|
80
80
|
`)){let n=t.trim();if(!n.startsWith("cert:"))continue;let[,i,o]=n.split(":");if(i&&o)e.set(i,o)}return e}function Gs(r,e){for(let[t,n]of r){if(n==="absent")continue;if(e.get(t)!==n)return!1}return!0}function Ks(r,e,t){let n=e.flatMap((i)=>[`${i}.crt`,`${i}.key`]);return["set -eu",`rm -f ${J(t)}`,`cd ${J(r)} 2>/dev/null || { echo "no certs dir at ${r}" >&2; exit 0; }`,`TS_CLOUD_CERTS="$(ls ${n.map(J).join(" ")} 2>/dev/null | tr '\\n' ' ' || true)"`,'[ -n "$TS_CLOUD_CERTS" ] || { echo "no certificates to carry"; exit 0; }',`tar czf ${J(t)} $TS_CLOUD_CERTS`].join(`
|
|
81
81
|
`)}function Hs(r,e){return["set -eu",`[ -s ${J(e)} ] || { echo "no certificate archive staged"; exit 0; }`,`mkdir -p ${J(r)}`,`tar xzf ${J(e)} -C ${J(r)}`,`chmod 600 ${J(r)}/*.key 2>/dev/null || true`,`rm -f ${J(e)}`].join(`
|
|
82
|
-
`)}async function Js(r,e){let{slug:t,siteName:n,appBase:i,from:o,to:s,targetAddress:c}=r;if(o===s)throw Error(`'${n}' is already on ${s}.`);if(r.database&&!e.database)throw Error(`'${n}' uses the on-box database '${r.database.name}', which lives in the engine's own data directory rather than in the site tree, and this move has no way to carry it. Moving the site without it would cut traffic over to an empty database.`);if(r.database&&e.database&&await e.database.targetHasData())throw Error(`${s} already has a database named '${r.database.name}' with tables in it. Restoring this site's dump over it would destroy that data. Rename or drop it on the target first, or point this project at a different database name.`);let l=[{id:"pause-workers",title:`Stop background work on ${o} so the snapshot is consistent`,satisfied:async()=>$u(await e.runOnSource(Su(t,n))),apply:async()=>{await e.runOnSource(wu(t,n))}},...e.database?[{id:"database-dump",title:`Dump the '${r.database?.name}' database on ${o}`,satisfied:async()=>!1,apply:()=>e.database.dump()}]:[],{id:"snapshot",title:`Archive the site's tree and units on ${o}`,change:{from:`${o}:${i}`,to:Mt(t,n)},satisfied:async()=>!1,apply:async()=>{await e.runOnSource(Cu(t,n,i))}},{id:"transfer",title:`Carry the archive to ${s}`,change:{from:o,to:s},satisfied:()=>e.archiveStaged(),apply:()=>e.transferArchive()},...e.database?[{id:"database-transfer",title:`Carry the database dump to ${s}`,change:{from:o,to:s},satisfied:()=>e.database.dumpStaged(),apply:()=>e.database.transferDump()},{id:"database-restore",title:`Create and load '${r.database?.name}' on ${s}`,satisfied:async()=>!1,apply:()=>e.database.restore()}]:[],{id:"restore",title:`Unpack the site on ${s} and start it`,change:{from:Mt(t,n),to:`${s}:${i}`},satisfied:async()=>Ru(await e.runOnTarget(ku(t,n,i))),apply:async()=>{await e.runOnTarget(Au(t,n,i))}}];if(r.port!=null){let{port:d}=r;l.push({id:"health",title:`Wait for the site to answer on ${s} (127.0.0.1:${d})`,satisfied:async()=>!1,apply:async()=>{await e.runOnTarget(Iu(d,r.healthCheckPath??"/"))}})}return l.push(...e.certificates?[{id:"certificates",title:`Carry the site's TLS material to ${s}`,satisfied:()=>e.certificates.inPlace(),apply:()=>e.certificates.carry()}]:[],{id:"gateway",title:`Route the site on ${s}`,satisfied:()=>e.targetRoutesSite(),apply:()=>e.refreshTargetGateway()},{id:"dns",title:`Point DNS at ${s}`,change:{from:`${o}`,to:c},satisfied:async()=>await e.publishedAddress()===c,apply:async()=>{let d=await e.cutoverDns();if(d.length>0)throw Error(d.join("; "))}},{id:"drain-source",title:`Stop the site on ${o}, leaving its files in place`,satisfied:async()=>Eu(await e.runOnSource(xu(t,n))),apply:async()=>{await e.runOnSource(Pu(t,n))}}),{operation:"site:move",target:n,steps:l}}async function kt(r,e,t){let n=await r.exec(e,t);if(n.code!==0)throw Error(`${e.name}: ${n.stderr.trim()||n.stdout.trim()||`exited ${n.code}`}`);return n.stdout}async function Bt(r,e,t){let n=Bun.spawn(["scp","-P",String(r.sshPort),"-o","BatchMode=yes","-o","StrictHostKeyChecking=yes",e,t],{stdout:"pipe",stderr:"pipe"}),[i,o]=await Promise.all([n.exited,new Response(n.stderr).text()]);if(i!==0)throw Error(`scp failed: ${o.trim()||`exited ${i}`}`)}async function Ou(r,e){if(!e.to)throw Error("Name the target server with --to <server>.");let t=await P(),n=t.sites?.[r];if(!n)throw Error(`Site '${r}' is not in cloud.config.ts.`);let i=t.project.slug,o=q(process.cwd(),t);try{let c=new
|
|
82
|
+
`)}async function Js(r,e){let{slug:t,siteName:n,appBase:i,from:o,to:s,targetAddress:c}=r;if(o===s)throw Error(`'${n}' is already on ${s}.`);if(r.database&&!e.database)throw Error(`'${n}' uses the on-box database '${r.database.name}', which lives in the engine's own data directory rather than in the site tree, and this move has no way to carry it. Moving the site without it would cut traffic over to an empty database.`);if(r.database&&e.database&&await e.database.targetHasData())throw Error(`${s} already has a database named '${r.database.name}' with tables in it. Restoring this site's dump over it would destroy that data. Rename or drop it on the target first, or point this project at a different database name.`);let l=[{id:"pause-workers",title:`Stop background work on ${o} so the snapshot is consistent`,satisfied:async()=>$u(await e.runOnSource(Su(t,n))),apply:async()=>{await e.runOnSource(wu(t,n))}},...e.database?[{id:"database-dump",title:`Dump the '${r.database?.name}' database on ${o}`,satisfied:async()=>!1,apply:()=>e.database.dump()}]:[],{id:"snapshot",title:`Archive the site's tree and units on ${o}`,change:{from:`${o}:${i}`,to:Mt(t,n)},satisfied:async()=>!1,apply:async()=>{await e.runOnSource(Cu(t,n,i))}},{id:"transfer",title:`Carry the archive to ${s}`,change:{from:o,to:s},satisfied:()=>e.archiveStaged(),apply:()=>e.transferArchive()},...e.database?[{id:"database-transfer",title:`Carry the database dump to ${s}`,change:{from:o,to:s},satisfied:()=>e.database.dumpStaged(),apply:()=>e.database.transferDump()},{id:"database-restore",title:`Create and load '${r.database?.name}' on ${s}`,satisfied:async()=>!1,apply:()=>e.database.restore()}]:[],{id:"restore",title:`Unpack the site on ${s} and start it`,change:{from:Mt(t,n),to:`${s}:${i}`},satisfied:async()=>Ru(await e.runOnTarget(ku(t,n,i))),apply:async()=>{await e.runOnTarget(Au(t,n,i))}}];if(r.port!=null){let{port:d}=r;l.push({id:"health",title:`Wait for the site to answer on ${s} (127.0.0.1:${d})`,satisfied:async()=>!1,apply:async()=>{await e.runOnTarget(Iu(d,r.healthCheckPath??"/"))}})}return l.push(...e.certificates?[{id:"certificates",title:`Carry the site's TLS material to ${s}`,satisfied:()=>e.certificates.inPlace(),apply:()=>e.certificates.carry()}]:[],{id:"gateway",title:`Route the site on ${s}`,satisfied:()=>e.targetRoutesSite(),apply:()=>e.refreshTargetGateway()},{id:"dns",title:`Point DNS at ${s}`,change:{from:`${o}`,to:c},satisfied:async()=>await e.publishedAddress()===c,apply:async()=>{let d=await e.cutoverDns();if(d.length>0)throw Error(d.join("; "))}},{id:"drain-source",title:`Stop the site on ${o}, leaving its files in place`,satisfied:async()=>Eu(await e.runOnSource(xu(t,n))),apply:async()=>{await e.runOnSource(Pu(t,n))}}),{operation:"site:move",target:n,steps:l}}async function kt(r,e,t){let n=await r.exec(e,t);if(n.code!==0)throw Error(`${e.name}: ${n.stderr.trim()||n.stdout.trim()||`exited ${n.code}`}`);return n.stdout}async function Bt(r,e,t){let n=Bun.spawn(["scp","-P",String(r.sshPort),"-o","BatchMode=yes","-o","StrictHostKeyChecking=yes",e,t],{stdout:"pipe",stderr:"pipe"}),[i,o]=await Promise.all([n.exited,new Response(n.stderr).text()]);if(i!==0)throw Error(`scp failed: ${o.trim()||`exited ${i}`}`)}async function Ou(r,e){if(!e.to)throw Error("Name the target server with --to <server>.");let t=await P(),n=t.sites?.[r];if(!n)throw Error(`Site '${r}' is not in cloud.config.ts.`);let i=t.project.slug,o=q(process.cwd(),t);try{let c=new et(o.store).list(o.project.id),l=(L)=>{let H=c.find((j)=>j.id===L||j.name===L);if(!H)throw Error(`Server ${L} was not found. Run \`cloud server:list\`.`);if(H.trustState!=="pinned")throw Error(`${H.name} has no pinned host key. Run \`cloud server:validate ${H.name}\` first.`);return H},d=l(e.to),u=c.filter((L)=>L.id!==d.id);if(!e.from&&u.length!==1)throw Error(`Name the source server with --from <server> — ${u.length} others are enrolled.`);let m=l(e.from??u[0].name),f=new Mn,y=Zs(i,r),S=Mt(i,r),g=n.domain,b=t.infrastructure?.dns?.provider,I=t.infrastructure?.compute?.proxy?.engine==="rpx"?t.infrastructure.compute.proxy:void 0,v=t.infrastructure?.dns?.domain,C=(L)=>v&&L.endsWith(v)?v:L.split(".").slice(-2).join("."),k=Fn(t),T=k?.name&&Qs(k)?{name:k.name}:void 0,E=`/tmp/ts-cloud-move-${i}-${r}.sql.gz`,D=Ws(i,r),N=I?.certsDir??ta,_=[g,...n.aliases??[]].filter((L)=>!!L),B=k?.engine??"mysql",V={runOnSource:(L)=>kt(f,m,L),runOnTarget:(L)=>kt(f,d,L),archiveStaged:async()=>(await f.exec(d,`test -f ${S} && echo staged || true`)).stdout.includes("staged"),transferArchive:async()=>{let L=`${process.cwd()}/.ts-cloud-move-${i}-${r}.tar.gz`;await Bt(m,`${m.sshUser}@${m.endpoint}:${S}`,L),await Bt(d,L,`${d.sshUser}@${d.endpoint}:${S}`),await Bun.file(L).delete().catch(()=>{})},targetRoutesSite:async()=>{if(!I||!g)return!0;return(await f.exec(d,`grep -qF ${JSON.stringify(g)} /etc/rpx/sites.d/${i}.json 2>/dev/null && echo routed || true`)).stdout.includes("routed")},refreshTargetGateway:async()=>{if(!I)throw Error(`This project does not run the rpx gateway, so the move cannot publish a route. Point ${d.name}'s web server at ${y}/current yourself, then re-run to continue.`);await kt(f,d,ra({config:na(t.sites??{},{proxy:I,slug:i}),slug:i,preserveManagementDashboardRoutes:!0}).join(`
|
|
83
83
|
`))},publishedAddress:async()=>{if(!g||!b)return d.endpoint;let L=ce(b);if(!L)return;return await jr(Re(L),C(g),g,d.endpoint,"A")?d.endpoint:void 0},...T?{database:{dump:async()=>{await kt(f,m,["set -euo pipefail",'eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true',...ia(B,T.name,"/tmp",k),`mv -f "$(ls -1t /tmp/${T.name}-*.sql.gz | head -1)" ${E}`].join(`
|
|
84
84
|
`))},dumpStaged:async()=>(await f.exec(d,`test -s ${E} && echo staged || true`)).stdout.includes("staged"),transferDump:async()=>{let L=`${process.cwd()}/.ts-cloud-move-${i}-${r}.sql.gz`;await Bt(m,`${m.sshUser}@${m.endpoint}:${E}`,L),await Bt(d,L,`${d.sshUser}@${d.endpoint}:${E}`),await Bun.file(L).delete().catch(()=>{})},restore:async()=>{await kt(f,d,[...Xs(k,t.infrastructure?.compute?.managedServices??{}),...ea(k,{from:E}),`rm -f ${E}`].join(`
|
|
85
85
|
`))},targetHasData:async()=>{let L=B==="postgres"?`psql -tAc "select count(*) from information_schema.tables where table_schema='public'" -d ${T.name} 2>/dev/null || echo 0`:`mysql -N -B -e "select count(*) from information_schema.tables where table_schema='${T.name}'" 2>/dev/null || echo 0`,H=await f.exec(d,`eval "$(cd /opt/pantry && pantry env 2>/dev/null)" || true
|
|
@@ -315,9 +315,9 @@ Resources to create: ${k}`);let T={};for(let E of Object.values(d.Resources||{})
|
|
|
315
315
|
No changes detected - infrastructure is up to date`);return}if(a(`
|
|
316
316
|
Changes detected:
|
|
317
317
|
`),b.length>0){h(`Resources to add (${b.length}):`);for(let C of b){let k=y[C].Type;a(` + ${C} (${k})`)}console.log()}if(I.length>0){p(`Resources to remove (${I.length}):`);for(let C of I){let k=f[C].Type;a(` - ${C} (${k})`)}console.log()}if(v.length>0){R(`Resources to modify (${v.length}):`);for(let C of v){let k=y[C].Type;a(` ~ ${C} (${k})`)}console.log()}a("Summary:"),a(` - Add: ${b.length}`),a(` - Remove: ${I.length}`),a(` - Modify: ${v.length}`),a("\nRun `cloud deploy` to apply these changes")}catch(t){p(`Diff failed: ${t.message}`)}})}function gt(r,e=0){let t=Number(r);return Number.isFinite(t)?t:e}function qr(r){return gt(r)*1024}function Gu(r){let e=r?.date?.trim(),t=r?.time?.trim();if(!e||!t)return null;let n=new Date(`${e}T${t}Z`);return Number.isFinite(n.getTime())?n.toISOString():null}function Ku(r,e,t){let n;try{n=JSON.parse(r)}catch{return[]}let i=(n?.sysstat?.hosts??[]).flatMap((s)=>s.statistics??[]),o=[];for(let s of i){let c=Gu(s.timestamp);if(!c)continue;let l=new Date(c).getTime();if(l<e.getTime()||l>t.getTime())continue;let d=s["cpu-load"]?.find((m)=>m.cpu==="all")??s["cpu-load"]?.[0],u=gt(d?.idle,100);o.push({timestamp:c,cpuUsedPercent:Math.max(0,Math.min(100,100-u)),cpuUserPercent:gt(d?.user),cpuSystemPercent:gt(d?.system),cpuIowaitPercent:gt(d?.iowait),memoryUsedPercent:gt(s.memory?.["memused-percent"]),memoryUsedBytes:qr(s.memory?.memused),memoryAvailableBytes:qr(s.memory?.avail),swapUsedPercent:gt(s.memory?.["swpused-percent"]),swapUsedBytes:qr(s.memory?.swpused)})}return o.sort((s,c)=>s.timestamp.localeCompare(c.timestamp))}function Hu(r){return Object.fromEntries(Object.entries(r.time_series??{}).map(([e,t])=>[e,(t.values??[]).map(([n,i])=>({timestamp:new Date(Number(n)*1000).toISOString(),value:gt(i,Number.NaN)})).filter((n)=>Number.isFinite(n.value))]))}function ga(r){return r.length?r.reduce((e,t)=>e+t,0)/r.length:null}function Gr(r){return r.length?Math.max(...r):null}function Ju(r){return r.length?Math.min(...r):null}async function ha(r){let[e,t]=await Promise.all([r.client.getServerMetrics(r.serverId,{types:["cpu","disk","network"],start:r.from,end:r.to,step:r.step}),Kr(r.remote.host,"LC_ALL=C sadf -j -- -u -r -S",r.remote).catch(()=>"")]),n=Ku(t,r.from,r.to),i=Hu(e),s=(i.cpu??[]).map((m)=>Math.max(0,Math.min(100,m.value/Math.max(1,r.cores)))),c=n.map((m)=>m.cpuUsedPercent),l=s.length?s:c,d=n.map((m)=>m.memoryUsedPercent),u=n.map((m)=>m.swapUsedPercent);return{from:r.from.toISOString(),to:r.to.toISOString(),cores:r.cores,host:n,provider:i,summary:{cpuAveragePercent:ga(l),cpuPeakPercent:Gr(l),memoryAveragePercent:ga(d),memoryPeakPercent:Gr(d),memoryMinimumAvailableBytes:Ju(n.map((m)=>m.memoryAvailableBytes)),swapPeakPercent:Gr(u)}}}function Yu(r){if(r.length===0)throw Error("A server name cannot be empty.");if(r.length>253)throw Error(`'${r}' is longer than the 253 characters a hostname allows.`);for(let e of r.split(".")){if(e.length===0)throw Error(`'${r}' has an empty label — a hostname cannot contain '..'.`);if(e.length>63)throw Error(`'${e}' is longer than the 63 characters a hostname label allows.`);if(!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(e))throw Error(`'${e}' is not a valid hostname label. Use letters, digits and hyphens, not starting or ending with a hyphen.`)}}async function ya(r,e,t){if(Yu(e),r===e)throw Error(`'${r}' is already named that.`);if((await t.takenNames()).some((o)=>o===e&&o!==r))throw Error(`'${e}' is already taken. Server names have to be unique within a provider project.`);let i=[];if(t.providerName&&t.renameProvider){let{providerName:o,renameProvider:s}=t;i.push({id:"provider",title:"Rename the server at the provider",change:{from:r,to:e},satisfied:async()=>await o()===e,apply:()=>s(e)})}if(t.stateName&&t.writeStateName){let{stateName:o,writeStateName:s}=t;i.push({id:"state-pin",title:"Update the recorded name in the local driver state",change:{from:r,to:e},satisfied:async()=>{let c=await o();return c===void 0||c===e},apply:()=>s(e)})}if(t.remoteHostname&&t.setRemoteHostname){let{remoteHostname:o,setRemoteHostname:s}=t;i.push({id:"hostname",title:"Set the hostname on the box",change:{from:r,to:e},satisfied:async()=>await o()===e,apply:()=>s(e)})}return i.push({id:"inventory",title:"Rename the fleet inventory record",change:{from:r,to:e},satisfied:async()=>t.inventoryName()===e,apply:async()=>{await t.renameInventory(e)}}),{operation:"server:rename",target:r,steps:i}}function va(r){let e=`'${r.replace(/'/g,`'"'"'`)}'`;return["set -eu",'TS_CLOUD_OLD="$(hostname)"',`hostnamectl set-hostname ${e} 2>/dev/null || { echo ${e} > /etc/hostname && hostname ${e}; }`,'if grep -q "127.0.1.1" /etc/hosts; then',` sed -i "s/^127\\.0\\.1\\.1.*/127.0.1.1\\t${r}/" /etc/hosts`,"else",` printf '127.0.1.1\\t%s\\n' ${e} >> /etc/hosts`,"fi",'printf "%s -> %s\\n" "$TS_CLOUD_OLD" "$(hostname)"'].join(`
|
|
318
|
-
`)}async function Zu(){let r=await P(),e=q(process.cwd(),r),t=new Xe(e.store),n=new Ta(t,[new Un("aws"),new Un("hetzner"),new Un("ssh")]);return{controlPlane:e,store:t,service:n}}async function Ue(r){let e=await Zu();try{return await r(e)}finally{e.controlPlane.store.close()}}var yt=(r,e)=>{let t=r.store.list(r.controlPlane.project.id).find((n)=>n.id===e||n.name===e);if(!t)throw Error(`Server ${e} was not found.`);return t},fe=(r)=>{p(r instanceof Error?r.message:String(r)),process.exitCode=1};function Zr(r,e){let t=Number.parseInt(r??"",10);return Number.isFinite(t)&&t>=0?t:e}function Qu(r="3h"){let e=r.match(/^(\d+)(m|h)$/);if(!e)throw Error("--range must use minutes or hours, for example 30m or 3h.");let t=Number(e[1])*(e[2]==="h"?3600000:60000);if(t<300000||t>86400000)throw Error("--range must be between 5 minutes and 24 hours.");return t}function cn(r,e="%"){return r==null?"unavailable":`${r.toFixed(1)}${e}`}async function Xu(r,e){let t=await P();if(t.cloud?.provider!=="hetzner")throw Error("server:monitoring currently requires the Hetzner provider.");let n=e.env??process.env.CLOUD_ENV??"production",i=Ve(t,n),o=await Rt(i);if(!o?.serverId)throw Error(`No pinned Hetzner server was found for ${i}.`);if(![o.serverName,String(o.serverId),o.stackName].includes(r))throw Error(`Refusing to read ${r}: project state is pinned to ${o.serverName}.`);let s=ln(t),c=new Vt({apiToken:Wt(s.apiToken,t)}),l=await c.getServer(o.serverId),d=o.publicIp??l.public_net.ipv4?.ip;if(!d)throw Error(`Server ${l.name} has no public IPv4 address.`);let u=new Date,m=new Date(u.getTime()-Qu(e.range)),f=Math.min(3600,Math.max(60,Zr(e.step,60))),y=await ha({client:c,serverId:l.id,cores:Math.max(1,Number(l.server_type.cores)||1),from:m,to:u,step:f,remote:{host:d,user:o.sshUser??s.sshUser,identityFile:s.sshPrivateKeyPath,connectTimeoutSec:15}});if(e.json){console.log(JSON.stringify({schemaVersion:1,server:l.name,result:y},null,2));return}x(["Server","Window","CPU average / peak","RAM average / peak","Minimum available","Swap peak"],[[l.name,`${y.from} to ${y.to}`,`${cn(y.summary.cpuAveragePercent)} / ${cn(y.summary.cpuPeakPercent)}`,`${cn(y.summary.memoryAveragePercent)} / ${cn(y.summary.memoryPeakPercent)}`,y.summary.memoryMinimumAvailableBytes==null?"unavailable":`${(y.summary.memoryMinimumAvailableBytes/1073741824).toFixed(2)} GB`,cn(y.summary.swapPeakPercent)]])}function Na(r){let e=r.targetType.prices?.find((t)=>t.location===r.location);return{serverId:r.serverId,serverName:r.serverName,location:r.location,from:r.currentType.name,to:r.targetType.name,capacityAvailable:r.capacityAvailable,alreadyComplete:r.alreadyComplete,upgradeDisk:r.upgradeDisk,target:{cores:r.targetType.cores,memoryGb:r.targetType.memory,diskGb:r.targetType.disk,architecture:r.targetType.architecture,cpuType:r.targetType.cpu_type},price:e?{hourlyNet:e.price_hourly.net,hourlyGross:e.price_hourly.gross,monthlyNet:e.price_monthly.net,monthlyGross:e.price_monthly.gross}:void 0}}function em(r){let e=Na(r);x(["Server","Location","Resize","Capacity","CPU / RAM / disk","Monthly"],[[`${e.serverName} (${e.serverId})`,e.location,`${e.from} -> ${e.to}`,e.capacityAvailable?"available":"waiting",`${e.target.cores??0} / ${e.target.memoryGb??0} GB / ${e.target.diskGb??0} GB`,e.price?.monthlyGross??e.price?.monthlyNet??"not reported"]])}async function tm(r,e,t){let n=await P();if(n.cloud?.provider!=="hetzner")throw Error("server:resize currently requires the Hetzner provider.");let i=t.env??process.env.CLOUD_ENV??"production",o=Ve(n,i),s=await Rt(o),c=ln(n),l=new Vt({apiToken:Wt(c.apiToken,n)}),u=s&&(r===s.serverName||r===String(s.serverId)||r===s.stackName)?await l.getServer(s.serverId):(await l.listServers()).find((E)=>E.name===r||String(E.id)===r);if(!u)throw Error(`Hetzner server ${r} was not found.`);if(s?.serverId&&s.serverId!==u.id)throw Error(`Refusing to resize ${r}: project state is pinned to server ${s.serverId}.`);let m=wa(e),f=t.upgradeDisk!==!1,y=await Jr(l,u.id,m,f);if(t.json)console.log(JSON.stringify({schemaVersion:1,plan:Na(y)},null,2));else em(y);if(!t.apply){if(!t.json)a(`Preview only. Re-run with --apply --confirm ${u.name} to execute.`);return}if(t.confirm!==u.name)throw Error(`Resize requires --confirm ${u.name}.`);let S=s?.publicIp??u.public_net.ipv4?.ip;if(!S)throw Error(`Server ${u.name} has no public IPv4 address for verification.`);let g={host:S,user:s?.sshUser??c.sshUser,identityFile:c.sshPrivateKeyPath,connectTimeoutSec:15,sshTimeoutMs:600000,routeTimeoutMs:20000},b=await Ea(o),I=new Date().toISOString(),v={schemaVersion:1,operationId:b?.targetType===m&&b.serverId===u.id?b.operationId:crypto.randomUUID(),stackName:o,serverId:u.id,serverName:u.name,sourceType:y.currentType.name,targetType:m,upgradeDisk:f,phase:"planning",status:"running",attempts:b?.targetType===m?b.attempts:0,startedAt:b?.targetType===m?b.startedAt:I,updatedAt:I,manifest:b?.targetType===m?b.manifest:void 0};await ht(v);let C=Zr(t.pollSeconds,900)*1000,k=Zr(t.maxWaitMinutes,0)*60000,T=Date.now();for(;;){let E=await Jr(l,u.id,m,f);if(!E.capacityAvailable&&!E.alreadyComplete){if(v={...v,phase:"waiting-capacity",status:"waiting-capacity",updatedAt:new Date().toISOString()},await ht(v),!t.wait){if(!t.json)a(`${m} has no capacity in ${E.location}. The server remains running and unchanged. Use --wait to poll safely.`);return}if(k>0&&Date.now()-T>=k)throw Error(`Timed out waiting for ${m} capacity in ${E.location}.`);if(!t.json)a(`Waiting ${Math.round(C/1000)} seconds for ${m} capacity...`);await Bun.sleep(C);continue}let D=await Da(o);try{let N=await(async()=>{try{return v={...v,status:"running",attempts:v.attempts+1,updatedAt:new Date().toISOString()},await ht(v),await $a({client:l,serverId:u.id,targetType:m,upgradeDisk:f,resumeFromOff:v.phase==="shutting-down"||v.phase==="resizing"||v.phase==="powering-on"||v.phase==="verifying",manifest:v.manifest,hooks:{preflight:async()=>{let _=await Ca(g);return v={...v,manifest:_,updatedAt:new Date().toISOString()},await ht(v),_},afterBoot:async(_,B,V,K)=>Aa(g,B,V,K),onPhase:async(_,B)=>{if(v={...v,phase:_,status:_==="waiting-capacity"?"waiting-capacity":_==="complete"?"completed":"running",updatedAt:new Date().toISOString()},await ht(v),!t.json)a(`${_}${B?` ${JSON.stringify(B)}`:""}`)}}})}finally{await D()}})();if(v={...v,status:N.status==="completed"||N.status==="already-complete"?"completed":N.status==="recovered"?"recovered":"waiting-capacity",lastError:N.status==="recovered"?N.error:void 0,updatedAt:new Date().toISOString()},await ht(v),t.json)console.log(JSON.stringify({schemaVersion:1,result:N},null,2));else if(N.status==="completed"||N.status==="already-complete")h(`${u.name} is on ${m} and passed full post-boot verification.`);else if(N.status==="recovered"&&N.retryable&&t.wait){a(`Capacity race recovered safely. Waiting ${Math.round(C/1000)} seconds before retry.`),await Bun.sleep(C);continue}else if(N.status==="recovered")throw Error(`Resize did not complete and the original server was recovered: ${N.error}`);return}catch(N){throw v={...v,status:"failed",lastError:N instanceof Error?N.message:String(N),updatedAt:new Date().toISOString()},await ht(v),N}}}async function nm(r,e){let t=await P();if(t.cloud?.provider!=="hetzner")throw Error("server:optimize currently requires the Hetzner provider.");let n=e.env??process.env.CLOUD_ENV??"production",i=Ve(t,n),o=await Rt(i);if(!o?.serverId)throw Error(`No pinned Hetzner server was found for ${i}.`);if(![o.serverName,String(o.serverId),o.stackName].includes(r))throw Error(`Refusing to optimize ${r}: project state is pinned to ${o.serverName}.`);let s=ln(t),c=new Vt({apiToken:Wt(s.apiToken,t)}),l=await c.getServer(o.serverId),d=o.publicIp??l.public_net.ipv4?.ip;if(!d)throw Error(`Server ${l.name} has no public IPv4 address.`);let u=Ra(t);if(e.json)console.log(JSON.stringify({schemaVersion:1,server:l.name,plan:u},null,2));else x(["Server","Firewall ports","Monitoring","Auto updates","Swap","SSH passwords"],[[l.name,u.firewallPorts.join(","),u.monitoring?"on":"off",u.autoUpdates?"on":"off",`${u.swapGb} GB`,"disabled"]]);if(!e.apply){if(!e.json)a(`Preview only. Re-run with --apply --confirm ${l.name} to execute.`);return}if(e.confirm!==l.name)throw Error(`Optimization requires --confirm ${l.name}.`);let m={host:d,user:o.sshUser??s.sshUser,identityFile:s.sshPrivateKeyPath,connectTimeoutSec:15},f=await Yr(m),y=f.routeProbes.filter((V)=>!V.ok),S=t.infrastructure?.compute,g=Hr(S)?new Set(["nginx.service","bun-gateway.service","ts-cloud-nginx.service"]):new Set,b=f.failedUnits.filter((V)=>!g.has(V));if(b.length>0)throw Error(`Preflight found failed services: ${b.join(", ")}`);if(y.length>0)throw Error(`Preflight found unhealthy routes: ${y.map((V)=>V.domain).join(", ")}`);let I=Hr(S)?[]:Object.values(t.sites??{}).flatMap((V)=>typeof V?.port==="number"&&![80,443].includes(V.port)?[V.port]:[]),v=ba({allowSsh:S?.allowSsh!==!1,sitePorts:I,allowedPorts:S?.firewall?.allowedPorts}),C=(o.firewallId?(await c.listFirewalls()).find((V)=>V.id===o.firewallId):void 0)??(await c.listFirewalls()).find((V)=>V.name===`${t.project.slug}-${n}-app-fw`);if(!C)throw Error(`Managed firewall for ${l.name} was not found.`);let k=await c.setFirewallRules(C.id,v);await Promise.all(k.map((V)=>c.waitForAction(V.id))),await Ia(t,m);let[T,E]=await Promise.all([Yr(m),Pa(m)]),D=xa(u,E),N=ka(f,T),_=T.routeProbes.filter((V)=>!V.ok);if(N.stoppedServices.length>0)D.push(`services stopped: ${N.stoppedServices.join(", ")}`);if(N.changedRouteFragments.length>0)D.push(`route fragments changed: ${N.changedRouteFragments.join(", ")}`);if(N.missingRouteIds.length>0)D.push(`routes disappeared: ${N.missingRouteIds.join(", ")}`);if(N.changedReleaseLinks.length>0)D.push(`release links changed: ${N.changedReleaseLinks.join(", ")}`);if(N.missingData.length>0)D.push(`databases or volumes disappeared: ${N.missingData.join(", ")}`);if(_.length>0)D.push(`unhealthy routes: ${_.map((V)=>V.domain).join(", ")}`);if(D.length>0)throw Error(`Host optimization verification failed: ${D.join("; ")}`);let B={serverId:l.id,serverName:l.name,services:T.runningServices.length,routes:T.routeIds.length,healthyDomains:T.routeProbes.length,releaseLinks:T.releaseLinks.length,databasesAndVolumes:T.dataCatalog.length,report:E};if(e.json)console.log(JSON.stringify({schemaVersion:1,result:B},null,2));else h(`${l.name} passed full host, rpx route, service, release, and data verification.`)}async function rm(r,e,t){let n=await P(),i=t.env??"production",o=Ve(n,i);await Ue(async(s)=>{let c=yt(s,r),l=s.store.list(s.controlPlane.project.id,!0),d=c.provider==="hetzner"&&c.providerId?(()=>{try{let C=ln(n);return new Vt({apiToken:Wt(C.apiToken,n)})}catch{return null}})():null,u=c.providerId?Number(c.providerId):Number.NaN,m=await Rt(o),f=m!=null&&m.serverId===u,y=new Mn,S=c.trustState==="pinned",g={takenNames:async()=>{let C=l.map((k)=>k.name);if(d)C.push(...(await d.listServers()).map((k)=>k.name));return C},inventoryName:()=>s.store.get(c.id)?.name??c.name,renameInventory:(C)=>{s.store.update(c.id,{name:C})},...d&&Number.isFinite(u)?{providerName:async()=>(await d.getServer(u)).name,renameProvider:async(C)=>{await d.renameServer(u,C)}}:{},...f?{stateName:async()=>(await Rt(o))?.serverName,writeStateName:async(C)=>{let k=await Rt(o);if(k)await Sa(o,{...k,serverName:C})}}:{},...S?{remoteHostname:async()=>{let C=await y.exec(c,"hostname");return C.code===0?C.stdout.trim():void 0},setRemoteHostname:async(C)=>{let k=await y.exec(c,va(C));if(k.code!==0)throw Error(k.stderr.trim()||`hostname exited ${k.code}`)}}:{}},b=await ya(c.name,e,g),I=await On(b);if(!t.apply){let C=[d?"":"provider record (no provider id or no API token)",f?"":"local state pin (none points at this server)",S?"":"box hostname (host key is not pinned — run server:validate first)"].filter(Boolean);if(t.json){console.log(JSON.stringify({schemaVersion:1,operation:b.operation,target:b.target,rename:{from:c.name,to:e},notCovered:C,steps:I.map((k)=>({id:k.step.id,title:k.step.title,state:k.state,change:k.step.change,reason:k.reason}))},null,2));return}for(let k of Ln(b,I))console.log(k);for(let k of C)console.log(` not covered: ${k}`);console.log(" Re-run with --apply to perform it.");return}let v=await jn(b,I,{log:(C)=>a(C),audit:(C)=>s.controlPlane.store.appendEvent({projectId:s.controlPlane.project.id,resourceId:c.resourceId,type:`${C.operation}.${C.step}.${C.state}`,level:C.state==="failed"?"error":"info",payload:{target:C.target,renameTo:e,...C.error?{error:C.error}:{}}})});if(t.json)console.log(JSON.stringify({schemaVersion:1,outcome:v},null,2));if(!v.success){let C=v.steps.find((k)=>k.state==="failed");throw Error(`${b.operation} stopped at '${C?.title}': ${C?.error}. Earlier steps stayed applied; re-run the same command to continue from here.`)}if(!t.json)h(`Renamed ${c.name} to ${e}.`)})}function Qr(r){r.command("capabilities [server]","Show provider and target operation support").option("--json","Print structured JSON").action(async(e,t)=>{try{await Ue(async(n)=>{let i=e?[yt(n,e)]:n.store.list(n.controlPlane.project.id),o=i.flatMap((s)=>Object.entries(s.capabilities).map(([c,l])=>({server:s.name,provider:s.provider,action:c,...l})));if(t.json)console.log(JSON.stringify({schemaVersion:1,targets:o},null,2));else x(["Server","Provider","Action","Support","Explanation"],o.map((s)=>[s.server,s.provider,s.action,s.supported?"supported":"unsupported",s.reason??"—"]))})}catch(n){fe(n)}}),r.command("server:resize <name> <type>","Safely resize Hetzner compute with full host verification").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Execute the reviewed resize plan").option("--confirm <name>","Exact server name required for mutation").option("--upgrade-disk","Permanently expand the root disk to the target size",{default:!0}).option("--wait","Poll capacity without taking the server offline until placement is available").option("--poll-seconds <seconds>","Capacity retry interval",{default:"900"}).option("--max-wait-minutes <minutes>","Stop waiting after this many minutes; 0 waits indefinitely",{default:"0"}).option("--json","Print structured JSON").action(async(e,t,n)=>{try{await tm(e,t,n)}catch(i){fe(i)}}),r.command("server:optimize <name>","Reconcile and verify a production Hetzner host").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Apply the reviewed host optimization plan").option("--confirm <name>","Exact server name required for mutation").option("--json","Print structured JSON").action(async(e,t)=>{try{await nm(e,t)}catch(n){fe(n)}}),r.command("server:monitoring <name>","Read historical Hetzner CPU, RAM, swap, disk, and network metrics").option("--env <environment>","Deployment environment",{default:"production"}).option("--range <duration>","Historical window from 5m to 24h",{default:"3h"}).option("--step <seconds>","Hetzner provider sample interval",{default:"60"}).option("--json","Print structured JSON").action(async(e,t)=>{try{await Xu(e,t)}catch(n){fe(n)}}),r.command("server:list","List fleet servers").option("--json","Print structured JSON").action(async(e)=>{try{await Ue(async(t)=>{let n=t.store.list(t.controlPlane.project.id);if(e.json)console.log(JSON.stringify(n,null,2));else x(["Name","Provider ID","Provider / region","Status / trust","Roles","CPU / memory","Heartbeat"],n.map((i)=>[i.name,i.providerId??"—",`${i.provider} / ${i.region??"external"}`,`${i.status} / ${i.trustState}`,i.roles.join(","),`${i.capacity.cpu??0} / ${i.capacity.memoryBytes??0}`,i.heartbeatAt??"never"]))})}catch(t){fe(t)}}),r.command("server:create <name>","Enroll a provisioned or existing server").option("--provider <provider>","aws, hetzner, or ssh",{default:"ssh"}).option("--provider-id <id>","Stable provider identity").option("--endpoint <host>","SSH hostname or IP").option("--user <user>","Non-root SSH user",{default:"deploy"}).option("--credential-ref <ref>","Secret reference",{default:"secret://fleet/agent"}).option("--region <region>","Provider region").option("--roles <roles>","Comma-separated roles",{default:"application"}).option("--labels <labels>","Comma-separated key=value labels").action(async(e,t)=>{try{await Ue(async(n)=>{let i=t.provider;if(!["aws","hetzner","ssh"].includes(i)||!t.endpoint)throw Error("A supported --provider and --endpoint are required; provisioning and enrollment are separate operations.");let o=n.service.enroll({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,name:e,provider:i,providerId:t.providerId,endpoint:t.endpoint,sshUser:t.user??"deploy",credentialRef:t.credentialRef??"secret://fleet/agent",region:t.region,roles:String(t.roles??"application").split(","),labels:Object.fromEntries(String(t.labels??"").split(",").map((s)=>s.split("=")).filter((s)=>s[0]&&s[1]))});h(`Enrolled ${o.name} as ${o.id}; no remote mutation was performed.`)})}catch(n){fe(n)}}),r.command("server:validate <name>","Pin trust and validate a server").option("--accept-host-key <fingerprint>","Accept reviewed rotation").option("--json","Print JSON").action(async(e,t)=>{try{await Ue(async(n)=>{let i=yt(n,e);if(i=await n.service.test(i.id),i.trustState==="rotation_pending"){if(!t.acceptHostKey)throw Error(`Host key changed to ${i.pendingHostKey}; review it first.`);i=n.service.reviewHostKey(i.id,t.acceptHostKey)}let o=await n.service.validate(i.id);if(t.json)console.log(JSON.stringify(o.validation,null,2));else x(["Severity","Code","Finding","Remediation"],(o.validation?.findings??[]).map((s)=>[s.severity,s.code,s.message,s.remediation??"—"]));if(!o.validation?.valid)process.exitCode=1})}catch(n){fe(n)}}),r.command("server:bootstrap <name>","Preview or queue bootstrap").option("--apply","Apply reviewed plan").action(async(e,t)=>{try{await Ue(async(n)=>{let i=n.service.bootstrap(yt(n,e).id,!!t.apply);if(i.preview)x(["Step"],i.steps.map((o)=>[o]));else h(`Bootstrap queued: ${i.operation?.id}`)})}catch(n){fe(n)}}),r.command("server:rename <name> <new-name>","Rename a server in place, without recreating it").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Perform the reviewed rename plan").option("--json","Print structured JSON").action(async(e,t,n)=>{try{await rm(e,t,n)}catch(i){fe(i)}}),r.command("server:drain <name>","Drain without terminating").option("--complete","Mark movement complete").action(async(e,t)=>{try{await Ue(async(n)=>h(n.service.drain(yt(n,e).id,!!t.complete).status))}catch(n){fe(n)}}),r.command("server:uncordon <name>","Return a server to scheduling").action(async(e)=>{try{await Ue(async(t)=>{t.service.uncordon(yt(t,e).id),h(`Uncordoned ${e}.`)})}catch(t){fe(t)}}),r.command("server:archive <name>","Archive inventory without termination").option("--confirm <name>","Exact name").action(async(e,t)=>{try{await Ue(async(n)=>{n.service.archive(yt(n,e).id,t.confirm??""),h(`Archived ${e}; provider infrastructure was not terminated.`)})}catch(n){fe(n)}}),r.command("server:ssh <name>","Open strict SSH using the enrolled endpoint").action(async(e)=>{try{await Ue(async(t)=>{let n=yt(t,e);if(n.trustState!=="pinned")throw Error("Validate and pin the server host key first.");let i=Bun.spawn(["ssh","-p",String(n.sshPort),"-o","StrictHostKeyChecking=yes",`${n.sshUser}@${n.endpoint}`],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}),o=await i.exited;if(o)process.exitCode=o})}catch(t){fe(t)}});for(let[e,t]of[["server:logs <name>","Use the runtime log service"],["server:deploy <name>","Use immutable release deployment"],["server:reboot <name>","Reboot provider compute"],["server:destroy <name>","Terminate provider compute"],["server:recipe <name> <recipe>","Apply a server recipe"],["server:firewall:add <name> <rule>","Add firewall rule"],["server:firewall:list <name>","List firewall rules"],["server:firewall:remove <name> <rule>","Remove firewall rule"],["server:ssl:install <domain>","Install TLS"],["server:ssl:renew <domain>","Renew TLS"],["server:snapshot <name>","Create provider snapshot"],["server:snapshot:restore <name> <snapshot-id>","Restore provider snapshot"],["server:update <name>","Update OS packages"],["server:secure <name>","Apply OS hardening"]])r.command(e,t).action(async(...n)=>be(e.split(" ")[0],{target:String(n[0]??""),nextAction:"Use the dashboard capability view or the corresponding runtime, release, firewall, backup, or maintenance service."}))}async function im(){let r=await P(),e=q(process.cwd(),r),t=new La(e.store),n=new ja(t,new Xe(e.store));return{controlPlane:e,store:t,service:n}}async function dn(r){let e=await im();try{await r(e)}catch(t){p(t instanceof Error?t.message:String(t)),process.exitCode=1}finally{e.controlPlane.store.close()}}var Vn=(r)=>Object.fromEntries(String(r??"").split(",").map((e)=>e.split("=")).filter((e)=>e[0]&&e[1])),Oa=(r)=>Object.fromEntries(Object.entries(Vn(r)).map(([e,t])=>[e,Number(t)]));function Xr(r){r.command("pool:list","List capacity pools and reservations").option("--json","Print structured JSON").action(async(e)=>dn((t)=>{let n=t.store.listPools(t.controlPlane.project.id);if(e.json)console.log(JSON.stringify(n,null,2));else x(["Name","Purpose","Backend","Region / arch","Status","Capacity","Concurrency"],n.map((i)=>[i.name,i.purpose,i.backend,`${i.region??"any"} / ${i.architecture??"any"}`,i.status,JSON.stringify(i.capacity),String(i.concurrency)]))})),r.command("pool:create <name>","Create a capacity pool").option("--purpose <purpose>","application, build, worker, monitoring, or backup",{default:"application"}).option("--backend <backend>","server, ecs, or asg",{default:"server"}).option("--capacity <values>","cpu=4,memoryBytes=8589934592,diskBytes=...").option("--region <region>","Required region").option("--arch <architecture>","Required architecture").option("--labels <labels>","Pool key=value labels").option("--selector <labels>","Required server labels").option("--max <count>","Maximum workloads",{default:"10"}).option("--concurrency <count>","Build concurrency",{default:"1"}).action(async(e,t)=>dn((n)=>{let{purpose:i,backend:o}=t;if(!["application","build","worker","monitoring","backup"].includes(i)||!["server","ecs","asg"].includes(o))throw Error("Unsupported pool purpose or backend.");let s=n.store.createPool({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,name:e,purpose:i,backend:o,region:t.region,architecture:t.arch,labels:Vn(t.labels),requiredServerLabels:Vn(t.selector),toleratedTaints:[],capacity:{cpu:0,memoryBytes:0,diskBytes:0,gpu:0,...Oa(t.capacity)},maxWorkloads:Number(t.max),costWeight:1,concurrency:Number(t.concurrency),ephemeralWorkspaces:!0,allowProductionSecrets:!1,status:"active"});h(`Created ${s.name} (${s.id}).`)})),r.command("pool:add-server <pool> <server>","Attach an enrolled server to a pool").action(async(e,t)=>dn((n)=>{let i=n.store.listPools(n.controlPlane.project.id).find((s)=>s.id===e||s.name===e),o=new Xe(n.controlPlane.store).list(n.controlPlane.project.id).find((s)=>s.id===t||s.name===t);if(!i||!o)throw Error("Pool or server was not found.");n.store.addMember(i.id,o.id),h(`Attached ${o.name} to ${i.name}.`)})),r.command("placement:explain","Explain eligible and rejected placement targets").option("--purpose <purpose>","Workload purpose",{default:"application"}).option("--resources <values>","Requested resources").option("--region <region>","Required region").option("--arch <architecture>","Required architecture").option("--labels <labels>","Required pool labels").option("--json","Print structured JSON").action(async(e)=>dn((t)=>{let n=t.service.explain(t.controlPlane.project.id,{purpose:e.purpose,resources:Oa(e.resources),region:e.region,architecture:e.arch,labels:Vn(e.labels)});if(e.json)console.log(JSON.stringify(n,null,2));else x(["Pool","Server","Eligible","Available","Fit / spread / cost","Reasons"],n.map((i)=>[i.poolName,i.serverId??"provider-managed",i.eligible?"yes":"no",JSON.stringify(i.available),`${i.score.fit.toFixed(2)} / ${i.score.spread} / ${i.score.cost}`,i.reasons.join("; ")||"eligible"]))})),r.command("pool:drain <pool>","Move stateless placements and block stateful placements").action(async(e)=>dn((t)=>{let n=t.store.listPools(t.controlPlane.project.id).find((o)=>o.id===e||o.name===e);if(!n)throw Error("Pool was not found.");let i=t.service.drainPool(n.id);h(`Draining ${n.name}: moved ${i.moved.length}, blocked ${i.blocked.length} stateful placement(s).`)}))}async function om(){let r=await P(),e=q(process.cwd(),r),t=new Fa(e.store),n=new Ma(t);return{controlPlane:e,store:t,service:n}}async function It(r){let e=await om();try{await r(e)}catch(t){p(t instanceof Error?t.message:String(t)),process.exitCode=1}finally{e.controlPlane.store.close()}}var un=(r,e)=>{let t=r.store.list(r.controlPlane.project.id).find((n)=>n.id===e||n.name===e);if(!t)throw Error("Regional topology was not found.");return t},sm=(r)=>r.split(",").map((e,t)=>{let[n,i="aws"]=e.split(":");return{region:String(n),provider:i,role:t===0?"primary":"secondary"}}),am=(r)=>{let e=new Set(["s3","dynamodb","secrets"]),t=r.split(",").map((i)=>i.trim()),n=t.find((i)=>!e.has(i));if(n)throw Error(`Unsupported replication kind: ${n}`);return t};function ei(r){r.command("region:list","List regional topology, traffic, and replication health").option("--json","Print structured JSON").action(async(e)=>It((t)=>{let n=t.store.list(t.controlPlane.project.id).map((i)=>({topology:i,targets:t.store.targets(i.id),channels:t.store.channels(i.id),route:t.store.route(i.id),executions:t.store.executions(i.id)}));if(e.json)console.log(JSON.stringify(n,null,2));else x(["Name","Hostname","Active","Status","Revision","Replication"],n.map((i)=>[i.topology.name,i.topology.hostname,i.topology.activeRegion,i.topology.status,i.topology.revision??"—",`${i.channels.filter((o)=>o.status==="in_sync").length}/${i.channels.length} in sync`]))})),r.command("region:create <name>","Create a persistent multi-region topology").option("--hostname <hostname>","Traffic hostname").option("--regions <regions>","Comma-separated region[:provider], primary first").option("--replicate <kinds>","s3,dynamodb,secrets",{default:"s3,dynamodb,secrets"}).option("--max-lag <seconds>","Maximum failover lag",{default:"30"}).option("--cdn","Enable CloudFront edge state").option("--waf","Enable WAF edge state").action(async(e,t)=>It((n)=>{if(!t.hostname||!t.regions)throw Error("--hostname and --regions are required.");let i=n.service.create({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,name:e,hostname:t.hostname,regions:sm(t.regions),trafficPolicy:"active_passive",dataPolicy:{replicate:am(String(t.replicate)),maxLagSeconds:Number(t.maxLag),retainOnDestroy:!0},dnsProvider:"route53",cdnEnabled:!!t.cdn,wafEnabled:!!t.waf});h(`Created ${i.name} (${i.id}); run region:rollout with an immutable manifest.`)})),r.command("region:plan <name> <operation>","Preview exact regional execution steps").option("--target <region>","Failover or failback region").action(async(e,t,n)=>It((i)=>x(["Step"],i.service.plan(un(i,e).id,t,n.target).map((o)=>[o])))),r.command("region:rollout <name> <revision>","Queue secondary-first regional stack rollout").option("--manifest <file>","Regional stack manifest JSON").action(async(e,t,n)=>It(async(i)=>{let o=n.manifest?await Bun.file(n.manifest).json():{},s=i.service.enqueue({topologyId:un(i,e).id,kind:"rollout",revision:t,manifest:o});h(`Regional rollout queued: ${s.operation.id}`)})),r.command("region:failover <name> <region>","Queue health and RPO-gated failover").action(async(e,t)=>It((n)=>{let i=n.service.enqueue({topologyId:un(n,e).id,kind:"failover",targetRegion:t});h(`Failover queued: ${i.operation.id}`)})),r.command("region:failback <name> <region>","Queue health and RPO-gated failback").action(async(e,t)=>It((n)=>{let i=n.service.enqueue({topologyId:un(n,e).id,kind:"failback",targetRegion:t});h(`Failback queued: ${i.operation.id}`)})),r.command("region:destroy <name>","Drain traffic and delete regional stacks while retaining data").option("--confirm <name>","Exact topology name").action(async(e,t)=>It((n)=>{let i=n.service.enqueue({topologyId:un(n,e).id,kind:"destroy",confirmation:t.confirm});h(`Regional teardown queued: ${i.operation.id}; replicated data remains retained.`)}))}var mn="0.11.2";async function lm(){let r=await P(),e=q(process.cwd(),r),t=new Ba(e.store),n=new _a(t),i=["releases","artifacts","operations","logs","previews","snapshots","orphaned_resources"].map((o)=>new za(o,e.store,e.project.id,e.organization.id));return{controlPlane:e,store:t,service:n,cleanup:i}}async function et(r){let e=await lm();try{await r(e)}catch(t){p(t instanceof Error?t.message:String(t)),process.exitCode=1}finally{e.controlPlane.store.close()}}function ti(r){r.command("maintenance:list","List updates, campaigns, cleanup plans, and DR drills").option("--json","Print structured JSON").action(async(e)=>et((t)=>{let n={manifests:t.store.manifests(t.controlPlane.organization.id),windows:t.store.windows(t.controlPlane.project.id),campaigns:t.store.campaigns(t.controlPlane.project.id),cleanups:t.store.cleanups(t.controlPlane.project.id),drills:t.store.drills(t.controlPlane.project.id)},i=[...n.manifests.map((o)=>["update",o.version,o.compatibility.compatible?"compatible":"blocked",o.compatibility.reasons.join("; ")||o.channel]),...n.campaigns.map((o)=>["upgrade",o.id,o.status,o.currentStage??o.fromVersion]),...n.cleanups.map((o)=>["cleanup",o.kind,o.status,`${o.candidates.length} items / ${o.estimatedBytes} bytes`]),...n.drills.map((o)=>["DR drill",o.scenario,o.status,o.isolatedTarget])];if(e.json)console.log(JSON.stringify(n,null,2));else x(["Area","Name / version","Status","Detail"],i)})),r.command("maintenance:key:add <name> <pem>","Trust an Ed25519 update signing key").action(async(e,t)=>et(async(n)=>{let i=n.store.addTrustedKey({organizationId:n.controlPlane.organization.id,name:e,publicKeyPem:await Bun.file(t).text()});h(`Trusted ${i.name}: ${i.fingerprint}`)})),r.command("maintenance:manifest <document> <signature> <key>","Verify and import a signed update manifest").action(async(e,t,n)=>et(async(i)=>{let o=await Bun.file(e).json(),s=(await Bun.file(t).text()).trim(),c=i.store.importManifest({organizationId:i.controlPlane.organization.id,keyId:n,document:o,signature:s,current:{schemaVersion:i.controlPlane.store.health().schemaVersion,currentVersion:mn,platform:process.platform,architecture:process.arch}});h(`Verified ${c.version}; ${c.compatibility.compatible?"compatible":"blocked: "+c.compatibility.reasons.join("; ")}`)})),r.command("maintenance:window <name> <schedule>","Create an approved maintenance window").option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--minutes <minutes>","Duration",{default:"60"}).action(async(e,t,n)=>et((i)=>{let o=i.store.createWindow({projectId:i.controlPlane.project.id,name:e,schedule:t,timezone:n.timezone,durationMinutes:Number(n.minutes),allowedOperations:["platform.upgrade"],requireApproval:!0});h(`Created ${o.name} (${o.id}).`)})),r.command("platform:upgrade <manifest> <backup>","Plan and queue a signed staged platform update").option("--strategy <strategy>","canary or rolling",{default:"canary"}).option("--batch <count>","Rolling batch size",{default:"1"}).action(async(e,t,n)=>et((i)=>{let o=new Xe(i.controlPlane.store).list(i.controlPlane.project.id).filter((l)=>l.status==="ready").map((l)=>({serverId:l.id,target:l.name,previousVersion:l.bootstrapVersion??mn}));if(!o.length)o.push({target:"control-plane",previousVersion:mn});let s=i.store.createCampaign({projectId:i.controlPlane.project.id,manifestId:e,fromVersion:mn,strategy:n.strategy==="rolling"?"rolling":"canary",batchSize:Number(n.batch),healthGate:{kind:"platform"},backupId:t,targets:o}),c=i.service.enqueueCampaign(s.id);h(`Staged upgrade queued: ${c.operation.id}`)})),r.command("cleanup:preview <kind>","Discover a bounded cleanup candidate set without deleting").option("--older-than <days>","Minimum age",{default:"30"}).action(async(e,t)=>et(async(n)=>{let i=await n.service.previewCleanup({projectId:n.controlPlane.project.id,kind:e,criteria:{olderThanDays:Number(t.olderThan)}},n.cleanup);x(["ID","Candidate","Bytes"],i.candidates.map((o)=>[o.id,o.label,String(o.bytes)])),a(`Apply with: cloud cleanup:apply ${i.id} --confirm "DELETE ${i.candidates.length} ${i.kind}"`)})),r.command("cleanup:apply <plan>","Apply an unchanged cleanup preview").option("--confirm <text>","Exact preview confirmation").action(async(e,t)=>et((n)=>{let i=n.service.enqueueCleanup(e,String(t.confirm??""));h(`Cleanup queued: ${i.operation.id}`)})),r.command("dr:plan <scenario> <target>","Plan an isolated disaster-recovery drill").option("--rpo <minutes>","RPO objective",{default:"60"}).option("--rto <minutes>","RTO objective",{default:"30"}).option("--recovery-point <id>","Recovery point").option("--topology <id>","Regional topology").action(async(e,t,n)=>et((i)=>{let o=i.store.createDrill({projectId:i.controlPlane.project.id,scenario:e,isolatedTarget:t,expectedRpoMinutes:Number(n.rpo),expectedRtoMinutes:Number(n.rto),recoveryPointId:n.recoveryPoint,topologyId:n.topology});h(`Planned isolated drill ${o.id}.`)})),r.command("dr:run <drill>","Queue an isolated disaster-recovery drill").action(async(e)=>et((t)=>{let n=t.service.enqueueDrill(e);h(`DR drill queued: ${n.operation.id}`)}))}async function ni(){try{return(await P()).project.region||process.env.AWS_REGION||"us-east-1"}catch{return process.env.AWS_REGION||"us-east-1"}}function ri(r){r.command("function:list","List all Lambda functions").option("--region <region>","AWS region").action(async(e)=>{w("Lambda Functions");let t=e?.region||await ni(),n=new tt(t);try{let{Functions:i=[]}=await n.listFunctions({MaxItems:50});if(!i.length){a("No functions found");return}x(["Name","Runtime","Memory","Timeout","Last Modified"],i.map((o)=>[o.FunctionName??"-",o.Runtime??"-",o.MemorySize?`${o.MemorySize} MB`:"-",o.Timeout?`${o.Timeout}s`:"-",o.LastModified??"-"]))}catch(i){p(`Failed to list functions: ${i.message}`),process.exitCode=1}}),r.command("function:invoke <name>","Invoke a Lambda function").option("--payload <json>","Event payload as JSON",{default:"{}"}).option("--async","Fire-and-forget (Event invocation)").option("--region <region>","AWS region").action(async(e,t)=>{w(`Invoking ${e}`);let n=t?.region||await ni(),i=new tt(n),o=new A("Invoking function...");o.start();try{let s=await i.invoke({FunctionName:e,InvocationType:t?.async?"Event":"RequestResponse",Payload:t?.payload||"{}",LogType:t?.async?"None":"Tail"});if(s.FunctionError){if(o.fail(`Function returned an error (${s.FunctionError})`),s.Payload)a(s.Payload);process.exitCode=1;return}if(o.succeed(`Invoked (status ${s.StatusCode})`),s.Payload)a(`
|
|
318
|
+
`)}async function Zu(){let r=await P(),e=q(process.cwd(),r),t=new et(e.store),n=new Ta(t,[new Un("aws"),new Un("hetzner"),new Un("ssh")]);return{controlPlane:e,store:t,service:n}}async function Ue(r){let e=await Zu();try{return await r(e)}finally{e.controlPlane.store.close()}}var yt=(r,e)=>{let t=r.store.list(r.controlPlane.project.id).find((n)=>n.id===e||n.name===e);if(!t)throw Error(`Server ${e} was not found.`);return t},fe=(r)=>{p(r instanceof Error?r.message:String(r)),process.exitCode=1};function Zr(r,e){let t=Number.parseInt(r??"",10);return Number.isFinite(t)&&t>=0?t:e}function Qu(r="3h"){let e=r.match(/^(\d+)(m|h)$/);if(!e)throw Error("--range must use minutes or hours, for example 30m or 3h.");let t=Number(e[1])*(e[2]==="h"?3600000:60000);if(t<300000||t>86400000)throw Error("--range must be between 5 minutes and 24 hours.");return t}function cn(r,e="%"){return r==null?"unavailable":`${r.toFixed(1)}${e}`}async function Xu(r,e){let t=await P();if(t.cloud?.provider!=="hetzner")throw Error("server:monitoring currently requires the Hetzner provider.");let n=e.env??process.env.CLOUD_ENV??"production",i=Ve(t,n),o=await Rt(i);if(!o?.serverId)throw Error(`No pinned Hetzner server was found for ${i}.`);if(![o.serverName,String(o.serverId),o.stackName].includes(r))throw Error(`Refusing to read ${r}: project state is pinned to ${o.serverName}.`);let s=ln(t),c=new Vt({apiToken:Wt(s.apiToken,t)}),l=await c.getServer(o.serverId),d=o.publicIp??l.public_net.ipv4?.ip;if(!d)throw Error(`Server ${l.name} has no public IPv4 address.`);let u=new Date,m=new Date(u.getTime()-Qu(e.range)),f=Math.min(3600,Math.max(60,Zr(e.step,60))),y=await ha({client:c,serverId:l.id,cores:Math.max(1,Number(l.server_type.cores)||1),from:m,to:u,step:f,remote:{host:d,user:o.sshUser??s.sshUser,identityFile:s.sshPrivateKeyPath,connectTimeoutSec:15}});if(e.json){console.log(JSON.stringify({schemaVersion:1,server:l.name,result:y},null,2));return}x(["Server","Window","CPU average / peak","RAM average / peak","Minimum available","Swap peak"],[[l.name,`${y.from} to ${y.to}`,`${cn(y.summary.cpuAveragePercent)} / ${cn(y.summary.cpuPeakPercent)}`,`${cn(y.summary.memoryAveragePercent)} / ${cn(y.summary.memoryPeakPercent)}`,y.summary.memoryMinimumAvailableBytes==null?"unavailable":`${(y.summary.memoryMinimumAvailableBytes/1073741824).toFixed(2)} GB`,cn(y.summary.swapPeakPercent)]])}function Na(r){let e=r.targetType.prices?.find((t)=>t.location===r.location);return{serverId:r.serverId,serverName:r.serverName,location:r.location,from:r.currentType.name,to:r.targetType.name,capacityAvailable:r.capacityAvailable,alreadyComplete:r.alreadyComplete,upgradeDisk:r.upgradeDisk,target:{cores:r.targetType.cores,memoryGb:r.targetType.memory,diskGb:r.targetType.disk,architecture:r.targetType.architecture,cpuType:r.targetType.cpu_type},price:e?{hourlyNet:e.price_hourly.net,hourlyGross:e.price_hourly.gross,monthlyNet:e.price_monthly.net,monthlyGross:e.price_monthly.gross}:void 0}}function em(r){let e=Na(r);x(["Server","Location","Resize","Capacity","CPU / RAM / disk","Monthly"],[[`${e.serverName} (${e.serverId})`,e.location,`${e.from} -> ${e.to}`,e.capacityAvailable?"available":"waiting",`${e.target.cores??0} / ${e.target.memoryGb??0} GB / ${e.target.diskGb??0} GB`,e.price?.monthlyGross??e.price?.monthlyNet??"not reported"]])}async function tm(r,e,t){let n=await P();if(n.cloud?.provider!=="hetzner")throw Error("server:resize currently requires the Hetzner provider.");let i=t.env??process.env.CLOUD_ENV??"production",o=Ve(n,i),s=await Rt(o),c=ln(n),l=new Vt({apiToken:Wt(c.apiToken,n)}),u=s&&(r===s.serverName||r===String(s.serverId)||r===s.stackName)?await l.getServer(s.serverId):(await l.listServers()).find((E)=>E.name===r||String(E.id)===r);if(!u)throw Error(`Hetzner server ${r} was not found.`);if(s?.serverId&&s.serverId!==u.id)throw Error(`Refusing to resize ${r}: project state is pinned to server ${s.serverId}.`);let m=wa(e),f=t.upgradeDisk!==!1,y=await Jr(l,u.id,m,f);if(t.json)console.log(JSON.stringify({schemaVersion:1,plan:Na(y)},null,2));else em(y);if(!t.apply){if(!t.json)a(`Preview only. Re-run with --apply --confirm ${u.name} to execute.`);return}if(t.confirm!==u.name)throw Error(`Resize requires --confirm ${u.name}.`);let S=s?.publicIp??u.public_net.ipv4?.ip;if(!S)throw Error(`Server ${u.name} has no public IPv4 address for verification.`);let g={host:S,user:s?.sshUser??c.sshUser,identityFile:c.sshPrivateKeyPath,connectTimeoutSec:15,sshTimeoutMs:600000,routeTimeoutMs:20000},b=await Ea(o),I=new Date().toISOString(),v={schemaVersion:1,operationId:b?.targetType===m&&b.serverId===u.id?b.operationId:crypto.randomUUID(),stackName:o,serverId:u.id,serverName:u.name,sourceType:y.currentType.name,targetType:m,upgradeDisk:f,phase:"planning",status:"running",attempts:b?.targetType===m?b.attempts:0,startedAt:b?.targetType===m?b.startedAt:I,updatedAt:I,manifest:b?.targetType===m?b.manifest:void 0};await ht(v);let C=Zr(t.pollSeconds,900)*1000,k=Zr(t.maxWaitMinutes,0)*60000,T=Date.now();for(;;){let E=await Jr(l,u.id,m,f);if(!E.capacityAvailable&&!E.alreadyComplete){if(v={...v,phase:"waiting-capacity",status:"waiting-capacity",updatedAt:new Date().toISOString()},await ht(v),!t.wait){if(!t.json)a(`${m} has no capacity in ${E.location}. The server remains running and unchanged. Use --wait to poll safely.`);return}if(k>0&&Date.now()-T>=k)throw Error(`Timed out waiting for ${m} capacity in ${E.location}.`);if(!t.json)a(`Waiting ${Math.round(C/1000)} seconds for ${m} capacity...`);await Bun.sleep(C);continue}let D=await Da(o);try{let N=await(async()=>{try{return v={...v,status:"running",attempts:v.attempts+1,updatedAt:new Date().toISOString()},await ht(v),await $a({client:l,serverId:u.id,targetType:m,upgradeDisk:f,resumeFromOff:v.phase==="shutting-down"||v.phase==="resizing"||v.phase==="powering-on"||v.phase==="verifying",manifest:v.manifest,hooks:{preflight:async()=>{let _=await Ca(g);return v={...v,manifest:_,updatedAt:new Date().toISOString()},await ht(v),_},afterBoot:async(_,B,V,K)=>Aa(g,B,V,K),onPhase:async(_,B)=>{if(v={...v,phase:_,status:_==="waiting-capacity"?"waiting-capacity":_==="complete"?"completed":"running",updatedAt:new Date().toISOString()},await ht(v),!t.json)a(`${_}${B?` ${JSON.stringify(B)}`:""}`)}}})}finally{await D()}})();if(v={...v,status:N.status==="completed"||N.status==="already-complete"?"completed":N.status==="recovered"?"recovered":"waiting-capacity",lastError:N.status==="recovered"?N.error:void 0,updatedAt:new Date().toISOString()},await ht(v),t.json)console.log(JSON.stringify({schemaVersion:1,result:N},null,2));else if(N.status==="completed"||N.status==="already-complete")h(`${u.name} is on ${m} and passed full post-boot verification.`);else if(N.status==="recovered"&&N.retryable&&t.wait){a(`Capacity race recovered safely. Waiting ${Math.round(C/1000)} seconds before retry.`),await Bun.sleep(C);continue}else if(N.status==="recovered")throw Error(`Resize did not complete and the original server was recovered: ${N.error}`);return}catch(N){throw v={...v,status:"failed",lastError:N instanceof Error?N.message:String(N),updatedAt:new Date().toISOString()},await ht(v),N}}}async function nm(r,e){let t=await P();if(t.cloud?.provider!=="hetzner")throw Error("server:optimize currently requires the Hetzner provider.");let n=e.env??process.env.CLOUD_ENV??"production",i=Ve(t,n),o=await Rt(i);if(!o?.serverId)throw Error(`No pinned Hetzner server was found for ${i}.`);if(![o.serverName,String(o.serverId),o.stackName].includes(r))throw Error(`Refusing to optimize ${r}: project state is pinned to ${o.serverName}.`);let s=ln(t),c=new Vt({apiToken:Wt(s.apiToken,t)}),l=await c.getServer(o.serverId),d=o.publicIp??l.public_net.ipv4?.ip;if(!d)throw Error(`Server ${l.name} has no public IPv4 address.`);let u=Ra(t);if(e.json)console.log(JSON.stringify({schemaVersion:1,server:l.name,plan:u},null,2));else x(["Server","Firewall ports","Monitoring","Auto updates","Swap","SSH passwords"],[[l.name,u.firewallPorts.join(","),u.monitoring?"on":"off",u.autoUpdates?"on":"off",`${u.swapGb} GB`,"disabled"]]);if(!e.apply){if(!e.json)a(`Preview only. Re-run with --apply --confirm ${l.name} to execute.`);return}if(e.confirm!==l.name)throw Error(`Optimization requires --confirm ${l.name}.`);let m={host:d,user:o.sshUser??s.sshUser,identityFile:s.sshPrivateKeyPath,connectTimeoutSec:15},f=await Yr(m),y=f.routeProbes.filter((V)=>!V.ok),S=t.infrastructure?.compute,g=Hr(S)?new Set(["nginx.service","bun-gateway.service","ts-cloud-nginx.service"]):new Set,b=f.failedUnits.filter((V)=>!g.has(V));if(b.length>0)throw Error(`Preflight found failed services: ${b.join(", ")}`);if(y.length>0)throw Error(`Preflight found unhealthy routes: ${y.map((V)=>V.domain).join(", ")}`);let I=Hr(S)?[]:Object.values(t.sites??{}).flatMap((V)=>typeof V?.port==="number"&&![80,443].includes(V.port)?[V.port]:[]),v=ba({allowSsh:S?.allowSsh!==!1,sitePorts:I,allowedPorts:S?.firewall?.allowedPorts}),C=(o.firewallId?(await c.listFirewalls()).find((V)=>V.id===o.firewallId):void 0)??(await c.listFirewalls()).find((V)=>V.name===`${t.project.slug}-${n}-app-fw`);if(!C)throw Error(`Managed firewall for ${l.name} was not found.`);let k=await c.setFirewallRules(C.id,v);await Promise.all(k.map((V)=>c.waitForAction(V.id))),await Ia(t,m);let[T,E]=await Promise.all([Yr(m),Pa(m)]),D=xa(u,E),N=ka(f,T),_=T.routeProbes.filter((V)=>!V.ok);if(N.stoppedServices.length>0)D.push(`services stopped: ${N.stoppedServices.join(", ")}`);if(N.changedRouteFragments.length>0)D.push(`route fragments changed: ${N.changedRouteFragments.join(", ")}`);if(N.missingRouteIds.length>0)D.push(`routes disappeared: ${N.missingRouteIds.join(", ")}`);if(N.changedReleaseLinks.length>0)D.push(`release links changed: ${N.changedReleaseLinks.join(", ")}`);if(N.missingData.length>0)D.push(`databases or volumes disappeared: ${N.missingData.join(", ")}`);if(_.length>0)D.push(`unhealthy routes: ${_.map((V)=>V.domain).join(", ")}`);if(D.length>0)throw Error(`Host optimization verification failed: ${D.join("; ")}`);let B={serverId:l.id,serverName:l.name,services:T.runningServices.length,routes:T.routeIds.length,healthyDomains:T.routeProbes.length,releaseLinks:T.releaseLinks.length,databasesAndVolumes:T.dataCatalog.length,report:E};if(e.json)console.log(JSON.stringify({schemaVersion:1,result:B},null,2));else h(`${l.name} passed full host, rpx route, service, release, and data verification.`)}async function rm(r,e,t){let n=await P(),i=t.env??"production",o=Ve(n,i);await Ue(async(s)=>{let c=yt(s,r),l=s.store.list(s.controlPlane.project.id,!0),d=c.provider==="hetzner"&&c.providerId?(()=>{try{let C=ln(n);return new Vt({apiToken:Wt(C.apiToken,n)})}catch{return null}})():null,u=c.providerId?Number(c.providerId):Number.NaN,m=await Rt(o),f=m!=null&&m.serverId===u,y=new Mn,S=c.trustState==="pinned",g={takenNames:async()=>{let C=l.map((k)=>k.name);if(d)C.push(...(await d.listServers()).map((k)=>k.name));return C},inventoryName:()=>s.store.get(c.id)?.name??c.name,renameInventory:(C)=>{s.store.update(c.id,{name:C})},...d&&Number.isFinite(u)?{providerName:async()=>(await d.getServer(u)).name,renameProvider:async(C)=>{await d.renameServer(u,C)}}:{},...f?{stateName:async()=>(await Rt(o))?.serverName,writeStateName:async(C)=>{let k=await Rt(o);if(k)await Sa(o,{...k,serverName:C})}}:{},...S?{remoteHostname:async()=>{let C=await y.exec(c,"hostname");return C.code===0?C.stdout.trim():void 0},setRemoteHostname:async(C)=>{let k=await y.exec(c,va(C));if(k.code!==0)throw Error(k.stderr.trim()||`hostname exited ${k.code}`)}}:{}},b=await ya(c.name,e,g),I=await On(b);if(!t.apply){let C=[d?"":"provider record (no provider id or no API token)",f?"":"local state pin (none points at this server)",S?"":"box hostname (host key is not pinned — run server:validate first)"].filter(Boolean);if(t.json){console.log(JSON.stringify({schemaVersion:1,operation:b.operation,target:b.target,rename:{from:c.name,to:e},notCovered:C,steps:I.map((k)=>({id:k.step.id,title:k.step.title,state:k.state,change:k.step.change,reason:k.reason}))},null,2));return}for(let k of Ln(b,I))console.log(k);for(let k of C)console.log(` not covered: ${k}`);console.log(" Re-run with --apply to perform it.");return}let v=await jn(b,I,{log:(C)=>a(C),audit:(C)=>s.controlPlane.store.appendEvent({projectId:s.controlPlane.project.id,resourceId:c.resourceId,type:`${C.operation}.${C.step}.${C.state}`,level:C.state==="failed"?"error":"info",payload:{target:C.target,renameTo:e,...C.error?{error:C.error}:{}}})});if(t.json)console.log(JSON.stringify({schemaVersion:1,outcome:v},null,2));if(!v.success){let C=v.steps.find((k)=>k.state==="failed");throw Error(`${b.operation} stopped at '${C?.title}': ${C?.error}. Earlier steps stayed applied; re-run the same command to continue from here.`)}if(!t.json)h(`Renamed ${c.name} to ${e}.`)})}function Qr(r){r.command("capabilities [server]","Show provider and target operation support").option("--json","Print structured JSON").action(async(e,t)=>{try{await Ue(async(n)=>{let i=e?[yt(n,e)]:n.store.list(n.controlPlane.project.id),o=i.flatMap((s)=>Object.entries(s.capabilities).map(([c,l])=>({server:s.name,provider:s.provider,action:c,...l})));if(t.json)console.log(JSON.stringify({schemaVersion:1,targets:o},null,2));else x(["Server","Provider","Action","Support","Explanation"],o.map((s)=>[s.server,s.provider,s.action,s.supported?"supported":"unsupported",s.reason??"—"]))})}catch(n){fe(n)}}),r.command("server:resize <name> <type>","Safely resize Hetzner compute with full host verification").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Execute the reviewed resize plan").option("--confirm <name>","Exact server name required for mutation").option("--upgrade-disk","Permanently expand the root disk to the target size",{default:!0}).option("--wait","Poll capacity without taking the server offline until placement is available").option("--poll-seconds <seconds>","Capacity retry interval",{default:"900"}).option("--max-wait-minutes <minutes>","Stop waiting after this many minutes; 0 waits indefinitely",{default:"0"}).option("--json","Print structured JSON").action(async(e,t,n)=>{try{await tm(e,t,n)}catch(i){fe(i)}}),r.command("server:optimize <name>","Reconcile and verify a production Hetzner host").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Apply the reviewed host optimization plan").option("--confirm <name>","Exact server name required for mutation").option("--json","Print structured JSON").action(async(e,t)=>{try{await nm(e,t)}catch(n){fe(n)}}),r.command("server:monitoring <name>","Read historical Hetzner CPU, RAM, swap, disk, and network metrics").option("--env <environment>","Deployment environment",{default:"production"}).option("--range <duration>","Historical window from 5m to 24h",{default:"3h"}).option("--step <seconds>","Hetzner provider sample interval",{default:"60"}).option("--json","Print structured JSON").action(async(e,t)=>{try{await Xu(e,t)}catch(n){fe(n)}}),r.command("server:list","List fleet servers").option("--json","Print structured JSON").action(async(e)=>{try{await Ue(async(t)=>{let n=t.store.list(t.controlPlane.project.id);if(e.json)console.log(JSON.stringify(n,null,2));else x(["Name","Provider ID","Provider / region","Status / trust","Roles","CPU / memory","Heartbeat"],n.map((i)=>[i.name,i.providerId??"—",`${i.provider} / ${i.region??"external"}`,`${i.status} / ${i.trustState}`,i.roles.join(","),`${i.capacity.cpu??0} / ${i.capacity.memoryBytes??0}`,i.heartbeatAt??"never"]))})}catch(t){fe(t)}}),r.command("server:create <name>","Enroll a provisioned or existing server").option("--provider <provider>","aws, hetzner, or ssh",{default:"ssh"}).option("--provider-id <id>","Stable provider identity").option("--endpoint <host>","SSH hostname or IP").option("--user <user>","Non-root SSH user",{default:"deploy"}).option("--credential-ref <ref>","Secret reference",{default:"secret://fleet/agent"}).option("--region <region>","Provider region").option("--roles <roles>","Comma-separated roles",{default:"application"}).option("--labels <labels>","Comma-separated key=value labels").action(async(e,t)=>{try{await Ue(async(n)=>{let i=t.provider;if(!["aws","hetzner","ssh"].includes(i)||!t.endpoint)throw Error("A supported --provider and --endpoint are required; provisioning and enrollment are separate operations.");let o=n.service.enroll({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,name:e,provider:i,providerId:t.providerId,endpoint:t.endpoint,sshUser:t.user??"deploy",credentialRef:t.credentialRef??"secret://fleet/agent",region:t.region,roles:String(t.roles??"application").split(","),labels:Object.fromEntries(String(t.labels??"").split(",").map((s)=>s.split("=")).filter((s)=>s[0]&&s[1]))});h(`Enrolled ${o.name} as ${o.id}; no remote mutation was performed.`)})}catch(n){fe(n)}}),r.command("server:validate <name>","Pin trust and validate a server").option("--accept-host-key <fingerprint>","Accept reviewed rotation").option("--json","Print JSON").action(async(e,t)=>{try{await Ue(async(n)=>{let i=yt(n,e);if(i=await n.service.test(i.id),i.trustState==="rotation_pending"){if(!t.acceptHostKey)throw Error(`Host key changed to ${i.pendingHostKey}; review it first.`);i=n.service.reviewHostKey(i.id,t.acceptHostKey)}let o=await n.service.validate(i.id);if(t.json)console.log(JSON.stringify(o.validation,null,2));else x(["Severity","Code","Finding","Remediation"],(o.validation?.findings??[]).map((s)=>[s.severity,s.code,s.message,s.remediation??"—"]));if(!o.validation?.valid)process.exitCode=1})}catch(n){fe(n)}}),r.command("server:bootstrap <name>","Preview or queue bootstrap").option("--apply","Apply reviewed plan").action(async(e,t)=>{try{await Ue(async(n)=>{let i=n.service.bootstrap(yt(n,e).id,!!t.apply);if(i.preview)x(["Step"],i.steps.map((o)=>[o]));else h(`Bootstrap queued: ${i.operation?.id}`)})}catch(n){fe(n)}}),r.command("server:rename <name> <new-name>","Rename a server in place, without recreating it").option("--env <environment>","Deployment environment",{default:"production"}).option("--apply","Perform the reviewed rename plan").option("--json","Print structured JSON").action(async(e,t,n)=>{try{await rm(e,t,n)}catch(i){fe(i)}}),r.command("server:drain <name>","Drain without terminating").option("--complete","Mark movement complete").action(async(e,t)=>{try{await Ue(async(n)=>h(n.service.drain(yt(n,e).id,!!t.complete).status))}catch(n){fe(n)}}),r.command("server:uncordon <name>","Return a server to scheduling").action(async(e)=>{try{await Ue(async(t)=>{t.service.uncordon(yt(t,e).id),h(`Uncordoned ${e}.`)})}catch(t){fe(t)}}),r.command("server:archive <name>","Archive inventory without termination").option("--confirm <name>","Exact name").action(async(e,t)=>{try{await Ue(async(n)=>{n.service.archive(yt(n,e).id,t.confirm??""),h(`Archived ${e}; provider infrastructure was not terminated.`)})}catch(n){fe(n)}}),r.command("server:ssh <name>","Open strict SSH using the enrolled endpoint").action(async(e)=>{try{await Ue(async(t)=>{let n=yt(t,e);if(n.trustState!=="pinned")throw Error("Validate and pin the server host key first.");let i=Bun.spawn(["ssh","-p",String(n.sshPort),"-o","StrictHostKeyChecking=yes",`${n.sshUser}@${n.endpoint}`],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}),o=await i.exited;if(o)process.exitCode=o})}catch(t){fe(t)}});for(let[e,t]of[["server:logs <name>","Use the runtime log service"],["server:deploy <name>","Use immutable release deployment"],["server:reboot <name>","Reboot provider compute"],["server:destroy <name>","Terminate provider compute"],["server:recipe <name> <recipe>","Apply a server recipe"],["server:firewall:add <name> <rule>","Add firewall rule"],["server:firewall:list <name>","List firewall rules"],["server:firewall:remove <name> <rule>","Remove firewall rule"],["server:ssl:install <domain>","Install TLS"],["server:ssl:renew <domain>","Renew TLS"],["server:snapshot <name>","Create provider snapshot"],["server:snapshot:restore <name> <snapshot-id>","Restore provider snapshot"],["server:update <name>","Update OS packages"],["server:secure <name>","Apply OS hardening"]])r.command(e,t).action(async(...n)=>be(e.split(" ")[0],{target:String(n[0]??""),nextAction:"Use the dashboard capability view or the corresponding runtime, release, firewall, backup, or maintenance service."}))}async function im(){let r=await P(),e=q(process.cwd(),r),t=new La(e.store),n=new ja(t,new et(e.store));return{controlPlane:e,store:t,service:n}}async function dn(r){let e=await im();try{await r(e)}catch(t){p(t instanceof Error?t.message:String(t)),process.exitCode=1}finally{e.controlPlane.store.close()}}var Vn=(r)=>Object.fromEntries(String(r??"").split(",").map((e)=>e.split("=")).filter((e)=>e[0]&&e[1])),Oa=(r)=>Object.fromEntries(Object.entries(Vn(r)).map(([e,t])=>[e,Number(t)]));function Xr(r){r.command("pool:list","List capacity pools and reservations").option("--json","Print structured JSON").action(async(e)=>dn((t)=>{let n=t.store.listPools(t.controlPlane.project.id);if(e.json)console.log(JSON.stringify(n,null,2));else x(["Name","Purpose","Backend","Region / arch","Status","Capacity","Concurrency"],n.map((i)=>[i.name,i.purpose,i.backend,`${i.region??"any"} / ${i.architecture??"any"}`,i.status,JSON.stringify(i.capacity),String(i.concurrency)]))})),r.command("pool:create <name>","Create a capacity pool").option("--purpose <purpose>","application, build, worker, monitoring, or backup",{default:"application"}).option("--backend <backend>","server, ecs, or asg",{default:"server"}).option("--capacity <values>","cpu=4,memoryBytes=8589934592,diskBytes=...").option("--region <region>","Required region").option("--arch <architecture>","Required architecture").option("--labels <labels>","Pool key=value labels").option("--selector <labels>","Required server labels").option("--max <count>","Maximum workloads",{default:"10"}).option("--concurrency <count>","Build concurrency",{default:"1"}).action(async(e,t)=>dn((n)=>{let{purpose:i,backend:o}=t;if(!["application","build","worker","monitoring","backup"].includes(i)||!["server","ecs","asg"].includes(o))throw Error("Unsupported pool purpose or backend.");let s=n.store.createPool({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,name:e,purpose:i,backend:o,region:t.region,architecture:t.arch,labels:Vn(t.labels),requiredServerLabels:Vn(t.selector),toleratedTaints:[],capacity:{cpu:0,memoryBytes:0,diskBytes:0,gpu:0,...Oa(t.capacity)},maxWorkloads:Number(t.max),costWeight:1,concurrency:Number(t.concurrency),ephemeralWorkspaces:!0,allowProductionSecrets:!1,status:"active"});h(`Created ${s.name} (${s.id}).`)})),r.command("pool:add-server <pool> <server>","Attach an enrolled server to a pool").action(async(e,t)=>dn((n)=>{let i=n.store.listPools(n.controlPlane.project.id).find((s)=>s.id===e||s.name===e),o=new et(n.controlPlane.store).list(n.controlPlane.project.id).find((s)=>s.id===t||s.name===t);if(!i||!o)throw Error("Pool or server was not found.");n.store.addMember(i.id,o.id),h(`Attached ${o.name} to ${i.name}.`)})),r.command("placement:explain","Explain eligible and rejected placement targets").option("--purpose <purpose>","Workload purpose",{default:"application"}).option("--resources <values>","Requested resources").option("--region <region>","Required region").option("--arch <architecture>","Required architecture").option("--labels <labels>","Required pool labels").option("--json","Print structured JSON").action(async(e)=>dn((t)=>{let n=t.service.explain(t.controlPlane.project.id,{purpose:e.purpose,resources:Oa(e.resources),region:e.region,architecture:e.arch,labels:Vn(e.labels)});if(e.json)console.log(JSON.stringify(n,null,2));else x(["Pool","Server","Eligible","Available","Fit / spread / cost","Reasons"],n.map((i)=>[i.poolName,i.serverId??"provider-managed",i.eligible?"yes":"no",JSON.stringify(i.available),`${i.score.fit.toFixed(2)} / ${i.score.spread} / ${i.score.cost}`,i.reasons.join("; ")||"eligible"]))})),r.command("pool:drain <pool>","Move stateless placements and block stateful placements").action(async(e)=>dn((t)=>{let n=t.store.listPools(t.controlPlane.project.id).find((o)=>o.id===e||o.name===e);if(!n)throw Error("Pool was not found.");let i=t.service.drainPool(n.id);h(`Draining ${n.name}: moved ${i.moved.length}, blocked ${i.blocked.length} stateful placement(s).`)}))}async function om(){let r=await P(),e=q(process.cwd(),r),t=new Fa(e.store),n=new Ma(t);return{controlPlane:e,store:t,service:n}}async function It(r){let e=await om();try{await r(e)}catch(t){p(t instanceof Error?t.message:String(t)),process.exitCode=1}finally{e.controlPlane.store.close()}}var un=(r,e)=>{let t=r.store.list(r.controlPlane.project.id).find((n)=>n.id===e||n.name===e);if(!t)throw Error("Regional topology was not found.");return t},sm=(r)=>r.split(",").map((e,t)=>{let[n,i="aws"]=e.split(":");return{region:String(n),provider:i,role:t===0?"primary":"secondary"}}),am=(r)=>{let e=new Set(["s3","dynamodb","secrets"]),t=r.split(",").map((i)=>i.trim()),n=t.find((i)=>!e.has(i));if(n)throw Error(`Unsupported replication kind: ${n}`);return t};function ei(r){r.command("region:list","List regional topology, traffic, and replication health").option("--json","Print structured JSON").action(async(e)=>It((t)=>{let n=t.store.list(t.controlPlane.project.id).map((i)=>({topology:i,targets:t.store.targets(i.id),channels:t.store.channels(i.id),route:t.store.route(i.id),executions:t.store.executions(i.id)}));if(e.json)console.log(JSON.stringify(n,null,2));else x(["Name","Hostname","Active","Status","Revision","Replication"],n.map((i)=>[i.topology.name,i.topology.hostname,i.topology.activeRegion,i.topology.status,i.topology.revision??"—",`${i.channels.filter((o)=>o.status==="in_sync").length}/${i.channels.length} in sync`]))})),r.command("region:create <name>","Create a persistent multi-region topology").option("--hostname <hostname>","Traffic hostname").option("--regions <regions>","Comma-separated region[:provider], primary first").option("--replicate <kinds>","s3,dynamodb,secrets",{default:"s3,dynamodb,secrets"}).option("--max-lag <seconds>","Maximum failover lag",{default:"30"}).option("--cdn","Enable CloudFront edge state").option("--waf","Enable WAF edge state").action(async(e,t)=>It((n)=>{if(!t.hostname||!t.regions)throw Error("--hostname and --regions are required.");let i=n.service.create({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,name:e,hostname:t.hostname,regions:sm(t.regions),trafficPolicy:"active_passive",dataPolicy:{replicate:am(String(t.replicate)),maxLagSeconds:Number(t.maxLag),retainOnDestroy:!0},dnsProvider:"route53",cdnEnabled:!!t.cdn,wafEnabled:!!t.waf});h(`Created ${i.name} (${i.id}); run region:rollout with an immutable manifest.`)})),r.command("region:plan <name> <operation>","Preview exact regional execution steps").option("--target <region>","Failover or failback region").action(async(e,t,n)=>It((i)=>x(["Step"],i.service.plan(un(i,e).id,t,n.target).map((o)=>[o])))),r.command("region:rollout <name> <revision>","Queue secondary-first regional stack rollout").option("--manifest <file>","Regional stack manifest JSON").action(async(e,t,n)=>It(async(i)=>{let o=n.manifest?await Bun.file(n.manifest).json():{},s=i.service.enqueue({topologyId:un(i,e).id,kind:"rollout",revision:t,manifest:o});h(`Regional rollout queued: ${s.operation.id}`)})),r.command("region:failover <name> <region>","Queue health and RPO-gated failover").action(async(e,t)=>It((n)=>{let i=n.service.enqueue({topologyId:un(n,e).id,kind:"failover",targetRegion:t});h(`Failover queued: ${i.operation.id}`)})),r.command("region:failback <name> <region>","Queue health and RPO-gated failback").action(async(e,t)=>It((n)=>{let i=n.service.enqueue({topologyId:un(n,e).id,kind:"failback",targetRegion:t});h(`Failback queued: ${i.operation.id}`)})),r.command("region:destroy <name>","Drain traffic and delete regional stacks while retaining data").option("--confirm <name>","Exact topology name").action(async(e,t)=>It((n)=>{let i=n.service.enqueue({topologyId:un(n,e).id,kind:"destroy",confirmation:t.confirm});h(`Regional teardown queued: ${i.operation.id}; replicated data remains retained.`)}))}var mn="0.11.3";async function lm(){let r=await P(),e=q(process.cwd(),r),t=new Ba(e.store),n=new _a(t),i=["releases","artifacts","operations","logs","previews","snapshots","orphaned_resources"].map((o)=>new za(o,e.store,e.project.id,e.organization.id));return{controlPlane:e,store:t,service:n,cleanup:i}}async function tt(r){let e=await lm();try{await r(e)}catch(t){p(t instanceof Error?t.message:String(t)),process.exitCode=1}finally{e.controlPlane.store.close()}}function ti(r){r.command("maintenance:list","List updates, campaigns, cleanup plans, and DR drills").option("--json","Print structured JSON").action(async(e)=>tt((t)=>{let n={manifests:t.store.manifests(t.controlPlane.organization.id),windows:t.store.windows(t.controlPlane.project.id),campaigns:t.store.campaigns(t.controlPlane.project.id),cleanups:t.store.cleanups(t.controlPlane.project.id),drills:t.store.drills(t.controlPlane.project.id)},i=[...n.manifests.map((o)=>["update",o.version,o.compatibility.compatible?"compatible":"blocked",o.compatibility.reasons.join("; ")||o.channel]),...n.campaigns.map((o)=>["upgrade",o.id,o.status,o.currentStage??o.fromVersion]),...n.cleanups.map((o)=>["cleanup",o.kind,o.status,`${o.candidates.length} items / ${o.estimatedBytes} bytes`]),...n.drills.map((o)=>["DR drill",o.scenario,o.status,o.isolatedTarget])];if(e.json)console.log(JSON.stringify(n,null,2));else x(["Area","Name / version","Status","Detail"],i)})),r.command("maintenance:key:add <name> <pem>","Trust an Ed25519 update signing key").action(async(e,t)=>tt(async(n)=>{let i=n.store.addTrustedKey({organizationId:n.controlPlane.organization.id,name:e,publicKeyPem:await Bun.file(t).text()});h(`Trusted ${i.name}: ${i.fingerprint}`)})),r.command("maintenance:manifest <document> <signature> <key>","Verify and import a signed update manifest").action(async(e,t,n)=>tt(async(i)=>{let o=await Bun.file(e).json(),s=(await Bun.file(t).text()).trim(),c=i.store.importManifest({organizationId:i.controlPlane.organization.id,keyId:n,document:o,signature:s,current:{schemaVersion:i.controlPlane.store.health().schemaVersion,currentVersion:mn,platform:process.platform,architecture:process.arch}});h(`Verified ${c.version}; ${c.compatibility.compatible?"compatible":"blocked: "+c.compatibility.reasons.join("; ")}`)})),r.command("maintenance:window <name> <schedule>","Create an approved maintenance window").option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--minutes <minutes>","Duration",{default:"60"}).action(async(e,t,n)=>tt((i)=>{let o=i.store.createWindow({projectId:i.controlPlane.project.id,name:e,schedule:t,timezone:n.timezone,durationMinutes:Number(n.minutes),allowedOperations:["platform.upgrade"],requireApproval:!0});h(`Created ${o.name} (${o.id}).`)})),r.command("platform:upgrade <manifest> <backup>","Plan and queue a signed staged platform update").option("--strategy <strategy>","canary or rolling",{default:"canary"}).option("--batch <count>","Rolling batch size",{default:"1"}).action(async(e,t,n)=>tt((i)=>{let o=new et(i.controlPlane.store).list(i.controlPlane.project.id).filter((l)=>l.status==="ready").map((l)=>({serverId:l.id,target:l.name,previousVersion:l.bootstrapVersion??mn}));if(!o.length)o.push({target:"control-plane",previousVersion:mn});let s=i.store.createCampaign({projectId:i.controlPlane.project.id,manifestId:e,fromVersion:mn,strategy:n.strategy==="rolling"?"rolling":"canary",batchSize:Number(n.batch),healthGate:{kind:"platform"},backupId:t,targets:o}),c=i.service.enqueueCampaign(s.id);h(`Staged upgrade queued: ${c.operation.id}`)})),r.command("cleanup:preview <kind>","Discover a bounded cleanup candidate set without deleting").option("--older-than <days>","Minimum age",{default:"30"}).action(async(e,t)=>tt(async(n)=>{let i=await n.service.previewCleanup({projectId:n.controlPlane.project.id,kind:e,criteria:{olderThanDays:Number(t.olderThan)}},n.cleanup);x(["ID","Candidate","Bytes"],i.candidates.map((o)=>[o.id,o.label,String(o.bytes)])),a(`Apply with: cloud cleanup:apply ${i.id} --confirm "DELETE ${i.candidates.length} ${i.kind}"`)})),r.command("cleanup:apply <plan>","Apply an unchanged cleanup preview").option("--confirm <text>","Exact preview confirmation").action(async(e,t)=>tt((n)=>{let i=n.service.enqueueCleanup(e,String(t.confirm??""));h(`Cleanup queued: ${i.operation.id}`)})),r.command("dr:plan <scenario> <target>","Plan an isolated disaster-recovery drill").option("--rpo <minutes>","RPO objective",{default:"60"}).option("--rto <minutes>","RTO objective",{default:"30"}).option("--recovery-point <id>","Recovery point").option("--topology <id>","Regional topology").action(async(e,t,n)=>tt((i)=>{let o=i.store.createDrill({projectId:i.controlPlane.project.id,scenario:e,isolatedTarget:t,expectedRpoMinutes:Number(n.rpo),expectedRtoMinutes:Number(n.rto),recoveryPointId:n.recoveryPoint,topologyId:n.topology});h(`Planned isolated drill ${o.id}.`)})),r.command("dr:run <drill>","Queue an isolated disaster-recovery drill").action(async(e)=>tt((t)=>{let n=t.service.enqueueDrill(e);h(`DR drill queued: ${n.operation.id}`)}))}async function ni(){try{return(await P()).project.region||process.env.AWS_REGION||"us-east-1"}catch{return process.env.AWS_REGION||"us-east-1"}}function ri(r){r.command("function:list","List all Lambda functions").option("--region <region>","AWS region").action(async(e)=>{w("Lambda Functions");let t=e?.region||await ni(),n=new nt(t);try{let{Functions:i=[]}=await n.listFunctions({MaxItems:50});if(!i.length){a("No functions found");return}x(["Name","Runtime","Memory","Timeout","Last Modified"],i.map((o)=>[o.FunctionName??"-",o.Runtime??"-",o.MemorySize?`${o.MemorySize} MB`:"-",o.Timeout?`${o.Timeout}s`:"-",o.LastModified??"-"]))}catch(i){p(`Failed to list functions: ${i.message}`),process.exitCode=1}}),r.command("function:invoke <name>","Invoke a Lambda function").option("--payload <json>","Event payload as JSON",{default:"{}"}).option("--async","Fire-and-forget (Event invocation)").option("--region <region>","AWS region").action(async(e,t)=>{w(`Invoking ${e}`);let n=t?.region||await ni(),i=new nt(n),o=new A("Invoking function...");o.start();try{let s=await i.invoke({FunctionName:e,InvocationType:t?.async?"Event":"RequestResponse",Payload:t?.payload||"{}",LogType:t?.async?"None":"Tail"});if(s.FunctionError){if(o.fail(`Function returned an error (${s.FunctionError})`),s.Payload)a(s.Payload);process.exitCode=1;return}if(o.succeed(`Invoked (status ${s.StatusCode})`),s.Payload)a(`
|
|
319
319
|
Response:`),a(s.Payload);if(s.LogResult)a(`
|
|
320
|
-
Logs:`),a(Buffer.from(s.LogResult,"base64").toString("utf-8"))}catch(s){o.fail(`Invocation failed: ${s.message}`),process.exitCode=1}}),r.command("function:logs <name>","View recent function logs").option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <minutes>","Look back this many minutes",{default:"15"}).option("--region <region>","AWS region").action(async(e,t)=>{w(`Logs for ${e}`);let n=t?.region||await ni(),i=new
|
|
320
|
+
Logs:`),a(Buffer.from(s.LogResult,"base64").toString("utf-8"))}catch(s){o.fail(`Invocation failed: ${s.message}`),process.exitCode=1}}),r.command("function:logs <name>","View recent function logs").option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <minutes>","Look back this many minutes",{default:"15"}).option("--region <region>","AWS region").action(async(e,t)=>{w(`Logs for ${e}`);let n=t?.region||await ni(),i=new rt(n),o=e.startsWith("/aws/lambda/")?e:`/aws/lambda/${e}`,s=Number(t?.since??15),c=(l)=>{for(let d of l){let u=d.timestamp?new Date(d.timestamp).toISOString():"";a(`${u} ${(d.message??"").trimEnd()}`)}};try{let l=Date.now()-s*60000,{events:d=[]}=await i.filterLogEvents({logGroupName:o,startTime:l,filterPattern:t?.filter,limit:200});if(c(d),d.length)l=(d[d.length-1].timestamp??l)+1;if(t?.tail){a(`
|
|
321
321
|
(streaming — Ctrl+C to stop)`);for(;;){await new Promise((m)=>setTimeout(m,3000));let{events:u=[]}=await i.filterLogEvents({logGroupName:o,startTime:l,filterPattern:t?.filter,limit:200});if(u.length)c(u),l=(u[u.length-1].timestamp??l)+1}}}catch(l){p(`Failed to read logs: ${l.message}`),process.exitCode=1}})}async function Ua(r){let e=Bun.spawn(["docker",...r],{stdin:"inherit",stdout:"inherit",stderr:"pipe"}),[t,n]=await Promise.all([e.exited,new Response(e.stderr).text()]);return{ok:t===0,exitCode:t,command:["docker",...r].join(" "),stderr:n.trim()}}function ii(r){r.command("container:build","Build a Docker image").option("--tag <tag>","Image tag",{default:"latest"}).option("--file <dockerfile>","Dockerfile",{default:"Dockerfile"}).option("--dry-run","Print the exact command").option("--json","Print result JSON").action(async(e)=>{let t=["build","--file",e.file??"Dockerfile","--tag",e.tag??"latest","."];if(e.dryRun){console.log(`docker ${t.join(" ")}`);return}let n=await Ua(t);if(e.json)console.log(JSON.stringify(n));if(!n.ok)p(n.stderr||`docker exited ${n.exitCode}`),process.exitCode=n.exitCode;else h(`Built ${e.tag??"latest"}.`)}),r.command("container:push","Push an already-tagged image").option("--image <image>","Full registry image reference").option("--dry-run","Print command").option("--json","Print JSON").action(async(e)=>{if(!e.image){p("--image with a full registry reference is required; credentials must come from docker credential helpers."),process.exitCode=2;return}if(e.dryRun){console.log(`docker push ${e.image}`);return}let t=await Ua(["push",e.image]);if(e.json)console.log(JSON.stringify(t));if(!t.ok)p(t.stderr),process.exitCode=t.exitCode;else h(`Pushed ${e.image}.`)}),r.command("container:deploy","Deploy a container image").action(async()=>be("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 mi(r){r.command("domain:list","List all domains").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").action(async(e)=>{w("Domains");try{let t=Be(e?.provider),n=ce(e?.provider)?.provider||"unknown",i=new A(`Fetching domains from ${n}...`);i.start();let o=await t.listDomains();if(i.succeed(`Found ${o.length} domain(s)`),o.length===0){a("No domains found in this provider");return}let s=o.map((c)=>[c,"Active","-",n.charAt(0).toUpperCase()+n.slice(1)]);x(["Domain","Status","SSL","Provider"],s)}catch(t){p(`Failed to list domains: ${t instanceof Error?t.message:String(t)}`)}}),r.command("domain:add <domain>","Add a new domain").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").action(async(e,t)=>{w(`Adding Domain: ${e}`);try{let n=Be(t?.provider),i=ce(t?.provider)?.provider||"unknown",o=new A(`Checking if ${i} can manage ${e}...`);if(o.start(),await n.canManageDomain(e))o.succeed(`Domain ${e} is already available in ${i}`),a(`
|
|
322
322
|
The domain is ready to use. You can now:`),a(` - Add DNS records: cloud dns:add ${e} A 192.168.1.1`),a(` - Generate SSL: cloud domain:ssl ${e}`);else if(o.warn(`Domain ${e} is not available in ${i}`),a(`
|
|
323
323
|
To add this domain:`),i==="route53")a(" - Create a hosted zone in Route53 for this domain"),a(" - Update nameservers at your registrar to point to Route53");else a(` - Ensure the domain is registered with ${i}`),a(" - Enable API access for the domain in your provider dashboard")}catch(n){p(`Failed to check domain: ${n instanceof Error?n.message:String(n)}`)}}),r.command("domain:ssl <domain>","Generate SSL certificate via ACM with DNS validation").option("--provider <provider>","DNS provider for validation: porkbun, godaddy, or route53").option("--region <region>","AWS region for ACM (default: us-east-1 for CloudFront compatibility)").option("--wait","Wait for certificate validation to complete").action(async(e,t)=>{w(`Generating SSL Certificate for ${e}`);try{let n=ce(t?.provider);if(!n)throw Error("No DNS provider configured");let i=t?.region||"us-east-1",o=n.provider;a(`DNS Provider: ${o}`),a(`ACM Region: ${i}`);let s=new ui(n,i),c=new A("Requesting certificate and creating validation records...");c.start();let l=await s.findOrCreateCertificate({domainName:e,subjectAlternativeNames:[`*.${e}`],waitForValidation:t?.wait??!0,maxWaitMinutes:10});if(l.isNew)c.succeed("Certificate requested and validation records created");else c.succeed("Found existing valid certificate");if(a(`Certificate ARN: ${l.certificateArn}`),a(`Status: ${l.status}`),l.status==="issued")h(`
|
|
@@ -331,7 +331,7 @@ Record summary:`);for(let[y,S]of f)a(` - ${y}: ${S}`)}}catch(n){p(`Failed to ve
|
|
|
331
331
|
Record created!`),a(`
|
|
332
332
|
Note: DNS changes may take a few minutes to propagate`)}catch(o){p(`Failed to add record: ${o instanceof Error?o.message:String(o)}`)}}),r.command("dns:delete <domain> <type>","Delete DNS record").option("--provider <provider>","DNS provider: porkbun, godaddy, cloudflare, or route53").option("--name <name>","Record name (subdomain)",{default:"@"}).option("--value <value>","Record value (required for multi-value records)").action(async(e,t,n)=>{w("Deleting DNS Record");try{let i=Be(n?.provider),o=ce(n?.provider)?.provider||"unknown",s=n?.name||"@",c=t.toUpperCase();a(`Provider: ${o}`),a(`Domain: ${e}`),a(`Type: ${c}`),a(`Name: ${s}`);let l=new A("Finding record...");l.start();let m=((await i.listRecords(e)).records||[]).filter((S)=>S.type.toUpperCase()===c&&(S.name===s||S.name===""&&s==="@"));if(m.length===0){l.fail("No matching record found");return}if(m.length>1&&!n?.value){l.warn("Multiple records found"),a(`
|
|
333
333
|
Please specify --value to identify which record to delete:`);for(let S of m)a(` - ${S.content}`);return}let f=n?.value?m.find((S)=>S.content===n.value)||m[0]:m[0];if(a(`Value: ${f.content}`),!await O("Delete this record?",!1)){a("Deletion cancelled");return}l.text=`Deleting record via ${o}...`,l.start(),await i.deleteRecord(e,f),l.succeed("DNS record deleted successfully"),h(`
|
|
334
|
-
Record deleted!`)}catch(i){p(`Failed to delete record: ${i instanceof Error?i.message:String(i)}`)}})}function dm(){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 Va=["postgres","mysql","mariadb","redis","mongodb","libsql"],Wa=["aws_rds","aws_aurora","aws_elasticache","server","container","external"],qa=["observe","connect","backup","restore","restart","resize","version","rotate","expose","delete","logs","slow_queries","users","databases"];async function um(r,e){let t=await P(),n=q(process.cwd(),t),i=new Gn(n.store),o=!r&&e?i.get(e)??i.list(n.project.id).find((g)=>g.name===e):void 0,s=o?.environmentId?n.store.listEnvironments(n.project.id).find((g)=>g.id===o.environmentId)?.slug:void 0,c=r??s??Object.keys(t.environments??{})[0]??"production",l=n.environments.get(c);if(!l)throw n.store.close(),Error(`Environment ${c} was not found.`);let d=new We(n.store,{workerId:`cli:${process.pid}`}),u=new Kn(n.store,le(process.cwd())),m=new Ya(i,d,u),f=n.store.getActorByExternalId("system","cli")??n.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),y=t.project?.region??"us-east-1",S={aws_rds:new Za(new nc(new qn(y))),aws_aurora:new Qa(new rc(new qn(y))),aws_elasticache:new Xa(new ic(new qe(y))),server:new ec(new hi),container:new tc(new hi)};return{config:t,controlPlane:n,environmentRecord:l,env:c,store:i,queue:d,secrets:u,lifecycle:m,actor:f,resolveAdapter:(g)=>S[g.provider]}}async function Pe(r,e,t){let n=await um(r,t);try{return await e(n)}finally{n.controlPlane.store.close()}}async function we(r){try{await r()}catch(e){p(e instanceof Error?e.message:String(e))}}function pi(r){if(!Va.includes(r))throw Error(`--engine must be ${Va.join(", ")}.`);return r}function fi(r){if(!Wa.includes(r))throw Error(`--provider must be ${Wa.join(", ")}.`);return r}function Pt(r,e){let t=r.store.list(r.controlPlane.project.id,r.environmentRecord.id),n=r.store.get(e)??t.find((i)=>i.name===e);if(!n||n.environmentId!==r.environmentRecord.id)throw Error(`Data service ${e} was not found in ${r.env}.`);return n}function Ga(r){return r.map((e)=>[e.id,e.name,e.engine,e.provider,e.status,e.plan,e.publicExposure?"public allowlist":"private",e.managementEnabled?"managed":"read-only"])}function mm(r,e){return r!==null&&typeof r==="object"&&!Array.isArray(r)&&r.serviceId===e}function pm(r){let e={};if(r.plan)e.plan=r.plan;if(r.storage!=null)e.storageGb=Number(r.storage);if(r.engineVersion)e.engineVersion=r.engineVersion;if(r.retention)e.retention=r.retention;if(r.confirm)e.confirm=r.confirm;if(r.backupId)e.backupId=r.backupId;if(r.compatibilityReviewed)e.compatibilityReviewed=!0;if(r.minCapacity!=null)e.minCapacity=Number(r.minCapacity);if(r.maxCapacity!=null)e.maxCapacity=Number(r.maxCapacity);if(r.public!=null)e.publicExposure=r.public;if(r.cidrs)e.allowedCidrs=r.cidrs.split(",").map((t)=>t.trim()).filter(Boolean);return e}function Ka(r){let e=typeof r.observedState.endpoint==="string"?r.observedState.endpoint:r.placement,t=Number(r.observedState.port)||(r.engine==="postgres"?5432:r.engine==="redis"?6379:r.engine==="mongodb"?27017:r.engine==="libsql"?8080:3306),n=typeof r.observedState.database==="string"?r.observedState.database:void 0;return[{type:r.publicExposure?"external":"internal",host:e,port:t,database:n,tls:typeof r.observedState.tls==="boolean"?r.observedState.tls:r.provider.startsWith("aws_")},{type:"tunnel",host:e,port:t,database:n,tls:typeof r.observedState.tls==="boolean"?r.observedState.tls:r.provider.startsWith("aws_")}]}function Ha(r){r.option("--env <environment>","Target environment").option("--engine <engine>","postgres, mysql, mariadb, redis, mongodb, or libsql",{default:"postgres"}).option("--provider <provider>","aws_rds, aws_aurora, aws_elasticache, server, container, or external",{default:"aws_rds"}).option("--placement <id>","Provider identifier or runtime placement").option("--plan <plan>","Provider plan or instance type",{default:"db.t4g.micro"}).option("--engine-version <version>","Engine version").option("--storage <gb>","Allocated storage in GB",{default:"20"}).option("--username <name>","Generated credential username",{default:"app"}).option("--database <name>","Initial logical database").option("--subnet-group <name>","Private subnet group").option("--security-groups <ids>","Comma-separated security group IDs").option("--high-availability","Enable provider high availability").option("--public","Request a public endpoint; requires narrow --cidrs").option("--cidrs <ranges>","Comma-separated narrow public CIDRs")}function yi(r){r.command("data:list","List first-class databases and caches").option("--env <environment>","Target environment").option("--json","Print structured JSON").action((n)=>we(()=>Pe(n.env,async(i)=>{let o=i.store.list(i.controlPlane.project.id,i.environmentRecord.id);if(n.json)a(JSON.stringify(o,null,2));else x(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],Ga(o))}))),r.command("data:capabilities <engine> <provider>","Explain lifecycle support before taking action").action((n,i)=>we(async()=>{a(JSON.stringify(Ja(pi(n),fi(i)),null,2))}));let e=r.command("data:create <name>","Provision an encrypted, private-by-default data service");Ha(e),e.action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=pi(i.engine),c=fi(i.provider),l=o.resolveAdapter({provider:c});if(!l||!l.engines.includes(s))throw Error(`No ${c}/${s} provisioning adapter is configured. Use data:adopt for read-only inventory.`);let d={...i.database?{database:i.database}:{},...i.subnetGroup?{subnetGroup:i.subnetGroup}:{},...i.securityGroups?{securityGroupIds:i.securityGroups.split(",").map((m)=>m.trim()).filter(Boolean)}:{}},u=await o.lifecycle.create({organizationId:o.controlPlane.organization.id,projectId:o.controlPlane.project.id,environmentId:o.environmentRecord.id,name:n,engine:s,provider:c,placement:i.placement??n,engineVersion:i.engineVersion,plan:i.plan??"db.t4g.micro",storageGb:Number(i.storage)||void 0,highAvailability:i.highAvailability===!0,publicExposure:i.public===!0,allowedCidrs:i.cidrs?i.cidrs.split(",").map((m)=>m.trim()).filter(Boolean):[],desiredState:d,observedState:{},origin:"managed",managementEnabled:!0,ownerActorId:o.actor.id,username:i.username});if(h(`Queued ${u.service.name} as operation ${u.operationId}.`),u.credential)fn("One-time credential — copy it now:"),a(`Username: ${u.credential.username}`),a(`Password: ${u.credential.password}`),a(`Reference: ${u.credential.secretRef}`)})));let t=r.command("data:adopt <name>","Adopt an existing provider resource as read-only inventory");Ha(t),t.action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=o.store.create({organizationId:o.controlPlane.organization.id,projectId:o.controlPlane.project.id,environmentId:o.environmentRecord.id,name:n,engine:pi(i.engine),provider:fi(i.provider),placement:i.placement??n,engineVersion:i.engineVersion,plan:i.plan??"external",storageGb:Number(i.storage)||void 0,highAvailability:i.highAvailability===!0,publicExposure:i.public===!0,allowedCidrs:i.cidrs?i.cidrs.split(",").map((c)=>c.trim()).filter(Boolean):[],desiredState:{},observedState:{},credentialRef:void 0,status:"adopted",origin:"adopted",managementEnabled:!1,ownerActorId:o.actor.id});h(`Adopted ${s.name} (${s.id}) read-only.`)}))),r.command("data:show <service>","Show safe service, capability, dependency, and operation metadata").option("--env <environment>","Target environment").option("--reveal","Reveal the encrypted credential with exact confirmation").option("--confirm <name>","Exact service name required with --reveal").action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=Pt(o,n),c=o.store.credential(s.id),l={...s,credentialRef:void 0,credential:c?{configured:!0,username:c.username,version:c.version,rotatedAt:c.rotatedAt}:void 0,dependencies:o.store.dependencies(s.id).map((d)=>({resourceId:d.resourceId,requiresRedeploy:d.requiresRedeploy})),operations:o.queue.list({projectId:s.projectId,limit:250}).filter((d)=>mm(d.operation.input,s.id))};if(a(JSON.stringify(l,null,2)),i.reveal){if(i.confirm!==s.name)throw Error(`Credential reveal requires --confirm ${s.name}.`);if(!c)throw Error("No managed credential is configured.");fn("Credential value follows; it will not be logged or persisted by ts-cloud:"),a(`Username: ${c.username}`),a(`Password: ${await o.secrets.resolve(c.secretRef)}`)}},n))),r.command("data:connect <service>","Print secret-free connection and tunnel guidance").option("--env <environment>","Target environment").action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=Pt(o,n);x(["Type","Command","Credential reference"],gi(s,Ka(s)).map((c)=>[c.type,c.command,c.secretRef??"—"]))},n))),r.command("data:action <service> <action>","Preview or enqueue a durable lifecycle action").option("--env <environment>","Target environment").option("--execute","Enqueue after validation; default is preview only").option("--plan <plan>","New provider plan").option("--storage <gb>","New storage allocation").option("--engine-version <version>","Target engine version").option("--min-capacity <acu>","Aurora minimum capacity").option("--max-capacity <acu>","Aurora maximum capacity").option("--public","Enable public exposure").option("--cidrs <ranges>","Comma-separated narrow public CIDRs").option("--retention <choice>","final_backup or retain for deletion").option("--confirm <name>","Exact service name for destructive actions").option("--backup-id <id>","Required backup for a major version change").option("--compatibility-reviewed","Confirm major-version compatibility review").action((n,i,o)=>we(()=>Pe(o.env,async(s)=>{if(!qa.includes(i))throw Error(`Action must be ${qa.join(", ")}.`);let c=Pt(s,n),l=i,d=pm(o),u=s.lifecycle.plan(c,l,d);if(a(JSON.stringify({...u,productionExecutionCreated:!1},null,2)),!o.execute)return;let m=s.lifecycle.enqueue(c,l,d,s.actor.id);h(`Queued ${l} as ${m}.`)},n))),r.command("data:management <service>","Enable reviewed management of an adopted service").option("--env <environment>","Target environment").option("--confirm <name>","Exact service name").action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=Pt(o,n);if(s.origin!=="adopted"||i.confirm!==s.name)throw Error(`Enabling adopted management requires --confirm ${s.name}.`);let c=o.store.update(s.id,s.version,{managementEnabled:!0});h(`${c.name} management is enabled.`)},n))),r.command("data:dependency <service> <resource>","Register or remove an application credential dependency").option("--env <environment>","Target environment").option("--dynamic","Dependency consumes a dynamic reference and does not need redeploy").option("--remove","Remove the dependency").action((n,i,o)=>we(()=>Pe(o.env,async(s)=>{let c=Pt(s,n),l=s.controlPlane.store.listResources(s.controlPlane.project.id,s.environmentRecord.id).find((d)=>d.id===i||d.slug===i);if(!l)throw Error(`Resource ${i} was not found in ${s.env}.`);if(o.remove){a(s.store.removeDependency(c.id,l.id)?"Dependency removed.":"Dependency was not registered.");return}s.store.addDependency({serviceId:c.id,resourceId:l.id,secretRef:c.credentialRef??`secret://data-services/${c.projectId}/${c.name}`,requiresRedeploy:!o.dynamic}),h(`Registered dependency ${l.slug} -> ${c.name}.`)},n))),r.command("db:list","Alias for data:list").option("--env <environment>","Target environment").action((n)=>we(()=>Pe(n.env,async(i)=>x(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],Ga(i.store.list(i.controlPlane.project.id,i.environmentRecord.id)))))),r.command("db:connect <service>","Alias for data:connect").option("--env <environment>","Target environment").action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=Pt(o,n);x(["Type","Command","Credential reference"],gi(s,Ka(s)).map((c)=>[c.type,c.command,c.secretRef??"—"]))},n)));for(let n of["backup","snapshot"])r.command(`db:${n} <service>`,`Alias for data:action ${n==="backup"?"backup":"backup"}`).option("--env <environment>","Target environment").option("--execute","Enqueue the backup; default previews").action((i,o)=>we(()=>Pe(o.env,async(s)=>{let c=Pt(s,i),l=s.lifecycle.plan(c,"backup");if(a(JSON.stringify({...l,productionExecutionCreated:!1},null,2)),o.execute)h(`Queued backup as ${s.lifecycle.enqueue(c,"backup",{},s.actor.id)}.`)},i)))}function oc(r,e=15){if(!r)return e*60000;let t=/^(\d+)\s*([smhd])?$/.exec(r.trim());if(!t)return e*60000;let n=Number(t[1]),i=t[2]??"m";return n*({s:1000,m:60000,h:3600000,d:86400000}[i]??60000)}function fm(r,e){for(let t of r){let n=t.timestamp?new Date(t.timestamp).toISOString():"",i=e?`[${e}] `:"";a(`${n} ${i}${(t.message??"").trimEnd()}`)}}function vi(r){r.command("logs","Stream serverless application logs (http + queue + cli)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli | all",{default:"all"}).option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <duration>","Look back (e.g. 1h, 30m, 15)",{default:"15m"}).action(async(e)=>{try{let t=await P(),n=e?.env||"production",{region:i,functions:o}=bt(t,n),s=new nt(i),c=(e?.function??"all").toLowerCase();if(c!=="all"&&!["http","queue","cli"].includes(c)){p("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let d=(c==="all"?["http","queue","cli"]:[c]).map((f)=>({mode:f,group:`/aws/lambda/${o[f]}`}));w(`Logs — ${t.project.slug} (${n})`);let u=Date.now()-oc(e?.since),m=async()=>{let f=u;for(let{mode:y,group:S}of d)try{let{events:g=[]}=await s.filterLogEvents({logGroupName:S,startTime:u,filterPattern:e?.filter,limit:200});fm(g,d.length>1?y:void 0);for(let b of g)if((b.timestamp??0)>f)f=b.timestamp??f}catch(g){if(!/ResourceNotFound/i.test(String(g?.message)))throw g}u=f+1};if(await m(),e?.tail){a(`
|
|
334
|
+
Record deleted!`)}catch(i){p(`Failed to delete record: ${i instanceof Error?i.message:String(i)}`)}})}function dm(){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 Va=["postgres","mysql","mariadb","redis","mongodb","libsql"],Wa=["aws_rds","aws_aurora","aws_elasticache","server","container","external"],qa=["observe","connect","backup","restore","restart","resize","version","rotate","expose","delete","logs","slow_queries","users","databases"];async function um(r,e){let t=await P(),n=q(process.cwd(),t),i=new Gn(n.store),o=!r&&e?i.get(e)??i.list(n.project.id).find((g)=>g.name===e):void 0,s=o?.environmentId?n.store.listEnvironments(n.project.id).find((g)=>g.id===o.environmentId)?.slug:void 0,c=r??s??Object.keys(t.environments??{})[0]??"production",l=n.environments.get(c);if(!l)throw n.store.close(),Error(`Environment ${c} was not found.`);let d=new We(n.store,{workerId:`cli:${process.pid}`}),u=new Kn(n.store,le(process.cwd())),m=new Ya(i,d,u),f=n.store.getActorByExternalId("system","cli")??n.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"}),y=t.project?.region??"us-east-1",S={aws_rds:new Za(new nc(new qn(y))),aws_aurora:new Qa(new rc(new qn(y))),aws_elasticache:new Xa(new ic(new qe(y))),server:new ec(new hi),container:new tc(new hi)};return{config:t,controlPlane:n,environmentRecord:l,env:c,store:i,queue:d,secrets:u,lifecycle:m,actor:f,resolveAdapter:(g)=>S[g.provider]}}async function Pe(r,e,t){let n=await um(r,t);try{return await e(n)}finally{n.controlPlane.store.close()}}async function we(r){try{await r()}catch(e){p(e instanceof Error?e.message:String(e))}}function pi(r){if(!Va.includes(r))throw Error(`--engine must be ${Va.join(", ")}.`);return r}function fi(r){if(!Wa.includes(r))throw Error(`--provider must be ${Wa.join(", ")}.`);return r}function Pt(r,e){let t=r.store.list(r.controlPlane.project.id,r.environmentRecord.id),n=r.store.get(e)??t.find((i)=>i.name===e);if(!n||n.environmentId!==r.environmentRecord.id)throw Error(`Data service ${e} was not found in ${r.env}.`);return n}function Ga(r){return r.map((e)=>[e.id,e.name,e.engine,e.provider,e.status,e.plan,e.publicExposure?"public allowlist":"private",e.managementEnabled?"managed":"read-only"])}function mm(r,e){return r!==null&&typeof r==="object"&&!Array.isArray(r)&&r.serviceId===e}function pm(r){let e={};if(r.plan)e.plan=r.plan;if(r.storage!=null)e.storageGb=Number(r.storage);if(r.engineVersion)e.engineVersion=r.engineVersion;if(r.retention)e.retention=r.retention;if(r.confirm)e.confirm=r.confirm;if(r.backupId)e.backupId=r.backupId;if(r.compatibilityReviewed)e.compatibilityReviewed=!0;if(r.minCapacity!=null)e.minCapacity=Number(r.minCapacity);if(r.maxCapacity!=null)e.maxCapacity=Number(r.maxCapacity);if(r.public!=null)e.publicExposure=r.public;if(r.cidrs)e.allowedCidrs=r.cidrs.split(",").map((t)=>t.trim()).filter(Boolean);return e}function Ka(r){let e=typeof r.observedState.endpoint==="string"?r.observedState.endpoint:r.placement,t=Number(r.observedState.port)||(r.engine==="postgres"?5432:r.engine==="redis"?6379:r.engine==="mongodb"?27017:r.engine==="libsql"?8080:3306),n=typeof r.observedState.database==="string"?r.observedState.database:void 0;return[{type:r.publicExposure?"external":"internal",host:e,port:t,database:n,tls:typeof r.observedState.tls==="boolean"?r.observedState.tls:r.provider.startsWith("aws_")},{type:"tunnel",host:e,port:t,database:n,tls:typeof r.observedState.tls==="boolean"?r.observedState.tls:r.provider.startsWith("aws_")}]}function Ha(r){r.option("--env <environment>","Target environment").option("--engine <engine>","postgres, mysql, mariadb, redis, mongodb, or libsql",{default:"postgres"}).option("--provider <provider>","aws_rds, aws_aurora, aws_elasticache, server, container, or external",{default:"aws_rds"}).option("--placement <id>","Provider identifier or runtime placement").option("--plan <plan>","Provider plan or instance type",{default:"db.t4g.micro"}).option("--engine-version <version>","Engine version").option("--storage <gb>","Allocated storage in GB",{default:"20"}).option("--username <name>","Generated credential username",{default:"app"}).option("--database <name>","Initial logical database").option("--subnet-group <name>","Private subnet group").option("--security-groups <ids>","Comma-separated security group IDs").option("--high-availability","Enable provider high availability").option("--public","Request a public endpoint; requires narrow --cidrs").option("--cidrs <ranges>","Comma-separated narrow public CIDRs")}function yi(r){r.command("data:list","List first-class databases and caches").option("--env <environment>","Target environment").option("--json","Print structured JSON").action((n)=>we(()=>Pe(n.env,async(i)=>{let o=i.store.list(i.controlPlane.project.id,i.environmentRecord.id);if(n.json)a(JSON.stringify(o,null,2));else x(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],Ga(o))}))),r.command("data:capabilities <engine> <provider>","Explain lifecycle support before taking action").action((n,i)=>we(async()=>{a(JSON.stringify(Ja(pi(n),fi(i)),null,2))}));let e=r.command("data:create <name>","Provision an encrypted, private-by-default data service");Ha(e),e.action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=pi(i.engine),c=fi(i.provider),l=o.resolveAdapter({provider:c});if(!l||!l.engines.includes(s))throw Error(`No ${c}/${s} provisioning adapter is configured. Use data:adopt for read-only inventory.`);let d={...i.database?{database:i.database}:{},...i.subnetGroup?{subnetGroup:i.subnetGroup}:{},...i.securityGroups?{securityGroupIds:i.securityGroups.split(",").map((m)=>m.trim()).filter(Boolean)}:{}},u=await o.lifecycle.create({organizationId:o.controlPlane.organization.id,projectId:o.controlPlane.project.id,environmentId:o.environmentRecord.id,name:n,engine:s,provider:c,placement:i.placement??n,engineVersion:i.engineVersion,plan:i.plan??"db.t4g.micro",storageGb:Number(i.storage)||void 0,highAvailability:i.highAvailability===!0,publicExposure:i.public===!0,allowedCidrs:i.cidrs?i.cidrs.split(",").map((m)=>m.trim()).filter(Boolean):[],desiredState:d,observedState:{},origin:"managed",managementEnabled:!0,ownerActorId:o.actor.id,username:i.username});if(h(`Queued ${u.service.name} as operation ${u.operationId}.`),u.credential)fn("One-time credential — copy it now:"),a(`Username: ${u.credential.username}`),a(`Password: ${u.credential.password}`),a(`Reference: ${u.credential.secretRef}`)})));let t=r.command("data:adopt <name>","Adopt an existing provider resource as read-only inventory");Ha(t),t.action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=o.store.create({organizationId:o.controlPlane.organization.id,projectId:o.controlPlane.project.id,environmentId:o.environmentRecord.id,name:n,engine:pi(i.engine),provider:fi(i.provider),placement:i.placement??n,engineVersion:i.engineVersion,plan:i.plan??"external",storageGb:Number(i.storage)||void 0,highAvailability:i.highAvailability===!0,publicExposure:i.public===!0,allowedCidrs:i.cidrs?i.cidrs.split(",").map((c)=>c.trim()).filter(Boolean):[],desiredState:{},observedState:{},credentialRef:void 0,status:"adopted",origin:"adopted",managementEnabled:!1,ownerActorId:o.actor.id});h(`Adopted ${s.name} (${s.id}) read-only.`)}))),r.command("data:show <service>","Show safe service, capability, dependency, and operation metadata").option("--env <environment>","Target environment").option("--reveal","Reveal the encrypted credential with exact confirmation").option("--confirm <name>","Exact service name required with --reveal").action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=Pt(o,n),c=o.store.credential(s.id),l={...s,credentialRef:void 0,credential:c?{configured:!0,username:c.username,version:c.version,rotatedAt:c.rotatedAt}:void 0,dependencies:o.store.dependencies(s.id).map((d)=>({resourceId:d.resourceId,requiresRedeploy:d.requiresRedeploy})),operations:o.queue.list({projectId:s.projectId,limit:250}).filter((d)=>mm(d.operation.input,s.id))};if(a(JSON.stringify(l,null,2)),i.reveal){if(i.confirm!==s.name)throw Error(`Credential reveal requires --confirm ${s.name}.`);if(!c)throw Error("No managed credential is configured.");fn("Credential value follows; it will not be logged or persisted by ts-cloud:"),a(`Username: ${c.username}`),a(`Password: ${await o.secrets.resolve(c.secretRef)}`)}},n))),r.command("data:connect <service>","Print secret-free connection and tunnel guidance").option("--env <environment>","Target environment").action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=Pt(o,n);x(["Type","Command","Credential reference"],gi(s,Ka(s)).map((c)=>[c.type,c.command,c.secretRef??"—"]))},n))),r.command("data:action <service> <action>","Preview or enqueue a durable lifecycle action").option("--env <environment>","Target environment").option("--execute","Enqueue after validation; default is preview only").option("--plan <plan>","New provider plan").option("--storage <gb>","New storage allocation").option("--engine-version <version>","Target engine version").option("--min-capacity <acu>","Aurora minimum capacity").option("--max-capacity <acu>","Aurora maximum capacity").option("--public","Enable public exposure").option("--cidrs <ranges>","Comma-separated narrow public CIDRs").option("--retention <choice>","final_backup or retain for deletion").option("--confirm <name>","Exact service name for destructive actions").option("--backup-id <id>","Required backup for a major version change").option("--compatibility-reviewed","Confirm major-version compatibility review").action((n,i,o)=>we(()=>Pe(o.env,async(s)=>{if(!qa.includes(i))throw Error(`Action must be ${qa.join(", ")}.`);let c=Pt(s,n),l=i,d=pm(o),u=s.lifecycle.plan(c,l,d);if(a(JSON.stringify({...u,productionExecutionCreated:!1},null,2)),!o.execute)return;let m=s.lifecycle.enqueue(c,l,d,s.actor.id);h(`Queued ${l} as ${m}.`)},n))),r.command("data:management <service>","Enable reviewed management of an adopted service").option("--env <environment>","Target environment").option("--confirm <name>","Exact service name").action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=Pt(o,n);if(s.origin!=="adopted"||i.confirm!==s.name)throw Error(`Enabling adopted management requires --confirm ${s.name}.`);let c=o.store.update(s.id,s.version,{managementEnabled:!0});h(`${c.name} management is enabled.`)},n))),r.command("data:dependency <service> <resource>","Register or remove an application credential dependency").option("--env <environment>","Target environment").option("--dynamic","Dependency consumes a dynamic reference and does not need redeploy").option("--remove","Remove the dependency").action((n,i,o)=>we(()=>Pe(o.env,async(s)=>{let c=Pt(s,n),l=s.controlPlane.store.listResources(s.controlPlane.project.id,s.environmentRecord.id).find((d)=>d.id===i||d.slug===i);if(!l)throw Error(`Resource ${i} was not found in ${s.env}.`);if(o.remove){a(s.store.removeDependency(c.id,l.id)?"Dependency removed.":"Dependency was not registered.");return}s.store.addDependency({serviceId:c.id,resourceId:l.id,secretRef:c.credentialRef??`secret://data-services/${c.projectId}/${c.name}`,requiresRedeploy:!o.dynamic}),h(`Registered dependency ${l.slug} -> ${c.name}.`)},n))),r.command("db:list","Alias for data:list").option("--env <environment>","Target environment").action((n)=>we(()=>Pe(n.env,async(i)=>x(["ID","Name","Engine","Provider","Status","Plan","Exposure","Management"],Ga(i.store.list(i.controlPlane.project.id,i.environmentRecord.id)))))),r.command("db:connect <service>","Alias for data:connect").option("--env <environment>","Target environment").action((n,i)=>we(()=>Pe(i.env,async(o)=>{let s=Pt(o,n);x(["Type","Command","Credential reference"],gi(s,Ka(s)).map((c)=>[c.type,c.command,c.secretRef??"—"]))},n)));for(let n of["backup","snapshot"])r.command(`db:${n} <service>`,`Alias for data:action ${n==="backup"?"backup":"backup"}`).option("--env <environment>","Target environment").option("--execute","Enqueue the backup; default previews").action((i,o)=>we(()=>Pe(o.env,async(s)=>{let c=Pt(s,i),l=s.lifecycle.plan(c,"backup");if(a(JSON.stringify({...l,productionExecutionCreated:!1},null,2)),o.execute)h(`Queued backup as ${s.lifecycle.enqueue(c,"backup",{},s.actor.id)}.`)},i)))}function oc(r,e=15){if(!r)return e*60000;let t=/^(\d+)\s*([smhd])?$/.exec(r.trim());if(!t)return e*60000;let n=Number(t[1]),i=t[2]??"m";return n*({s:1000,m:60000,h:3600000,d:86400000}[i]??60000)}function fm(r,e){for(let t of r){let n=t.timestamp?new Date(t.timestamp).toISOString():"",i=e?`[${e}] `:"";a(`${n} ${i}${(t.message??"").trimEnd()}`)}}function vi(r){r.command("logs","Stream serverless application logs (http + queue + cli)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli | all",{default:"all"}).option("--tail","Continuously stream new log events").option("--filter <pattern>","CloudWatch filter pattern").option("--since <duration>","Look back (e.g. 1h, 30m, 15)",{default:"15m"}).action(async(e)=>{try{let t=await P(),n=e?.env||"production",{region:i,functions:o}=bt(t,n),s=new rt(i),c=(e?.function??"all").toLowerCase();if(c!=="all"&&!["http","queue","cli"].includes(c)){p("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let d=(c==="all"?["http","queue","cli"]:[c]).map((f)=>({mode:f,group:`/aws/lambda/${o[f]}`}));w(`Logs — ${t.project.slug} (${n})`);let u=Date.now()-oc(e?.since),m=async()=>{let f=u;for(let{mode:y,group:S}of d)try{let{events:g=[]}=await s.filterLogEvents({logGroupName:S,startTime:u,filterPattern:e?.filter,limit:200});fm(g,d.length>1?y:void 0);for(let b of g)if((b.timestamp??0)>f)f=b.timestamp??f}catch(g){if(!/ResourceNotFound/i.test(String(g?.message)))throw g}u=f+1};if(await m(),e?.tail){a(`
|
|
335
335
|
(streaming — Ctrl+C to stop)`);for(;;)await new Promise((f)=>setTimeout(f,3000)),await m()}}catch(t){p(`Failed to read logs: ${t.message}`),process.exitCode=1}}),r.command("metrics","Show serverless function metrics (invocations, errors, duration, throttles)").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--since <duration>","Window (e.g. 1h, 24h)",{default:"1h"}).action(async(e)=>{try{let t=await P(),n=e?.env||"production",{region:i,functions:o}=bt(t,n),s=new qt(i),c=oc(e?.since,60),l=new Date,d=new Date(l.getTime()-c),u=Math.max(60,Math.round(c/1000));w(`Metrics — ${t.project.slug} (${n}), last ${e?.since??"1h"}`);let m=[];for(let f of["http","queue","cli"]){let y=[{Name:"FunctionName",Value:o[f]}],S=async(T,E)=>{let D=await s.getMetricStatistics({Namespace:"AWS/Lambda",MetricName:T,Dimensions:y,StartTime:d,EndTime:l,Period:u,Statistics:[E]});if(!D.length)return 0;if(E==="Sum")return D.reduce((N,_)=>N+(_.Sum??0),0);if(E==="Maximum")return Math.max(...D.map((N)=>N.Maximum??0));return D.reduce((N,_)=>N+(_.Average??0),0)/D.length},[g,b,I,v,C]=await Promise.all([S("Invocations","Sum"),S("Errors","Sum"),S("Throttles","Sum"),S("Duration","Average"),S("Duration","Maximum")]),k=g?`${(b/g*100).toFixed(2)}%`:"—";m.push([f,String(g),String(b),k,String(I),`${v.toFixed(0)}ms`,`${C.toFixed(0)}ms`])}x(["Function","Invocations","Errors","Error rate","Throttles","Avg","Max"],m)}catch(t){p(`Failed to read metrics: ${t.message}`),process.exitCode=1}}),r.command("metrics:dashboard","Print the CloudWatch console URL for this app").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).action(async(e)=>{try{let t=await P(),n=e?.env||"production",{region:i,functions:o}=bt(t,n),s=encodeURIComponent(JSON.stringify({metrics:["http","queue","cli"].flatMap((c)=>[["AWS/Lambda","Invocations","FunctionName",o[c]],["AWS/Lambda","Errors","FunctionName",o[c]]]),view:"timeSeries",region:i}));w("CloudWatch dashboard"),a(`https://console.aws.amazon.com/cloudwatch/home?region=${i}#metricsV2:graph=${s}`)}catch(t){p(t.message),process.exitCode=1}}),r.command("alarms","List CloudWatch alarms").option("--prefix <prefix>","Filter by alarm name prefix").option("--region <region>","AWS region").action(async(e)=>{w("CloudWatch Alarms");let t=e?.region;if(!t)try{t=(await P()).project.region}catch{}try{let i=await new qt(t||process.env.AWS_REGION||"us-east-1").describeAlarms({AlarmNamePrefix:e?.prefix,MaxRecords:100});if(!i.length){a("No alarms found");return}x(["Name","State","Metric","Condition"],i.map((o)=>[o.AlarmName??"-",o.StateValue??"-",o.MetricName??"-",`${o.ComparisonOperator??""} ${o.Threshold??""}`.trim()]))}catch(n){p(`Failed to list alarms: ${n.message}`),process.exitCode=1}}),r.command("alarms:create","Create a CloudWatch alarm on a Lambda metric").option("--name <name>","Alarm name").option("--env <environment>","Environment",{default:"production"}).option("--function <which>","Function: http | queue | cli",{default:"http"}).option("--metric <metric>","Lambda metric (Errors, Throttles, Duration, Invocations)",{default:"Errors"}).option("--threshold <value>","Threshold value").option("--comparison <op>","GreaterThanThreshold | GreaterThanOrEqualToThreshold | LessThanThreshold",{default:"GreaterThanThreshold"}).option("--statistic <stat>","Sum | Average | Maximum | Minimum",{default:"Sum"}).action(async(e)=>{if(w("Creating CloudWatch Alarm"),!e?.name||e?.threshold==null){p("Missing required options: --name and --threshold"),process.exitCode=1;return}try{let t=await P(),n=e.env||"production",{region:i,functions:o}=bt(t,n),s=e.function??"http";await new qt(i).putMetricAlarm({AlarmName:e.name,Namespace:"AWS/Lambda",MetricName:e.metric??"Errors",ComparisonOperator:e.comparison??"GreaterThanThreshold",Threshold:Number(e.threshold),EvaluationPeriods:1,Period:300,Statistic:e.statistic??"Sum",Dimensions:[{Name:"FunctionName",Value:o[s]}],AlarmDescription:`ts-cloud: ${o[s]} ${e.metric} ${e.comparison} ${e.threshold}`}),h(`Alarm '${e.name}' created on ${o[s]} ${e.metric}`)}catch(t){p(`Failed to create alarm: ${t.message}`),process.exitCode=1}})}async function Hn(r){let e=await P(),t=r||"production";return{region:e.environments?.[t]?.region||e.project.region||"us-east-1",prefix:`${e.project.slug}/${t}`}}function bi(r){r.command("secrets:list","List secrets for an environment").option("--env <environment>","Environment (production, staging, development)").action(async(e)=>{w("Secrets");try{let{region:t,prefix:n}=await Hn(e?.env),i=new pt(t),{SecretList:o=[]}=await i.listSecrets({Filters:[{Key:"name",Values:[`${n}/`]}],MaxResults:100});if(!o.length){a(`No secrets found under ${n}/`);return}x(["Key","Last Changed"],o.map((s)=>[(s.Name??"").replace(`${n}/`,""),s.LastChangedDate??s.LastAccessedDate??"-"]))}catch(t){p(`Failed to list secrets: ${t.message}`),process.exitCode=1}}),r.command("secrets:set <key> <value>","Set (create or update) a secret").option("--env <environment>","Environment (production, staging, development)").action(async(e,t,n)=>{w(`Setting secret ${e}`);let{region:i,prefix:o}=await Hn(n?.env),s=new pt(i),c=`${o}/${e}`,l=new A("Storing secret...");l.start();try{try{await s.putSecretValue({SecretId:c,SecretString:t})}catch{await s.createSecret({Name:c,SecretString:t})}l.succeed(`Secret ${e} stored (${c})`),R("Redeploy for the new value to take effect in running functions.")}catch(d){l.fail(`Failed to store secret: ${d.message}`),process.exitCode=1}}),r.command("secrets:get <key>","Reveal a secret value").option("--env <environment>","Environment (production, staging, development)").action(async(e,t)=>{try{let{region:n,prefix:i}=await Hn(t?.env),s=await new pt(n).getSecretValue({SecretId:`${i}/${e}`});a(s.SecretString??"")}catch(n){p(`Failed to read secret: ${n.message}`),process.exitCode=1}}),r.command("secrets:delete <key>","Delete a secret").option("--env <environment>","Environment (production, staging, development)").option("--force","Delete immediately without a recovery window").action(async(e,t)=>{try{let{region:n,prefix:i}=await Hn(t?.env);await new pt(n).deleteSecret({SecretId:`${i}/${e}`,ForceDeleteWithoutRecovery:t?.force}),h(`Secret ${e} deleted`)}catch(n){p(`Failed to delete secret: ${n.message}`),process.exitCode=1}})}function wi(r){for(let[e,t]of[["firewall:rules","List provider firewall policy"],["firewall:block <ip>","Block an address"],["firewall:unblock <ip>","Unblock an address"],["firewall:countries","Manage geographic policy"]])r.command(e,t).action(async(...n)=>be(e.split(" ")[0],{target:String(n[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 Si(r){r.command("ssl:list","List all SSL certificates").option("--region <region>","AWS region (default: us-east-1)").action(async(e)=>{w("SSL Certificates");let t=e?.region||"us-east-1",n=new A("Fetching certificates from ACM...");n.start();try{let i=new vt(t),o=await i.listCertificates();if(o.CertificateSummaryList.length===0){n.succeed("No certificates found"),a(`
|
|
336
336
|
No SSL certificates found in region ${t}`),a("Use 'cloud domain:ssl <domain>' to request a new certificate");return}let s=await Promise.all(o.CertificateSummaryList.map(async(d)=>await i.describeCertificate({CertificateArn:d.CertificateArn})));n.succeed(`Found ${s.length} certificate(s)`);let c=(d)=>{if(!d)return"N/A";let u=typeof d==="string"?Number.parseFloat(d):d,m=u<1000000000000?u*1000:u;return new Date(m).toISOString().split("T")[0]},l=s.map((d)=>{let u=c(d.NotAfter),m=d.Type==="AMAZON_ISSUED"?"Amazon Issued":d.Type||"Unknown",f=d.Status==="ISSUED"?"Available":d.Status||"Unknown";return[d.DomainName,d.Status||"Unknown",u,m,f]});x(["Domain","Status","Expiry","Type","State"],l),a(`
|
|
337
337
|
ACM certificates are automatically renewed by AWS`),a(`Region: ${t}`)}catch(i){n.fail("Failed to fetch certificates"),p(i.message)}}),r.command("ssl:renew <domain>","Renew SSL certificate").option("--region <region>","AWS region (default: us-east-1)").action(async(e,t)=>{w(`Checking SSL Certificate for ${e}`);let n=t?.region||"us-east-1";a(`Domain: ${e}`),a(`Region: ${n}`);let i=new A("Checking certificate status...");i.start();try{let s=await new vt(n).findCertificateByDomain(e);if(!s){i.fail("Certificate not found"),p(`No certificate found for domain: ${e}`),a("Use 'cloud domain:ssl <domain>' to request a new certificate");return}i.succeed("Certificate found"),a(`
|
|
@@ -362,17 +362,17 @@ ${i.resources.length} resource${i.resources.length===1?"":"s"} discovered.`);for
|
|
|
362
362
|
Known monthly savings: ${n.savingsAvailable?se(i):"unavailable (enable CUR resource IDs)"}`)}for(let i of n.inventory.warnings)R(i)}catch(n){t.stop(),p(`Unused-resource analysis failed: ${n instanceof Error?n.message:String(n)}`)}}),r.command("optimize","Suggest cost optimizations").option("--env <environment>","Environment (production, staging, development)").option("--type <type>","Limit analysis to one resource type").option("--region <region>","AWS region for regional resources").option("--include-commitments","Include estimated RI and Savings Plan candidates").action(async(e)=>{w(`AWS Cost Optimizations${e?.profile?` (profile: ${e.profile})`:""}`);let t=new A("Combining inventory, CloudWatch signals, and Cost Explorer line items...");t.start();try{let n=await new Yn(e?.profile,e?.region).optimize({type:e?.type,includeCommitments:e?.includeCommitments});if(t.stop(),n.recommendations.length===0)a("No resources met the conservative optimization thresholds.");else{x(["Category","Resource","Current","Projected","Savings","Source signal","Recommendation"],n.recommendations.map((o)=>[o.category,`${o.resource.service}:${o.resource.type}/${o.resource.name}`,o.currentCost==null?"—":se(o.currentCost),o.projectedCost==null?"—":se(o.projectedCost),o.monthlySavings==null?"—":se(o.monthlySavings),o.sourceSignal,o.recommendation]));let i=n.recommendations.reduce((o,s)=>o+(s.monthlySavings??0),0);a(`
|
|
363
363
|
Estimated monthly savings: ${n.savingsAvailable?se(i):"unavailable (enable CUR resource IDs)"}`)}for(let i of n.warnings)R(i);a("Estimates are directional; validate workload peaks and current AWS pricing before applying changes.")}catch(n){t.stop(),p(`Optimization analysis failed: ${n instanceof Error?n.message:String(n)}`)}})}async function Ht(r){let e=await P(),t=r??Object.keys(e.environments??{})[0]??"production";if(!Object.hasOwn(e.environments??{},t))throw Error(`Environment ${t} was not found`);let n=q(process.cwd(),e);return{config:e,env:t,controlPlane:n,controls:new vn(n.store)}}function Jt(r){p(r instanceof Error?r.message:String(r)),process.exitCode=1}function mc(r){let e=r?.infrastructure?.compute??{};return{ddos:e.ddos===!1?void 0:typeof e.ddos==="object"?e.ddos:{ports:[80,443]},waf:e.waf===!1?void 0:typeof e.waf==="object"?e.waf:{mode:"detection"}}}function Oi(r){r.command("protect:status","Show the current edge-protection posture").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(e)=>{try{let t=await Ht(e.env),n=t.controls.current(),i=Ti(n);if(e.json){a(JSON.stringify({posture:i,controls:t.controls.raw()},null,2));return}if(w("Edge protection"),i.mitigationPaused||i.attackMode)R(i.summary);else h(i.summary);let o=mc(t.config);if(a(`L3/L4 mitigation: ${o.ddos?"enabled":"disabled in config"}`),a(`WAF: ${o.waf?`enabled (${o.waf.mode??"detection"})`:"disabled in config"}`),a(`Allowlist: ${n.ipRules.allow.length} entry(s)`),a(`Blocklist: ${n.ipRules.block.length} entry(s)`),n.ipRules.allow.length>0||n.ipRules.block.length>0)x(["List","CIDR"],[...n.ipRules.allow.map((s)=>["allow",s]),...n.ipRules.block.map((s)=>["block",s])])}catch(t){Jt(t)}}),r.command("protect:attack-mode <state>","Challenge every visitor for a bounded window (on|off)").option("--hours <hours>",`How long before it lifts itself (default ${Di}, max ${Qn})`).option("--reason <reason>","Why, for the record").option("--env <environment>","Target environment").action(async(e,t)=>{try{let n=await Ht(t.env);if(e==="off"){h(n.controls.disableAttackMode()?"Attack mode is off.":"Attack mode was not on.");return}if(e!=="on")throw Error("State must be 'on' or 'off'.");let i=n.controls.enableAttackMode({hours:t.hours==null?void 0:Number(t.hours),reason:t.reason??"Enabled from the CLI."});R("Attack mode is ON. Every visitor is challenged, including legitimate ones."),a(`It lifts itself at ${i.expiresAt} — you do not need to remember.`)}catch(n){Jt(n)}}),r.command("protect:pause <state>","Pause or resume automatic mitigation (on|off)").option("--hours <hours>",`How long (max ${Qn})`).option("--reason <reason>","Why — required when pausing").option("--env <environment>","Target environment").action(async(e,t)=>{try{let n=await Ht(t.env);if(e==="off"){h(n.controls.resumeMitigations()?"Automatic mitigation resumed.":"Mitigation was not paused.");return}if(e!=="on")throw Error("State must be 'on' or 'off'.");if(!t.reason)throw Error("--reason is required: you are liable for the traffic this admits.");let i=n.controls.pauseMitigations({hours:t.hours==null?void 0:Number(t.hours),reason:t.reason});R("Automatic mitigation is PAUSED. All traffic is served, and billed, including attacks."),a(`It resumes automatically at ${i.expiresAt}.`)}catch(n){Jt(n)}}),r.command("protect:allow <cidr>","Never rate-limit or block this IP or CIDR").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(e,t)=>{try{let n=await Ht(t.env),i=t.remove?n.controls.removeIpRule("allow",e):n.controls.addIpRule("allow",e);h(`Allowlist now has ${i.allow.length} entry(s).`),a("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(n){Jt(n)}}),r.command("protect:block <cidr>","Drop traffic from this IP or CIDR at the kernel").option("--remove","Remove the entry instead").option("--env <environment>","Target environment").action(async(e,t)=>{try{let n=await Ht(t.env),i=t.remove?n.controls.removeIpRule("block",e):n.controls.addIpRule("block",e);h(`Blocklist now has ${i.block.length} entry(s).`),a("It reaches the boxes on the next deploy. `cloud protect:apply` shows exactly what will land.")}catch(n){Jt(n)}}),r.command("protect:apply","Print the ruleset the current controls produce").option("--env <environment>","Target environment").option("--waf","Print the WAF configuration instead of the firewall script").action(async(e)=>{try{let t=await Ht(e.env),n=mc(t.config);if(e.waf){if(!n.waf)throw Error("The WAF is disabled in this configuration.");let i=Ei(n.waf);for(let o of i.warnings)R(o);a(i.config);return}if(!n.ddos)throw Error("L3/L4 mitigation is disabled in this configuration.");a(xi(Ni(n.ddos,t.controls.current())))}catch(t){Jt(t)}})}async function Se(r){let e=await P(),t=r??Object.keys(e.environments??{})[0]??"production";if(!Object.hasOwn(e.environments??{},t))throw Error(`Environment ${t} was not found`);let n=q(process.cwd(),e),i=n.environments.get(t),o=new yc(n.store),s=new bn(n.store,{encryptionKey:le(process.cwd())});return{config:e,env:t,controlPlane:n,environmentRecord:i,store:o,alerts:s,service:new bc(o),gate:new wc(n.store),organizationId:n.organization.id,projectId:n.project.id}}function Xn(r){if(r==null)return;let e=String(r).trim().replace(/[$,\s]/g,"");if(!e)return;if(/^\d+c$/i.test(e))return Number.parseInt(e,10);let t=Number(e);if(!Number.isFinite(t)||t<0)throw Error(`Invalid amount: ${r}`);return Math.round(t*100)}function pc(r){if(!r)return;let e=r.split(",").map((t)=>t.trim()).filter(Boolean);for(let t of e)if(!Li.includes(t))throw Error(`Unknown enforcement action '${t}'. Valid: ${Li.join(", ")}`);return e}function fc(r){if(r==null)return"monthly";if(r==="daily"||r==="weekly"||r==="monthly")return r;throw Error(`Unknown period '${r}'. Valid: daily, weekly, monthly`)}function Cm(r){return gc[r]?.label??r}function Am(r,e=24){let t=Math.max(0,Math.min(e,Math.round(r/100*e)));return`${"█".repeat(t)}${"░".repeat(e-t)}`}function $e(r){p(r instanceof Error?r.message:String(r)),process.exitCode=1}function Mi(r){r.command("usage","Show current usage, cost, and remaining budget headroom").option("--env <environment>","Target environment").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--json","Print structured JSON").action(async(e)=>{try{let t=await Se(e.env),n=t.service.usageReport({organizationId:t.organizationId,projectId:t.projectId,period:fc(e.period),timezone:e.timezone});if(e.json){a(JSON.stringify(n,null,2));return}if(w(`Usage — ${n.window.label}`),a(`Total: ${Ge(n.totalCents,n.currency)}`),n.byMeter.length===0)a("No metered usage in this window yet.");else x(["Meter","Provider","Quantity","Cost"],n.byMeter.map((i)=>[Cm(i.meter),i.provider||"-",i.quantity.toLocaleString(void 0,{maximumFractionDigits:3}),Ge(i.costCents,n.currency)]));if(n.budgets.length===0){R("No budgets are configured. Nothing is capping this project."),a("Create one with: cloud budget:create --name 'Monthly' --hard 200");return}if(w("Budgets"),x(["Name","Period","Spent","Limit","Used","Projected","Time to cap","State"],n.budgets.map((i)=>[i.name+(i.dryRun?" (dry run)":""),i.period,Ge(i.spentCents,n.currency),i.limitCents==null?"-":Ge(i.limitCents,n.currency),`${Am(i.usedPercent)} ${i.usedPercent.toFixed(0)}%`,`${Ge(i.projectedCents,n.currency)} (${(i.projectionConfidence*100).toFixed(0)}% conf)`,i.timeToCap||"-",i.level])),n.enforcement.strongestAction)R(`Enforcement in force: ${n.enforcement.strongestAction}`)}catch(t){$e(t)}}),r.command("budget:list","List budgets governing this project").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(e)=>{try{let t=await Se(e.env),n=t.store.listBudgets({organizationId:t.organizationId,projectId:t.projectId});if(e.json){a(JSON.stringify(n,null,2));return}if(n.length===0){R("No budgets configured.");return}x(["ID","Name","Scope","Period","Soft","Hard","Mode","Enabled"],n.map((i)=>[i.id.slice(0,8),i.name,i.environmentId?"environment":i.projectId?"project":"organization",i.period,i.softLimitCents==null?"-":Ge(i.softLimitCents,i.currency),i.hardLimitCents==null?"-":Ge(i.hardLimitCents,i.currency),i.dryRun?"dry run":"enforcing",i.enabled?"yes":"no"]))}catch(t){$e(t)}}),r.command("budget:create","Create a spend budget").option("--name <name>","Budget name").option("--soft <amount>","Soft limit in dollars (warn only)").option("--hard <amount>","Hard limit in dollars (enforces)").option("--period <period>","daily, weekly, or monthly (default monthly)").option("--timezone <tz>","IANA timezone for period boundaries (default UTC)").option("--scope <scope>","organization, project, or environment (default project)").option("--actions <list>","Comma-separated actions to run at 100% (default notify,block_builds,block_deployments)").option("--grace <seconds>","Seconds a breach must persist before enforcing").option("--dry-run","Evaluate and report but never enforce").option("--env <environment>","Target environment").action(async(e)=>{try{if(!e.name)throw Error("--name is required.");let t=await Se(e.env),n=e.scope??"project";if(!["organization","project","environment"].includes(n))throw Error(`Unknown scope '${n}'. Valid: organization, project, environment`);let i=pc(e.actions),o=t.store.createBudget({organizationId:t.organizationId,projectId:n==="organization"?void 0:t.projectId,environmentId:n==="environment"?t.environmentRecord?.id:void 0,name:e.name,period:fc(e.period),timezone:e.timezone,softLimitCents:Xn(e.soft),hardLimitCents:Xn(e.hard),graceSeconds:e.grace==null?void 0:Number(e.grace),thresholds:i?[{atPercent:80,actions:["notify"]},{atPercent:100,actions:i}]:[...hc],dryRun:e.dryRun===!0});h(`Created budget '${o.name}' (${o.id}).`),a(`Mode: ${o.dryRun?"dry run — reports only, enforces nothing":"enforcing"}`),a(`Ladder: ${o.thresholds.map((s)=>`${s.atPercent}% → ${s.actions.join("+")}`).join(", ")}`)}catch(t){$e(t)}}),r.command("budget:update <budgetId>","Update a budget").option("--soft <amount>","Soft limit in dollars").option("--hard <amount>","Hard limit in dollars").option("--enable","Enable the budget").option("--disable","Disable the budget").option("--enforce","Leave dry run and start enforcing").option("--dry-run","Return to dry run").option("--env <environment>","Target environment").action(async(e,t)=>{try{let n=await Se(t.env),i=n.store.getBudget(e);if(!i)throw Error(`Budget '${e}' was not found.`);let o=n.store.updateBudget(e,{softLimitCents:Xn(t.soft)??i.softLimitCents,hardLimitCents:Xn(t.hard)??i.hardLimitCents,enabled:t.disable?!1:t.enable?!0:i.enabled,dryRun:t.enforce?!1:t.dryRun?!0:i.dryRun});h(`Updated budget '${o.name}'.`),a(`Mode: ${o.dryRun?"dry run":"enforcing"} · Enabled: ${o.enabled?"yes":"no"}`)}catch(n){$e(n)}}),r.command("budget:delete <budgetId>","Delete a budget and lift anything it was enforcing").option("--env <environment>","Target environment").action(async(e,t)=>{try{let n=await Se(t.env),i=n.store.getBudget(e);if(!i)throw Error(`Budget '${e}' was not found.`);let o=n.gate.closeBudget(e);if(n.store.deleteBudget(e),h(`Deleted budget '${i.name}'.`),o>0)a(`Lifted ${o} enforcement action(s) it had in force.`)}catch(n){$e(n)}}),r.command("spend:check","Run one spend cycle now and report what it would do").option("--env <environment>","Target environment").option("--apply","Actually apply enforcement (default is a preview)").option("--json","Print structured JSON").action(async(e)=>{try{let t=await Se(e.env),n=new ji({controlPlane:t.controlPlane.store,store:t.store,alerts:e.apply?t.alerts:void 0});if(!e.apply){let s=t.store.budgetsForScope(t.organizationId,t.projectId).map((c)=>t.service.status(c));if(e.json){a(JSON.stringify(s,null,2));return}if(w("Spend check (preview — nothing was applied)"),s.length===0){R("No budgets govern this project.");return}x(["Budget","Used","Projected","Level","Would apply"],s.map((c)=>[c.budget.name,`${c.decision.usedPercent.toFixed(1)}%`,`${c.decision.projectedPercent.toFixed(1)}%`,c.decision.level,c.decision.actions.join(", ")||"-"])),a(`
|
|
364
364
|
Run with --apply to enforce.`);return}let i=await n.run({organizationId:t.organizationId,projectId:t.projectId,environmentId:t.environmentRecord?.id,environmentKind:t.environmentRecord?.kind});if(e.json){a(JSON.stringify(i,null,2));return}if(w("Spend cycle"),a(`Evaluated ${i.decisions.length} budget(s) in ${i.durationMs}ms.`),i.applied.length>0)R(`Applied: ${i.applied.join(", ")}`);if(i.released.length>0)h(`Released: ${i.released.join(", ")}`);if(i.withheld.length>0)R(`Withheld pending approval: ${i.withheld.join(", ")}`);if(i.anomalies.length>0)R(`${i.anomalies.length} anomaly(ies) detected.`);a(`Notifications queued: ${i.notificationsSent}`);for(let o of i.warnings)R(o);if(i.applied.length===0&&i.released.length===0)h("Nothing to change.")}catch(t){$e(t)}}),r.command("spend:work","Run the spend loop continuously: meter, evaluate, enforce, notify").option("--env <environment>","Target environment").option("--interval <seconds>",`Seconds between cycles (default ${Fi})`).option("--once","Run a single cycle and exit").action(async(e)=>{try{let t=await Se(e.env),n=new ji({controlPlane:t.controlPlane.store,store:t.store,alerts:t.alerts}),i={organizationId:t.organizationId,projectId:t.projectId,environmentId:t.environmentRecord?.id,environmentKind:t.environmentRecord?.kind};if(e.once){let d=await n.run(i);h(`Cycle finished in ${d.durationMs}ms.`);for(let u of d.warnings)R(u);return}let o=Math.max(10,Number(e.interval??Fi)),s=new Sc(t.controlPlane.store,{owner:`cli:${process.pid}`});w(`Spend worker (every ${o}s)`),a("Press Ctrl+C to stop.");let c=$c(n,{intervalSeconds:o,lease:s,immediate:!0,onResult:(d)=>{let u=d.flatMap((y)=>y.applied),m=d.flatMap((y)=>y.released),f=d.flatMap((y)=>y.warnings);if(u.length>0)R(`Applied: ${u.join(", ")}`);if(m.length>0)h(`Released: ${m.join(", ")}`);for(let y of f)R(y)},onSkip:(d)=>a(`Skipped: ${d} holds the loop lease.`),onError:(d)=>p(d instanceof Error?d.message:String(d))}),l=()=>{c(),a(`
|
|
365
|
-
Spend worker stopped; the loop lease was released.`),process.exit(0)};process.on("SIGINT",l),process.on("SIGTERM",l),await new Promise(()=>{})}catch(t){$e(t)}}),r.command("spend:status","Show enforcement currently in force").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(e)=>{try{let t=await Se(e.env),n=t.gate.listUnder({organizationId:t.organizationId});if(e.json){a(JSON.stringify(n,null,2));return}if(n.length===0){h("No spend enforcement is in force.");return}x(["Action","Budget","Scope","Mode","Since","Reason"],n.map((i)=>[i.action,i.budgetId.slice(0,8),i.environmentId?"environment":i.projectId?"project":"organization",i.simulated?"dry run":"enforcing",i.appliedAt,i.reason]))}catch(t){$e(t)}}),r.command("spend:release <budgetId> <action>","Manually lift one enforcement action").option("--env <environment>","Target environment").action(async(e,t,n)=>{try{let i=await Se(n.env),o=pc(t)?.[0];if(!o)throw Error("An action is required.");if(!i.gate.close(e,o))throw Error(`${o} was not in force for budget ${e}.`);h(`Lifted ${o} for budget ${e}.`),R("The next spend cycle will re-apply this if spend is still over the threshold.")}catch(i){$e(i)}}),r.command("anomaly:config","Tune anomaly detection for a signal").option("--signal <signal>","Signal to configure (default cost)").option("--sensitivity <level>","low, medium, or high").option("--season <points>","Points per season: 24 for daily, 168 for weekly").option("--min-delta <amount>","Absolute change below which nothing is reported, in the signal's units").option("--severity <severity>","info, warning, or critical").option("--disable","Stop detecting this signal").option("--enable","Resume detecting this signal").option("--list","List configured signals instead of changing one").option("--signals","List every detectable signal and its data rules").option("--env <environment>","Target environment").action(async(e)=>{try{if(e.signals){x(["Signal","Source","Empty hour","Min samples","Unit"],vc.map((s)=>[s.key,s.source,s.gapPolicy==="zero"?"counts as zero":"ignored",String(s.minSamples),s.unit]));return}let t=await Se(e.env),n=t.service.anomalyConfigs;if(e.list){let s=n.list({organizationId:t.organizationId,projectId:t.projectId});if(s.length===0){a("No signals are configured; every one uses its shipped defaults.");return}x(["Signal","Enabled","Sensitivity","Season","Min delta","Severity"],s.map((c)=>[c.signal,c.enabled?"yes":"no",c.sensitivity,String(c.seasonLength),String(c.minAbsoluteDelta),c.severity]));return}let i=e.sensitivity;if(i&&!["low","medium","high"].includes(i))throw Error("--sensitivity must be low, medium, or high.");let o=n.upsert({organizationId:t.organizationId,projectId:t.projectId,signal:e.signal??"cost",sensitivity:i,seasonLength:e.season==null?void 0:Number(e.season),minAbsoluteDelta:e.minDelta==null?void 0:Number(e.minDelta),severity:e.severity,enabled:e.disable?!1:e.enable?!0:void 0});h(`Anomaly detection for '${o.signal}' updated.`),a(`${o.enabled?"Enabled":"Disabled"} · ${o.sensitivity} sensitivity · season ${o.seasonLength} · floor ${o.minAbsoluteDelta} · ${o.severity}`)}catch(t){$e(t)}}),r.command("anomaly:silence","Skip detection for a signal, route, or status code").option("--signal <signal>","Signal to silence").option("--route <pattern>","Route glob, e.g. /webhooks/**").option("--status <code>","HTTP status code").option("--reason <reason>","Why — required").option("--until <timestamp>","ISO timestamp the silence expires").option("--list","List active silences").option("--remove <id>","Remove a silence").option("--env <environment>","Target environment").action(async(e)=>{try{let t=await Se(e.env),n=t.service.anomalyConfigs;if(e.remove){h(n.removeSilence(e.remove)?"Silence removed.":"That silence was not found.");return}if(e.list){let o=n.listSilences({organizationId:t.organizationId,projectId:t.projectId});if(o.length===0){h("No active silences.");return}x(["ID","Signal","Route","Status","Expires","Reason"],o.map((s)=>[s.id.slice(0,8),s.signal??"-",s.routePattern??"-",s.statusCode==null?"-":String(s.statusCode),s.expiresAt??"never",s.reason]));return}if(!e.reason)throw Error("--reason is required: someone will ask why this stopped alerting.");let i=n.silence({organizationId:t.organizationId,projectId:t.projectId,signal:e.signal,routePattern:e.route,statusCode:e.status==null?void 0:Number(e.status),reason:e.reason,expiresAt:e.until});if(h(`Silenced (${i.id.slice(0,8)}).`),!i.expiresAt)R("This silence never expires. Pass --until to bound it, or it will outlive the reason for it.")}catch(t){$e(t)}}),r.command("spend:anomalies","List detected spend anomalies").option("--env <environment>","Target environment").option("--unacknowledged","Only anomalies nobody has acknowledged").option("--json","Print structured JSON").action(async(e)=>{try{let t=await Se(e.env),n=t.store.listAnomalies({organizationId:t.organizationId,projectId:t.projectId,unacknowledgedOnly:e.unacknowledged===!0});if(e.json){a(JSON.stringify(n,null,2));return}if(n.length===0){h("No anomalies detected.");return}x(["Hour","Signal","Direction","Observed","Expected","Score","Severity"],n.map((i)=>[i.bucketStart,i.signal,i.direction,Ge(i.observed),Ge(i.expected),i.score.toFixed(1),i.severity]))}catch(t){$e(t)}})}import{readFileSync as km}from"node:fs";var Rm={github:"GITHUB_TOKEN",gitlab:"GITLAB_TOKEN",bitbucket:"BITBUCKET_TOKEN",gitea:"GITEA_TOKEN",generic_https:"GIT_TOKEN"},Im={github:"https://github.com",gitlab:"https://gitlab.com",bitbucket:"https://bitbucket.org",gitea:"https://gitea.com"};function _i(r,e={}){if(e.provider&&!["github","gitlab","bitbucket","gitea","generic_https","generic_ssh"].includes(e.provider))throw Error(`Unsupported Git provider: ${e.provider}`);let t=e.cloneUrl??(/^(?:https?:\/\/|ssh:\/\/|git@)/.test(r)?r:void 0),n="",i=t??r;if(t?.startsWith("git@")){let c=/^git@([^:]+):(.+)$/.exec(t);if(!c)throw Error("SSH repository URL must use git@host:owner/name.git");n=c[1],i=c[2]}else if(t){let c=new URL(t);n=c.hostname,i=c.pathname}if(i=i.replace(/^\/+|\/+$/g,"").replace(/\.git$/,""),!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(i))throw Error("Repository must use owner/name or a supported clone URL");let o=e.provider??(n.includes("github")?"github":n.includes("gitlab")?"gitlab":n.includes("bitbucket")?"bitbucket":n.includes("gitea")?"gitea":t?.startsWith("git@")||t?.startsWith("ssh://")?"generic_ssh":t?"generic_https":"github"),s=e.host??Im[o]??(n?`https://${n}`:"");if(!s)throw Error("A provider host is required");return{provider:o,host:s,fullName:i,cloneUrl:t}}async function Pm(){let r=await P(),e=q(process.cwd(),r);return{config:r,controlPlane:e,sources:new er(e.store,{encryptionKey:le(process.cwd())})}}async function rt(r){let e=await Pm();try{return await r(e)}finally{e.controlPlane.store.close()}}function wn(r,e,t){let n=r.listConnections(e);if(!t)return n.length===1?n[0]:void 0;return n.find((i)=>i.id===t||i.name.toLowerCase()===t.toLowerCase())}function Cc(r,e){if(!r)throw Error(`${e} file is required`);return km(r,"utf8")}async function xm(r,e,t,n){if(["github","gitlab","bitbucket","gitea"].includes(e.provider)){let o=r.listRepositories(e.id).find((l)=>l.fullName.toLowerCase()===t.fullName.toLowerCase());if(o)return o;let c=(await Rc(r,e.id,{search:t.fullName,maxPages:20})).find((l)=>l.fullName.toLowerCase()===t.fullName.toLowerCase());if(!c)throw Error(`Repository ${t.fullName} is not granted to this connection`);return c}let i=n.url??t.cloneUrl;if(!i)throw Error("Generic Git connections require --url <clone-url>");return r.upsertRepository({connectionId:e.id,providerRepositoryId:`manual:${t.fullName}`,fullName:t.fullName,cloneUrl:i,defaultBranch:n.branch??"main",visibility:"unknown",archived:!1,metadata:{source:"cli"}})}async function Ac(r,e,t){let n=_i(e,{provider:t.provider,host:t.host,cloneUrl:t.url}),i=wn(r.sources,r.controlPlane.organization.id,t.connection);if(!i){if(t.connection)throw Error(`Source connection ${t.connection} was not found`);let s=t.tokenEnv??Rm[n.provider],c=s?process.env[s]:void 0;if(["github","gitlab","bitbucket","gitea"].includes(n.provider)&&!c)throw Error(`Set ${s} or pass --token-env <name>; secrets are never accepted as command arguments`);let l=n.provider==="generic_ssh"?{publicKey:Cc(t.publicKey,"Public key"),privateKey:Cc(t.privateKey,"Private key"),hostKey:t.hostKey??""}:void 0;r.controlPlane.store.database.transaction(()=>{if(i=r.sources.createConnection({organizationId:r.controlPlane.organization.id,provider:n.provider,name:t.name??`${n.provider.replace("_"," ")} · ${new URL(n.host).host}`,host:n.host,owner:n.fullName.split("/")[0],authKind:n.provider==="generic_ssh"?"deploy_key":c?"access_token":"none",credential:c?{token:c}:void 0}),l)r.sources.createDeployKey({connectionId:i.id,name:t.deployKey??`${n.fullName} deploy key`,...l,host:t.sshHost??new URL(n.host).hostname,actorId:void 0})})()}if(!i)throw Error("Source connection could not be created");if(i.status==="disconnected")throw Error("The selected source connection is disconnected");let o=await xm(r.sources,i,n,t);if(t.site){let s=r.controlPlane.environments.get("production")??[...r.controlPlane.environments.values()][0],c=r.controlPlane.store.listResources(r.controlPlane.project.id,s?.id).find((d)=>d.kind==="application"&&d.slug===t.site);if(!c)throw Error(`Application ${t.site} was not found`);let l=i.provider==="generic_ssh"?r.sources.listDeployKeys(i.id)[0]:void 0;r.sources.createBinding({projectId:r.controlPlane.project.id,environmentId:s?.id,resourceId:c.id,connectionId:i.id,repositoryId:o.id,repositoryFullName:o.fullName,defaultBranch:t.branch??o.defaultBranch,branchRule:t.tag?void 0:t.branch??o.defaultBranch,tagRule:t.tag,monorepoRoot:t.root??".",includePaths:t.include?.split(",").map((d)=>d.trim()).filter(Boolean),excludePaths:t.exclude?.split(",").map((d)=>d.trim()).filter(Boolean),submodules:t.submodules,cloneDepth:t.fullClone?void 0:20,deployKeyId:l?.id})}return{connection:i,repository:o}}function it(r){p(r instanceof Error?r.message:"Git integration command failed")}function zi(r){r.command("git:connections","List encrypted Git provider connections").action(async()=>{try{await rt(async({sources:e,controlPlane:t})=>{let n=e.listConnections(t.organization.id);x(["ID","Name","Provider","Status","Credential","Last sync"],n.map((i)=>[i.id,i.name,i.provider,i.status,i.credentialConfigured?"encrypted":"none",i.lastSyncedAt??"-"]))})}catch(e){it(e)}}),r.command("git:repositories","List repositories granted to a connection").option("--connection <id>","Connection ID or name").action(async(e)=>{try{await rt(async({sources:t,controlPlane:n})=>{let i=wn(t,n.organization.id,e.connection);if(!i)throw Error("Choose a connection with --connection <id>");let o=t.listRepositories(i.id);x(["ID","Repository","Default branch","Visibility","Archived"],o.map((s)=>[s.id,s.fullName,s.defaultBranch,s.visibility,s.archived?"yes":"no"]))})}catch(t){it(t)}}),r.command("git:add <repo>","Connect and optionally bind a Git repository").option("--branch <branch>","Default branch to deploy",{default:"main"}).option("--connection <id>","Reuse a connection ID or name").option("--provider <provider>","github, gitlab, bitbucket, gitea, generic_https, or generic_ssh").option("--host <url>","Provider API host").option("--name <name>","Connection name").option("--token-env <name>","Environment variable containing the access token").option("--url <clone-url>","Credential-free clone URL").option("--site <slug>","Bind the repository to an application").option("--root <path>","Monorepo root",{default:"."}).option("--include <globs>","Comma-separated watched paths").option("--exclude <globs>","Comma-separated ignored paths").option("--tag <glob>","Deploy matching tags instead of branch pushes").option("--full-clone","Use full history instead of a shallow clone").option("--submodules","Initialize repository submodules").option("--deploy-key <name>","Deploy-key display name").option("--public-key <path>","SSH public-key file").option("--private-key <path>","SSH private-key file").option("--host-key <key>","Pinned SSH host key").option("--ssh-host <host>","SSH clone host").option("--yes","Skip confirmation").action(async(e,t)=>{try{if(!t.yes&&!await O(`Connect ${e}?`,!0))return;let n=await rt((i)=>Ac(i,e,t));if(h(`Connected ${n.repository.fullName}`),a(`Connection: ${n.connection.name} (${n.connection.id})`),t.site)a(`Application binding: ${t.site}`)}catch(n){it(n)}}),r.command("git:import","Import site.repository entries from cloud.config.ts").option("--connection <id>","Reuse a connection ID or name").option("--token-env <name>","Environment variable containing provider token").option("--yes","Skip confirmation").action(async(e)=>{try{await rt(async(t)=>{let n=Object.entries(t.config.sites??{}).filter(([,i])=>i.repository?.url);if(!n.length)throw Error("No site.repository entries were found");if(!e.yes&&!await O(`Import ${n.length} repository configuration${n.length===1?"":"s"}?`,!0))return;for(let[i,o]of n){let s=o.repository,c=s.provider==="custom"?void 0:s.provider;await Ac(t,s.url,{connection:e.connection,tokenEnv:e.tokenEnv,site:i,provider:c,branch:s.branch??"main",tag:s.strategy==="tag"?s.tag??s.tagPattern??"v*":void 0,url:s.url}),h(`Imported ${i} · ${s.url}`)}})}catch(t){it(t)}}),r.command("git:branches <repo>","List branches or tags visible to a connection").option("--connection <id>","Connection ID or name").option("--tags","List tags instead of branches").option("--deploy-key <id>","SSH deploy-key ID").action(async(e,t)=>{try{await rt(async({sources:n,controlPlane:i})=>{let o=wn(n,i.organization.id,t.connection);if(!o)throw Error("Choose a connection with --connection <id>");let s=n.listRepositories(o.id).find((l)=>l.id===e||l.fullName.toLowerCase()===e.toLowerCase());if(!s)throw Error(`Repository ${e} was not found`);let c=await Bi(n,{connectionId:o.id,repository:s.fullName,repositoryId:s.id,type:t.tags?"tags":"branches",deployKeyId:t.deployKey??n.listDeployKeys(o.id)[0]?.id});x([t.tags?"Tag":"Branch","Commit","Protected"],c.refs.map((l)=>[l.name,l.commitSha,l.protected?"yes":"no"]))})}catch(n){it(n)}}),r.command("git:deploy <branch>","Queue an idempotent deployment from a Git branch").option("--binding <id>","Source binding ID").option("--repo <owner/name>","Bound repository").option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{try{await rt(async({sources:n,controlPlane:i})=>{let o=i.environments.get(t.env??"production");if(!o)throw Error(`Environment ${t.env} was not found`);let s=n.listBindings({projectId:i.project.id,status:"active"}).find((u)=>(!t.binding||u.id===t.binding)&&(!t.repo||u.repositoryFullName.toLowerCase()===t.repo.toLowerCase())&&(!u.environmentId||u.environmentId===o.id));if(!s)throw Error("No active source binding matched; use --binding or --repo");let l=(await Bi(n,{connectionId:s.connectionId,repository:s.repositoryFullName,repositoryId:s.repositoryId,type:"branches",deployKeyId:s.deployKeyId})).refs.find((u)=>u.name===e);if(!l)throw Error(`Branch ${e} was not found`);let d=i.store.createOperation({projectId:s.projectId,environmentId:o.id,resourceId:s.resourceId,kind:"deploy.source",idempotencyKey:`cli-source:${s.id}:${l.commitSha}`,correlationId:`cli-source:${s.id}:${l.commitSha}`,input:{source:{connectionId:s.connectionId,bindingId:s.id,repository:s.repositoryFullName,commitSha:l.commitSha,branch:e,monorepoRoot:s.monorepoRoot,submodules:s.submodules,cloneDepth:s.cloneDepth??null}}});h(`Deployment queued: ${d.id}`),a(`${s.repositoryFullName}@${e} · ${l.commitSha}`)})}catch(n){it(n)}}),r.command("git:webhook:add <repo>","Create and reconcile a signed auto-deploy webhook").option("--connection <id>","Connection ID or name").option("--base-url <url>","Public HTTPS dashboard URL").action(async(e,t)=>{try{await rt(async({sources:n,controlPlane:i})=>{let o=wn(n,i.organization.id,t.connection);if(!o)throw Error("Choose a connection with --connection <id>");let s=n.listRepositories(o.id).find((m)=>m.id===e||m.fullName.toLowerCase()===e.toLowerCase());if(!s)throw Error(`Repository ${e} was not found`);let c=t.baseUrl??process.env.TS_CLOUD_WEBHOOK_BASE_URL;if(!c)throw Error("Pass --base-url or set TS_CLOUD_WEBHOOK_BASE_URL");let l=n.createWebhook({connectionId:o.id,repositoryId:s.id,repositoryFullName:s.fullName}),d=kc(c,l.webhook),u=await Ic(n,l.webhook.id,c);h(`Webhook ${u.status}: ${d}`),a("The signing secret is encrypted and is not printed.")})}catch(n){it(n)}}),r.command("git:webhook:remove <webhook>","Disable and remove a provider webhook").option("--yes","Skip confirmation").action(async(e,t)=>{try{await rt(async({sources:n,controlPlane:i})=>{let o=n.listConnections(i.organization.id).flatMap((c)=>n.listWebhooks(c.id)).find((c)=>c.id===e||c.repositoryFullName.toLowerCase()===e.toLowerCase());if(!o)throw Error(`Webhook ${e} was not found`);if(!t.yes&&!await O(`Remove webhook for ${o.repositoryFullName}?`,!0))return;let s=await Pc(n,o.id);h(`Webhook disabled: ${s.id}`)})}catch(n){it(n)}}),r.command("git:disconnect <connection>","Preview and disconnect a Git provider connection").option("--yes","Skip confirmation").action(async(e,t)=>{try{await rt(async({sources:n,controlPlane:i})=>{let o=wn(n,i.organization.id,e);if(!o)throw Error(`Source connection ${e} was not found`);let s=n.listBindings({connectionId:o.id,status:"active"});a(`${s.length} active binding${s.length===1?"":"s"} will be disabled.`);for(let c of s)a(` ${c.repositoryFullName} · ${c.branchRule??c.defaultBranch} · ${c.monorepoRoot}`);if(!t.yes&&!await O(`Disconnect ${o.name}?`,!1))return;n.disconnectConnection(o.id),h(`Disconnected ${o.name}`)})}catch(n){it(n)}})}import{readFileSync as jc,writeFileSync as Em}from"node:fs";import{resolve as tr}from"node:path";async function Dm(){let r=await P(),e=q(process.cwd(),r),t=le(process.cwd());return{config:r,controlPlane:e,drafts:new Tc(e.store),artifacts:new Oc(e.store,{cwd:process.cwd()}),registries:new Nc(e.store,{encryptionKey:t}),sources:new er(e.store,{encryptionKey:t})}}async function xe(r){let e=await Dm();try{return await r(e)}finally{e.controlPlane.store.close()}}function pe(r){p(r instanceof Error?r.message:"Application onboarding command failed")}function xc(r){return JSON.parse(jc(tr(r),"utf8"))}function Tm(r){let e=r.site,t=["laravel","php","statamic","wordpress"].includes(e.type),n=["static","spa"].includes(e.type)||!e.start&&!t,i=n?{kind:"static",publishDirectory:e.root??e.webDirectory??"dist",buildCommand:e.build}:{kind:"server",runtime:t?"php":"bun",runtimeVersion:t?e.phpVersion:void 0,installCommand:t?"composer install --no-dev --prefer-dist --optimize-autoloader":void 0,buildCommand:e.build,startCommand:e.start??(t?"php -S 0.0.0.0:$PORT -t public":"bun run start")};return{schemaVersion:1,name:e.name??r.siteSlug,slug:r.siteSlug,projectId:r.projectId,environmentId:r.environmentId,source:r.source??{kind:"local",root:e.root??"."},build:i,runtime:{target:"server",architecture:e.architecture==="arm64"?"arm64":"x86_64",port:n?void 0:Number(e.port??3000),healthCheck:n?void 0:{protocol:"http",path:e.healthCheck?.path??"/health"}},domain:e.domain?{hostname:e.domain,tls:e.ssl?.provider!=="none"}:void 0}}function Vi(r){r.command("app:detect [root]","Detect application frameworks without executing project code").action(async(e=".")=>{try{let t=Ec(Dc(tr(e)));x(["Framework","Strategy","Confidence","Evidence"],t.map((n)=>[n.framework,n.strategy,`${Math.round(n.confidence*100)}%`,n.evidence.map((i)=>i.path).join(", ")||"manual"]));for(let n of t)a(`${n.framework}: ${n.description}`)}catch(t){pe(t)}}),r.command("app:plan <draft>","Validate and preview a secret-free application draft").option("--secrets <names>","Comma-separated configured secret names").action(async(e,t)=>{try{let n=Ui(xc(e),t.secrets?.split(",").map((i)=>i.trim()).filter(Boolean));if(a(JSON.stringify(n,null,2)),!n.valid)pe(Error("Plan is not deployable; resolve the reported issues and missing secrets."))}catch(n){pe(n)}}),r.command("app:draft:save <draft>","Persist or update a resumable application draft").option("--id <id>","Existing draft ID").option("--version <version>","Expected draft version").option("--step <step>","Wizard step",{default:"source"}).option("--secrets <names>","Comma-separated configured secret names").action(async(e,t)=>{try{await xe(async({drafts:n,controlPlane:i})=>{let o=xc(e),s=t.secrets?.split(",").map((l)=>l.trim()).filter(Boolean)??[],c=t.id?n.update(t.id,Number(t.version),{draft:o,step:t.step,suppliedSecretNames:s}):n.create({organizationId:i.organization.id,projectId:i.project.id,name:o.name,draft:{...o,projectId:i.project.id},step:t.step,suppliedSecretNames:s});h(`Draft saved: ${c.id} · v${c.version} · ${c.status}`)})}catch(n){pe(n)}}),r.command("app:drafts","List resumable application drafts").action(async()=>{try{await xe(async({drafts:e,controlPlane:t})=>{let n=e.list(t.project.id);x(["ID","Application","Step","Status","Version","Updated"],n.map((i)=>[i.id,i.input.name,i.step,i.status,String(i.version),i.updatedAt]))})}catch(e){pe(e)}}),r.command("app:export <draft>","Export a deterministic application manifest").option("--output <path>","Output path",{default:"application.manifest.json"}).action(async(e,t)=>{try{await xe(async({drafts:n})=>{let i=n.get(e);if(!i)throw Error(`Draft ${e} was not found`);let o=Ui(i.input,i.suppliedSecretNames),s=tr(t.output??"application.manifest.json");Em(s,o.serializedManifest,{mode:384}),h(`Manifest exported: ${s}`)})}catch(n){pe(n)}}),r.command("app:deploy <draft>","Confirm and queue a validated application plan").option("--confirm <environment>","Exact target environment slug").action(async(e,t)=>{try{await xe(async({drafts:n,controlPlane:i})=>{let o=n.get(e);if(!o)throw Error(`Draft ${e} was not found`);if(!t.confirm)throw Error("Pass --confirm <environment> to acknowledge the target and recurring cost drivers");let s=Lc({controlPlane:i.store,drafts:n,draftId:e,expectedVersion:o.version,confirmEnvironment:t.confirm});h(`Application queued: ${s.operation.id}`),a(`Cost drivers: ${s.plan.costDrivers.join(", ")}`)})}catch(n){pe(n)}}),r.command("app:import <site>","Import an existing cloud.config.ts site as a draft").option("--connection <id>","Git connection ID for repository-backed sites").option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{try{await xe(async({config:n,controlPlane:i,sources:o,drafts:s})=>{let c=n.sites?.[e];if(!c)throw Error(`Site ${e} was not found`);let l=i.environments.get(t.env??"production");if(!l)throw Error(`Environment ${t.env} was not found`);let d;if(c.repository?.url){if(!t.connection)throw Error("Repository-backed imports require --connection <id>");let f=o.getConnection(t.connection);if(!f)throw Error("Git connection was not found");let y=_i(c.repository.url),S=o.listRepositories(f.id).find((g)=>g.fullName.toLowerCase()===y.fullName.toLowerCase());if(!S)throw Error(`Repository ${y.fullName} is not synchronized`);d={kind:"git",connectionId:f.id,repositoryId:S.id,repositoryFullName:S.fullName,ref:c.repository.branch??"main"}}let u=Tm({site:c,siteSlug:e,projectId:i.project.id,environmentId:l.id,source:d}),m=s.create({organizationId:i.organization.id,projectId:i.project.id,name:`Import ${e}`,draft:u,step:"review"});h(`Imported draft: ${m.id}`)})}catch(n){pe(n)}}),r.command("app:artifact:add <file>","Inspect and store a bounded ZIP or TAR artifact").action(async(e)=>{try{await xe(async({artifacts:t,controlPlane:n})=>{let i=tr(e),o=t.create({organizationId:n.organization.id,projectId:n.project.id,filename:i.split("/").pop(),bytes:jc(i)});h(`Artifact stored: ${o.id} · ${o.entryCount} entries · ${o.sha256}`)})}catch(t){pe(t)}}),r.command("app:registry:add <host>","Create an encrypted private-registry connection").option("--name <name>","Connection name",{default:"Container registry"}).option("--provider <provider>","docker_hub, ghcr, ecr, gcr, or generic",{default:"generic"}).option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).action(async(e,t)=>{try{await xe(async({registries:n,controlPlane:i})=>{let o={username:process.env[t.usernameEnv],password:process.env[t.passwordEnv],token:process.env[t.tokenEnv]},s=n.create({organizationId:i.organization.id,provider:t.provider,name:t.name,host:e,credential:Object.values(o).some(Boolean)?o:void 0});h(`Registry connection created: ${s.id}`)})}catch(n){pe(n)}}),r.command("app:registries","List safe registry connection metadata").action(async()=>{try{await xe(async({registries:e,controlPlane:t})=>x(["ID","Name","Provider","Host","Status"],e.list(t.organization.id).map((n)=>[n.id,n.name,n.provider,n.host,n.status])))}catch(e){pe(e)}}),r.command("app:registry:test <id>","Test registry and optional image pull access").option("--image <image>","Image reference").action(async(e,t)=>{try{await xe(async({registries:n})=>{let i=await n.test(e,{image:t.image});a(`${i.status}: ${i.healthMessage}`)})}catch(n){pe(n)}}),r.command("app:registry:rotate <id>","Rotate write-only registry credentials").option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).option("--expires <date>","Credential expiry timestamp").action(async(e,t)=>{try{await xe(async({registries:n})=>{let i={username:process.env[t.usernameEnv],password:process.env[t.passwordEnv],token:process.env[t.tokenEnv]};if(!Object.values(i).some(Boolean))throw Error("Set at least one configured registry credential environment variable");let o=n.rotate(e,i,{expiresAt:t.expires?new Date(t.expires).toISOString():void 0});h(`Registry credential rotated: ${o.id} · ${o.status}`)})}catch(n){pe(n)}}),r.command("app:registry:disconnect <id>","Disconnect and erase registry credentials").option("--yes","Skip confirmation").action(async(e,t)=>{try{await xe(async({registries:n,drafts:i,controlPlane:o})=>{let s=n.get(e);if(!s)throw Error(`Registry ${e} was not found`);let c=i.list(o.project.id).filter((l)=>l.input.source.kind==="image"&&l.input.source.registryConnectionId===e||l.input.build.kind==="prebuilt_image"&&l.input.build.registryConnectionId===e);if(a(`${c.length} application draft${c.length===1?"":"s"} reference this registry.`),!t.yes&&!await O(`Disconnect ${s.name}?`,!1))return;n.disconnect(e),h(`Disconnected ${s.name}`)})}catch(n){pe(n)}})}import{existsSync as Nm,readFileSync as Om,writeFileSync as Lm}from"node:fs";function Wi(r,e){p(`'${r}' is not implemented yet.`),a(e),process.exitCode=1}function jm(r){let e={};for(let t of r.split(`
|
|
365
|
+
Spend worker stopped; the loop lease was released.`),process.exit(0)};process.on("SIGINT",l),process.on("SIGTERM",l),await new Promise(()=>{})}catch(t){$e(t)}}),r.command("spend:status","Show enforcement currently in force").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(e)=>{try{let t=await Se(e.env),n=t.gate.listUnder({organizationId:t.organizationId});if(e.json){a(JSON.stringify(n,null,2));return}if(n.length===0){h("No spend enforcement is in force.");return}x(["Action","Budget","Scope","Mode","Since","Reason"],n.map((i)=>[i.action,i.budgetId.slice(0,8),i.environmentId?"environment":i.projectId?"project":"organization",i.simulated?"dry run":"enforcing",i.appliedAt,i.reason]))}catch(t){$e(t)}}),r.command("spend:release <budgetId> <action>","Manually lift one enforcement action").option("--env <environment>","Target environment").action(async(e,t,n)=>{try{let i=await Se(n.env),o=pc(t)?.[0];if(!o)throw Error("An action is required.");if(!i.gate.close(e,o))throw Error(`${o} was not in force for budget ${e}.`);h(`Lifted ${o} for budget ${e}.`),R("The next spend cycle will re-apply this if spend is still over the threshold.")}catch(i){$e(i)}}),r.command("anomaly:config","Tune anomaly detection for a signal").option("--signal <signal>","Signal to configure (default cost)").option("--sensitivity <level>","low, medium, or high").option("--season <points>","Points per season: 24 for daily, 168 for weekly").option("--min-delta <amount>","Absolute change below which nothing is reported, in the signal's units").option("--severity <severity>","info, warning, or critical").option("--disable","Stop detecting this signal").option("--enable","Resume detecting this signal").option("--list","List configured signals instead of changing one").option("--signals","List every detectable signal and its data rules").option("--env <environment>","Target environment").action(async(e)=>{try{if(e.signals){x(["Signal","Source","Empty hour","Min samples","Unit"],vc.map((s)=>[s.key,s.source,s.gapPolicy==="zero"?"counts as zero":"ignored",String(s.minSamples),s.unit]));return}let t=await Se(e.env),n=t.service.anomalyConfigs;if(e.list){let s=n.list({organizationId:t.organizationId,projectId:t.projectId});if(s.length===0){a("No signals are configured; every one uses its shipped defaults.");return}x(["Signal","Enabled","Sensitivity","Season","Min delta","Severity"],s.map((c)=>[c.signal,c.enabled?"yes":"no",c.sensitivity,String(c.seasonLength),String(c.minAbsoluteDelta),c.severity]));return}let i=e.sensitivity;if(i&&!["low","medium","high"].includes(i))throw Error("--sensitivity must be low, medium, or high.");let o=n.upsert({organizationId:t.organizationId,projectId:t.projectId,signal:e.signal??"cost",sensitivity:i,seasonLength:e.season==null?void 0:Number(e.season),minAbsoluteDelta:e.minDelta==null?void 0:Number(e.minDelta),severity:e.severity,enabled:e.disable?!1:e.enable?!0:void 0});h(`Anomaly detection for '${o.signal}' updated.`),a(`${o.enabled?"Enabled":"Disabled"} · ${o.sensitivity} sensitivity · season ${o.seasonLength} · floor ${o.minAbsoluteDelta} · ${o.severity}`)}catch(t){$e(t)}}),r.command("anomaly:silence","Skip detection for a signal, route, or status code").option("--signal <signal>","Signal to silence").option("--route <pattern>","Route glob, e.g. /webhooks/**").option("--status <code>","HTTP status code").option("--reason <reason>","Why — required").option("--until <timestamp>","ISO timestamp the silence expires").option("--list","List active silences").option("--remove <id>","Remove a silence").option("--env <environment>","Target environment").action(async(e)=>{try{let t=await Se(e.env),n=t.service.anomalyConfigs;if(e.remove){h(n.removeSilence(e.remove)?"Silence removed.":"That silence was not found.");return}if(e.list){let o=n.listSilences({organizationId:t.organizationId,projectId:t.projectId});if(o.length===0){h("No active silences.");return}x(["ID","Signal","Route","Status","Expires","Reason"],o.map((s)=>[s.id.slice(0,8),s.signal??"-",s.routePattern??"-",s.statusCode==null?"-":String(s.statusCode),s.expiresAt??"never",s.reason]));return}if(!e.reason)throw Error("--reason is required: someone will ask why this stopped alerting.");let i=n.silence({organizationId:t.organizationId,projectId:t.projectId,signal:e.signal,routePattern:e.route,statusCode:e.status==null?void 0:Number(e.status),reason:e.reason,expiresAt:e.until});if(h(`Silenced (${i.id.slice(0,8)}).`),!i.expiresAt)R("This silence never expires. Pass --until to bound it, or it will outlive the reason for it.")}catch(t){$e(t)}}),r.command("spend:anomalies","List detected spend anomalies").option("--env <environment>","Target environment").option("--unacknowledged","Only anomalies nobody has acknowledged").option("--json","Print structured JSON").action(async(e)=>{try{let t=await Se(e.env),n=t.store.listAnomalies({organizationId:t.organizationId,projectId:t.projectId,unacknowledgedOnly:e.unacknowledged===!0});if(e.json){a(JSON.stringify(n,null,2));return}if(n.length===0){h("No anomalies detected.");return}x(["Hour","Signal","Direction","Observed","Expected","Score","Severity"],n.map((i)=>[i.bucketStart,i.signal,i.direction,Ge(i.observed),Ge(i.expected),i.score.toFixed(1),i.severity]))}catch(t){$e(t)}})}import{readFileSync as km}from"node:fs";var Rm={github:"GITHUB_TOKEN",gitlab:"GITLAB_TOKEN",bitbucket:"BITBUCKET_TOKEN",gitea:"GITEA_TOKEN",generic_https:"GIT_TOKEN"},Im={github:"https://github.com",gitlab:"https://gitlab.com",bitbucket:"https://bitbucket.org",gitea:"https://gitea.com"};function _i(r,e={}){if(e.provider&&!["github","gitlab","bitbucket","gitea","generic_https","generic_ssh"].includes(e.provider))throw Error(`Unsupported Git provider: ${e.provider}`);let t=e.cloneUrl??(/^(?:https?:\/\/|ssh:\/\/|git@)/.test(r)?r:void 0),n="",i=t??r;if(t?.startsWith("git@")){let c=/^git@([^:]+):(.+)$/.exec(t);if(!c)throw Error("SSH repository URL must use git@host:owner/name.git");n=c[1],i=c[2]}else if(t){let c=new URL(t);n=c.hostname,i=c.pathname}if(i=i.replace(/^\/+|\/+$/g,"").replace(/\.git$/,""),!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(i))throw Error("Repository must use owner/name or a supported clone URL");let o=e.provider??(n.includes("github")?"github":n.includes("gitlab")?"gitlab":n.includes("bitbucket")?"bitbucket":n.includes("gitea")?"gitea":t?.startsWith("git@")||t?.startsWith("ssh://")?"generic_ssh":t?"generic_https":"github"),s=e.host??Im[o]??(n?`https://${n}`:"");if(!s)throw Error("A provider host is required");return{provider:o,host:s,fullName:i,cloneUrl:t}}async function Pm(){let r=await P(),e=q(process.cwd(),r);return{config:r,controlPlane:e,sources:new er(e.store,{encryptionKey:le(process.cwd())})}}async function it(r){let e=await Pm();try{return await r(e)}finally{e.controlPlane.store.close()}}function wn(r,e,t){let n=r.listConnections(e);if(!t)return n.length===1?n[0]:void 0;return n.find((i)=>i.id===t||i.name.toLowerCase()===t.toLowerCase())}function Cc(r,e){if(!r)throw Error(`${e} file is required`);return km(r,"utf8")}async function xm(r,e,t,n){if(["github","gitlab","bitbucket","gitea"].includes(e.provider)){let o=r.listRepositories(e.id).find((l)=>l.fullName.toLowerCase()===t.fullName.toLowerCase());if(o)return o;let c=(await Rc(r,e.id,{search:t.fullName,maxPages:20})).find((l)=>l.fullName.toLowerCase()===t.fullName.toLowerCase());if(!c)throw Error(`Repository ${t.fullName} is not granted to this connection`);return c}let i=n.url??t.cloneUrl;if(!i)throw Error("Generic Git connections require --url <clone-url>");return r.upsertRepository({connectionId:e.id,providerRepositoryId:`manual:${t.fullName}`,fullName:t.fullName,cloneUrl:i,defaultBranch:n.branch??"main",visibility:"unknown",archived:!1,metadata:{source:"cli"}})}async function Ac(r,e,t){let n=_i(e,{provider:t.provider,host:t.host,cloneUrl:t.url}),i=wn(r.sources,r.controlPlane.organization.id,t.connection);if(!i){if(t.connection)throw Error(`Source connection ${t.connection} was not found`);let s=t.tokenEnv??Rm[n.provider],c=s?process.env[s]:void 0;if(["github","gitlab","bitbucket","gitea"].includes(n.provider)&&!c)throw Error(`Set ${s} or pass --token-env <name>; secrets are never accepted as command arguments`);let l=n.provider==="generic_ssh"?{publicKey:Cc(t.publicKey,"Public key"),privateKey:Cc(t.privateKey,"Private key"),hostKey:t.hostKey??""}:void 0;r.controlPlane.store.database.transaction(()=>{if(i=r.sources.createConnection({organizationId:r.controlPlane.organization.id,provider:n.provider,name:t.name??`${n.provider.replace("_"," ")} · ${new URL(n.host).host}`,host:n.host,owner:n.fullName.split("/")[0],authKind:n.provider==="generic_ssh"?"deploy_key":c?"access_token":"none",credential:c?{token:c}:void 0}),l)r.sources.createDeployKey({connectionId:i.id,name:t.deployKey??`${n.fullName} deploy key`,...l,host:t.sshHost??new URL(n.host).hostname,actorId:void 0})})()}if(!i)throw Error("Source connection could not be created");if(i.status==="disconnected")throw Error("The selected source connection is disconnected");let o=await xm(r.sources,i,n,t);if(t.site){let s=r.controlPlane.environments.get("production")??[...r.controlPlane.environments.values()][0],c=r.controlPlane.store.listResources(r.controlPlane.project.id,s?.id).find((d)=>d.kind==="application"&&d.slug===t.site);if(!c)throw Error(`Application ${t.site} was not found`);let l=i.provider==="generic_ssh"?r.sources.listDeployKeys(i.id)[0]:void 0;r.sources.createBinding({projectId:r.controlPlane.project.id,environmentId:s?.id,resourceId:c.id,connectionId:i.id,repositoryId:o.id,repositoryFullName:o.fullName,defaultBranch:t.branch??o.defaultBranch,branchRule:t.tag?void 0:t.branch??o.defaultBranch,tagRule:t.tag,monorepoRoot:t.root??".",includePaths:t.include?.split(",").map((d)=>d.trim()).filter(Boolean),excludePaths:t.exclude?.split(",").map((d)=>d.trim()).filter(Boolean),submodules:t.submodules,cloneDepth:t.fullClone?void 0:20,deployKeyId:l?.id})}return{connection:i,repository:o}}function ot(r){p(r instanceof Error?r.message:"Git integration command failed")}function zi(r){r.command("git:connections","List encrypted Git provider connections").action(async()=>{try{await it(async({sources:e,controlPlane:t})=>{let n=e.listConnections(t.organization.id);x(["ID","Name","Provider","Status","Credential","Last sync"],n.map((i)=>[i.id,i.name,i.provider,i.status,i.credentialConfigured?"encrypted":"none",i.lastSyncedAt??"-"]))})}catch(e){ot(e)}}),r.command("git:repositories","List repositories granted to a connection").option("--connection <id>","Connection ID or name").action(async(e)=>{try{await it(async({sources:t,controlPlane:n})=>{let i=wn(t,n.organization.id,e.connection);if(!i)throw Error("Choose a connection with --connection <id>");let o=t.listRepositories(i.id);x(["ID","Repository","Default branch","Visibility","Archived"],o.map((s)=>[s.id,s.fullName,s.defaultBranch,s.visibility,s.archived?"yes":"no"]))})}catch(t){ot(t)}}),r.command("git:add <repo>","Connect and optionally bind a Git repository").option("--branch <branch>","Default branch to deploy",{default:"main"}).option("--connection <id>","Reuse a connection ID or name").option("--provider <provider>","github, gitlab, bitbucket, gitea, generic_https, or generic_ssh").option("--host <url>","Provider API host").option("--name <name>","Connection name").option("--token-env <name>","Environment variable containing the access token").option("--url <clone-url>","Credential-free clone URL").option("--site <slug>","Bind the repository to an application").option("--root <path>","Monorepo root",{default:"."}).option("--include <globs>","Comma-separated watched paths").option("--exclude <globs>","Comma-separated ignored paths").option("--tag <glob>","Deploy matching tags instead of branch pushes").option("--full-clone","Use full history instead of a shallow clone").option("--submodules","Initialize repository submodules").option("--deploy-key <name>","Deploy-key display name").option("--public-key <path>","SSH public-key file").option("--private-key <path>","SSH private-key file").option("--host-key <key>","Pinned SSH host key").option("--ssh-host <host>","SSH clone host").option("--yes","Skip confirmation").action(async(e,t)=>{try{if(!t.yes&&!await O(`Connect ${e}?`,!0))return;let n=await it((i)=>Ac(i,e,t));if(h(`Connected ${n.repository.fullName}`),a(`Connection: ${n.connection.name} (${n.connection.id})`),t.site)a(`Application binding: ${t.site}`)}catch(n){ot(n)}}),r.command("git:import","Import site.repository entries from cloud.config.ts").option("--connection <id>","Reuse a connection ID or name").option("--token-env <name>","Environment variable containing provider token").option("--yes","Skip confirmation").action(async(e)=>{try{await it(async(t)=>{let n=Object.entries(t.config.sites??{}).filter(([,i])=>i.repository?.url);if(!n.length)throw Error("No site.repository entries were found");if(!e.yes&&!await O(`Import ${n.length} repository configuration${n.length===1?"":"s"}?`,!0))return;for(let[i,o]of n){let s=o.repository,c=s.provider==="custom"?void 0:s.provider;await Ac(t,s.url,{connection:e.connection,tokenEnv:e.tokenEnv,site:i,provider:c,branch:s.branch??"main",tag:s.strategy==="tag"?s.tag??s.tagPattern??"v*":void 0,url:s.url}),h(`Imported ${i} · ${s.url}`)}})}catch(t){ot(t)}}),r.command("git:branches <repo>","List branches or tags visible to a connection").option("--connection <id>","Connection ID or name").option("--tags","List tags instead of branches").option("--deploy-key <id>","SSH deploy-key ID").action(async(e,t)=>{try{await it(async({sources:n,controlPlane:i})=>{let o=wn(n,i.organization.id,t.connection);if(!o)throw Error("Choose a connection with --connection <id>");let s=n.listRepositories(o.id).find((l)=>l.id===e||l.fullName.toLowerCase()===e.toLowerCase());if(!s)throw Error(`Repository ${e} was not found`);let c=await Bi(n,{connectionId:o.id,repository:s.fullName,repositoryId:s.id,type:t.tags?"tags":"branches",deployKeyId:t.deployKey??n.listDeployKeys(o.id)[0]?.id});x([t.tags?"Tag":"Branch","Commit","Protected"],c.refs.map((l)=>[l.name,l.commitSha,l.protected?"yes":"no"]))})}catch(n){ot(n)}}),r.command("git:deploy <branch>","Queue an idempotent deployment from a Git branch").option("--binding <id>","Source binding ID").option("--repo <owner/name>","Bound repository").option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{try{await it(async({sources:n,controlPlane:i})=>{let o=i.environments.get(t.env??"production");if(!o)throw Error(`Environment ${t.env} was not found`);let s=n.listBindings({projectId:i.project.id,status:"active"}).find((u)=>(!t.binding||u.id===t.binding)&&(!t.repo||u.repositoryFullName.toLowerCase()===t.repo.toLowerCase())&&(!u.environmentId||u.environmentId===o.id));if(!s)throw Error("No active source binding matched; use --binding or --repo");let l=(await Bi(n,{connectionId:s.connectionId,repository:s.repositoryFullName,repositoryId:s.repositoryId,type:"branches",deployKeyId:s.deployKeyId})).refs.find((u)=>u.name===e);if(!l)throw Error(`Branch ${e} was not found`);let d=i.store.createOperation({projectId:s.projectId,environmentId:o.id,resourceId:s.resourceId,kind:"deploy.source",idempotencyKey:`cli-source:${s.id}:${l.commitSha}`,correlationId:`cli-source:${s.id}:${l.commitSha}`,input:{source:{connectionId:s.connectionId,bindingId:s.id,repository:s.repositoryFullName,commitSha:l.commitSha,branch:e,monorepoRoot:s.monorepoRoot,submodules:s.submodules,cloneDepth:s.cloneDepth??null}}});h(`Deployment queued: ${d.id}`),a(`${s.repositoryFullName}@${e} · ${l.commitSha}`)})}catch(n){ot(n)}}),r.command("git:webhook:add <repo>","Create and reconcile a signed auto-deploy webhook").option("--connection <id>","Connection ID or name").option("--base-url <url>","Public HTTPS dashboard URL").action(async(e,t)=>{try{await it(async({sources:n,controlPlane:i})=>{let o=wn(n,i.organization.id,t.connection);if(!o)throw Error("Choose a connection with --connection <id>");let s=n.listRepositories(o.id).find((m)=>m.id===e||m.fullName.toLowerCase()===e.toLowerCase());if(!s)throw Error(`Repository ${e} was not found`);let c=t.baseUrl??process.env.TS_CLOUD_WEBHOOK_BASE_URL;if(!c)throw Error("Pass --base-url or set TS_CLOUD_WEBHOOK_BASE_URL");let l=n.createWebhook({connectionId:o.id,repositoryId:s.id,repositoryFullName:s.fullName}),d=kc(c,l.webhook),u=await Ic(n,l.webhook.id,c);h(`Webhook ${u.status}: ${d}`),a("The signing secret is encrypted and is not printed.")})}catch(n){ot(n)}}),r.command("git:webhook:remove <webhook>","Disable and remove a provider webhook").option("--yes","Skip confirmation").action(async(e,t)=>{try{await it(async({sources:n,controlPlane:i})=>{let o=n.listConnections(i.organization.id).flatMap((c)=>n.listWebhooks(c.id)).find((c)=>c.id===e||c.repositoryFullName.toLowerCase()===e.toLowerCase());if(!o)throw Error(`Webhook ${e} was not found`);if(!t.yes&&!await O(`Remove webhook for ${o.repositoryFullName}?`,!0))return;let s=await Pc(n,o.id);h(`Webhook disabled: ${s.id}`)})}catch(n){ot(n)}}),r.command("git:disconnect <connection>","Preview and disconnect a Git provider connection").option("--yes","Skip confirmation").action(async(e,t)=>{try{await it(async({sources:n,controlPlane:i})=>{let o=wn(n,i.organization.id,e);if(!o)throw Error(`Source connection ${e} was not found`);let s=n.listBindings({connectionId:o.id,status:"active"});a(`${s.length} active binding${s.length===1?"":"s"} will be disabled.`);for(let c of s)a(` ${c.repositoryFullName} · ${c.branchRule??c.defaultBranch} · ${c.monorepoRoot}`);if(!t.yes&&!await O(`Disconnect ${o.name}?`,!1))return;n.disconnectConnection(o.id),h(`Disconnected ${o.name}`)})}catch(n){ot(n)}})}import{readFileSync as jc,writeFileSync as Em}from"node:fs";import{resolve as tr}from"node:path";async function Dm(){let r=await P(),e=q(process.cwd(),r),t=le(process.cwd());return{config:r,controlPlane:e,drafts:new Tc(e.store),artifacts:new Oc(e.store,{cwd:process.cwd()}),registries:new Nc(e.store,{encryptionKey:t}),sources:new er(e.store,{encryptionKey:t})}}async function xe(r){let e=await Dm();try{return await r(e)}finally{e.controlPlane.store.close()}}function pe(r){p(r instanceof Error?r.message:"Application onboarding command failed")}function xc(r){return JSON.parse(jc(tr(r),"utf8"))}function Tm(r){let e=r.site,t=["laravel","php","statamic","wordpress"].includes(e.type),n=["static","spa"].includes(e.type)||!e.start&&!t,i=n?{kind:"static",publishDirectory:e.root??e.webDirectory??"dist",buildCommand:e.build}:{kind:"server",runtime:t?"php":"bun",runtimeVersion:t?e.phpVersion:void 0,installCommand:t?"composer install --no-dev --prefer-dist --optimize-autoloader":void 0,buildCommand:e.build,startCommand:e.start??(t?"php -S 0.0.0.0:$PORT -t public":"bun run start")};return{schemaVersion:1,name:e.name??r.siteSlug,slug:r.siteSlug,projectId:r.projectId,environmentId:r.environmentId,source:r.source??{kind:"local",root:e.root??"."},build:i,runtime:{target:"server",architecture:e.architecture==="arm64"?"arm64":"x86_64",port:n?void 0:Number(e.port??3000),healthCheck:n?void 0:{protocol:"http",path:e.healthCheck?.path??"/health"}},domain:e.domain?{hostname:e.domain,tls:e.ssl?.provider!=="none"}:void 0}}function Vi(r){r.command("app:detect [root]","Detect application frameworks without executing project code").action(async(e=".")=>{try{let t=Ec(Dc(tr(e)));x(["Framework","Strategy","Confidence","Evidence"],t.map((n)=>[n.framework,n.strategy,`${Math.round(n.confidence*100)}%`,n.evidence.map((i)=>i.path).join(", ")||"manual"]));for(let n of t)a(`${n.framework}: ${n.description}`)}catch(t){pe(t)}}),r.command("app:plan <draft>","Validate and preview a secret-free application draft").option("--secrets <names>","Comma-separated configured secret names").action(async(e,t)=>{try{let n=Ui(xc(e),t.secrets?.split(",").map((i)=>i.trim()).filter(Boolean));if(a(JSON.stringify(n,null,2)),!n.valid)pe(Error("Plan is not deployable; resolve the reported issues and missing secrets."))}catch(n){pe(n)}}),r.command("app:draft:save <draft>","Persist or update a resumable application draft").option("--id <id>","Existing draft ID").option("--version <version>","Expected draft version").option("--step <step>","Wizard step",{default:"source"}).option("--secrets <names>","Comma-separated configured secret names").action(async(e,t)=>{try{await xe(async({drafts:n,controlPlane:i})=>{let o=xc(e),s=t.secrets?.split(",").map((l)=>l.trim()).filter(Boolean)??[],c=t.id?n.update(t.id,Number(t.version),{draft:o,step:t.step,suppliedSecretNames:s}):n.create({organizationId:i.organization.id,projectId:i.project.id,name:o.name,draft:{...o,projectId:i.project.id},step:t.step,suppliedSecretNames:s});h(`Draft saved: ${c.id} · v${c.version} · ${c.status}`)})}catch(n){pe(n)}}),r.command("app:drafts","List resumable application drafts").action(async()=>{try{await xe(async({drafts:e,controlPlane:t})=>{let n=e.list(t.project.id);x(["ID","Application","Step","Status","Version","Updated"],n.map((i)=>[i.id,i.input.name,i.step,i.status,String(i.version),i.updatedAt]))})}catch(e){pe(e)}}),r.command("app:export <draft>","Export a deterministic application manifest").option("--output <path>","Output path",{default:"application.manifest.json"}).action(async(e,t)=>{try{await xe(async({drafts:n})=>{let i=n.get(e);if(!i)throw Error(`Draft ${e} was not found`);let o=Ui(i.input,i.suppliedSecretNames),s=tr(t.output??"application.manifest.json");Em(s,o.serializedManifest,{mode:384}),h(`Manifest exported: ${s}`)})}catch(n){pe(n)}}),r.command("app:deploy <draft>","Confirm and queue a validated application plan").option("--confirm <environment>","Exact target environment slug").action(async(e,t)=>{try{await xe(async({drafts:n,controlPlane:i})=>{let o=n.get(e);if(!o)throw Error(`Draft ${e} was not found`);if(!t.confirm)throw Error("Pass --confirm <environment> to acknowledge the target and recurring cost drivers");let s=Lc({controlPlane:i.store,drafts:n,draftId:e,expectedVersion:o.version,confirmEnvironment:t.confirm});h(`Application queued: ${s.operation.id}`),a(`Cost drivers: ${s.plan.costDrivers.join(", ")}`)})}catch(n){pe(n)}}),r.command("app:import <site>","Import an existing cloud.config.ts site as a draft").option("--connection <id>","Git connection ID for repository-backed sites").option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{try{await xe(async({config:n,controlPlane:i,sources:o,drafts:s})=>{let c=n.sites?.[e];if(!c)throw Error(`Site ${e} was not found`);let l=i.environments.get(t.env??"production");if(!l)throw Error(`Environment ${t.env} was not found`);let d;if(c.repository?.url){if(!t.connection)throw Error("Repository-backed imports require --connection <id>");let f=o.getConnection(t.connection);if(!f)throw Error("Git connection was not found");let y=_i(c.repository.url),S=o.listRepositories(f.id).find((g)=>g.fullName.toLowerCase()===y.fullName.toLowerCase());if(!S)throw Error(`Repository ${y.fullName} is not synchronized`);d={kind:"git",connectionId:f.id,repositoryId:S.id,repositoryFullName:S.fullName,ref:c.repository.branch??"main"}}let u=Tm({site:c,siteSlug:e,projectId:i.project.id,environmentId:l.id,source:d}),m=s.create({organizationId:i.organization.id,projectId:i.project.id,name:`Import ${e}`,draft:u,step:"review"});h(`Imported draft: ${m.id}`)})}catch(n){pe(n)}}),r.command("app:artifact:add <file>","Inspect and store a bounded ZIP or TAR artifact").action(async(e)=>{try{await xe(async({artifacts:t,controlPlane:n})=>{let i=tr(e),o=t.create({organizationId:n.organization.id,projectId:n.project.id,filename:i.split("/").pop(),bytes:jc(i)});h(`Artifact stored: ${o.id} · ${o.entryCount} entries · ${o.sha256}`)})}catch(t){pe(t)}}),r.command("app:registry:add <host>","Create an encrypted private-registry connection").option("--name <name>","Connection name",{default:"Container registry"}).option("--provider <provider>","docker_hub, ghcr, ecr, gcr, or generic",{default:"generic"}).option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).action(async(e,t)=>{try{await xe(async({registries:n,controlPlane:i})=>{let o={username:process.env[t.usernameEnv],password:process.env[t.passwordEnv],token:process.env[t.tokenEnv]},s=n.create({organizationId:i.organization.id,provider:t.provider,name:t.name,host:e,credential:Object.values(o).some(Boolean)?o:void 0});h(`Registry connection created: ${s.id}`)})}catch(n){pe(n)}}),r.command("app:registries","List safe registry connection metadata").action(async()=>{try{await xe(async({registries:e,controlPlane:t})=>x(["ID","Name","Provider","Host","Status"],e.list(t.organization.id).map((n)=>[n.id,n.name,n.provider,n.host,n.status])))}catch(e){pe(e)}}),r.command("app:registry:test <id>","Test registry and optional image pull access").option("--image <image>","Image reference").action(async(e,t)=>{try{await xe(async({registries:n})=>{let i=await n.test(e,{image:t.image});a(`${i.status}: ${i.healthMessage}`)})}catch(n){pe(n)}}),r.command("app:registry:rotate <id>","Rotate write-only registry credentials").option("--username-env <name>","Username environment variable",{default:"REGISTRY_USERNAME"}).option("--password-env <name>","Password environment variable",{default:"REGISTRY_PASSWORD"}).option("--token-env <name>","Bearer-token environment variable",{default:"REGISTRY_TOKEN"}).option("--expires <date>","Credential expiry timestamp").action(async(e,t)=>{try{await xe(async({registries:n})=>{let i={username:process.env[t.usernameEnv],password:process.env[t.passwordEnv],token:process.env[t.tokenEnv]};if(!Object.values(i).some(Boolean))throw Error("Set at least one configured registry credential environment variable");let o=n.rotate(e,i,{expiresAt:t.expires?new Date(t.expires).toISOString():void 0});h(`Registry credential rotated: ${o.id} · ${o.status}`)})}catch(n){pe(n)}}),r.command("app:registry:disconnect <id>","Disconnect and erase registry credentials").option("--yes","Skip confirmation").action(async(e,t)=>{try{await xe(async({registries:n,drafts:i,controlPlane:o})=>{let s=n.get(e);if(!s)throw Error(`Registry ${e} was not found`);let c=i.list(o.project.id).filter((l)=>l.input.source.kind==="image"&&l.input.source.registryConnectionId===e||l.input.build.kind==="prebuilt_image"&&l.input.build.registryConnectionId===e);if(a(`${c.length} application draft${c.length===1?"":"s"} reference this registry.`),!t.yes&&!await O(`Disconnect ${s.name}?`,!1))return;n.disconnect(e),h(`Disconnected ${s.name}`)})}catch(n){pe(n)}})}import{existsSync as Nm,readFileSync as Om,writeFileSync as Lm}from"node:fs";function Wi(r,e){p(`'${r}' is not implemented yet.`),a(e),process.exitCode=1}function jm(r){let e={};for(let t of r.split(`
|
|
366
366
|
`)){let n=t.trim();if(!n||n.startsWith("#"))continue;let i=n.indexOf("=");if(i===-1)continue;let o=n.slice(0,i).trim(),s=n.slice(i+1).trim();if(s.startsWith('"')&&s.endsWith('"')||s.startsWith("'")&&s.endsWith("'"))s=s.slice(1,-1);e[o]=s}return e}function Fm(r){return Object.entries(r).sort(([t],[n])=>t.localeCompare(n)).map(([t,n])=>{if(!/[\s#"']/.test(n))return`${t}=${n}`;let i='"'+n.replace(/"/g,"\\\"")+'"';return`${t}=${i}`}).join(`
|
|
367
367
|
`)+`
|
|
368
368
|
`}function qi(r){r.command("env:list","List configured environments and their deployed stack status").action(async()=>{w("Environments");try{let e=await P(),t=Object.keys(e.environments??{});if(!t.length){a("No environments configured. Add environments.<env> to your cloud.config.");return}let n=[];for(let i of t){let o=e.environments[i],s=o?.region||e.project.region||"us-east-1",c=o?.app?`serverless (${o.app.kind})`:"server/static",l="not deployed";if(o?.app)try{let d=new ie(s),{Stacks:u}=await d.describeStacks({stackName:Fc(e,i)});l=u?.[0]?.StackStatus??"not deployed"}catch{l="not deployed"}n.push([i,o?.type??i,c,s,l])}x(["Environment","Type","Kind","Region","Stack status"],n)}catch(e){p(`Failed to list environments: ${e.message}`),process.exitCode=1}}),r.command("env:compare <env1> <env2>","Compare two environments' app configuration").action(async(e,t)=>{w(`Comparing ${e} ↔ ${t}`);try{let n=await P(),i=n.environments?.[e],o=n.environments?.[t];if(!i||!o){p(`Both environments must exist in config (have: ${Object.keys(n.environments??{}).join(", ")||"none"}).`),process.exitCode=1;return}let s=(m,f="")=>{let y={};for(let[S,g]of Object.entries(m??{})){let b=f?`${f}.${S}`:S;if(g&&typeof g==="object"&&!Array.isArray(g))Object.assign(y,s(g,b));else y[b]=Array.isArray(g)?JSON.stringify(g):String(g)}return y},c=s(i),l=s(o),u=[...new Set([...Object.keys(c),...Object.keys(l)])].sort().filter((m)=>c[m]!==l[m]).map((m)=>[m,c[m]??"—",l[m]??"—"]);if(!u.length){h("Identical — no configuration differences.");return}x(["Setting",e,t],u),a(`
|
|
369
|
-
${u.length} difference(s)`)}catch(n){p(`Comparison failed: ${n.message}`),process.exitCode=1}}),r.command("env:pull","Download a serverless function's environment to a .env file").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli",{default:"http"}).option("--file <path>","Output file (default .env.<environment>)").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{region:i,functions:o}=bt(t,n),s=e?.function??"http",d=(await new tt(i).getFunction(o[s])).Configuration?.Environment?.Variables??{},u=e?.file??`.env.${n}`;Lm(u,Fm(d)),h(`Wrote ${Object.keys(d).length} variable(s) from ${o[s]} → ${u}`)}catch(t){p(`env:pull failed: ${t.message}`),process.exitCode=1}}),r.command("env:push","Upload a .env file to a serverless function's configuration").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Function: http | queue | cli | all",{default:"all"}).option("--file <path>","Input file (default .env.<environment>)").option("--replace","Replace the entire env instead of merging over the live config").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{region:i,functions:o}=bt(t,n),s=e?.file??`.env.${n}`;if(!Nm(s)){p(`File not found: ${s}`),process.exitCode=1;return}let c=jm(Om(s,"utf8")),l=(e?.function??"all").toLowerCase();if(l!=="all"&&!["http","queue","cli"].includes(l)){p("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let d=l==="all"?["http","queue","cli"]:[l],u=(t.environments?.[n]?.app?.provisionedConcurrency??0)>0,m=new tt(i);for(let f of d){let y=o[f],S=c;if(!e?.replace)S={...(await m.getFunction(y)).Configuration?.Environment?.Variables??{},...c};if(Mc(y,S),await m.updateFunctionConfiguration({FunctionName:y,Environment:{Variables:S}}),await m.waitForFunctionActive(y,120),u){let g=await m.publishVersion({FunctionName:y});if(g.Version)await m.updateAlias({FunctionName:y,Name:"live",FunctionVersion:g.Version})}h(`Pushed ${Object.keys(c).length} var(s) → ${y}${e?.replace?" (replaced)":" (merged)"}${u?" (published + flipped live alias)":""}`)}a("Note: a subsequent `cloud deploy:serverless` re-injects infra/secret env from config.")}catch(t){p(`env:push failed: ${t.message}`),process.exitCode=1}}),r.command("env:create <name>","Create new environment").action(async(e)=>{Wi(`env:create ${e}`,"Add an `environments.<name>` block to your cloud.config and run `cloud deploy:serverless --env <name>`.")}),r.command("env:switch <name>","Switch active environment").action(async(e)=>{Wi(`env:switch ${e}`,'ts-cloud has no persistent "active" environment — pass --env <name> to each command.')});for(let e of["clone","promote","sync"])r.command(`env:${e} <source> <target>`,`(${e}) environment configuration`).action(async(t,n)=>{Wi(`env:${e} ${t} ${n}`,`Copy the \`environments.${t}\` block to \`environments.${n}\` in cloud.config, then \`cloud deploy:serverless --env ${n}\`.`)});r.command("env:preview <branch>","Create, inspect, rebuild, extend, or destroy an immutable preview").option("--sha <commit>","Exact 40-64 character source commit").option("--pr <number>","Pull request number").option("--site <name>","Application site (defaults to the first site)").option("--env <environment>","Base environment",{default:"production"}).option("--domain <pattern>","HTTPS URL pattern containing {name}").option("--ttl <hours>","Preview lifetime in hours").option("--destroy","Queue teardown for this preview").option("--rebuild","Rebuild the currently recorded commit").option("--extend <hours>","Extend expiry from now/current expiry").option("--get-url","Print the stable preview URL without mutation").option("--yes","Skip destructive confirmation").action(async(e,t)=>{let n;try{let i=await P(),o=t.env||"production",s=t.site||Object.keys(i.sites??{})[0];if(!s)throw Error("No application site is configured; pass --site after adding one");n=q(process.cwd(),i);let c=n.environments.get(o),l=c?n.store.listResources(n.project.id,c.id).find((g)=>g.kind==="application"&&g.slug===s):void 0;if(!c||!l)throw Error(`Application ${s} was not found in base environment ${o}`);let d=new nr(n.store),u=d.previews.getDefinitionForResource(l.id);if(!u){let g=i.sites?.[s]?.domain,b=t.domain||(g?`https://{name}.preview.${g}`:void 0);if(!b)throw Error("Pass --domain with an HTTPS pattern containing {name} to configure previews");u=d.previews.createDefinition({projectId:n.project.id,resourceId:l.id,baseEnvironmentId:c.id,domainPattern:b,ttlHours:Number(t.ttl)||void 0})}let m=Number(t.pr)||void 0,f=m?d.previews.findForPullRequest(u.id,"local",m):d.previews.findForBranch(u.id,"local",e);if(t.getUrl){if(!f)throw Error("Preview was not found");a(f.url??"URL unavailable");return}if(t.extend){if(!f)throw Error("Preview was not found");let g=d.previews.extend(f.id,Number(t.extend));h(`Extended ${g.name} until ${g.expiresAt}`);return}if(t.destroy){if(!f)throw Error("Preview was not found");if(!t.yes&&!await O(`Destroy ${f.name} and only its tagged resources?`,!1))return;let g=d.enqueueDestroy(f,"manual");h(`Preview teardown queued: ${g.id}`);return}if(t.rebuild){if(!f)throw Error("Preview was not found");let g=d.enqueueDeploy(f,{reason:"manual_rebuild"});h(`Preview rebuild queued: ${g.id}`);return}if(!t.sha)throw Error("Pass --sha with the exact immutable commit to deploy");let y=d.previews.upsert({definitionId:u.id,repository:"local",branch:e,pullRequestNumber:m,commitSha:t.sha}),S=d.enqueueDeploy(y.preview,{created:y.created});h(`${y.created?"Preview creation":"Preview update"} queued: ${S.id}`),a(`${y.preview.url} · expires ${y.preview.expiresAt}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n?.store.close()}}),r.command("env:cleanup","Remove stale preview environments").option("--dry-run","List cleanup candidates without queuing teardown").option("--max-age <hours>","Also remove previews older than this age").option("--keep <count>","Keep this many newest previews per application").action(async(e)=>{let t;try{let n=await P();t=q(process.cwd(),n);let o=new nr(t.store).cleanup({dryRun:e.dryRun,maxAgeHours:Number(e.maxAge)||void 0,keepCount:Number(e.keep)||void 0});x(["Preview","Status","Expires","Reason"],o.candidates.map((s)=>[s.preview.name,s.preview.status,s.preview.expiresAt,s.reasons.join(", ")])),h(e.dryRun?`${o.candidates.length} cleanup candidate(s)`:`${o.operations.length} teardown job(s) queued`)}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}finally{t?.store.close()}}),r.command("env:previews","List persistent preview environments").option("--site <name>","Filter application site").action(async(e)=>{let t;try{let n=await P();t=q(process.cwd(),n);let i=new nr(t.store),o=e.site?new Set(t.store.listResources(t.project.id).filter((c)=>c.slug===e.site).map((c)=>c.id)):void 0,s=i.previews.listInstances({projectId:t.project.id}).filter((c)=>!o||o.has(c.resourceId));x(["ID","Name","Status","Branch / PR","Commit","URL","Expires","Cost"],s.map((c)=>[c.id,c.name,c.status,c.pullRequestNumber?`PR #${c.pullRequestNumber}`:c.branch,c.commitSha.slice(0,12),c.url??"—",c.expiresAt,c.costEstimate==null?"—":`$${c.costEstimate.toFixed(2)}`]))}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}finally{t?.store.close()}})}import{readFileSync as rr}from"node:fs";async function Ee(){let r=await P(),e=q(process.cwd(),r);return{config:r,controlPlane:e,applications:new Hc(e.store)}}async function Gi(r,e,t){let n=await Ee();try{let i=n.applications.get(r);if(!i)throw Error("Compose application was not found");let o=e??n.controlPlane.store.listEnvironments(i.projectId).find((c)=>c.id===i.environmentId)?.slug;if(!o)throw Error("Compose environment was not found");let s=await Jc(n.config,o,t(i));if(!s.ok)throw Error(s.stderr||s.error||"Remote Compose command failed");if(s.stdout)a(s.stdout)}finally{n.controlPlane.store.close()}}function Ki(r){r.command("compose:preview <file>","Parse Compose without persistence or provider mutation").option("--name <name>","Application name",{default:"Compose application"}).option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{let n=await Ee();try{let i=n.controlPlane.environments.get(t.env??"production");if(!i)throw Error("Environment was not found");let o=n.applications.preview(rr(e,"utf8"),{name:t.name??"Compose application",projectId:n.controlPlane.project.id,environmentId:i.id});if(x(["Severity","Path","Code","Message"],o.diagnostics.map((s)=>[s.severity,s.path,s.code,s.message])),!o.valid)process.exitCode=1;else h(`${Object.keys(o.manifest.spec.services).length} services are valid`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:import <file>","Import and persist a validated Compose application").option("--name <name>","Application name").option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{let n=await Ee();try{let i=n.controlPlane.environments.get(t.env??"production");if(!i)throw Error("Environment was not found");let o=t.name??e.split("/").at(-2)??"Compose application",s=n.applications.import(rr(e,"utf8"),{name:o,projectId:n.controlPlane.project.id,environmentId:i.id});h(`Imported ${s.application.name} (${s.application.id})`),a(`${Object.keys(s.application.manifest.spec.services).length} services · ${s.parsed.diagnostics.length} diagnostic(s)`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:templates","List pinned verified Compose templates").action(()=>x(["ID","Version","Category","Architecture","Verified"],Uc().map((e)=>[e.id,e.version,e.category,e.architecture,e.lastVerifiedAt]))),r.command("compose:catalog <file>","Validate and inspect a local/custom template catalog").option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{let n=await Ee();try{let i=n.controlPlane.environments.get(t.env??"production");if(!i)throw Error("Environment was not found");let o=Vc(rr(e,"utf8"),{projectId:n.controlPlane.project.id,environmentId:i.id});if(x(["ID","Version","Category","Services","Checksum"],o.templates.map((s)=>[s.template.id,s.template.version,s.template.category,String(Object.keys(s.parsed.manifest.spec.services).length),s.template.checksum.slice(0,12)])),o.diagnostics.length)x(["Severity","Path","Message"],o.diagnostics.map((s)=>[s.severity,s.path,s.message]));if(!o.valid)process.exitCode=1}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:template <template>","Create an editable application from a template").option("--name <name>","Application name").option("--domain <domain>","Primary domain").option("--env <environment>","Target environment",{default:"production"}).option("--version <version>","Pinned template version").action(async(e,t)=>{let n=await Ee();try{let i=n.controlPlane.environments.get(t.env??"production");if(!i)throw Error("Environment was not found");let o=n.applications.fromTemplate(e,{domain:t.domain??""},{name:t.name??e,projectId:n.controlPlane.project.id,environmentId:i.id,version:t.version});h(`Created ${o.application.name} from ${e}@${o.application.templateVersion}`),a(`Export with cloud compose:export ${o.application.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:list","List Compose applications").action(async()=>{let e=await Ee();try{x(["ID","Name","Status","Services","Source","Updated"],e.applications.list({projectId:e.controlPlane.project.id}).map((t)=>[t.id,t.name,t.status,String(Object.keys(t.manifest.spec.services).length),t.templateId?`${t.templateId}@${t.templateVersion}`:"compose",t.updatedAt]))}finally{e.controlPlane.store.close()}}),r.command("compose:export <application>","Print a normalized editable Compose file").action(async(e)=>{let t=await Ee();try{let n=t.applications.get(e);if(!n)throw Error("Compose application was not found");a(_c(n.manifest))}finally{t.controlPlane.store.close()}}),r.command("compose:diff <application> <file>","Compare persisted and proposed Compose manifests").action(async(e,t)=>{let n=await Ee();try{let i=n.applications.get(e);if(!i)throw Error("Compose application was not found");let o=Bc(rr(t,"utf8"),i.manifest.metadata);x(["Path","Before","After"],zc(i.manifest,o.manifest).map((s)=>[s.path,JSON.stringify(s.before),JSON.stringify(s.after)]))}finally{n.controlPlane.store.close()}});for(let e of["deploy","redeploy","start","stop"])r.command(`compose:${e} <application>`,`${e} a Compose application through the durable queue`).action(async(t)=>{let n=await Ee();try{let i=n.applications.get(t);if(!i)throw Error("Compose application was not found");let o=new ir(n.controlPlane.store).enqueue(i,e);h(`${e} queued: ${o.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}});r.command("compose:scale <application> <service> <replicas>","Scale one capable Compose service").action(async(e,t,n)=>{let i=await Ee();try{let o=i.applications.get(e);if(!o)throw Error("Compose application was not found");let s=new ir(i.controlPlane.store).enqueue(o,"scale",{service:t,replicas:Number(n)});h(`Scale queued: ${s.id}`)}catch(o){p(o instanceof Error?o.message:String(o)),process.exitCode=1}finally{i.controlPlane.store.close()}}),r.command("compose:delete <application>","Delete a Compose stack; named volumes are preserved by default").option("--remove-volumes","Also permanently remove named volumes").option("--confirm <text>","Exact application confirmation").action(async(e,t)=>{let n=await Ee();try{let i=n.applications.get(e);if(!i)throw Error("Compose application was not found");let o=new ir(n.controlPlane.store).enqueue(i,"delete",{removeVolumes:t.removeVolumes,confirmation:t.confirm});h(`Deletion queued: ${o.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:logs <application> <service>","Read ordered service logs").option("--lines <count>","Tail line count",{default:"200"}).option("--env <environment>","Target environment").action((e,t,n)=>Gi(e,n.env,(i)=>Gc(i.manifest,t,Number(n.lines)||200))),r.command("compose:shell <application> <service> [command...]","Run a command in one service").option("--env <environment>","Target environment").action((e,t,n,i)=>Gi(e,i.env,(o)=>Kc(o.manifest,t,n?.length?n:["sh"]))),r.command("compose:apply <application>","Internal durable Compose runtime action").option("--action <action>","Runtime action").option("--env <environment>","Target environment").option("--service <service>","Service name").option("--replicas <count>","Replica count").option("--remove-volumes","Remove named volumes").action((e,t)=>Gi(e,t.env,(n)=>{if(t.action==="scale")return qc(n.manifest,t.service??"",Number(t.replicas));return Wc(n.manifest,t.action??"deploy",{service:t.service,removeVolumes:t.removeVolumes})}))}import{readFileSync as Mm}from"node:fs";async function De(){let r=await P(),e=q(process.cwd(),r);return{controlPlane:e,releases:new Zc(e.store)}}function Hi(r){if(!r)return{};return JSON.parse(Mm(r,"utf8"))}function Ji(r){r.command("release:artifact <uri>","Register a verified content-addressed artifact").option("--digest <sha256>","Required sha256: digest").option("--kind <kind>","Deployable kind").option("--size <bytes>","Artifact size",{default:"0"}).option("--media-type <type>","Artifact media type").action(async(e,t)=>{let n=await De();try{let i=n.releases.releases.registerArtifact({organizationId:n.controlPlane.organization.id,digest:String(t.digest??""),kind:t.kind??"compute",uri:e,size:Number(t.size),mediaType:t.mediaType});h(`Artifact registered: ${i.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("release:create <resource> <artifact>","Create an immutable release record").option("--env <environment>","Target environment",{default:"production"}).option("--kind <kind>","Deployable kind").option("--source-sha <sha>","Exact source commit").option("--strategy <strategy>","Activation strategy",{default:"atomic"}).option("--manifest <file>","Immutable manifest JSON").option("--config <file>","Resolved non-secret config JSON").option("--health-path <path>","HTTP health path").option("--approval","Require an environment approval").option("--trigger <trigger>","Build trigger",{default:"manual"}).action(async(e,t,n)=>{let i=await De();try{let o=i.controlPlane.environments.get(n.env??"production"),s=o?i.controlPlane.store.listResources(i.controlPlane.project.id,o.id).find((l)=>l.slug===e):void 0;if(!o||!s)throw Error("Release resource was not found");let c=i.releases.releases.create({organizationId:i.controlPlane.organization.id,projectId:i.controlPlane.project.id,environmentId:o.id,resourceId:s.id,artifactId:t,kind:n.kind??"compute",sourceSha:n.sourceSha,config:Hi(n.config),manifest:Hi(n.manifest),strategy:n.strategy??"atomic",healthGate:n.healthPath?{protocol:"http",path:n.healthPath,timeoutSeconds:5,intervalSeconds:5,healthyThreshold:2,unhealthyThreshold:2}:void 0,approvalRequired:n.approval,trigger:n.trigger});h(`Release created: ${c.id} (${c.status})`)}catch(o){p(o instanceof Error?o.message:String(o)),process.exitCode=1}finally{i.controlPlane.store.close()}}),r.command("release:list","List unified release history").option("--env <environment>","Filter environment").action(async(e)=>{let t=await De();try{let n=e.env?t.controlPlane.environments.get(e.env):void 0;x(["ID","Resource","Status","Kind","Artifact","Strategy","Source","Created"],t.releases.releases.list({projectId:t.controlPlane.project.id,environmentId:n?.id}).map((i)=>[i.id,t.controlPlane.store.getResource(i.resourceId)?.slug??i.resourceId,i.status,i.kind,i.artifactId.slice(0,12),i.strategy,i.sourceSha?.slice(0,12)??"—",i.createdAt]))}finally{t.controlPlane.store.close()}}),r.command("release:show <release>","Show provenance, health, approvals, and transitions").action(async(e)=>{let t=await De();try{let n=t.releases.releases.get(e);if(!n)throw Error("Release was not found");a(JSON.stringify({release:n,artifact:t.releases.releases.getArtifact(n.artifactId),approvals:t.releases.releases.approvals(e),transitions:t.releases.releases.transitions(e)},null,2))}finally{t.controlPlane.store.close()}}),r.command("release:compare <left> <right>","Compare artifact, source, config, manifest, and data caveats").action(async(e,t)=>{let n=await De();try{let i=n.releases.releases.compare(e,t);a(JSON.stringify(i,null,2))}finally{n.controlPlane.store.close()}}),r.command("release:capabilities <kind>","Explain supported deployment strategies").option("--health","Readiness gate is configured").option("--replicas <count>","Replica count",{default:"1"}).action((e,t)=>x(["Strategy","Supported","Capacity","Cost","Explanation","Rollback"],Yc({kind:e,hasHealthGate:!!t.health,replicas:Number(t.replicas)}).map((n)=>[n.strategy,n.supported?"yes":"no",`${n.capacityMultiplier}x`,n.costImpact,n.explanation,n.rollback]))),r.command("release:promote <release> <resource>","Promote the same artifact without rebuilding").option("--env <environment>","Target environment").option("--config <file>","Target non-secret config JSON").option("--approval","Require approval").action(async(e,t,n)=>{let i=await De();try{let o=i.controlPlane.environments.get(String(n.env??"")),s=o?i.controlPlane.store.listResources(i.controlPlane.project.id,o.id).find((l)=>l.slug===t):void 0;if(!o||!s)throw Error("Promotion target was not found");let c=i.releases.releases.promote(e,{targetEnvironmentId:o.id,targetResourceId:s.id,config:Hi(n.config),approvalRequired:n.approval});h(`Promoted without rebuild: ${c.id} (${c.status})`)}catch(o){p(o instanceof Error?o.message:String(o)),process.exitCode=1}finally{i.controlPlane.store.close()}}),r.command("release:approve <release>","Approve or reject an environment gate").option("--actor <id>","Approving actor ID").option("--reject","Reject instead").option("--comment <text>","Approval comment").action(async(e,t)=>{let n=await De();try{if(!t.actor)throw Error("--actor is required");let i=n.releases.releases.approve(e,{actorId:t.actor,decision:t.reject?"rejected":"approved",comment:t.comment});h(`Release is ${i.status}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("release:activate <release>","Queue immutable artifact activation").action(async(e)=>{let t=await De();try{let n=t.releases.releases.get(e);if(!n)throw Error("Release was not found");let i=t.releases.enqueueActivation(n);h(`Activation queued: ${i.id}`)}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}finally{t.controlPlane.store.close()}}),r.command("release:rollback <release>","Restore the preserved prior immutable release").option("--to <release>","Explicit preserved release ID").action(async(e,t)=>{let n=await De();try{let i=n.releases.releases.get(e);if(!i)throw Error("Release was not found");let o=n.releases.enqueueRollback(i,{targetReleaseId:t.to});h(`Rollback queued: ${o.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("release:health <release>","Record activation health and trigger bounded rollback").option("--healthy","Health gate passed").option("--operation <id>","Activation operation ID").option("--message <text>","Health summary").action(async(e,t)=>{let n=await De();try{let i=n.releases.completeHealthGate(e,{healthy:!!t.healthy,operationId:t.operation,message:t.message});h(i.rollbackOperation?`Health failed; rollback queued: ${i.rollbackOperation.id}`:`Release is ${i.release.status}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("release:pin <release>","Pin or unpin a release against retention").option("--remove","Remove pin").option("--reason <text>","Pin reason").action(async(e,t)=>{let n=await De();try{let i=n.releases.releases.pin(e,!t.remove,t.reason);h(i.pinned?"Release pinned.":"Release unpinned.")}finally{n.controlPlane.store.close()}})}import{existsSync as Bm}from"node:fs";function Yi(r){r.command("assets:build","Run the configured build hooks for an environment").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).action(async(e)=>{w("Building Assets");try{let t=await P(),n=e?.env||"production",o=t.environments?.[n]?.app?.build??[];if(!o.length){p(`No build hooks defined for '${n}'. Set environments.${n}.app.build = ['bun run build', …] in cloud.config.`),process.exitCode=1;return}let{execSync:s}=await import("node:child_process");for(let c of o)M(`$ ${c}`),s(c,{stdio:"inherit"});h("Build hooks complete")}catch(t){p(`Build failed: ${t.message}`),process.exitCode=1}});for(let e of["assets:optimize:images","images:optimize"])r.command(e,"Optimize images (not implemented)").option("--dir <directory>","Directory to optimize").action(async()=>{p(`'${e}' is not implemented — ts-cloud has no built-in image optimizer.`),a("Optimize images in your own build step (e.g. sharp/squoosh), then `cloud assets:deploy`."),process.exitCode=1});r.command("assets:deploy","Deploy static assets to S3").option("--source <path>","Source directory",{default:"dist"}).option("--bucket <name>","S3 bucket name").option("--prefix <prefix>","S3 prefix/folder").option("--delete","Delete files not in source").option("--cache-control <value>","Cache-Control header",{default:"public, max-age=31536000"}).action(async(e)=>{w("Deploying Assets to S3");try{let n=(await P()).project.region||"us-east-1",i=e?.source||"dist",o=e?.bucket,s=e?.prefix,c=e?.delete||!1,l=e?.cacheControl||"public, max-age=31536000";if(!o){p("--bucket is required");return}if(!Bm(i)){p(`Source directory not found: ${i}`);return}if(a(`Source: ${i}`),a(`Bucket: s3://${o}${s?`/${s}`:""}`),a(`Cache-Control: ${l}`),c)R("Delete mode enabled - files not in source will be removed");if(!await O(`
|
|
369
|
+
${u.length} difference(s)`)}catch(n){p(`Comparison failed: ${n.message}`),process.exitCode=1}}),r.command("env:pull","Download a serverless function's environment to a .env file").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Which function: http | queue | cli",{default:"http"}).option("--file <path>","Output file (default .env.<environment>)").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{region:i,functions:o}=bt(t,n),s=e?.function??"http",d=(await new nt(i).getFunction(o[s])).Configuration?.Environment?.Variables??{},u=e?.file??`.env.${n}`;Lm(u,Fm(d)),h(`Wrote ${Object.keys(d).length} variable(s) from ${o[s]} → ${u}`)}catch(t){p(`env:pull failed: ${t.message}`),process.exitCode=1}}),r.command("env:push","Upload a .env file to a serverless function's configuration").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).option("--function <which>","Function: http | queue | cli | all",{default:"all"}).option("--file <path>","Input file (default .env.<environment>)").option("--replace","Replace the entire env instead of merging over the live config").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{region:i,functions:o}=bt(t,n),s=e?.file??`.env.${n}`;if(!Nm(s)){p(`File not found: ${s}`),process.exitCode=1;return}let c=jm(Om(s,"utf8")),l=(e?.function??"all").toLowerCase();if(l!=="all"&&!["http","queue","cli"].includes(l)){p("--function must be one of: http, queue, cli, all"),process.exitCode=1;return}let d=l==="all"?["http","queue","cli"]:[l],u=(t.environments?.[n]?.app?.provisionedConcurrency??0)>0,m=new nt(i);for(let f of d){let y=o[f],S=c;if(!e?.replace)S={...(await m.getFunction(y)).Configuration?.Environment?.Variables??{},...c};if(Mc(y,S),await m.updateFunctionConfiguration({FunctionName:y,Environment:{Variables:S}}),await m.waitForFunctionActive(y,120),u){let g=await m.publishVersion({FunctionName:y});if(g.Version)await m.updateAlias({FunctionName:y,Name:"live",FunctionVersion:g.Version})}h(`Pushed ${Object.keys(c).length} var(s) → ${y}${e?.replace?" (replaced)":" (merged)"}${u?" (published + flipped live alias)":""}`)}a("Note: a subsequent `cloud deploy:serverless` re-injects infra/secret env from config.")}catch(t){p(`env:push failed: ${t.message}`),process.exitCode=1}}),r.command("env:create <name>","Create new environment").action(async(e)=>{Wi(`env:create ${e}`,"Add an `environments.<name>` block to your cloud.config and run `cloud deploy:serverless --env <name>`.")}),r.command("env:switch <name>","Switch active environment").action(async(e)=>{Wi(`env:switch ${e}`,'ts-cloud has no persistent "active" environment — pass --env <name> to each command.')});for(let e of["clone","promote","sync"])r.command(`env:${e} <source> <target>`,`(${e}) environment configuration`).action(async(t,n)=>{Wi(`env:${e} ${t} ${n}`,`Copy the \`environments.${t}\` block to \`environments.${n}\` in cloud.config, then \`cloud deploy:serverless --env ${n}\`.`)});r.command("env:preview <branch>","Create, inspect, rebuild, extend, or destroy an immutable preview").option("--sha <commit>","Exact 40-64 character source commit").option("--pr <number>","Pull request number").option("--site <name>","Application site (defaults to the first site)").option("--env <environment>","Base environment",{default:"production"}).option("--domain <pattern>","HTTPS URL pattern containing {name}").option("--ttl <hours>","Preview lifetime in hours").option("--destroy","Queue teardown for this preview").option("--rebuild","Rebuild the currently recorded commit").option("--extend <hours>","Extend expiry from now/current expiry").option("--get-url","Print the stable preview URL without mutation").option("--yes","Skip destructive confirmation").action(async(e,t)=>{let n;try{let i=await P(),o=t.env||"production",s=t.site||Object.keys(i.sites??{})[0];if(!s)throw Error("No application site is configured; pass --site after adding one");n=q(process.cwd(),i);let c=n.environments.get(o),l=c?n.store.listResources(n.project.id,c.id).find((g)=>g.kind==="application"&&g.slug===s):void 0;if(!c||!l)throw Error(`Application ${s} was not found in base environment ${o}`);let d=new nr(n.store),u=d.previews.getDefinitionForResource(l.id);if(!u){let g=i.sites?.[s]?.domain,b=t.domain||(g?`https://{name}.preview.${g}`:void 0);if(!b)throw Error("Pass --domain with an HTTPS pattern containing {name} to configure previews");u=d.previews.createDefinition({projectId:n.project.id,resourceId:l.id,baseEnvironmentId:c.id,domainPattern:b,ttlHours:Number(t.ttl)||void 0})}let m=Number(t.pr)||void 0,f=m?d.previews.findForPullRequest(u.id,"local",m):d.previews.findForBranch(u.id,"local",e);if(t.getUrl){if(!f)throw Error("Preview was not found");a(f.url??"URL unavailable");return}if(t.extend){if(!f)throw Error("Preview was not found");let g=d.previews.extend(f.id,Number(t.extend));h(`Extended ${g.name} until ${g.expiresAt}`);return}if(t.destroy){if(!f)throw Error("Preview was not found");if(!t.yes&&!await O(`Destroy ${f.name} and only its tagged resources?`,!1))return;let g=d.enqueueDestroy(f,"manual");h(`Preview teardown queued: ${g.id}`);return}if(t.rebuild){if(!f)throw Error("Preview was not found");let g=d.enqueueDeploy(f,{reason:"manual_rebuild"});h(`Preview rebuild queued: ${g.id}`);return}if(!t.sha)throw Error("Pass --sha with the exact immutable commit to deploy");let y=d.previews.upsert({definitionId:u.id,repository:"local",branch:e,pullRequestNumber:m,commitSha:t.sha}),S=d.enqueueDeploy(y.preview,{created:y.created});h(`${y.created?"Preview creation":"Preview update"} queued: ${S.id}`),a(`${y.preview.url} · expires ${y.preview.expiresAt}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n?.store.close()}}),r.command("env:cleanup","Remove stale preview environments").option("--dry-run","List cleanup candidates without queuing teardown").option("--max-age <hours>","Also remove previews older than this age").option("--keep <count>","Keep this many newest previews per application").action(async(e)=>{let t;try{let n=await P();t=q(process.cwd(),n);let o=new nr(t.store).cleanup({dryRun:e.dryRun,maxAgeHours:Number(e.maxAge)||void 0,keepCount:Number(e.keep)||void 0});x(["Preview","Status","Expires","Reason"],o.candidates.map((s)=>[s.preview.name,s.preview.status,s.preview.expiresAt,s.reasons.join(", ")])),h(e.dryRun?`${o.candidates.length} cleanup candidate(s)`:`${o.operations.length} teardown job(s) queued`)}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}finally{t?.store.close()}}),r.command("env:previews","List persistent preview environments").option("--site <name>","Filter application site").action(async(e)=>{let t;try{let n=await P();t=q(process.cwd(),n);let i=new nr(t.store),o=e.site?new Set(t.store.listResources(t.project.id).filter((c)=>c.slug===e.site).map((c)=>c.id)):void 0,s=i.previews.listInstances({projectId:t.project.id}).filter((c)=>!o||o.has(c.resourceId));x(["ID","Name","Status","Branch / PR","Commit","URL","Expires","Cost"],s.map((c)=>[c.id,c.name,c.status,c.pullRequestNumber?`PR #${c.pullRequestNumber}`:c.branch,c.commitSha.slice(0,12),c.url??"—",c.expiresAt,c.costEstimate==null?"—":`$${c.costEstimate.toFixed(2)}`]))}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}finally{t?.store.close()}})}import{readFileSync as rr}from"node:fs";async function Ee(){let r=await P(),e=q(process.cwd(),r);return{config:r,controlPlane:e,applications:new Hc(e.store)}}async function Gi(r,e,t){let n=await Ee();try{let i=n.applications.get(r);if(!i)throw Error("Compose application was not found");let o=e??n.controlPlane.store.listEnvironments(i.projectId).find((c)=>c.id===i.environmentId)?.slug;if(!o)throw Error("Compose environment was not found");let s=await Jc(n.config,o,t(i));if(!s.ok)throw Error(s.stderr||s.error||"Remote Compose command failed");if(s.stdout)a(s.stdout)}finally{n.controlPlane.store.close()}}function Ki(r){r.command("compose:preview <file>","Parse Compose without persistence or provider mutation").option("--name <name>","Application name",{default:"Compose application"}).option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{let n=await Ee();try{let i=n.controlPlane.environments.get(t.env??"production");if(!i)throw Error("Environment was not found");let o=n.applications.preview(rr(e,"utf8"),{name:t.name??"Compose application",projectId:n.controlPlane.project.id,environmentId:i.id});if(x(["Severity","Path","Code","Message"],o.diagnostics.map((s)=>[s.severity,s.path,s.code,s.message])),!o.valid)process.exitCode=1;else h(`${Object.keys(o.manifest.spec.services).length} services are valid`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:import <file>","Import and persist a validated Compose application").option("--name <name>","Application name").option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{let n=await Ee();try{let i=n.controlPlane.environments.get(t.env??"production");if(!i)throw Error("Environment was not found");let o=t.name??e.split("/").at(-2)??"Compose application",s=n.applications.import(rr(e,"utf8"),{name:o,projectId:n.controlPlane.project.id,environmentId:i.id});h(`Imported ${s.application.name} (${s.application.id})`),a(`${Object.keys(s.application.manifest.spec.services).length} services · ${s.parsed.diagnostics.length} diagnostic(s)`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:templates","List pinned verified Compose templates").action(()=>x(["ID","Version","Category","Architecture","Verified"],Uc().map((e)=>[e.id,e.version,e.category,e.architecture,e.lastVerifiedAt]))),r.command("compose:catalog <file>","Validate and inspect a local/custom template catalog").option("--env <environment>","Target environment",{default:"production"}).action(async(e,t)=>{let n=await Ee();try{let i=n.controlPlane.environments.get(t.env??"production");if(!i)throw Error("Environment was not found");let o=Vc(rr(e,"utf8"),{projectId:n.controlPlane.project.id,environmentId:i.id});if(x(["ID","Version","Category","Services","Checksum"],o.templates.map((s)=>[s.template.id,s.template.version,s.template.category,String(Object.keys(s.parsed.manifest.spec.services).length),s.template.checksum.slice(0,12)])),o.diagnostics.length)x(["Severity","Path","Message"],o.diagnostics.map((s)=>[s.severity,s.path,s.message]));if(!o.valid)process.exitCode=1}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:template <template>","Create an editable application from a template").option("--name <name>","Application name").option("--domain <domain>","Primary domain").option("--env <environment>","Target environment",{default:"production"}).option("--version <version>","Pinned template version").action(async(e,t)=>{let n=await Ee();try{let i=n.controlPlane.environments.get(t.env??"production");if(!i)throw Error("Environment was not found");let o=n.applications.fromTemplate(e,{domain:t.domain??""},{name:t.name??e,projectId:n.controlPlane.project.id,environmentId:i.id,version:t.version});h(`Created ${o.application.name} from ${e}@${o.application.templateVersion}`),a(`Export with cloud compose:export ${o.application.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:list","List Compose applications").action(async()=>{let e=await Ee();try{x(["ID","Name","Status","Services","Source","Updated"],e.applications.list({projectId:e.controlPlane.project.id}).map((t)=>[t.id,t.name,t.status,String(Object.keys(t.manifest.spec.services).length),t.templateId?`${t.templateId}@${t.templateVersion}`:"compose",t.updatedAt]))}finally{e.controlPlane.store.close()}}),r.command("compose:export <application>","Print a normalized editable Compose file").action(async(e)=>{let t=await Ee();try{let n=t.applications.get(e);if(!n)throw Error("Compose application was not found");a(_c(n.manifest))}finally{t.controlPlane.store.close()}}),r.command("compose:diff <application> <file>","Compare persisted and proposed Compose manifests").action(async(e,t)=>{let n=await Ee();try{let i=n.applications.get(e);if(!i)throw Error("Compose application was not found");let o=Bc(rr(t,"utf8"),i.manifest.metadata);x(["Path","Before","After"],zc(i.manifest,o.manifest).map((s)=>[s.path,JSON.stringify(s.before),JSON.stringify(s.after)]))}finally{n.controlPlane.store.close()}});for(let e of["deploy","redeploy","start","stop"])r.command(`compose:${e} <application>`,`${e} a Compose application through the durable queue`).action(async(t)=>{let n=await Ee();try{let i=n.applications.get(t);if(!i)throw Error("Compose application was not found");let o=new ir(n.controlPlane.store).enqueue(i,e);h(`${e} queued: ${o.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}});r.command("compose:scale <application> <service> <replicas>","Scale one capable Compose service").action(async(e,t,n)=>{let i=await Ee();try{let o=i.applications.get(e);if(!o)throw Error("Compose application was not found");let s=new ir(i.controlPlane.store).enqueue(o,"scale",{service:t,replicas:Number(n)});h(`Scale queued: ${s.id}`)}catch(o){p(o instanceof Error?o.message:String(o)),process.exitCode=1}finally{i.controlPlane.store.close()}}),r.command("compose:delete <application>","Delete a Compose stack; named volumes are preserved by default").option("--remove-volumes","Also permanently remove named volumes").option("--confirm <text>","Exact application confirmation").action(async(e,t)=>{let n=await Ee();try{let i=n.applications.get(e);if(!i)throw Error("Compose application was not found");let o=new ir(n.controlPlane.store).enqueue(i,"delete",{removeVolumes:t.removeVolumes,confirmation:t.confirm});h(`Deletion queued: ${o.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("compose:logs <application> <service>","Read ordered service logs").option("--lines <count>","Tail line count",{default:"200"}).option("--env <environment>","Target environment").action((e,t,n)=>Gi(e,n.env,(i)=>Gc(i.manifest,t,Number(n.lines)||200))),r.command("compose:shell <application> <service> [command...]","Run a command in one service").option("--env <environment>","Target environment").action((e,t,n,i)=>Gi(e,i.env,(o)=>Kc(o.manifest,t,n?.length?n:["sh"]))),r.command("compose:apply <application>","Internal durable Compose runtime action").option("--action <action>","Runtime action").option("--env <environment>","Target environment").option("--service <service>","Service name").option("--replicas <count>","Replica count").option("--remove-volumes","Remove named volumes").action((e,t)=>Gi(e,t.env,(n)=>{if(t.action==="scale")return qc(n.manifest,t.service??"",Number(t.replicas));return Wc(n.manifest,t.action??"deploy",{service:t.service,removeVolumes:t.removeVolumes})}))}import{readFileSync as Mm}from"node:fs";async function De(){let r=await P(),e=q(process.cwd(),r);return{controlPlane:e,releases:new Zc(e.store)}}function Hi(r){if(!r)return{};return JSON.parse(Mm(r,"utf8"))}function Ji(r){r.command("release:artifact <uri>","Register a verified content-addressed artifact").option("--digest <sha256>","Required sha256: digest").option("--kind <kind>","Deployable kind").option("--size <bytes>","Artifact size",{default:"0"}).option("--media-type <type>","Artifact media type").action(async(e,t)=>{let n=await De();try{let i=n.releases.releases.registerArtifact({organizationId:n.controlPlane.organization.id,digest:String(t.digest??""),kind:t.kind??"compute",uri:e,size:Number(t.size),mediaType:t.mediaType});h(`Artifact registered: ${i.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("release:create <resource> <artifact>","Create an immutable release record").option("--env <environment>","Target environment",{default:"production"}).option("--kind <kind>","Deployable kind").option("--source-sha <sha>","Exact source commit").option("--strategy <strategy>","Activation strategy",{default:"atomic"}).option("--manifest <file>","Immutable manifest JSON").option("--config <file>","Resolved non-secret config JSON").option("--health-path <path>","HTTP health path").option("--approval","Require an environment approval").option("--trigger <trigger>","Build trigger",{default:"manual"}).action(async(e,t,n)=>{let i=await De();try{let o=i.controlPlane.environments.get(n.env??"production"),s=o?i.controlPlane.store.listResources(i.controlPlane.project.id,o.id).find((l)=>l.slug===e):void 0;if(!o||!s)throw Error("Release resource was not found");let c=i.releases.releases.create({organizationId:i.controlPlane.organization.id,projectId:i.controlPlane.project.id,environmentId:o.id,resourceId:s.id,artifactId:t,kind:n.kind??"compute",sourceSha:n.sourceSha,config:Hi(n.config),manifest:Hi(n.manifest),strategy:n.strategy??"atomic",healthGate:n.healthPath?{protocol:"http",path:n.healthPath,timeoutSeconds:5,intervalSeconds:5,healthyThreshold:2,unhealthyThreshold:2}:void 0,approvalRequired:n.approval,trigger:n.trigger});h(`Release created: ${c.id} (${c.status})`)}catch(o){p(o instanceof Error?o.message:String(o)),process.exitCode=1}finally{i.controlPlane.store.close()}}),r.command("release:list","List unified release history").option("--env <environment>","Filter environment").action(async(e)=>{let t=await De();try{let n=e.env?t.controlPlane.environments.get(e.env):void 0;x(["ID","Resource","Status","Kind","Artifact","Strategy","Source","Created"],t.releases.releases.list({projectId:t.controlPlane.project.id,environmentId:n?.id}).map((i)=>[i.id,t.controlPlane.store.getResource(i.resourceId)?.slug??i.resourceId,i.status,i.kind,i.artifactId.slice(0,12),i.strategy,i.sourceSha?.slice(0,12)??"—",i.createdAt]))}finally{t.controlPlane.store.close()}}),r.command("release:show <release>","Show provenance, health, approvals, and transitions").action(async(e)=>{let t=await De();try{let n=t.releases.releases.get(e);if(!n)throw Error("Release was not found");a(JSON.stringify({release:n,artifact:t.releases.releases.getArtifact(n.artifactId),approvals:t.releases.releases.approvals(e),transitions:t.releases.releases.transitions(e)},null,2))}finally{t.controlPlane.store.close()}}),r.command("release:compare <left> <right>","Compare artifact, source, config, manifest, and data caveats").action(async(e,t)=>{let n=await De();try{let i=n.releases.releases.compare(e,t);a(JSON.stringify(i,null,2))}finally{n.controlPlane.store.close()}}),r.command("release:capabilities <kind>","Explain supported deployment strategies").option("--health","Readiness gate is configured").option("--replicas <count>","Replica count",{default:"1"}).action((e,t)=>x(["Strategy","Supported","Capacity","Cost","Explanation","Rollback"],Yc({kind:e,hasHealthGate:!!t.health,replicas:Number(t.replicas)}).map((n)=>[n.strategy,n.supported?"yes":"no",`${n.capacityMultiplier}x`,n.costImpact,n.explanation,n.rollback]))),r.command("release:promote <release> <resource>","Promote the same artifact without rebuilding").option("--env <environment>","Target environment").option("--config <file>","Target non-secret config JSON").option("--approval","Require approval").action(async(e,t,n)=>{let i=await De();try{let o=i.controlPlane.environments.get(String(n.env??"")),s=o?i.controlPlane.store.listResources(i.controlPlane.project.id,o.id).find((l)=>l.slug===t):void 0;if(!o||!s)throw Error("Promotion target was not found");let c=i.releases.releases.promote(e,{targetEnvironmentId:o.id,targetResourceId:s.id,config:Hi(n.config),approvalRequired:n.approval});h(`Promoted without rebuild: ${c.id} (${c.status})`)}catch(o){p(o instanceof Error?o.message:String(o)),process.exitCode=1}finally{i.controlPlane.store.close()}}),r.command("release:approve <release>","Approve or reject an environment gate").option("--actor <id>","Approving actor ID").option("--reject","Reject instead").option("--comment <text>","Approval comment").action(async(e,t)=>{let n=await De();try{if(!t.actor)throw Error("--actor is required");let i=n.releases.releases.approve(e,{actorId:t.actor,decision:t.reject?"rejected":"approved",comment:t.comment});h(`Release is ${i.status}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("release:activate <release>","Queue immutable artifact activation").action(async(e)=>{let t=await De();try{let n=t.releases.releases.get(e);if(!n)throw Error("Release was not found");let i=t.releases.enqueueActivation(n);h(`Activation queued: ${i.id}`)}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}finally{t.controlPlane.store.close()}}),r.command("release:rollback <release>","Restore the preserved prior immutable release").option("--to <release>","Explicit preserved release ID").action(async(e,t)=>{let n=await De();try{let i=n.releases.releases.get(e);if(!i)throw Error("Release was not found");let o=n.releases.enqueueRollback(i,{targetReleaseId:t.to});h(`Rollback queued: ${o.id}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("release:health <release>","Record activation health and trigger bounded rollback").option("--healthy","Health gate passed").option("--operation <id>","Activation operation ID").option("--message <text>","Health summary").action(async(e,t)=>{let n=await De();try{let i=n.releases.completeHealthGate(e,{healthy:!!t.healthy,operationId:t.operation,message:t.message});h(i.rollbackOperation?`Health failed; rollback queued: ${i.rollbackOperation.id}`:`Release is ${i.release.status}`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}finally{n.controlPlane.store.close()}}),r.command("release:pin <release>","Pin or unpin a release against retention").option("--remove","Remove pin").option("--reason <text>","Pin reason").action(async(e,t)=>{let n=await De();try{let i=n.releases.releases.pin(e,!t.remove,t.reason);h(i.pinned?"Release pinned.":"Release unpinned.")}finally{n.controlPlane.store.close()}})}import{existsSync as Bm}from"node:fs";function Yi(r){r.command("assets:build","Run the configured build hooks for an environment").option("--env <environment>","Environment (production, staging, development)",{default:"production"}).action(async(e)=>{w("Building Assets");try{let t=await P(),n=e?.env||"production",o=t.environments?.[n]?.app?.build??[];if(!o.length){p(`No build hooks defined for '${n}'. Set environments.${n}.app.build = ['bun run build', …] in cloud.config.`),process.exitCode=1;return}let{execSync:s}=await import("node:child_process");for(let c of o)M(`$ ${c}`),s(c,{stdio:"inherit"});h("Build hooks complete")}catch(t){p(`Build failed: ${t.message}`),process.exitCode=1}});for(let e of["assets:optimize:images","images:optimize"])r.command(e,"Optimize images (not implemented)").option("--dir <directory>","Directory to optimize").action(async()=>{p(`'${e}' is not implemented — ts-cloud has no built-in image optimizer.`),a("Optimize images in your own build step (e.g. sharp/squoosh), then `cloud assets:deploy`."),process.exitCode=1});r.command("assets:deploy","Deploy static assets to S3").option("--source <path>","Source directory",{default:"dist"}).option("--bucket <name>","S3 bucket name").option("--prefix <prefix>","S3 prefix/folder").option("--delete","Delete files not in source").option("--cache-control <value>","Cache-Control header",{default:"public, max-age=31536000"}).action(async(e)=>{w("Deploying Assets to S3");try{let n=(await P()).project.region||"us-east-1",i=e?.source||"dist",o=e?.bucket,s=e?.prefix,c=e?.delete||!1,l=e?.cacheControl||"public, max-age=31536000";if(!o){p("--bucket is required");return}if(!Bm(i)){p(`Source directory not found: ${i}`);return}if(a(`Source: ${i}`),a(`Bucket: s3://${o}${s?`/${s}`:""}`),a(`Cache-Control: ${l}`),c)R("Delete mode enabled - files not in source will be removed");if(!await O(`
|
|
370
370
|
Deploy assets now?`,!0)){a("Deployment cancelled");return}let u=new de(n),m=new A("Uploading assets to S3...");m.start(),await u.sync({source:i,bucket:o,prefix:s,delete:c,cacheControl:l,acl:"public-read"}),m.succeed("Assets deployed successfully!");let y=(await u.getBucketSize(o,s)/1024/1024).toFixed(2);h(`
|
|
371
371
|
Deployment complete!`),a(`Total size: ${y} MB`),a(`
|
|
372
372
|
Assets URL: https://${o}.s3.${n}.amazonaws.com${s?`/${s}`:""}`)}catch(t){p(`Deployment failed: ${t.message}`)}}),r.command("assets:invalidate","Invalidate CloudFront cache").option("--distribution <id>","CloudFront distribution ID").option("--paths <paths>","Paths to invalidate (comma-separated)",{default:"/*"}).option("--wait","Wait for invalidation to complete").action(async(e)=>{w("Invalidating CloudFront Cache");try{let t=e?.distribution;if(!t){p("--distribution is required");return}let i=(e?.paths||"/*").split(",").map((u)=>u.trim()),o=e?.wait||!1;if(a(`Distribution: ${t}`),a(`Paths: ${i.join(", ")}`),!await O(`
|
|
373
373
|
Invalidate cache now?`,!0)){a("Invalidation cancelled");return}let c=new ne,l=new A("Creating invalidation...");l.start();let d=await c.invalidatePaths(t,i);if(l.succeed("Invalidation created"),h(`
|
|
374
374
|
Invalidation ID: ${d.Id}`),a(`Status: ${d.Status}`),a(`Created: ${new Date(d.CreateTime).toLocaleString()}`),o){let u=new A("Waiting for invalidation to complete...");u.start(),await c.waitForInvalidation(t,d.Id),u.succeed("Invalidation completed!")}else a(`
|
|
375
|
-
Invalidation is in progress. Use --wait to wait for completion.`)}catch(t){p(`Invalidation failed: ${t.message}`)}})}function Zi(r){for(let[e,t]of[["team:add <email> <role>","Invite an organization member"],["team:list","List organization members"],["team:remove <email>","Revoke organization membership"]])r.command(e,t).action(async(...n)=>be(e.split(" ")[0],{target:String(n[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 Hm,execSync as Po}from"node:child_process";import{copyFileSync as Cn,existsSync as ge,mkdirSync as yl,readFileSync as go,writeFileSync as Jm}from"node:fs";import{tmpdir as vl}from"node:os";import{dirname as Ym,join as bl,resolve as Zm}from"node:path";import{existsSync as Qi,readdirSync as Qc,statSync as Xi}from"node:fs";import{join as or}from"node:path";var _m=new Set([".DS_Store","Thumbs.db",".gitkeep",".keep"]);function Xc(r){return _m.has(r)}function el(r,e=0){if(e>8)return!0;let t;try{t=Qc(r)}catch{return!1}for(let n of t){if(Xc(n))continue;let i=or(r,n),o;try{o=Xi(i)}catch{continue}if(o.isFile())return!0;if(o.isDirectory()&&el(i,e+1))return!0}return!1}function zm(r){let e;try{e=Qc(r)}catch{return[]}let t=[];for(let n of e){if(Xc(n))continue;let i=or(r,n);try{if(Xi(i).isDirectory()&&Qi(or(i,"index.html")))t.push(n)}catch{}}return t.sort((n,i)=>n.localeCompare(i))}function tl(r){let{root:e,siteName:t,expectIndex:n}=r,i=[];if(!Qi(e))return i.push({level:"error",message:`Build output not found at ${e} for site '${t}'`}),i;if(!Xi(e).isDirectory())return i.push({level:"error",message:`Site '${t}' has root ${e}, which is a file, not a directory`}),i;if(!el(e))return i.push({level:"error",message:`Site '${t}' would ship an empty release: ${e} contains no files. Check that the build ran and wrote where 'root' points.`}),i;if(!n)return i;if(Qi(or(e,"index.html")))return i;let o=zm(e);if(o.length===1){let s=`${e.replace(/\/+$/,"")}/${o[0]}`;return i.push({level:"error",message:`Site '${t}' has no index.html at the top of ${e}, but ${s} has one. The generator wrote one level down, so this release would serve 404s at the domain root. Set root: '${s}'.`}),i}if(o.length>1)return i.push({level:"warning",message:`Site '${t}' has no index.html at the top of ${e}. These subdirectories have one: ${o.join(", ")}. The domain root will 404.`}),i;return i.push({level:"warning",message:`Site '${t}' has no index.html anywhere under ${e}. The domain root will 404.`}),i}function Um(r){if(!r)return[];if(Array.isArray(r))return r.map(String);let e=r,t=e.Items??e.Item;if(!t)return[];if(Array.isArray(t))return t.map(String);if(typeof t==="object"&&t!==null){let n=t;if("Item"in n){let i=n.Item;if(!i)return[];return Array.isArray(i)?i.map(String):[String(i)]}if("CNAME"in n){let i=n.CNAME;if(!i)return[];return Array.isArray(i)?i.map(String):[String(i)]}return Object.values(n).flatMap((i)=>{if(Array.isArray(i))return i.map(String);return typeof i==="string"?[i]:[]})}return[String(t)]}async function sr(r){if(r.length===0)return;let e=new ne,t=await e.listDistributions(),n=[...new Set(r.map((i)=>i.toLowerCase()))];for(let i of n){let o=t.find((c)=>Um(c.Aliases).some((d)=>d.toLowerCase()===i));if(!o?.Id){R(`No CloudFront distribution found for ${i} — skipping dynamic HTTP method sync`);continue}if(await e.ensureDynamicHttpMethods(o.Id))h(`CloudFront ${o.Id} (${i}): enabled POST/PUT/PATCH/DELETE on dynamic paths`);else a(`CloudFront ${o.Id} (${i}): already allows dynamic HTTP methods`)}}import{execSync as Vm}from"node:child_process";async function Wm(r,e,t,n={},i=process.cwd()){if(!r)return;if(n.step?.(`Running ${t} hook`),typeof r==="string"){Vm(r,{stdio:"inherit",cwd:i});return}await r(e)}async function Sn(r,e,t={},n){try{return await Wm(r.hooks?.[e],r,e,t,n),!0}catch(i){return t.error?.(`${e} hook failed: ${i?.message||i}`),!1}}var nl=["js","mjs","cjs","css","map","woff","woff2","ttf","otf","eot","png","jpg","jpeg","gif","webp","avif","svg","ico","mp4","webm","ogg","mp3","wav","pdf","wasm"];function eo(r){return`"${r.replace(/\\/g,"\\\\").replace(/"/g,"\\\"")}"`}function rl(r){return`{${r.map(eo).join(" ")}}`}function to(r){if(r.length===1)return`http.host eq ${eo(r[0])}`;return`http.host in ${rl(r)}`}function no(r,e={}){if(e.enabled===!1||r.length===0)return[];let t=to(r),n=[],i=e.bypassPaths??[],o=i.length>0?`(${i.map((d)=>`starts_with(http.request.uri.path, ${eo(d)})`).join(" or ")})`:void 0;if(o)n.push({action:"set_cache_settings",description:"bypass cache",expression:`(${t} and ${o})`,enabled:!0,action_parameters:{cache:!1}});let s=o?`${t} and not ${o}`:t,c=e.assetExtensions??nl,l=c.length>0?`lower(http.request.uri.path.extension) in ${rl([...c])}`:void 0;if(l)n.push({action:"set_cache_settings",description:"cache fingerprinted assets",expression:`(${s} and ${l})`,enabled:!0,action_parameters:{cache:!0,edge_ttl:{mode:"override_origin",default:e.assetEdgeTtl??2592000},browser_ttl:{mode:"override_origin",default:e.assetBrowserTtl??31536000}}});return n.push({action:"set_cache_settings",description:"cache documents",expression:l?`(${s} and not ${l})`:`(${s})`,enabled:!0,action_parameters:{cache:!0,edge_ttl:{mode:"override_origin",default:e.documentEdgeTtl??3600},browser_ttl:{mode:"override_origin",default:e.documentBrowserTtl??0}}}),n}function ro(r,e,t){if(r.length===0||!e||!t)return[];return[{action:"rewrite",description:"origin guard header",expression:`(${to(r)})`,enabled:!0,action_parameters:{headers:{[e]:{operation:"set",value:t}}}}]}var ot=(r)=>r===void 0?void 0:r?"on":"off";function io(r){let e={ssl:r.ssl,always_use_https:ot(r.alwaysUseHttps),automatic_https_rewrites:ot(r.automaticHttpsRewrites),min_tls_version:r.minTlsVersion,tls_1_3:ot(r.tls13),brotli:ot(r.brotli),http3:ot(r.http3),"0rtt":ot(r.zeroRtt),early_hints:ot(r.earlyHints),browser_cache_ttl:r.browserCacheTtl,always_online:ot(r.alwaysOnline),websockets:ot(r.websockets)};if(r.hsts)e.security_header={strict_transport_security:{enabled:r.hsts.enabled,max_age:r.hsts.maxAge??31536000,include_subdomains:r.hsts.includeSubdomains??!1,preload:r.hsts.preload??!1,nosniff:r.hsts.noSniff??!0}};for(let t of Object.keys(e))if(e[t]===void 0)delete e[t];return{...e,...r.raw??{}}}var $n={ssl:"strict",alwaysUseHttps:!0,automaticHttpsRewrites:!0,minTlsVersion:"1.2",tls13:!0,brotli:!0,http3:!0,earlyHints:!0,alwaysOnline:!0,browserCacheTtl:0};import{connect as qm}from"node:tls";async function oo(r){let{address:e,serverName:t,port:n=443,timeoutMs:i=5000}=r;return new Promise((o)=>{let s=!1,c=(d)=>{if(s)return;s=!0;try{l.destroy()}catch{}o(d)},l=qm({host:e,port:n,servername:t,rejectUnauthorized:!1,timeout:i});l.once("secureConnect",()=>{if(l.authorized){c({reachable:!0,trusted:!0});return}c({reachable:!0,trusted:!1,reason:l.authorizationError?String(l.authorizationError):"origin certificate is not trusted for this hostname"})}),l.once("timeout",()=>{c({reachable:!1,trusted:!1,reason:`no TLS handshake within ${i}ms`})}),l.once("error",(d)=>{c({reachable:!1,trusted:!1,reason:d.message})})})}function Gm(r,e){let t=e.replace(/\.$/,"").toLowerCase();return r.some((n)=>{let i=n.replace(/\.$/,"").toLowerCase();if(i===t)return!0;if(!i.startsWith("*."))return!1;let o=i.slice(2);if(!t.endsWith(`.${o}`))return!1;return!t.slice(0,t.length-o.length-1).includes(".")})}async function ao(r){let{provider:e,zone:t,hosts:n,ipv4:i,ipv6:o,proxied:s=!0,settings:c=$n,cache:l,originGuard:d,purge:u=!0,ttl:m=300,skipOriginProbe:f=!1,skipEdgeCertificateCheck:y=!1}=r,S={records:[],settingsChanged:[],cacheRules:0,originGuard:!1,purged:!1,deferredProxy:[],warnings:[]};if(n.length===0)return S;let g=[],b=s&&!y?await e.listEdgeCertificateHosts(t):null;for(let v of n){let C=s;if(C&&b&&!Gm(b,v)){C=!1;let T=b.length===0?"the zone has no active edge certificate yet":`no active edge certificate covers it (${b.join(", ")})`;S.deferredProxy.push({host:v,reason:T}),S.warnings.push(`${v}: publishing DNS-only — ${T}. 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(C&&!f){let T=await oo({address:i,serverName:v});if(!T.trusted){C=!1;let E=T.reason||"origin is not serving a trusted certificate";S.deferredProxy.push({host:v,reason:E}),S.warnings.push(`${v}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${E}). The origin issues certificates over HTTP-01, which needs the name to reach it directly; re-run the deploy once it has one and the record will be proxied.`)}}if(C)g.push(v);let k=[{type:"A",content:i}];if(o&&so(v))k.push({type:"AAAA",content:o});for(let{type:T,content:E}of k){let D=await e.upsertRecord(t,{name:v,type:T,content:E,ttl:m,proxied:C});if(D.success)S.records.push({host:v,type:T,content:E,proxied:C});else S.warnings.push(`${v} ${T} → ${E} failed: ${D.message||"unknown provider error"}`)}}if(g.length===0){if(s)S.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");return S}try{let v=io(c),C=await e.applyZoneSettings(t,v);S.settingsChanged=C.changed;for(let k of C.failed)S.warnings.push(`zone setting '${k.id}' could not be applied: ${k.error}`)}catch(v){S.warnings.push(`zone settings could not be read: ${v instanceof Error?v.message:String(v)}`)}let I=no(g,l);if(I.length>0){let v=await e.putManagedPhaseRules(t,"http_request_cache_settings",I);if(v.success)S.cacheRules=I.length;else S.warnings.push(`cache rules could not be written: ${v.message}`)}if(d){let v=ro(g,d.header,d.value);if(v.length>0){let C=await e.putManagedPhaseRules(t,"http_request_late_transform",v);if(C.success)S.originGuard=!0;else S.warnings.push(`origin guard header could not be written: ${C.message}. `+"The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.")}}if(u){if(S.purged=await e.purgeCache(t,{hosts:g}),!S.purged)S.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.")}return S}var Km=(r)=>process.env[r];function il(r,e){let t=r.replace(/\.$/,"").toLowerCase();if(e){let n=e.replace(/\.$/,"").toLowerCase();if(t===n||t.endsWith(`.${n}`))return n}return t.split(".").slice(-2).join(".")}function lo(r,e={}){let t=e.env??Km,n=r.infrastructure?.compute?.proxy?.cdn,i=[];if(!n||n.provider!=="cloudflare")return{plan:null,errors:i};if(n.originDomain)i.push(`compute.proxy.cdn.originDomain (${n.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 o=t("CLOUDFLARE_API_TOKEN");if(!o)return i.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled."),{plan:null,errors:i};let s=r.sites??{},c=n.frontedHosts?.length?[...new Set(n.frontedHosts.map((f)=>f.toLowerCase()))]:co(s,{autoWww:r.infrastructure?.compute?.proxy?.autoWww!==!1});if(c.length===0)return i.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain."),{plan:null,errors:i};let l=r.infrastructure?.dns?.domain,d=new Set(c.map((f)=>il(f,l)));if(d.size>1)return i.push(`Fronted hosts span more than one zone (${[...d].join(", ")}). A Cloudflare zone is configured as a unit, so run one CDN config per zone.`),{plan:null,errors:i};let u=n.cloudflare??{},m=n.secret?{header:n.secretHeader??"X-Origin-Verify",value:n.secret}:void 0;return{plan:{zone:[...d][0],hosts:c,apiToken:o,zoneId:u.zoneId??t("CLOUDFLARE_ZONE_ID"),accountId:u.accountId??t("CLOUDFLARE_ACCOUNT_ID"),proxied:u.proxied!==!1,settings:u.settings??$n,cache:u.cache,originGuard:m,purge:u.purgeOnDeploy!==!1,skipOriginProbe:u.skipOriginProbe===!0},errors:i}}async function Qm(r){let e=ol();if(e.emptyEnvKey||e.emptyEnvSecret)R("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.");a(`AWS credentials: ${e.description}`);try{let n=await new Dt(r).getCallerIdentity();if(n.Arn)a(`AWS identity: ${n.Arn}`)}catch(t){R(`Could not verify AWS identity: ${t.message}`)}}function Xm(){try{return Po("git rev-parse --short HEAD",{stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return Date.now().toString(36)}}async function uo(r,e,t,n){let{ensureManagementDashboard:i}=await import("./chunk-zpe64t4r.js");i(r,{logger:{info:a,warn:R}});let o=r.sites||{},s=Object.entries(o);if(s.length===0)return R("infrastructure.compute is set but no sites are configured — nothing to deploy. Add sites to `cloud.config.ts`."),!0;if(n&&!o[n])return p(`Site '${n}' was not found. Configured sites: ${s.map(([I])=>I).join(", ")}`),!1;let c=s.filter(([I,v])=>{if(!v)return!1;if(n&&I!==n)return!1;if(kn(v)==="bucket")return R(`Site '${I}' targets a bucket — skipping compute deploy (handled by the static-site path).`),!1;return wo(v)});if(n)a(`Deploying only '${n}'. Other sites keep their current release; proxy routes are still regenerated in full.`);let l=r.project.slug,u=(r.infrastructure?.compute||{}).runtime||"bun",m=Ke({config:r}),f=Xm(),y=new Map,S=new Map,g={step:(I)=>M(I),error:(I)=>p(I)};if(!await Sn(r,"beforeDeploy",g))return!1;if(!await Sn(r,"beforeBuild",g))return!1;for(let[I,v]of c){let C=kn(v);if(w(`Preparing site: ${I}`),a(`Domain: ${v.domain||"(none)"}`),a(`Source: ${v.root}`),a(`Kind: ${C==="server-static"?"static (shipped to /var/www)":"app (systemd service)"}`),C==="server-app")a(`Start: ${v.start}`),a(`Port: ${v.port??"(unset)"}`);if(v.build){M(`Running build: ${v.build}`);try{Po(v.build,{stdio:"inherit",cwd:process.cwd()})}catch(N){return p(`Build failed for site '${I}': ${N.message}`),!1}}let k=tl({root:v.root,siteName:I,expectIndex:C==="server-static"});for(let N of k)if(N.level==="error")p(N.message);else R(N.message);if(k.some((N)=>N.level==="error"))return!1;let T=v.build?void 0:JSON.stringify([Zm(v.root),[...v.exclude||[]].sort()]),E=T?S.get(T):void 0;if(E){M(`Reusing package for ${v.root} → ${E}`),y.set(I,E);continue}let D=bl(vl(),`${l}-${I}-${f}.tar.gz`);M(`Packaging ${v.root} → ${D}`);try{let N=(v.exclude||[]).map((_)=>`--exclude=${_}`);Hm("tar",["czf",D,"-C",v.root,...N,"."],{stdio:"inherit",env:{...process.env,COPYFILE_DISABLE:"1"}})}catch(N){return p(`Failed to package '${I}': ${N.message}`),!1}if(y.set(I,D),T)S.set(T,D)}if(!await Sn(r,"afterBuild",g))return!1;let b=await So({config:n?{...r,sites:Object.fromEntries(c)}:r,rpxConfig:n?r:void 0,environment:e,driver:m,sha:f,runtime:u,tarballForSite:(I)=>{let v=y.get(I);if(!v)throw Error(`Missing tarball for site '${I}'`);return v},logger:{info:(I)=>a(I),warn:(I)=>R(I),error:(I)=>p(I),step:(I)=>M(I),success:(I)=>h(I)}});if(b&&!await Sn(r,"afterDeploy",g))return!1;return b}async function ar(r){let e=new pl;if(M("Running pre-deployment security scan..."),r.exclude?.length)a(`Excluding from the scan: ${r.exclude.join(", ")}`);let t=await e.scan({directory:r.sourceDir,failOnSeverity:r.failOnSeverity||"critical",skipPatterns:r.skipPatterns,exclude:r.exclude});return{passed:t.passed,result:t}}function ep(){let r=bl(vl(),`ts-cloud-empty-scan-${process.pid}`);return yl(r,{recursive:!0}),r}function cr(r){let{summary:e,findings:t,scannedFiles:n,duration:i}=r;if(a(`
|
|
375
|
+
Invalidation is in progress. Use --wait to wait for completion.`)}catch(t){p(`Invalidation failed: ${t.message}`)}})}function Zi(r){for(let[e,t]of[["team:add <email> <role>","Invite an organization member"],["team:list","List organization members"],["team:remove <email>","Revoke organization membership"]])r.command(e,t).action(async(...n)=>be(e.split(" ")[0],{target:String(n[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 Hm,execSync as Po}from"node:child_process";import{copyFileSync as Cn,existsSync as ge,mkdirSync as yl,readFileSync as go,writeFileSync as Jm}from"node:fs";import{tmpdir as vl}from"node:os";import{dirname as Ym,join as bl,resolve as Zm}from"node:path";import{existsSync as Qi,readdirSync as Qc,statSync as Xi}from"node:fs";import{join as or}from"node:path";var _m=new Set([".DS_Store","Thumbs.db",".gitkeep",".keep"]);function Xc(r){return _m.has(r)}function el(r,e=0){if(e>8)return!0;let t;try{t=Qc(r)}catch{return!1}for(let n of t){if(Xc(n))continue;let i=or(r,n),o;try{o=Xi(i)}catch{continue}if(o.isFile())return!0;if(o.isDirectory()&&el(i,e+1))return!0}return!1}function zm(r){let e;try{e=Qc(r)}catch{return[]}let t=[];for(let n of e){if(Xc(n))continue;let i=or(r,n);try{if(Xi(i).isDirectory()&&Qi(or(i,"index.html")))t.push(n)}catch{}}return t.sort((n,i)=>n.localeCompare(i))}function tl(r){let{root:e,siteName:t,expectIndex:n}=r,i=[];if(!Qi(e))return i.push({level:"error",message:`Build output not found at ${e} for site '${t}'`}),i;if(!Xi(e).isDirectory())return i.push({level:"error",message:`Site '${t}' has root ${e}, which is a file, not a directory`}),i;if(!el(e))return i.push({level:"error",message:`Site '${t}' would ship an empty release: ${e} contains no files. Check that the build ran and wrote where 'root' points.`}),i;if(!n)return i;if(Qi(or(e,"index.html")))return i;let o=zm(e);if(o.length===1){let s=`${e.replace(/\/+$/,"")}/${o[0]}`;return i.push({level:"error",message:`Site '${t}' has no index.html at the top of ${e}, but ${s} has one. The generator wrote one level down, so this release would serve 404s at the domain root. Set root: '${s}'.`}),i}if(o.length>1)return i.push({level:"warning",message:`Site '${t}' has no index.html at the top of ${e}. These subdirectories have one: ${o.join(", ")}. The domain root will 404.`}),i;return i.push({level:"warning",message:`Site '${t}' has no index.html anywhere under ${e}. The domain root will 404.`}),i}function Um(r){if(!r)return[];if(Array.isArray(r))return r.map(String);let e=r,t=e.Items??e.Item;if(!t)return[];if(Array.isArray(t))return t.map(String);if(typeof t==="object"&&t!==null){let n=t;if("Item"in n){let i=n.Item;if(!i)return[];return Array.isArray(i)?i.map(String):[String(i)]}if("CNAME"in n){let i=n.CNAME;if(!i)return[];return Array.isArray(i)?i.map(String):[String(i)]}return Object.values(n).flatMap((i)=>{if(Array.isArray(i))return i.map(String);return typeof i==="string"?[i]:[]})}return[String(t)]}async function sr(r){if(r.length===0)return;let e=new ne,t=await e.listDistributions(),n=[...new Set(r.map((i)=>i.toLowerCase()))];for(let i of n){let o=t.find((c)=>Um(c.Aliases).some((d)=>d.toLowerCase()===i));if(!o?.Id){R(`No CloudFront distribution found for ${i} — skipping dynamic HTTP method sync`);continue}if(await e.ensureDynamicHttpMethods(o.Id))h(`CloudFront ${o.Id} (${i}): enabled POST/PUT/PATCH/DELETE on dynamic paths`);else a(`CloudFront ${o.Id} (${i}): already allows dynamic HTTP methods`)}}import{execSync as Vm}from"node:child_process";async function Wm(r,e,t,n={},i=process.cwd()){if(!r)return;if(n.step?.(`Running ${t} hook`),typeof r==="string"){Vm(r,{stdio:"inherit",cwd:i});return}await r(e)}async function Sn(r,e,t={},n){try{return await Wm(r.hooks?.[e],r,e,t,n),!0}catch(i){return t.error?.(`${e} hook failed: ${i?.message||i}`),!1}}var nl=["js","mjs","cjs","css","map","woff","woff2","ttf","otf","eot","png","jpg","jpeg","gif","webp","avif","svg","ico","mp4","webm","ogg","mp3","wav","pdf","wasm"];function eo(r){return`"${r.replace(/\\/g,"\\\\").replace(/"/g,"\\\"")}"`}function rl(r){return`{${r.map(eo).join(" ")}}`}function to(r){if(r.length===1)return`http.host eq ${eo(r[0])}`;return`http.host in ${rl(r)}`}function no(r,e={}){if(e.enabled===!1||r.length===0)return[];let t=to(r),n=[],i=e.bypassPaths??[],o=i.length>0?`(${i.map((d)=>`starts_with(http.request.uri.path, ${eo(d)})`).join(" or ")})`:void 0;if(o)n.push({action:"set_cache_settings",description:"bypass cache",expression:`(${t} and ${o})`,enabled:!0,action_parameters:{cache:!1}});let s=o?`${t} and not ${o}`:t,c=e.assetExtensions??nl,l=c.length>0?`lower(http.request.uri.path.extension) in ${rl([...c])}`:void 0;if(l)n.push({action:"set_cache_settings",description:"cache fingerprinted assets",expression:`(${s} and ${l})`,enabled:!0,action_parameters:{cache:!0,edge_ttl:{mode:"override_origin",default:e.assetEdgeTtl??2592000},browser_ttl:{mode:"override_origin",default:e.assetBrowserTtl??31536000}}});return n.push({action:"set_cache_settings",description:"cache documents",expression:l?`(${s} and not ${l})`:`(${s})`,enabled:!0,action_parameters:{cache:!0,edge_ttl:{mode:"override_origin",default:e.documentEdgeTtl??3600},browser_ttl:{mode:"override_origin",default:e.documentBrowserTtl??0}}}),n}function ro(r,e,t){if(r.length===0||!e||!t)return[];return[{action:"rewrite",description:"origin guard header",expression:`(${to(r)})`,enabled:!0,action_parameters:{headers:{[e]:{operation:"set",value:t}}}}]}var Ke=(r)=>r===void 0?void 0:r?"on":"off";function io(r){let e={ssl:r.ssl,always_use_https:Ke(r.alwaysUseHttps),automatic_https_rewrites:Ke(r.automaticHttpsRewrites),min_tls_version:r.minTlsVersion,tls_1_3:Ke(r.tls13),brotli:Ke(r.brotli),http3:Ke(r.http3),"0rtt":Ke(r.zeroRtt),early_hints:Ke(r.earlyHints),browser_cache_ttl:r.browserCacheTtl,always_online:Ke(r.alwaysOnline),websockets:Ke(r.websockets),email_obfuscation:Ke(r.emailObfuscation)};if(r.hsts)e.security_header={strict_transport_security:{enabled:r.hsts.enabled,max_age:r.hsts.maxAge??31536000,include_subdomains:r.hsts.includeSubdomains??!1,preload:r.hsts.preload??!1,nosniff:r.hsts.noSniff??!0}};for(let t of Object.keys(e))if(e[t]===void 0)delete e[t];return{...e,...r.raw??{}}}var $n={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 qm}from"node:tls";async function oo(r){let{address:e,serverName:t,port:n=443,timeoutMs:i=5000}=r;return new Promise((o)=>{let s=!1,c=(d)=>{if(s)return;s=!0;try{l.destroy()}catch{}o(d)},l=qm({host:e,port:n,servername:t,rejectUnauthorized:!1,timeout:i});l.once("secureConnect",()=>{if(l.authorized){c({reachable:!0,trusted:!0});return}c({reachable:!0,trusted:!1,reason:l.authorizationError?String(l.authorizationError):"origin certificate is not trusted for this hostname"})}),l.once("timeout",()=>{c({reachable:!1,trusted:!1,reason:`no TLS handshake within ${i}ms`})}),l.once("error",(d)=>{c({reachable:!1,trusted:!1,reason:d.message})})})}function Gm(r,e){let t=e.replace(/\.$/,"").toLowerCase();return r.some((n)=>{let i=n.replace(/\.$/,"").toLowerCase();if(i===t)return!0;if(!i.startsWith("*."))return!1;let o=i.slice(2);if(!t.endsWith(`.${o}`))return!1;return!t.slice(0,t.length-o.length-1).includes(".")})}async function ao(r){let{provider:e,zone:t,hosts:n,ipv4:i,ipv6:o,proxied:s=!0,settings:c=$n,cache:l,originGuard:d,purge:u=!0,ttl:m=300,skipOriginProbe:f=!1,skipEdgeCertificateCheck:y=!1}=r,S={records:[],settingsChanged:[],cacheRules:0,originGuard:!1,purged:!1,deferredProxy:[],warnings:[]};if(n.length===0)return S;let g=[],b=s&&!y?await e.listEdgeCertificateHosts(t):null;for(let v of n){let C=s;if(C&&b&&!Gm(b,v)){C=!1;let T=b.length===0?"the zone has no active edge certificate yet":`no active edge certificate covers it (${b.join(", ")})`;S.deferredProxy.push({host:v,reason:T}),S.warnings.push(`${v}: publishing DNS-only — ${T}. 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(C&&!f){let T=await oo({address:i,serverName:v});if(!T.trusted){C=!1;let E=T.reason||"origin is not serving a trusted certificate";S.deferredProxy.push({host:v,reason:E}),S.warnings.push(`${v}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${E}). The origin issues certificates over HTTP-01, which needs the name to reach it directly; re-run the deploy once it has one and the record will be proxied.`)}}if(C)g.push(v);let k=[{type:"A",content:i}];if(o&&so(v))k.push({type:"AAAA",content:o});for(let{type:T,content:E}of k){let D=await e.upsertRecord(t,{name:v,type:T,content:E,ttl:m,proxied:C});if(D.success)S.records.push({host:v,type:T,content:E,proxied:C});else S.warnings.push(`${v} ${T} → ${E} failed: ${D.message||"unknown provider error"}`)}}if(g.length===0){if(s)S.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");return S}try{let v=io(c),C=await e.applyZoneSettings(t,v);S.settingsChanged=C.changed;for(let k of C.failed)S.warnings.push(`zone setting '${k.id}' could not be applied: ${k.error}`)}catch(v){S.warnings.push(`zone settings could not be read: ${v instanceof Error?v.message:String(v)}`)}let I=no(g,l);if(I.length>0){let v=await e.putManagedPhaseRules(t,"http_request_cache_settings",I);if(v.success)S.cacheRules=I.length;else S.warnings.push(`cache rules could not be written: ${v.message}`)}if(d){let v=ro(g,d.header,d.value);if(v.length>0){let C=await e.putManagedPhaseRules(t,"http_request_late_transform",v);if(C.success)S.originGuard=!0;else S.warnings.push(`origin guard header could not be written: ${C.message}. `+"The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.")}}if(u){if(S.purged=await e.purgeCache(t,{hosts:g}),!S.purged)S.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.")}return S}var Km=(r)=>process.env[r];function il(r,e){let t=r.replace(/\.$/,"").toLowerCase();if(e){let n=e.replace(/\.$/,"").toLowerCase();if(t===n||t.endsWith(`.${n}`))return n}return t.split(".").slice(-2).join(".")}function lo(r,e={}){let t=e.env??Km,n=r.infrastructure?.compute?.proxy?.cdn,i=[];if(!n||n.provider!=="cloudflare")return{plan:null,errors:i};if(n.originDomain)i.push(`compute.proxy.cdn.originDomain (${n.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 o=t("CLOUDFLARE_API_TOKEN");if(!o)return i.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled."),{plan:null,errors:i};let s=r.sites??{},c=n.frontedHosts?.length?[...new Set(n.frontedHosts.map((f)=>f.toLowerCase()))]:co(s,{autoWww:r.infrastructure?.compute?.proxy?.autoWww!==!1});if(c.length===0)return i.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain."),{plan:null,errors:i};let l=r.infrastructure?.dns?.domain,d=new Set(c.map((f)=>il(f,l)));if(d.size>1)return i.push(`Fronted hosts span more than one zone (${[...d].join(", ")}). A Cloudflare zone is configured as a unit, so run one CDN config per zone.`),{plan:null,errors:i};let u=n.cloudflare??{},m=n.secret?{header:n.secretHeader??"X-Origin-Verify",value:n.secret}:void 0;return{plan:{zone:[...d][0],hosts:c,apiToken:o,zoneId:u.zoneId??t("CLOUDFLARE_ZONE_ID"),accountId:u.accountId??t("CLOUDFLARE_ACCOUNT_ID"),proxied:u.proxied!==!1,settings:u.settings??$n,cache:u.cache,originGuard:m,purge:u.purgeOnDeploy!==!1,skipOriginProbe:u.skipOriginProbe===!0},errors:i}}async function Qm(r){let e=ol();if(e.emptyEnvKey||e.emptyEnvSecret)R("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.");a(`AWS credentials: ${e.description}`);try{let n=await new Dt(r).getCallerIdentity();if(n.Arn)a(`AWS identity: ${n.Arn}`)}catch(t){R(`Could not verify AWS identity: ${t.message}`)}}function Xm(){try{return Po("git rev-parse --short HEAD",{stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return Date.now().toString(36)}}async function uo(r,e,t,n){let{ensureManagementDashboard:i}=await import("./chunk-zpe64t4r.js");i(r,{logger:{info:a,warn:R}});let o=r.sites||{},s=Object.entries(o);if(s.length===0)return R("infrastructure.compute is set but no sites are configured — nothing to deploy. Add sites to `cloud.config.ts`."),!0;if(n&&!o[n])return p(`Site '${n}' was not found. Configured sites: ${s.map(([I])=>I).join(", ")}`),!1;let c=s.filter(([I,v])=>{if(!v)return!1;if(n&&I!==n)return!1;if(kn(v)==="bucket")return R(`Site '${I}' targets a bucket — skipping compute deploy (handled by the static-site path).`),!1;return wo(v)});if(n)a(`Deploying only '${n}'. Other sites keep their current release; proxy routes are still regenerated in full.`);let l=r.project.slug,u=(r.infrastructure?.compute||{}).runtime||"bun",m=He({config:r}),f=Xm(),y=new Map,S=new Map,g={step:(I)=>M(I),error:(I)=>p(I)};if(!await Sn(r,"beforeDeploy",g))return!1;if(!await Sn(r,"beforeBuild",g))return!1;for(let[I,v]of c){let C=kn(v);if(w(`Preparing site: ${I}`),a(`Domain: ${v.domain||"(none)"}`),a(`Source: ${v.root}`),a(`Kind: ${C==="server-static"?"static (shipped to /var/www)":"app (systemd service)"}`),C==="server-app")a(`Start: ${v.start}`),a(`Port: ${v.port??"(unset)"}`);if(v.build){M(`Running build: ${v.build}`);try{Po(v.build,{stdio:"inherit",cwd:process.cwd()})}catch(N){return p(`Build failed for site '${I}': ${N.message}`),!1}}let k=tl({root:v.root,siteName:I,expectIndex:C==="server-static"});for(let N of k)if(N.level==="error")p(N.message);else R(N.message);if(k.some((N)=>N.level==="error"))return!1;let T=v.build?void 0:JSON.stringify([Zm(v.root),[...v.exclude||[]].sort()]),E=T?S.get(T):void 0;if(E){M(`Reusing package for ${v.root} → ${E}`),y.set(I,E);continue}let D=bl(vl(),`${l}-${I}-${f}.tar.gz`);M(`Packaging ${v.root} → ${D}`);try{let N=(v.exclude||[]).map((_)=>`--exclude=${_}`);Hm("tar",["czf",D,"-C",v.root,...N,"."],{stdio:"inherit",env:{...process.env,COPYFILE_DISABLE:"1"}})}catch(N){return p(`Failed to package '${I}': ${N.message}`),!1}if(y.set(I,D),T)S.set(T,D)}if(!await Sn(r,"afterBuild",g))return!1;let b=await So({config:n?{...r,sites:Object.fromEntries(c)}:r,rpxConfig:n?r:void 0,environment:e,driver:m,sha:f,runtime:u,tarballForSite:(I)=>{let v=y.get(I);if(!v)throw Error(`Missing tarball for site '${I}'`);return v},logger:{info:(I)=>a(I),warn:(I)=>R(I),error:(I)=>p(I),step:(I)=>M(I),success:(I)=>h(I)}});if(b&&!await Sn(r,"afterDeploy",g))return!1;return b}async function ar(r){let e=new pl;if(M("Running pre-deployment security scan..."),r.exclude?.length)a(`Excluding from the scan: ${r.exclude.join(", ")}`);let t=await e.scan({directory:r.sourceDir,failOnSeverity:r.failOnSeverity||"critical",skipPatterns:r.skipPatterns,exclude:r.exclude});return{passed:t.passed,result:t}}function ep(){let r=bl(vl(),`ts-cloud-empty-scan-${process.pid}`);return yl(r,{recursive:!0}),r}function cr(r){let{summary:e,findings:t,scannedFiles:n,duration:i}=r;if(a(`
|
|
376
376
|
Scanned ${n} files in ${i}ms`),e.critical>0)p(` Critical: ${e.critical}`);else a(` Critical: ${e.critical}`);if(e.high>0)R(` High: ${e.high}`);else a(` High: ${e.high}`);if(a(` Medium: ${e.medium}`),a(` Low: ${e.low}`),t.length>0){a(`
|
|
377
377
|
Findings:`);let o=t.filter((u)=>u.pattern.severity==="critical"),s=t.filter((u)=>u.pattern.severity==="high"),c=t.filter((u)=>u.pattern.severity==="medium"),l=t.filter((u)=>u.pattern.severity==="low"),d=(u,m,f)=>{if(u.length>0){console.log(`
|
|
378
378
|
${sl(`[${m}]`,f)}`);for(let y of u.slice(0,10))a(` ${y.pattern.name}`),a(` File: ${y.file}:${y.line}`),a(` Match: ${y.match}`);if(u.length>10)a(` ... and ${u.length-10} more ${m.toLowerCase()} findings`)}};d(o,"CRITICAL","red"),d(s,"HIGH","yellow"),d(c,"MEDIUM","blue"),d(l,"LOW","gray")}}function wl(r,e){let t=Object.keys(r.environments??{});if(e&&t.includes(e))return e;if(t.includes("production"))return"production";if(t[0])return t[0];throw Error("A configured environment is required for security policy evaluation")}function mo(r,e,t){let n=wl(r,e),i=q(process.cwd(),r);try{let o=new Ao(i.store);Ro(i);let s=ko(i,n);if(t)fl(o,s,t);else gl(o,s);let c=Io(o,{scope:s,desiredConfigHash:i.project.desiredConfigHash});return a(`Security policy: ${c.decision.policyId} v${c.decision.policyVersion} — ${c.decision.outcome.toUpperCase()}`),a(`Security decision: ${c.decision.explanation}`),{allowed:c.decision.outcome!=="block",explanation:c.decision.explanation}}finally{i.store.close()}}async function tp(r,e){let t=wl(r),n=q(process.cwd(),r);try{let i=new Ao(n.store);Ro(n);let o=ko(n,t),s=e.imageSha256?`${e.imageRef}@sha256:${e.imageSha256.replace(/^sha256:/,"")}`:e.imageRef;M("Scanning the built image and producing release security artifacts...");let c=await hl(i,{scope:o,releaseId:s,imageRef:e.imageRef,imageSha256:e.imageSha256,artifactRoot:e.artifactRoot,invocationId:e.invocationId,startedAt:e.startedAt,externalParameters:{imageRef:e.imageRef,artifactRoot:e.artifactRoot}});a(`Image scanner: ${c.scan.run.status} (${c.scan.findings.length} findings)`),a(`Release SBOM: ${c.sbomSource==="image"?"full image inventory":c.sbomSource==="manifest"?"package-manifest fallback":"unavailable"}`);let l=Io(i,{scope:o,operationId:e.invocationId,desiredConfigHash:n.project.desiredConfigHash});return a(`Release policy: ${l.decision.policyId} v${l.decision.policyVersion} — ${l.decision.outcome.toUpperCase()}`),a(`Release decision: ${l.decision.explanation}`),{allowed:l.decision.outcome!=="block",explanation:l.decision.explanation}}finally{n.store.close()}}function np(r){try{let e=q(process.cwd(),r),t=new vn(e.store).current();if(!t.attackMode&&!t.mitigationPause&&t.ipRules.allow.length===0&&t.ipRules.block.length===0)return r;let n=ml(r,t);return a(`Applying protection controls: ${t.ipRules.allow.length} allow, ${t.ipRules.block.length} block${t.attackMode?", attack mode ON":""}${t.mitigationPause?", mitigation PAUSED":""}`),n}catch{return r}}function xo(r){r.command("deploy:security-scan","Run pre-deployment security scan").option("--source <path>","Source directory to scan",{default:"."}).option("--fail-on <severity>","Fail on severity level (critical, high, medium, low)",{default:"critical"}).option("--skip-patterns <patterns>","Comma-separated list of pattern names to skip").action(async(e)=>{w("Pre-Deployment Security Scan");try{let t=e?.source||".",n=e?.failOn||"critical",i=e?.skipPatterns?.split(",").map((c)=>c.trim())||[];if(!ge(t)){p(`Source directory not found: ${t}`);return}a(`Source: ${t}`),a(`Fail on: ${n} or higher severity`);let{passed:o,result:s}=await ar({sourceDir:t,failOnSeverity:n,skipPatterns:i});if(cr(s),o)h(`
|
|
@@ -382,9 +382,9 @@ Recommendations:`),a(" 1. Remove any hardcoded credentials from your code"),a("
|
|
|
382
382
|
✗ Deployment configuration is invalid:`);for(let j of c)p(` • ${j}`);process.exitCode=1;return}if(e?.dryRun){if(e.site&&!s.sites?.[e.site]){p(`Site '${e.site}' was not found. Configured sites: ${Object.keys(s.sites||{}).join(", ")||"none"}`),process.exitCode=1;return}let j=An(s),W=e.stack||Ve(s,n),ue=Yt(s),ae=s.project.region||"us-east-1",Fe=e.site?[e.site]:Object.keys(s.sites||{});w("Deployment Plan"),a(`Cloud provider: ${ue}`),a(`Mode: ${j}`),a(`Stack: ${W}`),a(`Region: ${ae}`),a(`Environment: ${n}`),a(`Sites: ${Fe.length>0?Fe.join(", "):"none"}`),h("Dry run complete. No infrastructure, DNS, certificates, or application files were changed.");return}if(Yt(s)==="aws"){let j=s.project?.region||"us-east-1";await Qm(j)}let d;if(!e?.skipSecurityScan){let j=ul(s,e?.site);if(!j)a("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:W}=await ar({sourceDir:j?process.cwd():ep(),failOnSeverity:e?.securityFailOn||"critical",exclude:s.infrastructure?.security?.scan?.exclude});cr(W),d=W}else R(`Security scan skipped (--skip-security-scan)
|
|
383
383
|
`);let u=mo(s,n,d);if(!u.allowed){p(`
|
|
384
384
|
✗ Deployment blocked by security policy: ${u.explanation}`),a("Remediate the findings or create an authorized, time-limited waiver in the Security posture center."),process.exitCode=1;return}if(h(`✓ Security policy allows this deployment
|
|
385
|
-
`),An(s)==="serverless"){M("Serverless project detected: deploying the Lambda application");let{deployServerlessApp:j}=await import("./chunk-0zwknfwp.js");await j(s,n,{}),h("Serverless deployment complete");return}let m=e?.stack||Ve(s,n),f=s.project.region||"us-east-1",y=s.infrastructure?.deployStack!==!1,S=s.sites&&Object.keys(s.sites).length>0,g=s.infrastructure?.dns?.provider;if(S&&g){if(await ap(s,e?.site,g,f,e?.skipDnsVerification,n,t),!y){if(s.infrastructure?.compute){let j=Object.values(s.sites).map((W)=>W.domain).filter((W)=>!!W);if(j.length>0)M("Syncing CloudFront dynamic HTTP methods for app domains..."),await sr(j)}return}}if(!y){a("Infrastructure stack deployment disabled (infrastructure.deployStack: false)");return}let b=Yt(s);if(a(`Cloud provider: ${b}`),a(`Stack: ${m}`),a(`Region: ${f}`),a(`Environment: ${n}`),(b==="hetzner"||s.infrastructure?.compute?.mode==="server")&&fo(s)){M(`Provisioning ${b} compute infrastructure...`);let j=
|
|
385
|
+
`),An(s)==="serverless"){M("Serverless project detected: deploying the Lambda application");let{deployServerlessApp:j}=await import("./chunk-0zwknfwp.js");await j(s,n,{}),h("Serverless deployment complete");return}let m=e?.stack||Ve(s,n),f=s.project.region||"us-east-1",y=s.infrastructure?.deployStack!==!1,S=s.sites&&Object.keys(s.sites).length>0,g=s.infrastructure?.dns?.provider;if(S&&g){if(await ap(s,e?.site,g,f,e?.skipDnsVerification,n,t),!y){if(s.infrastructure?.compute){let j=Object.values(s.sites).map((W)=>W.domain).filter((W)=>!!W);if(j.length>0)M("Syncing CloudFront dynamic HTTP methods for app domains..."),await sr(j)}return}}if(!y){a("Infrastructure stack deployment disabled (infrastructure.deployStack: false)");return}let b=Yt(s);if(a(`Cloud provider: ${b}`),a(`Stack: ${m}`),a(`Region: ${f}`),a(`Environment: ${n}`),(b==="hetzner"||s.infrastructure?.compute?.mode==="server")&&fo(s)){M(`Provisioning ${b} compute infrastructure...`);let j=He({config:s,provider:b});if(!j.provisionComputeInfrastructure){p(`${b} driver does not support compute provisioning`),process.exitCode=1;return}let W=await j.provisionComputeInfrastructure({config:s,environment:n});if(h(`${b} compute infrastructure ready`),W.appPublicIp)a(`App server: ${W.appPublicIp}`);if(W.appInstanceId)a(`Server ID: ${W.appInstanceId}`);if(!await uo(s,n,f,e?.site))p(`App deploy to ${b} compute reported a failure`),process.exitCode=1;else{if(h(`App deployed to ${b} compute`),e?.skipDnsVerification)a("DNS verification and record reconciliation skipped (--skip-dns-verification)");else await ip(s,W.appPublicIp,g,W.appPublicIpv6),await op(s,g);let ae=await $o({config:s,environment:n,driver:j,logger:{info:a,warn:R,error:p,step:M,success:h}});if(!ae)p("App deployed, but rpx TLS certificate reconciliation failed");if(!ae)process.exitCode=1;if(!e?.skipDnsVerification)await sp(s,W.appPublicIp,W.appPublicIpv6)}return}M("Generating CloudFormation template...");let v=new Ut({config:s,environment:n});v.generate();let C=v.toJSON(),k=JSON.parse(C);if(Object.keys(k.Resources||{}).length===0){a(`No infrastructure resources to deploy for environment '${n}' — skipping stack ${m}.`);return}M("Validating template...");let E=on(k),D=sn(C),N=an(k),_=[...E.errors,...D.errors,...N.errors];if(_.length>0){p("Template validation failed:");for(let j of _)p(` - ${j.path}: ${j.message}`);return}let B=[...E.warnings,...D.warnings,...N.warnings];if(B.length>0)for(let j of B)R(` - ${j.path}: ${j.message}`);h("Template validated successfully");let V=Object.keys(k.Resources).length;a(`
|
|
386
386
|
Resources to deploy: ${V}`);let K={};for(let j of Object.values(k.Resources)){let W=j.Type;K[W]=(K[W]||0)+1}for(let[j,W]of Object.entries(K).sort((ue,ae)=>ae[1]-ue[1]).slice(0,5))a(` - ${j}: ${W}`);if(!(t||await O(`
|
|
387
|
-
Deploy now?`,!0))){a("Deployment cancelled");return}let G=new ie(f);M("Checking stack status...");let L=!1;try{let j=await G.describeStacks({stackName:m});L=j.Stacks&&j.Stacks.length>0}catch(j){L=!1}if(L){a("Stack exists, updating...");let j=new A("Updating CloudFormation stack...");j.start();try{await G.updateStack({stackName:m,templateBody:C,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:s.project.name},{Key:"Environment",Value:n},{Key:"ManagedBy",Value:"ts-cloud"}]}),j.succeed("Update initiated"),M("Waiting for stack update to complete..."),await G.waitForStack(m,"stack-update-complete"),h("Stack updated successfully!")}catch(W){if(W.message.includes("No updates are to be performed"))j.succeed("No changes detected"),a("Stack is already up to date — continuing to app deploy");else throw W}}else{a("Creating new stack...");let j=new A("Creating CloudFormation stack...");j.start(),await G.createStack({stackName:m,templateBody:C,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:s.project.name},{Key:"Environment",Value:n},{Key:"ManagedBy",Value:"ts-cloud"}]}),j.succeed("Stack creation initiated"),M("Waiting for stack creation to complete..."),await G.waitForStack(m,"stack-create-complete"),h("Stack created successfully!")}let H=await G.getStackOutputs(m);if(
|
|
387
|
+
Deploy now?`,!0))){a("Deployment cancelled");return}let G=new ie(f);M("Checking stack status...");let L=!1;try{let j=await G.describeStacks({stackName:m});L=j.Stacks&&j.Stacks.length>0}catch(j){L=!1}if(L){a("Stack exists, updating...");let j=new A("Updating CloudFormation stack...");j.start();try{await G.updateStack({stackName:m,templateBody:C,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:s.project.name},{Key:"Environment",Value:n},{Key:"ManagedBy",Value:"ts-cloud"}]}),j.succeed("Update initiated"),M("Waiting for stack update to complete..."),await G.waitForStack(m,"stack-update-complete"),h("Stack updated successfully!")}catch(W){if(W.message.includes("No updates are to be performed"))j.succeed("No changes detected"),a("Stack is already up to date — continuing to app deploy");else throw W}}else{a("Creating new stack...");let j=new A("Creating CloudFormation stack...");j.start(),await G.createStack({stackName:m,templateBody:C,capabilities:["CAPABILITY_IAM","CAPABILITY_NAMED_IAM"],tags:[{Key:"Project",Value:s.project.name},{Key:"Environment",Value:n},{Key:"ManagedBy",Value:"ts-cloud"}]}),j.succeed("Stack creation initiated"),M("Waiting for stack creation to complete..."),await G.waitForStack(m,"stack-create-complete"),h("Stack created successfully!")}let H=await G.getStackOutputs(m);if(Xe(`Deployment Complete!
|
|
388
388
|
|
|
389
389
|
Stack: ${m}
|
|
390
390
|
Region: ${f}
|
|
@@ -395,14 +395,14 @@ View in console:
|
|
|
395
395
|
https://console.aws.amazon.com/cloudformation/home?region=${f}#/stacks/stackinfo?stackId=${encodeURIComponent(m)}`,"green"),Object.keys(H).length>0){a(`
|
|
396
396
|
Stack Outputs:`);for(let[j,W]of Object.entries(H))a(` - ${j}: ${W}`)}if(s.infrastructure?.storage)for(let[j,W]of Object.entries(s.infrastructure.storage)){if(!W.website||!W.root)continue;let ue=H[`${j}BucketName`]||H.FrontendBucketName,ae=H[`${j}CloudFrontDistributionId`];if(!ue){R(`Could not find bucket name for storage '${j}' — skipping file upload`);continue}if(!ge(W.root)){R(`Source directory not found: ${W.root} — skipping file upload for '${j}'`);continue}M(`Uploading files from ${W.root} to s3://${ue}...`);let{uploadStaticFiles:Fe,invalidateCache:jt}=await import("./chunk-zdt2rjk6.js"),ye=await Fe({sourceDir:W.root,bucket:ue,region:f,onProgress:(Y,X,ke)=>{if(Y%10===0||Y===X)a(` ${Y}/${X}: ${ke}`)}});if(ye.errors.length>0)R(`Upload completed with errors: ${ye.errors.join(", ")}`);else{let Y=ye.skipped>0?`Uploaded ${ye.uploaded} files (${ye.skipped} unchanged)`:`Uploaded ${ye.uploaded} files`;h(Y)}if(ae&&ye.uploaded>0){M("Invalidating CloudFront cache...");let{invalidationId:Y}=await jt(ae);h(`Cache invalidation created: ${Y}`)}}if(s.infrastructure?.compute)await uo(s,n,f,e?.site);if(s.infrastructure?.compute&&s.sites){let j=Object.values(s.sites).map((W)=>W.domain).filter((W)=>!!W);if(j.length>0)M("Syncing CloudFront dynamic HTTP methods for app domains..."),await sr(j)}}catch(o){if(p(`Deployment failed: ${o.message}`),process.exitCode=1,o.stack)a(`
|
|
397
397
|
Stack trace:`),console.error(o.stack)}finally{if(i)await i()}}),r.command("deploy:server","Deploy EC2 infrastructure").option("--env <environment>","Environment (production, staging, development)").option("--site <name>","Deploy specific site only (routes are still regenerated in full)").action(async(e)=>{w("Deploying Server Infrastructure");try{let t=await P(),n=e?.env||"production",i=t.project.region||"us-east-1",o=po(t);if(o){p(o),process.exitCode=1;return}if(!fo(t)){p("No `infrastructure.compute` configured — nothing to deploy as a server."),a("Add an EC2 compute block to cloud.config, or use `cloud deploy:serverless` for Lambda apps."),process.exitCode=1;return}if(a(`Region: ${i}`),a(`Environment: ${n}`),!await uo(t,n,i,e?.site)){process.exitCode=1;return}if(t.sites){let c=Object.values(t.sites).map((l)=>l.domain).filter((l)=>!!l);if(c.length>0)M("Syncing CloudFront dynamic HTTP methods for app domains..."),await sr(c)}h(`
|
|
398
|
-
Server deployment complete!`)}catch(t){p(`Deployment failed: ${t.message}`),process.exitCode=1}}),r.command("deploy:serverless","Deploy a serverless application (http/queue/cli Lambda functions)").option("--env <environment>","Environment (production, staging, development)").option("--skip-build","Skip local build hooks").option("--skip-hooks","Skip remote deploy hooks (e.g. migrations)").option("--redeploy","Re-activate the last build without rebuilding").action(async(e)=>{try{let t=await P(),n=e?.env||"production",i=po(t);if(i){p(i),process.exitCode=1;return}let{deployServerlessApp:o,redeployServerlessApp:s}=await import("./chunk-0zwknfwp.js");if(e?.redeploy){await s(t,n);return}await o(t,n,{skipBuild:e?.skipBuild,skipDeployHooks:e?.skipHooks})}catch(t){p(`Deployment failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:rollback","Roll back a serverless app to the previous build").option("--env <environment>","Environment (production, staging, development)").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{rollbackServerlessApp:i}=await import("./chunk-0zwknfwp.js");await i(t,n)}catch(t){p(`Rollback failed: ${t.message}`),process.exitCode=1}}),r.command("deploy:rollback [site]","Roll a compute site back to a previous release").option("--env <environment>","Environment (production, staging, development)").option("--to <release>","Release id to roll back to (default: the previous release)").action(async(e,t)=>{w("Rolling Back Release");try{let n=await P(),i=t?.env||"production",o=e||Object.keys(n.sites||{})[0];if(!o){p("No site configured to roll back."),process.exitCode=1;return}let{rollbackComputeSite:s}=await import("./chunk-104xrc2w.js"),c=await s({driver:
|
|
398
|
+
Server deployment complete!`)}catch(t){p(`Deployment failed: ${t.message}`),process.exitCode=1}}),r.command("deploy:serverless","Deploy a serverless application (http/queue/cli Lambda functions)").option("--env <environment>","Environment (production, staging, development)").option("--skip-build","Skip local build hooks").option("--skip-hooks","Skip remote deploy hooks (e.g. migrations)").option("--redeploy","Re-activate the last build without rebuilding").action(async(e)=>{try{let t=await P(),n=e?.env||"production",i=po(t);if(i){p(i),process.exitCode=1;return}let{deployServerlessApp:o,redeployServerlessApp:s}=await import("./chunk-0zwknfwp.js");if(e?.redeploy){await s(t,n);return}await o(t,n,{skipBuild:e?.skipBuild,skipDeployHooks:e?.skipHooks})}catch(t){p(`Deployment failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:rollback","Roll back a serverless app to the previous build").option("--env <environment>","Environment (production, staging, development)").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{rollbackServerlessApp:i}=await import("./chunk-0zwknfwp.js");await i(t,n)}catch(t){p(`Rollback failed: ${t.message}`),process.exitCode=1}}),r.command("deploy:rollback [site]","Roll a compute site back to a previous release").option("--env <environment>","Environment (production, staging, development)").option("--to <release>","Release id to roll back to (default: the previous release)").action(async(e,t)=>{w("Rolling Back Release");try{let n=await P(),i=t?.env||"production",o=e||Object.keys(n.sites||{})[0];if(!o){p("No site configured to roll back."),process.exitCode=1;return}let{rollbackComputeSite:s}=await import("./chunk-104xrc2w.js"),c=await s({driver:He({config:n}),slug:n.project.slug,environment:i,logger:z},{siteName:o,to:t?.to});if(!c.success){p(`Rollback failed: ${c.error||"unknown error"}`),process.exitCode=1;return}for(let l of c.perInstance||[])a(` ${l.instanceId}: ${l.output?.trim()||l.status}`)}catch(n){p(`Rollback failed: ${n.message}`),process.exitCode=1}}),r.command("deploy:history [site]","Show a compute site's deployment history").option("--env <environment>","Environment (production, staging, development)").option("--limit <n>","Number of entries to show",{default:"20"}).action(async(e,t)=>{w("Deployment History");try{let n=await P(),i=t?.env||"production",o=e||Object.keys(n.sites||{})[0];if(!o){p("No site configured."),process.exitCode=1;return}let{getComputeDeployHistory:s}=await import("./chunk-104xrc2w.js"),c=await s({driver:He({config:n}),slug:n.project.slug,environment:i,logger:z},{siteName:o,limit:Number.parseInt(t?.limit||"20",10)||20});if(!c.success){p(`Could not read history: ${c.error||"unknown error"}`),process.exitCode=1;return}for(let l of c.perInstance||[])a(`
|
|
399
399
|
${l.instanceId}:`),a(l.output?.trimEnd()||"(no output)")}catch(n){p(`History lookup failed: ${n.message}`),process.exitCode=1}}),r.command("deploy:recipe <name> <script>","Run a reusable server recipe (a local bash file) across servers").option("--env <environment>","Environment (production, staging, development)").option("--user <user>","User to run the recipe as",{default:"root"}).action(async(e,t,n)=>{w(`Running Recipe: ${e}`);try{if(!ge(t)){p(`Recipe script not found: ${t}`),process.exitCode=1;return}let i=await P(),o=n?.env||"production",s=go(t,"utf8").split(`
|
|
400
|
-
`),{runComputeRecipe:c}=await import("./chunk-104xrc2w.js"),l=await c({driver:
|
|
401
|
-
${d.instanceId}:`),a(d.output?.trimEnd()||"(no output)");if(!l.success)process.exitCode=1}catch(i){p(`Recipe failed: ${i.message}`),process.exitCode=1}}),r.command("db:restore-backup [from]","Restore the app database from an on-box ts-backups dump (latest, or a given file)").option("--env <environment>","Environment (production, staging, development)").action(async(e,t)=>{w("Database Restore");try{let n=await P(),i=t?.env||"production",{restoreDatabaseBackup:o}=await import("./chunk-104xrc2w.js"),s=await o({driver:
|
|
400
|
+
`),{runComputeRecipe:c}=await import("./chunk-104xrc2w.js"),l=await c({driver:He({config:i}),slug:i.project.slug,environment:o,logger:z},{name:e,script:s,user:n?.user});for(let d of l.perInstance||[])a(`
|
|
401
|
+
${d.instanceId}:`),a(d.output?.trimEnd()||"(no output)");if(!l.success)process.exitCode=1}catch(i){p(`Recipe failed: ${i.message}`),process.exitCode=1}}),r.command("db:restore-backup [from]","Restore the app database from an on-box ts-backups dump (latest, or a given file)").option("--env <environment>","Environment (production, staging, development)").action(async(e,t)=>{w("Database Restore");try{let n=await P(),i=t?.env||"production",{restoreDatabaseBackup:o}=await import("./chunk-104xrc2w.js"),s=await o({driver:He({config:n}),slug:n.project.slug,environment:i,logger:z},{database:Fn(n),from:e});for(let c of s.perInstance||[])a(` ${c.instanceId}: ${c.output?.trim()||c.status}`);if(!s.success)p(`Restore failed: ${s.error||"unknown error"}`),process.exitCode=1}catch(n){p(`Restore failed: ${n.message}`),process.exitCode=1}}),r.command("quick-deploy","Generate a push-to-deploy CI pipeline for your git provider (Forge Quick Deploy)").option("--env <environment>","Environment to deploy on push",{default:"production"}).option("--provider <provider>","CI provider (github, gitlab, bitbucket); defaults to the origin remote").option("--site <site>","Deploy only this configured site").option("--skip-dns-verification","Generate a deploy that skips DNS verification and record creation").option("--ssh-key-secret <name>","CI secret/variable containing the Hetzner deployment SSH private key",{default:"SSH_PRIVATE_KEY"}).option("--force","Overwrite an existing pipeline file").action(async(e)=>{w("Quick Deploy (push-to-deploy)");try{let t=await P(),{buildQuickDeployCi:n,inferQuickDeployProvider:i}=await import("./chunk-hq9x489e.js"),o=e?.provider?.toLowerCase();if(o&&!["github","gitlab","bitbucket"].includes(o))throw Error(`Unsupported CI provider '${e?.provider}'. Use github, gitlab, or bitbucket.`);let s=o;if(!s)try{let l=Po("git remote get-url origin",{stdio:["ignore","pipe","ignore"]}).toString().trim();s=i(l)}catch{}let c=n(t,e?.env||"production",{provider:s,site:e?.site,skipDnsVerification:e?.skipDnsVerification,setup:ge("pantry.lock")?"pantry":"bun",sshPrivateKeySecret:e?.sshKeySecret});if(!c){R("Could not resolve a GitHub, GitLab, or Bitbucket provider from --provider, the origin remote, or a configured site repository."),process.exitCode=1;return}if(ge(c.path)&&!e?.force){R(`${c.path} already exists — re-run with --force to overwrite.`);return}yl(Ym(c.path),{recursive:!0}),Jm(c.path,c.content),h(`Wrote ${c.path} (${c.provider}, deploys on push to '${c.branch}').`),a("Next: commit it and add your provider credentials as CI secrets/variables.")}catch(t){p(`Quick deploy setup failed: ${t.message}`),process.exitCode=1}}),r.command("down","Put the serverless app into maintenance mode (503)").option("--env <environment>","Environment (production, staging, development)").option("--secret <secret>","Bypass secret (send as x-maintenance-bypass header)").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{setMaintenance:i}=await import("./chunk-0zwknfwp.js");await i(t,n,!0,e?.secret)}catch(t){p(`Failed to enable maintenance mode: ${t.message}`),process.exitCode=1}}),r.command("up","Bring the serverless app out of maintenance mode").option("--env <environment>","Environment (production, staging, development)").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{setMaintenance:i}=await import("./chunk-0zwknfwp.js");await i(t,n,!1)}catch(t){p(`Failed to disable maintenance mode: ${t.message}`),process.exitCode=1}}),r.command("command <cmd>","Run a command on the serverless CLI function (e.g. migrations)").option("--env <environment>","Environment (production, staging, development)").action(async(e,t)=>{try{let n=await P(),i=t?.env||"production",{runAndRecordCommand:o}=await import("./chunk-0zwknfwp.js"),{id:s,output:c}=await o(n,i,e);if(a(c),s>0)a(`
|
|
402
402
|
(recorded as #${s} — re-run with \`cloud command:again ${s}\`)`)}catch(n){p(`Command failed: ${n.message}`),process.exitCode=1}}),r.command("command:history","List recorded CLI-function command invocations").option("--env <environment>","Environment (production, staging, development)").option("--limit <n>","Show at most this many (most recent)",{default:"20"}).action(async(e)=>{try{let t=await P(),n=e?.env||"production",{listCommandHistory:i}=await import("./chunk-0zwknfwp.js"),o=await i(t,n);if(!o.length){a("No command history yet.");return}let s=Number(e?.limit??20);w(`Command history — ${t.project.slug} (${n})`),x(["#","When","Status","Command"],o.slice(-s).reverse().map((c)=>[String(c.id),c.timestamp,c.status,c.command]))}catch(t){p(`Failed to read history: ${t.message}`),process.exitCode=1}}),r.command("command:again [id]","Re-run a recorded command (defaults to the most recent)").option("--env <environment>","Environment (production, staging, development)").action(async(e,t)=>{try{let n=await P(),i=t?.env||"production",{getCommandRecord:o,runAndRecordCommand:s}=await import("./chunk-0zwknfwp.js"),c=await o(n,i,e?Number(e):void 0);if(!c){p(e?`No command #${e} in history.`:"No command history to re-run."),process.exitCode=1;return}a(`Re-running #${c.id}: ${c.command}`);let l=await s(n,i,c.command);if(a(l.output),l.id>0)a(`
|
|
403
|
-
(recorded as #${l.id})`)}catch(n){p(`Command failed: ${n.message}`),process.exitCode=1}}),r.command("serverless:db-shell <sql>","Run a SQL statement against a private serverless database (via the CLI function)").option("--env <environment>","Environment (production, staging, development)").action(async(e,t)=>{try{let n=await P(),i=t?.env||"production",{runDbQuery:o}=await import("./chunk-0zwknfwp.js"),s=await o(n,i,e);a(s)}catch(n){p(`Query failed: ${n.message}`),process.exitCode=1}}),r.command("serverless:db-scale <min> <max>","Rescale the serverless Aurora cluster (min/max ACUs)").option("--env <environment>","Environment (production, staging, development)").action(async(e,t,n)=>{w("Scaling serverless database");try{let i=await P(),o=n?.env||"production",s=Number(e),c=Number(t);if(!(s>0)||!(c>=s)){p("Provide valid capacities: min > 0 and max >= min (e.g. `serverless:db-scale 0.5 8`)."),process.exitCode=1;return}let{scaleServerlessDatabase:l}=await import("./chunk-0zwknfwp.js");await l(i,o,s,c)}catch(i){p(`Scale failed: ${i.message}`),process.exitCode=1}}),r.command("serverless:db-restore","Restore the serverless Aurora cluster to a point in time (as a new cluster)").option("--env <environment>","Environment (production, staging, development)").option("--to <timestamp>","Restore to this ISO-8601 time (UTC)").option("--latest","Restore to the latest restorable time").option("--target <id>","New cluster identifier (defaults to <cluster>-restore-<stamp>)").action(async(e)=>{w("Restoring serverless database");try{let t=await P(),n=e?.env||"production",i;if(e?.to){if(i=new Date(e.to),Number.isNaN(i.getTime())){p(`Invalid --to timestamp: ${e.to} (use ISO-8601, e.g. 2026-06-18T10:00:00Z).`),process.exitCode=1;return}}let{restoreServerlessDatabase:o}=await import("./chunk-0zwknfwp.js");await o(t,n,{toTime:i,latest:e?.latest,target:e?.target})}catch(t){p(`Restore failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:info","Show an operational summary of a deployed serverless app").option("--env <environment>","Environment (production, staging, development)").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{serverlessInfo:i}=await import("./chunk-0zwknfwp.js"),o=await i(t,n);if(w(`${o.slug} (${o.environment})`),a(`Stack: ${o.stackStatus}`),a(`Region: ${o.region}`),o.endpoint)a(`URL: ${o.endpoint}`);if(o.assetUrl)a(`Assets: ${o.assetUrl}`);if(a(`Scheduler:${o.scheduler==="off"?" off":` ${o.scheduler}`}`),a(`Queues: ${o.queues.length?o.queues.join(", "):"none"}`),a(`Warming: ${o.provisionedConcurrency>0?`provisioned concurrency ×${o.provisionedConcurrency}`:"none / ping-warm"}`),o.lastRelease)a(`Release: ${o.lastRelease.sha.slice(0,12)} @ ${o.lastRelease.timestamp}`);x(["Function","Version","Provisioned"],o.functions.map((s)=>[s.name,s.version,s.provisioned?`${s.provisioned.status} ${s.provisioned.allocated}/${s.provisioned.requested}`:"—"]))}catch(t){p(`Failed to read info: ${t.message}`),process.exitCode=1}}),r.command("dashboard:build","Build the management dashboard with live data baked in").option("--env <environment>","Environment (production, staging, development)").option("--ui <dir>","UI directory (default ./ui)",{default:"ui"}).action(async(e)=>{w("Building management dashboard (live data)");try{let{existsSync:t}=await import("node:fs"),n=e?.ui||"ui";if(!t(`${n}/package.json`)){p(`No UI project at ${n}/ (expected ${n}/package.json). Pass --ui <dir>.`),process.exitCode=1;return}let i=await P(),o=e?.env||"production";M("Gathering live data from AWS");let s=null;if(i.environments?.[o]?.app){let{resolveDashboardData:u}=await import("./chunk-f06c5np9.js"),m=await u(i,o).catch((f)=>(R(`Serverless data unavailable (${f.message}) — that view uses sample data.`),null));if(m)s={...s??{},...m}}if(i.infrastructure?.compute){let{resolveServerDashboardData:u}=await import("./chunk-m189194f.js"),m=await u(i,o).catch(()=>null);if(m){if(m._serverReachable===!1)R("Server box not reachable over SSM — server view uses config + sample data.");s={...s??{},...m}}}M("Building dashboard");let{encodeDashboardPayload:c}=await import("./chunk-djg4zyqk.js"),{execSync:l}=await import("node:child_process"),{rmSync:d}=await import("node:fs");for(let u of[".stx/ssg-cache",".stx/cache"])d(`${n}/${u}`,{recursive:!0,force:!0});l("bun run build",{cwd:n,stdio:"inherit",env:{...process.env,...s?{TSCLOUD_DASHBOARD_DATA:c(s)}:{}}}),h(`Dashboard built → ${n}/dist${s?" (live data)":" (sample data)"}`)}catch(t){p(`Dashboard build failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:build-php-layer","Build + publish the ts-cloud PHP runtime layer (requires Docker)").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--php <version>","PHP version",{default:"8.3"}).option("--name <name>","Layer name",{default:"tscloud-php"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(e)=>{w("Building PHP runtime layer");try{let t=await P().catch(()=>null),n=e?.region||t?.project.region||"us-east-1",i=e?.arch||"x86_64",o=e?.php||"8.3",s=`${e?.name||"tscloud-php"}-${o.replace(".","")}-${i}`,c=e?.bucket||`${t?.project.slug||"tscloud"}-layers`,{buildPhpRuntimeLayerZip:l}=await import("./chunk-k3120rx6.js"),{S3Client:d}=await import("./chunk-zh2d4704.js"),{LambdaClient:u}=await import("./chunk-dkaz333b.js"),m=l({architecture:i,phpVersion:o,onStep:(b)=>M(b)});a(`Layer: ${m.fileCount} files, ${(m.zip.length/1024/1024).toFixed(1)} MB`);let f=new d(n);if(!await f.bucketExists(c))M(`Creating layer bucket ${c}`),await f.createBucket(c);let y=`layers/${s}.zip`;M("Uploading layer zip"),await f.putObject({bucket:c,key:y,body:m.zip,contentType:"application/zip"}),M("Publishing layer version");let g=await new u(n).publishLayerVersion({LayerName:s,Description:`ts-cloud PHP ${o} runtime (${i})`,Content:{S3Bucket:c,S3Key:y},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[i]});
|
|
404
|
-
`),"green")}catch(t){p(`Layer build failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:build-node-layer","Build + publish a ts-cloud Node custom runtime layer (any version, incl. 24)").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--node <version>","Node version (e.g. 24)",{default:"24"}).option("--name <name>","Layer name",{default:"tscloud-node"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(e)=>{w("Building Node runtime layer");try{let t=await P().catch(()=>null),n=e?.region||t?.project.region||"us-east-1",i=e?.arch||"x86_64",o=e?.node||"24",{buildNodeRuntimeLayerZip:s}=await import("./chunk-k3120rx6.js"),{S3Client:c}=await import("./chunk-zh2d4704.js"),{LambdaClient:l}=await import("./chunk-dkaz333b.js"),d=s({architecture:i,version:o,onStep:(b)=>M(b)}),u=`${e?.name||"tscloud-node"}-${d.version.split(".")[0]}-${i}`,m=e?.bucket||`${t?.project.slug||"tscloud"}-layers`;a(`Layer: Node ${d.version}, ${(d.zip.length/1024/1024).toFixed(1)} MB`);let f=new c(n);if(!await f.bucketExists(m))M(`Creating layer bucket ${m}`),await f.createBucket(m);let y=`layers/${u}.zip`;M("Uploading layer zip"),await f.putObject({bucket:m,key:y,body:d.zip,contentType:"application/zip"}),M("Publishing layer version");let g=await new l(n).publishLayerVersion({LayerName:u,Description:`ts-cloud Node ${d.version} runtime (${i})`,Content:{S3Bucket:m,S3Key:y},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[i]});
|
|
405
|
-
`),"green")}catch(t){p(`Layer build failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:build-bun-layer","Build + publish a ts-cloud Bun custom runtime layer").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--bun <version>",'Bun version (e.g. 1.3.13, or "latest")',{default:"latest"}).option("--name <name>","Layer name",{default:"tscloud-bun"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(e)=>{w("Building Bun runtime layer");try{let t=await P().catch(()=>null),n=e?.region||t?.project.region||"us-east-1",i=e?.arch||"x86_64",o=e?.bun||"latest",{buildBunRuntimeLayerZip:s}=await import("./chunk-k3120rx6.js"),{S3Client:c}=await import("./chunk-zh2d4704.js"),{LambdaClient:l}=await import("./chunk-dkaz333b.js"),d=s({architecture:i,version:o,onStep:(b)=>M(b)}),u=`${e?.name||"tscloud-bun"}-${d.version.replace(/\./g,"")}-${i}`,m=e?.bucket||`${t?.project.slug||"tscloud"}-layers`;a(`Layer: Bun ${d.version}, ${(d.zip.length/1024/1024).toFixed(1)} MB`);let f=new c(n);if(!await f.bucketExists(m))M(`Creating layer bucket ${m}`),await f.createBucket(m);let y=`layers/${u}.zip`;M("Uploading layer zip"),await f.putObject({bucket:m,key:y,body:d.zip,contentType:"application/zip"}),M("Publishing layer version");let g=await new l(n).publishLayerVersion({LayerName:u,Description:`ts-cloud Bun ${d.version} runtime (${i})`,Content:{S3Bucket:m,S3Key:y},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[i]});
|
|
403
|
+
(recorded as #${l.id})`)}catch(n){p(`Command failed: ${n.message}`),process.exitCode=1}}),r.command("serverless:db-shell <sql>","Run a SQL statement against a private serverless database (via the CLI function)").option("--env <environment>","Environment (production, staging, development)").action(async(e,t)=>{try{let n=await P(),i=t?.env||"production",{runDbQuery:o}=await import("./chunk-0zwknfwp.js"),s=await o(n,i,e);a(s)}catch(n){p(`Query failed: ${n.message}`),process.exitCode=1}}),r.command("serverless:db-scale <min> <max>","Rescale the serverless Aurora cluster (min/max ACUs)").option("--env <environment>","Environment (production, staging, development)").action(async(e,t,n)=>{w("Scaling serverless database");try{let i=await P(),o=n?.env||"production",s=Number(e),c=Number(t);if(!(s>0)||!(c>=s)){p("Provide valid capacities: min > 0 and max >= min (e.g. `serverless:db-scale 0.5 8`)."),process.exitCode=1;return}let{scaleServerlessDatabase:l}=await import("./chunk-0zwknfwp.js");await l(i,o,s,c)}catch(i){p(`Scale failed: ${i.message}`),process.exitCode=1}}),r.command("serverless:db-restore","Restore the serverless Aurora cluster to a point in time (as a new cluster)").option("--env <environment>","Environment (production, staging, development)").option("--to <timestamp>","Restore to this ISO-8601 time (UTC)").option("--latest","Restore to the latest restorable time").option("--target <id>","New cluster identifier (defaults to <cluster>-restore-<stamp>)").action(async(e)=>{w("Restoring serverless database");try{let t=await P(),n=e?.env||"production",i;if(e?.to){if(i=new Date(e.to),Number.isNaN(i.getTime())){p(`Invalid --to timestamp: ${e.to} (use ISO-8601, e.g. 2026-06-18T10:00:00Z).`),process.exitCode=1;return}}let{restoreServerlessDatabase:o}=await import("./chunk-0zwknfwp.js");await o(t,n,{toTime:i,latest:e?.latest,target:e?.target})}catch(t){p(`Restore failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:info","Show an operational summary of a deployed serverless app").option("--env <environment>","Environment (production, staging, development)").action(async(e)=>{try{let t=await P(),n=e?.env||"production",{serverlessInfo:i}=await import("./chunk-0zwknfwp.js"),o=await i(t,n);if(w(`${o.slug} (${o.environment})`),a(`Stack: ${o.stackStatus}`),a(`Region: ${o.region}`),o.endpoint)a(`URL: ${o.endpoint}`);if(o.assetUrl)a(`Assets: ${o.assetUrl}`);if(a(`Scheduler:${o.scheduler==="off"?" off":` ${o.scheduler}`}`),a(`Queues: ${o.queues.length?o.queues.join(", "):"none"}`),a(`Warming: ${o.provisionedConcurrency>0?`provisioned concurrency ×${o.provisionedConcurrency}`:"none / ping-warm"}`),o.lastRelease)a(`Release: ${o.lastRelease.sha.slice(0,12)} @ ${o.lastRelease.timestamp}`);x(["Function","Version","Provisioned"],o.functions.map((s)=>[s.name,s.version,s.provisioned?`${s.provisioned.status} ${s.provisioned.allocated}/${s.provisioned.requested}`:"—"]))}catch(t){p(`Failed to read info: ${t.message}`),process.exitCode=1}}),r.command("dashboard:build","Build the management dashboard with live data baked in").option("--env <environment>","Environment (production, staging, development)").option("--ui <dir>","UI directory (default ./ui)",{default:"ui"}).action(async(e)=>{w("Building management dashboard (live data)");try{let{existsSync:t}=await import("node:fs"),n=e?.ui||"ui";if(!t(`${n}/package.json`)){p(`No UI project at ${n}/ (expected ${n}/package.json). Pass --ui <dir>.`),process.exitCode=1;return}let i=await P(),o=e?.env||"production";M("Gathering live data from AWS");let s=null;if(i.environments?.[o]?.app){let{resolveDashboardData:u}=await import("./chunk-f06c5np9.js"),m=await u(i,o).catch((f)=>(R(`Serverless data unavailable (${f.message}) — that view uses sample data.`),null));if(m)s={...s??{},...m}}if(i.infrastructure?.compute){let{resolveServerDashboardData:u}=await import("./chunk-m189194f.js"),m=await u(i,o).catch(()=>null);if(m){if(m._serverReachable===!1)R("Server box not reachable over SSM — server view uses config + sample data.");s={...s??{},...m}}}M("Building dashboard");let{encodeDashboardPayload:c}=await import("./chunk-djg4zyqk.js"),{execSync:l}=await import("node:child_process"),{rmSync:d}=await import("node:fs");for(let u of[".stx/ssg-cache",".stx/cache"])d(`${n}/${u}`,{recursive:!0,force:!0});l("bun run build",{cwd:n,stdio:"inherit",env:{...process.env,...s?{TSCLOUD_DASHBOARD_DATA:c(s)}:{}}}),h(`Dashboard built → ${n}/dist${s?" (live data)":" (sample data)"}`)}catch(t){p(`Dashboard build failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:build-php-layer","Build + publish the ts-cloud PHP runtime layer (requires Docker)").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--php <version>","PHP version",{default:"8.3"}).option("--name <name>","Layer name",{default:"tscloud-php"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(e)=>{w("Building PHP runtime layer");try{let t=await P().catch(()=>null),n=e?.region||t?.project.region||"us-east-1",i=e?.arch||"x86_64",o=e?.php||"8.3",s=`${e?.name||"tscloud-php"}-${o.replace(".","")}-${i}`,c=e?.bucket||`${t?.project.slug||"tscloud"}-layers`,{buildPhpRuntimeLayerZip:l}=await import("./chunk-k3120rx6.js"),{S3Client:d}=await import("./chunk-zh2d4704.js"),{LambdaClient:u}=await import("./chunk-dkaz333b.js"),m=l({architecture:i,phpVersion:o,onStep:(b)=>M(b)});a(`Layer: ${m.fileCount} files, ${(m.zip.length/1024/1024).toFixed(1)} MB`);let f=new d(n);if(!await f.bucketExists(c))M(`Creating layer bucket ${c}`),await f.createBucket(c);let y=`layers/${s}.zip`;M("Uploading layer zip"),await f.putObject({bucket:c,key:y,body:m.zip,contentType:"application/zip"}),M("Publishing layer version");let g=await new u(n).publishLayerVersion({LayerName:s,Description:`ts-cloud PHP ${o} runtime (${i})`,Content:{S3Bucket:c,S3Key:y},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[i]});Xe(["PHP runtime layer published","",`ARN: ${g.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'php', layers: ['${g.LayerVersionArn}'] }`,"or set TSCLOUD_PHP_LAYER_ARN before deploying."].join(`
|
|
404
|
+
`),"green")}catch(t){p(`Layer build failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:build-node-layer","Build + publish a ts-cloud Node custom runtime layer (any version, incl. 24)").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--node <version>","Node version (e.g. 24)",{default:"24"}).option("--name <name>","Layer name",{default:"tscloud-node"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(e)=>{w("Building Node runtime layer");try{let t=await P().catch(()=>null),n=e?.region||t?.project.region||"us-east-1",i=e?.arch||"x86_64",o=e?.node||"24",{buildNodeRuntimeLayerZip:s}=await import("./chunk-k3120rx6.js"),{S3Client:c}=await import("./chunk-zh2d4704.js"),{LambdaClient:l}=await import("./chunk-dkaz333b.js"),d=s({architecture:i,version:o,onStep:(b)=>M(b)}),u=`${e?.name||"tscloud-node"}-${d.version.split(".")[0]}-${i}`,m=e?.bucket||`${t?.project.slug||"tscloud"}-layers`;a(`Layer: Node ${d.version}, ${(d.zip.length/1024/1024).toFixed(1)} MB`);let f=new c(n);if(!await f.bucketExists(m))M(`Creating layer bucket ${m}`),await f.createBucket(m);let y=`layers/${u}.zip`;M("Uploading layer zip"),await f.putObject({bucket:m,key:y,body:d.zip,contentType:"application/zip"}),M("Publishing layer version");let g=await new l(n).publishLayerVersion({LayerName:u,Description:`ts-cloud Node ${d.version} runtime (${i})`,Content:{S3Bucket:m,S3Key:y},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[i]});Xe(["Node runtime layer published","",`ARN: ${g.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'node', runtimeVersion: '${d.version.split(".")[0]}', layers: ['${g.LayerVersionArn}'] }`,"or set TSCLOUD_NODE_LAYER_ARN before deploying."].join(`
|
|
405
|
+
`),"green")}catch(t){p(`Layer build failed: ${t.message}`),process.exitCode=1}}),r.command("serverless:build-bun-layer","Build + publish a ts-cloud Bun custom runtime layer").option("--arch <architecture>","x86_64 or arm64",{default:"x86_64"}).option("--bun <version>",'Bun version (e.g. 1.3.13, or "latest")',{default:"latest"}).option("--name <name>","Layer name",{default:"tscloud-bun"}).option("--bucket <bucket>","S3 bucket to stage the layer zip (defaults to {slug}-layers)").option("--region <region>","AWS region").action(async(e)=>{w("Building Bun runtime layer");try{let t=await P().catch(()=>null),n=e?.region||t?.project.region||"us-east-1",i=e?.arch||"x86_64",o=e?.bun||"latest",{buildBunRuntimeLayerZip:s}=await import("./chunk-k3120rx6.js"),{S3Client:c}=await import("./chunk-zh2d4704.js"),{LambdaClient:l}=await import("./chunk-dkaz333b.js"),d=s({architecture:i,version:o,onStep:(b)=>M(b)}),u=`${e?.name||"tscloud-bun"}-${d.version.replace(/\./g,"")}-${i}`,m=e?.bucket||`${t?.project.slug||"tscloud"}-layers`;a(`Layer: Bun ${d.version}, ${(d.zip.length/1024/1024).toFixed(1)} MB`);let f=new c(n);if(!await f.bucketExists(m))M(`Creating layer bucket ${m}`),await f.createBucket(m);let y=`layers/${u}.zip`;M("Uploading layer zip"),await f.putObject({bucket:m,key:y,body:d.zip,contentType:"application/zip"}),M("Publishing layer version");let g=await new l(n).publishLayerVersion({LayerName:u,Description:`ts-cloud Bun ${d.version} runtime (${i})`,Content:{S3Bucket:m,S3Key:y},CompatibleRuntimes:["provided.al2023"],CompatibleArchitectures:[i]});Xe(["Bun runtime layer published","",`ARN: ${g.LayerVersionArn}`,"","Reference it in your config:",` app: { kind: 'bun', layers: ['${g.LayerVersionArn}'] }`,"or set TSCLOUD_BUN_LAYER_ARN before deploying."].join(`
|
|
406
406
|
`),"green")}catch(t){p(`Layer build failed: ${t.message}`),process.exitCode=1}}),r.command("deploy:status","Check deployment status").option("--stack <name>","Stack name").option("--env <environment>","Environment").action(async(e)=>{w("Deployment Status");try{let t=await P(),n=e?.env||"production",i=e?.stack||Ve(t,n),o=t.project.region||"us-east-1";a(`Stack: ${i}`),a(`Region: ${o}`);let s=new A("Checking deployment status...");s.start();let l=await new ie(o).describeStacks({stackName:i});if(l.Stacks.length===0){s.fail("Stack not found"),fn("No deployment found for this environment");return}let d=l.Stacks[0];if(s.succeed("Status retrieved"),a(`
|
|
407
407
|
Status: ${d.StackStatus}`),a(`Created: ${d.CreationTime}`),d.LastUpdatedTime)a(`Last Updated: ${d.LastUpdatedTime}`);if(d.Outputs&&d.Outputs.length>0){a(`
|
|
408
408
|
Outputs:`);for(let u of d.Outputs)a(` ${u.OutputKey}: ${u.OutputValue}`)}}catch(t){p(`Failed to get status: ${t.message}`)}}),r.command("deploy:rollback","Rollback to previous version").option("--stack <name>","Stack name").option("--env <environment>","Environment").action(async(e)=>{w("Rolling Back Deployment");try{let t=await P(),n=e?.env||"production",i=e?.stack||Ve(t,n),o=t.project.region||"us-east-1";if(a(`Stack: ${i}`),a(`Region: ${o}`),!await O(`
|
|
@@ -410,7 +410,7 @@ Are you sure you want to rollback?`,!1)){a("Rollback cancelled");return}let c=ne
|
|
|
410
410
|
`);let s=mo(t,void 0,o);if(!s.allowed){p(`
|
|
411
411
|
✗ Static deployment blocked by security policy: ${s.explanation}`),a("Remediate the findings or create an authorized, time-limited waiver in the Security posture center."),process.exitCode=1;return}h(`✓ Security policy allows this deployment
|
|
412
412
|
`);let c=e?.bucket,l=e?.distribution,d=e?.prefix,u=e?.delete||!1,m=e?.cacheControl||"public, max-age=31536000",f=e?.invalidate!==!1,y=e?.wait||!1;if(!c){p("--bucket is required");return}if(!ge(i)){p(`Source directory not found: ${i}`);return}if(a(`Source: ${i}`),a(`Bucket: s3://${c}${d?`/${d}`:""}`),a(`Cache-Control: ${m}`),l)a(`CloudFront Distribution: ${l}`);if(u)R("Delete mode enabled - files not in source will be removed");if(!await O(`
|
|
413
|
-
Deploy static site now?`,!0)){a("Deployment cancelled");return}let g=new de(n),b=new A("Uploading files to S3...");b.start(),await g.sync({source:i,bucket:c,prefix:d,delete:u,cacheControl:m,acl:"public-read"}),b.succeed("Files uploaded successfully!");let v=(await g.getBucketSize(c,d)/1024/1024).toFixed(2);if(a(`Total size: ${v} MB`),f&&l){let C=new ne,k=new A("Invalidating CloudFront cache...");k.start();let T=await C.invalidateAll(l);if(k.succeed("Invalidation created"),a(`Invalidation ID: ${T.Id}`),y){let E=new A("Waiting for invalidation to complete...");E.start(),await C.waitForInvalidation(l,T.Id),E.succeed("Invalidation completed!")}}
|
|
413
|
+
Deploy static site now?`,!0)){a("Deployment cancelled");return}let g=new de(n),b=new A("Uploading files to S3...");b.start(),await g.sync({source:i,bucket:c,prefix:d,delete:u,cacheControl:m,acl:"public-read"}),b.succeed("Files uploaded successfully!");let v=(await g.getBucketSize(c,d)/1024/1024).toFixed(2);if(a(`Total size: ${v} MB`),f&&l){let C=new ne,k=new A("Invalidating CloudFront cache...");k.start();let T=await C.invalidateAll(l);if(k.succeed("Invalidation created"),a(`Invalidation ID: ${T.Id}`),y){let E=new A("Waiting for invalidation to complete...");E.start(),await C.waitForInvalidation(l,T.Id),E.succeed("Invalidation completed!")}}Xe(`Static Site Deployed!
|
|
414
414
|
|
|
415
415
|
Source: ${i}
|
|
416
416
|
Bucket: s3://${c}${d?`/${d}`:""}
|
|
@@ -434,14 +434,14 @@ Existing CNAME record detected:`),a(` ${B.name} -> ${B.content}`),V||K)a(`
|
|
|
434
434
|
This domain is currently pointing to ${G}.`),a("Deploying will update this record to point to AWS CloudFront.");if(!(s||await O(`
|
|
435
435
|
Update DNS record to point to AWS CloudFront?`,!0))){a("Deployment cancelled");continue}M(`Removing old ${G} CNAME record...`);let H=await S.provider.deleteRecord(g,{name:B.name,type:"CNAME",content:B.content});if(H.success)h(`Removed CNAME: ${B.name} -> ${B.content}`);else R(`Could not remove old record: ${H.message}`),a("The deployment will attempt to update it instead.")}}}let I=y.root,v,C=!!y.installScript;if(C){let{mkdtempSync:D,copyFileSync:N,readdirSync:_,statSync:B}=await import("node:fs"),{tmpdir:V}=await import("node:os"),{join:K,resolve:oe}=await import("node:path");v=D(K(V(),"cloud-install-"));let G=oe(y.installScript);N(G,K(v,"index.html"));let L=G.split("/").pop();if(L!=="index.html")N(G,K(v,L));if(ge(y.root)){let H=_(y.root);for(let j of H){let W=K(y.root,j);if(B(W).isFile()&&j!=="index.html")N(W,K(v,j))}}I=v,a(`Install script: ${y.installScript}`),a(`Serving at: curl -fsSL https://${g} | bash`)}M("Deploying to AWS (S3 + CloudFront)...");let k=al(r,f,y,o),T=cl(r,f);a(`Stack: ${k}`);let E=await lr({siteName:T,stackName:k,domain:g,region:n,bucket:ll(r.project.slug,o,f,y.bucket),sourceDir:I,certificateArn:y.certificateArn,dnsProvider:S.config,skipDnsVerification:i,passthroughUrls:C,dynamicApp:!!r.infrastructure?.compute?.cloudFrontOriginDomain,computeOriginDomain:r.infrastructure?.compute?.cloudFrontOriginDomain,computeOriginPort:r.infrastructure?.compute?.cloudFrontOriginPort??r.infrastructure?.api?.port??3008,computeOriginId:r.infrastructure?.compute?.cloudFrontOriginId??`${r.project.slug}-site-ec2`,onProgress:(D,N)=>{if(D==="infrastructure")M(N||"Setting up infrastructure...");else if(D==="upload"){if(N?.includes("/")&&!N.includes("1/")){let _=N.match(/(\d+)\/(\d+)/);if(_){let[,B,V]=_;if(Number(B)%10===0||B===V)a(` Uploaded ${B}/${V} files`)}}}else if(D==="invalidate")M("Invalidating CDN cache...")}});if(v){let{rmSync:D}=await import("node:fs");D(v,{recursive:!0,force:!0})}if(E.success){h(`
|
|
436
436
|
Deployment successful!`);let D=E.filesSkipped>0?`${E.filesUploaded} uploaded, ${E.filesSkipped} unchanged`:`${E.filesUploaded}`,N=C?`
|
|
437
|
-
Install: curl -fsSL https://${E.domain} | bash`:"";
|
|
437
|
+
Install: curl -fsSL https://${E.domain} | bash`:"";Xe(`Site Deployed!
|
|
438
438
|
|
|
439
439
|
Domain: https://${E.domain}
|
|
440
440
|
CloudFront: ${E.distributionDomain}
|
|
441
441
|
Bucket: ${E.bucket}
|
|
442
442
|
Files: ${D}${N}
|
|
443
443
|
|
|
444
|
-
Your site is now live at https://${E.domain}`,"green")}else throw Error(E.message||`Site '${f}' deployment failed`)}}import{execFileSync as cp}from"node:child_process";import{createHash as lp}from"node:crypto";import{existsSync as $l,mkdirSync as dp,mkdtempSync as up,readdirSync as mp,readFileSync as pp,rmSync as fp,statSync as Cl,writeFileSync as gp}from"node:fs";import{tmpdir as hp}from"node:os";import{basename as yp,join as Zt,relative as vp,resolve as Eo}from"node:path";var bp=new Set([".git","node_modules","dist","coverage"]);function Al(r,e=r){let t=[];for(let n of mp(r).sort()){if(bp.has(n))continue;let i=Zt(r,n);if(Sl(i,e))continue;let o=Cl(i);if(o.isDirectory())t.push(...Al(i,e));else if(o.isFile())t.push(vp(e,i))}return t}function wp(r){let e=Eo(r);if(!$l(e)||!Cl(e).isDirectory())throw Error(`Container context does not exist: ${e}`);let t=lp("sha256");for(let n of Al(e))t.update(n),t.update("\x00"),t.update(pp(Zt(e,n))),t.update("\x00");return t.digest("hex")}function Sp(r){return{ecr:new Rn(r.region||"us-east-1",r.profile),run:(e,t,n)=>{cp(e,t,{cwd:n?.cwd,env:n?.env,stdio:n?.stdio||"inherit"})}}}async function kl(r,e){let t=Eo(r.context),n=Eo(r.dockerfile||Zt(t,"Dockerfile"));if(!$l(n))throw Error(`Dockerfile does not exist: ${n}`);if(!r.repository.split("/").every((S)=>/^[a-z0-9][a-z0-9._-]*$/.test(S)&&!/[._-]$/.test(S)&&!/[._-]{2}/.test(S)))throw Error("ECR repository name is invalid");let s=e||Sp(r),c=wp(t),l=`sha-${c.slice(0,20)}`,d="";try{d=(await s.ecr.describeRepositories({repositoryNames:[r.repository]})).repositories?.[0]?.repositoryUri||""}catch{}if(!d)d=(await s.ecr.createRepository({repositoryName:r.repository,imageTagMutability:"IMMUTABLE",imageScanningConfiguration:{scanOnPush:!0},tags:[{Key:"ManagedBy",Value:"ts-cloud"}]})).repository?.repositoryUri||"";if(!d)throw Error(`Could not resolve ECR repository URI for ${r.repository}`);await s.ecr.putLifecyclePolicy({repositoryName:r.repository,lifecyclePolicyText:JSON.stringify({rules:[{rulePriority:1,description:"Retain the newest 25 release images",selection:{tagStatus:"any",countType:"imageCountMoreThan",countNumber:25},action:{type:"expire"}}]})});let u=`${d}:${l}`,m=up(Zt(hp(),"ts-cloud-container-"));try{s.run("docker",["version"],{stdio:"ignore"}),s.run("docker",["build","--platform",r.platform||"linux/amd64","--provenance=false","--sbom=false","--file",n,"--tag",u,t],{cwd:t});let S=(await s.ecr.getAuthorizationToken()).authorizationData?.[0];if(!S?.authorizationToken||!S.proxyEndpoint)throw Error("ECR did not return Docker authorization data");let g=S.proxyEndpoint.replace(/^https?:\/\//,""),b=Zt(m,"docker");dp(b,{recursive:!0}),gp(Zt(b,"config.json"),JSON.stringify({auths:{[g]:{auth:S.authorizationToken}}}),{mode:384}),s.run("docker",["push",u],{env:{...process.env,DOCKER_CONFIG:b}})}finally{fp(m,{recursive:!0,force:!0})}let y=(await s.ecr.describeImages({repositoryName:r.repository,imageIds:[{imageTag:l}]})).imageDetails?.[0]?.imageDigest;if(!y||!/^sha256:[a-f0-9]{64}$/i.test(y))throw Error(`ECR did not return an immutable digest for ${yp(u)}`);return{repository:r.repository,tag:l,imageUri:u,digest:y,digestUri:`${d}@${y}`,contextSha256:c}}import{createHash as $p}from"node:crypto";function Cp(r){let e=r;return String(e?.code||e?.name||e?.statusCode||"")}function Ap(r){return Cp(r)==="ValidationError"&&String(r.message).includes("does not exist")}function kp(r){return Object.fromEntries((r||[]).map((e)=>[e.OutputKey,e.OutputValue]))}function Rp(r){let e=r?.Aliases?.Items??r?.Aliases;if(Array.isArray(e))return e.map(String);if(typeof e==="string")return[e];return[]}function Pl(r){if(!/^[a-z][a-z0-9-]{1,62}$/.test(r.slug))throw Error("Slug must be a lowercase DNS-safe identifier");if(!/^[A-Z0-9]{8,32}$/.test(r.distributionId))throw Error("CloudFront distribution ID is invalid");if(!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9-]{2,63}$/.test(r.expectedAlias))throw Error("Expected distribution alias is invalid");let e=r.imageUri.match(/@((?:sha256:)[a-f0-9]{64})$/i)?.[1];if(!e)throw Error("Container image must use an immutable ECR digest URI ending in @sha256:<64 hex>");let t=r.stackName||`${r.slug}-backend`;if(!/^[A-Za-z][A-Za-z0-9-]{0,127}$/.test(t))throw Error("CloudFormation stack name is invalid");let n=r.pathPattern||"/api/*";if(!/^\/[A-Za-z0-9_.*?~!$&'()+,;=:@%/-]+\*$/.test(n)||n==="/*")throw Error("Backend path must be a non-default wildcard pattern");let i=r.originId||`${r.slug}-backend`;if(!/^[A-Za-z0-9_.-]{1,128}$/.test(i))throw Error("CloudFront origin ID is invalid");if(r.originVerifySecret&&r.originVerifySecret.length<24)throw Error("Origin verification secret must contain at least 24 characters");if(!!r.originDomain!==!!r.certificateArn)throw Error("A custom origin domain and matching ACM certificate ARN must be provided together");return{stackName:t,region:r.region||"us-east-1",pathPattern:n,originId:i,digest:e}}function Do(r){let e=r.region||"us-east-1";return{cloudformation:new ie(e,r.profile),cloudfront:new ne(r.profile),sts:new Dt(e,r.profile),fetch,sleep:(t)=>new Promise((n)=>setTimeout(n,t))}}function Ip(r){let e=Pl(r),t=Il({name:r.name,slug:r.slug,domain:r.originDomain||r.expectedAlias,imageUri:r.imageUri,certificateArn:r.certificateArn,originVerifySecret:r.originVerifySecret,desiredCount:r.desiredCount,database:r.database,cache:r.cache,queue:r.queue}),n=Rl(t);return n.Description=`Managed backend for existing static frontend ${r.expectedAlias}`,n.Metadata={ManagedBy:"ts-cloud",ExistingDistributionId:r.distributionId,ExistingDistributionAlias:r.expectedAlias,BackendPathPattern:e.pathPattern,ContainerDigest:e.digest},n}async function Pp(r,e,t){let n;for(let i=0;i<30;i++){let o=Date.now();try{let s=await r.fetch(e,{redirect:"manual",headers:t?{"x-origin-verify":t}:void 0}),c=Date.now()-o,l=await s.text(),d=l;try{d=JSON.parse(l)}catch{}if(s.ok)return{url:e,status:s.status,latencyMs:c,body:d};n=Error(`origin returned ${s.status}`)}catch(s){n=s}if(i<29)await r.sleep(1e4)}throw Error(`Backend origin did not become healthy: ${n instanceof Error?n.message:String(n)}`)}async function xl(r,e){let t=Pl(r),n=e||Do({region:t.region,profile:r.profile}),[i,o]=await Promise.all([n.sts.getCallerIdentity(),n.cloudfront.getDistribution(r.distributionId)]);if(!i.Account)throw Error("AWS identity did not return an account ID");if(!o.Enabled)throw Error(`CloudFront distribution ${r.distributionId} is disabled`);if(!Rp(o).includes(r.expectedAlias))throw Error(`Distribution ${r.distributionId} does not contain expected alias ${r.expectedAlias}`);let s;try{s=(await n.cloudformation.describeStacks({stackName:t.stackName})).Stacks[0]}catch(k){if(!Ap(k))throw k}let c=Ip(r),l=JSON.stringify(c);if(Buffer.byteLength(l)>51200)throw Error("Generated template exceeds the direct CloudFormation template body limit");let d=Object.values(c.Resources||{}).reduce((k,T)=>(k[T.Type]=(k[T.Type]||0)+1,k),{}),u=[`cloud cdn:origin:remove ${r.distributionId} ${r.originDomain||"ALB_HOST"} --id ${t.originId} --path '${t.pathPattern}' --profile ${r.profile||"default"} --apply --confirm 'remove:${r.distributionId}:${t.pathPattern}'`,`After CloudFront is deployed, restore external DNS if changed and delete stack ${t.stackName} only with an approved data-retention decision.`],m={mode:r.apply?"apply":"plan",accountId:i.Account,stack:{name:t.stackName,existed:!!s,status:s?.StackStatus,templateSha256:$p("sha256").update(l).digest("hex"),resourceCount:Object.keys(c.Resources||{}).length,resourceTypes:d},distribution:{id:o.Id,alias:r.expectedAlias,domainName:o.DomainName,status:o.Status,pathPattern:t.pathPattern,originId:t.originId},artifact:{imageUri:r.imageUri,digest:t.digest},services:{database:r.database!==!1,cache:r.cache!==!1,queue:r.queue!==!1,mail:"ses",desiredCount:r.desiredCount||1},cloudFrontApplied:!1,dnsApplied:!1,applied:!1,rollback:u};if(!r.apply)return m;let f=`${r.distributionId}:${t.pathPattern}:${t.stackName}`;if(r.confirm!==f)throw Error(`Pass exact confirmation token ${f} to provision the backend and change live routing`);if(s)try{await n.cloudformation.updateStack({stackName:t.stackName,templateBody:l,capabilities:["CAPABILITY_NAMED_IAM"],tags:[{Key:"ManagedBy",Value:"ts-cloud"},{Key:"Application",Value:r.slug}]}),await n.cloudformation.waitForStack(t.stackName,"stack-update-complete")}catch(k){if(!String(k.message).includes("No updates are to be performed"))throw k}else await n.cloudformation.createStack({stackName:t.stackName,templateBody:l,capabilities:["CAPABILITY_NAMED_IAM"],onFailure:"ROLLBACK",tags:[{Key:"ManagedBy",Value:"ts-cloud"},{Key:"Application",Value:r.slug}]}),await n.cloudformation.waitForStack(t.stackName,"stack-create-complete");let y=(await n.cloudformation.describeStacks({stackName:t.stackName})).Stacks[0],S=kp(y?.Outputs),g=S.AppLoadBalancerDnsName;if(!g)throw Error("Backend stack did not return AppLoadBalancerDnsName");let b=!1;if(r.originDomain&&r.originDomain!==g){if(!n.dns)throw Error(`External DNS provider is required to map ${r.originDomain} to ${g}`);let k=await n.dns.upsertRecord(r.originDomain,{name:r.originDomain,type:"CNAME",content:g,ttl:300});if(!k.success)throw Error(k.message||"External DNS update failed");b=!0}let I=r.originDomain||g,v=r.certificateArn?"https-only":"http-only",C=r.skipHealthCheck?void 0:await Pp(n,`${v==="https-only"?"https":"http"}://${I}/api/health`,r.originVerifySecret);return await n.cloudfront.upsertExistingDistributionOrigin(r.distributionId,{id:t.originId,domainName:I,pathPattern:t.pathPattern,protocolPolicy:v,customHeaders:r.originVerifySecret?{"X-Origin-Verify":r.originVerifySecret}:void 0}),{...m,outputs:S,health:C,cloudFrontApplied:!0,dnsApplied:b,applied:!0}}function El(r={}){let t={fargate:730*(r.desiredCount||1)*0.012342500000000001,applicationLoadBalancer:16.425,natGateway:32.85,postgres:730*(r.multiAzDatabase?0.032:0.016),redis:23.36};return{monthlyUsd:Number(Object.values(t).reduce((n,i)=>n+i,0).toFixed(2)),components:Object.fromEntries(Object.entries(t).map(([n,i])=>[n,Number(i.toFixed(2))])),assumptions:["us-east-1 public on-demand rate snapshot, July 2026","one 0.25 vCPU/0.5 GB Linux Fargate task unless desiredCount is changed","one ALB before LCU charges and one NAT gateway before data processing","single-AZ db.t4g.micro PostgreSQL by default and two cache.t4g.micro Redis nodes","excludes storage, backups, CloudFront, data transfer, public IPv4, logs, email, queue requests, taxes, and free-tier credits"]}}function To(r){r.command("container:artifact <repository>","Build, scan, push, and resolve an immutable ECR container artifact").option("--context <path>","Docker build context",{default:"."}).option("--dockerfile <path>","Dockerfile path").option("--platform <platform>","Target platform",{default:"linux/amd64"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").action(async(e,t)=>{try{let n=await kl({repository:e,context:t.context||".",dockerfile:t.dockerfile,platform:t.platform,region:t.region,profile:t.profile});h("Immutable container artifact published."),a(JSON.stringify(n,null,2))}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}}),r.command("deploy:fullstack <distributionId> <alias>","Plan or deploy an ECS/RDS/Redis/SQS/SES backend behind an existing static site").option("--name <name>","Application display name").option("--slug <slug>","Application slug").option("--image <digest-uri>","Immutable ECR image URI with @sha256 digest").option("--stack <name>","Backend CloudFormation stack name").option("--path <pattern>","CloudFront backend path",{default:"/api/*"}).option("--origin-id <id>","CloudFront origin ID").option("--origin-domain <domain>","DNS name for the ALB origin").option("--certificate-arn <arn>","ACM certificate matching the origin domain").option("--dns-provider <provider>","External DNS provider (porkbun, godaddy, cloudflare, route53)").option("--origin-secret-env <name>","Environment variable holding the CloudFront-to-ALB secret",{default:"TS_CLOUD_ORIGIN_SECRET"}).option("--desired-count <count>","Baseline Fargate task count",{default:"1"}).option("--no-database","Do not provision PostgreSQL").option("--no-cache","Do not provision Redis").option("--no-queue","Do not provision SQS").option("--region <region>","AWS region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").option("--skip-health-check","Skip the ALB health gate before CloudFront").option("--apply","Provision and change live routing").option("--confirm <text>","Exact distribution:path:stack confirmation").action(async(e,t,n)=>{try{if(!n.slug)throw Error("--slug is required");if(!n.image)throw Error("--image must be an immutable digest URI from container:artifact");let i=n.originSecretEnv||"TS_CLOUD_ORIGIN_SECRET",o=process.env[i];if(n.apply&&!o)throw Error(`${i} must contain a stable random CloudFront-to-ALB secret before apply`);let s=Do({region:n.region,profile:n.profile}),c=n.originDomain&&n.apply?{...s,dns:Be(n.dnsProvider)}:s,l=await xl({name:n.name||n.slug,slug:n.slug,imageUri:n.image,distributionId:e,expectedAlias:t,stackName:n.stack,pathPattern:n.path,originId:n.originId,originDomain:n.originDomain,certificateArn:n.certificateArn,originVerifySecret:o,desiredCount:Number(n.desiredCount)||1,database:n.database!==!1,cache:n.cache!==!1,queue:n.queue!==!1,region:n.region,profile:n.profile,skipHealthCheck:!!n.skipHealthCheck,apply:!!n.apply,confirm:n.confirm},c);if(a(JSON.stringify(l,null,2)),l.applied)h("Full-stack backend is healthy and the CloudFront path patch was submitted.");else a(`Plan only. Apply with --confirm '${e}:${l.distribution.pathPattern}:${l.stack.name}'.`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}}),r.command("deploy:fullstack:cost","Estimate the always-on full-stack backend baseline").option("--desired-count <count>","Fargate task count",{default:"1"}).option("--multi-az-database","Estimate a two-instance database baseline").action((e)=>a(JSON.stringify(El({desiredCount:Number(e.desiredCount)||1,multiAzDatabase:!!e.multiAzDatabase}),null,2)))}function No(r){r.command("destroy","Destroy the single-server compute (instance + firewall)").option("--env <env>","Environment",{default:"production"}).option("--force","Skip the confirmation prompt").action(async(e)=>{w("Destroy Compute");let t=await P(),n=e?.env||"production",i=Yt(t),o=Ke({config:t,provider:i});if(!o.destroyCompute){p(`The ${i} driver does not support compute teardown`);return}if(R(`This terminates the ${i} server for ${t.project.slug}/${n} and deletes its firewall.`),!e?.force){if(!await O("This is irreversible. Continue?",!1)){a("Cancelled");return}}let s=new A("Destroying compute...");s.start();try{let{destroyed:c}=await o.destroyCompute({config:t,environment:n});if(s.succeed("Compute destroyed"),c.length>0)c.forEach((l)=>a(` removed ${l}`));else a("Nothing to destroy (no matching resources found)")}catch(c){s.fail("Teardown failed"),p(c instanceof Error?c.message:String(c))}})}import{statSync as xp,writeFileSync as Ep}from"node:fs";function Oo(r){r.command("stack:list","List all CloudFormation stacks").action(async()=>{w("CloudFormation Stacks");try{let t=(await P()).project.region||"us-east-1",n=new ie(t),i=new A("Loading stacks...");i.start();let o=await n.listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE","ROLLBACK_COMPLETE","UPDATE_ROLLBACK_COMPLETE","CREATE_IN_PROGRESS","UPDATE_IN_PROGRESS"]);if(i.succeed(`Found ${o.StackSummaries.length} stacks`),o.StackSummaries.length===0){a("No stacks found");return}let s=["Stack Name","Status","Created","Updated"],c=o.StackSummaries.map((l)=>[l.StackName,l.StackStatus,new Date(l.CreationTime).toLocaleString(),l.LastUpdatedTime?new Date(l.LastUpdatedTime).toLocaleString():"Never"]);x(s,c)}catch(e){p(`Failed to list stacks: ${e.message}`)}}),r.command("stack:describe STACK_NAME","Describe a CloudFormation stack").action(async(e)=>{w(`Stack: ${e}`);try{let n=(await P()).project.region||"us-east-1",i=new ie(n),o=new A("Loading stack details...");o.start();let s=await i.describeStacks({stackName:e});if(!s.Stacks||s.Stacks.length===0){o.fail("Stack not found");return}let c=s.Stacks[0];if(o.succeed("Stack details loaded"),a(`
|
|
444
|
+
Your site is now live at https://${E.domain}`,"green")}else throw Error(E.message||`Site '${f}' deployment failed`)}}import{execFileSync as cp}from"node:child_process";import{createHash as lp}from"node:crypto";import{existsSync as $l,mkdirSync as dp,mkdtempSync as up,readdirSync as mp,readFileSync as pp,rmSync as fp,statSync as Cl,writeFileSync as gp}from"node:fs";import{tmpdir as hp}from"node:os";import{basename as yp,join as Zt,relative as vp,resolve as Eo}from"node:path";var bp=new Set([".git","node_modules","dist","coverage"]);function Al(r,e=r){let t=[];for(let n of mp(r).sort()){if(bp.has(n))continue;let i=Zt(r,n);if(Sl(i,e))continue;let o=Cl(i);if(o.isDirectory())t.push(...Al(i,e));else if(o.isFile())t.push(vp(e,i))}return t}function wp(r){let e=Eo(r);if(!$l(e)||!Cl(e).isDirectory())throw Error(`Container context does not exist: ${e}`);let t=lp("sha256");for(let n of Al(e))t.update(n),t.update("\x00"),t.update(pp(Zt(e,n))),t.update("\x00");return t.digest("hex")}function Sp(r){return{ecr:new Rn(r.region||"us-east-1",r.profile),run:(e,t,n)=>{cp(e,t,{cwd:n?.cwd,env:n?.env,stdio:n?.stdio||"inherit"})}}}async function kl(r,e){let t=Eo(r.context),n=Eo(r.dockerfile||Zt(t,"Dockerfile"));if(!$l(n))throw Error(`Dockerfile does not exist: ${n}`);if(!r.repository.split("/").every((S)=>/^[a-z0-9][a-z0-9._-]*$/.test(S)&&!/[._-]$/.test(S)&&!/[._-]{2}/.test(S)))throw Error("ECR repository name is invalid");let s=e||Sp(r),c=wp(t),l=`sha-${c.slice(0,20)}`,d="";try{d=(await s.ecr.describeRepositories({repositoryNames:[r.repository]})).repositories?.[0]?.repositoryUri||""}catch{}if(!d)d=(await s.ecr.createRepository({repositoryName:r.repository,imageTagMutability:"IMMUTABLE",imageScanningConfiguration:{scanOnPush:!0},tags:[{Key:"ManagedBy",Value:"ts-cloud"}]})).repository?.repositoryUri||"";if(!d)throw Error(`Could not resolve ECR repository URI for ${r.repository}`);await s.ecr.putLifecyclePolicy({repositoryName:r.repository,lifecyclePolicyText:JSON.stringify({rules:[{rulePriority:1,description:"Retain the newest 25 release images",selection:{tagStatus:"any",countType:"imageCountMoreThan",countNumber:25},action:{type:"expire"}}]})});let u=`${d}:${l}`,m=up(Zt(hp(),"ts-cloud-container-"));try{s.run("docker",["version"],{stdio:"ignore"}),s.run("docker",["build","--platform",r.platform||"linux/amd64","--provenance=false","--sbom=false","--file",n,"--tag",u,t],{cwd:t});let S=(await s.ecr.getAuthorizationToken()).authorizationData?.[0];if(!S?.authorizationToken||!S.proxyEndpoint)throw Error("ECR did not return Docker authorization data");let g=S.proxyEndpoint.replace(/^https?:\/\//,""),b=Zt(m,"docker");dp(b,{recursive:!0}),gp(Zt(b,"config.json"),JSON.stringify({auths:{[g]:{auth:S.authorizationToken}}}),{mode:384}),s.run("docker",["push",u],{env:{...process.env,DOCKER_CONFIG:b}})}finally{fp(m,{recursive:!0,force:!0})}let y=(await s.ecr.describeImages({repositoryName:r.repository,imageIds:[{imageTag:l}]})).imageDetails?.[0]?.imageDigest;if(!y||!/^sha256:[a-f0-9]{64}$/i.test(y))throw Error(`ECR did not return an immutable digest for ${yp(u)}`);return{repository:r.repository,tag:l,imageUri:u,digest:y,digestUri:`${d}@${y}`,contextSha256:c}}import{createHash as $p}from"node:crypto";function Cp(r){let e=r;return String(e?.code||e?.name||e?.statusCode||"")}function Ap(r){return Cp(r)==="ValidationError"&&String(r.message).includes("does not exist")}function kp(r){return Object.fromEntries((r||[]).map((e)=>[e.OutputKey,e.OutputValue]))}function Rp(r){let e=r?.Aliases?.Items??r?.Aliases;if(Array.isArray(e))return e.map(String);if(typeof e==="string")return[e];return[]}function Pl(r){if(!/^[a-z][a-z0-9-]{1,62}$/.test(r.slug))throw Error("Slug must be a lowercase DNS-safe identifier");if(!/^[A-Z0-9]{8,32}$/.test(r.distributionId))throw Error("CloudFront distribution ID is invalid");if(!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9-]{2,63}$/.test(r.expectedAlias))throw Error("Expected distribution alias is invalid");let e=r.imageUri.match(/@((?:sha256:)[a-f0-9]{64})$/i)?.[1];if(!e)throw Error("Container image must use an immutable ECR digest URI ending in @sha256:<64 hex>");let t=r.stackName||`${r.slug}-backend`;if(!/^[A-Za-z][A-Za-z0-9-]{0,127}$/.test(t))throw Error("CloudFormation stack name is invalid");let n=r.pathPattern||"/api/*";if(!/^\/[A-Za-z0-9_.*?~!$&'()+,;=:@%/-]+\*$/.test(n)||n==="/*")throw Error("Backend path must be a non-default wildcard pattern");let i=r.originId||`${r.slug}-backend`;if(!/^[A-Za-z0-9_.-]{1,128}$/.test(i))throw Error("CloudFront origin ID is invalid");if(r.originVerifySecret&&r.originVerifySecret.length<24)throw Error("Origin verification secret must contain at least 24 characters");if(!!r.originDomain!==!!r.certificateArn)throw Error("A custom origin domain and matching ACM certificate ARN must be provided together");return{stackName:t,region:r.region||"us-east-1",pathPattern:n,originId:i,digest:e}}function Do(r){let e=r.region||"us-east-1";return{cloudformation:new ie(e,r.profile),cloudfront:new ne(r.profile),sts:new Dt(e,r.profile),fetch,sleep:(t)=>new Promise((n)=>setTimeout(n,t))}}function Ip(r){let e=Pl(r),t=Il({name:r.name,slug:r.slug,domain:r.originDomain||r.expectedAlias,imageUri:r.imageUri,certificateArn:r.certificateArn,originVerifySecret:r.originVerifySecret,desiredCount:r.desiredCount,database:r.database,cache:r.cache,queue:r.queue}),n=Rl(t);return n.Description=`Managed backend for existing static frontend ${r.expectedAlias}`,n.Metadata={ManagedBy:"ts-cloud",ExistingDistributionId:r.distributionId,ExistingDistributionAlias:r.expectedAlias,BackendPathPattern:e.pathPattern,ContainerDigest:e.digest},n}async function Pp(r,e,t){let n;for(let i=0;i<30;i++){let o=Date.now();try{let s=await r.fetch(e,{redirect:"manual",headers:t?{"x-origin-verify":t}:void 0}),c=Date.now()-o,l=await s.text(),d=l;try{d=JSON.parse(l)}catch{}if(s.ok)return{url:e,status:s.status,latencyMs:c,body:d};n=Error(`origin returned ${s.status}`)}catch(s){n=s}if(i<29)await r.sleep(1e4)}throw Error(`Backend origin did not become healthy: ${n instanceof Error?n.message:String(n)}`)}async function xl(r,e){let t=Pl(r),n=e||Do({region:t.region,profile:r.profile}),[i,o]=await Promise.all([n.sts.getCallerIdentity(),n.cloudfront.getDistribution(r.distributionId)]);if(!i.Account)throw Error("AWS identity did not return an account ID");if(!o.Enabled)throw Error(`CloudFront distribution ${r.distributionId} is disabled`);if(!Rp(o).includes(r.expectedAlias))throw Error(`Distribution ${r.distributionId} does not contain expected alias ${r.expectedAlias}`);let s;try{s=(await n.cloudformation.describeStacks({stackName:t.stackName})).Stacks[0]}catch(k){if(!Ap(k))throw k}let c=Ip(r),l=JSON.stringify(c);if(Buffer.byteLength(l)>51200)throw Error("Generated template exceeds the direct CloudFormation template body limit");let d=Object.values(c.Resources||{}).reduce((k,T)=>(k[T.Type]=(k[T.Type]||0)+1,k),{}),u=[`cloud cdn:origin:remove ${r.distributionId} ${r.originDomain||"ALB_HOST"} --id ${t.originId} --path '${t.pathPattern}' --profile ${r.profile||"default"} --apply --confirm 'remove:${r.distributionId}:${t.pathPattern}'`,`After CloudFront is deployed, restore external DNS if changed and delete stack ${t.stackName} only with an approved data-retention decision.`],m={mode:r.apply?"apply":"plan",accountId:i.Account,stack:{name:t.stackName,existed:!!s,status:s?.StackStatus,templateSha256:$p("sha256").update(l).digest("hex"),resourceCount:Object.keys(c.Resources||{}).length,resourceTypes:d},distribution:{id:o.Id,alias:r.expectedAlias,domainName:o.DomainName,status:o.Status,pathPattern:t.pathPattern,originId:t.originId},artifact:{imageUri:r.imageUri,digest:t.digest},services:{database:r.database!==!1,cache:r.cache!==!1,queue:r.queue!==!1,mail:"ses",desiredCount:r.desiredCount||1},cloudFrontApplied:!1,dnsApplied:!1,applied:!1,rollback:u};if(!r.apply)return m;let f=`${r.distributionId}:${t.pathPattern}:${t.stackName}`;if(r.confirm!==f)throw Error(`Pass exact confirmation token ${f} to provision the backend and change live routing`);if(s)try{await n.cloudformation.updateStack({stackName:t.stackName,templateBody:l,capabilities:["CAPABILITY_NAMED_IAM"],tags:[{Key:"ManagedBy",Value:"ts-cloud"},{Key:"Application",Value:r.slug}]}),await n.cloudformation.waitForStack(t.stackName,"stack-update-complete")}catch(k){if(!String(k.message).includes("No updates are to be performed"))throw k}else await n.cloudformation.createStack({stackName:t.stackName,templateBody:l,capabilities:["CAPABILITY_NAMED_IAM"],onFailure:"ROLLBACK",tags:[{Key:"ManagedBy",Value:"ts-cloud"},{Key:"Application",Value:r.slug}]}),await n.cloudformation.waitForStack(t.stackName,"stack-create-complete");let y=(await n.cloudformation.describeStacks({stackName:t.stackName})).Stacks[0],S=kp(y?.Outputs),g=S.AppLoadBalancerDnsName;if(!g)throw Error("Backend stack did not return AppLoadBalancerDnsName");let b=!1;if(r.originDomain&&r.originDomain!==g){if(!n.dns)throw Error(`External DNS provider is required to map ${r.originDomain} to ${g}`);let k=await n.dns.upsertRecord(r.originDomain,{name:r.originDomain,type:"CNAME",content:g,ttl:300});if(!k.success)throw Error(k.message||"External DNS update failed");b=!0}let I=r.originDomain||g,v=r.certificateArn?"https-only":"http-only",C=r.skipHealthCheck?void 0:await Pp(n,`${v==="https-only"?"https":"http"}://${I}/api/health`,r.originVerifySecret);return await n.cloudfront.upsertExistingDistributionOrigin(r.distributionId,{id:t.originId,domainName:I,pathPattern:t.pathPattern,protocolPolicy:v,customHeaders:r.originVerifySecret?{"X-Origin-Verify":r.originVerifySecret}:void 0}),{...m,outputs:S,health:C,cloudFrontApplied:!0,dnsApplied:b,applied:!0}}function El(r={}){let t={fargate:730*(r.desiredCount||1)*0.012342500000000001,applicationLoadBalancer:16.425,natGateway:32.85,postgres:730*(r.multiAzDatabase?0.032:0.016),redis:23.36};return{monthlyUsd:Number(Object.values(t).reduce((n,i)=>n+i,0).toFixed(2)),components:Object.fromEntries(Object.entries(t).map(([n,i])=>[n,Number(i.toFixed(2))])),assumptions:["us-east-1 public on-demand rate snapshot, July 2026","one 0.25 vCPU/0.5 GB Linux Fargate task unless desiredCount is changed","one ALB before LCU charges and one NAT gateway before data processing","single-AZ db.t4g.micro PostgreSQL by default and two cache.t4g.micro Redis nodes","excludes storage, backups, CloudFront, data transfer, public IPv4, logs, email, queue requests, taxes, and free-tier credits"]}}function To(r){r.command("container:artifact <repository>","Build, scan, push, and resolve an immutable ECR container artifact").option("--context <path>","Docker build context",{default:"."}).option("--dockerfile <path>","Dockerfile path").option("--platform <platform>","Target platform",{default:"linux/amd64"}).option("--region <region>","AWS region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").action(async(e,t)=>{try{let n=await kl({repository:e,context:t.context||".",dockerfile:t.dockerfile,platform:t.platform,region:t.region,profile:t.profile});h("Immutable container artifact published."),a(JSON.stringify(n,null,2))}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}}),r.command("deploy:fullstack <distributionId> <alias>","Plan or deploy an ECS/RDS/Redis/SQS/SES backend behind an existing static site").option("--name <name>","Application display name").option("--slug <slug>","Application slug").option("--image <digest-uri>","Immutable ECR image URI with @sha256 digest").option("--stack <name>","Backend CloudFormation stack name").option("--path <pattern>","CloudFront backend path",{default:"/api/*"}).option("--origin-id <id>","CloudFront origin ID").option("--origin-domain <domain>","DNS name for the ALB origin").option("--certificate-arn <arn>","ACM certificate matching the origin domain").option("--dns-provider <provider>","External DNS provider (porkbun, godaddy, cloudflare, route53)").option("--origin-secret-env <name>","Environment variable holding the CloudFront-to-ALB secret",{default:"TS_CLOUD_ORIGIN_SECRET"}).option("--desired-count <count>","Baseline Fargate task count",{default:"1"}).option("--no-database","Do not provision PostgreSQL").option("--no-cache","Do not provision Redis").option("--no-queue","Do not provision SQS").option("--region <region>","AWS region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").option("--skip-health-check","Skip the ALB health gate before CloudFront").option("--apply","Provision and change live routing").option("--confirm <text>","Exact distribution:path:stack confirmation").action(async(e,t,n)=>{try{if(!n.slug)throw Error("--slug is required");if(!n.image)throw Error("--image must be an immutable digest URI from container:artifact");let i=n.originSecretEnv||"TS_CLOUD_ORIGIN_SECRET",o=process.env[i];if(n.apply&&!o)throw Error(`${i} must contain a stable random CloudFront-to-ALB secret before apply`);let s=Do({region:n.region,profile:n.profile}),c=n.originDomain&&n.apply?{...s,dns:Be(n.dnsProvider)}:s,l=await xl({name:n.name||n.slug,slug:n.slug,imageUri:n.image,distributionId:e,expectedAlias:t,stackName:n.stack,pathPattern:n.path,originId:n.originId,originDomain:n.originDomain,certificateArn:n.certificateArn,originVerifySecret:o,desiredCount:Number(n.desiredCount)||1,database:n.database!==!1,cache:n.cache!==!1,queue:n.queue!==!1,region:n.region,profile:n.profile,skipHealthCheck:!!n.skipHealthCheck,apply:!!n.apply,confirm:n.confirm},c);if(a(JSON.stringify(l,null,2)),l.applied)h("Full-stack backend is healthy and the CloudFront path patch was submitted.");else a(`Plan only. Apply with --confirm '${e}:${l.distribution.pathPattern}:${l.stack.name}'.`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}}),r.command("deploy:fullstack:cost","Estimate the always-on full-stack backend baseline").option("--desired-count <count>","Fargate task count",{default:"1"}).option("--multi-az-database","Estimate a two-instance database baseline").action((e)=>a(JSON.stringify(El({desiredCount:Number(e.desiredCount)||1,multiAzDatabase:!!e.multiAzDatabase}),null,2)))}function No(r){r.command("destroy","Destroy the single-server compute (instance + firewall)").option("--env <env>","Environment",{default:"production"}).option("--force","Skip the confirmation prompt").action(async(e)=>{w("Destroy Compute");let t=await P(),n=e?.env||"production",i=Yt(t),o=He({config:t,provider:i});if(!o.destroyCompute){p(`The ${i} driver does not support compute teardown`);return}if(R(`This terminates the ${i} server for ${t.project.slug}/${n} and deletes its firewall.`),!e?.force){if(!await O("This is irreversible. Continue?",!1)){a("Cancelled");return}}let s=new A("Destroying compute...");s.start();try{let{destroyed:c}=await o.destroyCompute({config:t,environment:n});if(s.succeed("Compute destroyed"),c.length>0)c.forEach((l)=>a(` removed ${l}`));else a("Nothing to destroy (no matching resources found)")}catch(c){s.fail("Teardown failed"),p(c instanceof Error?c.message:String(c))}})}import{statSync as xp,writeFileSync as Ep}from"node:fs";function Oo(r){r.command("stack:list","List all CloudFormation stacks").action(async()=>{w("CloudFormation Stacks");try{let t=(await P()).project.region||"us-east-1",n=new ie(t),i=new A("Loading stacks...");i.start();let o=await n.listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE","ROLLBACK_COMPLETE","UPDATE_ROLLBACK_COMPLETE","CREATE_IN_PROGRESS","UPDATE_IN_PROGRESS"]);if(i.succeed(`Found ${o.StackSummaries.length} stacks`),o.StackSummaries.length===0){a("No stacks found");return}let s=["Stack Name","Status","Created","Updated"],c=o.StackSummaries.map((l)=>[l.StackName,l.StackStatus,new Date(l.CreationTime).toLocaleString(),l.LastUpdatedTime?new Date(l.LastUpdatedTime).toLocaleString():"Never"]);x(s,c)}catch(e){p(`Failed to list stacks: ${e.message}`)}}),r.command("stack:describe STACK_NAME","Describe a CloudFormation stack").action(async(e)=>{w(`Stack: ${e}`);try{let n=(await P()).project.region||"us-east-1",i=new ie(n),o=new A("Loading stack details...");o.start();let s=await i.describeStacks({stackName:e});if(!s.Stacks||s.Stacks.length===0){o.fail("Stack not found");return}let c=s.Stacks[0];if(o.succeed("Stack details loaded"),a(`
|
|
445
445
|
Stack Information:`),a(` - Name: ${c.StackName}`),a(` - Status: ${c.StackStatus}`),a(` - Created: ${new Date(c.CreationTime).toLocaleString()}`),c.LastUpdatedTime)a(` - Updated: ${new Date(c.LastUpdatedTime).toLocaleString()}`);if(c.Parameters&&c.Parameters.length>0){a(`
|
|
446
446
|
Parameters:`);for(let d of c.Parameters)a(` - ${d.ParameterKey}: ${d.ParameterValue}`)}if(c.Outputs&&c.Outputs.length>0){a(`
|
|
447
447
|
Outputs:`);for(let d of c.Outputs)if(a(` - ${d.OutputKey}: ${d.OutputValue}`),d.Description)a(` ${d.Description}`)}if(c.Tags&&c.Tags.length>0){a(`
|
|
@@ -469,7 +469,7 @@ export const handler = async (event) => {
|
|
|
469
469
|
body: JSON.stringify({ ok: statusCode === 200, service: 'ts-cloud-static-api', path, coldStart: currentColdStart, initializedAt })
|
|
470
470
|
}
|
|
471
471
|
}
|
|
472
|
-
`;function zl(r){let e=r;return String(e?.code||e?.name||e?.statusCode||"")}function _p(r){return["NoSuchEntity","ResourceNotFoundException","404"].includes(zl(r))}function Bl(r){return["EntityAlreadyExists","ResourceAlreadyExistsException","ResourceConflictException","409"].includes(zl(r))}function _l(r,e){let t=r?.Items;if(!t)return[];if(Array.isArray(t))return t;let n=t[e];if(n===void 0)return[];return Array.isArray(n)?n:[n]}function Mo(r){let e=r?.Aliases?.Items;if(Array.isArray(e))return e.map(String);if(typeof e==="string")return[e];return[]}function zp(r){if(!/^[A-Z0-9]{8,32}$/.test(r.distributionId))throw Error("CloudFront distribution ID is invalid");if(!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9-]{2,63}$/.test(r.expectedAlias))throw Error("Expected CloudFront alias is invalid");if(!/^[A-Za-z0-9-_]{1,64}$/.test(r.functionName))throw Error("Lambda function name is invalid");let e=r.pathPattern?.startsWith("/")?r.pathPattern:`/${r.pathPattern||"api/*"}`;if(e==="/"||e==="/*"||!e.endsWith("*"))throw Error("API path must be a non-default wildcard pattern such as /api/*");let t=r.originId||`${r.functionName}-url`,n=r.roleName||`${r.functionName}-execution`;if(!/^[A-Za-z0-9_.-]{1,64}$/.test(n))throw Error("IAM role name is invalid");let i=r.memorySize??256,o=r.timeout??10,s=r.logRetentionDays??14;if(i<128||i>10240)throw Error("Lambda memory must be between 128 and 10240 MB");if(o<1||o>900)throw Error("Lambda timeout must be between 1 and 900 seconds");return{region:r.region||"us-east-1",pathPattern:e,originId:t,roleName:n,memorySize:i,timeout:o,logRetentionDays:s}}function Up(r){let e=r.region||"us-east-1";return{cloudfront:new ne(r.profile),iam:new he(e,r.profile),lambda:new tt(e,r.profile),logs:new nt(e,r.profile),sts:new Dt(e,r.profile),sleep:(t)=>new Promise((n)=>setTimeout(n,t))}}async function Ul(r,e){let t=zp(r),n=e||Up({profile:r.profile,region:t.region}),[i,o,s]=await Promise.all([n.sts.getCallerIdentity(),n.cloudfront.getDistribution(r.distributionId),n.cloudfront.getDistributionConfig(r.distributionId)]);if(!i.Account)throw Error("AWS identity did not return an account ID");if(!o.Enabled)throw Error(`CloudFront distribution ${r.distributionId} is disabled`);if(!(Mo(o).length?Mo(o):Mo(s.DistributionConfig)).includes(r.expectedAlias))throw Error(`Distribution ${r.distributionId} does not contain expected alias ${r.expectedAlias}`);let l,d=!0;try{l=await n.iam.getRole({RoleName:t.roleName})}catch(G){if(!_p(G))throw G;d=!1}let u=l?.Arn||`arn:aws:iam::${i.Account}:role/${t.roleName}`,m=await n.lambda.functionExists(r.functionName),f=m?await n.lambda.getFunctionUrl(r.functionName):null,y=await n.cloudfront.listOriginAccessControls(),S=`${r.functionName}-lambda-url`,g=y.some((G)=>G.Name===S&&G.OriginAccessControlOriginType==="lambda"),b=_l(s.DistributionConfig.Origins,"Origin"),I=_l(s.DistributionConfig.CacheBehaviors,"CacheBehavior"),v=b.some((G)=>String(G.Id)===t.originId),C=I.some((G)=>String(G.PathPattern)===t.pathPattern),k=[...!d?[`create IAM execution role ${t.roleName}`]:[`reconcile IAM execution policy ${t.roleName}`],...m?[`update Lambda ${r.functionName} code and configuration`]:[`create Lambda ${r.functionName}`],...!f?["create private AWS_IAM function URL"]:[],...!g?[`create Lambda origin access control ${S}`]:[],"grant CloudFront function-URL and invocation permissions",`${C?"reconcile":"add"} ${t.pathPattern} behavior without changing the default behavior`],T={command:`cloud cdn:origin:remove ${r.distributionId} FUNCTION_URL_HOST --id ${t.originId} --path '${t.pathPattern}' --apply --confirm 'remove:${r.distributionId}:${t.pathPattern}'`,removeFunction:!m,removeRole:!d},E={mode:r.apply?"apply":"plan",accountId:i.Account,identityArn:i.Arn,distribution:{id:o.Id,arn:o.ARN,alias:r.expectedAlias,domainName:o.DomainName,status:o.Status},function:{name:r.functionName,existed:m,runtime:"nodejs22.x",urlExisted:!!f,url:f?.FunctionUrl},role:{name:t.roleName,existed:d,arn:u},origin:{id:t.originId,pathPattern:t.pathPattern,existed:v,behaviorExisted:C,accessControlExisted:g},changes:k,rollback:T,applied:!1};if(!r.apply)return E;let D=`${r.distributionId}:${t.pathPattern}`;if(r.confirm!==D)throw Error(`Pass exact confirmation token ${D} to mutate the live distribution`);if(!l)l=await n.iam.createRole({RoleName:t.roleName,Description:`Execution role for ${r.functionName}`,AssumeRolePolicyDocument:JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]}),Tags:[{Key:"ManagedBy",Value:"ts-cloud"}]});await n.iam.putRolePolicy({RoleName:t.roleName,PolicyName:"lambda-logs",PolicyDocument:JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["logs:CreateLogStream","logs:PutLogEvents"],Resource:`arn:aws:logs:${t.region}:${i.Account}:log-group:/aws/lambda/${r.functionName}:*`}]})});try{await n.logs.createLogGroup(`/aws/lambda/${r.functionName}`,{ManagedBy:"ts-cloud"})}catch(G){if(!Bl(G))throw G}await n.logs.putRetentionPolicy(`/aws/lambda/${r.functionName}`,t.logRetentionDays);let N=r.code||Bp;if(m)await n.lambda.updateFunctionCodeInline(r.functionName,N,"index.mjs"),await n.lambda.waitForFunctionActive(r.functionName),await n.lambda.updateFunctionConfiguration({FunctionName:r.functionName,Runtime:"nodejs22.x",Role:l.Arn,Handler:"index.handler",Description:`Private API origin for ${r.expectedAlias}`,Timeout:t.timeout,MemorySize:t.memorySize});else{let G;for(let L=0;L<8;L++)try{await n.lambda.createFunctionWithCode({FunctionName:r.functionName,Runtime:"nodejs22.x",Role:l.Arn,Handler:"index.handler",Code:N,Filename:"index.mjs",Description:`Private API origin for ${r.expectedAlias}`,Timeout:t.timeout,MemorySize:t.memorySize}),G=void 0;break}catch(H){if(G=H,!String(H.message).toLowerCase().includes("role"))throw H;await n.sleep(1000)}if(G)throw G}await n.lambda.waitForFunctionActive(r.functionName);let _=f||await n.lambda.createFunctionUrl({FunctionName:r.functionName,AuthType:"AWS_IAM",InvokeMode:"BUFFERED"});if(!_.FunctionUrl)throw Error("Lambda did not return a function URL");let B=new URL(_.FunctionUrl).hostname,V=await n.cloudfront.findOrCreateOriginAccessControl(S,"lambda"),K=o.ARN||`arn:aws:cloudfront::${i.Account}:distribution/${r.distributionId}`,oe=[{FunctionName:r.functionName,StatementId:"AllowCloudFrontFunctionUrl",Action:"lambda:InvokeFunctionUrl",Principal:"cloudfront.amazonaws.com",SourceArn:K,FunctionUrlAuthType:"AWS_IAM"},{FunctionName:r.functionName,StatementId:"AllowCloudFrontInvoke",Action:"lambda:InvokeFunction",Principal:"cloudfront.amazonaws.com",SourceArn:K,InvokedViaFunctionUrl:!0}];for(let G of oe)try{await n.lambda.addPermission(G)}catch(L){if(!Bl(L))throw L}return await n.cloudfront.upsertExistingDistributionOrigin(r.distributionId,{id:t.originId,domainName:B,pathPattern:t.pathPattern,originAccessControlId:V.Id}),{...E,function:{...E.function,url:_.FunctionUrl},applied:!0}}async function Vl(r){let e=`https://${r.alias}/`,t=`https://${r.alias}/api/health`,n=await fetch(e,{redirect:"follow",headers:{"cache-control":"no-cache"}}),i=new Uint8Array(await n.arrayBuffer()),o=Mp("sha256").update(i).digest("hex"),s=Date.now(),c=await fetch(t,{redirect:"follow",headers:{"cache-control":"no-cache"}}),l=Date.now()-s,d=await c.text(),u=d;try{u=JSON.parse(d)}catch{}let m;if(r.logs&&r.functionName){let f=await r.logs.filterLogEvents({logGroupName:`/aws/lambda/${r.functionName}`,startTime:s-60000,filterPattern:'"Init Duration"',limit:25});for(let y of f.events||[]){let S=y.message?.match(/Init Duration:\s*([0-9.]+)\s*ms/i);if(S)m=Number(S[1])}}return{frontend:{url:e,status:n.status,sha256:o,unchanged:r.expectedFrontendSha256?o===r.expectedFrontendSha256:void 0,bytes:i.byteLength},api:{url:t,status:c.status,latencyMs:l,healthy:c.ok&&!!u?.ok,body:u},coldStart:{observed:m!==void 0,initDurationMs:m},verifiedAt:(r.now?.()||new Date).toISOString()}}function Wl(r){let e=(r.memoryMb||256)/1024,t=Math.max(0,r.requests-(r.includeFreeTier===!1?0:1e6)),n=Math.max(0,r.requests*(r.averageDurationMs/1000)*e-(r.includeFreeTier===!1?0:400000)),i=t/1e6*0.2+n*0.0000166667,o=9.010025,s=16.425;return{lambdaUsd:Number(i.toFixed(2)),alwaysOnFargateAndAlbUsd:Number(25.435025000000003 .toFixed(2)),assumptions:["us-east-1 public list rates snapshot, July 2026","Lambda x86 request and duration pricing with optional monthly free tier","Fargate comparison uses one always-on 0.25 vCPU/0.5 GB task plus one ALB before LCU charges","excludes CloudFront, data transfer, logs, taxes, and application data services"]}}function Bo(r){r.command("cdn:api:deploy <distributionId> <alias>","Plan or deploy a private Lambda API behind an existing static distribution").option("--function <name>","Lambda function name",{default:"ts-cloud-static-api"}).option("--path <pattern>","CloudFront API path",{default:"/api/*"}).option("--origin-id <id>","Stable CloudFront origin ID").option("--role <name>","Lambda execution role name").option("--region <region>","Lambda region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").option("--memory <mb>","Lambda memory in MB",{default:"256"}).option("--timeout <seconds>","Lambda timeout",{default:"10"}).option("--retention <days>","CloudWatch log retention",{default:"14"}).option("--apply","Create resources and patch the live distribution").option("--confirm <text>","Exact distribution:path confirmation").action(async(e,t,n)=>{try{let i=await Ul({distributionId:e,expectedAlias:t,functionName:n.function||"ts-cloud-static-api",pathPattern:n.path,originId:n.originId,roleName:n.role,region:n.region,profile:n.profile,memorySize:Number(n.memory),timeout:Number(n.timeout),logRetentionDays:Number(n.retention),apply:!!n.apply,confirm:n.confirm});if(a(JSON.stringify(i,null,2)),i.applied)h("Private API origin submitted to CloudFront. Run cdn:api:verify after propagation.");else a(`Plan only. Re-run with --apply --confirm '${e}:${i.origin.pathPattern}'.`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}}),r.command("cdn:api:verify <alias>","Verify static frontend integrity, API health, latency, and Lambda initialization").option("--function <name>","Lambda function name",{default:"ts-cloud-static-api"}).option("--frontend-sha256 <digest>","Expected SHA-256 of the frontend response").option("--region <region>","Lambda region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").action(async(e,t)=>{try{let n=new nt(t.region||"us-east-1",t.profile),i=await Vl({alias:e,expectedFrontendSha256:t.frontendSha256,logs:n,functionName:t.function||"ts-cloud-static-api"});if(a(JSON.stringify(i,null,2)),!i.api.healthy||i.frontend.unchanged===!1)throw Error("Static API verification failed");h("Static frontend and Lambda API verification passed.")}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}}),r.command("cdn:api:cost <requests>","Compare a low-volume Lambda API with one always-on Fargate task and ALB").option("--duration <milliseconds>","Average Lambda duration",{default:"100"}).option("--memory <mb>","Lambda memory in MB",{default:"256"}).option("--no-free-tier","Exclude the Lambda free tier").action((e,t)=>{let n=Wl({requests:Number(e),averageDurationMs:Number(t.duration),memoryMb:Number(t.memory),includeFreeTier:t.freeTier!==!1});a(JSON.stringify(n,null,2))}),r.command("cdn:origin:add <distributionId> <domain>","Safely add a backend origin and path to an existing distribution").option("--id <origin>","Stable origin ID",{default:"api-backend"}).option("--path <pattern>","Path behavior",{default:"/api/*"}).option("--profile <name>","AWS credential profile").option("--replace","Replace a reviewed path/origin collision").option("--apply","Apply the ETag-protected patch").option("--confirm <text>","Exact distribution:path confirmation").action(async(e,t,n)=>{try{let i=new ne(n.profile),o={id:n.id??"api-backend",domainName:t,pathPattern:n.path??"/api/*",replaceExisting:!!n.replace},s=await i.upsertExistingDistributionOrigin(e,{...o,dryRun:!0});if(a(JSON.stringify(s,null,2)),!n.apply){a("Dry run only. Re-run with --apply and the exact --confirm token.");return}let c=`${e}:${s.pathPattern}`;if(n.confirm!==c)throw Error(`Pass --confirm "${c}" to apply this live distribution change`);let l=await i.upsertExistingDistributionOrigin(e,o);h(l.changed?"Backend origin patch submitted.":"Backend origin already matches."),a(JSON.stringify(l,null,2))}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}}),r.command("cdn:origin:remove <distributionId> <domain>","Remove one exact backend behavior and its unreferenced origin").option("--id <origin>","Origin ID",{default:"api-backend"}).option("--path <pattern>","Path behavior",{default:"/api/*"}).option("--profile <name>","AWS credential profile").option("--apply","Apply the ETag-protected rollback").option("--confirm <text>","Exact remove:distribution:path confirmation").action(async(e,t,n)=>{try{let i=new ne(n.profile),o={id:n.id??"api-backend",domainName:t,pathPattern:n.path??"/api/*"},s=await i.removeExistingDistributionOrigin(e,{...o,dryRun:!0});if(a(JSON.stringify(s,null,2)),!n.apply){a("Dry run only. Re-run with --apply and the exact --confirm token.");return}let c=`remove:${e}:${s.pathPattern}`;if(n.confirm!==c)throw Error(`Pass --confirm "${c}" to apply this live distribution rollback`);let l=await i.removeExistingDistributionOrigin(e,o);h(l.changed?"Backend behavior rollback submitted.":"Backend behavior was already absent."),a(JSON.stringify(l,null,2))}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}}),r.command("cdn:list","List all CloudFront distributions").action(async()=>{w("CloudFront Distributions");try{let e=await P(),t=new ne,n=new A("Fetching distributions...");n.start();let i=await t.listDistributions();if(n.succeed(`Found ${i.length} distribution(s)`),i.length===0){a("No CloudFront distributions found"),a("Use `cloud cdn:create` to create a new distribution");return}x(["ID","Domain","Status","Enabled","Aliases"],i.map((o)=>[o.Id||"N/A",o.DomainName||"N/A",o.Status||"N/A",o.Enabled?"Yes":"No",(o.Aliases?.Items?.length||0).toString()]))}catch(e){p(`Failed to list distributions: ${e.message}`),process.exit(1)}}),r.command("cdn:status <distributionId>","Get CloudFront distribution status").action(async(e)=>{w(`CloudFront Distribution: ${e}`);try{let t=new ne,n=new A("Fetching distribution details...");n.start();let i=await t.getDistribution(e);if(n.succeed("Distribution details loaded"),a(`
|
|
472
|
+
`;function zl(r){let e=r;return String(e?.code||e?.name||e?.statusCode||"")}function _p(r){return["NoSuchEntity","ResourceNotFoundException","404"].includes(zl(r))}function Bl(r){return["EntityAlreadyExists","ResourceAlreadyExistsException","ResourceConflictException","409"].includes(zl(r))}function _l(r,e){let t=r?.Items;if(!t)return[];if(Array.isArray(t))return t;let n=t[e];if(n===void 0)return[];return Array.isArray(n)?n:[n]}function Mo(r){let e=r?.Aliases?.Items;if(Array.isArray(e))return e.map(String);if(typeof e==="string")return[e];return[]}function zp(r){if(!/^[A-Z0-9]{8,32}$/.test(r.distributionId))throw Error("CloudFront distribution ID is invalid");if(!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9-]{2,63}$/.test(r.expectedAlias))throw Error("Expected CloudFront alias is invalid");if(!/^[A-Za-z0-9-_]{1,64}$/.test(r.functionName))throw Error("Lambda function name is invalid");let e=r.pathPattern?.startsWith("/")?r.pathPattern:`/${r.pathPattern||"api/*"}`;if(e==="/"||e==="/*"||!e.endsWith("*"))throw Error("API path must be a non-default wildcard pattern such as /api/*");let t=r.originId||`${r.functionName}-url`,n=r.roleName||`${r.functionName}-execution`;if(!/^[A-Za-z0-9_.-]{1,64}$/.test(n))throw Error("IAM role name is invalid");let i=r.memorySize??256,o=r.timeout??10,s=r.logRetentionDays??14;if(i<128||i>10240)throw Error("Lambda memory must be between 128 and 10240 MB");if(o<1||o>900)throw Error("Lambda timeout must be between 1 and 900 seconds");return{region:r.region||"us-east-1",pathPattern:e,originId:t,roleName:n,memorySize:i,timeout:o,logRetentionDays:s}}function Up(r){let e=r.region||"us-east-1";return{cloudfront:new ne(r.profile),iam:new he(e,r.profile),lambda:new nt(e,r.profile),logs:new rt(e,r.profile),sts:new Dt(e,r.profile),sleep:(t)=>new Promise((n)=>setTimeout(n,t))}}async function Ul(r,e){let t=zp(r),n=e||Up({profile:r.profile,region:t.region}),[i,o,s]=await Promise.all([n.sts.getCallerIdentity(),n.cloudfront.getDistribution(r.distributionId),n.cloudfront.getDistributionConfig(r.distributionId)]);if(!i.Account)throw Error("AWS identity did not return an account ID");if(!o.Enabled)throw Error(`CloudFront distribution ${r.distributionId} is disabled`);if(!(Mo(o).length?Mo(o):Mo(s.DistributionConfig)).includes(r.expectedAlias))throw Error(`Distribution ${r.distributionId} does not contain expected alias ${r.expectedAlias}`);let l,d=!0;try{l=await n.iam.getRole({RoleName:t.roleName})}catch(G){if(!_p(G))throw G;d=!1}let u=l?.Arn||`arn:aws:iam::${i.Account}:role/${t.roleName}`,m=await n.lambda.functionExists(r.functionName),f=m?await n.lambda.getFunctionUrl(r.functionName):null,y=await n.cloudfront.listOriginAccessControls(),S=`${r.functionName}-lambda-url`,g=y.some((G)=>G.Name===S&&G.OriginAccessControlOriginType==="lambda"),b=_l(s.DistributionConfig.Origins,"Origin"),I=_l(s.DistributionConfig.CacheBehaviors,"CacheBehavior"),v=b.some((G)=>String(G.Id)===t.originId),C=I.some((G)=>String(G.PathPattern)===t.pathPattern),k=[...!d?[`create IAM execution role ${t.roleName}`]:[`reconcile IAM execution policy ${t.roleName}`],...m?[`update Lambda ${r.functionName} code and configuration`]:[`create Lambda ${r.functionName}`],...!f?["create private AWS_IAM function URL"]:[],...!g?[`create Lambda origin access control ${S}`]:[],"grant CloudFront function-URL and invocation permissions",`${C?"reconcile":"add"} ${t.pathPattern} behavior without changing the default behavior`],T={command:`cloud cdn:origin:remove ${r.distributionId} FUNCTION_URL_HOST --id ${t.originId} --path '${t.pathPattern}' --apply --confirm 'remove:${r.distributionId}:${t.pathPattern}'`,removeFunction:!m,removeRole:!d},E={mode:r.apply?"apply":"plan",accountId:i.Account,identityArn:i.Arn,distribution:{id:o.Id,arn:o.ARN,alias:r.expectedAlias,domainName:o.DomainName,status:o.Status},function:{name:r.functionName,existed:m,runtime:"nodejs22.x",urlExisted:!!f,url:f?.FunctionUrl},role:{name:t.roleName,existed:d,arn:u},origin:{id:t.originId,pathPattern:t.pathPattern,existed:v,behaviorExisted:C,accessControlExisted:g},changes:k,rollback:T,applied:!1};if(!r.apply)return E;let D=`${r.distributionId}:${t.pathPattern}`;if(r.confirm!==D)throw Error(`Pass exact confirmation token ${D} to mutate the live distribution`);if(!l)l=await n.iam.createRole({RoleName:t.roleName,Description:`Execution role for ${r.functionName}`,AssumeRolePolicyDocument:JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Principal:{Service:"lambda.amazonaws.com"},Action:"sts:AssumeRole"}]}),Tags:[{Key:"ManagedBy",Value:"ts-cloud"}]});await n.iam.putRolePolicy({RoleName:t.roleName,PolicyName:"lambda-logs",PolicyDocument:JSON.stringify({Version:"2012-10-17",Statement:[{Effect:"Allow",Action:["logs:CreateLogStream","logs:PutLogEvents"],Resource:`arn:aws:logs:${t.region}:${i.Account}:log-group:/aws/lambda/${r.functionName}:*`}]})});try{await n.logs.createLogGroup(`/aws/lambda/${r.functionName}`,{ManagedBy:"ts-cloud"})}catch(G){if(!Bl(G))throw G}await n.logs.putRetentionPolicy(`/aws/lambda/${r.functionName}`,t.logRetentionDays);let N=r.code||Bp;if(m)await n.lambda.updateFunctionCodeInline(r.functionName,N,"index.mjs"),await n.lambda.waitForFunctionActive(r.functionName),await n.lambda.updateFunctionConfiguration({FunctionName:r.functionName,Runtime:"nodejs22.x",Role:l.Arn,Handler:"index.handler",Description:`Private API origin for ${r.expectedAlias}`,Timeout:t.timeout,MemorySize:t.memorySize});else{let G;for(let L=0;L<8;L++)try{await n.lambda.createFunctionWithCode({FunctionName:r.functionName,Runtime:"nodejs22.x",Role:l.Arn,Handler:"index.handler",Code:N,Filename:"index.mjs",Description:`Private API origin for ${r.expectedAlias}`,Timeout:t.timeout,MemorySize:t.memorySize}),G=void 0;break}catch(H){if(G=H,!String(H.message).toLowerCase().includes("role"))throw H;await n.sleep(1000)}if(G)throw G}await n.lambda.waitForFunctionActive(r.functionName);let _=f||await n.lambda.createFunctionUrl({FunctionName:r.functionName,AuthType:"AWS_IAM",InvokeMode:"BUFFERED"});if(!_.FunctionUrl)throw Error("Lambda did not return a function URL");let B=new URL(_.FunctionUrl).hostname,V=await n.cloudfront.findOrCreateOriginAccessControl(S,"lambda"),K=o.ARN||`arn:aws:cloudfront::${i.Account}:distribution/${r.distributionId}`,oe=[{FunctionName:r.functionName,StatementId:"AllowCloudFrontFunctionUrl",Action:"lambda:InvokeFunctionUrl",Principal:"cloudfront.amazonaws.com",SourceArn:K,FunctionUrlAuthType:"AWS_IAM"},{FunctionName:r.functionName,StatementId:"AllowCloudFrontInvoke",Action:"lambda:InvokeFunction",Principal:"cloudfront.amazonaws.com",SourceArn:K,InvokedViaFunctionUrl:!0}];for(let G of oe)try{await n.lambda.addPermission(G)}catch(L){if(!Bl(L))throw L}return await n.cloudfront.upsertExistingDistributionOrigin(r.distributionId,{id:t.originId,domainName:B,pathPattern:t.pathPattern,originAccessControlId:V.Id}),{...E,function:{...E.function,url:_.FunctionUrl},applied:!0}}async function Vl(r){let e=`https://${r.alias}/`,t=`https://${r.alias}/api/health`,n=await fetch(e,{redirect:"follow",headers:{"cache-control":"no-cache"}}),i=new Uint8Array(await n.arrayBuffer()),o=Mp("sha256").update(i).digest("hex"),s=Date.now(),c=await fetch(t,{redirect:"follow",headers:{"cache-control":"no-cache"}}),l=Date.now()-s,d=await c.text(),u=d;try{u=JSON.parse(d)}catch{}let m;if(r.logs&&r.functionName){let f=await r.logs.filterLogEvents({logGroupName:`/aws/lambda/${r.functionName}`,startTime:s-60000,filterPattern:'"Init Duration"',limit:25});for(let y of f.events||[]){let S=y.message?.match(/Init Duration:\s*([0-9.]+)\s*ms/i);if(S)m=Number(S[1])}}return{frontend:{url:e,status:n.status,sha256:o,unchanged:r.expectedFrontendSha256?o===r.expectedFrontendSha256:void 0,bytes:i.byteLength},api:{url:t,status:c.status,latencyMs:l,healthy:c.ok&&!!u?.ok,body:u},coldStart:{observed:m!==void 0,initDurationMs:m},verifiedAt:(r.now?.()||new Date).toISOString()}}function Wl(r){let e=(r.memoryMb||256)/1024,t=Math.max(0,r.requests-(r.includeFreeTier===!1?0:1e6)),n=Math.max(0,r.requests*(r.averageDurationMs/1000)*e-(r.includeFreeTier===!1?0:400000)),i=t/1e6*0.2+n*0.0000166667,o=9.010025,s=16.425;return{lambdaUsd:Number(i.toFixed(2)),alwaysOnFargateAndAlbUsd:Number(25.435025000000003 .toFixed(2)),assumptions:["us-east-1 public list rates snapshot, July 2026","Lambda x86 request and duration pricing with optional monthly free tier","Fargate comparison uses one always-on 0.25 vCPU/0.5 GB task plus one ALB before LCU charges","excludes CloudFront, data transfer, logs, taxes, and application data services"]}}function Bo(r){r.command("cdn:api:deploy <distributionId> <alias>","Plan or deploy a private Lambda API behind an existing static distribution").option("--function <name>","Lambda function name",{default:"ts-cloud-static-api"}).option("--path <pattern>","CloudFront API path",{default:"/api/*"}).option("--origin-id <id>","Stable CloudFront origin ID").option("--role <name>","Lambda execution role name").option("--region <region>","Lambda region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").option("--memory <mb>","Lambda memory in MB",{default:"256"}).option("--timeout <seconds>","Lambda timeout",{default:"10"}).option("--retention <days>","CloudWatch log retention",{default:"14"}).option("--apply","Create resources and patch the live distribution").option("--confirm <text>","Exact distribution:path confirmation").action(async(e,t,n)=>{try{let i=await Ul({distributionId:e,expectedAlias:t,functionName:n.function||"ts-cloud-static-api",pathPattern:n.path,originId:n.originId,roleName:n.role,region:n.region,profile:n.profile,memorySize:Number(n.memory),timeout:Number(n.timeout),logRetentionDays:Number(n.retention),apply:!!n.apply,confirm:n.confirm});if(a(JSON.stringify(i,null,2)),i.applied)h("Private API origin submitted to CloudFront. Run cdn:api:verify after propagation.");else a(`Plan only. Re-run with --apply --confirm '${e}:${i.origin.pathPattern}'.`)}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}}),r.command("cdn:api:verify <alias>","Verify static frontend integrity, API health, latency, and Lambda initialization").option("--function <name>","Lambda function name",{default:"ts-cloud-static-api"}).option("--frontend-sha256 <digest>","Expected SHA-256 of the frontend response").option("--region <region>","Lambda region",{default:"us-east-1"}).option("--profile <name>","AWS credential profile").action(async(e,t)=>{try{let n=new rt(t.region||"us-east-1",t.profile),i=await Vl({alias:e,expectedFrontendSha256:t.frontendSha256,logs:n,functionName:t.function||"ts-cloud-static-api"});if(a(JSON.stringify(i,null,2)),!i.api.healthy||i.frontend.unchanged===!1)throw Error("Static API verification failed");h("Static frontend and Lambda API verification passed.")}catch(n){p(n instanceof Error?n.message:String(n)),process.exitCode=1}}),r.command("cdn:api:cost <requests>","Compare a low-volume Lambda API with one always-on Fargate task and ALB").option("--duration <milliseconds>","Average Lambda duration",{default:"100"}).option("--memory <mb>","Lambda memory in MB",{default:"256"}).option("--no-free-tier","Exclude the Lambda free tier").action((e,t)=>{let n=Wl({requests:Number(e),averageDurationMs:Number(t.duration),memoryMb:Number(t.memory),includeFreeTier:t.freeTier!==!1});a(JSON.stringify(n,null,2))}),r.command("cdn:origin:add <distributionId> <domain>","Safely add a backend origin and path to an existing distribution").option("--id <origin>","Stable origin ID",{default:"api-backend"}).option("--path <pattern>","Path behavior",{default:"/api/*"}).option("--profile <name>","AWS credential profile").option("--replace","Replace a reviewed path/origin collision").option("--apply","Apply the ETag-protected patch").option("--confirm <text>","Exact distribution:path confirmation").action(async(e,t,n)=>{try{let i=new ne(n.profile),o={id:n.id??"api-backend",domainName:t,pathPattern:n.path??"/api/*",replaceExisting:!!n.replace},s=await i.upsertExistingDistributionOrigin(e,{...o,dryRun:!0});if(a(JSON.stringify(s,null,2)),!n.apply){a("Dry run only. Re-run with --apply and the exact --confirm token.");return}let c=`${e}:${s.pathPattern}`;if(n.confirm!==c)throw Error(`Pass --confirm "${c}" to apply this live distribution change`);let l=await i.upsertExistingDistributionOrigin(e,o);h(l.changed?"Backend origin patch submitted.":"Backend origin already matches."),a(JSON.stringify(l,null,2))}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}}),r.command("cdn:origin:remove <distributionId> <domain>","Remove one exact backend behavior and its unreferenced origin").option("--id <origin>","Origin ID",{default:"api-backend"}).option("--path <pattern>","Path behavior",{default:"/api/*"}).option("--profile <name>","AWS credential profile").option("--apply","Apply the ETag-protected rollback").option("--confirm <text>","Exact remove:distribution:path confirmation").action(async(e,t,n)=>{try{let i=new ne(n.profile),o={id:n.id??"api-backend",domainName:t,pathPattern:n.path??"/api/*"},s=await i.removeExistingDistributionOrigin(e,{...o,dryRun:!0});if(a(JSON.stringify(s,null,2)),!n.apply){a("Dry run only. Re-run with --apply and the exact --confirm token.");return}let c=`remove:${e}:${s.pathPattern}`;if(n.confirm!==c)throw Error(`Pass --confirm "${c}" to apply this live distribution rollback`);let l=await i.removeExistingDistributionOrigin(e,o);h(l.changed?"Backend behavior rollback submitted.":"Backend behavior was already absent."),a(JSON.stringify(l,null,2))}catch(i){p(i instanceof Error?i.message:String(i)),process.exitCode=1}}),r.command("cdn:list","List all CloudFront distributions").action(async()=>{w("CloudFront Distributions");try{let e=await P(),t=new ne,n=new A("Fetching distributions...");n.start();let i=await t.listDistributions();if(n.succeed(`Found ${i.length} distribution(s)`),i.length===0){a("No CloudFront distributions found"),a("Use `cloud cdn:create` to create a new distribution");return}x(["ID","Domain","Status","Enabled","Aliases"],i.map((o)=>[o.Id||"N/A",o.DomainName||"N/A",o.Status||"N/A",o.Enabled?"Yes":"No",(o.Aliases?.Items?.length||0).toString()]))}catch(e){p(`Failed to list distributions: ${e.message}`),process.exit(1)}}),r.command("cdn:status <distributionId>","Get CloudFront distribution status").action(async(e)=>{w(`CloudFront Distribution: ${e}`);try{let t=new ne,n=new A("Fetching distribution details...");n.start();let i=await t.getDistribution(e);if(n.succeed("Distribution details loaded"),a(`
|
|
473
473
|
Distribution Information:`),a(` ID: ${i.Id}`),a(` Domain: ${i.DomainName}`),a(` Status: ${i.Status}`),a(` ARN: ${i.ARN}`),a(` Enabled: ${i.Enabled?"Yes":"No"}`),i.Aliases?.Items?.length)a(` Aliases: ${i.Aliases.Items.join(", ")}`);let o=await t.getDistributionConfig(e);a(`
|
|
474
474
|
Origins:`);let s=o.DistributionConfig?.Origins?.Items||[];if(Array.isArray(s))for(let c of s)a(` - ${c.Id}: ${c.DomainName}`);if(o.DistributionConfig?.DefaultCacheBehavior){let c=o.DistributionConfig.DefaultCacheBehavior;a(`
|
|
475
475
|
Default Cache Behavior:`),a(` Target Origin: ${c.TargetOriginId}`),a(` Viewer Protocol: ${c.ViewerProtocolPolicy}`)}}catch(t){p(`Failed to get distribution: ${t.message}`),process.exit(1)}}),r.command("cdn:create","Create a new CloudFront distribution for an S3 bucket").option("--bucket <name>","S3 bucket name").option("--region <region>","S3 bucket region",{default:"us-east-1"}).option("--alias <domain>","Custom domain alias (CNAME)").option("--certificate <arn>","ACM certificate ARN for custom domain").option("--comment <text>","Distribution comment/description").action(async(e)=>{w("Create CloudFront Distribution");try{if(!e.bucket)p("--bucket is required"),a("Example: cloud cdn:create --bucket my-bucket --region us-east-1"),process.exit(1);let t=new ne;if(a(`Bucket: ${e.bucket}`),a(`Region: ${e.region}`),e.alias)a(`Alias: ${e.alias}`);if(!await O(`
|
|
@@ -546,32 +546,32 @@ Schedule Information:`),a(` Name: ${o.Name}`),a(` ARN: ${o.Arn}`),a(` Group:
|
|
|
546
546
|
Target:`),a(` ARN: ${o.Target.Arn}`),a(` Role ARN: ${o.Target.RoleArn}`),o.Target.Input)a(` Input: ${o.Target.Input}`)}if(a(`
|
|
547
547
|
Flexible Time Window:`),a(` Mode: ${o.FlexibleTimeWindow?.Mode}`),o.FlexibleTimeWindow?.MaximumWindowInMinutes)a(` Max Window: ${o.FlexibleTimeWindow.MaximumWindowInMinutes} minutes`);if(o.StartDate)a(`
|
|
548
548
|
Start Date: ${o.StartDate}`);if(o.EndDate)a(`End Date: ${o.EndDate}`);a(`
|
|
549
|
-
Created: ${o.CreationDate||"N/A"}`),a(`Last Modified: ${o.LastModificationDate||"N/A"}`)}catch(n){p(`Failed to get schedule: ${n.message}`),process.exit(1)}}),r.command("scheduler:groups","List schedule groups").option("--region <region>","AWS region").action(async(e)=>{w("Schedule Groups");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new ct(n),o=new A("Fetching groups...");o.start();let c=(await i.listScheduleGroups()).ScheduleGroups||[];if(o.succeed(`Found ${c.length} group(s)`),c.length===0){a("No schedule groups found");return}x(["Name","State","Created"],c.map((l)=>[l.Name||"N/A",l.State||"N/A",l.CreationDate?new Date(l.CreationDate).toLocaleString():"N/A"]))}catch(t){p(`Failed to list groups: ${t.message}`),process.exit(1)}})}function Yo(r){r.command("events:list","List all EventBridge rules").option("--region <region>","AWS region").option("--bus <name>","Event bus name",{default:"default"}).action(async(e)=>{w("EventBridge Rules");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new
|
|
550
|
-
Examples:`),a(' Schedule: --schedule "rate(5 minutes)"'),a(` Pattern: --pattern '{"source": ["aws.ec2"]}'`);return}let n=new
|
|
549
|
+
Created: ${o.CreationDate||"N/A"}`),a(`Last Modified: ${o.LastModificationDate||"N/A"}`)}catch(n){p(`Failed to get schedule: ${n.message}`),process.exit(1)}}),r.command("scheduler:groups","List schedule groups").option("--region <region>","AWS region").action(async(e)=>{w("Schedule Groups");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new ct(n),o=new A("Fetching groups...");o.start();let c=(await i.listScheduleGroups()).ScheduleGroups||[];if(o.succeed(`Found ${c.length} group(s)`),c.length===0){a("No schedule groups found");return}x(["Name","State","Created"],c.map((l)=>[l.Name||"N/A",l.State||"N/A",l.CreationDate?new Date(l.CreationDate).toLocaleString():"N/A"]))}catch(t){p(`Failed to list groups: ${t.message}`),process.exit(1)}})}function Yo(r){r.command("events:list","List all EventBridge rules").option("--region <region>","AWS region").option("--bus <name>","Event bus name",{default:"default"}).action(async(e)=>{w("EventBridge Rules");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new Ye(n),o=new A("Fetching rules...");o.start();let c=(await i.listRules({EventBusName:e.bus})).Rules||[];if(o.succeed(`Found ${c.length} rule(s)`),c.length===0){a("No EventBridge rules found"),a("Use `cloud events:create` to create a new rule");return}x(["Name","State","Schedule/Pattern","Description"],c.map((l)=>[l.Name||"N/A",l.State||"N/A",l.ScheduleExpression||(l.EventPattern?"Event Pattern":"N/A"),(l.Description||"").substring(0,40)]))}catch(t){p(`Failed to list rules: ${t.message}`),process.exit(1)}}),r.command("events:create <name>","Create a new EventBridge rule").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).option("--schedule <expression>","Schedule expression (rate or cron)").option("--pattern <json>","Event pattern JSON").option("--pattern-file <path>","Event pattern from JSON file").option("--description <text>","Rule description").option("--disabled","Create in disabled state").action(async(e,t)=>{w("Create EventBridge Rule");try{if(!t.schedule&&!t.pattern&&!t.patternFile){p("Either --schedule or --pattern/--pattern-file is required"),a(`
|
|
550
|
+
Examples:`),a(' Schedule: --schedule "rate(5 minutes)"'),a(` Pattern: --pattern '{"source": ["aws.ec2"]}'`);return}let n=new Ye(t.region),i;if(t.patternFile)i=await Bun.file(t.patternFile).text();else if(t.pattern)i=t.pattern;if(a(`Name: ${e}`),a(`Event Bus: ${t.bus}`),t.schedule)a(`Schedule: ${t.schedule}`);if(i)a(`Event Pattern: ${i}`);if(a(`State: ${t.disabled?"DISABLED":"ENABLED"}`),!await O(`
|
|
551
551
|
Create this rule?`,!0)){a("Operation cancelled");return}let s=new A("Creating rule...");s.start();let c=await n.putRule({Name:e,EventBusName:t.bus,ScheduleExpression:t.schedule,EventPattern:i,Description:t.description,State:t.disabled?"DISABLED":"ENABLED"});s.succeed("Rule created"),h(`
|
|
552
|
-
Rule ARN: ${c.RuleArn}`),a("\nNote: Add targets to the rule with `cloud events:target`")}catch(n){p(`Failed to create rule: ${n.message}`),process.exit(1)}}),r.command("events:delete <name>","Delete an EventBridge rule").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).option("--force","Remove all targets and delete rule").action(async(e,t)=>{w("Delete EventBridge Rule");try{let n=new
|
|
552
|
+
Rule ARN: ${c.RuleArn}`),a("\nNote: Add targets to the rule with `cloud events:target`")}catch(n){p(`Failed to create rule: ${n.message}`),process.exit(1)}}),r.command("events:delete <name>","Delete an EventBridge rule").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).option("--force","Remove all targets and delete rule").action(async(e,t)=>{w("Delete EventBridge Rule");try{let n=new Ye(t.region);if(R(`This will delete rule: ${e}`),!await O(`
|
|
553
553
|
Delete this rule?`,!1)){a("Operation cancelled");return}let o=new A("Checking targets...");o.start();let s=await n.listTargetsByRule({Rule:e,EventBusName:t.bus});if(s.Targets&&s.Targets.length>0){if(!t.force){o.fail("Rule has targets"),a(`
|
|
554
|
-
The rule has ${s.Targets.length} target(s).`),a("Use --force to remove targets and delete the rule.");return}o.text="Removing targets...",await n.removeTargets({Rule:e,EventBusName:t.bus,Ids:s.Targets.map((c)=>c.Id)})}o.text="Deleting rule...",await n.deleteRule({Name:e,EventBusName:t.bus}),o.succeed("Rule deleted")}catch(n){p(`Failed to delete rule: ${n.message}`),process.exit(1)}}),r.command("events:describe <name>","Show EventBridge rule details").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).action(async(e,t)=>{w(`Rule: ${e}`);try{let n=new
|
|
554
|
+
The rule has ${s.Targets.length} target(s).`),a("Use --force to remove targets and delete the rule.");return}o.text="Removing targets...",await n.removeTargets({Rule:e,EventBusName:t.bus,Ids:s.Targets.map((c)=>c.Id)})}o.text="Deleting rule...",await n.deleteRule({Name:e,EventBusName:t.bus}),o.succeed("Rule deleted")}catch(n){p(`Failed to delete rule: ${n.message}`),process.exit(1)}}),r.command("events:describe <name>","Show EventBridge rule details").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).action(async(e,t)=>{w(`Rule: ${e}`);try{let n=new Ye(t.region),i=new A("Fetching rule...");i.start();let o=await n.describeRule({Name:e,EventBusName:t.bus});if(!o){i.fail("Rule not found");return}let s=await n.listTargetsByRule({Rule:e,EventBusName:t.bus});if(i.succeed("Rule loaded"),a(`
|
|
555
555
|
Rule Information:`),a(` Name: ${o.Name}`),a(` ARN: ${o.Arn}`),a(` State: ${o.State}`),a(` Event Bus: ${o.EventBusName}`),o.Description)a(` Description: ${o.Description}`);if(o.ScheduleExpression)a(`
|
|
556
556
|
Schedule Expression: ${o.ScheduleExpression}`);if(o.EventPattern)a(`
|
|
557
557
|
Event Pattern:`),console.log(JSON.stringify(JSON.parse(o.EventPattern),null,2));if(s.Targets&&s.Targets.length>0){a(`
|
|
558
558
|
Targets (${s.Targets.length}):`);for(let c of s.Targets)if(a(` - ${c.Id}: ${c.Arn}`),c.Input)a(` Input: ${c.Input}`)}else a(`
|
|
559
|
-
No targets configured.`)}catch(n){p(`Failed to get rule: ${n.message}`),process.exit(1)}}),r.command("events:target <ruleName>","Add a target to an EventBridge rule").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).option("--id <id>","Target ID").option("--arn <arn>","Target ARN (Lambda, SQS, SNS, etc.)").option("--role <arn>","IAM role ARN (for some targets)").option("--input <json>","Constant JSON input").option("--input-path <path>","JSONPath expression for input").action(async(e,t)=>{w("Add EventBridge Target");try{if(!t.arn){p("--arn is required");return}let n=new
|
|
559
|
+
No targets configured.`)}catch(n){p(`Failed to get rule: ${n.message}`),process.exit(1)}}),r.command("events:target <ruleName>","Add a target to an EventBridge rule").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).option("--id <id>","Target ID").option("--arn <arn>","Target ARN (Lambda, SQS, SNS, etc.)").option("--role <arn>","IAM role ARN (for some targets)").option("--input <json>","Constant JSON input").option("--input-path <path>","JSONPath expression for input").action(async(e,t)=>{w("Add EventBridge Target");try{if(!t.arn){p("--arn is required");return}let n=new Ye(t.region),i=t.id||`target-${Date.now()}`;if(a(`Rule: ${e}`),a(`Target ID: ${i}`),a(`Target ARN: ${t.arn}`),!await O(`
|
|
560
560
|
Add this target?`,!0)){a("Operation cancelled");return}let s=new A("Adding target...");s.start();let c={Id:i,Arn:t.arn};if(t.role)c.RoleArn=t.role;if(t.input)c.Input=t.input;if(t.inputPath)c.InputPath=t.inputPath;await n.putTargets({Rule:e,EventBusName:t.bus,Targets:[c]}),s.succeed("Target added"),h(`
|
|
561
|
-
Target ${i} added to rule ${e}`)}catch(n){p(`Failed to add target: ${n.message}`),process.exit(1)}}),r.command("events:buses","List event buses").option("--region <region>","AWS region").action(async(e)=>{w("Event Buses");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new
|
|
561
|
+
Target ${i} added to rule ${e}`)}catch(n){p(`Failed to add target: ${n.message}`),process.exit(1)}}),r.command("events:buses","List event buses").option("--region <region>","AWS region").action(async(e)=>{w("Event Buses");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new Ye(n),o=new A("Fetching event buses...");o.start();let c=(await i.listEventBuses()).EventBuses||[];if(o.succeed(`Found ${c.length} event bus(es)`),c.length===0){a("No event buses found");return}x(["Name","ARN","Policy"],c.map((l)=>[l.Name||"N/A",l.Arn||"N/A",l.Policy?"Custom":"Default"]))}catch(t){p(`Failed to list event buses: ${t.message}`),process.exit(1)}}),r.command("events:enable <name>","Enable an EventBridge rule").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).action(async(e,t)=>{w("Enable EventBridge Rule");try{let n=new Ye(t.region),i=new A("Enabling rule...");i.start(),await n.enableRule({Name:e,EventBusName:t.bus}),i.succeed("Rule enabled")}catch(n){p(`Failed to enable rule: ${n.message}`),process.exit(1)}}),r.command("events:disable <name>","Disable an EventBridge rule").option("--region <region>","AWS region",{default:"us-east-1"}).option("--bus <name>","Event bus name",{default:"default"}).action(async(e,t)=>{w("Disable EventBridge Rule");try{let n=new Ye(t.region),i=new A("Disabling rule...");i.start(),await n.disableRule({Name:e,EventBusName:t.bus}),i.succeed("Rule disabled")}catch(n){p(`Failed to disable rule: ${n.message}`),process.exit(1)}})}function Zo(r){r.command("email:identities","List verified email identities").option("--region <region>","AWS region").action(async(e)=>{w("Email Identities");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new Je(n),o=new A("Fetching identities...");o.start();let c=(await i.listEmailIdentities()).EmailIdentities||[];if(o.succeed(`Found ${c.length} identity(s)`),c.length===0){a("No email identities found"),a("Use `cloud email:verify` to verify an email or domain");return}let l=[];for(let d of c){let u=d.IdentityName||"Unknown",m="Unknown";try{m=(await i.getEmailIdentity(u)).VerificationStatus||"Unknown"}catch{}let f=d.IdentityType==="EMAIL_ADDRESS"?"Email":d.IdentityType==="DOMAIN"?"Domain":d.IdentityType||"Unknown";l.push([u,f,m])}x(["Identity","Type","Verification Status"],l)}catch(t){let n=t instanceof Error?t.message:String(t);p(`Failed to list identities: ${n}`),process.exit(1)}}),r.command("email:verify <identity>","Verify an email address or domain").option("--region <region>","AWS region",{default:"us-east-1"}).action(async(e,t)=>{w("Verify Email Identity");try{let n=new Je(t.region),i=e.includes("@");if(a(`Identity: ${e}`),a(`Type: ${i?"Email Address":"Domain"}`),!await O(`
|
|
562
562
|
Send verification?`,!0)){a("Operation cancelled");return}let s=new A("Initiating verification...");if(s.start(),i)await n.createEmailIdentity({EmailIdentity:e}),s.succeed("Verification email sent"),a(`
|
|
563
563
|
A verification email has been sent to ${e}`),a("Click the link in the email to complete verification.");else{let c=await n.verifyDomain(e);s.succeed("Domain verification initiated");let l=c.dkimTokens||[];if(l.length>0){a(`
|
|
564
564
|
DKIM Records (for email authentication):`);for(let d of l)a(`
|
|
565
565
|
Name: ${d}._domainkey.${e}`),a(" Type: CNAME"),a(` Value: ${d}.dkim.amazonses.com`)}a(`
|
|
566
|
-
Verification Status: ${c.verificationStatus||"PENDING"}`)}}catch(n){let i=n instanceof Error?n.message:String(n);p(`Failed to verify identity: ${i}`),process.exit(1)}}),r.command("email:delete <identity>","Delete a verified email identity").option("--region <region>","AWS region",{default:"us-east-1"}).action(async(e,t)=>{w("Delete Email Identity");try{let n=new
|
|
567
|
-
Delete this identity?`,!1)){a("Operation cancelled");return}let o=new A("Deleting identity...");o.start(),await n.deleteEmailIdentity(e),o.succeed("Identity deleted")}catch(n){let i=n instanceof Error?n.message:String(n);p(`Failed to delete identity: ${i}`),process.exit(1)}}),r.command("email:send","Send a test email").option("--region <region>","AWS region",{default:"us-east-1"}).option("--from <email>","From email address (must be verified)").option("--to <email>","To email address").option("--subject <text>","Email subject").option("--body <text>","Email body (text)").option("--html <html>","Email body (HTML)").action(async(e)=>{w("Send Email");try{let t=new
|
|
566
|
+
Verification Status: ${c.verificationStatus||"PENDING"}`)}}catch(n){let i=n instanceof Error?n.message:String(n);p(`Failed to verify identity: ${i}`),process.exit(1)}}),r.command("email:delete <identity>","Delete a verified email identity").option("--region <region>","AWS region",{default:"us-east-1"}).action(async(e,t)=>{w("Delete Email Identity");try{let n=new Je(t.region);if(R(`This will remove identity: ${e}`),!await O(`
|
|
567
|
+
Delete this identity?`,!1)){a("Operation cancelled");return}let o=new A("Deleting identity...");o.start(),await n.deleteEmailIdentity(e),o.succeed("Identity deleted")}catch(n){let i=n instanceof Error?n.message:String(n);p(`Failed to delete identity: ${i}`),process.exit(1)}}),r.command("email:send","Send a test email").option("--region <region>","AWS region",{default:"us-east-1"}).option("--from <email>","From email address (must be verified)").option("--to <email>","To email address").option("--subject <text>","Email subject").option("--body <text>","Email body (text)").option("--html <html>","Email body (HTML)").action(async(e)=>{w("Send Email");try{let t=new Je(e.region),n=e.from||await Z("From (verified email)"),i=e.to||await Z("To"),o=e.subject||await Z("Subject","Test Email from ts-cloud"),s=e.body||await Z("Body","This is a test email sent from ts-cloud CLI.");if(a(`
|
|
568
568
|
From: ${n}`),a(`To: ${i}`),a(`Subject: ${o}`),!await O(`
|
|
569
569
|
Send this email?`,!0)){a("Operation cancelled");return}let l=new A("Sending email...");l.start();let d=await t.sendEmail({FromEmailAddress:n,Destination:{ToAddresses:[i]},Content:{Simple:{Subject:{Data:o},Body:{Text:{Data:s},...e.html&&{Html:{Data:e.html}}}}}});l.succeed("Email sent"),h(`
|
|
570
|
-
Message ID: ${d.MessageId}`)}catch(t){let n=t instanceof Error?t.message:String(t);p(`Failed to send email: ${n}`),process.exit(1)}}),r.command("email:templates","List email templates").option("--region <region>","AWS region").action(async(e)=>{w("Email Templates");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new
|
|
570
|
+
Message ID: ${d.MessageId}`)}catch(t){let n=t instanceof Error?t.message:String(t);p(`Failed to send email: ${n}`),process.exit(1)}}),r.command("email:templates","List email templates").option("--region <region>","AWS region").action(async(e)=>{w("Email Templates");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new Je(n),o=new A("Fetching templates...");o.start();let c=(await i.listEmailTemplates()).TemplatesMetadata||[];if(o.succeed(`Found ${c.length} template(s)`),c.length===0){a("No email templates found"),a("Use `cloud email:template:create` to create a template");return}x(["Name","Created"],c.map((l)=>[l.TemplateName||"N/A",l.CreatedTimestamp?new Date(l.CreatedTimestamp).toLocaleString():"N/A"]))}catch(t){let n=t instanceof Error?t.message:String(t);p(`Failed to list templates: ${n}`),process.exit(1)}}),r.command("email:template:create <name>","Create an email template").option("--region <region>","AWS region",{default:"us-east-1"}).option("--subject <text>","Email subject (supports {{variable}} placeholders)").option("--text <text>","Text body").option("--html <html>","HTML body").option("--html-file <path>","HTML body from file").action(async(e,t)=>{w("Create Email Template");try{let n=new Je(t.region),i=t.subject||await Z("Subject template","Hello {{name}}"),o=t.text||await Z("Text body","Hello {{name}}, this is a test."),s=t.html;if(t.htmlFile)s=await Bun.file(t.htmlFile).text();if(a(`
|
|
571
571
|
Template Name: ${e}`),a(`Subject: ${i}`),!await O(`
|
|
572
572
|
Create this template?`,!0)){a("Operation cancelled");return}let l=new A("Creating template...");l.start(),await n.createEmailTemplate({TemplateName:e,TemplateContent:{Subject:i,Text:o,Html:s}}),l.succeed("Template created"),a(`
|
|
573
|
-
To send using this template:`),a(` cloud email:send:template --template ${e} --to user@example.com --data '{"name":"John"}'`)}catch(n){let i=n instanceof Error?n.message:String(n);p(`Failed to create template: ${i}`),process.exit(1)}}),r.command("email:template:delete <name>","Delete an email template").option("--region <region>","AWS region",{default:"us-east-1"}).action(async(e,t)=>{w("Delete Email Template");try{let n=new
|
|
574
|
-
Delete this template?`,!1)){a("Operation cancelled");return}let o=new A("Deleting template...");o.start(),await n.deleteEmailTemplate(e),o.succeed("Template deleted")}catch(n){let i=n instanceof Error?n.message:String(n);p(`Failed to delete template: ${i}`),process.exit(1)}}),r.command("email:stats","Show SES sending statistics").option("--region <region>","AWS region").action(async(e)=>{w("Email Statistics");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new
|
|
573
|
+
To send using this template:`),a(` cloud email:send:template --template ${e} --to user@example.com --data '{"name":"John"}'`)}catch(n){let i=n instanceof Error?n.message:String(n);p(`Failed to create template: ${i}`),process.exit(1)}}),r.command("email:template:delete <name>","Delete an email template").option("--region <region>","AWS region",{default:"us-east-1"}).action(async(e,t)=>{w("Delete Email Template");try{let n=new Je(t.region);if(R(`This will delete template: ${e}`),!await O(`
|
|
574
|
+
Delete this template?`,!1)){a("Operation cancelled");return}let o=new A("Deleting template...");o.start(),await n.deleteEmailTemplate(e),o.succeed("Template deleted")}catch(n){let i=n instanceof Error?n.message:String(n);p(`Failed to delete template: ${i}`),process.exit(1)}}),r.command("email:stats","Show SES sending statistics").option("--region <region>","AWS region").action(async(e)=>{w("Email Statistics");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=new Je(n),o=new A("Fetching statistics...");o.start();let[s,c]=await Promise.all([i.getSendQuota(),i.getSendStatistics()]);o.succeed("Statistics loaded"),a(`
|
|
575
575
|
Sending Quota:`),a(` Max 24-hour Send: ${s.Max24HourSend||0}`),a(` Max Send Rate: ${s.MaxSendRate||0} emails/second`),a(` Sent Last 24h: ${s.SentLast24Hours||0}`);let l=(s.Max24HourSend||0)-(s.SentLast24Hours||0);if(a(` Remaining: ${l}`),c.SendDataPoints&&c.SendDataPoints.length>0){a(`
|
|
576
576
|
Recent Statistics:`);let d=0,u=0,m=0,f=0;for(let y of c.SendDataPoints)d+=y.DeliveryAttempts||0,u+=y.Bounces||0,m+=y.Complaints||0,f+=y.Rejects||0;if(a(` Total Attempts: ${d}`),a(` Bounces: ${u}`),a(` Complaints: ${m}`),a(` Rejects: ${f}`),d>0){let y=(u/d*100).toFixed(2),S=(m/d*100).toFixed(2);if(a(`
|
|
577
577
|
Bounce Rate: ${y}%`),a(` Complaint Rate: ${S}%`),Number.parseFloat(y)>5)R(`
|
|
@@ -638,7 +638,7 @@ Job ID: ${s.BackupJobId}`),a("Use `cloud backup:jobs` to check status")}catch(n)
|
|
|
638
638
|
Restore will create new resources. Review carefully.`),!await O(`
|
|
639
639
|
Start restore?`,!1)){a("Operation cancelled");return}let s=new A("Starting restore job...");s.start();let c=await n.startRestoreJob({RecoveryPointArn:e,IamRoleArn:t.role,Metadata:i,IdempotencyToken:`cli-${Date.now()}`});s.succeed("Restore job started"),h(`
|
|
640
640
|
Job ID: ${c.RestoreJobId}`),a("Use `cloud backup:restore-jobs` to check status")}catch(n){p(`Failed to start restore: ${n.message}`),process.exit(1)}}),r.command("backup:jobs","List recent backup jobs").option("--region <region>","AWS region").option("--state <state>","Filter by state (CREATED, PENDING, RUNNING, COMPLETED, FAILED)").action(async(e)=>{w("Backup Jobs");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=await Le(n),o=new A("Fetching backup jobs...");o.start();let c=(await i.listBackupJobs({ByState:e.state,MaxResults:50})).BackupJobs||[];if(o.succeed(`Found ${c.length} job(s)`),c.length===0){a("No backup jobs found");return}x(["Job ID","Resource","State","Started","Size"],c.map((l)=>[l.BackupJobId?.substring(0,16)||"N/A",l.ResourceArn?.split(":").pop()||"N/A",l.State||"N/A",l.CreationDate?new Date(l.CreationDate).toLocaleString():"N/A",l.BackupSizeInBytes?Xl(l.BackupSizeInBytes):"N/A"]))}catch(t){p(`Failed to list backup jobs: ${t.message}`),process.exit(1)}}),r.command("backup:restore-jobs","List recent restore jobs").option("--region <region>","AWS region").option("--status <status>","Filter by status").action(async(e)=>{w("Restore Jobs");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=await Le(n),o=new A("Fetching restore jobs...");o.start();let c=(await i.listRestoreJobs({ByStatus:e.status,MaxResults:50})).RestoreJobs||[];if(o.succeed(`Found ${c.length} job(s)`),c.length===0){a("No restore jobs found");return}x(["Job ID","Resource","Status","Started","Completed"],c.map((l)=>[l.RestoreJobId?.substring(0,16)||"N/A",l.CreatedResourceArn?.split(":").pop()||"Pending",l.Status||"N/A",l.CreationDate?new Date(l.CreationDate).toLocaleString():"N/A",l.CompletionDate?new Date(l.CompletionDate).toLocaleString():"-"]))}catch(t){p(`Failed to list restore jobs: ${t.message}`),process.exit(1)}}),r.command("backup:schedule","Show backup schedule overview").option("--region <region>","AWS region").action(async(e)=>{w("Backup Schedule Overview");try{let t=await P(),n=e.region||t.project.region||"us-east-1",i=await Le(n),o=new A("Fetching backup plans...");o.start();let c=(await i.listBackupPlans()).BackupPlansList||[];if(o.succeed(`Found ${c.length} backup plan(s)`),c.length===0){a("No backup plans configured");return}for(let l of c){a(`
|
|
641
|
-
${l.BackupPlanName}`),a("=".repeat(40));try{let u=(await i.getBackupPlan(l.BackupPlanId)).BackupPlan?.Rules||[];for(let f of u)if(a(` Rule: ${f.RuleName}`),a(` Schedule: ${f.ScheduleExpression}`),a(` Vault: ${f.TargetBackupVaultName}`),f.Lifecycle?.DeleteAfterDays)a(` Retention: ${f.Lifecycle.DeleteAfterDays} days`);let m=await i.listBackupSelections(l.BackupPlanId);if(m.BackupSelectionsList&&m.BackupSelectionsList.length>0)a(` Selections: ${m.BackupSelectionsList.length}`)}catch{a(" (Unable to load plan details)")}}}catch(t){p(`Failed to get schedule: ${t.message}`),process.exit(1)}})}function Xl(r){if(r===0)return"0 B";let e=1024,t=["B","KB","MB","GB","TB"],n=Math.floor(Math.log(r)/Math.log(e));return`${Number.parseFloat((r/e**n).toFixed(2))} ${t[n]}`}async function mf(r){let e=await P(),t=q(process.cwd(),e),n=r??Object.keys(e.environments??{})[0]??"production",i=t.environments.get(n);if(!i)throw t.store.close(),Error(`Environment ${n} was not found.`);let o=new We(t.store,{workerId:`cli:${process.pid}`}),s=new td(t.store),c=new rd(s,o),l=new Gn(t.store),d=new Kn(t.store,le(process.cwd())),u=t.store.getActorByExternalId("system","cli")??t.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:e,controlPlane:t,env:n,environmentRecord:i,queue:o,store:s,coordinator:c,dataServices:l,secrets:d,actor:u}}async function Ye(r,e){let t=await mf(r);try{return await e(t)}finally{t.controlPlane.store.close()}}async function Ze(r){try{await r()}catch(e){p(e instanceof Error?e.message:String(e))}}function dt(r,e,t,n){r.controlPlane.store.appendEvent({organizationId:r.controlPlane.organization.id,projectId:r.controlPlane.project.id,resourceId:n,actorId:r.actor.id,type:`backup.${e}`,payload:t})}function pf(r,e){let t=r.store.getPolicy(e)??r.store.listPolicies(r.controlPlane.project.id,r.environmentRecord.id).find((n)=>n.name===e);if(!t||t.environmentId!==r.environmentRecord.id)throw Error(`Backup policy ${e} was not found in ${r.env}.`);return t}function hr(r,e){let t=r.store.getRecoveryPoint(e),n=r.store.listRecoveryPoints(r.controlPlane.project.id).filter((o)=>o.id.startsWith(e)),i=t??(n.length===1?n[0]:void 0);if(!i)throw Error(n.length>1?`Recovery point prefix ${e} is ambiguous.`:`Recovery point ${e} was not found.`);return i}function ed(r,e){let t=r.store.getDestination(e)??r.store.listDestinations(r.controlPlane.project.id).find((n)=>n.name===e);if(!t)throw Error(`Backup destination ${e} was not found.`);return t}function ff(r,e){return r.kind==="volume"?{volumeName:e}:r.kind==="logical_database"?{targetId:e,dataServiceId:e}:{targetId:e}}function gf(r,e=[]){let t=new Map(e.map((n)=>[n.id,n.name]));return r.map((n)=>[n.id,n.status,n.verificationState,n.kind,t.get(n.policyId??"")??"manual",n.pointInTime,n.sizeBytes?`${n.sizeBytes} B`:"provider",n.held?"hold":n.pinned?"pinned":"retention"])}function rs(r){r.command("recovery:list","Show backup coverage, destination health, and recent recovery points").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(e)=>Ze(async()=>Ye(e.env,async(t)=>{let n=t.store.listPolicies(t.controlPlane.project.id,t.environmentRecord.id),i=new Set(n.map((c)=>c.id)),o=t.store.listRecoveryPoints(t.controlPlane.project.id).filter((c)=>!c.policyId||i.has(c.policyId)),s={coverage:t.store.coverage(t.controlPlane.project.id).filter((c)=>i.has(c.policy.id)),destinations:t.store.listDestinations(t.controlPlane.project.id).map((c)=>({...c,credentialRef:void 0,encryptionKeyRef:void 0,credentialsConfigured:!!c.credentialRef,clientEncryptionConfigured:!!c.encryptionKeyRef})),policies:n,recoveryPoints:o,jobs:t.store.listJobs(t.controlPlane.project.id).filter((c)=>!c.policyId||i.has(c.policyId))};if(e.json)a(JSON.stringify(s,null,2));else x(["Policy","RPO","Destination","Latest point","Verification"],s.coverage.map((c)=>[c.policy.name,c.missedRpo?"MISSED":"protected",c.destinationHealthy?"healthy":"attention",c.lastRecoveryPoint?.pointInTime??"none",c.lastRecoveryPoint?.verificationState??"none"])),x(["ID","State","Verification","Kind","Policy","Point in time","Size","Protection"],gf(o,n))}))),r.command("recovery:jobs","List durable backup, verification, restore, drill, and cleanup jobs").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(e)=>Ze(async()=>Ye(e.env,async(t)=>{let n=new Set(t.store.listPolicies(t.controlPlane.project.id,t.environmentRecord.id).map((o)=>o.id)),i=t.store.listJobs(t.controlPlane.project.id).filter((o)=>!o.policyId||n.has(o.policyId));if(e.json)a(JSON.stringify(i,null,2));else x(["ID","Kind","State","Phase","Operation","Started","Finished","Error"],i.map((o)=>[o.id,o.kind,o.status,String(o.progress.phase??"queued"),o.operationId??"—",o.startedAt??"—",o.finishedAt??"—",o.error??"—"]))}))),r.command("recovery:destination:add <name>","Connect an S3, S3-compatible, or AWS Backup destination").option("--env <environment>","Target environment").option("--provider <provider>","aws_s3, s3_compatible, or aws_backup",{default:"aws_s3"}).option("--bucket <bucket>","Object bucket").option("--endpoint <url>","Origin-only HTTPS endpoint").option("--allow-private","Allow a private endpoint").option("--path-style","Force path-style S3 URLs").option("--prefix <prefix>","Object key prefix").option("--region <region>","Destination region").option("--credentials-env <name>","Environment variable containing credential JSON").option("--encryption <mode>","provider, client_side, or both",{default:"provider"}).option("--encryption-key-env <name>","Environment variable containing the client encryption key").option("--lock-days <days>","Default immutable retention days").action(async(e,t)=>Ze(async()=>Ye(t.env,async(n)=>{if(!["aws_s3","s3_compatible","aws_backup"].includes(t.provider??""))throw Error("--provider must be aws_s3, s3_compatible, or aws_backup.");if(!["provider","client_side","both"].includes(t.encryption??""))throw Error("--encryption must be provider, client_side, or both.");let i=`secret://data-services/backups/${n.controlPlane.project.id}/${e}`,o=[];try{let s,c;if(t.credentialsEnv){let d=process.env[t.credentialsEnv];if(!d)throw Error(`${t.credentialsEnv} is empty.`);s=`${i}/credentials`,await n.secrets.put(s,d),o.push(s)}if(t.encryption!=="provider"){if(!t.encryptionKeyEnv)throw Error("Client encryption requires --encryption-key-env.");let d=process.env[t.encryptionKeyEnv];if(!d||d.length<32)throw Error(`${t.encryptionKeyEnv} must contain at least 32 characters.`);c=`${i}/encryption-key`,await n.secrets.put(c,d),o.push(c)}let l=n.store.createDestination({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,name:e,provider:t.provider,endpoint:t.endpoint,endpointPolicy:t.allowPrivate?"allow_private":"public_https",bucket:t.bucket,prefix:t.prefix??"",region:t.region??n.config.project.region,forcePathStyle:!!t.pathStyle,credentialRef:s,encryption:t.encryption,encryptionKeyRef:c,immutability:{objectLock:!!t.lockDays,defaultRetentionDays:t.lockDays?Number(t.lockDays):void 0},status:"untested"});dt(n,"destination_created",{destinationId:l.id,provider:l.provider}),h(`Created destination ${l.name} (${l.id}); test it with cloud recovery:destination:test ${l.name}`)}catch(s){for(let c of o)await n.secrets.remove(c).catch(()=>{});throw s}}))),r.command("recovery:destination:test <destination>","Write, read, verify, and clean a destination health object").option("--env <environment>","Target environment").action(async(e,t)=>Ze(async()=>Ye(t.env,async(n)=>{let i=ed(n,e);if(i.provider==="aws_backup")throw Error("AWS Backup is verified by creating a provider recovery point.");try{await new nd(n.secrets).test(i),n.store.recordDestinationTest(i.id,{ok:!0}),dt(n,"destination_tested",{destinationId:i.id,ok:!0}),h(`${i.name} passed write, read, checksum, and cleanup checks.`)}catch(o){let s=o instanceof Error?o.message:String(o);throw n.store.recordDestinationTest(i.id,{ok:!1,error:s}),dt(n,"destination_tested",{destinationId:i.id,ok:!1}),o}}))),r.command("recovery:policy:add <name>","Create a scheduled recovery policy").option("--env <environment>","Target environment").option("--destination <id>","Destination ID or name").option("--kind <kind>","managed_database, logical_database, volume, files, control_plane, or infrastructure").option("--data-service <id>","Data service ID or name").option("--resource <id>","Resource ID or slug").option("--volume <name>","Named Docker volume").option("--include <path>","Project-relative include path or provider setting (repeatable)").option("--exclude <pattern>","Archive exclude pattern (repeatable)").option("--schedule <expression>","Cron, rate, or preset",{default:"daily"}).option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--rpo <minutes>","Recovery point objective",{default:"1440"}).option("--rto <minutes>","Recovery time objective",{default:"120"}).option("--keep <count>","Minimum latest points",{default:"7"}).option("--expire <days>","Expiry days",{default:"30"}).option("--compression <kind>","none, gzip, or zstd",{default:"gzip"}).option("--disabled","Create disabled").action(async(e,t)=>Ze(async()=>Ye(t.env,async(n)=>{if(!t.destination)throw Error("--destination is required.");if(!["managed_database","logical_database","volume","files","control_plane","infrastructure"].includes(t.kind??""))throw Error("--kind must be managed_database, logical_database, volume, files, control_plane, or infrastructure.");let i=ed(n,t.destination),o=n.dataServices.list(n.controlPlane.project.id,n.environmentRecord.id),s=t.dataService?n.dataServices.get(t.dataService)??o.find((f)=>f.name===t.dataService):void 0,c=n.controlPlane.store.listResources(n.controlPlane.project.id,n.environmentRecord.id),l=t.resource?c.find((f)=>f.id===t.resource||f.slug===t.resource):void 0,d=t.include?Array.isArray(t.include)?t.include:[t.include]:[],u=t.exclude?Array.isArray(t.exclude)?t.exclude:[t.exclude]:[];if(["managed_database","logical_database"].includes(t.kind??"")&&!s)throw Error("--data-service must identify a service in this environment.");if(t.kind==="volume"&&(!l||!t.volume))throw Error("Volume policies require --resource and --volume.");if(t.kind==="files"&&(!l||!d.length))throw Error("File policies require --resource and at least one --include path.");if(t.kind==="infrastructure"&&(!d.some((f)=>f.startsWith("resource:arn:"))||!d.some((f)=>f.startsWith("role:arn:"))))throw Error("Infrastructure policies require --include resource:<arn> and --include role:<arn>.");let m=n.store.createPolicy({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,environmentId:n.environmentRecord.id,resourceId:l?.id,dataServiceId:s?.id,destinationId:i.id,name:e,resourceKind:t.kind,schedule:t.schedule??"daily",timezone:t.timezone??"UTC",retention:{keepLast:Number(t.keep)||7,expireAfterDays:Number(t.expire)||30},compression:t.compression,encryption:"destination",includePatterns:t.volume?[t.volume,...d]:d,excludePatterns:u,expectedRpoMinutes:Number(t.rpo)||1440,expectedRtoMinutes:Number(t.rto)||120,enabled:!t.disabled});dt(n,"policy_created",{policyId:m.id,resourceKind:m.resourceKind},m.resourceId),h(`Created policy ${m.name} (${m.id}); next run ${m.nextRunAt}`)}))),r.command("recovery:run <policy>","Queue an on-demand run of a recovery policy").option("--env <environment>","Target environment").action(async(e,t)=>Ze(async()=>Ye(t.env,async(n)=>{let i=pf(n,e),o=n.coordinator.enqueueBackup(i,new Date().toISOString(),n.actor.id);dt(n,"run_queued",{policyId:i.id,backupJobId:o.id},i.resourceId),h(`Queued backup job ${o.id}; inspect it with cloud recovery:jobs`)}))),r.command("recovery:verify <point>","Queue independent verification of a recovery point").option("--env <environment>","Target environment").action(async(e,t)=>Ze(async()=>Ye(t.env,async(n)=>{let i=hr(n,e),o=n.coordinator.enqueueVerification(i);dt(n,"verification_queued",{recoveryPointId:i.id,backupJobId:o.id},i.resourceId),h(`Queued verification job ${o.id}`)}))),r.command("recovery:restore <point> <target>","Validate or queue an isolated, in-place, or drill restore").option("--env <environment>","Target environment").option("--mode <mode>","isolated or in_place",{default:"isolated"}).option("--drill","Validate health and clean the isolated target").option("--execute","Queue the validated restore").option("--confirm <target>","Exact in-place target confirmation").option("--ack-downtime","Acknowledge downtime and data replacement").option("--safety <point>","Distinct verified safety recovery point").option("--metadata <json>","Provider restore metadata JSON").action(async(e,t,n)=>Ze(async()=>Ye(n.env,async(i)=>{if(!["isolated","in_place"].includes(n.mode??""))throw Error("--mode must be isolated or in_place.");if(n.drill&&n.mode==="in_place")throw Error("Recovery drills require --mode isolated.");let o=hr(i,e),s={mode:n.mode,target:{...ff(o,t),...n.metadata?{metadata:JSON.parse(n.metadata)}:{},inPlace:n.mode==="in_place"},targetName:t,confirm:n.confirm,recentAuth:!0,downtimeAcknowledged:!!n.ackDowntime,safetyBackupId:n.safety?hr(i,n.safety).id:void 0},c=i.coordinator.planRestore(o,s);if(!n.execute){a(JSON.stringify({...c,productionExecutionCreated:!1},null,2));return}let l=i.coordinator.enqueueRestore(o,{...s,drill:!!n.drill,actorId:i.actor.id});dt(i,n.drill?"drill_queued":"restore_queued",{recoveryPointId:o.id,backupJobId:l.id,mode:s.mode,target:t},o.resourceId),h(`Queued ${n.drill?"drill":"restore"} job ${l.id}`)}))),r.command("recovery:protect <point>","Pin, hold, or release retention protection").option("--env <environment>","Target environment").option("--pin","Pin the point").option("--unpin","Remove its pin").option("--hold","Place a legal hold").option("--release-hold","Release its legal hold").action(async(e,t)=>Ze(async()=>Ye(t.env,async(n)=>{let i=hr(n,e);if(![t.pin,t.unpin,t.hold,t.releaseHold].filter(Boolean).length)throw Error("Choose --pin, --unpin, --hold, or --release-hold.");let o=n.store.updateRecoveryPoint(i.id,{pinned:t.pin?!0:t.unpin?!1:i.pinned,held:t.hold?!0:t.releaseHold?!1:i.held});dt(n,"protection_updated",{recoveryPointId:i.id,pinned:o.pinned,held:o.held},i.resourceId),h(`${o.id}: pinned=${o.pinned} held=${o.held}`)}))),r.command("recovery:retention","Queue deletion of expired, unlocked, unheld recovery points").option("--env <environment>","Target environment").action(async(e)=>Ze(async()=>Ye(e.env,async(t)=>{let n=t.coordinator.enqueueRetention();dt(t,"retention_queued",{jobs:n.length}),h(`Queued ${n.length} retention cleanup job${n.length===1?"":"s"}.`)})))}function is(r){r.command("api:list","List all API Gateway APIs").option("--region <region>","AWS region").action(async(e)=>{w("API Gateway APIs");try{let t=await P(),n=e.region||t.project.region||"us-east-1",{LambdaClient:i}=await import("./chunk-dkaz333b.js"),o=new i(n),s=new A("Fetching APIs...");s.start();let{CloudFormationClient:c}=await import("./chunk-nw3d4k52.js"),u=(await new c(n).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE"])).StackSummaries.filter((m)=>m.StackName?.includes("api")||m.StackName?.includes("Api")||m.StackName?.includes("API"));if(s.succeed("APIs listed"),a(`
|
|
641
|
+
${l.BackupPlanName}`),a("=".repeat(40));try{let u=(await i.getBackupPlan(l.BackupPlanId)).BackupPlan?.Rules||[];for(let f of u)if(a(` Rule: ${f.RuleName}`),a(` Schedule: ${f.ScheduleExpression}`),a(` Vault: ${f.TargetBackupVaultName}`),f.Lifecycle?.DeleteAfterDays)a(` Retention: ${f.Lifecycle.DeleteAfterDays} days`);let m=await i.listBackupSelections(l.BackupPlanId);if(m.BackupSelectionsList&&m.BackupSelectionsList.length>0)a(` Selections: ${m.BackupSelectionsList.length}`)}catch{a(" (Unable to load plan details)")}}}catch(t){p(`Failed to get schedule: ${t.message}`),process.exit(1)}})}function Xl(r){if(r===0)return"0 B";let e=1024,t=["B","KB","MB","GB","TB"],n=Math.floor(Math.log(r)/Math.log(e));return`${Number.parseFloat((r/e**n).toFixed(2))} ${t[n]}`}async function mf(r){let e=await P(),t=q(process.cwd(),e),n=r??Object.keys(e.environments??{})[0]??"production",i=t.environments.get(n);if(!i)throw t.store.close(),Error(`Environment ${n} was not found.`);let o=new We(t.store,{workerId:`cli:${process.pid}`}),s=new td(t.store),c=new rd(s,o),l=new Gn(t.store),d=new Kn(t.store,le(process.cwd())),u=t.store.getActorByExternalId("system","cli")??t.store.createActor({kind:"system",externalId:"cli",displayName:"ts-cloud CLI"});return{config:e,controlPlane:t,env:n,environmentRecord:i,queue:o,store:s,coordinator:c,dataServices:l,secrets:d,actor:u}}async function Ze(r,e){let t=await mf(r);try{return await e(t)}finally{t.controlPlane.store.close()}}async function Qe(r){try{await r()}catch(e){p(e instanceof Error?e.message:String(e))}}function dt(r,e,t,n){r.controlPlane.store.appendEvent({organizationId:r.controlPlane.organization.id,projectId:r.controlPlane.project.id,resourceId:n,actorId:r.actor.id,type:`backup.${e}`,payload:t})}function pf(r,e){let t=r.store.getPolicy(e)??r.store.listPolicies(r.controlPlane.project.id,r.environmentRecord.id).find((n)=>n.name===e);if(!t||t.environmentId!==r.environmentRecord.id)throw Error(`Backup policy ${e} was not found in ${r.env}.`);return t}function hr(r,e){let t=r.store.getRecoveryPoint(e),n=r.store.listRecoveryPoints(r.controlPlane.project.id).filter((o)=>o.id.startsWith(e)),i=t??(n.length===1?n[0]:void 0);if(!i)throw Error(n.length>1?`Recovery point prefix ${e} is ambiguous.`:`Recovery point ${e} was not found.`);return i}function ed(r,e){let t=r.store.getDestination(e)??r.store.listDestinations(r.controlPlane.project.id).find((n)=>n.name===e);if(!t)throw Error(`Backup destination ${e} was not found.`);return t}function ff(r,e){return r.kind==="volume"?{volumeName:e}:r.kind==="logical_database"?{targetId:e,dataServiceId:e}:{targetId:e}}function gf(r,e=[]){let t=new Map(e.map((n)=>[n.id,n.name]));return r.map((n)=>[n.id,n.status,n.verificationState,n.kind,t.get(n.policyId??"")??"manual",n.pointInTime,n.sizeBytes?`${n.sizeBytes} B`:"provider",n.held?"hold":n.pinned?"pinned":"retention"])}function rs(r){r.command("recovery:list","Show backup coverage, destination health, and recent recovery points").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(e)=>Qe(async()=>Ze(e.env,async(t)=>{let n=t.store.listPolicies(t.controlPlane.project.id,t.environmentRecord.id),i=new Set(n.map((c)=>c.id)),o=t.store.listRecoveryPoints(t.controlPlane.project.id).filter((c)=>!c.policyId||i.has(c.policyId)),s={coverage:t.store.coverage(t.controlPlane.project.id).filter((c)=>i.has(c.policy.id)),destinations:t.store.listDestinations(t.controlPlane.project.id).map((c)=>({...c,credentialRef:void 0,encryptionKeyRef:void 0,credentialsConfigured:!!c.credentialRef,clientEncryptionConfigured:!!c.encryptionKeyRef})),policies:n,recoveryPoints:o,jobs:t.store.listJobs(t.controlPlane.project.id).filter((c)=>!c.policyId||i.has(c.policyId))};if(e.json)a(JSON.stringify(s,null,2));else x(["Policy","RPO","Destination","Latest point","Verification"],s.coverage.map((c)=>[c.policy.name,c.missedRpo?"MISSED":"protected",c.destinationHealthy?"healthy":"attention",c.lastRecoveryPoint?.pointInTime??"none",c.lastRecoveryPoint?.verificationState??"none"])),x(["ID","State","Verification","Kind","Policy","Point in time","Size","Protection"],gf(o,n))}))),r.command("recovery:jobs","List durable backup, verification, restore, drill, and cleanup jobs").option("--env <environment>","Target environment").option("--json","Print structured JSON").action(async(e)=>Qe(async()=>Ze(e.env,async(t)=>{let n=new Set(t.store.listPolicies(t.controlPlane.project.id,t.environmentRecord.id).map((o)=>o.id)),i=t.store.listJobs(t.controlPlane.project.id).filter((o)=>!o.policyId||n.has(o.policyId));if(e.json)a(JSON.stringify(i,null,2));else x(["ID","Kind","State","Phase","Operation","Started","Finished","Error"],i.map((o)=>[o.id,o.kind,o.status,String(o.progress.phase??"queued"),o.operationId??"—",o.startedAt??"—",o.finishedAt??"—",o.error??"—"]))}))),r.command("recovery:destination:add <name>","Connect an S3, S3-compatible, or AWS Backup destination").option("--env <environment>","Target environment").option("--provider <provider>","aws_s3, s3_compatible, or aws_backup",{default:"aws_s3"}).option("--bucket <bucket>","Object bucket").option("--endpoint <url>","Origin-only HTTPS endpoint").option("--allow-private","Allow a private endpoint").option("--path-style","Force path-style S3 URLs").option("--prefix <prefix>","Object key prefix").option("--region <region>","Destination region").option("--credentials-env <name>","Environment variable containing credential JSON").option("--encryption <mode>","provider, client_side, or both",{default:"provider"}).option("--encryption-key-env <name>","Environment variable containing the client encryption key").option("--lock-days <days>","Default immutable retention days").action(async(e,t)=>Qe(async()=>Ze(t.env,async(n)=>{if(!["aws_s3","s3_compatible","aws_backup"].includes(t.provider??""))throw Error("--provider must be aws_s3, s3_compatible, or aws_backup.");if(!["provider","client_side","both"].includes(t.encryption??""))throw Error("--encryption must be provider, client_side, or both.");let i=`secret://data-services/backups/${n.controlPlane.project.id}/${e}`,o=[];try{let s,c;if(t.credentialsEnv){let d=process.env[t.credentialsEnv];if(!d)throw Error(`${t.credentialsEnv} is empty.`);s=`${i}/credentials`,await n.secrets.put(s,d),o.push(s)}if(t.encryption!=="provider"){if(!t.encryptionKeyEnv)throw Error("Client encryption requires --encryption-key-env.");let d=process.env[t.encryptionKeyEnv];if(!d||d.length<32)throw Error(`${t.encryptionKeyEnv} must contain at least 32 characters.`);c=`${i}/encryption-key`,await n.secrets.put(c,d),o.push(c)}let l=n.store.createDestination({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,name:e,provider:t.provider,endpoint:t.endpoint,endpointPolicy:t.allowPrivate?"allow_private":"public_https",bucket:t.bucket,prefix:t.prefix??"",region:t.region??n.config.project.region,forcePathStyle:!!t.pathStyle,credentialRef:s,encryption:t.encryption,encryptionKeyRef:c,immutability:{objectLock:!!t.lockDays,defaultRetentionDays:t.lockDays?Number(t.lockDays):void 0},status:"untested"});dt(n,"destination_created",{destinationId:l.id,provider:l.provider}),h(`Created destination ${l.name} (${l.id}); test it with cloud recovery:destination:test ${l.name}`)}catch(s){for(let c of o)await n.secrets.remove(c).catch(()=>{});throw s}}))),r.command("recovery:destination:test <destination>","Write, read, verify, and clean a destination health object").option("--env <environment>","Target environment").action(async(e,t)=>Qe(async()=>Ze(t.env,async(n)=>{let i=ed(n,e);if(i.provider==="aws_backup")throw Error("AWS Backup is verified by creating a provider recovery point.");try{await new nd(n.secrets).test(i),n.store.recordDestinationTest(i.id,{ok:!0}),dt(n,"destination_tested",{destinationId:i.id,ok:!0}),h(`${i.name} passed write, read, checksum, and cleanup checks.`)}catch(o){let s=o instanceof Error?o.message:String(o);throw n.store.recordDestinationTest(i.id,{ok:!1,error:s}),dt(n,"destination_tested",{destinationId:i.id,ok:!1}),o}}))),r.command("recovery:policy:add <name>","Create a scheduled recovery policy").option("--env <environment>","Target environment").option("--destination <id>","Destination ID or name").option("--kind <kind>","managed_database, logical_database, volume, files, control_plane, or infrastructure").option("--data-service <id>","Data service ID or name").option("--resource <id>","Resource ID or slug").option("--volume <name>","Named Docker volume").option("--include <path>","Project-relative include path or provider setting (repeatable)").option("--exclude <pattern>","Archive exclude pattern (repeatable)").option("--schedule <expression>","Cron, rate, or preset",{default:"daily"}).option("--timezone <timezone>","IANA timezone",{default:"UTC"}).option("--rpo <minutes>","Recovery point objective",{default:"1440"}).option("--rto <minutes>","Recovery time objective",{default:"120"}).option("--keep <count>","Minimum latest points",{default:"7"}).option("--expire <days>","Expiry days",{default:"30"}).option("--compression <kind>","none, gzip, or zstd",{default:"gzip"}).option("--disabled","Create disabled").action(async(e,t)=>Qe(async()=>Ze(t.env,async(n)=>{if(!t.destination)throw Error("--destination is required.");if(!["managed_database","logical_database","volume","files","control_plane","infrastructure"].includes(t.kind??""))throw Error("--kind must be managed_database, logical_database, volume, files, control_plane, or infrastructure.");let i=ed(n,t.destination),o=n.dataServices.list(n.controlPlane.project.id,n.environmentRecord.id),s=t.dataService?n.dataServices.get(t.dataService)??o.find((f)=>f.name===t.dataService):void 0,c=n.controlPlane.store.listResources(n.controlPlane.project.id,n.environmentRecord.id),l=t.resource?c.find((f)=>f.id===t.resource||f.slug===t.resource):void 0,d=t.include?Array.isArray(t.include)?t.include:[t.include]:[],u=t.exclude?Array.isArray(t.exclude)?t.exclude:[t.exclude]:[];if(["managed_database","logical_database"].includes(t.kind??"")&&!s)throw Error("--data-service must identify a service in this environment.");if(t.kind==="volume"&&(!l||!t.volume))throw Error("Volume policies require --resource and --volume.");if(t.kind==="files"&&(!l||!d.length))throw Error("File policies require --resource and at least one --include path.");if(t.kind==="infrastructure"&&(!d.some((f)=>f.startsWith("resource:arn:"))||!d.some((f)=>f.startsWith("role:arn:"))))throw Error("Infrastructure policies require --include resource:<arn> and --include role:<arn>.");let m=n.store.createPolicy({organizationId:n.controlPlane.organization.id,projectId:n.controlPlane.project.id,environmentId:n.environmentRecord.id,resourceId:l?.id,dataServiceId:s?.id,destinationId:i.id,name:e,resourceKind:t.kind,schedule:t.schedule??"daily",timezone:t.timezone??"UTC",retention:{keepLast:Number(t.keep)||7,expireAfterDays:Number(t.expire)||30},compression:t.compression,encryption:"destination",includePatterns:t.volume?[t.volume,...d]:d,excludePatterns:u,expectedRpoMinutes:Number(t.rpo)||1440,expectedRtoMinutes:Number(t.rto)||120,enabled:!t.disabled});dt(n,"policy_created",{policyId:m.id,resourceKind:m.resourceKind},m.resourceId),h(`Created policy ${m.name} (${m.id}); next run ${m.nextRunAt}`)}))),r.command("recovery:run <policy>","Queue an on-demand run of a recovery policy").option("--env <environment>","Target environment").action(async(e,t)=>Qe(async()=>Ze(t.env,async(n)=>{let i=pf(n,e),o=n.coordinator.enqueueBackup(i,new Date().toISOString(),n.actor.id);dt(n,"run_queued",{policyId:i.id,backupJobId:o.id},i.resourceId),h(`Queued backup job ${o.id}; inspect it with cloud recovery:jobs`)}))),r.command("recovery:verify <point>","Queue independent verification of a recovery point").option("--env <environment>","Target environment").action(async(e,t)=>Qe(async()=>Ze(t.env,async(n)=>{let i=hr(n,e),o=n.coordinator.enqueueVerification(i);dt(n,"verification_queued",{recoveryPointId:i.id,backupJobId:o.id},i.resourceId),h(`Queued verification job ${o.id}`)}))),r.command("recovery:restore <point> <target>","Validate or queue an isolated, in-place, or drill restore").option("--env <environment>","Target environment").option("--mode <mode>","isolated or in_place",{default:"isolated"}).option("--drill","Validate health and clean the isolated target").option("--execute","Queue the validated restore").option("--confirm <target>","Exact in-place target confirmation").option("--ack-downtime","Acknowledge downtime and data replacement").option("--safety <point>","Distinct verified safety recovery point").option("--metadata <json>","Provider restore metadata JSON").action(async(e,t,n)=>Qe(async()=>Ze(n.env,async(i)=>{if(!["isolated","in_place"].includes(n.mode??""))throw Error("--mode must be isolated or in_place.");if(n.drill&&n.mode==="in_place")throw Error("Recovery drills require --mode isolated.");let o=hr(i,e),s={mode:n.mode,target:{...ff(o,t),...n.metadata?{metadata:JSON.parse(n.metadata)}:{},inPlace:n.mode==="in_place"},targetName:t,confirm:n.confirm,recentAuth:!0,downtimeAcknowledged:!!n.ackDowntime,safetyBackupId:n.safety?hr(i,n.safety).id:void 0},c=i.coordinator.planRestore(o,s);if(!n.execute){a(JSON.stringify({...c,productionExecutionCreated:!1},null,2));return}let l=i.coordinator.enqueueRestore(o,{...s,drill:!!n.drill,actorId:i.actor.id});dt(i,n.drill?"drill_queued":"restore_queued",{recoveryPointId:o.id,backupJobId:l.id,mode:s.mode,target:t},o.resourceId),h(`Queued ${n.drill?"drill":"restore"} job ${l.id}`)}))),r.command("recovery:protect <point>","Pin, hold, or release retention protection").option("--env <environment>","Target environment").option("--pin","Pin the point").option("--unpin","Remove its pin").option("--hold","Place a legal hold").option("--release-hold","Release its legal hold").action(async(e,t)=>Qe(async()=>Ze(t.env,async(n)=>{let i=hr(n,e);if(![t.pin,t.unpin,t.hold,t.releaseHold].filter(Boolean).length)throw Error("Choose --pin, --unpin, --hold, or --release-hold.");let o=n.store.updateRecoveryPoint(i.id,{pinned:t.pin?!0:t.unpin?!1:i.pinned,held:t.hold?!0:t.releaseHold?!1:i.held});dt(n,"protection_updated",{recoveryPointId:i.id,pinned:o.pinned,held:o.held},i.resourceId),h(`${o.id}: pinned=${o.pinned} held=${o.held}`)}))),r.command("recovery:retention","Queue deletion of expired, unlocked, unheld recovery points").option("--env <environment>","Target environment").action(async(e)=>Qe(async()=>Ze(e.env,async(t)=>{let n=t.coordinator.enqueueRetention();dt(t,"retention_queued",{jobs:n.length}),h(`Queued ${n.length} retention cleanup job${n.length===1?"":"s"}.`)})))}function is(r){r.command("api:list","List all API Gateway APIs").option("--region <region>","AWS region").action(async(e)=>{w("API Gateway APIs");try{let t=await P(),n=e.region||t.project.region||"us-east-1",{LambdaClient:i}=await import("./chunk-dkaz333b.js"),o=new i(n),s=new A("Fetching APIs...");s.start();let{CloudFormationClient:c}=await import("./chunk-nw3d4k52.js"),u=(await new c(n).listStacks(["CREATE_COMPLETE","UPDATE_COMPLETE"])).StackSummaries.filter((m)=>m.StackName?.includes("api")||m.StackName?.includes("Api")||m.StackName?.includes("API"));if(s.succeed("APIs listed"),a(`
|
|
642
642
|
API-related CloudFormation stacks:`),u.length===0)a("No API Gateway stacks found"),a(`
|
|
643
643
|
To create an API, you can:`),a(" 1. Use cloud.config.ts to define your API"),a(" 2. Deploy with `cloud deploy`");else x(["Stack Name","Status","Created"],u.map((m)=>[m.StackName||"N/A",m.StackStatus||"N/A",m.CreationTime?new Date(m.CreationTime).toLocaleDateString():"N/A"]));a("\nTip: Use AWS Console or `aws apigateway get-rest-apis` for detailed API listing")}catch(t){p(`Failed to list APIs: ${t.message}`),process.exit(1)}}),r.command("api:describe <apiId>","Show API Gateway API details").option("--region <region>","AWS region").action(async(e,t)=>{w(`API: ${e}`);try{let n=await P(),i=t.region||n.project.region||"us-east-1";a(`API ID: ${e}`),a(`Region: ${i}`),a(""),a("For detailed API information, use AWS CLI:"),a(` aws apigateway get-rest-api --rest-api-id ${e} --region ${i}`),a(` aws apigateway get-resources --rest-api-id ${e} --region ${i}`),a(` aws apigateway get-stages --rest-api-id ${e} --region ${i}`)}catch(n){p(`Failed to describe API: ${n.message}`),process.exit(1)}}),r.command("api:stages <apiId>","List API stages").option("--region <region>","AWS region").action(async(e,t)=>{w(`API Stages: ${e}`);try{let n=await P(),i=t.region||n.project.region||"us-east-1";a(`API ID: ${e}`),a(`Region: ${i}`),a(""),a("Common stages:"),a(" - prod (production)"),a(" - staging"),a(" - dev (development)"),a(""),a("For detailed stage information, use AWS CLI:"),a(` aws apigateway get-stages --rest-api-id ${e} --region ${i}`)}catch(n){p(`Failed to list stages: ${n.message}`),process.exit(1)}}),r.command("api:deploy <apiId>","Deploy API to a stage").option("--region <region>","AWS region",{default:"us-east-1"}).option("--stage <name>","Stage name",{default:"prod"}).option("--description <text>","Deployment description").action(async(e,t)=>{w("Deploy API");try{if(a(`API ID: ${e}`),a(`Stage: ${t.stage}`),!await O(`
|
|
644
644
|
Deploy to this stage?`,!0)){a("Operation cancelled");return}if(a(""),a("To deploy an API Gateway API:"),a(" aws apigateway create-deployment \\"),a(` --rest-api-id ${e} \\`),a(` --stage-name ${t.stage} \\`),a(` --region ${t.region}`),t.description)a(` --description "${t.description}"`)}catch(n){p(`Failed to deploy API: ${n.message}`),process.exit(1)}}),r.command("api:domains","List custom domain names").option("--region <region>","AWS region").action(async(e)=>{w("API Gateway Custom Domains");try{let t=await P(),n=e.region||t.project.region||"us-east-1";a(`Region: ${n}`),a(""),a("To list custom domains, use AWS CLI:"),a(` aws apigateway get-domain-names --region ${n}`),a(""),a("To create a custom domain:"),a(" 1. Request or import an SSL certificate in ACM"),a(" 2. Create a custom domain in API Gateway"),a(" 3. Create a base path mapping to your API"),a(" 4. Add a DNS record pointing to the distribution")}catch(t){p(`Failed to list domains: ${t.message}`),process.exit(1)}}),r.command("api:usage <apiId>","Show API usage statistics").option("--region <region>","AWS region").option("--stage <name>","Stage name",{default:"prod"}).option("--days <number>","Number of days to show",{default:"7"}).action(async(e,t)=>{w("API Usage Statistics");try{let n=await P(),i=t.region||n.project.region||"us-east-1";a(`API ID: ${e}`),a(`Stage: ${t.stage}`),a(`Period: Last ${t.days} days`),a("");let o=new Date,s=new Date;s.setDate(s.getDate()-Number.parseInt(t.days)),a("To view API metrics in CloudWatch:"),a(" aws cloudwatch get-metric-statistics \\"),a(" --namespace AWS/ApiGateway \\"),a(" --metric-name Count \\"),a(` --dimensions Name=ApiName,Value=${e} Name=Stage,Value=${t.stage} \\`),a(` --start-time ${s.toISOString()} \\`),a(` --end-time ${o.toISOString()} \\`),a(" --period 86400 \\"),a(" --statistics Sum \\"),a(` --region ${i}`),a(""),a("Available metrics:"),a(" - Count: Total API calls"),a(" - Latency: Response latency"),a(" - 4XXError: Client errors"),a(" - 5XXError: Server errors"),a(" - IntegrationLatency: Backend latency")}catch(n){p(`Failed to get usage: ${n.message}`),process.exit(1)}}),r.command("api:export <apiId>","Export API specification").option("--region <region>","AWS region",{default:"us-east-1"}).option("--stage <name>","Stage name",{default:"prod"}).option("--format <format>","Export format (oas30, swagger)",{default:"oas30"}).option("--output <file>","Output file path").action(async(e,t)=>{w("Export API Specification");try{a(`API ID: ${e}`),a(`Stage: ${t.stage}`),a(`Format: ${t.format==="oas30"?"OpenAPI 3.0":"Swagger 2.0"}`),a("");let n=t.format==="oas30"?"oas30":"swagger";a("To export the API specification:"),a(" aws apigateway get-export \\"),a(` --rest-api-id ${e} \\`),a(` --stage-name ${t.stage} \\`),a(` --export-type ${n} \\`),a(" --accepts application/json \\"),a(` --region ${t.region} \\`),a(` ${t.output||"api-spec.json"}`)}catch(n){p(`Failed to export API: ${n.message}`),process.exit(1)}}),r.command("api:logs <apiId>","View API Gateway logs").option("--region <region>","AWS region").option("--stage <name>","Stage name",{default:"prod"}).option("--tail","Tail the logs").action(async(e,t)=>{w("API Gateway Logs");try{let n=await P(),i=t.region||n.project.region||"us-east-1",o=`API-Gateway-Execution-Logs_${e}/${t.stage}`;if(a(`Log Group: ${o}`),a(""),a("To view logs:"),a(" aws logs filter-log-events \\"),a(` --log-group-name "${o}" \\`),a(` --region ${i}`),t.tail)a(""),a("For real-time log tailing, use:"),a(` aws logs tail "${o}" --follow --region ${i}`);a(""),a("Note: Ensure logging is enabled for the API stage."),a("You can enable it in the stage settings.")}catch(n){p(`Failed to get logs: ${n.message}`),process.exit(1)}}),r.command("api:test <apiId> <path>","Test an API endpoint").option("--region <region>","AWS region").option("--stage <name>","Stage name",{default:"prod"}).option("--method <method>","HTTP method",{default:"GET"}).option("--body <json>","Request body (JSON)").option("--header <header>","Request header (can be specified multiple times)").action(async(e,t,n)=>{w("Test API Endpoint");try{let i=await P(),o=n.region||i.project.region||"us-east-1",s=`https://${e}.execute-api.${o}.amazonaws.com/${n.stage}${t.startsWith("/")?t:`/${t}`}`;a(`URL: ${s}`),a(`Method: ${n.method}`);let c={"Content-Type":"application/json"};if(n.header){let S=Array.isArray(n.header)?n.header:[n.header];for(let g of S){let[b,...I]=g.split(":");c[b.trim()]=I.join(":").trim()}}a("Headers:");for(let[S,g]of Object.entries(c))a(` ${S}: ${g}`);if(n.body)a(`Body: ${n.body}`);if(!await O(`
|