@stacksjs/ts-cloud 0.3.1 → 0.4.1

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.
package/dist/index.js CHANGED
@@ -39,17 +39,22 @@ import { cpSync, mkdtempSync, rmSync, writeFileSync as writeFileSync32 } from "n
39
39
  import { tmpdir } from "node:os";
40
40
  import { dirname as dirname22, isAbsolute as isAbsolute3, join as join62, resolve as resolve13 } from "node:path";
41
41
  import { fileURLToPath } from "node:url";
42
- import { readFileSync as readFileSync4 } from "node:fs";
43
- import { dirname as dirname32, join as join72 } from "node:path";
44
- import { fileURLToPath as fileURLToPath2 } from "node:url";
45
42
  import { execFileSync } from "node:child_process";
46
- import { existsSync as existsSync52, mkdtempSync as mkdtempSync2, readdirSync as readdirSync52, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync32, writeFileSync as writeFileSync42 } from "node:fs";
43
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
47
44
  import { tmpdir as tmpdir2 } from "node:os";
48
- import { join as join8, relative as relative22 } from "node:path";
45
+ import { dirname as dirname32, join as join72 } from "node:path";
46
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
47
+ import { readFileSync as readFileSync5 } from "node:fs";
48
+ import { dirname as dirname42, join as join8 } from "node:path";
49
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
50
+ import { execFileSync as execFileSync2 } from "node:child_process";
51
+ import { existsSync as existsSync52, mkdtempSync as mkdtempSync3, readdirSync as readdirSync52, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync32, writeFileSync as writeFileSync52 } from "node:fs";
52
+ import { tmpdir as tmpdir3 } from "node:os";
53
+ import { join as join9, relative as relative22 } from "node:path";
49
54
  import { execSync as execSync2 } from "node:child_process";
50
55
  import { createHash as createHash5 } from "node:crypto";
51
- import { readdirSync as readdirSync62, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
52
- import { join as join9, relative as relative32, resolve as resolve22 } from "node:path";
56
+ import { readdirSync as readdirSync62, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
57
+ import { join as join10, relative as relative32, resolve as resolve22 } from "node:path";
53
58
  function __exportSetter3(name, newValue) {
54
59
  this[name] = __returnValue3.bind(null, newValue);
55
60
  }
@@ -25711,7 +25716,7 @@ async function packageServerlessApp(opts) {
25711
25716
  opts.onStep?.("bundling application");
25712
25717
  const result = await Bun.build({
25713
25718
  entrypoints: [bootstrapPath],
25714
- target: "node",
25719
+ target: app.kind === "bun" ? "bun" : "node",
25715
25720
  format: "esm",
25716
25721
  minify: false,
25717
25722
  sourcemap: "none"
@@ -25742,6 +25747,54 @@ ${logs}`);
25742
25747
  rmSync(stage, { recursive: true, force: true });
25743
25748
  }
25744
25749
  }
25750
+ function normalizeNodeVersion(v) {
25751
+ if (!v)
25752
+ return "22";
25753
+ const m = String(v).match(/(\d+)/);
25754
+ return m ? m[1] : "22";
25755
+ }
25756
+ function resolveServerlessRuntime(app) {
25757
+ const kind = app.kind ?? "node";
25758
+ if (kind === "php") {
25759
+ return {
25760
+ lambdaRuntime: "provided.al2023",
25761
+ kind: "php",
25762
+ version: app.phpVersion ?? "8.3",
25763
+ usesLayer: true,
25764
+ layerKind: "php",
25765
+ layerEnvVar: "TSCLOUD_PHP_LAYER_ARN"
25766
+ };
25767
+ }
25768
+ if (kind === "bun") {
25769
+ return {
25770
+ lambdaRuntime: "provided.al2023",
25771
+ kind: "bun",
25772
+ version: app.runtimeVersion ?? "latest",
25773
+ usesLayer: true,
25774
+ layerKind: "bun",
25775
+ layerEnvVar: "TSCLOUD_BUN_LAYER_ARN"
25776
+ };
25777
+ }
25778
+ const version2 = normalizeNodeVersion(app.runtimeVersion);
25779
+ const explicitProvided = app.runtime === "provided.al2023";
25780
+ const managed = !explicitProvided && MANAGED_NODE_VERSIONS.includes(version2);
25781
+ if (managed) {
25782
+ return {
25783
+ lambdaRuntime: app.runtime && app.runtime.startsWith("nodejs") ? app.runtime : `nodejs${version2}.x`,
25784
+ kind: "node",
25785
+ version: version2,
25786
+ usesLayer: false
25787
+ };
25788
+ }
25789
+ return {
25790
+ lambdaRuntime: "provided.al2023",
25791
+ kind: "node",
25792
+ version: version2,
25793
+ usesLayer: true,
25794
+ layerKind: "node",
25795
+ layerEnvVar: "TSCLOUD_NODE_LAYER_ARN"
25796
+ };
25797
+ }
25745
25798
  function resolveQueueNames(app, slug, env) {
25746
25799
  if (app.queues === false)
25747
25800
  return [];
@@ -25755,7 +25808,7 @@ function resolveQueueNames(app, slug, env) {
25755
25808
  function composeServerlessAppTemplate(opts) {
25756
25809
  const { app, environment, handlers } = opts;
25757
25810
  const slug = opts.config.project.slug;
25758
- const runtime = app.runtime ?? "nodejs20.x";
25811
+ const runtime = resolveServerlessRuntime(app).lambdaRuntime;
25759
25812
  const architecture = app.architecture ?? "x86_64";
25760
25813
  const region = opts.config.project.region;
25761
25814
  const functionNames = {
@@ -26360,6 +26413,78 @@ COPY app/ \${LAMBDA_TASK_ROOT}/
26360
26413
  CMD [ "index.http" ]
26361
26414
  `;
26362
26415
  }
26416
+ function assetsDir() {
26417
+ return dirname32(fileURLToPath2(import.meta.url));
26418
+ }
26419
+ function sharedRuntimeLoop() {
26420
+ return readFileSync4(join72(assetsDir(), "runtime.mjs"), "utf-8");
26421
+ }
26422
+ function bootstrap(kind) {
26423
+ return readFileSync4(join72(assetsDir(), `${kind}-bootstrap`), "utf-8");
26424
+ }
26425
+ function buildNodeRuntimeLayerZip(options) {
26426
+ const architecture = options.architecture ?? "x86_64";
26427
+ const nodeArch = architecture === "arm64" ? "arm64" : "x64";
26428
+ const version2 = options.version.replace(/^v/, "");
26429
+ const step = options.onStep ?? (() => {});
26430
+ const stage = mkdtempSync2(join72(tmpdir2(), "tscloud-node-layer-"));
26431
+ try {
26432
+ const exact = version2.includes(".") ? version2 : resolveLatestNode(version2);
26433
+ const dir = `node-v${exact}-linux-${nodeArch}`;
26434
+ const url = `https://nodejs.org/dist/v${exact}/${dir}.tar.xz`;
26435
+ step(`downloading Node ${exact} (${architecture})`);
26436
+ const tar = join72(stage, "node.tar.xz");
26437
+ fetchToFile(url, tar);
26438
+ step("extracting node binary");
26439
+ execFileSync("tar", ["-xf", tar, "-C", stage, `${dir}/bin/node`], { stdio: "inherit" });
26440
+ const nodeBin = readFileSync4(join72(stage, dir, "bin", "node"));
26441
+ step("packaging layer");
26442
+ const entries = [
26443
+ { name: "bootstrap", data: bootstrap("node"), mode: 493 },
26444
+ { name: "runtime.mjs", data: sharedRuntimeLoop(), mode: 420 },
26445
+ { name: "bin/node", data: nodeBin, mode: 493 }
26446
+ ];
26447
+ return { zip: createZip(entries), architecture, version: exact, fileCount: entries.length };
26448
+ } finally {
26449
+ rmSync2(stage, { recursive: true, force: true });
26450
+ }
26451
+ }
26452
+ function buildBunRuntimeLayerZip(options) {
26453
+ const architecture = options.architecture ?? "x86_64";
26454
+ const bunArch = architecture === "arm64" ? "bun-linux-aarch64" : "bun-linux-x64";
26455
+ const version2 = options.version === "latest" ? "" : options.version.replace(/^v/, "");
26456
+ const step = options.onStep ?? (() => {});
26457
+ const stage = mkdtempSync2(join72(tmpdir2(), "tscloud-bun-layer-"));
26458
+ try {
26459
+ const url = version2 ? `https://github.com/oven-sh/bun/releases/download/bun-v${version2}/${bunArch}.zip` : `https://github.com/oven-sh/bun/releases/latest/download/${bunArch}.zip`;
26460
+ step(`downloading Bun ${version2 || "latest"} (${architecture})`);
26461
+ const zipPath = join72(stage, "bun.zip");
26462
+ fetchToFile(url, zipPath);
26463
+ step("extracting bun binary");
26464
+ execFileSync("unzip", ["-o", "-q", zipPath, "-d", stage], { stdio: "inherit" });
26465
+ const bunBin = readFileSync4(join72(stage, bunArch, "bun"));
26466
+ step("packaging layer");
26467
+ const entries = [
26468
+ { name: "bootstrap", data: bootstrap("bun"), mode: 493 },
26469
+ { name: "runtime.mjs", data: sharedRuntimeLoop(), mode: 420 },
26470
+ { name: "bin/bun", data: bunBin, mode: 493 }
26471
+ ];
26472
+ return { zip: createZip(entries), architecture, version: version2 || "latest", fileCount: entries.length };
26473
+ } finally {
26474
+ rmSync2(stage, { recursive: true, force: true });
26475
+ }
26476
+ }
26477
+ function fetchToFile(url, dest) {
26478
+ execFileSync("curl", ["-fsSL", "-o", dest, url], { stdio: ["ignore", "ignore", "inherit"] });
26479
+ }
26480
+ function resolveLatestNode(major) {
26481
+ const indexJson = execFileSync("curl", ["-fsSL", "https://nodejs.org/dist/index.json"], { encoding: "utf-8" });
26482
+ const releases = JSON.parse(indexJson);
26483
+ const match = releases.find((r) => r.version.startsWith(`v${major}.`));
26484
+ if (!match)
26485
+ throw new Error(`No Node release found for major version ${major}`);
26486
+ return match.version.replace(/^v/, "");
26487
+ }
26363
26488
  function resolveApp(mod) {
26364
26489
  const m = mod;
26365
26490
  const def = m?.default ?? m;
@@ -26508,12 +26633,12 @@ php_admin_flag[log_errors] = on
26508
26633
  clear_env = no
26509
26634
  `;
26510
26635
  }
26511
- function assetsDir() {
26512
- return join72(dirname32(fileURLToPath2(import.meta.url)), "runtime-assets");
26636
+ function assetsDir2() {
26637
+ return join8(dirname42(fileURLToPath3(import.meta.url)), "runtime-assets");
26513
26638
  }
26514
26639
  function phpRuntimeLayerAssets() {
26515
- const dir = assetsDir();
26516
- const read = (f) => readFileSync4(join72(dir, f), "utf-8");
26640
+ const dir = assetsDir2();
26641
+ const read = (f) => readFileSync5(join8(dir, f), "utf-8");
26517
26642
  return [
26518
26643
  { path: "bootstrap", contents: read("bootstrap"), mode: 493 },
26519
26644
  { path: "tscloud/runtime.php", contents: read("runtime.php"), mode: 420 },
@@ -26543,7 +26668,7 @@ function laravelServerlessEnvDefaults(opts = {}) {
26543
26668
  }
26544
26669
  function* walk(dir) {
26545
26670
  for (const entry of readdirSync52(dir)) {
26546
- const full = join8(dir, entry);
26671
+ const full = join9(dir, entry);
26547
26672
  if (statSync32(full).isDirectory())
26548
26673
  yield* walk(full);
26549
26674
  else
@@ -26554,19 +26679,19 @@ function buildPhpRuntimeLayerZip(options = {}) {
26554
26679
  const architecture = options.architecture ?? "x86_64";
26555
26680
  const platform = options.platform ?? (architecture === "arm64" ? "linux/arm64" : "linux/amd64");
26556
26681
  const step = options.onStep ?? (() => {});
26557
- const stage = mkdtempSync2(join8(tmpdir2(), "tscloud-php-layer-"));
26682
+ const stage = mkdtempSync3(join9(tmpdir3(), "tscloud-php-layer-"));
26558
26683
  const imageTag = "tscloud-php-layer:build";
26559
26684
  try {
26560
- writeFileSync42(join8(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
26685
+ writeFileSync52(join9(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
26561
26686
  step("Building PHP runtime image (docker)");
26562
- execFileSync("docker", ["build", "--platform", platform, "-t", imageTag, stage], { stdio: "inherit" });
26687
+ execFileSync2("docker", ["build", "--platform", platform, "-t", imageTag, stage], { stdio: "inherit" });
26563
26688
  step("Extracting /opt from image");
26564
- const cid = execFileSync("docker", ["create", "--platform", platform, imageTag], { encoding: "utf-8" }).trim();
26565
- const optDir = join8(stage, "opt");
26689
+ const cid = execFileSync2("docker", ["create", "--platform", platform, imageTag], { encoding: "utf-8" }).trim();
26690
+ const optDir = join9(stage, "opt");
26566
26691
  try {
26567
- execFileSync("docker", ["cp", `${cid}:/opt/.`, optDir], { stdio: "inherit" });
26692
+ execFileSync2("docker", ["cp", `${cid}:/opt/.`, optDir], { stdio: "inherit" });
26568
26693
  } finally {
26569
- execFileSync("docker", ["rm", cid], { stdio: "ignore" });
26694
+ execFileSync2("docker", ["rm", cid], { stdio: "ignore" });
26570
26695
  }
26571
26696
  if (!existsSync52(optDir))
26572
26697
  throw new Error("layer build produced no /opt directory");
@@ -26574,7 +26699,7 @@ function buildPhpRuntimeLayerZip(options = {}) {
26574
26699
  for (const file of walk(optDir)) {
26575
26700
  const rel = relative22(optDir, file).replace(/\\/g, "/");
26576
26701
  const mode = statSync32(file).mode & 73 ? 493 : 420;
26577
- entries.push({ name: rel, data: readFileSync5(file), mode });
26702
+ entries.push({ name: rel, data: readFileSync6(file), mode });
26578
26703
  }
26579
26704
  step("Injecting runtime assets");
26580
26705
  const assetPaths = new Set(phpRuntimeLayerAssets().map((a) => a.path));
@@ -26586,7 +26711,7 @@ function buildPhpRuntimeLayerZip(options = {}) {
26586
26711
  const zip2 = createZip(filtered);
26587
26712
  return { zip: zip2, architecture, fileCount: filtered.length };
26588
26713
  } finally {
26589
- rmSync2(stage, { recursive: true, force: true });
26714
+ rmSync3(stage, { recursive: true, force: true });
26590
26715
  }
26591
26716
  }
26592
26717
  function isExcluded(rel, excludes) {
@@ -26594,7 +26719,7 @@ function isExcluded(rel, excludes) {
26594
26719
  }
26595
26720
  function* walk2(dir, root, excludes) {
26596
26721
  for (const entry of readdirSync62(dir)) {
26597
- const full = join9(dir, entry);
26722
+ const full = join10(dir, entry);
26598
26723
  const rel = relative32(root, full).replace(/\\/g, "/");
26599
26724
  if (isExcluded(rel, excludes))
26600
26725
  continue;
@@ -26621,7 +26746,7 @@ function collectPhpAppEntries(projectRoot, exclude = []) {
26621
26746
  for (const file of walk2(root, root, excludes)) {
26622
26747
  const rel = relative32(root, file).replace(/\\/g, "/");
26623
26748
  const executable = (statSync4(file).mode & 73) !== 0;
26624
- entries.push({ name: rel, data: readFileSync6(file), mode: executable ? 493 : 420 });
26749
+ entries.push({ name: rel, data: readFileSync7(file), mode: executable ? 493 : 420 });
26625
26750
  }
26626
26751
  return entries;
26627
26752
  }
@@ -28068,7 +28193,7 @@ catch (error) {
28068
28193
 
28069
28194
  return { statusCode: 200, body: 'OK' };
28070
28195
  };
28071
- `, inbound_default, exports_advanced2, IVRBuilder, exports_advanced3, MmsSupport, SmsChatbot, fifoQueueManager, dlqMonitoringManager, batchProcessingManager, queueManagementManager, CRC_TABLE, PHP_LAYER_EXTENSIONS, TEXT_CONTENT, LARAVEL_SERVERLESS_BUILD_STEPS, PHP_DEFAULT_EXCLUDES, staticSiteManager, storageAdvancedManager, healthCheckManager, networkSecurityManager, resourceManagementManager;
28196
+ `, inbound_default, exports_advanced2, IVRBuilder, exports_advanced3, MmsSupport, SmsChatbot, fifoQueueManager, dlqMonitoringManager, batchProcessingManager, queueManagementManager, CRC_TABLE, MANAGED_NODE_VERSIONS, PHP_LAYER_EXTENSIONS, TEXT_CONTENT, LARAVEL_SERVERLESS_BUILD_STEPS, PHP_DEFAULT_EXCLUDES, staticSiteManager, storageAdvancedManager, healthCheckManager, networkSecurityManager, resourceManagementManager;
28072
28197
  var init_dist2 = __esm(async () => {
28073
28198
  __defProp3 = Object.defineProperty;
28074
28199
  __require3 = /* @__PURE__ */ createRequire2(import.meta.url);
@@ -41791,7 +41916,8 @@ Details:`;
41791
41916
  type: "object",
41792
41917
  description: "Serverless application manifest (Laravel-Vapor-equivalent). Defining this opts the environment into the serverless app deploy pipeline (http/queue/cli Lambda functions, assets, build/deploy hooks).",
41793
41918
  properties: {
41794
- runtime: { type: "string", description: "Lambda runtime (e.g. nodejs20.x, provided.al2023)" },
41919
+ runtime: { type: "string", description: "Lambda runtime override (usually derived from kind + runtimeVersion)" },
41920
+ runtimeVersion: { type: "string", description: "Node major (18/20/22 managed, 24+ custom layer) or Bun release for node/bun apps" },
41795
41921
  kind: { type: "string", enum: ["node", "bun", "php"], description: "Application kind (drives packaging + runtime)" },
41796
41922
  entry: { type: "string", description: "Entry file exporting the request handler" },
41797
41923
  memory: { type: "number", description: "HTTP function memory in MB" },
@@ -45604,6 +45730,7 @@ async function sendFallbackSms(to, message) {
45604
45730
  }
45605
45731
  return table2;
45606
45732
  })();
45733
+ MANAGED_NODE_VERSIONS = ["18", "20", "22"];
45607
45734
  PHP_LAYER_EXTENSIONS = [
45608
45735
  "cli",
45609
45736
  "fpm",
@@ -46829,9 +46956,9 @@ __export(exports_client, {
46829
46956
  AWSClient: () => AWSClient
46830
46957
  });
46831
46958
  import * as crypto2 from "node:crypto";
46832
- import { existsSync as existsSync14, readFileSync as readFileSync7 } from "node:fs";
46959
+ import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
46833
46960
  import { homedir as homedir6 } from "node:os";
46834
- import { join as join10 } from "node:path";
46961
+ import { join as join11 } from "node:path";
46835
46962
  function resolveS3Endpoint(options) {
46836
46963
  const base = options.endpoint || `s3.${options.region}.amazonaws.com`;
46837
46964
  if (!options.bucket) {
@@ -46889,12 +47016,12 @@ class AWSClient {
46889
47016
  }
46890
47017
  loadCredentialsFromFile() {
46891
47018
  const profile = process.env.AWS_PROFILE || "default";
46892
- const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join10(homedir6(), ".aws", "credentials");
47019
+ const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join11(homedir6(), ".aws", "credentials");
46893
47020
  if (!existsSync14(credentialsPath)) {
46894
47021
  return null;
46895
47022
  }
46896
47023
  try {
46897
- const content = readFileSync7(credentialsPath, "utf-8");
47024
+ const content = readFileSync8(credentialsPath, "utf-8");
46898
47025
  const credentials = this.parseCredentialsFile(content, profile);
46899
47026
  if (credentials.accessKeyId && credentials.secretAccessKey) {
46900
47027
  return credentials;
@@ -47388,7 +47515,7 @@ function detectCredentialSource() {
47388
47515
  };
47389
47516
  }
47390
47517
  const profile = process.env.AWS_PROFILE || "default";
47391
- const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join10(homedir6(), ".aws", "credentials");
47518
+ const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join11(homedir6(), ".aws", "credentials");
47392
47519
  if (existsSync14(credentialsPath)) {
47393
47520
  return {
47394
47521
  source: "file",
@@ -47430,13 +47557,13 @@ function resolveCredentials2(profile) {
47430
47557
  return loadProfileFromFile(process.env.AWS_PROFILE || "default") ?? { accessKeyId: "", secretAccessKey: "" };
47431
47558
  }
47432
47559
  function loadProfileFromFile(profile) {
47433
- const { existsSync: existsSync15, readFileSync: readFileSync8 } = __require("node:fs");
47560
+ const { existsSync: existsSync15, readFileSync: readFileSync9 } = __require("node:fs");
47434
47561
  const { homedir: homedir5 } = __require("node:os");
47435
- const { join: join11 } = __require("node:path");
47436
- const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join11(homedir5(), ".aws", "credentials");
47562
+ const { join: join12 } = __require("node:path");
47563
+ const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join12(homedir5(), ".aws", "credentials");
47437
47564
  if (!existsSync15(credentialsPath))
47438
47565
  return null;
47439
- const content = readFileSync8(credentialsPath, "utf-8");
47566
+ const content = readFileSync9(credentialsPath, "utf-8");
47440
47567
  let currentProfile = null;
47441
47568
  let accessKeyId;
47442
47569
  let secretAccessKey;
@@ -47482,8 +47609,8 @@ function loadProfileFromFile(profile) {
47482
47609
  // src/aws/s3.ts
47483
47610
  import * as crypto3 from "node:crypto";
47484
47611
  import { readdir as readdir4 } from "node:fs/promises";
47485
- import { join as join12 } from "node:path";
47486
- import { readFileSync as readFileSync9 } from "node:fs";
47612
+ import { join as join13 } from "node:path";
47613
+ import { readFileSync as readFileSync10 } from "node:fs";
47487
47614
  function toFetchBody(data) {
47488
47615
  const { buffer, byteOffset, byteLength } = data;
47489
47616
  if (buffer instanceof ArrayBuffer) {
@@ -47885,7 +48012,7 @@ class S3Client2 {
47885
48012
  }
47886
48013
  }
47887
48014
  async copy(options) {
47888
- const fileContent = readFileSync9(options.source);
48015
+ const fileContent = readFileSync10(options.source);
47889
48016
  await this.putObject({
47890
48017
  bucket: options.bucket,
47891
48018
  key: options.key,
@@ -47908,7 +48035,7 @@ class S3Client2 {
47908
48035
  const relativePath = file.substring(options.source.length + 1);
47909
48036
  const s3Key = options.prefix ? `${options.prefix}/${relativePath}` : relativePath;
47910
48037
  if (!options.dryRun) {
47911
- const fileContent = readFileSync9(file);
48038
+ const fileContent = readFileSync10(file);
47912
48039
  await this.putObject({
47913
48040
  bucket: options.bucket,
47914
48041
  key: s3Key,
@@ -47939,7 +48066,7 @@ class S3Client2 {
47939
48066
  const files = [];
47940
48067
  const entries = await readdir4(dir, { withFileTypes: true });
47941
48068
  for (const entry of entries) {
47942
- const fullPath = join12(dir, entry.name);
48069
+ const fullPath = join13(dir, entry.name);
47943
48070
  if (entry.isDirectory()) {
47944
48071
  const subFiles = await this.listFilesRecursive(fullPath);
47945
48072
  files.push(...subFiles);
@@ -56895,13 +57022,13 @@ async function uploadStaticFiles(options) {
56895
57022
  const { sourceDir, bucket, region, cacheControl = "max-age=31536000, public", onProgress } = options;
56896
57023
  const s32 = new S3Client2(region);
56897
57024
  const { readdir: readdir5 } = await import("node:fs/promises");
56898
- const { join: join11, relative: relative5 } = await import("node:path");
57025
+ const { join: join12, relative: relative5 } = await import("node:path");
56899
57026
  const { createHash: createHash7 } = await import("node:crypto");
56900
57027
  async function listFiles(dir) {
56901
57028
  const files2 = [];
56902
57029
  const entries = await readdir5(dir, { withFileTypes: true });
56903
57030
  for (const entry of entries) {
56904
- const fullPath = join11(dir, entry.name);
57031
+ const fullPath = join12(dir, entry.name);
56905
57032
  if (entry.isDirectory()) {
56906
57033
  files2.push(...await listFiles(fullPath));
56907
57034
  } else {
@@ -57122,13 +57249,13 @@ var exports_serverless_image = {};
57122
57249
  __export(exports_serverless_image, {
57123
57250
  buildAndPushServerlessImage: () => buildAndPushServerlessImage
57124
57251
  });
57125
- import { execFileSync as execFileSync2, execSync as execSync3 } from "node:child_process";
57126
- import { mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
57127
- import { tmpdir as tmpdir3 } from "node:os";
57128
- import { dirname as dirname7, join as join14 } from "node:path";
57252
+ import { execFileSync as execFileSync3, execSync as execSync3 } from "node:child_process";
57253
+ import { mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "node:fs";
57254
+ import { tmpdir as tmpdir4 } from "node:os";
57255
+ import { dirname as dirname7, join as join15 } from "node:path";
57129
57256
  function writeEntries(baseDir, entries) {
57130
57257
  for (const e of entries) {
57131
- const dest = join14(baseDir, e.name);
57258
+ const dest = join15(baseDir, e.name);
57132
57259
  mkdirSync7(dirname7(dest), { recursive: true });
57133
57260
  writeFileSync10(dest, e.data, e.mode ? { mode: e.mode } : undefined);
57134
57261
  }
@@ -57139,9 +57266,9 @@ async function buildAndPushServerlessImage(opts) {
57139
57266
  const platform = arch === "arm64" ? "linux/arm64" : "linux/amd64";
57140
57267
  const isPhp = app.kind === "php";
57141
57268
  const step2 = opts.onStep ?? (() => {});
57142
- const stage = mkdtempSync3(join14(tmpdir3(), "tscloud-img-"));
57269
+ const stage = mkdtempSync4(join15(tmpdir4(), "tscloud-img-"));
57143
57270
  try {
57144
- const appDir = join14(stage, "app");
57271
+ const appDir = join15(stage, "app");
57145
57272
  mkdirSync7(appDir, { recursive: true });
57146
57273
  let handlers;
57147
57274
  let contentHash;
@@ -57150,19 +57277,19 @@ async function buildAndPushServerlessImage(opts) {
57150
57277
  step2("staging application tree");
57151
57278
  const entries = collectPhpAppEntries(projectRoot);
57152
57279
  writeEntries(appDir, entries);
57153
- writeEntries(join14(stage, "runtime"), phpRuntimeLayerAssets().map((a) => ({ name: a.path, data: a.contents, mode: a.mode })));
57280
+ writeEntries(join15(stage, "runtime"), phpRuntimeLayerAssets().map((a) => ({ name: a.path, data: a.contents, mode: a.mode })));
57154
57281
  const handler8 = app.handlers?.http ?? "public/index.php";
57155
57282
  handlers = { http: handler8, queue: app.handlers?.queue ?? handler8, cli: app.handlers?.cli ?? handler8 };
57156
57283
  contentHash = sha256(entries.map((e) => `${e.name}:${e.data.length}`).join("|"));
57157
57284
  } else {
57158
57285
  step2("bundling application");
57159
57286
  const artifact = await packageServerlessApp({ projectRoot, app, skipBuild: opts.skipBuild, onStep: step2 });
57160
- writeFileSync10(join14(appDir, "index.mjs"), artifact.bundle);
57287
+ writeFileSync10(join15(appDir, "index.mjs"), artifact.bundle);
57161
57288
  handlers = artifact.handlers;
57162
57289
  contentHash = artifact.sha256;
57163
57290
  }
57164
57291
  const tag = contentHash.slice(0, 16);
57165
- writeFileSync10(join14(stage, "Dockerfile"), generateAppImageDockerfile({
57292
+ writeFileSync10(join15(stage, "Dockerfile"), generateAppImageDockerfile({
57166
57293
  kind: app.kind ?? "node",
57167
57294
  phpVersion: app.phpVersion,
57168
57295
  architecture: arch
@@ -57184,16 +57311,16 @@ async function buildAndPushServerlessImage(opts) {
57184
57311
  throw new Error(`could not resolve ECR repository URI for ${repository}`);
57185
57312
  const imageUri = `${repoUri}:${tag}`;
57186
57313
  step2("docker build");
57187
- execFileSync2("docker", ["build", "--platform", platform, "-t", imageUri, stage], { stdio: "inherit" });
57314
+ execFileSync3("docker", ["build", "--platform", platform, "-t", imageUri, stage], { stdio: "inherit" });
57188
57315
  step2("ecr login");
57189
57316
  const loginCmd = await ecr2.getDockerLoginCommand();
57190
57317
  execSync3(loginCmd, { stdio: "inherit" });
57191
57318
  step2("docker push");
57192
- execFileSync2("docker", ["push", imageUri], { stdio: "inherit" });
57319
+ execFileSync3("docker", ["push", imageUri], { stdio: "inherit" });
57193
57320
  info(`Image: ${imageUri}`);
57194
57321
  return { imageUri, tag, handlers };
57195
57322
  } finally {
57196
- rmSync3(stage, { recursive: true, force: true });
57323
+ rmSync4(stage, { recursive: true, force: true });
57197
57324
  }
57198
57325
  }
57199
57326
  var init_serverless_image = __esm(async () => {
@@ -78421,7 +78548,7 @@ class ApplicationAutoScalingClient {
78421
78548
  init_s3();
78422
78549
  import * as net from "node:net";
78423
78550
  import * as tls from "node:tls";
78424
- import { readFileSync as readFileSync10 } from "node:fs";
78551
+ import { readFileSync as readFileSync11 } from "node:fs";
78425
78552
  import * as crypto4 from "node:crypto";
78426
78553
 
78427
78554
  class ImapServer {
@@ -78456,8 +78583,8 @@ class ImapServer {
78456
78583
  });
78457
78584
  if (this.config.tls?.key && this.config.tls?.cert) {
78458
78585
  const tlsOptions = {
78459
- key: readFileSync10(this.config.tls.key),
78460
- cert: readFileSync10(this.config.tls.cert)
78586
+ key: readFileSync11(this.config.tls.key),
78587
+ cert: readFileSync11(this.config.tls.cert)
78461
78588
  };
78462
78589
  this.tlsServer = tls.createServer(tlsOptions, (socket) => {
78463
78590
  this.handleConnection(socket);
@@ -79261,8 +79388,8 @@ class ImapServer {
79261
79388
  }
79262
79389
  this.send(session, `${tag} OK Begin TLS negotiation`);
79263
79390
  const tlsOptions = {
79264
- key: readFileSync10(this.config.tls.key),
79265
- cert: readFileSync10(this.config.tls.cert),
79391
+ key: readFileSync11(this.config.tls.key),
79392
+ cert: readFileSync11(this.config.tls.cert),
79266
79393
  isServer: true
79267
79394
  };
79268
79395
  const tlsSocket = new tls.TLSSocket(session.socket, tlsOptions);
@@ -79855,7 +79982,7 @@ init_ses();
79855
79982
  init_s3();
79856
79983
  import * as net2 from "node:net";
79857
79984
  import * as tls2 from "node:tls";
79858
- import { readFileSync as readFileSync11 } from "node:fs";
79985
+ import { readFileSync as readFileSync12 } from "node:fs";
79859
79986
  import * as crypto5 from "node:crypto";
79860
79987
 
79861
79988
  class SmtpServer {
@@ -79886,8 +80013,8 @@ class SmtpServer {
79886
80013
  });
79887
80014
  if (this.config.tls?.key && this.config.tls?.cert) {
79888
80015
  const tlsOptions = {
79889
- key: readFileSync11(this.config.tls.key),
79890
- cert: readFileSync11(this.config.tls.cert)
80016
+ key: readFileSync12(this.config.tls.key),
80017
+ cert: readFileSync12(this.config.tls.cert)
79891
80018
  };
79892
80019
  this.tlsServer = tls2.createServer(tlsOptions, (socket) => {
79893
80020
  this.handleConnection(socket, true);
@@ -80021,8 +80148,8 @@ class SmtpServer {
80021
80148
  }
80022
80149
  this.send(session, "220 Ready to start TLS");
80023
80150
  const tlsOptions = {
80024
- key: readFileSync11(this.config.tls.key),
80025
- cert: readFileSync11(this.config.tls.cert),
80151
+ key: readFileSync12(this.config.tls.key),
80152
+ cert: readFileSync12(this.config.tls.cert),
80026
80153
  isServer: true
80027
80154
  };
80028
80155
  const tlsSocket = new tls2.TLSSocket(session.socket, tlsOptions);
@@ -82721,11 +82848,11 @@ init_static_site_external_dns();
82721
82848
  init_static_site_external_dns();
82722
82849
  import process19 from "node:process";
82723
82850
  import { existsSync as existsSync16 } from "node:fs";
82724
- import { join as join13 } from "node:path";
82851
+ import { join as join14 } from "node:path";
82725
82852
  async function deploySite(config6) {
82726
82853
  const start = Date.now();
82727
82854
  const sourceDir = config6.sourceDir ?? "dist";
82728
- if (!existsSync16(join13(sourceDir, "index.html"))) {
82855
+ if (!existsSync16(join14(sourceDir, "index.html"))) {
82729
82856
  return fail(start, `${sourceDir}/ has no index.html — build first.`);
82730
82857
  }
82731
82858
  if (!process19.env.AWS_ACCESS_KEY_ID || !process19.env.AWS_SECRET_ACCESS_KEY) {
@@ -82796,8 +82923,8 @@ function fail(start, message) {
82796
82923
  // src/deploy/serverless-app.ts
82797
82924
  init_cloudformation();
82798
82925
  await init_dist2();
82799
- import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync5 } from "node:fs";
82800
- import { join as join15, relative as relative6 } from "node:path";
82926
+ import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync5 } from "node:fs";
82927
+ import { join as join16, relative as relative6 } from "node:path";
82801
82928
  init_s3();
82802
82929
  init_cli();
82803
82930
  function resolveContext(config6, environment) {
@@ -82925,7 +83052,7 @@ function contentType(file) {
82925
83052
  }
82926
83053
  function* walk3(dir) {
82927
83054
  for (const entry of readdirSync8(dir)) {
82928
- const full = join15(dir, entry);
83055
+ const full = join16(dir, entry);
82929
83056
  if (statSync5(full).isDirectory())
82930
83057
  yield* walk3(full);
82931
83058
  else
@@ -82939,7 +83066,7 @@ async function uploadAssets(s32, bucket, dir, prefix) {
82939
83066
  await s32.putObject({
82940
83067
  bucket,
82941
83068
  key,
82942
- body: readFileSync12(file),
83069
+ body: readFileSync13(file),
82943
83070
  contentType: contentType(file),
82944
83071
  cacheControl: "public, max-age=31536000, immutable"
82945
83072
  });
@@ -82966,11 +83093,15 @@ async function deployServerlessApp(config6, environment, opts = {}) {
82966
83093
  const lambda2 = new LambdaClient(region);
82967
83094
  const isPhp = app.kind === "php";
82968
83095
  const imageMode = app.packaging === "image";
83096
+ const resolved = resolveServerlessRuntime(app);
82969
83097
  let handlers;
82970
83098
  let codeSource;
82971
83099
  let artifactSha;
82972
83100
  let runtimeLayers;
82973
83101
  const parameters = [];
83102
+ if (imageMode && resolved.usesLayer && resolved.layerKind !== "php") {
83103
+ throw new Error(`packaging: 'image' currently supports PHP and managed Node runtimes. ` + `${resolved.kind}${resolved.kind === "node" ? ` ${resolved.version}` : ""} uses a custom runtime — ` + `use the default zip packaging (the runtime layer is built + attached automatically).`);
83104
+ }
82974
83105
  if (imageMode) {
82975
83106
  step("Building + pushing container image");
82976
83107
  const { buildAndPushServerlessImage: buildAndPushServerlessImage2 } = await init_serverless_image().then(() => exports_serverless_image);
@@ -82993,10 +83124,12 @@ async function deployServerlessApp(config6, environment, opts = {}) {
82993
83124
  info(`Artifact: ${artifact.sha256.slice(0, 12)} (${(artifact.zip.length / 1024).toFixed(0)} KB)`);
82994
83125
  handlers = artifact.handlers;
82995
83126
  artifactSha = artifact.sha256;
82996
- if (isPhp) {
82997
- runtimeLayers = app.layers ?? (process.env.TSCLOUD_PHP_LAYER_ARN ? [process.env.TSCLOUD_PHP_LAYER_ARN] : undefined);
83127
+ if (resolved.usesLayer) {
83128
+ const envArn = resolved.layerEnvVar ? process.env[resolved.layerEnvVar] : undefined;
83129
+ runtimeLayers = app.layers ?? (envArn ? [envArn] : undefined);
82998
83130
  if (!runtimeLayers?.length) {
82999
- throw new Error("PHP serverless apps need a runtime layer. Set environments.<env>.app.layers or the TSCLOUD_PHP_LAYER_ARN env var (build one with `cloud serverless:build-php-layer`), or use packaging: 'image'.");
83131
+ const buildCmd = resolved.layerKind === "php" ? "serverless:build-php-layer" : resolved.layerKind === "bun" ? "serverless:build-bun-layer" : "serverless:build-node-layer";
83132
+ throw new Error(`${resolved.kind} apps on provided.al2023 need a runtime layer. Set environments.<env>.app.layers or the ${resolved.layerEnvVar} env var (build one with \`cloud ${buildCmd}\`)${resolved.kind === "node" ? ", or use a managed Node version (18/20/22)" : ""}.`);
83000
83133
  }
83001
83134
  }
83002
83135
  if (!await s32.bucketExists(artifactBucket)) {
@@ -83036,15 +83169,15 @@ async function deployServerlessApp(config6, environment, opts = {}) {
83036
83169
  const outputs = await cfn.getStackOutputs(stackName).catch(() => ({}));
83037
83170
  let assetUrl;
83038
83171
  if (app.assets) {
83039
- const assetsDir2 = join15(projectRoot, app.assets);
83040
- if (existsSync17(assetsDir2)) {
83172
+ const assetsDir3 = join16(projectRoot, app.assets);
83173
+ if (existsSync17(assetsDir3)) {
83041
83174
  const cdn = outputs.AssetsCdnDomain;
83042
83175
  assetUrl = cdn ? `https://${cdn}/${artifactSha}` : undefined;
83043
83176
  step(`Syncing assets from ${app.assets}`);
83044
- const n = await uploadAssets(s32, ctx.assetsBucket, assetsDir2, artifactSha);
83177
+ const n = await uploadAssets(s32, ctx.assetsBucket, assetsDir3, artifactSha);
83045
83178
  info(`Uploaded ${n} asset(s)${assetUrl ? ` → ${assetUrl}` : ""}`);
83046
83179
  } else {
83047
- warn(`Assets directory not found: ${assetsDir2}`);
83180
+ warn(`Assets directory not found: ${assetsDir3}`);
83048
83181
  }
83049
83182
  }
83050
83183
  step("Resolving secrets");
@@ -83185,10 +83318,26 @@ await init_dist2();
83185
83318
  // src/drivers/aws/driver.ts
83186
83319
  init_cloudformation();
83187
83320
  await init_dist2();
83188
- import { readFileSync as readFileSync13 } from "node:fs";
83321
+ import { readFileSync as readFileSync14 } from "node:fs";
83189
83322
  init_s3();
83190
83323
 
83191
83324
  // src/drivers/shared/package-manager.ts
83325
+ var PANTRY_PACKAGES = {
83326
+ php: "php.net",
83327
+ nginx: "nginx.org",
83328
+ composer: "getcomposer.org",
83329
+ mysql: "mysql.com",
83330
+ mariadb: "mariadb.com/server",
83331
+ postgres: "postgresql.org",
83332
+ redis: "redis.io",
83333
+ memcached: "memcached.org",
83334
+ meilisearch: "meilisearch.com",
83335
+ git: "git-scm.com",
83336
+ certbot: "certbot.eff.org",
83337
+ bun: "bun.sh",
83338
+ node: "nodejs.org",
83339
+ deno: "deno.land"
83340
+ };
83192
83341
  var PANTRY_INSTALL_DIR = "/usr/local/bin";
83193
83342
  var PANTRY_PROJECT_DIR = "/opt/pantry";
83194
83343
  function sh(value) {
@@ -83228,10 +83377,6 @@ function buildPantryServiceScript(services) {
83228
83377
  function enabled(spec) {
83229
83378
  return spec === true || typeof spec === "object" && spec != null;
83230
83379
  }
83231
- function sq(value) {
83232
- const escaped = value.split("'").join("'\\''");
83233
- return `'${escaped}'`;
83234
- }
83235
83380
  function planServices(services) {
83236
83381
  const packages = [];
83237
83382
  const names = [];
@@ -83239,7 +83384,7 @@ function planServices(services) {
83239
83384
  packages.push("mysql.com");
83240
83385
  names.push("mysql");
83241
83386
  } else if (enabled(services.mariadb)) {
83242
- packages.push("mariadb.org");
83387
+ packages.push(PANTRY_PACKAGES.mariadb);
83243
83388
  names.push("mariadb");
83244
83389
  }
83245
83390
  if (enabled(services.postgres)) {
@@ -83281,17 +83426,26 @@ function buildDatabaseSetupScript(database, services = {}) {
83281
83426
  const useMariadb = enabled(services.mariadb) || database.engine === "mariadb";
83282
83427
  const useMysql = enabled(services.mysql) || database.engine === "mysql";
83283
83428
  if (usePostgres && !useMysql && !useMariadb) {
83284
- const psql = "psql -h 127.0.0.1 -p 5432 -U postgres";
83429
+ const pgIdent = (v) => `"${v.replace(/"/g, '""')}"`;
83430
+ const pgLit = (v) => `'${v.replace(/'/g, "''")}'`;
83285
83431
  return [
83286
83432
  pantryEnvActivation(),
83287
- `${psql} -tc "SELECT 1 FROM pg_roles WHERE rolname=${sq(user)}" | grep -q 1 ` + `|| ${psql} -c "CREATE ROLE ${sq(user)} LOGIN PASSWORD ${sq(pass)};"`,
83288
- `${psql} -tc "SELECT 1 FROM pg_database WHERE datname=${sq(name)}" | grep -q 1 ` + `|| ${psql} -c "CREATE DATABASE ${sq(name)} OWNER ${sq(user)};"`
83433
+ "for i in $(seq 1 30); do pg_isready -h 127.0.0.1 -p 5432 -q && break; sleep 2; done",
83434
+ "psql -h 127.0.0.1 -p 5432 -U postgres <<'TS_CLOUD_PG_EOF'",
83435
+ "DO $$ BEGIN",
83436
+ ` IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = ${pgLit(user)}) THEN`,
83437
+ ` CREATE ROLE ${pgIdent(user)} LOGIN PASSWORD ${pgLit(pass)};`,
83438
+ " END IF;",
83439
+ "END $$;",
83440
+ `SELECT 'CREATE DATABASE ${pgIdent(name)} OWNER ${pgIdent(user)}' ` + `WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = ${pgLit(name)})\\gexec`,
83441
+ "TS_CLOUD_PG_EOF"
83289
83442
  ];
83290
83443
  }
83291
83444
  const ident = (v) => v.replace(/`/g, "``");
83292
83445
  const lit = (v) => v.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
83293
83446
  return [
83294
83447
  pantryEnvActivation(),
83448
+ "for i in $(seq 1 30); do mysqladmin -h 127.0.0.1 -P 3306 -u root ping 2>/dev/null | grep -q alive && break; sleep 2; done",
83295
83449
  "mysql -h 127.0.0.1 -P 3306 -u root <<'TS_CLOUD_SQL_EOF'",
83296
83450
  `CREATE DATABASE IF NOT EXISTS \`${ident(name)}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`,
83297
83451
  `CREATE USER IF NOT EXISTS '${lit(user)}'@'%' IDENTIFIED BY '${lit(pass)}';`,
@@ -83438,9 +83592,9 @@ function buildNginxVhostScript(options) {
83438
83592
  const out = [];
83439
83593
  if (options.auth) {
83440
83594
  const file = htpasswdPath(options.siteName);
83441
- const sq2 = (v) => v.split("'").join("'\\''");
83442
- const pw = sq2(options.auth.password);
83443
- const user = sq2(options.auth.username);
83595
+ const sq = (v) => v.split("'").join("'\\''");
83596
+ const pw = sq(options.auth.password);
83597
+ const user = sq(options.auth.username);
83444
83598
  out.push(`TS_CLOUD_HTPASS=$(openssl passwd -apr1 '${pw}')`, `printf '%s:%s\\n' '${user}' "$TS_CLOUD_HTPASS" > ${file}`, `chmod 640 ${file}`, `chown root:www-data ${file} 2>/dev/null || true`);
83445
83599
  }
83446
83600
  out.push(`cat > ${available} <<'TS_CLOUD_NGINX_EOF'`, vhost.replace(/\n$/, ""), "TS_CLOUD_NGINX_EOF", `ln -sf ${available} ${enabled2}`, "rm -f /etc/nginx/sites-enabled/default", `${NGINX_WRAPPER} -t`, "systemctl reload ts-cloud-nginx 2>/dev/null || systemctl restart ts-cloud-nginx 2>/dev/null || true");
@@ -84204,7 +84358,7 @@ class AwsDriver {
84204
84358
  await s32.putObject({
84205
84359
  bucket,
84206
84360
  key: options.remoteKey,
84207
- body: readFileSync13(options.localPath),
84361
+ body: readFileSync14(options.localPath),
84208
84362
  contentType: "application/gzip"
84209
84363
  });
84210
84364
  return { artifactRef: `s3://${bucket}/${options.remoteKey}` };
@@ -84311,9 +84465,9 @@ class AwsDriver {
84311
84465
 
84312
84466
  // src/drivers/hetzner/driver.ts
84313
84467
  await init_dist2();
84314
- import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
84468
+ import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
84315
84469
  import { homedir as homedir7 } from "node:os";
84316
- import { join as join17 } from "node:path";
84470
+ import { join as join18 } from "node:path";
84317
84471
  import { execSync as execSync5 } from "node:child_process";
84318
84472
 
84319
84473
  // src/drivers/hetzner/client.ts
@@ -84743,10 +84897,10 @@ function matchesTsCloudLabels(labels, slug, environment, role = "app") {
84743
84897
 
84744
84898
  // src/drivers/hetzner/state.ts
84745
84899
  import { mkdir as mkdir4, readFile, writeFile as writeFile4 } from "node:fs/promises";
84746
- import { join as join16 } from "node:path";
84900
+ import { join as join17 } from "node:path";
84747
84901
  var STATE_DIR = ".ts-cloud/state";
84748
84902
  function driverStatePath(stackName) {
84749
- return join16(process.cwd(), STATE_DIR, `${stackName}.json`);
84903
+ return join17(process.cwd(), STATE_DIR, `${stackName}.json`);
84750
84904
  }
84751
84905
  async function readDriverState(stackName) {
84752
84906
  try {
@@ -84758,7 +84912,7 @@ async function readDriverState(stackName) {
84758
84912
  }
84759
84913
  async function writeDriverState(stackName, state) {
84760
84914
  const path = driverStatePath(stackName);
84761
- await mkdir4(join16(process.cwd(), STATE_DIR), { recursive: true });
84915
+ await mkdir4(join17(process.cwd(), STATE_DIR), { recursive: true });
84762
84916
  await writeFile4(path, `${JSON.stringify(state, null, 2)}
84763
84917
  `, "utf8");
84764
84918
  }
@@ -84766,7 +84920,7 @@ async function writeDriverState(stackName, state) {
84766
84920
  // src/drivers/hetzner/driver.ts
84767
84921
  var SSH_MAX_BUFFER = 1024 * 1024 * 256;
84768
84922
  function expandHome(path) {
84769
- return path.startsWith("~/") ? join17(homedir7(), path.slice(2)) : path;
84923
+ return path.startsWith("~/") ? join18(homedir7(), path.slice(2)) : path;
84770
84924
  }
84771
84925
 
84772
84926
  class HetznerDriver {
@@ -84838,7 +84992,7 @@ class HetznerDriver {
84838
84992
  }) : undefined;
84839
84993
  const provision = buildComputeProvisionScripts(config6);
84840
84994
  const baked = compute.bakedImage === true;
84841
- const bootstrap = buildUbuntuBootstrapScript({
84995
+ const bootstrap2 = buildUbuntuBootstrapScript({
84842
84996
  runtime: provision.runtime,
84843
84997
  runtimeVersion: provision.runtimeVersion,
84844
84998
  systemPackages: compute.systemPackages,
@@ -84848,7 +85002,7 @@ class HetznerDriver {
84848
85002
  rpxProvision,
84849
85003
  baked
84850
85004
  });
84851
- const userData = wrapCloudInitUserData(bootstrap);
85005
+ const userData = wrapCloudInitUserData(bootstrap2);
84852
85006
  const serverType = resolveHetznerServerType(compute.size);
84853
85007
  const image = compute.image || config6.hetzner?.image || "ubuntu-24.04";
84854
85008
  const firewallName = `${slug}-${environment}-app-fw`;
@@ -85197,7 +85351,7 @@ class HetznerDriver {
85197
85351
  if (!existsSync18(this.sshPublicKeyPath)) {
85198
85352
  throw new Error(`SSH public key not found at ${this.sshPublicKeyPath}. ts-cloud deploys to Hetzner over SSH and needs a public key to authorize on the server. ` + `Generate one (\`ssh-keygen -t ed25519\`) or set hetzner.sshPrivateKeyPath / HCLOUD_SSH_PUBLIC_KEY.`);
85199
85353
  }
85200
- const publicKey = readFileSync14(this.sshPublicKeyPath, "utf8").trim();
85354
+ const publicKey = readFileSync15(this.sshPublicKeyPath, "utf8").trim();
85201
85355
  const normalized = normalizeSshPublicKey(publicKey);
85202
85356
  const existing = await this.client.listSshKeys();
85203
85357
  const match = existing.find((key) => normalizeSshPublicKey(key.public_key) === normalized);
@@ -86077,6 +86231,7 @@ export {
86077
86231
  stackDependencyManager,
86078
86232
  signRequestAsync,
86079
86233
  signRequest,
86234
+ sharedRuntimeLoop,
86080
86235
  sha256,
86081
86236
  setupDns01Challenge,
86082
86237
  setMaintenance,
@@ -86103,6 +86258,7 @@ export {
86103
86258
  resolveSiteKind,
86104
86259
  resolveSiteDeployTarget,
86105
86260
  resolveSiteBucketName,
86261
+ resolveServerlessRuntime,
86106
86262
  resolveServerlessAssetBucketName,
86107
86263
  resolveServerlessArtifactBucketName,
86108
86264
  resolveServerlessAppStackName,
@@ -86333,8 +86489,10 @@ export {
86333
86489
  buildSiteDeployScript,
86334
86490
  buildPhpRuntimeLayerZip,
86335
86491
  buildOptimizationManager,
86492
+ buildNodeRuntimeLayerZip,
86336
86493
  buildFunctionEnv,
86337
86494
  buildCloudFormationTemplate,
86495
+ buildBunRuntimeLayerZip,
86338
86496
  buildAndPushServerlessImage,
86339
86497
  bounceComplaintHandler,
86340
86498
  blueGreenManager,
@@ -86439,6 +86597,7 @@ export {
86439
86597
  MigrationManager,
86440
86598
  MetricsManager,
86441
86599
  Messaging,
86600
+ MANAGED_NODE_VERSIONS,
86442
86601
  LogsManager,
86443
86602
  LambdaVersionsManager,
86444
86603
  LambdaVPCManager,