@stacksjs/ts-cloud 0.7.15 → 0.7.17

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.
@@ -25,8 +25,9 @@ import {
25
25
  releasePaths,
26
26
  resolveSiteFramework,
27
27
  resolveSiteKind,
28
- resolveUiSource
29
- } from "./chunk-6agm101w.js";
28
+ resolveUiSource,
29
+ siteInstallBase
30
+ } from "./chunk-qergre5a.js";
30
31
  import {
31
32
  artifactKey,
32
33
  composeServerlessAppTemplate,
@@ -10284,13 +10285,13 @@ function shellSingleQuote(value) {
10284
10285
  function sedReplacement(value) {
10285
10286
  return value.replace(/[\\&/]/g, "\\$&");
10286
10287
  }
10287
- function deployHistoryScript(siteNames) {
10288
+ function deployHistoryScript(siteNames, slug) {
10288
10289
  if (siteNames.length === 0)
10289
10290
  return ["true"];
10290
10291
  return [
10291
10292
  "set +e",
10292
10293
  ...siteNames.map((siteName) => {
10293
- const historyPath = shellSingleQuote(`/var/www/${siteName}/.ts-cloud/deploy-history.log`);
10294
+ const historyPath = shellSingleQuote(`${siteInstallBase(slug, siteName)}/.ts-cloud/deploy-history.log`);
10294
10295
  const prefix = sedReplacement(siteName);
10295
10296
  return `TS_CLOUD_HISTORY=${historyPath}; { [ -f "$TS_CLOUD_HISTORY" ] && tail -n 24 "$TS_CLOUD_HISTORY" | sed "s/^/DEPLOY=${prefix}\\t/"; } || true`;
10296
10297
  }),
@@ -10673,7 +10674,7 @@ function resolveConfigOnlyServerDashboardData(config6, environment) {
10673
10674
  const kind = resolveSiteKind(site);
10674
10675
  return {
10675
10676
  ...s,
10676
- root: kind === "server-static" ? `/var/www/${s.name}` : `/var/www/${s.name}/current`,
10677
+ root: kind === "server-static" ? siteInstallBase(config6.project.slug, s.name) : `${siteInstallBase(config6.project.slug, s.name)}/current`,
10677
10678
  branch: kind === "server-static" ? "build artifact" : "main",
10678
10679
  build: site.build,
10679
10680
  php: site.php ?? site.phpVersion,
@@ -10754,7 +10755,7 @@ async function resolveServerDashboardData(config6, environment) {
10754
10755
  const siteNames = Object.keys(config6.sites ?? {});
10755
10756
  const historyResult = await driver.runRemoteDeploy({
10756
10757
  targets: [targets[0]],
10757
- commands: deployHistoryScript(siteNames),
10758
+ commands: deployHistoryScript(siteNames, config6.project.slug),
10758
10759
  comment: `ts-cloud dashboard:deploy-history ${config6.project.slug}`,
10759
10760
  tags: { Project: config6.project.slug, Environment: environment, Role: "app" }
10760
10761
  });
@@ -11013,7 +11014,7 @@ async function rollbackComputeSite(ctx, options) {
11013
11014
  const targets = await findTargets(ctx);
11014
11015
  if (targets.length === 0)
11015
11016
  return { success: false, error: `No '${ctx.role || "app"}' servers found for ${ctx.slug}/${ctx.environment}.` };
11016
- const appBase = `/var/www/${options.siteName}`;
11017
+ const appBase = siteInstallBase(ctx.slug, options.siteName);
11017
11018
  const paths = releasePaths(appBase, options.to || "unused");
11018
11019
  const commands = [
11019
11020
  "set -uo pipefail",
@@ -11139,8 +11140,8 @@ function serviceCommand(verb, target) {
11139
11140
  function pantryEnvEval() {
11140
11141
  return `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)"`;
11141
11142
  }
11142
- function siteArtisanCommand(site, artisan) {
11143
- const current = `/var/www/${site}/current`;
11143
+ function siteArtisanCommand(slug, site, artisan) {
11144
+ const current = `${siteInstallBase(slug, site)}/current`;
11144
11145
  return [
11145
11146
  "set -uo pipefail",
11146
11147
  `[ -d ${current} ] || { echo "site ${site} has no current release" >&2; exit 1; }`,
@@ -11149,7 +11150,7 @@ function siteArtisanCommand(site, artisan) {
11149
11150
  }
11150
11151
  function workerRestartCommand(framework, slug, site) {
11151
11152
  if (framework === "laravel")
11152
- return siteArtisanCommand(site, "queue:restart");
11153
+ return siteArtisanCommand(slug, site, "queue:restart");
11153
11154
  const pattern = `${slug}-${site}-queue-*.service`;
11154
11155
  return [
11155
11156
  "set -uo pipefail",
@@ -11161,7 +11162,7 @@ function workerRestartCommand(framework, slug, site) {
11161
11162
  }
11162
11163
  function schedulerRunCommand(framework, slug, site) {
11163
11164
  if (framework === "laravel")
11164
- return siteArtisanCommand(site, "schedule:run");
11165
+ return siteArtisanCommand(slug, site, "schedule:run");
11165
11166
  const unit = `${slug}-${site}-scheduler.service`;
11166
11167
  return [
11167
11168
  "set -uo pipefail",
@@ -1659,6 +1659,9 @@ runcmd:
1659
1659
  }
1660
1660
 
1661
1661
  // src/deploy/site-target.ts
1662
+ function siteInstallBase(slug, siteName) {
1663
+ return `/var/www/${slug}-${siteName}`;
1664
+ }
1662
1665
  var PHP_SITE_TYPES = new Set([
1663
1666
  "laravel",
1664
1667
  "php",
@@ -1815,6 +1818,7 @@ function resolveServerAppFrom(port, appBoxes) {
1815
1818
  }
1816
1819
  function buildRpxConfigInternal(sites, options, appBoxes) {
1817
1820
  const wwwRoot = (options.wwwRoot ?? "/var/www").replace(/\/+$/, "");
1821
+ const installSlug = options.slug ?? "app";
1818
1822
  const certsDir = options.proxy.certsDir ?? DEFAULT_RPX_CERTS_DIR;
1819
1823
  const loadBalancer = options.proxy.loadBalancer;
1820
1824
  const proxies = [];
@@ -1849,7 +1853,7 @@ function buildRpxConfigInternal(sites, options, appBoxes) {
1849
1853
  proxies.push({
1850
1854
  to: site.domain,
1851
1855
  path,
1852
- static: `${wwwRoot}/${name}/current`,
1856
+ static: `${wwwRoot}/${installSlug}-${name}/current`,
1853
1857
  cleanUrls: site.pathRewriteStyle !== "flat",
1854
1858
  spa: site.spa ?? false,
1855
1859
  ...auth ? { auth } : {},
@@ -2302,7 +2306,7 @@ class HetznerDriver {
2302
2306
  const sitePorts = this.collectUpstreamPorts(sites);
2303
2307
  const rpxProvision = compute.proxy?.engine === "rpx" ? buildRpxProvisionScript({
2304
2308
  proxy: compute.proxy,
2305
- config: buildRpxConfig(sites, { proxy: compute.proxy }),
2309
+ config: buildRpxConfig(sites, { proxy: compute.proxy, slug: config.project.slug }),
2306
2310
  slug: config.project.slug,
2307
2311
  bunBin: compute.runtime === "node" || compute.runtime === "deno" ? undefined : "/usr/local/bin/bun"
2308
2312
  }) : undefined;
@@ -2651,7 +2655,7 @@ class HetznerDriver {
2651
2655
  const rpxProxy = compute.proxy?.engine === "rpx" ? compute.proxy : { engine: "rpx" };
2652
2656
  const lbRpxProvision = buildRpxProvisionScript({
2653
2657
  proxy: rpxProxy,
2654
- config: buildRpxLbConfig(sites, appBoxes, { proxy: rpxProxy }),
2658
+ config: buildRpxLbConfig(sites, appBoxes, { proxy: rpxProxy, slug }),
2655
2659
  slug,
2656
2660
  bunBin: appProvisionScripts.runtime === "node" || appProvisionScripts.runtime === "deno" ? undefined : "/usr/local/bin/bun"
2657
2661
  });
@@ -3015,11 +3019,20 @@ ${out}`);
3015
3019
  "-o",
3016
3020
  "LogLevel=ERROR"
3017
3021
  ];
3022
+ static SSH_KEEPALIVE_OPTS = [
3023
+ "-o",
3024
+ "ConnectTimeout=30",
3025
+ "-o",
3026
+ "ServerAliveInterval=15",
3027
+ "-o",
3028
+ "ServerAliveCountMax=4"
3029
+ ];
3018
3030
  sshBaseArgs(host, extra = []) {
3019
3031
  return [
3020
3032
  "-i",
3021
3033
  this.sshPrivateKeyPath,
3022
3034
  ...HetznerDriver.SSH_HOST_KEY_OPTS,
3035
+ ...HetznerDriver.SSH_KEEPALIVE_OPTS,
3023
3036
  "-o",
3024
3037
  "BatchMode=yes",
3025
3038
  ...extra,
@@ -3027,16 +3040,27 @@ ${out}`);
3027
3040
  ];
3028
3041
  }
3029
3042
  scpToHost(host, localPath, remotePath) {
3030
- execSync([
3043
+ const cmd = [
3031
3044
  "scp",
3032
3045
  "-i",
3033
3046
  this.sshPrivateKeyPath,
3034
3047
  ...HetznerDriver.SSH_HOST_KEY_OPTS,
3048
+ ...HetznerDriver.SSH_KEEPALIVE_OPTS,
3035
3049
  "-o",
3036
3050
  "BatchMode=yes",
3037
3051
  localPath,
3038
3052
  `${this.sshUser}@${host}:${remotePath}`
3039
- ].map((arg) => `"${arg.replace(/"/g, "\\\"")}"`).join(" "), { stdio: "pipe", maxBuffer: SSH_MAX_BUFFER });
3053
+ ].map((arg) => `"${arg.replace(/"/g, "\\\"")}"`).join(" ");
3054
+ const attempts = 3;
3055
+ for (let attempt = 1;attempt <= attempts; attempt++) {
3056
+ try {
3057
+ execSync(cmd, { stdio: "pipe", maxBuffer: SSH_MAX_BUFFER });
3058
+ return;
3059
+ } catch (error) {
3060
+ if (attempt === attempts)
3061
+ throw new Error(formatSshFailure(error));
3062
+ }
3063
+ }
3040
3064
  }
3041
3065
  sshExec(host, script) {
3042
3066
  const escaped = script.replace(/'/g, `'\\''`);
@@ -4384,26 +4408,26 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
4384
4408
  if (isPhpSite(site)) {
4385
4409
  const compute2 = config.infrastructure?.compute;
4386
4410
  const phpVersion = site.phpVersion ?? compute2?.php?.default ?? compute2?.php?.versions?.[0];
4387
- const appBase = `/var/www/${siteName}`;
4411
+ const appBase2 = siteInstallBase(slug, siteName);
4388
4412
  const dbEnv = outputs.servicesPrivateIp ? buildFleetServicesEnv(outputs.servicesPrivateIp, config.infrastructure?.appDatabase) : buildManagedDbEnv(config.infrastructure?.appDatabase);
4389
4413
  const siteWithEnv = Object.keys(dbEnv).length > 0 ? { ...site, env: { ...dbEnv, ...site.env || {} } } : site;
4390
4414
  const deployScript = buildLaravelDeployScript({
4391
4415
  siteName,
4392
4416
  site: siteWithEnv,
4393
4417
  releaseId: sha,
4394
- appBase,
4418
+ appBase: appBase2,
4395
4419
  defaultPhpVersion: phpVersion
4396
4420
  });
4397
4421
  const useNginx = compute2?.webServer !== "rpx";
4398
4422
  const sslProvider = resolveSslProvider(site);
4399
4423
  const customCert = sslProvider === "custom" && site.ssl?.certPath && site.ssl?.keyPath ? { certPath: site.ssl.certPath, keyPath: site.ssl.keyPath } : undefined;
4400
- const poolScript = site.isolation ? buildPhpFpmPoolScript({ siteName, appBase }) : [];
4424
+ const poolScript = site.isolation ? buildPhpFpmPoolScript({ siteName, appBase: appBase2 }) : [];
4401
4425
  const vhostScript = useNginx ? buildNginxVhostScript({
4402
4426
  siteName,
4403
4427
  domain: site.domain || siteName,
4404
4428
  aliases: site.aliases,
4405
4429
  type: site.type,
4406
- appDir: `${appBase}/current`,
4430
+ appDir: `${appBase2}/current`,
4407
4431
  webDirectory: site.webDirectory,
4408
4432
  phpVersion,
4409
4433
  fastcgiPass: site.isolation ? phpFpmPoolListen(siteName) : undefined,
@@ -4417,12 +4441,12 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
4417
4441
  security: site.security
4418
4442
  }) : [];
4419
4443
  const sslScript = useNginx ? buildSslScript(site) : [];
4420
- const servicesScript2 = siteHasServices(site) ? buildSiteServicesScript({ slug, siteName, site, phpVersion, appBase }) : [];
4444
+ const servicesScript2 = siteHasServices(site) ? buildSiteServicesScript({ slug, siteName, site, phpVersion, appBase: appBase2 }) : [];
4421
4445
  const healthCheckScript = useNginx ? buildHealthCheckScript(site) : [];
4422
4446
  logger.step(`Deploying PHP site '${siteName}' to ${targets.length} target(s)...`);
4423
4447
  const phpResult = await driver.runRemoteDeploy({
4424
4448
  targets,
4425
- commands: [...buildSiteOwnerGuard(appBase, slug), ...deployScript, ...poolScript, ...vhostScript, ...sslScript, ...servicesScript2, ...healthCheckScript],
4449
+ commands: [...buildSiteOwnerGuard(appBase2, slug), ...deployScript, ...poolScript, ...vhostScript, ...sslScript, ...servicesScript2, ...healthCheckScript],
4426
4450
  comment: `ts-cloud deploy ${slug}/${siteName}@${sha}`,
4427
4451
  tags: { Project: slug, Environment: environment, Role: "app" }
4428
4452
  });
@@ -4455,14 +4479,17 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
4455
4479
  });
4456
4480
  const artifactFetch = driver.name === "aws" ? buildAwsArtifactFetch(outputs.deployBucketName, remoteKey, config.project.region || "us-east-1", siteName) : buildLocalArtifactFetch(uploadResult.artifactRef, siteName);
4457
4481
  const kind = resolveSiteKind(site);
4482
+ const appBase = siteInstallBase(slug, siteName);
4458
4483
  const baseScript = kind === "server-static" ? buildStaticSiteDeployScript({
4459
4484
  siteName,
4485
+ appDir: appBase,
4460
4486
  artifactFetch,
4461
4487
  releaseId: sha,
4462
4488
  preStartCommands: site.preStart
4463
4489
  }) : buildSiteDeployScript({
4464
4490
  siteName,
4465
4491
  slug,
4492
+ appDir: appBase,
4466
4493
  artifactFetch,
4467
4494
  releaseId: sha,
4468
4495
  execStart: resolveExecStart(site.start, runtime),
@@ -4479,7 +4506,7 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
4479
4506
  domain: site.domain,
4480
4507
  aliases: site.aliases,
4481
4508
  type: site.type === "spa" ? "spa" : "static",
4482
- appDir: `/var/www/${siteName}/current`,
4509
+ appDir: `${appBase}/current`,
4483
4510
  webDirectory: "",
4484
4511
  redirects: site.redirects,
4485
4512
  auth: site.auth && site.auth.enabled !== false && site.auth.password ? { username: site.auth.username || "admin", password: site.auth.password, realm: site.auth.realm } : undefined,
@@ -4490,14 +4517,14 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
4490
4517
  security: site.security
4491
4518
  }) : [];
4492
4519
  const staticSsl = wantsNginxStatic ? buildSslScript(site) : [];
4493
- const servicesScript = siteHasServices(site) ? buildSiteServicesScript({ slug, siteName, site, appBase: `/var/www/${siteName}` }) : [];
4520
+ const servicesScript = siteHasServices(site) ? buildSiteServicesScript({ slug, siteName, site, appBase }) : [];
4494
4521
  const remoteScript = [
4495
- ...buildDeployHistoryHeader(`/var/www/${siteName}`, {
4522
+ ...buildDeployHistoryHeader(appBase, {
4496
4523
  releaseId: sha,
4497
4524
  commit: sha,
4498
4525
  branch: site.branch ?? "main"
4499
4526
  }),
4500
- ...buildSiteOwnerGuard(`/var/www/${siteName}`, slug),
4527
+ ...buildSiteOwnerGuard(appBase, slug),
4501
4528
  ...baseScript,
4502
4529
  ...staticVhost,
4503
4530
  ...staticSsl,
@@ -4620,7 +4647,7 @@ async function reloadRpxGateway(options) {
4620
4647
  if (proxy?.engine !== "rpx")
4621
4648
  return true;
4622
4649
  const sites = routeSource.sites || {};
4623
- const rpxConfig = buildRpxConfig(sites, { proxy });
4650
+ const rpxConfig = buildRpxConfig(sites, { proxy, slug: config.project.slug });
4624
4651
  if (rpxConfig.proxies.length === 0) {
4625
4652
  logger.warn("rpx gateway: no server sites with a domain to route — skipping gateway reload.");
4626
4653
  return true;
@@ -4734,4 +4761,4 @@ function buildCloudFrontOriginConfig(options) {
4734
4761
  CustomErrorResponses: { Quantity: 0 }
4735
4762
  };
4736
4763
  }
4737
- export { isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, PANTRY_PROJECT_DIR, BACKUP_RUNNER_PATH, buildBackupRestoreScript, buildUbuntuBootstrapScript, AwsDriver, HetznerClient, resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, UBUNTU_2404_AMI_PARAM, buildBoxUserData, HetznerBoxProvisioner, AwsBoxProvisioner, createBoxProvisioner, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, buildAwsArtifactFetch, buildLocalArtifactFetch, MANAGEMENT_DASHBOARD_SITE, DASHBOARD_CREDENTIALS_FILE, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
4764
+ export { siteInstallBase, isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, PANTRY_PROJECT_DIR, BACKUP_RUNNER_PATH, buildBackupRestoreScript, buildUbuntuBootstrapScript, AwsDriver, HetznerClient, resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, UBUNTU_2404_AMI_PARAM, buildBoxUserData, HetznerBoxProvisioner, AwsBoxProvisioner, createBoxProvisioner, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, buildAwsArtifactFetch, buildLocalArtifactFetch, MANAGEMENT_DASHBOARD_SITE, DASHBOARD_CREDENTIALS_FILE, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
@@ -12,7 +12,7 @@ import {
12
12
  sanitizeCloudConfig,
13
13
  setMaintenance,
14
14
  startLocalDashboardServer
15
- } from "../chunk-mbpxadga.js";
15
+ } from "../chunk-pr3g0b0t.js";
16
16
  import {
17
17
  buildAndPushServerlessImage
18
18
  } from "../chunk-qgnyzfmx.js";
@@ -28,8 +28,9 @@ import {
28
28
  resolveSiteDeployTarget,
29
29
  resolveSiteKind,
30
30
  resolveUiSource,
31
+ siteInstallBase,
31
32
  validateDeploymentConfig
32
- } from "../chunk-6agm101w.js";
33
+ } from "../chunk-qergre5a.js";
33
34
  import"../chunk-49nmy775.js";
34
35
  import"../chunk-93hjhs78.js";
35
36
  import {
@@ -54,6 +55,7 @@ export {
54
55
  validateDeploymentConfig,
55
56
  uploadStaticFiles,
56
57
  startLocalDashboardServer,
58
+ siteInstallBase,
57
59
  setMaintenance,
58
60
  sanitizeCloudConfig,
59
61
  runRemoteCommand,
@@ -10,6 +10,23 @@ import type { CloudConfig, SiteConfig, SiteDeployTarget } from '@ts-cloud/core';
10
10
  * the gateway answers `domain` with an HTTP redirect.
11
11
  */
12
12
  export type SiteDeployKind = 'bucket' | 'server-app' | 'server-static' | 'server-php' | 'redirect';
13
+ /**
14
+ * On-disk base directory for a site's atomic release tree
15
+ * (`<base>/releases/<sha>` + `<base>/current`).
16
+ *
17
+ * Namespaced by the project **slug** so that on a shared / multi-tenant box
18
+ * (`cloud.attachTo`) two projects — or a tenant and the box owner — can never
19
+ * collide on the same `/var/www/<name>` path. A bare `/var/www/<siteName>`
20
+ * meant that e.g. every project with a `main` site fought over `/var/www/main`,
21
+ * silently overwriting each other's releases and round-robining stale responses
22
+ * on the shared port. This mirrors the systemd unit naming (`<slug>-<siteName>`)
23
+ * so a release directory and the service that runs it share one identity.
24
+ *
25
+ * MUST be the single source of truth for the install path — deploy, rpx routing
26
+ * (server-static), rollback/ops, and dashboard data all derive from it, so they
27
+ * can never diverge.
28
+ */
29
+ export declare function siteInstallBase(slug: string, siteName: string): string;
13
30
  /**
14
31
  * A PHP/Laravel site: deployed to the compute box via git clone into atomic
15
32
  * release directories and served by nginx + php-fpm. Identified by a PHP
@@ -135,6 +135,14 @@ export declare class HetznerDriver implements CloudDriver {
135
135
  * `REMOTE HOST IDENTIFICATION HAS CHANGED`.
136
136
  */
137
137
  private static readonly SSH_HOST_KEY_OPTS;
138
+ /**
139
+ * Keepalive + connect timeout for every ssh/scp. Without these a connection
140
+ * that stalls mid-transfer (a flaky network, an sshd hiccup) hangs the deploy
141
+ * FOREVER — execSync has no timeout, so a dead socket blocks the whole run.
142
+ * ServerAlive probes abort a silent connection after ~60s (15s × 4) so the
143
+ * transfer fails loudly (and, for scp, is retried) instead of wedging.
144
+ */
145
+ private static readonly SSH_KEEPALIVE_OPTS;
138
146
  private sshBaseArgs;
139
147
  private scpToHost;
140
148
  private sshExec;
@@ -48,7 +48,7 @@ import {
48
48
  waitForCloudInit,
49
49
  waitForSsh,
50
50
  wrapCloudInitUserData
51
- } from "../chunk-6agm101w.js";
51
+ } from "../chunk-qergre5a.js";
52
52
  import"../chunk-49nmy775.js";
53
53
  import"../chunk-93hjhs78.js";
54
54
  import"../chunk-qpj3edwz.js";
@@ -138,6 +138,14 @@ export interface BuildRpxConfigOptions {
138
138
  proxy: ComputeProxyConfig;
139
139
  /** Directory static sites are shipped to. @default '/var/www' */
140
140
  wwwRoot?: string;
141
+ /**
142
+ * Project slug — server-static routes are served from the slug-namespaced
143
+ * install dir (`<wwwRoot>/<slug>-<name>/current`), matching where the deploy
144
+ * ships them (see {@link siteInstallBase}). Omitted ⇒ `app` (single-tenant
145
+ * back-compat). MUST be set on a shared box or static routes point at the
146
+ * wrong directory.
147
+ */
148
+ slug?: string;
141
149
  }
142
150
  /**
143
151
  * Normalize a path prefix to a leading-slash, no-trailing-slash form, or
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  sanitizeCloudConfig,
56
56
  setMaintenance,
57
57
  startLocalDashboardServer
58
- } from "./chunk-mbpxadga.js";
58
+ } from "./chunk-pr3g0b0t.js";
59
59
  import {
60
60
  buildAndPushServerlessImage
61
61
  } from "./chunk-qgnyzfmx.js";
@@ -105,7 +105,7 @@ import {
105
105
  waitForCloudInit,
106
106
  waitForSsh,
107
107
  wrapCloudInitUserData
108
- } from "./chunk-6agm101w.js";
108
+ } from "./chunk-qergre5a.js";
109
109
  import {
110
110
  ABTestManager,
111
111
  AI,