@stacksjs/ts-cloud 0.2.24 → 0.2.26

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.
@@ -9,6 +9,13 @@ export interface UbuntuBootstrapOptions {
9
9
  systemPackages?: string[];
10
10
  database?: 'sqlite' | 'mysql' | 'postgres';
11
11
  caddyfile?: string;
12
+ /**
13
+ * Shell commands that install + start the rpx reverse-proxy gateway, built by
14
+ * {@link import('../shared/rpx-gateway').buildRpxProvisionScript}. Appended
15
+ * after the runtime is installed so `bun add -g @stacksjs/rpx` works. Mutually
16
+ * exclusive with `caddyfile` (the box runs one gateway).
17
+ */
18
+ rpxProvision?: string[];
12
19
  }
13
20
  export declare function generateUbuntuAppCloudInit(options?: UbuntuBootstrapOptions): string;
14
21
  /**
@@ -4,4 +4,8 @@ export { HetznerDriver } from './hetzner/driver';
4
4
  export { HetznerClient, resolveHetznerApiToken } from './hetzner/client';
5
5
  export { generateUbuntuAppCloudInit, wrapCloudInitUserData } from './hetzner/cloud-init';
6
6
  export { buildAwsArtifactFetch, buildLocalArtifactFetch, buildSiteDeployScript, buildStaticSiteDeployScript, resolveExecStart, } from './shared/deploy-script';
7
- export { deployAllComputeSites, deploySiteRelease } from './shared/compute-deploy';
7
+ export { deployAllComputeSites, deploySiteRelease, reloadRpxGateway } from './shared/compute-deploy';
8
+ export { buildRpxConfig, buildRpxProvisionScript, deriveRouteId, normalizeRoutePath, renderRpxLauncher, DEFAULT_RPX_CERTS_DIR, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, } from './shared/rpx-gateway';
9
+ export type { BuildRpxConfigOptions, BuildRpxProvisionOptions, RpxGatewayConfig, RpxRoute, } from './shared/rpx-gateway';
10
+ export { buildCloudFrontOriginConfig, MANAGED_CACHE_POLICY_DISABLED, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, } from './shared/cloudfront-origin';
11
+ export type { BuildCloudFrontOriginOptions, OriginFrontedBehavior, } from './shared/cloudfront-origin';
@@ -0,0 +1,61 @@
1
+ /**
2
+ * CloudFront-in-front-of-a-custom-origin distribution config.
3
+ *
4
+ * When a self-hosted gateway (rpx on a Hetzner box) is the origin behind
5
+ * CloudFront, the distribution needs a very specific shape — and several
6
+ * non-obvious settings will silently break it if wrong. This builder encodes
7
+ * the working configuration so it can't regress:
8
+ *
9
+ * - **One custom origin**, the dedicated origin host (e.g. `origin.example.com`),
10
+ * HTTPS-only. It must NOT be a public alias (that loops) and can't be a bare IP.
11
+ * - **Host forwarding** via the AWS-managed `AllViewer` origin-request policy, so
12
+ * the box receives `Host: <alias>` and routes by it (not by the origin host).
13
+ * - **No `DefaultRootObject`.** Setting it to `index.html` makes CloudFront fetch
14
+ * `/index.html`, which a gateway with clean-URLs 301-redirects back to `/` — an
15
+ * infinite loop. Leave it empty and let the origin serve `/`.
16
+ * - **No CloudFront Functions / Lambda@Edge.** S3-era URL-rewrite functions fight
17
+ * a gateway that already does its own path-mounting + clean URLs (→ 301 loops).
18
+ * - **Dynamic paths** (e.g. `/api/*`) use the managed `CachingDisabled` policy and
19
+ * allow all HTTP methods; **static paths** use `CachingOptimized`.
20
+ * - **Origin lockdown header** (optional): a secret injected on the origin hop,
21
+ * paired with rpx `createOriginGuard`, so the publicly-resolvable origin can't
22
+ * be used to bypass the CDN.
23
+ *
24
+ * The result is a complete `DistributionConfig` suitable for CloudFront
25
+ * `CreateDistribution` or `UpdateDistribution`.
26
+ */
27
+ /** AWS-managed cache/origin-request policy IDs (identical across all accounts). */
28
+ export declare const MANAGED_CACHE_POLICY_OPTIMIZED = "658327ea-f89d-4fab-a63d-7e88639e58f6";
29
+ export declare const MANAGED_CACHE_POLICY_DISABLED = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad";
30
+ export declare const MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER = "216adef6-5c7f-47e4-b989-5492eafa07d3";
31
+ export interface OriginFrontedBehavior {
32
+ /** Path pattern this behavior owns, e.g. `/api/*`, `/docs`, `/docs/*`. */
33
+ pathPattern: string;
34
+ /** `dynamic` → no caching + all methods (apps/APIs); `static` → cached (CachingOptimized). */
35
+ kind: 'dynamic' | 'static';
36
+ }
37
+ export interface BuildCloudFrontOriginOptions {
38
+ /** Public aliases (CNAMEs) on the distribution, e.g. `['example.com', 'www.example.com']`. */
39
+ aliases: string[];
40
+ /** Dedicated origin hostname CloudFront connects to. MUST resolve to the box, MUST NOT be an alias. */
41
+ originDomain: string;
42
+ /** ACM certificate ARN (us-east-1) covering {@link aliases}. */
43
+ viewerCertificateArn: string;
44
+ /** Per-path behaviors layered over the default. The default behavior (`/`) is always `static`. */
45
+ behaviors?: OriginFrontedBehavior[];
46
+ /** Secret injected on the origin hop (paired with rpx `createOriginGuard`). Omit to leave the origin open. */
47
+ originSecret?: string;
48
+ /** Header carrying {@link originSecret}. @default 'X-Origin-Verify' */
49
+ originSecretHeader?: string;
50
+ /** Stable id used to deterministically derive `CallerReference`. @default originDomain */
51
+ callerReference?: string;
52
+ /** Distribution comment. */
53
+ comment?: string;
54
+ /** `PriceClass_All` | `PriceClass_200` | `PriceClass_100`. @default 'PriceClass_All' */
55
+ priceClass?: string;
56
+ }
57
+ /**
58
+ * Build a complete CloudFront `DistributionConfig` for a self-hosted origin.
59
+ * See the module docblock for the rationale behind each fixed setting.
60
+ */
61
+ export declare function buildCloudFrontOriginConfig(options: BuildCloudFrontOriginOptions): Record<string, any>;
@@ -25,3 +25,10 @@ export interface DeployAllSitesOptions {
25
25
  * without `start`, shipped to `/var/www/<site>`). Bucket sites are skipped.
26
26
  */
27
27
  export declare function deployAllComputeSites(options: DeployAllSitesOptions): Promise<boolean>;
28
+ /**
29
+ * Regenerate the rpx gateway config from the sites model and (re)start the
30
+ * gateway on the compute targets. No-op (returns `true`) unless
31
+ * `compute.proxy.engine === 'rpx'`. Re-runnable: the provision script writes the
32
+ * launcher + unit and `systemctl restart`s, which reloads the new routes.
33
+ */
34
+ export declare function reloadRpxGateway(options: DeployAllSitesOptions): Promise<boolean>;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Generate the rpx reverse-proxy gateway config + provisioning from the `sites`
3
+ * model.
4
+ *
5
+ * ts-cloud's per-site deploy model resolves each site to one of three kinds
6
+ * (see {@link import('../../deploy/site-target').resolveSiteKind}):
7
+ * - `server-app` — a dynamic app running on a port (systemd service);
8
+ * - `server-static` — a static site shipped to `/var/www/<name>`;
9
+ * - `bucket` — object storage + CDN (not on the box; ignored here).
10
+ *
11
+ * The rpx gateway fronts :80/:443 on the box and routes by host **and path**:
12
+ * several sites can share one `domain` on different `path`s (e.g.
13
+ * `stacksjs.com/api/*` → app on :3000, `stacksjs.com/docs*` → `/var/www/docs`,
14
+ * `stacksjs.com/` → `/var/www/public`). This module maps the sites model to the
15
+ * rpx `proxies` array so `buddy deploy` can ship the config + wire the gateway.
16
+ *
17
+ * It replaces the old Caddyfile generation — pantry/stacks use rpx (their own
18
+ * tooling), so the gateway is rpx, not Caddy.
19
+ */
20
+ import type { ComputeProxyConfig, SiteConfig } from '@ts-cloud/core';
21
+ /** Default directory on the box that holds real per-domain TLS certs. */
22
+ export declare const DEFAULT_RPX_CERTS_DIR = "/etc/rpx/certs";
23
+ /** A single rpx proxy route, mapped from one site. */
24
+ export interface RpxRoute {
25
+ /** Public host this route is served under (the site's `domain`). */
26
+ to: string;
27
+ /** Path prefix within the host this route owns (e.g. `/api`). Omitted = `/`. */
28
+ path?: string;
29
+ /** Upstream `host:port` for a `server-app` route. */
30
+ from?: string;
31
+ /** Absolute directory served for a `server-static` route (`/var/www/<name>`). */
32
+ static?: string;
33
+ /** Strip `.html` and resolve clean URLs (set for static sites). */
34
+ cleanUrls?: boolean;
35
+ /** SPA fallback for static sites. */
36
+ spa?: boolean;
37
+ /** Stable id used when rpx registers the route. Derived from `to`+`path`. */
38
+ id: string;
39
+ }
40
+ /** The rpx daemon/proxy config produced from a sites model. */
41
+ export interface RpxGatewayConfig {
42
+ /** Multi-proxy route list (host + path keyed). */
43
+ proxies: RpxRoute[];
44
+ /**
45
+ * Production per-domain SNI certs: rpx serves a real PEM per server name from
46
+ * this directory (`<domain>.crt` / `<domain>.key`).
47
+ */
48
+ productionCerts: {
49
+ certsDir: string;
50
+ };
51
+ /**
52
+ * On-demand TLS (opt-in): lazily issue a real cert for an approved host the
53
+ * first time it's needed. The site domains form the allowlist.
54
+ */
55
+ onDemandTls?: {
56
+ enabled: true;
57
+ allowedSuffixes: string[];
58
+ email?: string;
59
+ certsDir: string;
60
+ };
61
+ /** Always `true` — the gateway terminates TLS on the box. */
62
+ https: true;
63
+ /** Never touch `/etc/hosts` on a real server with real DNS. */
64
+ hostsManagement: false;
65
+ /** Don't remove certs/hosts on exit. */
66
+ cleanup: {
67
+ hosts: false;
68
+ certs: false;
69
+ };
70
+ /**
71
+ * Origin lockdown (from `proxy.cdn` when a `secret` is set): rpx rejects
72
+ * direct hits to the CDN-fronted hosts that lack the shared-secret header.
73
+ */
74
+ originGuard?: {
75
+ header: string;
76
+ value: string;
77
+ hosts: string[];
78
+ };
79
+ }
80
+ export interface BuildRpxConfigOptions {
81
+ /** Proxy config from `infrastructure.compute.proxy`. */
82
+ proxy: ComputeProxyConfig;
83
+ /** Directory static sites are shipped to. @default '/var/www' */
84
+ wwwRoot?: string;
85
+ }
86
+ /**
87
+ * Normalize a path prefix to a leading-slash, no-trailing-slash form, or
88
+ * `undefined` for the host default. Mirrors rpx's `normalizePathPrefix`.
89
+ */
90
+ export declare function normalizeRoutePath(path: string | undefined): string | undefined;
91
+ /** Derive a stable, filesystem/registry-safe id from a host (+ optional path). */
92
+ export declare function deriveRouteId(to: string, path?: string): string;
93
+ /**
94
+ * Map the sites model to an rpx gateway config. Each non-bucket site with a
95
+ * `domain` becomes a route:
96
+ * - `server-app` → `{ to: domain, path, from: 'localhost:<port>' }`
97
+ * - `server-static` → `{ to: domain, path, static: '<wwwRoot>/<name>' }`
98
+ *
99
+ * Routes are grouped by domain so rpx's path-based routing can serve an app +
100
+ * several static dirs under one host. Bucket sites and sites without a `domain`
101
+ * (or a `server-app` without a `port`) are skipped.
102
+ */
103
+ export declare function buildRpxConfig(sites: Record<string, SiteConfig | undefined>, options: BuildRpxConfigOptions): RpxGatewayConfig;
104
+ /**
105
+ * Render the rpx gateway config as a self-contained launcher TS module. The
106
+ * systemd unit runs `bun <file>`, which imports `startProxies` from the
107
+ * globally-installed `@stacksjs/rpx` and starts the gateway with the generated
108
+ * options. We ship a runnable launcher (not a bare config) because rpx's CLI
109
+ * resolves its own config from its install dir, not an arbitrary path.
110
+ */
111
+ export declare function renderRpxLauncher(config: RpxGatewayConfig): string;
112
+ /** Default install location for the gateway launcher + config on the box. */
113
+ export declare const RPX_DIR = "/etc/rpx";
114
+ export declare const RPX_LAUNCHER_PATH = "/etc/rpx/gateway.ts";
115
+ export declare const RPX_SERVICE_NAME = "rpx-gateway.service";
116
+ export interface BuildRpxProvisionOptions {
117
+ config: RpxGatewayConfig;
118
+ proxy: ComputeProxyConfig;
119
+ /** Absolute path to the `bun` binary on the box. @default '/usr/local/bin/bun' */
120
+ bunBin?: string;
121
+ }
122
+ /**
123
+ * Build the idempotent, re-runnable shell commands that install rpx as the
124
+ * gateway, write the generated launcher + ensure the certs dir, install the
125
+ * systemd unit, and enable + (re)start it on :80/:443.
126
+ *
127
+ * Safe to run at first boot (cloud-init) and again on every deploy — the unit
128
+ * write + `systemctl restart` reloads the regenerated routes so new
129
+ * server-app/server-static sites appear in the gateway automatically.
130
+ */
131
+ export declare function buildRpxProvisionScript(options: BuildRpxProvisionOptions): string[];
package/dist/index.js CHANGED
@@ -81600,7 +81600,8 @@ function generateUbuntuAppCloudInit(options = {}) {
81600
81600
  runtimeVersion = "latest",
81601
81601
  systemPackages = [],
81602
81602
  database,
81603
- caddyfile
81603
+ caddyfile,
81604
+ rpxProvision
81604
81605
  } = options;
81605
81606
  const packages = new Set(systemPackages);
81606
81607
  if (database === "sqlite")
@@ -81692,6 +81693,13 @@ CADDY_CONFIG_EOF
81692
81693
  systemctl daemon-reload
81693
81694
  systemctl enable caddy
81694
81695
  systemctl start caddy
81696
+ `;
81697
+ }
81698
+ if (rpxProvision && rpxProvision.length > 0) {
81699
+ const body = rpxProvision[0] === "set -euo pipefail" ? rpxProvision.slice(1) : rpxProvision;
81700
+ script += `
81701
+ ${body.join(`
81702
+ `)}
81695
81703
  `;
81696
81704
  }
81697
81705
  script += `
@@ -81716,6 +81724,137 @@ runcmd:
81716
81724
  `;
81717
81725
  }
81718
81726
 
81727
+ // src/drivers/shared/rpx-gateway.ts
81728
+ var DEFAULT_RPX_CERTS_DIR = "/etc/rpx/certs";
81729
+ function normalizeRoutePath(path) {
81730
+ if (!path || path === "/")
81731
+ return;
81732
+ let p = `/${path}`.replace(/\/+/g, "/").replace(/\/+$/, "");
81733
+ if (!p.startsWith("/"))
81734
+ p = `/${p}`;
81735
+ return p === "" || p === "/" ? undefined : p;
81736
+ }
81737
+ function deriveRouteId(to, path) {
81738
+ const base = path ? `${to}${path}` : to;
81739
+ const cleaned = base.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
81740
+ return cleaned.length > 0 ? cleaned : "rpx";
81741
+ }
81742
+ function buildRpxConfig(sites, options) {
81743
+ const wwwRoot = (options.wwwRoot ?? "/var/www").replace(/\/+$/, "");
81744
+ const certsDir = options.proxy.certsDir ?? DEFAULT_RPX_CERTS_DIR;
81745
+ const proxies = [];
81746
+ const domains = new Set;
81747
+ for (const [name, site] of Object.entries(sites)) {
81748
+ if (!site || !site.domain)
81749
+ continue;
81750
+ const kind = resolveSiteKind(site);
81751
+ if (kind === "bucket")
81752
+ continue;
81753
+ const path = normalizeRoutePath(site.path);
81754
+ const id = deriveRouteId(site.domain, path);
81755
+ if (kind === "server-app") {
81756
+ if (typeof site.port !== "number")
81757
+ continue;
81758
+ proxies.push({ to: site.domain, path, from: `localhost:${site.port}`, id });
81759
+ } else {
81760
+ proxies.push({
81761
+ to: site.domain,
81762
+ path,
81763
+ static: `${wwwRoot}/${name}`,
81764
+ cleanUrls: site.pathRewriteStyle !== "flat",
81765
+ spa: site.spa ?? false,
81766
+ id
81767
+ });
81768
+ }
81769
+ domains.add(site.domain);
81770
+ }
81771
+ proxies.sort((a, b) => {
81772
+ if (a.to !== b.to)
81773
+ return a.to.localeCompare(b.to);
81774
+ return (b.path?.length ?? 0) - (a.path?.length ?? 0);
81775
+ });
81776
+ const config6 = {
81777
+ proxies,
81778
+ productionCerts: { certsDir },
81779
+ https: true,
81780
+ hostsManagement: false,
81781
+ cleanup: { hosts: false, certs: false }
81782
+ };
81783
+ if (options.proxy.onDemandTls && domains.size > 0) {
81784
+ config6.onDemandTls = {
81785
+ enabled: true,
81786
+ allowedSuffixes: [...domains],
81787
+ email: options.proxy.onDemandTlsEmail,
81788
+ certsDir
81789
+ };
81790
+ }
81791
+ const cdn = options.proxy.cdn;
81792
+ if (cdn?.secret && cdn.frontedHosts.length > 0) {
81793
+ config6.originGuard = {
81794
+ header: cdn.secretHeader ?? "X-Origin-Verify",
81795
+ value: cdn.secret,
81796
+ hosts: cdn.frontedHosts
81797
+ };
81798
+ }
81799
+ return config6;
81800
+ }
81801
+ function renderRpxLauncher(config6) {
81802
+ const json = JSON.stringify(config6, null, 2);
81803
+ return `// Generated by ts-cloud — rpx reverse-proxy gateway.
81804
+ // Routes are derived from the \`sites\` model on every \`buddy deploy\`.
81805
+ import { startProxies } from '@stacksjs/rpx'
81806
+
81807
+ const config = ${json} as const
81808
+
81809
+ await startProxies(config as any)
81810
+ `;
81811
+ }
81812
+ var RPX_DIR = "/etc/rpx";
81813
+ var RPX_LAUNCHER_PATH = "/etc/rpx/gateway.ts";
81814
+ var RPX_SERVICE_NAME = "rpx-gateway.service";
81815
+ function writeFileHeredoc(path, content, delimiter) {
81816
+ return [
81817
+ `cat > ${path} <<'${delimiter}'`,
81818
+ content,
81819
+ delimiter
81820
+ ];
81821
+ }
81822
+ function buildRpxProvisionScript(options) {
81823
+ const { config: config6, proxy } = options;
81824
+ const bunBin = options.bunBin ?? "/usr/local/bin/bun";
81825
+ const version2 = proxy.version ?? "latest";
81826
+ const certsDir = config6.productionCerts.certsDir;
81827
+ const launcher = renderRpxLauncher(config6);
81828
+ return [
81829
+ "set -euo pipefail",
81830
+ `mkdir -p ${RPX_DIR} ${certsDir}`,
81831
+ `${bunBin} add -g @stacksjs/rpx@${version2}`,
81832
+ ...writeFileHeredoc(RPX_LAUNCHER_PATH, launcher, "TS_CLOUD_RPX_EOF"),
81833
+ ...writeFileHeredoc(`/etc/systemd/system/${RPX_SERVICE_NAME}`, [
81834
+ "[Unit]",
81835
+ "Description=rpx reverse-proxy gateway (managed by ts-cloud)",
81836
+ "After=network.target network-online.target",
81837
+ "Wants=network-online.target",
81838
+ "",
81839
+ "[Service]",
81840
+ "Type=simple",
81841
+ `ExecStart=${bunBin} ${RPX_LAUNCHER_PATH}`,
81842
+ `Environment=BUN_INSTALL=/root/.bun`,
81843
+ "Restart=always",
81844
+ "RestartSec=5",
81845
+ "LimitNOFILE=1048576",
81846
+ "AmbientCapabilities=CAP_NET_BIND_SERVICE",
81847
+ "",
81848
+ "[Install]",
81849
+ "WantedBy=multi-user.target"
81850
+ ].join(`
81851
+ `), "TS_CLOUD_RPX_UNIT_EOF"),
81852
+ "systemctl daemon-reload",
81853
+ `systemctl enable ${RPX_SERVICE_NAME}`,
81854
+ `systemctl restart ${RPX_SERVICE_NAME}`
81855
+ ];
81856
+ }
81857
+
81719
81858
  // src/drivers/hetzner/firewall-rules.ts
81720
81859
  function buildHetznerFirewallRules(config6) {
81721
81860
  const openPorts = new Set([80, 443, ...config6.sitePorts]);
@@ -81852,11 +81991,17 @@ class HetznerDriver {
81852
81991
  }
81853
81992
  const sites = config6.sites || {};
81854
81993
  const sitePorts = this.collectUpstreamPorts(sites);
81994
+ const rpxProvision = compute.proxy?.engine === "rpx" ? buildRpxProvisionScript({
81995
+ proxy: compute.proxy,
81996
+ config: buildRpxConfig(sites, { proxy: compute.proxy }),
81997
+ bunBin: compute.runtime === "node" || compute.runtime === "deno" ? undefined : "/usr/local/bin/bun"
81998
+ }) : undefined;
81855
81999
  const bootstrap = generateUbuntuAppCloudInit({
81856
82000
  runtime: compute.runtime || "bun",
81857
82001
  runtimeVersion: compute.runtimeVersion || "latest",
81858
82002
  systemPackages: compute.systemPackages,
81859
- database: config6.infrastructure?.database
82003
+ database: config6.infrastructure?.database,
82004
+ rpxProvision
81860
82005
  });
81861
82006
  const userData = wrapCloudInitUserData(bootstrap);
81862
82007
  const serverType = resolveHetznerServerType(compute.size);
@@ -82344,6 +82489,48 @@ async function deployAllComputeSites(options) {
82344
82489
  }
82345
82490
  }
82346
82491
  }
82492
+ const reloaded = await reloadRpxGateway(options);
82493
+ if (!reloaded)
82494
+ return false;
82495
+ return true;
82496
+ }
82497
+ async function reloadRpxGateway(options) {
82498
+ const { config: config6, environment, driver, logger: logger4 = noopLogger } = options;
82499
+ const proxy = config6.infrastructure?.compute?.proxy;
82500
+ if (proxy?.engine !== "rpx")
82501
+ return true;
82502
+ const sites = config6.sites || {};
82503
+ const rpxConfig = buildRpxConfig(sites, { proxy });
82504
+ if (rpxConfig.proxies.length === 0) {
82505
+ logger4.warn("rpx gateway: no server sites with a domain to route — skipping gateway reload.");
82506
+ return true;
82507
+ }
82508
+ const targets = await driver.findComputeTargets({
82509
+ slug: config6.project.slug,
82510
+ environment,
82511
+ role: "app"
82512
+ });
82513
+ if (targets.length === 0) {
82514
+ logger4.warn("rpx gateway: no compute targets found — skipping gateway reload.");
82515
+ return true;
82516
+ }
82517
+ logger4.step(`Reloading rpx gateway with ${rpxConfig.proxies.length} route(s)...`);
82518
+ const script = buildRpxProvisionScript({ proxy, config: rpxConfig });
82519
+ const result = await driver.runRemoteDeploy({
82520
+ targets,
82521
+ commands: script,
82522
+ comment: `ts-cloud rpx gateway reload ${config6.project.slug}`,
82523
+ tags: {
82524
+ Project: config6.project.slug,
82525
+ Environment: environment,
82526
+ Role: "app"
82527
+ }
82528
+ });
82529
+ if (!result.success) {
82530
+ logger4.error(`rpx gateway reload failed: ${result.error || "unknown error"}`);
82531
+ return false;
82532
+ }
82533
+ logger4.success(`rpx gateway reloaded on ${result.instanceCount} target(s)`);
82347
82534
  return true;
82348
82535
  }
82349
82536
  // src/index.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/ts-cloud",
3
3
  "type": "module",
4
- "version": "0.2.24",
4
+ "version": "0.2.26",
5
5
  "description": "A lightweight, performant infrastructure-as-code library and CLI for deploying both server-based (EC2) and serverless applications.",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
7
7
  "license": "MIT",
@@ -89,8 +89,8 @@
89
89
  "test": "bun test"
90
90
  },
91
91
  "dependencies": {
92
- "@ts-cloud/aws-types": "0.2.24",
93
- "@ts-cloud/core": "0.2.24",
92
+ "@ts-cloud/aws-types": "0.2.26",
93
+ "@ts-cloud/core": "0.2.26",
94
94
  "@stacksjs/ts-xml": "^0.1.0"
95
95
  },
96
96
  "devDependencies": {