@stacksjs/ts-cloud 0.6.2 → 0.7.0

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 CHANGED
@@ -7462,7 +7462,7 @@ await startProxies(config)
7462
7462
  `),j=["[Unit]",`Description=Daily rpx gateway TLS cert issuance/renewal for ${K}`,"","[Timer]","OnCalendar=*-*-* 03:30:00","RandomizedDelaySec=1h","Persistent=true","","[Install]","WantedBy=timers.target"].join(`
7463
7463
  `);return[`mkdir -p ${Z}`,`(cd ${I2} && ${G} add @stacksjs/tlsx@${W}) || true`,...H8(V,_,"TS_CLOUD_RENEW_EOF"),`chmod +x ${V}`,...H8(`/etc/systemd/system/${A}`,E,"TS_CLOUD_RENEW_SVC_EOF"),...H8(`/etc/systemd/system/${O}`,j,"TS_CLOUD_RENEW_TIMER_EOF"),"systemctl daemon-reload",`systemctl enable --now ${O} || true`,`${V} || true`]}function x9($){let{config:J,proxy:Y}=$,Z=$.bunBin??"/usr/local/bin/bun",Q=Y.version??"latest",G=J.productionCerts.certsDir,W=($.slug||"app").replace(/[^a-z0-9._-]+/gi,"-"),z=JSON.stringify({slug:W,...J},null,2),U=gT(i7,G),X=[`Environment=RPX_UPSTREAM_TIMEOUT=${Y.upstreamTimeout??60}`];if(typeof Y.maxUpstreamConns==="number")X.push(`Environment=RPX_MAX_UPSTREAM_CONNS=${Y.maxUpstreamConns}`);return["set -euo pipefail",`mkdir -p ${a7} ${i7} ${G} ${I2}`,`rm -rf ${I2}/node_modules ${I2}/bun.lock ${I2}/package.json`,`(cd ${I2} && ${Z} add @stacksjs/rpx@${Q})`,`ln -sfn ${I2}/node_modules ${a7}/node_modules`,...H8(`${i7}/${W}.json`,z,"TS_CLOUD_RPX_FRAGMENT_EOF"),...H8(yQ,U,"TS_CLOUD_RPX_EOF"),...H8(`/etc/systemd/system/${U8}`,["[Unit]","Description=rpx reverse-proxy gateway (managed by ts-cloud)","After=network.target network-online.target","Wants=network-online.target","","[Service]","Type=simple",`ExecStart=${Z} ${yQ}`,`WorkingDirectory=${I2}`,"Environment=BUN_INSTALL=/root/.bun",...X,"Restart=always","RestartSec=5","LimitNOFILE=1048576","AmbientCapabilities=CAP_NET_BIND_SERVICE","","[Install]","WantedBy=multi-user.target"].join(`
7464
7464
  `),"TS_CLOUD_RPX_UNIT_EOF"),"systemctl daemon-reload","systemctl disable --now bun-gateway.service 2>/dev/null || true","systemctl disable --now ts-cloud-nginx.service 2>/dev/null || true",`systemctl enable ${U8}`,`systemctl restart ${U8}`,...dT($)]}var vQ="/etc/rpx/certs",yT="/var/www/acme-challenge",a7="/etc/rpx",I2="/opt/rpx-gateway",yQ="/etc/rpx/gateway.ts",U8="rpx-gateway.service",i7="/etc/rpx/sites.d";var t7=$0(async()=>{await T$()});function zX($={}){let J=Math.max(1,$.appServers??$.instances??1),Y=!!$.servicesServer||J>1,Z=J>1||!!$.server?.loadBalancer;return{appServers:J,loadBalancer:Z,dedicatedServices:Y,servicesOnApp:!Y}}function UX($,J){let Y={REDIS_HOST:$,MEILISEARCH_HOST:`http://${$}:7700`};if(J?.name){if(Y.DB_CONNECTION=J.engine==="postgres"?"pgsql":"mysql",Y.DB_HOST=$,Y.DB_PORT=String(J.port??(J.engine==="postgres"?5432:3306)),Y.DB_DATABASE=J.name,J.username)Y.DB_USERNAME=J.username;if(J.password)Y.DB_PASSWORD=J.password}return Y}function fQ($){let J=new Set([80,443,...$.sitePorts]);if($.allowSsh)J.add(22);return[...J].map((Y)=>{return{direction:"in",protocol:"tcp",port:String(Y),source_ips:["0.0.0.0/0","::/0"],description:`ts-cloud port ${Y}`}})}function s7($){if(!$)return gQ.micro;if($ in gQ)return gQ[$];return $}function k2($,J,Y="app"){return{[`${K1}/project`]:$,[`${K1}/environment`]:J,[`${K1}/role`]:Y,[`${K1}/managed-by`]:"ts-cloud"}}function w$($,J,Y,Z="app"){if(!$)return!1;return $[`${K1}/project`]===J&&$[`${K1}/environment`]===Y&&$[`${K1}/role`]===Z}var gQ,K1="ts-cloud";var HX=$0(()=>{gQ={micro:"cpx11",small:"cx23",medium:"cx33",large:"cx43",xlarge:"cx53","2xlarge":"ccx33"}});import{mkdir as cT,readFile as mT,writeFile as lT}from"node:fs/promises";import{join as XX}from"node:path";function BX($){return XX(process.cwd(),FX,`${$}.json`)}async function I9($){try{let J=await mT(BX($),"utf8");return JSON.parse(J)}catch{return null}}async function k9($,J){let Y=BX($);await cT(XX(process.cwd(),FX),{recursive:!0}),await lT(Y,`${JSON.stringify(J,null,2)}
7465
- `,"utf8")}var FX=".ts-cloud/state";var KX=()=>{};import{existsSync as pT,readFileSync as rT}from"node:fs";import{homedir as nT}from"node:os";import{join as iT}from"node:path";import{execSync as uQ}from"node:child_process";function VX($){return $.startsWith("~/")?iT(nT(),$.slice(2)):$}var dQ=268435456,R$;var cQ=$0(async()=>{kQ();CQ();c7();u7();DQ();HX();KX();await z$([v0(),t7()]);R$=class R${name="hetzner";usesCloudFormation=!1;client;sshPrivateKeyPath;sshPublicKeyPath;sshUser;location;waitForBoot;bootWait;constructor($={}){this.client=$.client??new P9({apiToken:n7($.apiToken)}),this.sshPrivateKeyPath=VX($.sshPrivateKeyPath||process.env.HCLOUD_SSH_KEY||"~/.ssh/id_ed25519"),this.sshPublicKeyPath=VX($.sshPublicKeyPath||process.env.HCLOUD_SSH_PUBLIC_KEY||`${this.sshPrivateKeyPath}.pub`),this.sshUser=$.sshUser||process.env.HCLOUD_SSH_USER||"root",this.location=$.location||process.env.HCLOUD_LOCATION||"fsn1",this.waitForBoot=$.waitForBoot??!0,this.bootWait={sshIntervalMs:$.bootWait?.sshIntervalMs??5000,sshTimeoutMs:$.bootWait?.sshTimeoutMs??300000,cloudInitIntervalMs:$.bootWait?.cloudInitIntervalMs??5000,cloudInitTimeoutMs:$.bootWait?.cloudInitTimeoutMs??600000}}async provisionComputeInfrastructure($){let{config:J,environment:Y}=$,Z=J.project.slug,Q=J.infrastructure?.compute;if(!Q)throw Error("infrastructure.compute is required to provision Hetzner compute");let G=w1(J,Y),W=`${Z}-${Y}-app`,z=zX(Q);if(z.dedicatedServices||z.appServers>1)return this.provisionFleet($,z);let U=await I9(G);if(U?.serverId){let y=await this.tryGetServer(U.serverId);if(y&&y.status!=="off")return this.outputsFromState(U,y)}let H=k2(Z,Y,"app"),X=await this.findExistingServer(Z,Y,W);if(X&&X.status!=="off"){let y={provider:"hetzner",stackName:G,serverId:X.id,serverName:X.name,publicIp:X.public_net.ipv4?.ip,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser};return await k9(G,y),this.outputsFromState(y,X)}let B=J.sites||{},K=this.collectUpstreamPorts(B),V=Q.proxy?.engine==="rpx"?x9({proxy:Q.proxy,config:C9(B,{proxy:Q.proxy}),slug:J.project.slug,bunBin:Q.runtime==="node"||Q.runtime==="deno"?void 0:"/usr/local/bin/bun"}):void 0,A=r7(J),O=Q.bakedImage===!0,_=x2({runtime:A.runtime,runtimeVersion:A.runtimeVersion,systemPackages:Q.systemPackages,database:J.infrastructure?.database,phpProvision:A.phpProvision,servicesProvision:A.servicesProvision,rpxProvision:V,baked:O}),E=z8(_),j=s7(Q.size),T=Q.image||J.hetzner?.image||"ubuntu-24.04",M=`${Z}-${Y}-app-fw`,{firewall:R}=await this.ensureFirewall(M,H,fQ({allowSsh:Q.allowSsh!==!1,sitePorts:K})),q=await this.ensureSshKey(Z,Y,H),{server:w,action:C}=await this.client.createServer({name:W,serverType:j,image:T,location:J.hetzner?.location||this.location,userData:E,labels:H,sshKeys:q?[q]:void 0,firewalls:[{firewall:R.id}]});await this.client.waitForAction(C.id);let S=await this.client.waitForServerRunning(w.id),x={provider:"hetzner",stackName:G,serverId:S.id,serverName:S.name,firewallId:R.id,publicIp:S.public_net.ipv4?.ip,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser};await k9(G,x);let k=S.public_net.ipv4?.ip;if(k&&this.waitForBoot)await this.waitForSshReady(k),await this.waitForCloudInit(k);return this.outputsFromState(x,S)}async provisionFleet($,J){let{config:Y,environment:Z}=$,Q=Y.project.slug,G=Y.infrastructure.compute,W=w1(Y,Z),z=s7(G.size),U=G.image||Y.hetzner?.image||"ubuntu-24.04",H=Y.hetzner?.location||this.location,X=G.bakedImage===!0,B=await I9(W);if(B?.loadBalancerId){let t=(await this.client.listLoadBalancers().catch(()=>[])).find((o)=>o.id===B.loadBalancerId);if(t)return{appPublicIp:t.public_net?.ipv4?.ip,loadBalancerIp:t.public_net?.ipv4?.ip,servicesPrivateIp:B.servicesPrivateIp,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser}}let K=await this.ensureSshKey(Q,Z,k2(Q,Z,"app")),V=`${Q}-${Z}-net`,O=(await this.client.listNetworks().catch(()=>[])).find((e)=>e.name===V)??await this.client.createNetwork({name:V,labels:k2(Q,Z,"app")}),{firewall:_}=await this.ensureFirewall(`${Q}-${Z}-app-fw`,k2(Q,Z,"app"),fQ({allowSsh:!0,sitePorts:[]})),{firewall:E}=await this.ensureFirewall(`${Q}-${Z}-services-fw`,k2(Q,Z,"services"),[{direction:"in",protocol:"tcp",port:"22",source_ips:["0.0.0.0/0","::/0"]},{direction:"in",protocol:"tcp",port:"3306",source_ips:[O.ip_range]},{direction:"in",protocol:"tcp",port:"5432",source_ips:[O.ip_range]},{direction:"in",protocol:"tcp",port:"6379",source_ips:[O.ip_range]},{direction:"in",protocol:"tcp",port:"7700",source_ips:[O.ip_range]}]),j=[...f7(G.managedServices??{mysql:!0,redis:!0},{bindPrivate:!0}),...g7(Y.infrastructure?.appDatabase,G.managedServices??{mysql:!0}),...q9(!0),...T9(!0),...w9(G.sshKeys)],T=z8(x2({runtime:"php",servicesProvision:j,baked:X})),M=await this.client.listServers().catch(()=>[]),R=[],q=M.find((e)=>w$(e.labels,Q,Z,"services"));if(!q){let{server:e,action:t}=await this.client.createServer({name:`${Q}-${Z}-services`,serverType:s7(typeof G.servicesServer==="object"?G.servicesServer.size:G.size),image:U,location:H,userData:T,labels:k2(Q,Z,"services"),sshKeys:K?[K]:void 0,firewalls:[{firewall:E.id}],networks:[O.id]});await this.client.waitForAction(t.id),q=await this.client.waitForServerRunning(e.id),R.push(e.id)}let w=q.id,C=q.private_net?.[0]?.ip??(await this.client.getServer(w)).private_net?.[0]?.ip,S=[...q9(!0),...T9(!0),...w9(G.sshKeys),...p7(Y.notifications)],x=d7({versions:G.php?.versions,default:G.php?.default,extensions:G.php?.extensions,installNginx:G.webServer!=="rpx",optimizeForProduction:G.php?.optimizeForProduction,ini:G.php?.ini}),k=z8(x2({runtime:"php",phpProvision:x,servicesProvision:S,baked:X})),y=M.filter((e)=>w$(e.labels,Q,Z,"app")),d=y.map((e)=>e.id);if(y.length>J.appServers)for(let e of y.slice(J.appServers))await this.client.deleteServer(e.id).catch(()=>{}),d.splice(d.indexOf(e.id),1);for(let e=y.length;e<J.appServers;e++){let{server:t,action:o}=await this.client.createServer({name:`${Q}-${Z}-app-${e+1}`,serverType:z,image:U,location:H,userData:k,labels:k2(Q,Z,"app"),sshKeys:K?[K]:void 0,firewalls:[{firewall:_.id}],networks:[O.id]});await this.client.waitForAction(o.id),d.push(t.id),R.push(t.id)}if(this.waitForBoot&&R.length>0)await Promise.all(R.map(async(e)=>{let o=(await this.client.waitForServerRunning(e)).public_net.ipv4?.ip;if(o)await this.waitForSshReady(o),await this.waitForCloudInit(o)}));let i=`${Q}-${Z}-lb`,u,l;if(J.loadBalancer){let t=(await this.client.listLoadBalancers().catch(()=>[])).find((o)=>o.name===i)??await this.client.createLoadBalancer({name:i,location:H,network:O.id,labels:k2(Q,Z,"lb"),labelSelector:`ts-cloud/project=${Q},ts-cloud/environment=${Z},ts-cloud/role=app`,services:[{listenPort:80,destinationPort:80},{listenPort:443,destinationPort:443}]});l=t.id,u=t.public_net?.ipv4?.ip}let a=u??(await this.client.getServer(d[0]).catch(()=>{return}))?.public_net.ipv4?.ip,J0={provider:"hetzner",stackName:W,networkId:O.id,loadBalancerId:l,servicesServerId:w,servicesPrivateIp:C,publicIp:a,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser};return await k9(W,J0),{appPublicIp:a,loadBalancerIp:u,servicesPrivateIp:C,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser}}async destroyCompute($){let{config:J,environment:Y}=$,Z=J.project.slug,Q=w1(J,Y),G=await I9(Q),W=[],z=`${Z}-${Y}-lb`,H=(await this.client.listLoadBalancers().catch(()=>[])).find((_)=>_.name===z);if(H)try{await this.client.deleteLoadBalancer(H.id),W.push(`load balancer ${z}`)}catch{}let X=await this.client.listServers().catch(()=>[]),B=new Set;for(let _ of X)if(w$(_.labels,Z,Y,"app")||w$(_.labels,Z,Y,"services"))B.add(_.id);if(G?.serverId)B.add(G.serverId);if(G?.servicesServerId)B.add(G.servicesServerId);for(let _ of B)try{await this.client.deleteServer(_),W.push(`server ${_}`)}catch{}let K=await this.client.listFirewalls().catch(()=>[]);for(let _ of[`${Z}-${Y}-app-fw`,`${Z}-${Y}-services-fw`]){let E=K.find((j)=>j.name===_);if(!E)continue;for(let j=0;j<12;j++)try{await this.client.deleteFirewall(E.id),W.push(`firewall ${_}`);break}catch{await this.sleep(3000)}}let V=`${Z}-${Y}-net`,O=(await this.client.listNetworks().catch(()=>[])).find((_)=>_.name===V);if(O)for(let _=0;_<12;_++)try{await this.client.deleteNetwork(O.id),W.push(`network ${V}`);break}catch{await this.sleep(3000)}return await k9(Q,{provider:"hetzner",stackName:Q}).catch(()=>{}),{destroyed:W}}async getComputeOutputs($){let J=w1($.config,$.environment),Y=await I9(J);if(Y?.serverId){let G=await this.client.getServer(Y.serverId);return this.outputsFromState(Y,G)}if(Y?.loadBalancerId||Y?.servicesPrivateIp)return this.outputsFromState(Y);let Q=(await this.findComputeTargets({slug:$.config.project.slug,environment:$.environment,role:"app"}))[0];return{deployStoragePath:"/var/ts-cloud/staging",appInstanceId:Q?.id,appPublicIp:Q?.publicIp,sshUser:this.sshUser}}async uploadRelease($){let J=$.targets?.length?$.targets:await this.findComputeTargets({slug:$.config.project.slug,environment:$.environment,role:"app"});if(J.length===0)throw Error("No Hetzner compute targets found for release upload");let Y=`/var/ts-cloud/staging/${$.remoteKey.split("/").pop()}`;for(let Z of J){if(!Z.publicIp)throw Error(`Target ${Z.id} has no public IP for SCP upload`);this.scpToHost(Z.publicIp,$.localPath,Y)}return{artifactRef:Y}}async findComputeTargets($){let J=await this.client.listServers(),Y=$.role||"app",Z=(G)=>({id:String(G.id),name:G.name,publicIp:G.public_net.ipv4?.ip,privateIp:G.private_net?.[0]?.ip,status:G.status}),Q=J.filter((G)=>w$(G.labels,$.slug,$.environment,Y));if(Q.length>0)return Q.map(Z);if(Y==="app"){let G=J.filter((W)=>W.status!=="off"&&W.labels?.[`${K1}/managed-by`]==="ts-cloud"&&W.labels?.[`${K1}/environment`]===$.environment&&W.labels?.[`${K1}/role`]==="app");if(G.length===1)return G.map(Z)}return[]}async runRemoteDeploy($){if($.targets.length===0)return{success:!1,instanceCount:0,perInstance:[],error:"No targets provided"};let J=$.commands.join(`
7465
+ `,"utf8")}var FX=".ts-cloud/state";var KX=()=>{};import{existsSync as pT,readFileSync as rT}from"node:fs";import{homedir as nT}from"node:os";import{join as iT}from"node:path";import{execSync as uQ}from"node:child_process";function VX($){return $.startsWith("~/")?iT(nT(),$.slice(2)):$}var dQ=268435456,R$;var cQ=$0(async()=>{kQ();CQ();c7();u7();DQ();HX();KX();await z$([v0(),t7()]);R$=class R${name="hetzner";usesCloudFormation=!1;client;sshPrivateKeyPath;sshPublicKeyPath;sshUser;location;waitForBoot;bootWait;constructor($={}){this.client=$.client??new P9({apiToken:n7($.apiToken)}),this.sshPrivateKeyPath=VX($.sshPrivateKeyPath||process.env.HCLOUD_SSH_KEY||"~/.ssh/id_ed25519"),this.sshPublicKeyPath=VX($.sshPublicKeyPath||process.env.HCLOUD_SSH_PUBLIC_KEY||`${this.sshPrivateKeyPath}.pub`),this.sshUser=$.sshUser||process.env.HCLOUD_SSH_USER||"root",this.location=$.location||process.env.HCLOUD_LOCATION||"fsn1",this.waitForBoot=$.waitForBoot??!0,this.bootWait={sshIntervalMs:$.bootWait?.sshIntervalMs??5000,sshTimeoutMs:$.bootWait?.sshTimeoutMs??300000,cloudInitIntervalMs:$.bootWait?.cloudInitIntervalMs??5000,cloudInitTimeoutMs:$.bootWait?.cloudInitTimeoutMs??600000}}async provisionComputeInfrastructure($){let{config:J,environment:Y}=$,Z=J.project.slug,Q=J.infrastructure?.compute;if(!Q)throw Error("infrastructure.compute is required to provision Hetzner compute");let G=w1(J,Y),W=`${Z}-${Y}-app`,z=zX(Q);if(z.dedicatedServices||z.appServers>1)return this.provisionFleet($,z);let U=await I9(G);if(U?.serverId){let y=await this.tryGetServer(U.serverId);if(y&&y.status!=="off")return this.outputsFromState(U,y)}let H=k2(Z,Y,"app"),X=await this.findExistingServer(Z,Y,W);if(X&&X.status!=="off"){let y={provider:"hetzner",stackName:G,serverId:X.id,serverName:X.name,publicIp:X.public_net.ipv4?.ip,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser};return await k9(G,y),this.outputsFromState(y,X)}let B=J.sites||{},K=this.collectUpstreamPorts(B),V=Q.proxy?.engine==="rpx"?x9({proxy:Q.proxy,config:C9(B,{proxy:Q.proxy}),slug:J.project.slug,bunBin:Q.runtime==="node"||Q.runtime==="deno"?void 0:"/usr/local/bin/bun"}):void 0,A=r7(J),O=Q.bakedImage===!0,_=x2({runtime:A.runtime,runtimeVersion:A.runtimeVersion,systemPackages:Q.systemPackages,database:J.infrastructure?.database,phpProvision:A.phpProvision,servicesProvision:A.servicesProvision,rpxProvision:V,baked:O}),E=z8(_),j=s7(Q.size),T=Q.image||J.hetzner?.image||"ubuntu-24.04",M=`${Z}-${Y}-app-fw`,{firewall:R}=await this.ensureFirewall(M,H,fQ({allowSsh:Q.allowSsh!==!1,sitePorts:K})),q=await this.ensureSshKey(Z,Y,H),{server:w,action:C}=await this.client.createServer({name:W,serverType:j,image:T,location:J.hetzner?.location||this.location,userData:E,labels:H,sshKeys:q?[q]:void 0,firewalls:[{firewall:R.id}]});await this.client.waitForAction(C.id);let S=await this.client.waitForServerRunning(w.id),x={provider:"hetzner",stackName:G,serverId:S.id,serverName:S.name,firewallId:R.id,publicIp:S.public_net.ipv4?.ip,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser};await k9(G,x);let k=S.public_net.ipv4?.ip;if(k&&this.waitForBoot)await this.waitForSshReady(k),await this.waitForCloudInit(k);return this.outputsFromState(x,S)}async provisionFleet($,J){let{config:Y,environment:Z}=$,Q=Y.project.slug,G=Y.infrastructure.compute,W=w1(Y,Z),z=s7(G.size),U=G.image||Y.hetzner?.image||"ubuntu-24.04",H=Y.hetzner?.location||this.location,X=G.bakedImage===!0,B=await I9(W);if(B?.loadBalancerId){let t=(await this.client.listLoadBalancers().catch(()=>[])).find((o)=>o.id===B.loadBalancerId);if(t)return{appPublicIp:t.public_net?.ipv4?.ip,loadBalancerIp:t.public_net?.ipv4?.ip,servicesPrivateIp:B.servicesPrivateIp,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser}}let K=await this.ensureSshKey(Q,Z,k2(Q,Z,"app")),V=`${Q}-${Z}-net`,O=(await this.client.listNetworks().catch(()=>[])).find((e)=>e.name===V)??await this.client.createNetwork({name:V,labels:k2(Q,Z,"app")}),{firewall:_}=await this.ensureFirewall(`${Q}-${Z}-app-fw`,k2(Q,Z,"app"),fQ({allowSsh:!0,sitePorts:[]})),{firewall:E}=await this.ensureFirewall(`${Q}-${Z}-services-fw`,k2(Q,Z,"services"),[{direction:"in",protocol:"tcp",port:"22",source_ips:["0.0.0.0/0","::/0"]},{direction:"in",protocol:"tcp",port:"3306",source_ips:[O.ip_range]},{direction:"in",protocol:"tcp",port:"5432",source_ips:[O.ip_range]},{direction:"in",protocol:"tcp",port:"6379",source_ips:[O.ip_range]},{direction:"in",protocol:"tcp",port:"7700",source_ips:[O.ip_range]}]),j=[...f7(G.managedServices??{mysql:!0,redis:!0},{bindPrivate:!0}),...g7(Y.infrastructure?.appDatabase,G.managedServices??{mysql:!0}),...q9(!0),...T9(!0),...w9(G.sshKeys)],T=z8(x2({runtime:"php",servicesProvision:j,baked:X})),M=await this.client.listServers().catch(()=>[]),R=[],q=M.find((e)=>w$(e.labels,Q,Z,"services"));if(!q){let{server:e,action:t}=await this.client.createServer({name:`${Q}-${Z}-services`,serverType:s7(typeof G.servicesServer==="object"?G.servicesServer.size:G.size),image:U,location:H,userData:T,labels:k2(Q,Z,"services"),sshKeys:K?[K]:void 0,firewalls:[{firewall:E.id}],networks:[O.id]});await this.client.waitForAction(t.id),q=await this.client.waitForServerRunning(e.id),R.push(e.id)}let w=q.id,C=q.private_net?.[0]?.ip??(await this.client.getServer(w)).private_net?.[0]?.ip,S=[...q9(!0),...T9(!0),...w9(G.sshKeys),...p7(Y.notifications)],x=d7({versions:G.php?.versions,default:G.php?.default,extensions:G.php?.extensions,installNginx:G.webServer!=="rpx",optimizeForProduction:G.php?.optimizeForProduction,ini:G.php?.ini}),k=z8(x2({runtime:"php",phpProvision:x,servicesProvision:S,baked:X})),y=M.filter((e)=>w$(e.labels,Q,Z,"app")),d=y.map((e)=>e.id);if(y.length>J.appServers)for(let e of y.slice(J.appServers))await this.client.deleteServer(e.id).catch(()=>{}),d.splice(d.indexOf(e.id),1);for(let e=y.length;e<J.appServers;e++){let{server:t,action:o}=await this.client.createServer({name:`${Q}-${Z}-app-${e+1}`,serverType:z,image:U,location:H,userData:k,labels:k2(Q,Z,"app"),sshKeys:K?[K]:void 0,firewalls:[{firewall:_.id}],networks:[O.id]});await this.client.waitForAction(o.id),d.push(t.id),R.push(t.id)}if(this.waitForBoot&&R.length>0)await Promise.all(R.map(async(e)=>{let o=(await this.client.waitForServerRunning(e)).public_net.ipv4?.ip;if(o)await this.waitForSshReady(o),await this.waitForCloudInit(o)}));let i=`${Q}-${Z}-lb`,u,l;if(J.loadBalancer){let t=(await this.client.listLoadBalancers().catch(()=>[])).find((o)=>o.name===i)??await this.client.createLoadBalancer({name:i,location:H,network:O.id,labels:k2(Q,Z,"lb"),labelSelector:`ts-cloud/project=${Q},ts-cloud/environment=${Z},ts-cloud/role=app`,services:[{listenPort:80,destinationPort:80},{listenPort:443,destinationPort:443}]});l=t.id,u=t.public_net?.ipv4?.ip}let a=u??(await this.client.getServer(d[0]).catch(()=>{return}))?.public_net.ipv4?.ip,J0={provider:"hetzner",stackName:W,networkId:O.id,loadBalancerId:l,servicesServerId:w,servicesPrivateIp:C,publicIp:a,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser};return await k9(W,J0),{appPublicIp:a,loadBalancerIp:u,servicesPrivateIp:C,deployStoragePath:"/var/ts-cloud/staging",sshUser:this.sshUser}}async destroyCompute($){let{config:J,environment:Y}=$,Z=J.project.slug,Q=w1(J,Y),G=await I9(Q),W=[],z=`${Z}-${Y}-lb`,H=(await this.client.listLoadBalancers().catch(()=>[])).find((_)=>_.name===z);if(H)try{await this.client.deleteLoadBalancer(H.id),W.push(`load balancer ${z}`)}catch{}let X=await this.client.listServers().catch(()=>[]),B=new Set;for(let _ of X)if(w$(_.labels,Z,Y,"app")||w$(_.labels,Z,Y,"services"))B.add(_.id);if(G?.serverId)B.add(G.serverId);if(G?.servicesServerId)B.add(G.servicesServerId);for(let _ of B)try{await this.client.deleteServer(_),W.push(`server ${_}`)}catch{}let K=await this.client.listFirewalls().catch(()=>[]);for(let _ of[`${Z}-${Y}-app-fw`,`${Z}-${Y}-services-fw`]){let E=K.find((j)=>j.name===_);if(!E)continue;for(let j=0;j<12;j++)try{await this.client.deleteFirewall(E.id),W.push(`firewall ${_}`);break}catch{await this.sleep(3000)}}let V=`${Z}-${Y}-net`,O=(await this.client.listNetworks().catch(()=>[])).find((_)=>_.name===V);if(O)for(let _=0;_<12;_++)try{await this.client.deleteNetwork(O.id),W.push(`network ${V}`);break}catch{await this.sleep(3000)}return await k9(Q,{provider:"hetzner",stackName:Q}).catch(()=>{}),{destroyed:W}}async getComputeOutputs($){let J=w1($.config,$.environment),Y=await I9(J);if(Y?.serverId){let G=await this.client.getServer(Y.serverId);return this.outputsFromState(Y,G)}if(Y?.loadBalancerId||Y?.servicesPrivateIp)return this.outputsFromState(Y);let Q=(await this.findComputeTargets({slug:$.config.project.slug,environment:$.environment,role:"app"}))[0];return{deployStoragePath:"/var/ts-cloud/staging",appInstanceId:Q?.id,appPublicIp:Q?.publicIp,sshUser:this.sshUser}}async uploadRelease($){let J=$.targets?.length?$.targets:await this.findComputeTargets({slug:$.config.project.slug,environment:$.environment,role:"app"});if(J.length===0)throw Error("No Hetzner compute targets found for release upload");let Z=`/var/ts-cloud/staging/${$.remoteKey.replace(/^releases\//,"").replace(/\//g,"-")}`;for(let Q of J){if(!Q.publicIp)throw Error(`Target ${Q.id} has no public IP for SCP upload`);this.scpToHost(Q.publicIp,$.localPath,Z)}return{artifactRef:Z}}async findComputeTargets($){let J=await this.client.listServers(),Y=$.role||"app",Z=(G)=>({id:String(G.id),name:G.name,publicIp:G.public_net.ipv4?.ip,privateIp:G.private_net?.[0]?.ip,status:G.status}),Q=J.filter((G)=>w$(G.labels,$.slug,$.environment,Y));if(Q.length>0)return Q.map(Z);if(Y==="app"){let G=J.filter((W)=>W.status!=="off"&&W.labels?.[`${K1}/managed-by`]==="ts-cloud"&&W.labels?.[`${K1}/environment`]===$.environment&&W.labels?.[`${K1}/role`]==="app");if(G.length===1)return G.map(Z)}return[]}async runRemoteDeploy($){if($.targets.length===0)return{success:!1,instanceCount:0,perInstance:[],error:"No targets provided"};let J=$.commands.join(`
7466
7466
  `),Y=[];for(let Q of $.targets){if(!Q.publicIp){Y.push({instanceId:Q.id,status:"Failed",error:"Missing public IP"});continue}try{let G=this.sshExec(Q.publicIp,J);Y.push({instanceId:Q.id,status:"Success",output:G})}catch(G){Y.push({instanceId:Q.id,status:"Failed",error:G.message})}}let Z=Y.every((Q)=>Q.status==="Success");return{success:Z,instanceCount:$.targets.length,perInstance:Y,error:Z?void 0:"One or more SSH deploy commands failed"}}async ensureSshKey($,J,Y){if(!pT(this.sshPublicKeyPath))throw Error(`SSH public key not found at ${this.sshPublicKeyPath}. ts-cloud deploys to Hetzner over SSH and needs a public key to authorize on the server. Generate one (\`ssh-keygen -t ed25519\`) or set hetzner.sshPrivateKeyPath / HCLOUD_SSH_PUBLIC_KEY.`);let Z=rT(this.sshPublicKeyPath,"utf8").trim(),Q=IQ(Z),W=(await this.client.listSshKeys()).find((U)=>IQ(U.public_key)===Q);if(W)return W.id;return(await this.client.createSshKey({name:`${$}-${J}-deploy`,publicKey:Z,labels:Y})).id}async tryGetServer($){try{return await this.client.getServer($)}catch{return null}}async findExistingServer($,J,Y){let Z=await this.client.listServers(),Q=Z.find((W)=>w$(W.labels,$,J,"app")||W.name===Y);if(Q)return Q;let G=Z.filter((W)=>W.status!=="off"&&W.labels?.[`${K1}/managed-by`]==="ts-cloud"&&W.labels?.[`${K1}/environment`]===J&&W.labels?.[`${K1}/role`]==="app");if(G.length===1){let W=G[0],z=W.labels?.[`${K1}/project`]??"unknown";return console.warn(`[ts-cloud] No server named '${Y}' found; adopting existing ts-cloud app server '${W.name}' (project label '${z}') for project '${$}' — `+"updating it in place instead of provisioning a new server."),W}return}async ensureFirewall($,J,Y){let Q=(await this.client.listFirewalls()).find((W)=>W.name===$);if(Q)return await this.client.setFirewallRules(Q.id,Y),{firewall:Q};let{firewall:G}=await this.client.createFirewall({name:$,labels:J,rules:Y});return{firewall:G}}collectUpstreamPorts($){let J=new Set;for(let Y of Object.values($))if(typeof Y.port==="number")J.add(Y.port);return[...J].filter((Y)=>![80,443].includes(Y))}async sleep($){await new Promise((J)=>setTimeout(J,$))}async waitForSshReady($){let{sshIntervalMs:J,sshTimeoutMs:Y}=this.bootWait,Z=Date.now(),Q;while(Date.now()-Z<Y)try{uQ(`ssh ${this.sshBaseArgs($,["-o","ConnectTimeout=5"]).map((G)=>`"${G.replace(/"/g,"\\\"")}"`).join(" ")} true`,{stdio:"pipe",maxBuffer:dQ});return}catch(G){Q=G,await this.sleep(J)}throw Error(`Timed out waiting for SSH on ${$} after ${Y}ms: ${Q?.message??"unknown error"}`)}async waitForCloudInit($){let{cloudInitIntervalMs:J,cloudInitTimeoutMs:Y}=this.bootWait,Z=Date.now();while(Date.now()-Z<Y){try{let Q=this.sshExec($,"cloud-init status --long 2>/dev/null || cloud-init status 2>/dev/null || echo status:\\ done");if(/status:\s*done/.test(Q))return;if(/status:\s*error/.test(Q))throw Error(`cloud-init reported an error on ${$}:
7467
7467
  ${Q}`)}catch(Q){if(Q instanceof Error&&/cloud-init reported an error/.test(Q.message))throw Q}await this.sleep(J)}throw Error(`Timed out waiting for cloud-init to finish on ${$} after ${Y}ms`)}outputsFromState($,J){return{deployStoragePath:$.deployStoragePath||"/var/ts-cloud/staging",appInstanceId:$.serverId?String($.serverId):void 0,appPublicIp:J?.public_net.ipv4?.ip||$.publicIp,sshUser:$.sshUser||this.sshUser,servicesPrivateIp:$.servicesPrivateIp}}static SSH_HOST_KEY_OPTS=["-o","StrictHostKeyChecking=no","-o","UserKnownHostsFile=/dev/null","-o","LogLevel=ERROR"];sshBaseArgs($,J=[]){return["-i",this.sshPrivateKeyPath,...R$.SSH_HOST_KEY_OPTS,"-o","BatchMode=yes",...J,`${this.sshUser}@${$}`]}scpToHost($,J,Y){uQ(["scp","-i",this.sshPrivateKeyPath,...R$.SSH_HOST_KEY_OPTS,"-o","BatchMode=yes",J,`${this.sshUser}@${$}:${Y}`].map((Z)=>`"${Z.replace(/"/g,"\\\"")}"`).join(" "),{stdio:"pipe",maxBuffer:dQ})}sshExec($,J){let Y=J.replace(/'/g,"'\\''");return uQ(`ssh ${this.sshBaseArgs($).map((Z)=>`"${Z.replace(/"/g,"\\\"")}"`).join(" ")} '${Y}'`,{encoding:"utf8",stdio:["pipe","pipe","pipe"],maxBuffer:dQ})}}});class o7{name="hetzner";usesCloudFormation=!1;async getComputeOutputs(){return{appPublicIp:"127.0.0.1",sshUser:"root"}}async uploadRelease($){return{artifactRef:$.localPath}}async findComputeTargets($){return[{id:"localhost",name:"localhost",publicIp:"127.0.0.1",status:"running"}]}async runRemoteDeploy($){let J=$.commands.join(`
7468
7468
  `);try{let Y=Bun.spawn(["bash","-c",J],{stdout:"pipe",stderr:"pipe",env:process.env}),[Z,Q,G]=await Promise.all([new Response(Y.stdout).text(),new Response(Y.stderr).text(),Y.exited]);return{success:G===0,instanceCount:1,perInstance:[{instanceId:"localhost",status:G===0?"Success":"Failed",output:Z,error:Q}]}}catch(Y){return{success:!1,instanceCount:1,perInstance:[{instanceId:"localhost",status:"Failed",error:Y?.message??String(Y)}],error:Y?.message??String(Y)}}}}function mQ(){let $=process.env.TS_CLOUD_DASHBOARD_BOX;return $!=null&&$!==""&&$!=="0"&&$.toLowerCase()!=="false"}function t0($){if(mQ())return new o7;switch($.provider??D2($.config)){case"aws":return new N9({region:$.config.project.region});case"hetzner":return new R$({apiToken:$.config.hetzner?.apiToken,sshPrivateKeyPath:$.config.hetzner?.sshPrivateKeyPath,sshUser:$.config.hetzner?.sshUser,location:$.config.hetzner?.location});default:throw Error(`Unknown cloud provider: ${$.provider??D2($.config)}`)}}class lQ{drivers=new Map;getDriver($,J){let Y=J??D2($),Z=`${Y}:${$.project.slug}:${$.project.region||"default"}`,Q=this.drivers.get(Z);if(Q)return Q;let G=t0({config:$,provider:Y});return this.drivers.set(Z,G),G}}var aT;var OX=$0(async()=>{await z$([v0(),xQ(),cQ()]);aT=new lQ});function e7($){return Object.entries($).map(([J,Y])=>`${J}=${tT(String(Y))}`).join(`
@@ -7561,7 +7561,7 @@ Run \`${this.name??"cli"} --help\` for usage.`;y2.stderr.write(`${G}${Q}${W}
7561
7561
  `).length-1;this.output.write(A2.move(-999,$*-1))}render(){let $=EW(this._render(this)??"",MW.stdout.columns,{hard:!0,trim:!1});if($===this._prevFrame)return;if(this.state==="initial")this.output.write(A2.hide);else{let J=GK(this._prevFrame,$);if(this.restoreCursor(),J&&J?.length===1){let Y=J[0];this.output.write(A2.move(0,Y)),this.output.write(N5.lines(1));let Z=$.split(`
7562
7562
  `);this.output.write(Z[Y]),this._prevFrame=$,this.output.write(A2.move(0,Z.length-Y-1));return}if(J&&J?.length>1){let Y=J[0];this.output.write(A2.move(0,Y)),this.output.write(N5.down());let Q=$.split(`
7563
7563
  `).slice(Y);this.output.write(Q.join(`
7564
- `)),this._prevFrame=$;return}this.output.write(N5.down())}if(this.output.write($),this.state==="initial")this.state="active";this._prevFrame=$}}function FK($,J){if($===void 0)return 0;if(J.length===0)return 0;let Z=J.findIndex((Q)=>Q.value===$);return Z!==-1?Z:0}function BK($,J){return(J.label??String(J.value)).toLowerCase().includes($.toLowerCase())}function KK($,J){if(!J)return;if($)return J;return J[0]}class VK extends k5{filteredOptions;multiple;isNavigating=!1;selectedValues=[];focusedValue;#$=0;#Y="";#Z;#J;get cursor(){return this.#$}get userInputWithCursor(){if(!this.userInput)return e9.inverse(e9.hidden("_"));if(this._cursor>=this.userInput.length)return`${this.userInput}█`;let $=this.userInput.slice(0,this._cursor),[J,...Y]=this.userInput.slice(this._cursor);return`${$}${e9.inverse(J)}${Y.join("")}`}get options(){if(typeof this.#J==="function")return this.#J();return this.#J}constructor($){super($);this.#J=$.options;let J=this.options;this.filteredOptions=[...J],this.multiple=$.multiple===!0,this.#Z=$.filter??BK;let Y;if($.initialValue&&Array.isArray($.initialValue))if(this.multiple)Y=$.initialValue;else Y=$.initialValue.slice(0,1);else if(!this.multiple&&this.options.length>0)Y=[this.options[0].value];if(Y)for(let Z of Y){let Q=J.findIndex((G)=>G.value===Z);if(Q!==-1)this.toggleSelected(Z),this.#$=Q}this.focusedValue=this.options[this.#$]?.value,this.on("key",(Z,Q)=>this.#Q(Z,Q)),this.on("userInput",(Z)=>this.#G(Z))}_isActionKey($,J){return $==="\t"||this.multiple&&this.isNavigating&&J.name==="space"&&$!==void 0&&$!==""}#Q($,J){let Y=J.name==="up",Z=J.name==="down",Q=J.name==="return";if(Y||Z){if(this.#$=Math.max(0,Math.min(this.#$+(Y?-1:1),this.filteredOptions.length-1)),this.focusedValue=this.filteredOptions[this.#$]?.value,!this.multiple)this.selectedValues=[this.focusedValue];this.isNavigating=!0}else if(Q)this.value=KK(this.multiple,this.selectedValues);else if(this.multiple)if(this.focusedValue!==void 0&&(J.name==="tab"||this.isNavigating&&J.name==="space"))this.toggleSelected(this.focusedValue);else this.isNavigating=!1;else{if(this.focusedValue)this.selectedValues=[this.focusedValue];this.isNavigating=!1}}deselectAll(){this.selectedValues=[]}toggleSelected($){if(this.filteredOptions.length===0)return;if(this.multiple)if(this.selectedValues.includes($))this.selectedValues=this.selectedValues.filter((J)=>J!==$);else this.selectedValues=[...this.selectedValues,$];else this.selectedValues=[$]}#G($){if($!==this.#Y){this.#Y=$;let J=this.options;if($)this.filteredOptions=J.filter((Y)=>this.#Z($,Y));else this.filteredOptions=[...J];if(this.#$=FK(this.focusedValue,this.filteredOptions),this.focusedValue=this.filteredOptions[this.#$]?.value,!this.multiple)if(this.focusedValue!==void 0)this.toggleSelected(this.focusedValue);else this.deselectAll()}}}class OK extends k5{options;cursor=0;#$;getGroupItems($){return this.options.filter((J)=>J.group===$)}isGroupSelected($){let J=this.getGroupItems($),Y=this.value;if(Y===void 0)return!1;return J.every((Z)=>Y.includes(Z.value))}toggleValue(){let $=this.options[this.cursor];if(this.value===void 0)this.value=[];if($.group===!0){let J=$.value,Y=this.getGroupItems(J);if(this.isGroupSelected(J))this.value=this.value.filter((Z)=>Y.findIndex((Q)=>Q.value===Z)===-1);else this.value=[...this.value,...Y.map((Z)=>Z.value)];this.value=Array.from(new Set(this.value))}else{let J=this.value.includes($.value);this.value=J?this.value.filter((Y)=>Y!==$.value):[...this.value,$.value]}}constructor($){super($,!1);let{options:J}=$;this.#$=$.selectableGroups!==!1,this.options=Object.entries(J).flatMap(([Y,Z])=>[{value:Y,group:!0,label:Y},...Z.map((Q)=>({...Q,group:Y}))]),this.value=[...$.initialValues??[]],this.cursor=Math.max(this.options.findIndex(({value:Y})=>Y===$.cursorAt),this.#$?0:1),this.on("cursor",(Y)=>{switch(Y){case"left":case"up":{this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;let Z=this.options[this.cursor]?.group===!0;if(!this.#$&&Z)this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;break}case"down":case"right":{this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;let Z=this.options[this.cursor]?.group===!0;if(!this.#$&&Z)this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;break}case"space":this.toggleValue();break}})}}var AK=wB();var N0=($,J)=>AK?$:J,uR=N0("◆","*"),dR=N0("■","x"),cR=N0("▲","x"),mR=N0("◇","o"),lR=N0("┌","T"),_K=N0("│","|"),pR=N0("└","—"),rR=N0("●",">"),nR=N0("○"," "),iR=N0("◻","[•]"),aR=N0("◼","[+]"),tR=N0("◻","[ ]"),sR=N0("▪","•"),oR=N0("─","-"),eR=N0("╮","+"),$D=N0("├","+"),JD=N0("╯","+"),YD=N0("●","•"),ZD=N0("◆","*"),QD=N0("▲","!"),GD=N0("■","x");var WD={light:N0("─","-"),heavy:N0("━","="),block:N0("█","#")};function jK(){return`${e9.gray(_K)} `}var zD=jK();var h5="0.6.2";U0();import{existsSync as l5}from"node:fs";import{mkdir as SK,writeFile as gW}from"node:fs/promises";function p5($){$.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(J)=>{if(N("Initializing ts-cloud Project"),l5("cloud.config.ts")){if(!await g("cloud.config.ts already exists. Overwrite?",!1)){F("Initialization cancelled");return}}let Y=J?.name||await T0("Project name","my-app"),Z=J?.mode||await Y6("Select deployment mode",["serverless","server"]),Q=J?.region||await Y6("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),G=new P("Creating configuration file...");G.start();let W=`import { defineConfig } from '@ts-cloud/core'
7564
+ `)),this._prevFrame=$;return}this.output.write(N5.down())}if(this.output.write($),this.state==="initial")this.state="active";this._prevFrame=$}}function FK($,J){if($===void 0)return 0;if(J.length===0)return 0;let Z=J.findIndex((Q)=>Q.value===$);return Z!==-1?Z:0}function BK($,J){return(J.label??String(J.value)).toLowerCase().includes($.toLowerCase())}function KK($,J){if(!J)return;if($)return J;return J[0]}class VK extends k5{filteredOptions;multiple;isNavigating=!1;selectedValues=[];focusedValue;#$=0;#Y="";#Z;#J;get cursor(){return this.#$}get userInputWithCursor(){if(!this.userInput)return e9.inverse(e9.hidden("_"));if(this._cursor>=this.userInput.length)return`${this.userInput}█`;let $=this.userInput.slice(0,this._cursor),[J,...Y]=this.userInput.slice(this._cursor);return`${$}${e9.inverse(J)}${Y.join("")}`}get options(){if(typeof this.#J==="function")return this.#J();return this.#J}constructor($){super($);this.#J=$.options;let J=this.options;this.filteredOptions=[...J],this.multiple=$.multiple===!0,this.#Z=$.filter??BK;let Y;if($.initialValue&&Array.isArray($.initialValue))if(this.multiple)Y=$.initialValue;else Y=$.initialValue.slice(0,1);else if(!this.multiple&&this.options.length>0)Y=[this.options[0].value];if(Y)for(let Z of Y){let Q=J.findIndex((G)=>G.value===Z);if(Q!==-1)this.toggleSelected(Z),this.#$=Q}this.focusedValue=this.options[this.#$]?.value,this.on("key",(Z,Q)=>this.#Q(Z,Q)),this.on("userInput",(Z)=>this.#G(Z))}_isActionKey($,J){return $==="\t"||this.multiple&&this.isNavigating&&J.name==="space"&&$!==void 0&&$!==""}#Q($,J){let Y=J.name==="up",Z=J.name==="down",Q=J.name==="return";if(Y||Z){if(this.#$=Math.max(0,Math.min(this.#$+(Y?-1:1),this.filteredOptions.length-1)),this.focusedValue=this.filteredOptions[this.#$]?.value,!this.multiple)this.selectedValues=[this.focusedValue];this.isNavigating=!0}else if(Q)this.value=KK(this.multiple,this.selectedValues);else if(this.multiple)if(this.focusedValue!==void 0&&(J.name==="tab"||this.isNavigating&&J.name==="space"))this.toggleSelected(this.focusedValue);else this.isNavigating=!1;else{if(this.focusedValue)this.selectedValues=[this.focusedValue];this.isNavigating=!1}}deselectAll(){this.selectedValues=[]}toggleSelected($){if(this.filteredOptions.length===0)return;if(this.multiple)if(this.selectedValues.includes($))this.selectedValues=this.selectedValues.filter((J)=>J!==$);else this.selectedValues=[...this.selectedValues,$];else this.selectedValues=[$]}#G($){if($!==this.#Y){this.#Y=$;let J=this.options;if($)this.filteredOptions=J.filter((Y)=>this.#Z($,Y));else this.filteredOptions=[...J];if(this.#$=FK(this.focusedValue,this.filteredOptions),this.focusedValue=this.filteredOptions[this.#$]?.value,!this.multiple)if(this.focusedValue!==void 0)this.toggleSelected(this.focusedValue);else this.deselectAll()}}}class OK extends k5{options;cursor=0;#$;getGroupItems($){return this.options.filter((J)=>J.group===$)}isGroupSelected($){let J=this.getGroupItems($),Y=this.value;if(Y===void 0)return!1;return J.every((Z)=>Y.includes(Z.value))}toggleValue(){let $=this.options[this.cursor];if(this.value===void 0)this.value=[];if($.group===!0){let J=$.value,Y=this.getGroupItems(J);if(this.isGroupSelected(J))this.value=this.value.filter((Z)=>Y.findIndex((Q)=>Q.value===Z)===-1);else this.value=[...this.value,...Y.map((Z)=>Z.value)];this.value=Array.from(new Set(this.value))}else{let J=this.value.includes($.value);this.value=J?this.value.filter((Y)=>Y!==$.value):[...this.value,$.value]}}constructor($){super($,!1);let{options:J}=$;this.#$=$.selectableGroups!==!1,this.options=Object.entries(J).flatMap(([Y,Z])=>[{value:Y,group:!0,label:Y},...Z.map((Q)=>({...Q,group:Y}))]),this.value=[...$.initialValues??[]],this.cursor=Math.max(this.options.findIndex(({value:Y})=>Y===$.cursorAt),this.#$?0:1),this.on("cursor",(Y)=>{switch(Y){case"left":case"up":{this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;let Z=this.options[this.cursor]?.group===!0;if(!this.#$&&Z)this.cursor=this.cursor===0?this.options.length-1:this.cursor-1;break}case"down":case"right":{this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;let Z=this.options[this.cursor]?.group===!0;if(!this.#$&&Z)this.cursor=this.cursor===this.options.length-1?0:this.cursor+1;break}case"space":this.toggleValue();break}})}}var AK=wB();var N0=($,J)=>AK?$:J,uR=N0("◆","*"),dR=N0("■","x"),cR=N0("▲","x"),mR=N0("◇","o"),lR=N0("┌","T"),_K=N0("│","|"),pR=N0("└","—"),rR=N0("●",">"),nR=N0("○"," "),iR=N0("◻","[•]"),aR=N0("◼","[+]"),tR=N0("◻","[ ]"),sR=N0("▪","•"),oR=N0("─","-"),eR=N0("╮","+"),$D=N0("├","+"),JD=N0("╯","+"),YD=N0("●","•"),ZD=N0("◆","*"),QD=N0("▲","!"),GD=N0("■","x");var WD={light:N0("─","-"),heavy:N0("━","="),block:N0("█","#")};function jK(){return`${e9.gray(_K)} `}var zD=jK();var h5="0.7.0";U0();import{existsSync as l5}from"node:fs";import{mkdir as SK,writeFile as gW}from"node:fs/promises";function p5($){$.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(J)=>{if(N("Initializing ts-cloud Project"),l5("cloud.config.ts")){if(!await g("cloud.config.ts already exists. Overwrite?",!1)){F("Initialization cancelled");return}}let Y=J?.name||await T0("Project name","my-app"),Z=J?.mode||await Y6("Select deployment mode",["serverless","server"]),Q=J?.region||await Y6("Select AWS region",["us-east-1","us-west-2","eu-west-1","eu-central-1","ap-southeast-1"]),G=new P("Creating configuration file...");G.start();let W=`import { defineConfig } from '@ts-cloud/core'
7565
7565
 
7566
7566
  export default defineConfig({
7567
7567
  project: {
package/dist/index.js CHANGED
@@ -86971,7 +86971,8 @@ class HetznerDriver {
86971
86971
  if (targets.length === 0) {
86972
86972
  throw new Error("No Hetzner compute targets found for release upload");
86973
86973
  }
86974
- const remotePath = `/var/ts-cloud/staging/${options.remoteKey.split("/").pop()}`;
86974
+ const stagingName = options.remoteKey.replace(/^releases\//, "").replace(/\//g, "-");
86975
+ const remotePath = `/var/ts-cloud/staging/${stagingName}`;
86975
86976
  for (const target of targets) {
86976
86977
  if (!target.publicIp) {
86977
86978
  throw new Error(`Target ${target.id} has no public IP for SCP upload`);