@stacksjs/buddy 0.70.222 → 0.70.223

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,12 @@ 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
+ /**
80
+ * Read a tenant's existing shared-server pin without requiring provider API
81
+ * credentials. The pin contains only compute metadata and is sufficient for
82
+ * an SSH release to a box that the owner project already provisioned.
83
+ */
84
+ export declare function resolvePersistedAttachTargetBox(tsCloudConfig: any, environment: string, cwd?: unknown): AttachedComputeBox | null;
79
85
  export declare function scrubLoopbackSitePortsForFirewall(tsCloudConfig: any): any;
80
86
  /**
81
87
  * Mail tenancy is an explicit deployment capability. The merged Stacks config
@@ -141,6 +147,12 @@ declare const log: {
141
147
  error: (...args: any[]) => unknown;
142
148
  debug: (...args: any[]) => void
143
149
  };
150
+ declare interface AttachedComputeBox {
151
+ serverId: number
152
+ serverName: string
153
+ publicIp: string
154
+ publicIpv6?: string
155
+ }
144
156
  /** What a mail-tenant reconcile resolved + provisioned, for the DNS step. */
145
157
  export declare interface MailTenantResult {
146
158
  domain: string
@@ -534,8 +534,8 @@ export function applyEnvironmentToSites(sites, environment, config) {
534
534
  return out;
535
535
  }
536
536
  async function deployToHetzner(tsCloudConfig, deployEnv, options) {
537
- const verbose = options.verbose === !0, environment = deployEnv === "prod" ? "production" : deployEnv;
538
- if (!(tsCloudConfig.hetzner?.apiToken || process.env.HCLOUD_TOKEN || process.env.HETZNER_API_TOKEN)) {
537
+ const verbose = options.verbose === !0, environment = deployEnv === "prod" ? "production" : deployEnv, apiToken = tsCloudConfig.hetzner?.apiToken || process.env.HCLOUD_TOKEN || process.env.HETZNER_API_TOKEN, persistedAttachBox = resolvePersistedAttachTargetBox(tsCloudConfig, environment);
538
+ if (!apiToken && !persistedAttachBox) {
539
539
  log.error("No Hetzner API token found. Set HCLOUD_TOKEN in your .env (or hetzner.apiToken in config/cloud.ts).");
540
540
  process.exit(ExitCode.FatalError);
541
541
  }
@@ -548,7 +548,7 @@ async function deployToHetzner(tsCloudConfig, deployEnv, options) {
548
548
  }
549
549
  const { createCloudDriver, deployAllComputeSites, ensureManagementDashboard, resolveSiteKind } = await loadTsCloudDeployApi();
550
550
  try {
551
- await runHetznerDeploy({ tsCloudConfig, environment, verbose, docker: options.docker === !0, createCloudDriver, deployAllComputeSites, ensureManagementDashboard, resolveSiteKind, onlySite: options.site || void 0 });
551
+ await runHetznerDeploy({ tsCloudConfig, environment, verbose, docker: options.docker === !0, createCloudDriver, deployAllComputeSites, ensureManagementDashboard, resolveSiteKind, onlySite: options.site || void 0, persistedAttachBox });
552
552
  } catch (err) {
553
553
  log.error("Hetzner deploy failed:");
554
554
  console.error(err instanceof Error ? err.stack || err.message : err);
@@ -567,6 +567,27 @@ export async function loadTsCloudDeployApi() {
567
567
  export function shouldInjectManagementDashboard(tsCloudConfig) {
568
568
  return !tsCloudConfig.cloud?.attachTo;
569
569
  }
570
+ export function resolvePersistedAttachTargetBox(tsCloudConfig, environment, cwd = process.cwd()) {
571
+ const owner = tsCloudConfig.cloud?.attachTo;
572
+ if (!owner)
573
+ return null;
574
+ const stackName = tsCloudConfig.project?.stackName || `${tsCloudConfig.project?.slug || "app"}-${environment}`, statePath = join(cwd, "storage", "cloud", "state", `${stackName}.json`);
575
+ if (!existsSync(statePath))
576
+ return null;
577
+ try {
578
+ const state = JSON.parse(readFileSync(statePath, "utf8"));
579
+ if (state.stackName !== stackName || typeof state.serverId !== "number" || typeof state.publicIp !== "string" || !state.publicIp.trim())
580
+ return null;
581
+ return {
582
+ serverId: state.serverId,
583
+ serverName: typeof state.serverName === "string" && state.serverName ? state.serverName : `${owner}-${environment}-app`,
584
+ publicIp: state.publicIp,
585
+ publicIpv6: typeof state.publicIpv6 === "string" ? state.publicIpv6 : void 0
586
+ };
587
+ } catch {
588
+ return null;
589
+ }
590
+ }
570
591
  export function scrubLoopbackSitePortsForFirewall(tsCloudConfig) {
571
592
  const sites = tsCloudConfig?.sites;
572
593
  if (!sites)
@@ -665,7 +686,7 @@ async function startGithubDeployments(args) {
665
686
  return records;
666
687
  }
667
688
  async function runHetznerDeploy(args) {
668
- const { tsCloudConfig, environment, verbose, docker, createCloudDriver, deployAllComputeSites, ensureManagementDashboard, resolveSiteKind, onlySite } = args, startTime = performance.now();
689
+ const { tsCloudConfig, environment, verbose, docker, createCloudDriver, deployAllComputeSites, ensureManagementDashboard, resolveSiteKind, onlySite, persistedAttachBox } = args, startTime = performance.now();
669
690
  console.log("");
670
691
  console.log("\uD83D\uDE80 Deploy \u2192 Hetzner Cloud");
671
692
  console.log("");
@@ -692,7 +713,7 @@ async function runHetznerDeploy(args) {
692
713
  const attachTo = tsCloudConfig.cloud?.attachTo;
693
714
  let ip, ipv6;
694
715
  if (attachTo) {
695
- const box = await resolveAttachTargetBox(attachTo, environment);
716
+ const box = persistedAttachBox ?? await resolveAttachTargetBox(attachTo, environment);
696
717
  if (!box?.publicIp) {
697
718
  log.error(`Attach target '${attachTo}' has no reachable box for '${environment}'. Is '${attachTo}-${environment}-app' provisioned (by its owner)?`);
698
719
  process.exit(ExitCode.FatalError);
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.222",
5
+ "version": "0.70.223",
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.90",
140
+ "@stacksjs/ts-cloud": "^0.7.91",
141
141
  "ajv": "^8.20.0",
142
142
  "ajv-formats": "^3.0.1",
143
143
  "ts-pantry": "^0.11.0"