@stacksjs/buddy 0.70.226 → 0.70.228

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.
@@ -76,6 +76,7 @@ export declare function loadTsCloudDeployApi(): Promise<typeof import('@stacksjs
76
76
  * port declaration is left alone.
77
77
  */
78
78
  export declare function shouldInjectManagementDashboard(tsCloudConfig: any): boolean;
79
+ export declare function reconcilePartialDeployManagementDashboards(tsCloudConfig: any, livePorts: Record<string, number>): { preserved: string[], removed: string[] };
79
80
  /**
80
81
  * Read a tenant's existing shared-server pin without requiring provider API
81
82
  * credentials. The pin contains only compute metadata and is sufficient for
@@ -567,6 +567,55 @@ export async function loadTsCloudDeployApi() {
567
567
  export function shouldInjectManagementDashboard(tsCloudConfig) {
568
568
  return !tsCloudConfig.cloud?.attachTo;
569
569
  }
570
+ export function reconcilePartialDeployManagementDashboards(tsCloudConfig, livePorts) {
571
+ const sites = tsCloudConfig.sites, preserved = [], removed = [];
572
+ if (!sites)
573
+ return { preserved, removed };
574
+ for (const [siteName, site] of Object.entries(sites)) {
575
+ if (siteName !== "dashboard" && !siteName.startsWith("dashboard-"))
576
+ continue;
577
+ const livePort = livePorts[siteName];
578
+ if (typeof livePort !== "number" || !Number.isInteger(livePort) || livePort < 1 || livePort > 65535) {
579
+ delete sites[siteName];
580
+ removed.push(siteName);
581
+ continue;
582
+ }
583
+ const next = { ...site, port: livePort };
584
+ if (typeof next.start === "string")
585
+ next.start = next.start.replace(/(--port(?:=|\s+))\d+/, `$1${livePort}`);
586
+ sites[siteName] = next;
587
+ preserved.push(siteName);
588
+ }
589
+ return { preserved, removed };
590
+ }
591
+ async function reconcilePartialDeployManagementDashboardsWithLiveBox(tsCloudConfig, ip) {
592
+ const slug = String(tsCloudConfig.project?.slug || "app").replace(/[^a-z0-9._-]+/gi, "-"), siteNames = Object.keys(tsCloudConfig.sites ?? {}).filter((name) => name === "dashboard" || name.startsWith("dashboard-"));
593
+ if (siteNames.length === 0)
594
+ return;
595
+ const units = siteNames.map((siteName) => ({
596
+ siteName,
597
+ unit: `${slug}-${siteName}.service`
598
+ })), probe = `
599
+ const units = ${JSON.stringify(units)}
600
+ const text = bytes => new TextDecoder().decode(bytes).trim()
601
+ const run = args => text(Bun.spawnSync(args).stdout)
602
+ const ports = {}
603
+ for (const entry of units) {
604
+ if (run(['systemctl', 'show', entry.unit, '--property=ActiveState', '--value']) !== 'active')
605
+ continue
606
+ const command = run(['systemctl', 'show', entry.unit, '--property=ExecStart', '--value'])
607
+ const match = command.match(/--port(?:=|\\s+)(\\d+)/)
608
+ if (match)
609
+ ports[entry.siteName] = Number(match[1])
610
+ }
611
+ console.log(JSON.stringify(ports))
612
+ `.trim(), encoded = Buffer.from(probe).toString("base64"), { sshExecOrThrow } = await import("@stacksjs/ts-cloud"), line = (await sshExecOrThrow(ip, `/usr/local/bin/bun -e "eval(Buffer.from('${encoded}','base64').toString())"`, { user: "root", connectTimeoutSec: 10 })).trim().split(`
613
+ `).at(-1) || "{}", livePorts = JSON.parse(line), result = reconcilePartialDeployManagementDashboards(tsCloudConfig, livePorts);
614
+ for (const siteName of result.preserved)
615
+ log.info(`Partial deploy: preserving the live management dashboard service '${siteName}' on port ${livePorts[siteName]}`);
616
+ for (const siteName of result.removed)
617
+ log.info(`Partial deploy: omitting management dashboard route '${siteName}' because no active service owns it`);
618
+ }
570
619
  export function resolvePersistedAttachTargetBox(tsCloudConfig, environment, cwd = process.cwd()) {
571
620
  const owner = tsCloudConfig.cloud?.attachTo;
572
621
  if (!owner)
@@ -751,6 +800,8 @@ async function runHetznerDeploy(args) {
751
800
  process.exit(ExitCode.FatalError);
752
801
  }
753
802
  await waitForRemoteReady(ip);
803
+ if (onlySite && !attachTo)
804
+ await reconcilePartialDeployManagementDashboardsWithLiveBox(tsCloudConfig, ip);
754
805
  const { execSync } = await import("node:child_process"), { tmpdir } = await import("node:os"), sites = applyEnvironmentToSites(tsCloudConfig.sites || {}, environment, tsCloudConfig), slug = tsCloudConfig.project?.slug || "app";
755
806
  let sha;
756
807
  try {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/buddy",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.226",
5
+ "version": "0.70.228",
6
6
  "description": "Meet Buddy. The Stacks runtime.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -137,7 +137,7 @@
137
137
  "@stacksjs/ui": "^0.70.180",
138
138
  "@stacksjs/utils": "^0.70.180",
139
139
  "@stacksjs/validation": "^0.70.180",
140
- "@stacksjs/ts-cloud": "^0.7.91",
140
+ "@stacksjs/ts-cloud": "^0.7.92",
141
141
  "ajv": "^8.20.0",
142
142
  "ajv-formats": "^3.0.1",
143
143
  "ts-pantry": "^0.11.0"