@stacksjs/ts-cloud 0.7.9 → 0.7.11

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.
Files changed (44) hide show
  1. package/dist/bin/cli.js +321 -321
  2. package/dist/{chunk-x07dz3sd.js → chunk-1518raqr.js} +6 -5
  3. package/dist/{chunk-pegd7rmf.js → chunk-9ra8b9b4.js} +2 -2
  4. package/dist/{chunk-0wxyppza.js → chunk-m03vddvr.js} +1 -1
  5. package/dist/{chunk-xzn0ntr0.js → chunk-nw5nnffj.js} +112 -28
  6. package/dist/{chunk-c6rgvg1j.js → chunk-vsgkcpqw.js} +62 -29
  7. package/dist/deploy/index.js +4 -4
  8. package/dist/deploy/management-dashboard.d.ts +2 -0
  9. package/dist/drivers/index.js +2 -2
  10. package/dist/drivers/shared/db-provision.test.d.ts +1 -0
  11. package/dist/drivers/shared/deploy-script.d.ts +38 -4
  12. package/dist/drivers/shared/releases.d.ts +10 -2
  13. package/dist/index.js +12 -4
  14. package/dist/ui/index.html +4 -4
  15. package/dist/ui/server/actions.html +4 -4
  16. package/dist/ui/server/activity.html +1 -1
  17. package/dist/ui/server/backups.html +4 -4
  18. package/dist/ui/server/database.html +4 -4
  19. package/dist/ui/server/deployments.html +4 -4
  20. package/dist/ui/server/diagnostics.html +1 -1
  21. package/dist/ui/server/firewall.html +4 -4
  22. package/dist/ui/server/logs.html +4 -4
  23. package/dist/ui/server/metrics.html +1 -1
  24. package/dist/ui/server/security.html +1 -1
  25. package/dist/ui/server/services.html +4 -4
  26. package/dist/ui/server/sites.html +4 -4
  27. package/dist/ui/server/ssh-keys.html +4 -4
  28. package/dist/ui/server/terminal.html +4 -4
  29. package/dist/ui/server/workers.html +4 -4
  30. package/dist/ui/serverless/alarms.html +4 -4
  31. package/dist/ui/serverless/assets.html +4 -4
  32. package/dist/ui/serverless/cost.html +1 -1
  33. package/dist/ui/serverless/data.html +4 -4
  34. package/dist/ui/serverless/deployments.html +4 -4
  35. package/dist/ui/serverless/firewall.html +1 -1
  36. package/dist/ui/serverless/functions.html +4 -4
  37. package/dist/ui/serverless/logs.html +4 -4
  38. package/dist/ui/serverless/metrics.html +1 -1
  39. package/dist/ui/serverless/queues.html +4 -4
  40. package/dist/ui/serverless/scheduler.html +4 -4
  41. package/dist/ui/serverless/secrets.html +4 -4
  42. package/dist/ui/serverless/traces.html +4 -4
  43. package/dist/ui/serverless.html +4 -4
  44. package/package.json +3 -3
@@ -6,7 +6,7 @@ import {
6
6
  step,
7
7
  success,
8
8
  warn
9
- } from "./chunk-0wxyppza.js";
9
+ } from "./chunk-m03vddvr.js";
10
10
  import {
11
11
  CloudWatchLogsClient,
12
12
  CostExplorerClient,
@@ -26,7 +26,7 @@ import {
26
26
  resolveSiteFramework,
27
27
  resolveSiteKind,
28
28
  resolveUiSource
29
- } from "./chunk-xzn0ntr0.js";
29
+ } from "./chunk-nw5nnffj.js";
30
30
  import {
31
31
  artifactKey,
32
32
  composeServerlessAppTemplate,
@@ -40,7 +40,7 @@ import {
40
40
  resolveServerlessArtifactBucketName,
41
41
  resolveServerlessAssetBucketName,
42
42
  resolveServerlessRuntime
43
- } from "./chunk-c6rgvg1j.js";
43
+ } from "./chunk-vsgkcpqw.js";
44
44
  import {
45
45
  deployStaticSiteWithExternalDnsFull
46
46
  } from "./chunk-d7p84vz5.js";
@@ -345,7 +345,7 @@ async function deployServerlessApp(config, environment, opts = {}) {
345
345
  }
346
346
  if (imageMode) {
347
347
  step("Building + pushing container image");
348
- const { buildAndPushServerlessImage } = await import("./chunk-pegd7rmf.js");
348
+ const { buildAndPushServerlessImage } = await import("./chunk-9ra8b9b4.js");
349
349
  const built = await buildAndPushServerlessImage({
350
350
  app,
351
351
  projectRoot,
@@ -8195,6 +8195,7 @@ class Logger3 {
8195
8195
  }
8196
8196
  }
8197
8197
  var logger3 = new Logger3("stacks");
8198
+
8198
8199
  class BunfigError2 extends Error {
8199
8200
  timestamp;
8200
8201
  context;
@@ -11016,7 +11017,7 @@ async function rollbackComputeSite(ctx, options) {
11016
11017
  const paths = releasePaths(appBase, options.to || "unused");
11017
11018
  const commands = [
11018
11019
  "set -uo pipefail",
11019
- ...buildRollbackScript(paths, options.to ? { to: options.to } : {}),
11020
+ ...buildRollbackScript(paths, { ...options.to ? { to: options.to } : {}, unitBase: `${ctx.slug}-${options.siteName}` }),
11020
11021
  `(cd ${PANTRY_PROJECT_DIR} && pantry restart php-fpm) 2>/dev/null || true`,
11021
11022
  `(cd ${paths.current} && eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)" && php artisan queue:restart) 2>/dev/null || true`
11022
11023
  ];
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  buildAndPushServerlessImage
3
- } from "./chunk-0wxyppza.js";
3
+ } from "./chunk-m03vddvr.js";
4
4
  import"./chunk-hsk6fe6x.js";
5
- import"./chunk-c6rgvg1j.js";
5
+ import"./chunk-vsgkcpqw.js";
6
6
  import"./chunk-arsh1g5h.js";
7
7
  import"./chunk-v0bahtg2.js";
8
8
  export {
@@ -8,7 +8,7 @@ import {
8
8
  phpRuntimeLayerAssets,
9
9
  runPhpBuildHooks,
10
10
  sha256
11
- } from "./chunk-c6rgvg1j.js";
11
+ } from "./chunk-vsgkcpqw.js";
12
12
 
13
13
  // src/deploy/serverless-image.ts
14
14
  import { execFileSync } from "node:child_process";
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  deploymentCoexistenceError,
3
3
  hasManagementDashboardSite,
4
+ isManagementDashboardSiteName,
4
5
  resolveCloudProvider,
5
- resolveManagementDashboardSite,
6
+ resolveManagementDashboardSites,
6
7
  resolveProjectStackName
7
- } from "./chunk-c6rgvg1j.js";
8
+ } from "./chunk-vsgkcpqw.js";
8
9
  import {
9
10
  EC2Client,
10
11
  SSMClient
@@ -194,7 +195,8 @@ function buildDatabaseSetupScript(database, services = {}) {
194
195
  function buildManagedDbEnv(database) {
195
196
  if (!database?.name)
196
197
  return {};
197
- const connection = database.engine === "postgres" ? "pgsql" : "mysql";
198
+ const isSingleStore = database.engine === "singlestore";
199
+ const connection = database.engine === "postgres" ? "pgsql" : isSingleStore ? "singlestore" : "mysql";
198
200
  const port = database.port ?? (database.engine === "postgres" ? 5432 : 3306);
199
201
  const env = {
200
202
  DB_CONNECTION: connection,
@@ -206,6 +208,8 @@ function buildManagedDbEnv(database) {
206
208
  env.DB_USERNAME = database.username;
207
209
  if (database.password)
208
210
  env.DB_PASSWORD = database.password;
211
+ if (isSingleStore && database.ssl !== false)
212
+ env.DB_SSL = "true";
209
213
  return env;
210
214
  }
211
215
 
@@ -3496,15 +3500,11 @@ function buildActivateRelease(paths) {
3496
3500
  ];
3497
3501
  }
3498
3502
  function buildRollbackScript(paths, options = {}) {
3499
- if (options.to) {
3500
- const target = `${paths.releases}/${options.to}`;
3501
- return [
3502
- `[ -d ${target} ] || { echo "rollback target ${target} not found" >&2; exit 1; }`,
3503
- `ln -sfn ${target} ${paths.current}.tmp`,
3504
- `mv -Tf ${paths.current}.tmp ${paths.current}`
3505
- ];
3506
- }
3507
- return [
3503
+ const flip = options.to ? [
3504
+ `[ -d ${paths.releases}/${options.to} ] || { echo "rollback target ${paths.releases}/${options.to} not found" >&2; exit 1; }`,
3505
+ `ln -sfn ${paths.releases}/${options.to} ${paths.current}.tmp`,
3506
+ `mv -Tf ${paths.current}.tmp ${paths.current}`
3507
+ ] : [
3508
3508
  `TS_CLOUD_CURRENT=$(readlink -f ${paths.current} 2>/dev/null || true)`,
3509
3509
  `TS_CLOUD_PREV=$(ls -1dt ${paths.releases}/*/ 2>/dev/null | sed 's#/$##' | while read -r r; do ` + '[ "$(readlink -f "$r")" != "$TS_CLOUD_CURRENT" ] && { echo "$r"; break; }; done)',
3510
3510
  '[ -n "$TS_CLOUD_PREV" ] || { echo "no previous release to roll back to" >&2; exit 1; }',
@@ -3512,6 +3512,14 @@ function buildRollbackScript(paths, options = {}) {
3512
3512
  `mv -Tf ${paths.current}.tmp ${paths.current}`,
3513
3513
  'echo "rolled back to $TS_CLOUD_PREV"'
3514
3514
  ];
3515
+ if (!options.unitBase)
3516
+ return flip;
3517
+ const unitBase = options.unitBase;
3518
+ return [
3519
+ ...flip,
3520
+ `TS_CLOUD_RB_ID=$(basename "$(readlink -f ${paths.current})")`,
3521
+ `if [ -f /etc/systemd/system/${unitBase}@.service ]; then ` + `systemctl start "${unitBase}@\${TS_CLOUD_RB_ID}.service"; sleep 2; ` + `systemctl is-active --quiet "${unitBase}@\${TS_CLOUD_RB_ID}.service" || { echo "rolled-back release failed to start" >&2; exit 1; }; ` + `systemctl enable "${unitBase}@\${TS_CLOUD_RB_ID}.service" 2>/dev/null || true; ` + `systemctl list-units --plain --no-legend --type=service "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | grep -v "^${unitBase}@\${TS_CLOUD_RB_ID}.service$" | while read -r TS_CLOUD_U; do systemctl stop "$TS_CLOUD_U" 2>/dev/null || true; systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done; ` + `elif [ -f /etc/systemd/system/${unitBase}.service ]; then systemctl restart ${unitBase}.service; fi`
3522
+ ];
3515
3523
  }
3516
3524
  function buildPruneReleases(paths, keep = DEFAULT_KEEP_RELEASES) {
3517
3525
  const n = Math.max(1, keep);
@@ -3559,15 +3567,19 @@ function buildSiteDeployScript(options) {
3559
3567
  envEntries,
3560
3568
  port,
3561
3569
  keepReleases = DEFAULT_KEEP_RELEASES,
3562
- preStartCommands = []
3570
+ preStartCommands = [],
3571
+ healthCheckPath,
3572
+ healthGateSeconds = 5
3563
3573
  } = options;
3574
+ const zeroDowntime = options.zeroDowntime ?? port != null;
3564
3575
  const base = options.appDir ?? `/var/www/${siteName}`;
3565
3576
  const paths = releasePaths(base, releaseId);
3566
- const serviceName = `${slug}-${siteName}.service`;
3577
+ const unitBase = `${slug}-${siteName}`;
3578
+ const serviceName = `${unitBase}.service`;
3567
3579
  const sharedPaths = [".env"];
3568
3580
  const envFile = formatEnvFile(envEntries);
3569
3581
  const preStart = preStartCommands.length > 0 ? [`cd ${paths.release}`, ...preStartCommands] : [];
3570
- return [
3582
+ const stageRelease = [
3571
3583
  "set -euo pipefail",
3572
3584
  ...artifactFetch,
3573
3585
  ...buildEnsureReleaseLayout(paths, sharedPaths),
@@ -3579,7 +3591,48 @@ function buildSiteDeployScript(options) {
3579
3591
  "TS_CLOUD_ENV_EOF",
3580
3592
  `chmod 600 ${paths.shared}/.env`,
3581
3593
  ...buildLinkSharedPaths(paths, sharedPaths),
3582
- ...preStart,
3594
+ ...preStart
3595
+ ];
3596
+ if (zeroDowntime && port != null) {
3597
+ const instance = `${unitBase}@${releaseId}.service`;
3598
+ const gatePath = healthCheckPath ? healthCheckPath.startsWith("/") ? healthCheckPath : `/${healthCheckPath}` : null;
3599
+ const failGate = `{ echo "[ts-cloud] release ${releaseId} failed its health gate — previous release keeps serving" >&2; journalctl -u ${instance} -n 50 --no-pager >&2 || true; systemctl stop ${instance} 2>/dev/null || true; exit 1; }`;
3600
+ return [
3601
+ ...stageRelease,
3602
+ `cat > /etc/systemd/system/${unitBase}@.service <<'TS_CLOUD_UNIT_EOF'`,
3603
+ "[Unit]",
3604
+ `Description=${siteName} release %i (managed by ts-cloud)`,
3605
+ "After=network.target",
3606
+ "",
3607
+ "[Service]",
3608
+ "Type=simple",
3609
+ `WorkingDirectory=${paths.releases}/%i`,
3610
+ `ExecStart=${execStart}`,
3611
+ "Restart=always",
3612
+ "RestartSec=5",
3613
+ `EnvironmentFile=${paths.releases}/%i/.env`,
3614
+ `Environment=PORT=${port}`,
3615
+ "",
3616
+ "[Install]",
3617
+ "WantedBy=multi-user.target",
3618
+ "TS_CLOUD_UNIT_EOF",
3619
+ "systemctl daemon-reload",
3620
+ `TS_CLOUD_OLD_UNITS=$(systemctl list-units --plain --no-legend --type=service "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | grep -v "^${instance}$" || true)`,
3621
+ `if [ -f /etc/systemd/system/${serviceName} ] && systemctl is-active --quiet ${serviceName}; then echo "[ts-cloud] retiring pre-zero-downtime unit ${serviceName} (one-time restart cutover)"; systemctl stop ${serviceName}; fi`,
3622
+ `systemctl start ${instance}`,
3623
+ `TS_CLOUD_GATE_OK=1; for TS_CLOUD_I in $(seq 1 ${Math.max(1, healthGateSeconds)}); do sleep 1; systemctl is-active --quiet ${instance} || { TS_CLOUD_GATE_OK=0; break; }; done`,
3624
+ `if [ "$TS_CLOUD_GATE_OK" -ne 1 ]; then echo "[ts-cloud] release ${releaseId} could not overlap the previous release (no SO_REUSEPORT?) — retiring old instances and retrying" >&2; for TS_CLOUD_RU in \${TS_CLOUD_OLD_UNITS}; do systemctl stop "$TS_CLOUD_RU" 2>/dev/null || true; done; systemctl restart ${instance}; for TS_CLOUD_I in $(seq 1 ${Math.max(1, healthGateSeconds)}); do sleep 1; systemctl is-active --quiet ${instance} || ${failGate}; done; fi`,
3625
+ ...gatePath ? [`curl -sf -o /dev/null --max-time 10 "http://127.0.0.1:${port}${gatePath}" || ${failGate}`] : [],
3626
+ ...buildActivateRelease(paths),
3627
+ `systemctl enable ${instance} 2>/dev/null || true`,
3628
+ `for TS_CLOUD_U in \${TS_CLOUD_OLD_UNITS}; do systemctl stop "$TS_CLOUD_U" 2>/dev/null || true; systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done`,
3629
+ `systemctl list-unit-files --plain --no-legend "${unitBase}@*.service" 2>/dev/null | awk '{print $1}' | grep -v "^${instance}$" | while read -r TS_CLOUD_U; do systemctl disable "$TS_CLOUD_U" 2>/dev/null || true; done`,
3630
+ `if [ -f /etc/systemd/system/${serviceName} ]; then systemctl disable ${serviceName} 2>/dev/null || true; rm -f /etc/systemd/system/${serviceName}; systemctl daemon-reload; fi`,
3631
+ ...buildPruneReleases(paths, keepReleases)
3632
+ ];
3633
+ }
3634
+ return [
3635
+ ...stageRelease,
3583
3636
  `cat > /etc/systemd/system/${serviceName} <<'TS_CLOUD_UNIT_EOF'`,
3584
3637
  "[Unit]",
3585
3638
  `Description=${siteName} (managed by ts-cloud)`,
@@ -3632,6 +3685,11 @@ function buildLocalArtifactFetch(localPath, siteName) {
3632
3685
  `cp "${localPath}" /tmp/${siteName}-release.tar.gz`
3633
3686
  ];
3634
3687
  }
3688
+ // src/drivers/shared/compute-deploy.ts
3689
+ import { copyFileSync } from "node:fs";
3690
+ import { tmpdir as tmpdir2 } from "node:os";
3691
+ import { join as join5 } from "node:path";
3692
+
3635
3693
  // src/deploy/management-dashboard.ts
3636
3694
  import { execSync as execSync2 } from "node:child_process";
3637
3695
  import { randomBytes } from "node:crypto";
@@ -3710,7 +3768,7 @@ function ensureManagementDashboard(config, options = {}) {
3710
3768
  const environment = config.environments && Object.keys(config.environments)[0];
3711
3769
  const live = truthy(process.env.TS_CLOUD_UI_LIVE);
3712
3770
  const port = Number(process.env.TS_CLOUD_UI_PORT) || undefined;
3713
- const resolved = resolveManagementDashboardSite(config, environment ?? "production", {
3771
+ const resolved = resolveManagementDashboardSites(config, environment ?? "production", {
3714
3772
  uiRoot: ui.uiRoot,
3715
3773
  build: ui.build,
3716
3774
  domain: process.env.TS_CLOUD_UI_DOMAIN?.trim() || undefined,
@@ -3720,15 +3778,22 @@ function ensureManagementDashboard(config, options = {}) {
3720
3778
  live,
3721
3779
  port
3722
3780
  });
3723
- if (!resolved) {
3781
+ if (resolved.length === 0) {
3724
3782
  logger.info("Management dashboard: no domain resolved (set TS_CLOUD_UI_DOMAIN or configure a site domain) — skipping.");
3725
3783
  return config;
3726
3784
  }
3727
- config.sites = { ...config.sites ?? {}, [resolved.name]: resolved.site };
3728
3785
  const authNote = auth.source === "public" ? "NO AUTH — TS_CLOUD_UI_PUBLIC is set (dashboard is publicly reachable)" : auth.source === "env" ? "htpasswd-protected (TS_CLOUD_UI_PASSWORD)" : `htpasswd-protected (auto-generated — see ${DASHBOARD_CREDENTIALS_FILE})`;
3729
- logger.info(`Management dashboard https://${resolved.site.domain} (${authNote})`);
3786
+ const sites = { ...config.sites ?? {} };
3787
+ for (const { name, site } of resolved) {
3788
+ sites[name] = site;
3789
+ logger.info(`Management dashboard → https://${site.domain} (${authNote})`);
3790
+ }
3791
+ config.sites = sites;
3730
3792
  return config;
3731
3793
  }
3794
+ function managementDashboardSiteNames(config) {
3795
+ return Object.keys(config.sites ?? {}).filter(isManagementDashboardSiteName);
3796
+ }
3732
3797
  function buildManagementDashboardArtifact(site, options) {
3733
3798
  if (!site?.root)
3734
3799
  return null;
@@ -4367,7 +4432,9 @@ async function deploySiteRelease(driver, options, logger = noopLogger2) {
4367
4432
  execStart: resolveExecStart(site.start, runtime),
4368
4433
  envEntries: site.env || {},
4369
4434
  port: site.port,
4370
- preStartCommands: site.preStart
4435
+ preStartCommands: site.preStart,
4436
+ zeroDowntime: site.zeroDowntime !== false,
4437
+ healthCheckPath: site.healthCheck?.path
4371
4438
  });
4372
4439
  const compute = config.infrastructure?.compute;
4373
4440
  const wantsNginxStatic = kind === "server-static" && compute?.webServer !== "rpx" && !!site.domain;
@@ -4430,16 +4497,33 @@ async function deployAllComputeSites(options) {
4430
4497
  const cwd = options.cwd ?? process.cwd();
4431
4498
  const hadDashboard = hasManagementDashboardSite(config);
4432
4499
  ensureManagementDashboard(config, { cwd, logger: { info: logger.info, warn: logger.warn } });
4433
- const injectedDashboard = !hadDashboard && hasManagementDashboardSite(config);
4500
+ const injectedDashboardSites = hadDashboard ? [] : managementDashboardSiteNames(config);
4434
4501
  if (options.rpxConfig && options.rpxConfig !== config && !hasManagementDashboardSite(options.rpxConfig))
4435
4502
  ensureManagementDashboard(options.rpxConfig, { cwd, logger: { info: () => {}, warn: () => {} } });
4436
4503
  let dashboardTarball = null;
4437
- if (injectedDashboard) {
4438
- dashboardTarball = buildManagementDashboardArtifact(config.sites?.[MANAGEMENT_DASHBOARD_SITE], { cwd, slug, sha, logger: { info: logger.info, warn: logger.warn } });
4504
+ const dashboardTarballBySite = new Map;
4505
+ if (injectedDashboardSites.length > 0) {
4506
+ dashboardTarball = buildManagementDashboardArtifact(config.sites?.[injectedDashboardSites[0]], { cwd, slug, sha, logger: { info: logger.info, warn: logger.warn } });
4439
4507
  if (!dashboardTarball) {
4440
- logger.warn("Management dashboard: no artifact available — skipping dashboard site for this deploy.");
4441
- if (config.sites)
4442
- delete config.sites[MANAGEMENT_DASHBOARD_SITE];
4508
+ logger.warn("Management dashboard: no artifact available — skipping dashboard site(s) for this deploy.");
4509
+ for (const name of injectedDashboardSites) {
4510
+ if (config.sites)
4511
+ delete config.sites[name];
4512
+ }
4513
+ } else {
4514
+ for (const name of injectedDashboardSites) {
4515
+ if (name === MANAGEMENT_DASHBOARD_SITE) {
4516
+ dashboardTarballBySite.set(name, dashboardTarball);
4517
+ continue;
4518
+ }
4519
+ try {
4520
+ const copy = join5(tmpdir2(), `${slug}-${name}-${sha}.tar.gz`);
4521
+ copyFileSync(dashboardTarball, copy);
4522
+ dashboardTarballBySite.set(name, copy);
4523
+ } catch {
4524
+ dashboardTarballBySite.set(name, dashboardTarball);
4525
+ }
4526
+ }
4443
4527
  }
4444
4528
  }
4445
4529
  const sites = config.sites || {};
@@ -4457,7 +4541,7 @@ async function deployAllComputeSites(options) {
4457
4541
  });
4458
4542
  if (deployable.length === 0)
4459
4543
  return true;
4460
- const tarballFor = (siteName) => siteName === MANAGEMENT_DASHBOARD_SITE && dashboardTarball ? dashboardTarball : tarballForSite(siteName);
4544
+ const tarballFor = (siteName) => dashboardTarballBySite.get(siteName) ?? tarballForSite(siteName);
4461
4545
  for (const [siteName, site] of deployable) {
4462
4546
  logger.step(`Deploying site: ${siteName}`);
4463
4547
  const localTarballPath = isPhpSite(site) ? undefined : tarballFor(siteName);
@@ -21905,27 +21905,44 @@ function apexOf(domain) {
21905
21905
  const parts = domain.split(".").filter(Boolean);
21906
21906
  return parts.length <= 2 ? domain : parts.slice(-2).join(".");
21907
21907
  }
21908
- function resolveDashboardDomain(config, environment, explicit) {
21909
- if (explicit)
21910
- return explicit;
21908
+ function collectDomains(config, environment) {
21911
21909
  const candidates = [];
21910
+ const dnsDomain = config.infrastructure?.dns?.domain;
21911
+ if (dnsDomain)
21912
+ candidates.push(dnsDomain);
21913
+ if (environment && config.environments?.[environment]?.domain)
21914
+ candidates.push(config.environments[environment].domain);
21912
21915
  for (const site of Object.values(config.sites ?? {})) {
21913
- const d = site?.domain;
21916
+ const s = site;
21917
+ if (!s || s.redirect)
21918
+ continue;
21919
+ const d = s.domain;
21914
21920
  if (typeof d === "string")
21915
21921
  candidates.push(d);
21916
21922
  else if (Array.isArray(d))
21917
21923
  candidates.push(...d.filter((x) => typeof x === "string"));
21918
21924
  }
21919
- if (environment && config.environments?.[environment]?.domain)
21920
- candidates.push(config.environments[environment].domain);
21921
- const dnsDomain = config.infrastructure?.dns?.domain;
21922
- if (dnsDomain)
21923
- candidates.push(dnsDomain);
21924
- const base = candidates.find((d) => d && !d.startsWith("dashboard."));
21925
+ return candidates.filter((d) => d && !d.startsWith("dashboard."));
21926
+ }
21927
+ function resolveDashboardDomain(config, environment, explicit) {
21928
+ if (explicit)
21929
+ return explicit;
21930
+ const base = collectDomains(config, environment)[0];
21925
21931
  if (!base)
21926
21932
  return null;
21927
21933
  return `dashboard.${apexOf(base)}`;
21928
21934
  }
21935
+ function resolveDashboardDomains(config, environment, explicit) {
21936
+ if (explicit)
21937
+ return [explicit];
21938
+ const apexes = [];
21939
+ for (const d of collectDomains(config, environment)) {
21940
+ const apex = apexOf(d);
21941
+ if (!apexes.includes(apex))
21942
+ apexes.push(apex);
21943
+ }
21944
+ return apexes.map((apex) => `dashboard.${apex}`);
21945
+ }
21929
21946
  function hasManagementDashboardSite(config) {
21930
21947
  return Object.entries(config.sites ?? {}).some(([name, site]) => {
21931
21948
  if (!site)
@@ -21934,37 +21951,53 @@ function hasManagementDashboardSite(config) {
21934
21951
  return name === "dashboard" || root === "ui/dist" || root.endsWith("/ui/dist") || root.endsWith("/dist/ui");
21935
21952
  });
21936
21953
  }
21937
- function resolveManagementDashboardSite(config, environment, opts) {
21954
+ var MANAGEMENT_DASHBOARD_SITE_PREFIX = "dashboard-";
21955
+ function dashboardSiteName(domain, primary) {
21956
+ if (primary)
21957
+ return "dashboard";
21958
+ return MANAGEMENT_DASHBOARD_SITE_PREFIX + domain.replace(/^dashboard\./, "").replace(/\./g, "-");
21959
+ }
21960
+ function isManagementDashboardSiteName(name) {
21961
+ return name === "dashboard" || name.startsWith(MANAGEMENT_DASHBOARD_SITE_PREFIX);
21962
+ }
21963
+ function resolveManagementDashboardSites(config, environment, opts) {
21938
21964
  if (hasManagementDashboardSite(config))
21939
- return null;
21940
- const domain = resolveDashboardDomain(config, environment, opts.domain);
21941
- if (!domain)
21942
- return null;
21965
+ return [];
21943
21966
  const auth2 = opts.password ? { auth: { username: opts.username || "admin", password: opts.password, realm: opts.realm } } : {};
21967
+ const build = opts.build === false || opts.build === undefined ? {} : { build: opts.build };
21944
21968
  if (opts.live) {
21969
+ const domain = resolveDashboardDomain(config, environment, opts.domain);
21970
+ if (!domain)
21971
+ return [];
21945
21972
  const port = opts.port ?? 7676;
21946
- const site2 = {
21973
+ const site = {
21947
21974
  root: opts.uiRoot,
21948
21975
  deploy: "server",
21949
21976
  domain,
21950
21977
  start: `cloud dashboard:serve --box --host 127.0.0.1 --port ${port}`,
21951
21978
  port,
21952
21979
  ssl: { provider: "letsencrypt" },
21953
- ...opts.build === false || opts.build === undefined ? {} : { build: opts.build },
21980
+ ...build,
21954
21981
  ...auth2
21955
21982
  };
21956
- return { name: "dashboard", site: site2 };
21983
+ return [{ name: "dashboard", site }];
21957
21984
  }
21958
- const site = {
21959
- root: opts.uiRoot,
21960
- deploy: "server",
21961
- type: "static",
21962
- domain,
21963
- ssl: { provider: "letsencrypt" },
21964
- ...opts.build === false || opts.build === undefined ? {} : { build: opts.build },
21965
- ...auth2
21966
- };
21967
- return { name: "dashboard", site };
21985
+ const domains = resolveDashboardDomains(config, environment, opts.domain);
21986
+ return domains.map((domain, i) => ({
21987
+ name: dashboardSiteName(domain, i === 0),
21988
+ site: {
21989
+ root: opts.uiRoot,
21990
+ deploy: "server",
21991
+ type: "static",
21992
+ domain,
21993
+ ssl: { provider: "letsencrypt" },
21994
+ ...build,
21995
+ ...auth2
21996
+ }
21997
+ }));
21998
+ }
21999
+ function resolveManagementDashboardSite(config, environment, opts) {
22000
+ return resolveManagementDashboardSites(config, environment, opts)[0] ?? null;
21968
22001
  }
21969
22002
  function deepMerge(target, source) {
21970
22003
  const result = { ...target };
@@ -46121,4 +46154,4 @@ class ResourceManagementManager {
46121
46154
  }
46122
46155
  var resourceManagementManager = new ResourceManagementManager;
46123
46156
 
46124
- export { detectServiceRegion, signRequest, signRequestAsync, createPresignedUrl, createPresignedUrlAsync, makeAWSRequest, makeAWSRequestOnce, makeAWSRequestAsync, parseXMLResponse, parseJSONResponse, clearSigningKeyCache, getSigningKeyCacheSize, isNodeCryptoAvailable, isWebCryptoAvailable, QueuePresets, RealtimePresets, detectDeploymentTargets, resolveDeploymentMode, deploymentCoexistenceError, resolveCloudProvider, TemplateBuilder, Pseudo, generateResourceName, generateLogicalId, getTimestamp, sanitizeName, resolveProjectStackName, resolveSiteStackName, resolveSiteResourceName, resolveSiteBucketName, resolveStorageBucketName, resolveDeployBucketName, resolveServerlessAppStackName, resolveServerlessArtifactBucketName, resolveServerlessAssetBucketName, DependencyGraph, analyzeStackDiff, formatDiff, Storage, Registry, CDN, DNS, Security, Compute, Network, FileSystem, Email, exports_incoming_call, Phone, Queue, JobLoader, StacksIntegration, exports_send, SMS, AI, Database, Cache, Permissions, ApiGateway, Messaging, Workflow, Monitoring, Auth, Deployment, AssetHasher, Secrets, ParameterStore, Search, Redirects, Communication, Fn2, Arn, AWS_PSEUDO_PARAMETERS, CloudFormationBuilder, buildCloudFormationTemplate, createStaticSitePreset, createNodeJsServerPreset, createNodeJsServerlessPreset, createServerlessNodePreset, createFullStackAppPreset, createApiBackendPreset, createWordPressPreset, createJamstackPreset, createMicroservicesPreset, createRealtimeAppPreset, createDataPipelinePreset, createMLApiPreset, createTraditionalWebAppPreset, createLaravelPreset, createServerlessLaravelPreset, createDashboardSite, resolveDashboardDomain, hasManagementDashboardSite, resolveManagementDashboardSite, extendPreset, composePresets, createPreset, mergeInfrastructure, withMonitoring, withSecurity, withDatabase, withCache, withCDN, withQueue, fromEnvironment, fromSharedCredentials, fromEC2Metadata, fromECSMetadata, fromWebIdentity, getCredentials, createCredentialProvider, resolveCredentials, resolveRegion, getAccountId, S3Error, createS3Client, CloudError, ConfigurationError, CredentialError, DeploymentError, ValidationError, AWSAPIError, ErrorCodes, getErrorDetails, createError, DebugLogger, validateConfiguration, detectMisconfigurations, validateCredentials, checkIAMPermissions, getRequiredPermissions, suggestIAMPolicy, DEFAULT_SERVICE_LIMITS, checkServiceQuotas, getQuotaUsageSummary, suggestQuotaIncrease, FileCache, TemplateCache, templateCache, hashFile, hashString, hashBuffer, hashDirectory, hashManifest, quickHash, findChangedFiles, HashCache, parallel, batch, parallelMap, parallelWithRetry, sequence, withTimeout, RateLimiter, chunk, processInChunks, diffTemplates, getDiffSummary, requiresReplacement, categorizeChanges, getDeploymentStrategy, getDiffStats, cloud_config_schema_default, validateAgainstSchema, defaultLocalConfig, getLocalConfig, isLocalDevelopment, getLocalEndpoint, getLocalCredentials, getLocalEnvVars, MockCloudFormation, MockS3, MockDynamoDB, createMockAWS, PreviewEnvironmentManager, previewManager, generatePreviewWorkflow, generateCleanupWorkflow, generateCostReportWorkflow, PreviewNotificationService, previewNotifications, formatTable, formatTree, formatProgressBar, formatBytes, formatDuration, formatList, formatKeyValue, ProgressBar, MultiStepProgress, Spinner, TaskList, suggestCommand, formatSuggestion, isLikelyTypo, getContextualHelp, searchCommands, autocomplete, suggestFlags, formatFlagSuggestions, getCommandUsage, validateCommand, REPL, REPLContext, REPLCommandBuilder, CommandHistory, formatHistory, formatHistoryStats, MultiRegionManager, multiRegionManager, CrossRegionReferenceManager, GlobalResourceManager, RegionPairManager, StackDependencyManager, crossRegionReferenceManager, globalResourceManager, regionPairManager, stackDependencyManager, AWS_REGIONS, getRegion, getAllRegions, getRegionsByLocation, getRegionsByCompliance, getRegionsByPricingTier, isValidRegion, getClosestRegion, suggestRegions, suggestRegionPairs, formatRegion, formatRegionList, getRegionStats, MultiAccountManager, OrganizationManager, multiAccountManager, organizationManager, RECOMMENDED_ACCOUNT_STRUCTURES, RECOMMENDED_SCPS, COMMON_CROSS_ACCOUNT_ROLES, getRecommendedStructure, generateCrossAccountRoleCF, validateAccountStructure, formatAccountStructure, generateDeploymentWorkflow, generateMultiEnvWorkflow, generatePRPreviewWorkflow, generateScheduledWorkflow, generateMatrixWorkflow, generateDeploymentPipeline, generateMultiEnvPipeline, generatePreviewPipeline, generateScheduledPipeline, generateManualPipeline, generateDeploymentConfig, generateMultiEnvConfig, generateScheduledConfig, generateApprovalConfig, generateParallelConfig, BackupManager, backupManager, DisasterRecoveryManager, drManager, AWSConfigManager, awsConfigManager, CloudTrailManager, cloudTrailManager, GuardDutyManager, guardDutyManager, SecurityHubManager, securityHubManager, BlueGreenManager, blueGreenManager, CanaryManager, canaryManager, ABTestManager, abTestManager, ProgressiveDeploymentManager, progressiveDeploymentManager, XRayManager, xrayManager, MetricsManager, metricsManager, LogsManager, logsManager, SyntheticsManager, syntheticsManager, MigrationManager, migrationManager, ReplicaManager, replicaManager, PerformanceManager, performanceManager, DatabaseUserManager, databaseUserManager, SecretsRotationManager, secretsRotationManager, SecretsManager, secretsManager, CertificateManager, certificateManager, SecurityScanningManager, securityScanningManager, ImageScanningManager, imageScanningManager, BuildOptimizationManager, buildOptimizationManager, ContainerRegistryManager, containerRegistryManager, ServiceMeshManager, serviceMeshManager, LambdaLayersManager, lambdaLayersManager, LambdaVersionsManager, lambdaVersionsManager, LambdaConcurrencyManager, lambdaConcurrencyManager, LambdaDestinationsManager, lambdaDestinationsManager, LambdaVPCManager, lambdaVPCManager, LambdaDLQManager, lambdaDLQManager, Route53RoutingManager, route53RoutingManager, DNSSECManager, dnssecManager, Route53ResolverManager, route53ResolverManager, BounceComplaintHandler, bounceComplaintHandler, EmailAnalyticsManager, emailAnalyticsManager, SenderReputationManager, senderReputationManager, EmailTemplateManager, emailTemplateManager, exports_advanced, exports_inbound, exports_advanced2, exports_advanced3, FIFOQueueManager, fifoQueueManager, DLQMonitoringManager, dlqMonitoringManager, BatchProcessingManager, batchProcessingManager, QueueManagementManager, queueManagementManager, createZip, generateBootstrap, runBuildHooks, sha256, artifactKey, packageServerlessApp, MANAGED_NODE_VERSIONS, resolveServerlessRuntime, resolveQueues, resolveQueueNames, composeServerlessAppTemplate, PHP_LAYER_EXTENSIONS, phpLayerPackages, phpLayerBuildStage, generatePhpLayerDockerfile, generateAppImageDockerfile, sharedRuntimeLoop, buildNodeRuntimeLayerZip, buildBunRuntimeLayerZip, resolveApp, eventToRequest, responseToResult, createHttpHandler, createQueueHandler, createCliHandler, createHandlers, generatePhpFpmConfig, phpRuntimeLayerAssets, laravelServerlessEnvDefaults, LARAVEL_SERVERLESS_BUILD_STEPS, buildPhpRuntimeLayerZip, PHP_DEFAULT_EXCLUDES, runPhpBuildHooks, collectPhpAppEntries, packagePhpApp, StaticSiteManager, staticSiteManager, StorageAdvancedManager, storageAdvancedManager, HealthCheckManager, healthCheckManager, NetworkSecurityManager, networkSecurityManager, ResourceManagementManager, resourceManagementManager };
46157
+ export { detectServiceRegion, signRequest, signRequestAsync, createPresignedUrl, createPresignedUrlAsync, makeAWSRequest, makeAWSRequestOnce, makeAWSRequestAsync, parseXMLResponse, parseJSONResponse, clearSigningKeyCache, getSigningKeyCacheSize, isNodeCryptoAvailable, isWebCryptoAvailable, QueuePresets, RealtimePresets, detectDeploymentTargets, resolveDeploymentMode, deploymentCoexistenceError, resolveCloudProvider, TemplateBuilder, Pseudo, generateResourceName, generateLogicalId, getTimestamp, sanitizeName, resolveProjectStackName, resolveSiteStackName, resolveSiteResourceName, resolveSiteBucketName, resolveStorageBucketName, resolveDeployBucketName, resolveServerlessAppStackName, resolveServerlessArtifactBucketName, resolveServerlessAssetBucketName, DependencyGraph, analyzeStackDiff, formatDiff, Storage, Registry, CDN, DNS, Security, Compute, Network, FileSystem, Email, exports_incoming_call, Phone, Queue, JobLoader, StacksIntegration, exports_send, SMS, AI, Database, Cache, Permissions, ApiGateway, Messaging, Workflow, Monitoring, Auth, Deployment, AssetHasher, Secrets, ParameterStore, Search, Redirects, Communication, Fn2, Arn, AWS_PSEUDO_PARAMETERS, CloudFormationBuilder, buildCloudFormationTemplate, createStaticSitePreset, createNodeJsServerPreset, createNodeJsServerlessPreset, createServerlessNodePreset, createFullStackAppPreset, createApiBackendPreset, createWordPressPreset, createJamstackPreset, createMicroservicesPreset, createRealtimeAppPreset, createDataPipelinePreset, createMLApiPreset, createTraditionalWebAppPreset, createLaravelPreset, createServerlessLaravelPreset, createDashboardSite, resolveDashboardDomain, resolveDashboardDomains, hasManagementDashboardSite, MANAGEMENT_DASHBOARD_SITE_PREFIX, isManagementDashboardSiteName, resolveManagementDashboardSites, resolveManagementDashboardSite, extendPreset, composePresets, createPreset, mergeInfrastructure, withMonitoring, withSecurity, withDatabase, withCache, withCDN, withQueue, fromEnvironment, fromSharedCredentials, fromEC2Metadata, fromECSMetadata, fromWebIdentity, getCredentials, createCredentialProvider, resolveCredentials, resolveRegion, getAccountId, S3Error, createS3Client, CloudError, ConfigurationError, CredentialError, DeploymentError, ValidationError, AWSAPIError, ErrorCodes, getErrorDetails, createError, DebugLogger, validateConfiguration, detectMisconfigurations, validateCredentials, checkIAMPermissions, getRequiredPermissions, suggestIAMPolicy, DEFAULT_SERVICE_LIMITS, checkServiceQuotas, getQuotaUsageSummary, suggestQuotaIncrease, FileCache, TemplateCache, templateCache, hashFile, hashString, hashBuffer, hashDirectory, hashManifest, quickHash, findChangedFiles, HashCache, parallel, batch, parallelMap, parallelWithRetry, sequence, withTimeout, RateLimiter, chunk, processInChunks, diffTemplates, getDiffSummary, requiresReplacement, categorizeChanges, getDeploymentStrategy, getDiffStats, cloud_config_schema_default, validateAgainstSchema, defaultLocalConfig, getLocalConfig, isLocalDevelopment, getLocalEndpoint, getLocalCredentials, getLocalEnvVars, MockCloudFormation, MockS3, MockDynamoDB, createMockAWS, PreviewEnvironmentManager, previewManager, generatePreviewWorkflow, generateCleanupWorkflow, generateCostReportWorkflow, PreviewNotificationService, previewNotifications, formatTable, formatTree, formatProgressBar, formatBytes, formatDuration, formatList, formatKeyValue, ProgressBar, MultiStepProgress, Spinner, TaskList, suggestCommand, formatSuggestion, isLikelyTypo, getContextualHelp, searchCommands, autocomplete, suggestFlags, formatFlagSuggestions, getCommandUsage, validateCommand, REPL, REPLContext, REPLCommandBuilder, CommandHistory, formatHistory, formatHistoryStats, MultiRegionManager, multiRegionManager, CrossRegionReferenceManager, GlobalResourceManager, RegionPairManager, StackDependencyManager, crossRegionReferenceManager, globalResourceManager, regionPairManager, stackDependencyManager, AWS_REGIONS, getRegion, getAllRegions, getRegionsByLocation, getRegionsByCompliance, getRegionsByPricingTier, isValidRegion, getClosestRegion, suggestRegions, suggestRegionPairs, formatRegion, formatRegionList, getRegionStats, MultiAccountManager, OrganizationManager, multiAccountManager, organizationManager, RECOMMENDED_ACCOUNT_STRUCTURES, RECOMMENDED_SCPS, COMMON_CROSS_ACCOUNT_ROLES, getRecommendedStructure, generateCrossAccountRoleCF, validateAccountStructure, formatAccountStructure, generateDeploymentWorkflow, generateMultiEnvWorkflow, generatePRPreviewWorkflow, generateScheduledWorkflow, generateMatrixWorkflow, generateDeploymentPipeline, generateMultiEnvPipeline, generatePreviewPipeline, generateScheduledPipeline, generateManualPipeline, generateDeploymentConfig, generateMultiEnvConfig, generateScheduledConfig, generateApprovalConfig, generateParallelConfig, BackupManager, backupManager, DisasterRecoveryManager, drManager, AWSConfigManager, awsConfigManager, CloudTrailManager, cloudTrailManager, GuardDutyManager, guardDutyManager, SecurityHubManager, securityHubManager, BlueGreenManager, blueGreenManager, CanaryManager, canaryManager, ABTestManager, abTestManager, ProgressiveDeploymentManager, progressiveDeploymentManager, XRayManager, xrayManager, MetricsManager, metricsManager, LogsManager, logsManager, SyntheticsManager, syntheticsManager, MigrationManager, migrationManager, ReplicaManager, replicaManager, PerformanceManager, performanceManager, DatabaseUserManager, databaseUserManager, SecretsRotationManager, secretsRotationManager, SecretsManager, secretsManager, CertificateManager, certificateManager, SecurityScanningManager, securityScanningManager, ImageScanningManager, imageScanningManager, BuildOptimizationManager, buildOptimizationManager, ContainerRegistryManager, containerRegistryManager, ServiceMeshManager, serviceMeshManager, LambdaLayersManager, lambdaLayersManager, LambdaVersionsManager, lambdaVersionsManager, LambdaConcurrencyManager, lambdaConcurrencyManager, LambdaDestinationsManager, lambdaDestinationsManager, LambdaVPCManager, lambdaVPCManager, LambdaDLQManager, lambdaDLQManager, Route53RoutingManager, route53RoutingManager, DNSSECManager, dnssecManager, Route53ResolverManager, route53ResolverManager, BounceComplaintHandler, bounceComplaintHandler, EmailAnalyticsManager, emailAnalyticsManager, SenderReputationManager, senderReputationManager, EmailTemplateManager, emailTemplateManager, exports_advanced, exports_inbound, exports_advanced2, exports_advanced3, FIFOQueueManager, fifoQueueManager, DLQMonitoringManager, dlqMonitoringManager, BatchProcessingManager, batchProcessingManager, QueueManagementManager, queueManagementManager, createZip, generateBootstrap, runBuildHooks, sha256, artifactKey, packageServerlessApp, MANAGED_NODE_VERSIONS, resolveServerlessRuntime, resolveQueues, resolveQueueNames, composeServerlessAppTemplate, PHP_LAYER_EXTENSIONS, phpLayerPackages, phpLayerBuildStage, generatePhpLayerDockerfile, generateAppImageDockerfile, sharedRuntimeLoop, buildNodeRuntimeLayerZip, buildBunRuntimeLayerZip, resolveApp, eventToRequest, responseToResult, createHttpHandler, createQueueHandler, createCliHandler, createHandlers, generatePhpFpmConfig, phpRuntimeLayerAssets, laravelServerlessEnvDefaults, LARAVEL_SERVERLESS_BUILD_STEPS, buildPhpRuntimeLayerZip, PHP_DEFAULT_EXCLUDES, runPhpBuildHooks, collectPhpAppEntries, packagePhpApp, StaticSiteManager, staticSiteManager, StorageAdvancedManager, storageAdvancedManager, HealthCheckManager, healthCheckManager, NetworkSecurityManager, networkSecurityManager, ResourceManagementManager, resourceManagementManager };
@@ -12,10 +12,10 @@ import {
12
12
  sanitizeCloudConfig,
13
13
  setMaintenance,
14
14
  startLocalDashboardServer
15
- } from "../chunk-x07dz3sd.js";
15
+ } from "../chunk-1518raqr.js";
16
16
  import {
17
17
  buildAndPushServerlessImage
18
- } from "../chunk-0wxyppza.js";
18
+ } from "../chunk-m03vddvr.js";
19
19
  import"../chunk-b82pbxyp.js";
20
20
  import"../chunk-hsk6fe6x.js";
21
21
  import {
@@ -29,8 +29,8 @@ import {
29
29
  resolveSiteKind,
30
30
  resolveUiSource,
31
31
  validateDeploymentConfig
32
- } from "../chunk-xzn0ntr0.js";
33
- import"../chunk-c6rgvg1j.js";
32
+ } from "../chunk-nw5nnffj.js";
33
+ import"../chunk-vsgkcpqw.js";
34
34
  import"../chunk-93hjhs78.js";
35
35
  import {
36
36
  deleteStaticSite,
@@ -62,6 +62,8 @@ export declare function ensureManagementDashboard(config: CloudConfig, options?:
62
62
  cwd?: string;
63
63
  logger?: EnsureDashboardLogger;
64
64
  }): CloudConfig;
65
+ /** Names of the management-dashboard sites currently present in `config`. */
66
+ export declare function managementDashboardSiteNames(config: CloudConfig): string[];
65
67
  export interface BuildDashboardArtifactOptions {
66
68
  cwd?: string;
67
69
  slug: string;
@@ -48,8 +48,8 @@ import {
48
48
  waitForCloudInit,
49
49
  waitForSsh,
50
50
  wrapCloudInitUserData
51
- } from "../chunk-xzn0ntr0.js";
52
- import"../chunk-c6rgvg1j.js";
51
+ } from "../chunk-nw5nnffj.js";
52
+ import"../chunk-vsgkcpqw.js";
53
53
  import"../chunk-93hjhs78.js";
54
54
  import"../chunk-qpj3edwz.js";
55
55
  import"../chunk-vd87cpvn.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -25,13 +25,47 @@ export interface BuildSiteDeployScriptOptions {
25
25
  * `node_modules`.
26
26
  */
27
27
  preStartCommands?: string[];
28
+ /**
29
+ * True zero-downtime cutover for ported sites: the new release runs as its
30
+ * own systemd instance (`<slug>-<site>@<releaseId>`) that binds the same
31
+ * port via SO_REUSEPORT while the old instance still serves, must pass a
32
+ * health gate, and only then is the old instance stopped. A release that
33
+ * crashes on boot fails the deploy with the old release still serving.
34
+ *
35
+ * Requires the app to bind with `reusePort` (Stacks' server does in
36
+ * production). Defaults to true when `port` is set; portless sites
37
+ * (queue workers, schedulers) always use the stop/start flow because two
38
+ * overlapping instances would double-process their work.
39
+ */
40
+ zeroDowntime?: boolean;
41
+ /**
42
+ * HTTP path polled on `127.0.0.1:<port>` as part of the health gate (e.g.
43
+ * `/health`). Optional — without it the gate is "the instance stays
44
+ * active for {@link BuildSiteDeployScriptOptions.healthGateSeconds}".
45
+ */
46
+ healthCheckPath?: string;
47
+ /**
48
+ * Seconds the new instance must stay active (and, with
49
+ * {@link BuildSiteDeployScriptOptions.healthCheckPath}, respond 2xx/3xx)
50
+ * before the old instance is stopped.
51
+ * @default 5
52
+ */
53
+ healthGateSeconds?: number;
28
54
  }
29
55
  /**
30
56
  * Build the remote shell commands that install/refresh a server-app site on a
31
- * compute target with a **zero-downtime atomic release** (Envoyer-style): unpack
32
- * into `releases/<id>`, link the shared `.env`, build, then atomically repoint
33
- * `current` and restart the systemd service (which runs from `current`). Old
34
- * releases are pruned for rollback.
57
+ * compute target with an atomic release (Envoyer-style): unpack into
58
+ * `releases/<id>`, link the shared `.env`, build, then cut over.
59
+ *
60
+ * The cutover has two modes:
61
+ * - **zero-downtime** (default for ported sites): the new release starts as a
62
+ * templated systemd instance that shares the port via SO_REUSEPORT with the
63
+ * still-running old instance, must pass a health gate, and only then does
64
+ * the old instance stop — no dropped connections, and a crash-on-boot
65
+ * release fails the deploy with the old one still serving.
66
+ * - **restart** (portless sites, or `zeroDowntime: false`): the classic flip
67
+ * `current` + `systemctl restart` — correct for workers/schedulers where two
68
+ * overlapping instances would double-process work.
35
69
  */
36
70
  export declare function buildSiteDeployScript(options: BuildSiteDeployScriptOptions): string[];
37
71
  export interface BuildStaticSiteDeployScriptOptions {
@@ -58,11 +58,19 @@ export declare function buildActivateRelease(paths: ReleasePaths): string[];
58
58
  * `to` set, points `current` at `releases/<to>`; otherwise picks the most recent
59
59
  * release that isn't the one `current` resolves to. Atomic (temp symlink + `mv
60
60
  * -T`), and a no-op-safe guard fails loudly if the target is missing rather than
61
- * leaving `current` dangling. The caller appends the engine reload
62
- * (php-fpm/queues) — see {@link import('./laravel-deploy')}.
61
+ * leaving `current` dangling.
62
+ *
63
+ * With `unitBase` set (e.g. `myapp-api`), the script also swaps the running
64
+ * systemd release instance for sites deployed zero-downtime style (templated
65
+ * `<unitBase>@<releaseId>` units pinned to their release dirs): it starts the
66
+ * instance for the rolled-back release — overlapping on the SO_REUSEPORT port —
67
+ * then stops the newer one, so even the rollback itself is zero-downtime. Sites
68
+ * on the legacy single unit just get a restart. The caller appends any engine
69
+ * reload (php-fpm/queues) — see {@link import('./laravel-deploy')}.
63
70
  */
64
71
  export declare function buildRollbackScript(paths: ReleasePaths, options?: {
65
72
  to?: string;
73
+ unitBase?: string;
66
74
  }): string[];
67
75
  /**
68
76
  * Remove all but the newest `keep` releases (by mtime). `current` always points