@stacksjs/ts-cloud 0.2.27 → 0.3.0

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
@@ -1194,9 +1194,9 @@ __export(exports_client, {
1194
1194
  AWSClient: () => AWSClient
1195
1195
  });
1196
1196
  import * as crypto2 from "node:crypto";
1197
- import { existsSync as existsSync14, readFileSync as readFileSync4 } from "node:fs";
1197
+ import { existsSync as existsSync14, readFileSync as readFileSync7 } from "node:fs";
1198
1198
  import { homedir as homedir6 } from "node:os";
1199
- import { join as join9 } from "node:path";
1199
+ import { join as join10 } from "node:path";
1200
1200
  function resolveS3Endpoint(options) {
1201
1201
  const base = options.endpoint || `s3.${options.region}.amazonaws.com`;
1202
1202
  if (!options.bucket) {
@@ -1254,12 +1254,12 @@ class AWSClient {
1254
1254
  }
1255
1255
  loadCredentialsFromFile() {
1256
1256
  const profile = process.env.AWS_PROFILE || "default";
1257
- const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join9(homedir6(), ".aws", "credentials");
1257
+ const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join10(homedir6(), ".aws", "credentials");
1258
1258
  if (!existsSync14(credentialsPath)) {
1259
1259
  return null;
1260
1260
  }
1261
1261
  try {
1262
- const content = readFileSync4(credentialsPath, "utf-8");
1262
+ const content = readFileSync7(credentialsPath, "utf-8");
1263
1263
  const credentials = this.parseCredentialsFile(content, profile);
1264
1264
  if (credentials.accessKeyId && credentials.secretAccessKey) {
1265
1265
  return credentials;
@@ -1688,7 +1688,7 @@ class AWSClient {
1688
1688
  return delay + jitter;
1689
1689
  }
1690
1690
  sleep(ms) {
1691
- return new Promise((resolve13) => setTimeout(resolve13, ms));
1691
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
1692
1692
  }
1693
1693
  getFromCache(key) {
1694
1694
  const entry = this.cache.get(key);
@@ -1753,7 +1753,7 @@ function detectCredentialSource() {
1753
1753
  };
1754
1754
  }
1755
1755
  const profile = process.env.AWS_PROFILE || "default";
1756
- const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join9(homedir6(), ".aws", "credentials");
1756
+ const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join10(homedir6(), ".aws", "credentials");
1757
1757
  if (existsSync14(credentialsPath)) {
1758
1758
  return {
1759
1759
  source: "file",
@@ -1795,13 +1795,13 @@ function resolveCredentials2(profile) {
1795
1795
  return loadProfileFromFile(process.env.AWS_PROFILE || "default") ?? { accessKeyId: "", secretAccessKey: "" };
1796
1796
  }
1797
1797
  function loadProfileFromFile(profile) {
1798
- const { existsSync: existsSync15, readFileSync: readFileSync5 } = __require("node:fs");
1798
+ const { existsSync: existsSync15, readFileSync: readFileSync8 } = __require("node:fs");
1799
1799
  const { homedir: homedir5 } = __require("node:os");
1800
- const { join: join8 } = __require("node:path");
1801
- const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join8(homedir5(), ".aws", "credentials");
1800
+ const { join: join11 } = __require("node:path");
1801
+ const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join11(homedir5(), ".aws", "credentials");
1802
1802
  if (!existsSync15(credentialsPath))
1803
1803
  return null;
1804
- const content = readFileSync5(credentialsPath, "utf-8");
1804
+ const content = readFileSync8(credentialsPath, "utf-8");
1805
1805
  let currentProfile = null;
1806
1806
  let accessKeyId;
1807
1807
  let secretAccessKey;
@@ -1847,8 +1847,8 @@ function loadProfileFromFile(profile) {
1847
1847
  // src/aws/s3.ts
1848
1848
  import * as crypto3 from "node:crypto";
1849
1849
  import { readdir as readdir4 } from "node:fs/promises";
1850
- import { join as join10 } from "node:path";
1851
- import { readFileSync as readFileSync6 } from "node:fs";
1850
+ import { join as join12 } from "node:path";
1851
+ import { readFileSync as readFileSync9 } from "node:fs";
1852
1852
  function toFetchBody(data) {
1853
1853
  const { buffer, byteOffset, byteLength } = data;
1854
1854
  if (buffer instanceof ArrayBuffer) {
@@ -2250,7 +2250,7 @@ class S3Client2 {
2250
2250
  }
2251
2251
  }
2252
2252
  async copy(options) {
2253
- const fileContent = readFileSync6(options.source);
2253
+ const fileContent = readFileSync9(options.source);
2254
2254
  await this.putObject({
2255
2255
  bucket: options.bucket,
2256
2256
  key: options.key,
@@ -2273,7 +2273,7 @@ class S3Client2 {
2273
2273
  const relativePath = file.substring(options.source.length + 1);
2274
2274
  const s3Key = options.prefix ? `${options.prefix}/${relativePath}` : relativePath;
2275
2275
  if (!options.dryRun) {
2276
- const fileContent = readFileSync6(file);
2276
+ const fileContent = readFileSync9(file);
2277
2277
  await this.putObject({
2278
2278
  bucket: options.bucket,
2279
2279
  key: s3Key,
@@ -2304,7 +2304,7 @@ class S3Client2 {
2304
2304
  const files = [];
2305
2305
  const entries = await readdir4(dir, { withFileTypes: true });
2306
2306
  for (const entry of entries) {
2307
- const fullPath = join10(dir, entry.name);
2307
+ const fullPath = join12(dir, entry.name);
2308
2308
  if (entry.isDirectory()) {
2309
2309
  const subFiles = await this.listFilesRecursive(fullPath);
2310
2310
  files.push(...subFiles);
@@ -3722,7 +3722,7 @@ class CloudFormationClient {
3722
3722
  if (attempts % 10 === 0) {
3723
3723
  console.log(`[waitForStack] Attempt ${attempts}: Stack not visible yet`);
3724
3724
  }
3725
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
3725
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
3726
3726
  attempts++;
3727
3727
  continue;
3728
3728
  }
@@ -3761,7 +3761,7 @@ class CloudFormationClient {
3761
3761
  if (failureStatuses.includes(stack.StackStatus)) {
3762
3762
  throw new Error(`Stack reached failure status: ${stack.StackStatus}`);
3763
3763
  }
3764
- await new Promise((resolve13) => setTimeout(resolve13, 5000));
3764
+ await new Promise((resolve14) => setTimeout(resolve14, 5000));
3765
3765
  attempts++;
3766
3766
  } catch (error) {
3767
3767
  if (waitType === "stack-delete-complete" && error.message?.includes("does not exist")) {
@@ -3771,7 +3771,7 @@ class CloudFormationClient {
3771
3771
  if (attempts % 10 === 0) {
3772
3772
  console.log(`[waitForStack] Attempt ${attempts}: Stack does not exist (error), retrying...`);
3773
3773
  }
3774
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
3774
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
3775
3775
  attempts++;
3776
3776
  continue;
3777
3777
  }
@@ -4042,7 +4042,7 @@ class CloudFormationClient {
4042
4042
  if (waitType === "stack-delete-complete") {
4043
4043
  return;
4044
4044
  }
4045
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
4045
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
4046
4046
  attempts++;
4047
4047
  continue;
4048
4048
  }
@@ -4060,14 +4060,14 @@ class CloudFormationClient {
4060
4060
  if (failureStatuses.includes(stack.StackStatus)) {
4061
4061
  throw new Error(`Stack reached failure status: ${stack.StackStatus}`);
4062
4062
  }
4063
- await new Promise((resolve13) => setTimeout(resolve13, 3000));
4063
+ await new Promise((resolve14) => setTimeout(resolve14, 3000));
4064
4064
  attempts++;
4065
4065
  } catch (error) {
4066
4066
  if (waitType === "stack-delete-complete" && error.message?.includes("does not exist")) {
4067
4067
  return;
4068
4068
  }
4069
4069
  if (waitType === "stack-create-complete" && error.message?.includes("does not exist")) {
4070
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
4070
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
4071
4071
  attempts++;
4072
4072
  continue;
4073
4073
  }
@@ -4105,7 +4105,7 @@ class CloudFormationClient {
4105
4105
  if (failureStatuses.includes(status)) {
4106
4106
  return { success: false, status, reason: stack.StackStatusReason };
4107
4107
  }
4108
- await new Promise((resolve13) => setTimeout(resolve13, delayMs));
4108
+ await new Promise((resolve14) => setTimeout(resolve14, delayMs));
4109
4109
  } catch (error) {
4110
4110
  if (error.message?.includes("does not exist")) {
4111
4111
  return { success: true, status: "DELETE_COMPLETE" };
@@ -4194,7 +4194,7 @@ class CloudFrontClient {
4194
4194
  if (invalidation.Status === "Completed") {
4195
4195
  return;
4196
4196
  }
4197
- await new Promise((resolve13) => setTimeout(resolve13, 5000));
4197
+ await new Promise((resolve14) => setTimeout(resolve14, 5000));
4198
4198
  attempts++;
4199
4199
  }
4200
4200
  throw new Error(`Timeout waiting for invalidation ${invalidationId} to complete`);
@@ -5024,7 +5024,7 @@ ${Object.entries(config6).filter(([k]) => !k.startsWith("@_")).map(([key, val])
5024
5024
  if (dist.Status === "Deployed") {
5025
5025
  return true;
5026
5026
  }
5027
- await new Promise((resolve13) => setTimeout(resolve13, 30000));
5027
+ await new Promise((resolve14) => setTimeout(resolve14, 30000));
5028
5028
  }
5029
5029
  return false;
5030
5030
  }
@@ -5085,7 +5085,7 @@ ${Object.entries(config6).filter(([k]) => !k.startsWith("@_")).map(([key, val])
5085
5085
  if (dist.Status === "Deployed" && !dist.Enabled) {
5086
5086
  return true;
5087
5087
  }
5088
- await new Promise((resolve13) => setTimeout(resolve13, 30000));
5088
+ await new Promise((resolve14) => setTimeout(resolve14, 30000));
5089
5089
  }
5090
5090
  return false;
5091
5091
  }
@@ -5618,7 +5618,7 @@ var init_route53 = __esm(() => {
5618
5618
  if (status === "INSYNC") {
5619
5619
  return true;
5620
5620
  }
5621
- await new Promise((resolve13) => setTimeout(resolve13, delayMs));
5621
+ await new Promise((resolve14) => setTimeout(resolve14, delayMs));
5622
5622
  }
5623
5623
  return false;
5624
5624
  }
@@ -6976,7 +6976,7 @@ class UnifiedDnsValidator {
6976
6976
  if (cert.DomainValidationOptions && cert.DomainValidationOptions.length > 0 && cert.DomainValidationOptions[0].ResourceRecord) {
6977
6977
  return;
6978
6978
  }
6979
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
6979
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
6980
6980
  }
6981
6981
  throw new Error("Timeout waiting for DNS validation options");
6982
6982
  }
@@ -7308,7 +7308,7 @@ class ACMClient {
7308
7308
  if (cert.Status === "FAILED" || cert.Status === "VALIDATION_TIMED_OUT") {
7309
7309
  return null;
7310
7310
  }
7311
- await new Promise((resolve13) => setTimeout(resolve13, delayMs));
7311
+ await new Promise((resolve14) => setTimeout(resolve14, delayMs));
7312
7312
  }
7313
7313
  return null;
7314
7314
  }
@@ -7456,7 +7456,7 @@ class ACMDnsValidator {
7456
7456
  if (cert.DomainValidationOptions && cert.DomainValidationOptions.length > 0 && cert.DomainValidationOptions[0].ResourceRecord) {
7457
7457
  return;
7458
7458
  }
7459
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
7459
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
7460
7460
  }
7461
7461
  throw new Error("Timeout waiting for DNS validation options");
7462
7462
  }
@@ -7875,7 +7875,7 @@ class SESClient {
7875
7875
  if (isVerified) {
7876
7876
  return true;
7877
7877
  }
7878
- await new Promise((resolve13) => setTimeout(resolve13, delayMs));
7878
+ await new Promise((resolve14) => setTimeout(resolve14, delayMs));
7879
7879
  }
7880
7880
  return false;
7881
7881
  }
@@ -10891,13 +10891,13 @@ async function uploadStaticFiles(options) {
10891
10891
  const { sourceDir, bucket, region, cacheControl = "max-age=31536000, public", onProgress } = options;
10892
10892
  const s32 = new S3Client2(region);
10893
10893
  const { readdir: readdir5 } = await import("node:fs/promises");
10894
- const { join: join8, relative: relative5 } = await import("node:path");
10895
- const { createHash: createHash5 } = await import("node:crypto");
10894
+ const { join: join11, relative: relative5 } = await import("node:path");
10895
+ const { createHash: createHash7 } = await import("node:crypto");
10896
10896
  async function listFiles(dir) {
10897
10897
  const files2 = [];
10898
10898
  const entries = await readdir5(dir, { withFileTypes: true });
10899
10899
  for (const entry of entries) {
10900
- const fullPath = join8(dir, entry.name);
10900
+ const fullPath = join11(dir, entry.name);
10901
10901
  if (entry.isDirectory()) {
10902
10902
  files2.push(...await listFiles(fullPath));
10903
10903
  } else {
@@ -10932,7 +10932,7 @@ async function uploadStaticFiles(options) {
10932
10932
  return types2[ext || ""] || "application/octet-stream";
10933
10933
  }
10934
10934
  function computeMD5(content) {
10935
- return createHash5("md5").update(content).digest("hex");
10935
+ return createHash7("md5").update(content).digest("hex");
10936
10936
  }
10937
10937
  async function getExistingETags() {
10938
10938
  const etagMap = new Map;
@@ -19781,6 +19781,24 @@ import { join as join42 } from "node:path";
19781
19781
  import { createHash as createHash3 } from "node:crypto";
19782
19782
  import { createReadStream as createReadStream4, readdirSync as readdirSync42, statSync as statSync22 } from "node:fs";
19783
19783
  import { join as join52, relative as relative4 } from "node:path";
19784
+ import { deflateRawSync } from "node:zlib";
19785
+ import { execSync } from "node:child_process";
19786
+ import { createHash as createHash4 } from "node:crypto";
19787
+ import { cpSync, mkdtempSync, rmSync, writeFileSync as writeFileSync32 } from "node:fs";
19788
+ import { tmpdir } from "node:os";
19789
+ import { dirname as dirname22, isAbsolute as isAbsolute3, join as join62, resolve as resolve13 } from "node:path";
19790
+ import { fileURLToPath } from "node:url";
19791
+ import { readFileSync as readFileSync4 } from "node:fs";
19792
+ import { dirname as dirname32, join as join72 } from "node:path";
19793
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
19794
+ import { execFileSync } from "node:child_process";
19795
+ 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";
19796
+ import { tmpdir as tmpdir2 } from "node:os";
19797
+ import { join as join8, relative as relative22 } from "node:path";
19798
+ import { execSync as execSync2 } from "node:child_process";
19799
+ import { createHash as createHash5 } from "node:crypto";
19800
+ import { readdirSync as readdirSync62, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
19801
+ import { join as join9, relative as relative32, resolve as resolve22 } from "node:path";
19784
19802
  var __defProp3 = Object.defineProperty;
19785
19803
  var __returnValue3 = (v) => v;
19786
19804
  function __exportSetter3(name, newValue) {
@@ -20320,7 +20338,7 @@ function calculateBackoff(attempt, initialDelayMs, maxDelayMs) {
20320
20338
  return Math.min(exponentialDelay + jitter, maxDelayMs);
20321
20339
  }
20322
20340
  function sleep(ms) {
20323
- return new Promise((resolve13) => setTimeout(resolve13, ms));
20341
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
20324
20342
  }
20325
20343
  async function makeAWSRequest(options, retryOptions) {
20326
20344
  const {
@@ -21040,6 +21058,15 @@ function resolveStorageBucketName(slug, environment, bucketKey, explicitBucket)
21040
21058
  function resolveDeployBucketName(slug, environment) {
21041
21059
  return `${slug}-${environment}-deploy`;
21042
21060
  }
21061
+ function resolveServerlessAppStackName(config6, environment) {
21062
+ return `${config6.project.slug}-${environment}-app`;
21063
+ }
21064
+ function resolveServerlessArtifactBucketName(slug, environment) {
21065
+ return `${slug}-${environment}-deployments`;
21066
+ }
21067
+ function resolveServerlessAssetBucketName(slug, environment) {
21068
+ return `${slug}-${environment}-assets`;
21069
+ }
21043
21070
 
21044
21071
  class DependencyGraph {
21045
21072
  nodes = new Map;
@@ -39955,6 +39982,44 @@ function createNodeJsServerlessPreset(options) {
39955
39982
  }
39956
39983
  };
39957
39984
  }
39985
+ function createServerlessNodePreset(options) {
39986
+ const {
39987
+ name,
39988
+ slug,
39989
+ entry,
39990
+ domain,
39991
+ runtime = "nodejs20.x",
39992
+ memory = 1024,
39993
+ build,
39994
+ deploy,
39995
+ assets,
39996
+ queues = true,
39997
+ scheduler = "on",
39998
+ region = "us-east-1"
39999
+ } = options;
40000
+ return {
40001
+ project: { name, slug, region },
40002
+ mode: "serverless",
40003
+ environments: {
40004
+ production: {
40005
+ type: "production",
40006
+ domain,
40007
+ app: {
40008
+ kind: "node",
40009
+ runtime,
40010
+ entry,
40011
+ memory,
40012
+ build,
40013
+ deploy,
40014
+ assets,
40015
+ queues,
40016
+ scheduler,
40017
+ domain
40018
+ }
40019
+ }
40020
+ }
40021
+ };
40022
+ }
39958
40023
  function createFullStackAppPreset(options) {
39959
40024
  const {
39960
40025
  name,
@@ -41515,6 +41580,47 @@ function createLaravelPreset(options) {
41515
41580
  }
41516
41581
  };
41517
41582
  }
41583
+ function createServerlessLaravelPreset(options) {
41584
+ const {
41585
+ name,
41586
+ slug,
41587
+ domain,
41588
+ layers,
41589
+ phpVersion = "8.3",
41590
+ architecture = "x86_64",
41591
+ memory = 1024,
41592
+ build,
41593
+ deploy = ["migrate --force"],
41594
+ cache: cache2 = "dynamodb",
41595
+ scheduler = "on",
41596
+ region = "us-east-1"
41597
+ } = options;
41598
+ return {
41599
+ project: { name, slug, region },
41600
+ mode: "serverless",
41601
+ environments: {
41602
+ production: {
41603
+ type: "production",
41604
+ domain,
41605
+ app: {
41606
+ kind: "php",
41607
+ runtime: "provided.al2023",
41608
+ phpVersion,
41609
+ architecture,
41610
+ layers,
41611
+ memory,
41612
+ build,
41613
+ deploy,
41614
+ assets: "public",
41615
+ queues: true,
41616
+ scheduler,
41617
+ cache: { driver: cache2 },
41618
+ domain
41619
+ }
41620
+ }
41621
+ }
41622
+ };
41623
+ }
41518
41624
  function createDashboardSite(options) {
41519
41625
  return {
41520
41626
  root: options.root ?? "ui/dist",
@@ -43287,11 +43393,11 @@ var templateCache = new TemplateCache;
43287
43393
  async function hashFile(filePath, options = {}) {
43288
43394
  const algorithm = options.algorithm || "sha256";
43289
43395
  const chunkSize = options.chunkSize || 65536;
43290
- return new Promise((resolve13, reject) => {
43396
+ return new Promise((resolve14, reject) => {
43291
43397
  const hash2 = createHash3(algorithm);
43292
43398
  const stream = createReadStream4(filePath, { highWaterMark: chunkSize });
43293
43399
  stream.on("data", (chunk) => hash2.update(chunk));
43294
- stream.on("end", () => resolve13(hash2.digest("hex")));
43400
+ stream.on("end", () => resolve14(hash2.digest("hex")));
43295
43401
  stream.on("error", reject);
43296
43402
  });
43297
43403
  }
@@ -43493,7 +43599,7 @@ async function sequence(tasks) {
43493
43599
  return results;
43494
43600
  }
43495
43601
  function sleep2(ms) {
43496
- return new Promise((resolve13) => setTimeout(resolve13, ms));
43602
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
43497
43603
  }
43498
43604
  async function withTimeout(task, timeoutMs, timeoutMessage = "Operation timed out") {
43499
43605
  return Promise.race([
@@ -43531,12 +43637,12 @@ class RateLimiter {
43531
43637
  }
43532
43638
  return sleep2(this.minInterval - timeSinceLastExecution);
43533
43639
  }
43534
- return new Promise((resolve13) => this.queue.push(resolve13));
43640
+ return new Promise((resolve14) => this.queue.push(resolve14));
43535
43641
  }
43536
43642
  processQueue() {
43537
43643
  if (this.queue.length > 0 && this.running < this.maxConcurrent) {
43538
- const resolve13 = this.queue.shift();
43539
- resolve13();
43644
+ const resolve14 = this.queue.shift();
43645
+ resolve14();
43540
43646
  }
43541
43647
  }
43542
43648
  stats() {
@@ -43847,6 +43953,27 @@ var cloud_config_schema_default = {
43847
43953
  region: {
43848
43954
  type: "string",
43849
43955
  description: "AWS region override for this environment"
43956
+ },
43957
+ app: {
43958
+ type: "object",
43959
+ 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).",
43960
+ properties: {
43961
+ runtime: { type: "string", description: "Lambda runtime (e.g. nodejs20.x, provided.al2023)" },
43962
+ kind: { type: "string", enum: ["node", "bun", "php"], description: "Application kind (drives packaging + runtime)" },
43963
+ entry: { type: "string", description: "Entry file exporting the request handler" },
43964
+ memory: { type: "number", description: "HTTP function memory in MB" },
43965
+ timeout: { type: "number", description: "HTTP request timeout in seconds" },
43966
+ gatewayVersion: { type: "number", enum: [1, 2], description: "API Gateway version (2 = HTTP API, 1 = REST)" },
43967
+ warm: { type: "number", description: "Keep-warm / provisioned concurrency count" },
43968
+ queues: { description: "Queue names (true = single default queue, false = disabled)" },
43969
+ scheduler: { type: "string", enum: ["off", "on", "sub-minute"], description: "Task scheduler mode" },
43970
+ build: { type: "array", items: { type: "string" }, description: "Commands run locally before packaging" },
43971
+ deploy: { type: "array", items: { type: "string" }, description: "Commands run remotely after activation (e.g. migrations)" },
43972
+ octane: { type: "boolean", description: "Persistent application mode (Laravel Octane)" },
43973
+ packaging: { type: "string", enum: ["zip", "image"], description: "Deployment package format (zip or container image)" },
43974
+ phpVersion: { type: "string", description: "PHP version for the runtime layer (kind: php)" },
43975
+ architecture: { type: "string", enum: ["x86_64", "arm64"], description: "CPU architecture" }
43976
+ }
43850
43977
  }
43851
43978
  }
43852
43979
  },
@@ -45920,9 +46047,9 @@ class REPL {
45920
46047
  this.running = false;
45921
46048
  }
45922
46049
  async readInput() {
45923
- return new Promise((resolve13) => {
46050
+ return new Promise((resolve14) => {
45924
46051
  process.stdout.write(this.options.prompt || "> ");
45925
- resolve13("");
46052
+ resolve14("");
45926
46053
  });
45927
46054
  }
45928
46055
  async executeCommand(input) {
@@ -50330,7 +50457,7 @@ class BlueGreenManager {
50330
50457
  console.log(` Switching to: ${targetEnv}`);
50331
50458
  console.log(`\\n1. Deploying to ${targetEnv} environment`);
50332
50459
  if (!dryRun) {
50333
- await new Promise((resolve13) => setTimeout(resolve13, 100));
50460
+ await new Promise((resolve14) => setTimeout(resolve14, 100));
50334
50461
  }
50335
50462
  console.log(`\\n2. Running health checks on ${targetEnv} environment`);
50336
50463
  if (deployment2.healthCheckConfig) {
@@ -50356,7 +50483,7 @@ class BlueGreenManager {
50356
50483
  }
50357
50484
  console.log(`\\n4. Monitoring ${targetEnv} environment`);
50358
50485
  if (!dryRun) {
50359
- await new Promise((resolve13) => setTimeout(resolve13, 100));
50486
+ await new Promise((resolve14) => setTimeout(resolve14, 100));
50360
50487
  }
50361
50488
  result.success = true;
50362
50489
  result.endTime = new Date;
@@ -50398,7 +50525,7 @@ class BlueGreenManager {
50398
50525
  let consecutiveSuccesses = 0;
50399
50526
  const maxAttempts = config22.healthyThreshold + 2;
50400
50527
  for (let i = 0;i < maxAttempts; i++) {
50401
- await new Promise((resolve13) => setTimeout(resolve13, 50));
50528
+ await new Promise((resolve14) => setTimeout(resolve14, 50));
50402
50529
  const healthy = Math.random() > 0.1;
50403
50530
  if (healthy) {
50404
50531
  consecutiveSuccesses++;
@@ -50646,7 +50773,7 @@ class CanaryManager {
50646
50773
  console.log(` [SKIPPED - DRY RUN]`);
50647
50774
  return true;
50648
50775
  }
50649
- await new Promise((resolve13) => setTimeout(resolve13, 100));
50776
+ await new Promise((resolve14) => setTimeout(resolve14, 100));
50650
50777
  const metrics = {
50651
50778
  baselineErrorRate: Math.random() * 0.5,
50652
50779
  canaryErrorRate: Math.random() * 0.8,
@@ -63219,7 +63346,7 @@ class DLQMonitoringManager {
63219
63346
  job.status = "processing";
63220
63347
  job.startedAt = new Date;
63221
63348
  job.attempts++;
63222
- await new Promise((resolve13) => setTimeout(resolve13, 100));
63349
+ await new Promise((resolve14) => setTimeout(resolve14, 100));
63223
63350
  const success = Math.random() > 0.3;
63224
63351
  job.status = success ? "success" : "failed";
63225
63352
  job.completedAt = new Date;
@@ -63422,7 +63549,7 @@ class BatchProcessingManager {
63422
63549
  async processMessage(message, config22) {
63423
63550
  message.status = "processing";
63424
63551
  const startTime = Date.now();
63425
- await new Promise((resolve13) => setTimeout(resolve13, Math.random() * 100));
63552
+ await new Promise((resolve14) => setTimeout(resolve14, Math.random() * 100));
63426
63553
  const processingTime = Date.now() - startTime;
63427
63554
  message.processingTime = processingTime;
63428
63555
  const success = Math.random() > 0.1;
@@ -63779,6 +63906,1115 @@ class QueueManagementManager {
63779
63906
  }
63780
63907
  }
63781
63908
  var queueManagementManager = new QueueManagementManager;
63909
+ var CRC_TABLE = (() => {
63910
+ const table2 = [];
63911
+ for (let i = 0;i < 256; i++) {
63912
+ let c = i;
63913
+ for (let j = 0;j < 8; j++)
63914
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
63915
+ table2[i] = c >>> 0;
63916
+ }
63917
+ return table2;
63918
+ })();
63919
+ function crc32(data) {
63920
+ let crc = 4294967295;
63921
+ for (let i = 0;i < data.length; i++)
63922
+ crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
63923
+ return (crc ^ 4294967295) >>> 0;
63924
+ }
63925
+ function toBuffer(data) {
63926
+ if (typeof data === "string")
63927
+ return Buffer.from(data, "utf-8");
63928
+ if (Buffer.isBuffer(data))
63929
+ return data;
63930
+ return Buffer.from(data);
63931
+ }
63932
+ function dosTimeDate(date) {
63933
+ const year = Math.max(1980, date.getFullYear());
63934
+ const time = (date.getHours() << 11 | date.getMinutes() << 5 | date.getSeconds() >> 1) & 65535;
63935
+ const d = (year - 1980 << 9 | date.getMonth() + 1 << 5 | date.getDate()) & 65535;
63936
+ return { time, date: d };
63937
+ }
63938
+ function createZip(entries) {
63939
+ const localParts = [];
63940
+ const centralParts = [];
63941
+ let offset = 0;
63942
+ for (const entry of entries) {
63943
+ const raw = toBuffer(entry.data);
63944
+ const compressed = deflateRawSync(raw);
63945
+ const crc = crc32(raw);
63946
+ const nameBuf = Buffer.from(entry.name.replace(/\\/g, "/"), "utf-8");
63947
+ const { time, date } = dosTimeDate(entry.date ?? new Date(0));
63948
+ const mode = entry.mode ?? 420;
63949
+ const local = Buffer.alloc(30 + nameBuf.length);
63950
+ local.writeUInt32LE(67324752, 0);
63951
+ local.writeUInt16LE(20, 4);
63952
+ local.writeUInt16LE(0, 6);
63953
+ local.writeUInt16LE(8, 8);
63954
+ local.writeUInt16LE(time, 10);
63955
+ local.writeUInt16LE(date, 12);
63956
+ local.writeUInt32LE(crc, 14);
63957
+ local.writeUInt32LE(compressed.length, 18);
63958
+ local.writeUInt32LE(raw.length, 22);
63959
+ local.writeUInt16LE(nameBuf.length, 26);
63960
+ local.writeUInt16LE(0, 28);
63961
+ nameBuf.copy(local, 30);
63962
+ const central = Buffer.alloc(46 + nameBuf.length);
63963
+ central.writeUInt32LE(33639248, 0);
63964
+ central.writeUInt16LE(798, 4);
63965
+ central.writeUInt16LE(20, 6);
63966
+ central.writeUInt16LE(0, 8);
63967
+ central.writeUInt16LE(8, 10);
63968
+ central.writeUInt16LE(time, 12);
63969
+ central.writeUInt16LE(date, 14);
63970
+ central.writeUInt32LE(crc, 16);
63971
+ central.writeUInt32LE(compressed.length, 20);
63972
+ central.writeUInt32LE(raw.length, 24);
63973
+ central.writeUInt16LE(nameBuf.length, 28);
63974
+ central.writeUInt16LE(0, 30);
63975
+ central.writeUInt16LE(0, 32);
63976
+ central.writeUInt16LE(0, 34);
63977
+ central.writeUInt16LE(0, 36);
63978
+ central.writeUInt32LE((mode & 65535) << 16, 38);
63979
+ central.writeUInt32LE(offset, 42);
63980
+ nameBuf.copy(central, 46);
63981
+ localParts.push(local, compressed);
63982
+ centralParts.push(central);
63983
+ offset += local.length + compressed.length;
63984
+ }
63985
+ const centralDir = Buffer.concat(centralParts);
63986
+ const end = Buffer.alloc(22);
63987
+ end.writeUInt32LE(101010256, 0);
63988
+ end.writeUInt16LE(0, 4);
63989
+ end.writeUInt16LE(0, 6);
63990
+ end.writeUInt16LE(entries.length, 8);
63991
+ end.writeUInt16LE(entries.length, 10);
63992
+ end.writeUInt32LE(centralDir.length, 12);
63993
+ end.writeUInt32LE(offset, 16);
63994
+ end.writeUInt16LE(0, 20);
63995
+ return Buffer.concat([...localParts, centralDir, end]);
63996
+ }
63997
+ function generateBootstrap(opts) {
63998
+ const adapter = opts.adapterImport ?? "./adapter";
63999
+ const entry = opts.entryImport.replace(/\\/g, "/");
64000
+ return `// Generated by ts-cloud — serverless app bootstrap. Do not edit.
64001
+ import * as __userModule from ${JSON.stringify(entry)}
64002
+ import { resolveApp, createHandlers } from ${JSON.stringify(adapter)}
64003
+
64004
+ const __app = resolveApp(__userModule)
64005
+ const __handlers = createHandlers(__app)
64006
+
64007
+ export const http = __handlers.http
64008
+ export const queue = __handlers.queue
64009
+ export const cli = __handlers.cli
64010
+ `;
64011
+ }
64012
+ function adapterSourcePath() {
64013
+ return join62(dirname22(fileURLToPath(import.meta.url)), "runtime", "adapter.ts");
64014
+ }
64015
+ function runBuildHooks(hooks, cwd, onStep) {
64016
+ for (const hook of hooks ?? []) {
64017
+ onStep?.(`build: ${hook}`);
64018
+ execSync(hook, { stdio: "inherit", cwd });
64019
+ }
64020
+ }
64021
+ function sha256(data) {
64022
+ return createHash4("sha256").update(data).digest("hex");
64023
+ }
64024
+ function artifactKey(slug, environment, hash2) {
64025
+ return `deployments/${slug}/${environment}/${hash2}.zip`;
64026
+ }
64027
+ async function packageServerlessApp(opts) {
64028
+ const projectRoot = resolve13(opts.projectRoot ?? process.cwd());
64029
+ const { app } = opts;
64030
+ if (!opts.skipBuild)
64031
+ runBuildHooks(app.build, projectRoot, opts.onStep);
64032
+ const entry = app.entry;
64033
+ if (!entry)
64034
+ throw new Error("serverless app: `entry` is required to package a Node/Bun application");
64035
+ const entryPath = isAbsolute3(entry) ? entry : join62(projectRoot, entry);
64036
+ const stage = mkdtempSync(join62(tmpdir(), "tscloud-pkg-"));
64037
+ try {
64038
+ cpSync(adapterSourcePath(), join62(stage, "adapter.ts"));
64039
+ const bootstrapPath = join62(stage, "bootstrap.ts");
64040
+ writeFileSync32(bootstrapPath, generateBootstrap({ entryImport: entryPath, adapterImport: "./adapter" }));
64041
+ opts.onStep?.("bundling application");
64042
+ const result = await Bun.build({
64043
+ entrypoints: [bootstrapPath],
64044
+ target: "node",
64045
+ format: "esm",
64046
+ minify: false,
64047
+ sourcemap: "none"
64048
+ });
64049
+ if (!result.success) {
64050
+ const logs = result.logs.map((l) => String(l)).join(`
64051
+ `);
64052
+ throw new Error(`serverless app bundle failed:
64053
+ ${logs}`);
64054
+ }
64055
+ const output = result.outputs[0];
64056
+ const bundle = Buffer.from(await output.arrayBuffer());
64057
+ const handlerFile = "index";
64058
+ const zip = createZip([{ name: `${handlerFile}.mjs`, data: bundle }]);
64059
+ return {
64060
+ zip,
64061
+ bundle,
64062
+ sha256: sha256(zip),
64063
+ handlerFile,
64064
+ handlers: {
64065
+ http: app.handlers?.http ?? `${handlerFile}.http`,
64066
+ queue: app.handlers?.queue ?? `${handlerFile}.queue`,
64067
+ cli: app.handlers?.cli ?? `${handlerFile}.cli`
64068
+ },
64069
+ bundleBytes: bundle.length
64070
+ };
64071
+ } finally {
64072
+ rmSync(stage, { recursive: true, force: true });
64073
+ }
64074
+ }
64075
+ function resolveQueueNames(app, slug, env) {
64076
+ if (app.queues === false)
64077
+ return [];
64078
+ if (app.queues === undefined || app.queues === true)
64079
+ return [`${slug}-${env}-default`];
64080
+ return app.queues.map((q) => {
64081
+ const name = typeof q === "string" ? q : Object.keys(q)[0];
64082
+ return `${slug}-${env}-${name}`;
64083
+ });
64084
+ }
64085
+ function composeServerlessAppTemplate(opts) {
64086
+ const { app, environment, handlers } = opts;
64087
+ const slug = opts.config.project.slug;
64088
+ const runtime = app.runtime ?? "nodejs20.x";
64089
+ const architecture = app.architecture ?? "x86_64";
64090
+ const region = opts.config.project.region;
64091
+ const functionNames = {
64092
+ http: `${slug}-${environment}-http`,
64093
+ queue: `${slug}-${environment}-queue`,
64094
+ cli: `${slug}-${environment}-cli`
64095
+ };
64096
+ const queueNames = resolveQueueNames(app, slug, environment);
64097
+ const hasQueue = queueNames.length > 0;
64098
+ const imageMode = app.packaging === "image";
64099
+ const schedulerEnabled = (app.scheduler ?? "on") !== "off";
64100
+ const cacheEnabled = (app.cache?.driver ?? "dynamodb") === "dynamodb";
64101
+ const assetsEnabled = Boolean(app.assets);
64102
+ const assetsBucket = resolveServerlessAssetBucketName(slug, environment);
64103
+ const tmpStorage = app.tmpStorage ?? 512;
64104
+ const resources = {};
64105
+ const outputs = {};
64106
+ const inlinePolicies = [
64107
+ {
64108
+ PolicyName: "tscloud-serverless-app",
64109
+ PolicyDocument: {
64110
+ Version: "2012-10-17",
64111
+ Statement: [
64112
+ {
64113
+ Effect: "Allow",
64114
+ Action: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
64115
+ Resource: Fn2.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*")
64116
+ },
64117
+ {
64118
+ Effect: "Allow",
64119
+ Action: ["lambda:InvokeFunction"],
64120
+ Resource: Fn2.sub(`arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:${slug}-${environment}-*`)
64121
+ },
64122
+ {
64123
+ Effect: "Allow",
64124
+ Action: ["secretsmanager:GetSecretValue", "ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
64125
+ Resource: [
64126
+ Fn2.sub(`arn:aws:secretsmanager:\${AWS::Region}:\${AWS::AccountId}:secret:${slug}/${environment}/*`),
64127
+ Fn2.sub(`arn:aws:ssm:\${AWS::Region}:\${AWS::AccountId}:parameter/${slug}/${environment}/*`)
64128
+ ]
64129
+ },
64130
+ {
64131
+ Effect: "Allow",
64132
+ Action: ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
64133
+ Resource: [
64134
+ Fn2.sub(`arn:aws:s3:::${assetsBucket}`),
64135
+ Fn2.sub(`arn:aws:s3:::${assetsBucket}/*`),
64136
+ ...app.storage?.bucket ? [Fn2.sub(`arn:aws:s3:::${app.storage.bucket}`), Fn2.sub(`arn:aws:s3:::${app.storage.bucket}/*`)] : []
64137
+ ]
64138
+ }
64139
+ ]
64140
+ }
64141
+ }
64142
+ ];
64143
+ if (hasQueue) {
64144
+ inlinePolicies[0].PolicyDocument.Statement.push({
64145
+ Effect: "Allow",
64146
+ Action: ["sqs:SendMessage", "sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes", "sqs:GetQueueUrl"],
64147
+ Resource: Fn2.sub(`arn:aws:sqs:\${AWS::Region}:\${AWS::AccountId}:${slug}-${environment}-*`)
64148
+ });
64149
+ }
64150
+ if (cacheEnabled) {
64151
+ inlinePolicies[0].PolicyDocument.Statement.push({
64152
+ Effect: "Allow",
64153
+ Action: ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:BatchGetItem", "dynamodb:BatchWriteItem"],
64154
+ Resource: Fn2.sub(`arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/${slug}-${environment}-cache*`)
64155
+ });
64156
+ }
64157
+ const managedPolicies = ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"];
64158
+ if (app.vpc?.subnets?.length)
64159
+ managedPolicies.push("arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole");
64160
+ resources.AppRole = {
64161
+ Type: "AWS::IAM::Role",
64162
+ Properties: {
64163
+ RoleName: `${slug}-${environment}-app-role`,
64164
+ AssumeRolePolicyDocument: {
64165
+ Version: "2012-10-17",
64166
+ Statement: [{ Effect: "Allow", Principal: { Service: "lambda.amazonaws.com" }, Action: "sts:AssumeRole" }]
64167
+ },
64168
+ ManagedPolicyArns: managedPolicies,
64169
+ Policies: inlinePolicies
64170
+ }
64171
+ };
64172
+ const baseEnv = (mode) => ({
64173
+ TSCLOUD_LAMBDA_MODE: mode,
64174
+ TSCLOUD_ENV: environment,
64175
+ ...app.octane ? { TSCLOUD_OCTANE: "1" } : {},
64176
+ ...cacheEnabled ? { TSCLOUD_CACHE_TABLE: `${slug}-${environment}-cache` } : {},
64177
+ ...hasQueue ? { TSCLOUD_QUEUE: queueNames[0] } : {},
64178
+ ...app.env ?? {}
64179
+ });
64180
+ const subnets = app.vpc?.subnets ?? [];
64181
+ const hasVpc = subnets.length > 0;
64182
+ const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy);
64183
+ if (needsDataVpc && !hasVpc) {
64184
+ throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy require app.vpc.subnets (private subnets) to be set.");
64185
+ }
64186
+ const vpcConfig = hasVpc ? {
64187
+ VpcConfig: {
64188
+ SubnetIds: subnets,
64189
+ SecurityGroupIds: [
64190
+ ...app.vpc?.securityGroups ?? [],
64191
+ ...needsDataVpc ? [Fn2.getAtt("DataSecurityGroup", "GroupId")] : []
64192
+ ]
64193
+ }
64194
+ } : {};
64195
+ function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency) {
64196
+ resources[`${logicalId}LogGroup`] = {
64197
+ Type: "AWS::Logs::LogGroup",
64198
+ Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: 14 }
64199
+ };
64200
+ const codeProps = imageMode ? {
64201
+ PackageType: "Image",
64202
+ Code: { ImageUri: Fn2.ref("ImageUri") },
64203
+ ...app.kind === "php" ? {} : { ImageConfig: { Command: [handler8] } }
64204
+ } : {
64205
+ Runtime: runtime,
64206
+ Handler: handler8,
64207
+ Code: { S3Bucket: Fn2.ref("ArtifactBucket"), S3Key: Fn2.ref("ArtifactKey") },
64208
+ ...opts.runtimeLayers?.length ? { Layers: opts.runtimeLayers } : {}
64209
+ };
64210
+ resources[logicalId] = {
64211
+ Type: "AWS::Lambda::Function",
64212
+ DependsOn: [`${logicalId}LogGroup`],
64213
+ Properties: {
64214
+ FunctionName: name,
64215
+ Architectures: [architecture],
64216
+ MemorySize: memory,
64217
+ Timeout: timeout,
64218
+ Role: Fn2.getAtt("AppRole", "Arn"),
64219
+ Environment: { Variables: baseEnv(mode) },
64220
+ EphemeralStorage: { Size: tmpStorage },
64221
+ ...codeProps,
64222
+ ...reservedConcurrency !== undefined ? { ReservedConcurrentExecutions: reservedConcurrency } : {},
64223
+ ...vpcConfig
64224
+ }
64225
+ };
64226
+ }
64227
+ addFunction("HttpFunction", functionNames.http, handlers.http, "http", app.memory ?? 1024, app.timeout ?? 28, app.concurrency);
64228
+ addFunction("CliFunction", functionNames.cli, handlers.cli, "cli", app.cliMemory ?? 1024, app.cliTimeout ?? 900);
64229
+ if (hasQueue)
64230
+ addFunction("QueueFunction", functionNames.queue, handlers.queue, "queue", app.queueMemory ?? 1024, app.queueTimeout ?? 120);
64231
+ resources.HttpApi = {
64232
+ Type: "AWS::ApiGatewayV2::Api",
64233
+ Properties: {
64234
+ Name: `${slug}-${environment}`,
64235
+ ProtocolType: "HTTP"
64236
+ }
64237
+ };
64238
+ resources.HttpIntegration = {
64239
+ Type: "AWS::ApiGatewayV2::Integration",
64240
+ Properties: {
64241
+ ApiId: Fn2.ref("HttpApi"),
64242
+ IntegrationType: "AWS_PROXY",
64243
+ IntegrationUri: Fn2.getAtt("HttpFunction", "Arn"),
64244
+ PayloadFormatVersion: "2.0"
64245
+ }
64246
+ };
64247
+ resources.HttpRoute = {
64248
+ Type: "AWS::ApiGatewayV2::Route",
64249
+ Properties: {
64250
+ ApiId: Fn2.ref("HttpApi"),
64251
+ RouteKey: "$default",
64252
+ Target: Fn2.join("/", ["integrations", Fn2.ref("HttpIntegration")])
64253
+ }
64254
+ };
64255
+ resources.HttpStage = {
64256
+ Type: "AWS::ApiGatewayV2::Stage",
64257
+ Properties: {
64258
+ ApiId: Fn2.ref("HttpApi"),
64259
+ StageName: "$default",
64260
+ AutoDeploy: true
64261
+ }
64262
+ };
64263
+ resources.HttpPermission = {
64264
+ Type: "AWS::Lambda::Permission",
64265
+ Properties: {
64266
+ FunctionName: Fn2.ref("HttpFunction"),
64267
+ Action: "lambda:InvokeFunction",
64268
+ Principal: "apigateway.amazonaws.com",
64269
+ SourceArn: Fn2.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*/*")
64270
+ }
64271
+ };
64272
+ outputs.HttpApiEndpoint = {
64273
+ Description: "HTTP API endpoint",
64274
+ Value: Fn2.getAtt("HttpApi", "ApiEndpoint")
64275
+ };
64276
+ outputs.HttpApiId = { Description: "HTTP API id", Value: Fn2.ref("HttpApi") };
64277
+ if (hasQueue) {
64278
+ resources.AppQueueDlq = {
64279
+ Type: "AWS::SQS::Queue",
64280
+ Properties: {
64281
+ QueueName: `${slug}-${environment}-dlq`,
64282
+ MessageRetentionPeriod: 1209600
64283
+ }
64284
+ };
64285
+ queueNames.forEach((qName, i) => {
64286
+ const qId = `AppQueue${i}`;
64287
+ resources[qId] = {
64288
+ Type: "AWS::SQS::Queue",
64289
+ Properties: {
64290
+ QueueName: qName,
64291
+ VisibilityTimeout: Math.max(app.queueTimeout ?? 120, app.queueTimeout ?? 120),
64292
+ RedrivePolicy: {
64293
+ deadLetterTargetArn: Fn2.getAtt("AppQueueDlq", "Arn"),
64294
+ maxReceiveCount: app.queueTries ?? 3
64295
+ }
64296
+ }
64297
+ };
64298
+ resources[`${qId}Mapping`] = {
64299
+ Type: "AWS::Lambda::EventSourceMapping",
64300
+ Properties: {
64301
+ EventSourceArn: Fn2.getAtt(qId, "Arn"),
64302
+ FunctionName: Fn2.ref("QueueFunction"),
64303
+ BatchSize: 1,
64304
+ FunctionResponseTypes: ["ReportBatchItemFailures"],
64305
+ ...app.queueConcurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, app.queueConcurrency) } } : {}
64306
+ }
64307
+ };
64308
+ outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${qName}`, Value: Fn2.ref(qId) };
64309
+ });
64310
+ }
64311
+ if (schedulerEnabled) {
64312
+ resources.SchedulerRule = {
64313
+ Type: "AWS::Events::Rule",
64314
+ Properties: {
64315
+ Name: `${slug}-${environment}-scheduler`,
64316
+ ScheduleExpression: "rate(1 minute)",
64317
+ State: "ENABLED",
64318
+ Targets: [{
64319
+ Id: "cli",
64320
+ Arn: Fn2.getAtt("CliFunction", "Arn"),
64321
+ Input: JSON.stringify({ command: "schedule:run" })
64322
+ }]
64323
+ }
64324
+ };
64325
+ resources.SchedulerPermission = {
64326
+ Type: "AWS::Lambda::Permission",
64327
+ Properties: {
64328
+ FunctionName: Fn2.ref("CliFunction"),
64329
+ Action: "lambda:InvokeFunction",
64330
+ Principal: "events.amazonaws.com",
64331
+ SourceArn: Fn2.getAtt("SchedulerRule", "Arn")
64332
+ }
64333
+ };
64334
+ }
64335
+ if (app.warm && app.warm > 0) {
64336
+ const TARGETS_PER_RULE = 5;
64337
+ const ruleCount = Math.ceil(app.warm / TARGETS_PER_RULE);
64338
+ let warmed = 0;
64339
+ for (let r = 0;r < ruleCount; r++) {
64340
+ const targets = Math.min(TARGETS_PER_RULE, app.warm - warmed);
64341
+ resources[`WarmerRule${r}`] = {
64342
+ Type: "AWS::Events::Rule",
64343
+ Properties: {
64344
+ Name: `${slug}-${environment}-warmer-${r}`,
64345
+ ScheduleExpression: "rate(5 minutes)",
64346
+ State: "ENABLED",
64347
+ Targets: Array.from({ length: targets }, (_, i) => ({
64348
+ Id: `warm-${r}-${i}`,
64349
+ Arn: Fn2.getAtt("HttpFunction", "Arn"),
64350
+ Input: JSON.stringify({ warmer: true })
64351
+ }))
64352
+ }
64353
+ };
64354
+ warmed += targets;
64355
+ }
64356
+ resources.WarmerPermission = {
64357
+ Type: "AWS::Lambda::Permission",
64358
+ Properties: {
64359
+ FunctionName: Fn2.ref("HttpFunction"),
64360
+ Action: "lambda:InvokeFunction",
64361
+ Principal: "events.amazonaws.com",
64362
+ SourceArn: Fn2.sub(`arn:aws:events:\${AWS::Region}:\${AWS::AccountId}:rule/${slug}-${environment}-warmer-*`)
64363
+ }
64364
+ };
64365
+ }
64366
+ if (cacheEnabled) {
64367
+ resources.CacheTable = {
64368
+ Type: "AWS::DynamoDB::Table",
64369
+ Properties: {
64370
+ TableName: `${slug}-${environment}-cache`,
64371
+ BillingMode: "PAY_PER_REQUEST",
64372
+ AttributeDefinitions: [{ AttributeName: "key", AttributeType: "S" }],
64373
+ KeySchema: [{ AttributeName: "key", KeyType: "HASH" }],
64374
+ TimeToLiveSpecification: { AttributeName: "expires_at", Enabled: true }
64375
+ }
64376
+ };
64377
+ outputs.CacheTableName = { Description: "DynamoDB cache table", Value: Fn2.ref("CacheTable") };
64378
+ }
64379
+ if (assetsEnabled) {
64380
+ resources.AssetsBucket = {
64381
+ Type: "AWS::S3::Bucket",
64382
+ Properties: {
64383
+ BucketName: assetsBucket,
64384
+ PublicAccessBlockConfiguration: {
64385
+ BlockPublicAcls: true,
64386
+ BlockPublicPolicy: true,
64387
+ IgnorePublicAcls: true,
64388
+ RestrictPublicBuckets: true
64389
+ }
64390
+ }
64391
+ };
64392
+ resources.AssetsOAC = {
64393
+ Type: "AWS::CloudFront::OriginAccessControl",
64394
+ Properties: {
64395
+ OriginAccessControlConfig: {
64396
+ Name: `${slug}-${environment}-assets-oac`,
64397
+ OriginAccessControlOriginType: "s3",
64398
+ SigningBehavior: "always",
64399
+ SigningProtocol: "sigv4"
64400
+ }
64401
+ }
64402
+ };
64403
+ resources.AssetsDistribution = {
64404
+ Type: "AWS::CloudFront::Distribution",
64405
+ Properties: {
64406
+ DistributionConfig: {
64407
+ Enabled: true,
64408
+ DefaultCacheBehavior: {
64409
+ TargetOriginId: "assets",
64410
+ ViewerProtocolPolicy: "redirect-to-https",
64411
+ Compress: true,
64412
+ CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6"
64413
+ },
64414
+ Origins: [{
64415
+ Id: "assets",
64416
+ DomainName: Fn2.getAtt("AssetsBucket", "RegionalDomainName"),
64417
+ OriginAccessControlId: Fn2.ref("AssetsOAC"),
64418
+ S3OriginConfig: { OriginAccessIdentity: "" }
64419
+ }]
64420
+ }
64421
+ }
64422
+ };
64423
+ resources.AssetsBucketPolicy = {
64424
+ Type: "AWS::S3::BucketPolicy",
64425
+ Properties: {
64426
+ Bucket: Fn2.ref("AssetsBucket"),
64427
+ PolicyDocument: {
64428
+ Version: "2012-10-17",
64429
+ Statement: [{
64430
+ Effect: "Allow",
64431
+ Principal: { Service: "cloudfront.amazonaws.com" },
64432
+ Action: "s3:GetObject",
64433
+ Resource: Fn2.sub(`arn:aws:s3:::${assetsBucket}/*`),
64434
+ Condition: { StringEquals: { "AWS:SourceArn": Fn2.sub("arn:aws:cloudfront::${AWS::AccountId}:distribution/${AssetsDistribution}") } }
64435
+ }]
64436
+ }
64437
+ }
64438
+ };
64439
+ outputs.AssetsBucketName = { Description: "Assets bucket", Value: Fn2.ref("AssetsBucket") };
64440
+ outputs.AssetsCdnDomain = { Description: "Assets CloudFront domain", Value: Fn2.getAtt("AssetsDistribution", "DomainName") };
64441
+ }
64442
+ if (app.firewall?.enabled) {
64443
+ const wafRules = [];
64444
+ let priority = 0;
64445
+ if (app.firewall.rateLimit) {
64446
+ wafRules.push({
64447
+ Name: "rate-limit",
64448
+ Priority: priority++,
64449
+ Action: { Block: {} },
64450
+ Statement: { RateBasedStatement: { Limit: app.firewall.rateLimit, AggregateKeyType: "IP" } },
64451
+ VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-rate` }
64452
+ });
64453
+ }
64454
+ const managed = {
64455
+ sqlInjection: "AWSManagedRulesSQLiRuleSet",
64456
+ xss: "AWSManagedRulesCommonRuleSet",
64457
+ common: "AWSManagedRulesCommonRuleSet",
64458
+ botControl: "AWSManagedRulesBotControlRuleSet",
64459
+ ipReputation: "AWSManagedRulesAmazonIpReputationList"
64460
+ };
64461
+ for (const rule of app.firewall.rules ?? []) {
64462
+ const name = managed[rule];
64463
+ if (!name)
64464
+ continue;
64465
+ wafRules.push({
64466
+ Name: `managed-${rule}`,
64467
+ Priority: priority++,
64468
+ OverrideAction: { None: {} },
64469
+ Statement: { ManagedRuleGroupStatement: { VendorName: "AWS", Name: name } },
64470
+ VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-${rule}` }
64471
+ });
64472
+ }
64473
+ resources.WebAcl = {
64474
+ Type: "AWS::WAFv2::WebACL",
64475
+ Properties: {
64476
+ Name: `${slug}-${environment}-waf`,
64477
+ Scope: "REGIONAL",
64478
+ DefaultAction: { Allow: {} },
64479
+ Rules: wafRules,
64480
+ VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-waf` }
64481
+ }
64482
+ };
64483
+ resources.WebAclAssociation = {
64484
+ Type: "AWS::WAFv2::WebACLAssociation",
64485
+ DependsOn: ["HttpStage"],
64486
+ Properties: {
64487
+ ResourceArn: Fn2.sub("arn:aws:apigateway:${AWS::Region}::/apis/${HttpApi}/stages/$default"),
64488
+ WebACLArn: Fn2.getAtt("WebAcl", "Arn")
64489
+ }
64490
+ };
64491
+ }
64492
+ if (hasVpc && needsDataVpc) {
64493
+ resources.DataSecurityGroup = {
64494
+ Type: "AWS::EC2::SecurityGroup",
64495
+ Properties: {
64496
+ GroupDescription: `${slug}-${environment} serverless data access`,
64497
+ SecurityGroupIngress: [{ IpProtocol: "-1", CidrIp: "10.0.0.0/8" }]
64498
+ }
64499
+ };
64500
+ }
64501
+ if (app.cache?.driver === "elasticache") {
64502
+ resources.CacheSubnetGroup = {
64503
+ Type: "AWS::ElastiCache::SubnetGroup",
64504
+ Properties: { Description: `${slug}-${environment} cache subnets`, SubnetIds: subnets }
64505
+ };
64506
+ resources.CacheCluster = {
64507
+ Type: "AWS::ElastiCache::ReplicationGroup",
64508
+ Properties: {
64509
+ ReplicationGroupId: `${slug}-${environment}-cache`,
64510
+ ReplicationGroupDescription: `${slug}-${environment} redis`,
64511
+ Engine: "redis",
64512
+ CacheNodeType: "cache.t4g.micro",
64513
+ NumCacheClusters: 1,
64514
+ AutomaticFailoverEnabled: false,
64515
+ CacheSubnetGroupName: Fn2.ref("CacheSubnetGroup"),
64516
+ SecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")],
64517
+ TransitEncryptionEnabled: false
64518
+ }
64519
+ };
64520
+ outputs.CacheEndpoint = { Description: "Redis primary endpoint", Value: Fn2.getAtt("CacheCluster", "PrimaryEndPoint.Address") };
64521
+ }
64522
+ if (app.database?.connection === "aurora-serverless") {
64523
+ resources.DbSubnetGroup = {
64524
+ Type: "AWS::RDS::DBSubnetGroup",
64525
+ Properties: { DBSubnetGroupDescription: `${slug}-${environment} db subnets`, SubnetIds: subnets }
64526
+ };
64527
+ resources.DbSecret = {
64528
+ Type: "AWS::SecretsManager::Secret",
64529
+ Properties: {
64530
+ Name: `${slug}/${environment}/db`,
64531
+ GenerateSecretString: {
64532
+ SecretStringTemplate: JSON.stringify({ username: "app" }),
64533
+ GenerateStringKey: "password",
64534
+ PasswordLength: 32,
64535
+ ExcludePunctuation: true
64536
+ }
64537
+ }
64538
+ };
64539
+ resources.DbCluster = {
64540
+ Type: "AWS::RDS::DBCluster",
64541
+ Properties: {
64542
+ Engine: "aurora-mysql",
64543
+ EngineMode: "provisioned",
64544
+ DBClusterIdentifier: `${slug}-${environment}-db`,
64545
+ MasterUsername: Fn2.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:username}}"),
64546
+ MasterUserPassword: Fn2.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:password}}"),
64547
+ ServerlessV2ScalingConfiguration: { MinCapacity: 0.5, MaxCapacity: 4 },
64548
+ DBSubnetGroupName: Fn2.ref("DbSubnetGroup"),
64549
+ VpcSecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")]
64550
+ }
64551
+ };
64552
+ resources.DbInstance = {
64553
+ Type: "AWS::RDS::DBInstance",
64554
+ Properties: {
64555
+ Engine: "aurora-mysql",
64556
+ DBInstanceClass: "db.serverless",
64557
+ DBClusterIdentifier: Fn2.ref("DbCluster")
64558
+ }
64559
+ };
64560
+ outputs.DbEndpoint = { Description: "Aurora cluster endpoint", Value: Fn2.getAtt("DbCluster", "Endpoint.Address") };
64561
+ }
64562
+ if (app.rdsProxy && app.database?.connection === "aurora-serverless") {
64563
+ resources.DbProxyRole = {
64564
+ Type: "AWS::IAM::Role",
64565
+ Properties: {
64566
+ AssumeRolePolicyDocument: {
64567
+ Version: "2012-10-17",
64568
+ Statement: [{ Effect: "Allow", Principal: { Service: "rds.amazonaws.com" }, Action: "sts:AssumeRole" }]
64569
+ },
64570
+ Policies: [{
64571
+ PolicyName: "read-db-secret",
64572
+ PolicyDocument: {
64573
+ Version: "2012-10-17",
64574
+ Statement: [{ Effect: "Allow", Action: ["secretsmanager:GetSecretValue"], Resource: Fn2.ref("DbSecret") }]
64575
+ }
64576
+ }]
64577
+ }
64578
+ };
64579
+ resources.DbProxy = {
64580
+ Type: "AWS::RDS::DBProxy",
64581
+ Properties: {
64582
+ DBProxyName: typeof app.rdsProxy === "object" && app.rdsProxy.name ? app.rdsProxy.name : `${slug}-${environment}-proxy`,
64583
+ EngineFamily: "MYSQL",
64584
+ RoleArn: Fn2.getAtt("DbProxyRole", "Arn"),
64585
+ Auth: [{ AuthScheme: "SECRETS", SecretArn: Fn2.ref("DbSecret"), IAMAuth: "DISABLED" }],
64586
+ VpcSubnetIds: subnets,
64587
+ VpcSecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")],
64588
+ RequireTLS: false
64589
+ }
64590
+ };
64591
+ outputs.DbProxyEndpoint = { Description: "RDS Proxy endpoint", Value: Fn2.getAtt("DbProxy", "Endpoint") };
64592
+ }
64593
+ outputs.HttpFunctionName = { Description: "HTTP function name", Value: Fn2.ref("HttpFunction") };
64594
+ outputs.CliFunctionName = { Description: "CLI function name", Value: Fn2.ref("CliFunction") };
64595
+ if (hasQueue)
64596
+ outputs.QueueFunctionName = { Description: "Queue function name", Value: Fn2.ref("QueueFunction") };
64597
+ const template = {
64598
+ AWSTemplateFormatVersion: "2010-09-09",
64599
+ Description: `Serverless application for ${opts.config.project.name} (${slug}-${environment})`,
64600
+ Parameters: imageMode ? {
64601
+ ImageUri: { Type: "String", Description: "ECR image URI of the deployment artifact" }
64602
+ } : {
64603
+ ArtifactBucket: { Type: "String", Description: "S3 bucket holding the deployment artifact" },
64604
+ ArtifactKey: { Type: "String", Description: "S3 key of the deployment artifact (zip)" }
64605
+ },
64606
+ Resources: resources,
64607
+ Outputs: outputs
64608
+ };
64609
+ const resourceSummary = {};
64610
+ for (const r of Object.values(resources)) {
64611
+ resourceSummary[r.Type] = (resourceSummary[r.Type] ?? 0) + 1;
64612
+ }
64613
+ return { template, functionNames, queueNames, resourceSummary };
64614
+ }
64615
+ var PHP_LAYER_EXTENSIONS = [
64616
+ "cli",
64617
+ "fpm",
64618
+ "mbstring",
64619
+ "xml",
64620
+ "pdo",
64621
+ "mysqlnd",
64622
+ "gd",
64623
+ "bcmath",
64624
+ "intl",
64625
+ "opcache",
64626
+ "sodium",
64627
+ "process",
64628
+ "pecl-redis6",
64629
+ "pecl-apcu",
64630
+ "pgsql"
64631
+ ];
64632
+ function phpLayerPackages(phpVersion) {
64633
+ const scl = `php${phpVersion.replace(".", "")}`;
64634
+ return PHP_LAYER_EXTENSIONS.map((ext) => `${scl}-php-${ext}`);
64635
+ }
64636
+ function phpLayerBuildStage(phpVersion, asName) {
64637
+ const scl = `php${phpVersion.replace(".", "")}`;
64638
+ const packages = phpLayerPackages(phpVersion).join(" \\\n ");
64639
+ const sclRoot = `/opt/remi/${scl}/root`;
64640
+ const from = asName ? `FROM amazonlinux:2023 AS ${asName}` : "FROM amazonlinux:2023";
64641
+ return `${from}
64642
+
64643
+ # Remi provides version-isolated PHP SCL packages for EL9 (AL2023 compatible).
64644
+ RUN dnf -y install dnf-plugins-core 'dnf-command(config-manager)' && \\
64645
+ dnf -y install https://rpms.remirepo.net/enterprise/remi-release-9.rpm && \\
64646
+ dnf -y update && \\
64647
+ dnf -y install \\
64648
+ ${packages} \\
64649
+ findutils tar gzip && \\
64650
+ dnf clean all
64651
+
64652
+ # Relocate PHP + php-fpm + extensions and their shared libs under /opt.
64653
+ RUN set -eux; \\
64654
+ mkdir -p /opt/php/bin /opt/php/sbin /opt/php/lib /opt/php/lib/php/modules /opt/php/etc/php.d /opt/tscloud; \\
64655
+ cp ${sclRoot}/usr/bin/php /opt/php/bin/php; \\
64656
+ cp ${sclRoot}/usr/sbin/php-fpm /opt/php/sbin/php-fpm; \\
64657
+ EXT_DIR="$(${sclRoot}/usr/bin/php -r 'echo ini_get("extension_dir");')"; \\
64658
+ cp -a "$EXT_DIR"/*.so /opt/php/lib/php/modules/ || true; \\
64659
+ cp -a ${sclRoot}/etc/php.d/*.ini /opt/php/etc/php.d/ 2>/dev/null || true; \\
64660
+ # Copy shared-library dependencies of php, php-fpm, and the extension modules.
64661
+ for bin in /opt/php/bin/php /opt/php/sbin/php-fpm /opt/php/lib/php/modules/*.so; do \\
64662
+ ldd "$bin" 2>/dev/null | awk '/=>/{print $3}/ld-linux/{print $1}' | sort -u | while read -r lib; do \\
64663
+ [ -f "$lib" ] && cp -Ln "$lib" /opt/php/lib/ || true; \\
64664
+ done; \\
64665
+ done
64666
+
64667
+ # Point PHP at the relocated config + extension dir.
64668
+ RUN printf 'extension_dir=/opt/php/lib/php/modules\\n' > /opt/php/etc/php.ini && \\
64669
+ cat /opt/php/etc/php.d/*.ini >> /opt/php/etc/php.ini 2>/dev/null || true
64670
+ ENV PHP_INI_SCAN_DIR=/opt/php/etc/php.d
64671
+
64672
+ # Runtime assets (bootstrap, runtime loops, fpm config) are added by the build
64673
+ # orchestrator after the image is produced. Export /opt as the layer payload.
64674
+ CMD ["true"]
64675
+ `;
64676
+ }
64677
+ function generatePhpLayerDockerfile(options = {}) {
64678
+ const phpVersion = options.phpVersion ?? "8.3";
64679
+ return `# ts-cloud PHP ${phpVersion} runtime layer (generated) — AWS Lambda provided.al2023.
64680
+ ${phpLayerBuildStage(phpVersion)}`;
64681
+ }
64682
+ function generateAppImageDockerfile(options) {
64683
+ if (options.kind === "php") {
64684
+ const phpVersion = options.phpVersion ?? "8.3";
64685
+ return `# ts-cloud serverless PHP app image (generated, multi-stage).
64686
+ ${phpLayerBuildStage(phpVersion, "phpbuild")}
64687
+
64688
+ FROM public.ecr.aws/lambda/provided:al2023
64689
+ # Bake the relocated PHP runtime from the build stage.
64690
+ COPY --from=phpbuild /opt/ /opt/
64691
+ # ts-cloud runtime assets (bootstrap, runtime loops, fpm config).
64692
+ COPY runtime/ /opt/
64693
+ # The application source tree.
64694
+ COPY app/ /var/task/
64695
+ # /opt/bootstrap is the runtime entrypoint; mode comes from TSCLOUD_LAMBDA_MODE.
64696
+ ENTRYPOINT [ "/opt/bootstrap" ]
64697
+ `;
64698
+ }
64699
+ const nodeMajor = options.nodeMajor ?? "20";
64700
+ return `# ts-cloud serverless Node app image (generated).
64701
+ FROM public.ecr.aws/lambda/nodejs:${nodeMajor}
64702
+
64703
+ # The bundled handler artifact (index.mjs) at the Lambda task root.
64704
+ COPY app/ \${LAMBDA_TASK_ROOT}/
64705
+
64706
+ # Per-function CMD (e.g. index.http) is supplied via ImageConfig.Command.
64707
+ CMD [ "index.http" ]
64708
+ `;
64709
+ }
64710
+ function resolveApp(mod) {
64711
+ const m = mod;
64712
+ const def = m?.default ?? m;
64713
+ if (typeof def === "function")
64714
+ return { fetch: def };
64715
+ return {
64716
+ fetch: def?.fetch ?? m?.fetch,
64717
+ queue: def?.queue ?? m?.queue,
64718
+ cli: def?.cli ?? m?.cli
64719
+ };
64720
+ }
64721
+ var TEXT_CONTENT = /^(?:text\/|application\/(?:json|xml|javascript|graphql|x-www-form-urlencoded|.*\+json|.*\+xml)|image\/svg)/i;
64722
+ function isTextContentType(contentType) {
64723
+ if (!contentType)
64724
+ return true;
64725
+ return TEXT_CONTENT.test(contentType);
64726
+ }
64727
+ function readMaintenance(opts) {
64728
+ if (opts?.maintenance)
64729
+ return opts.maintenance;
64730
+ const env = globalThis.process?.env ?? {};
64731
+ return {
64732
+ enabled: env.MAINTENANCE_MODE === "1" || env.MAINTENANCE_MODE === "true",
64733
+ bypassSecret: env.MAINTENANCE_BYPASS_SECRET
64734
+ };
64735
+ }
64736
+ function eventToRequest(event) {
64737
+ const host = event.requestContext?.domainName ?? "localhost";
64738
+ const query = event.rawQueryString ? `?${event.rawQueryString}` : "";
64739
+ const url = `https://${host}${event.rawPath || "/"}${query}`;
64740
+ const headers = new Headers;
64741
+ for (const [key, value] of Object.entries(event.headers ?? {})) {
64742
+ if (value !== undefined)
64743
+ headers.set(key, value);
64744
+ }
64745
+ if (event.cookies?.length)
64746
+ headers.set("cookie", event.cookies.join("; "));
64747
+ const method = event.requestContext.http.method;
64748
+ let body;
64749
+ if (event.body !== undefined && method !== "GET" && method !== "HEAD") {
64750
+ body = event.isBase64Encoded ? new Uint8Array(Buffer.from(event.body, "base64")) : new TextEncoder().encode(event.body);
64751
+ }
64752
+ return new Request(url, { method, headers, body });
64753
+ }
64754
+ async function responseToResult(response) {
64755
+ const headers = {};
64756
+ const cookies = [];
64757
+ const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
64758
+ for (const c of setCookies)
64759
+ cookies.push(c);
64760
+ response.headers.forEach((value, key) => {
64761
+ if (key.toLowerCase() === "set-cookie")
64762
+ return;
64763
+ headers[key] = value;
64764
+ });
64765
+ const buffer = Buffer.from(await response.arrayBuffer());
64766
+ const textual = isTextContentType(response.headers.get("content-type"));
64767
+ return {
64768
+ statusCode: response.status,
64769
+ headers,
64770
+ ...cookies.length ? { cookies } : {},
64771
+ body: textual ? buffer.toString("utf-8") : buffer.toString("base64"),
64772
+ isBase64Encoded: !textual
64773
+ };
64774
+ }
64775
+ function createHttpHandler(handler8, opts) {
64776
+ return async (event) => {
64777
+ if (event.warmer) {
64778
+ return { statusCode: 200, headers: { "content-type": "text/plain" }, body: "warm", isBase64Encoded: false };
64779
+ }
64780
+ if (!handler8) {
64781
+ return { statusCode: 501, headers: { "content-type": "text/plain" }, body: "No HTTP handler configured", isBase64Encoded: false };
64782
+ }
64783
+ const maintenance = readMaintenance(opts);
64784
+ if (maintenance.enabled) {
64785
+ const bypass = event.headers?.["x-maintenance-bypass"] ?? event.cookies?.find((c) => c.startsWith("tscloud_bypass="))?.split("=")[1];
64786
+ if (!maintenance.bypassSecret || bypass !== maintenance.bypassSecret) {
64787
+ return {
64788
+ statusCode: 503,
64789
+ headers: { "content-type": "text/plain", "retry-after": "120" },
64790
+ body: "Service temporarily unavailable (maintenance mode)",
64791
+ isBase64Encoded: false
64792
+ };
64793
+ }
64794
+ }
64795
+ const request = eventToRequest(event);
64796
+ const response = await handler8(request);
64797
+ return responseToResult(response);
64798
+ };
64799
+ }
64800
+ function parseRecordBody(body) {
64801
+ try {
64802
+ return JSON.parse(body);
64803
+ } catch {
64804
+ return body;
64805
+ }
64806
+ }
64807
+ function createQueueHandler(handler8) {
64808
+ return async (event) => {
64809
+ const batchItemFailures = [];
64810
+ if (!handler8)
64811
+ return { batchItemFailures };
64812
+ for (const record of event.Records ?? []) {
64813
+ try {
64814
+ await handler8(parseRecordBody(record.body), record);
64815
+ } catch {
64816
+ batchItemFailures.push({ itemIdentifier: record.messageId });
64817
+ }
64818
+ }
64819
+ return { batchItemFailures };
64820
+ };
64821
+ }
64822
+ function createCliHandler(handler8) {
64823
+ return async (event) => {
64824
+ if (!handler8)
64825
+ return { statusCode: 501, output: "No CLI handler configured" };
64826
+ return handler8(event);
64827
+ };
64828
+ }
64829
+ function createHandlers(app, opts) {
64830
+ return {
64831
+ http: createHttpHandler(app.fetch, opts),
64832
+ queue: createQueueHandler(app.queue),
64833
+ cli: createCliHandler(app.cli)
64834
+ };
64835
+ }
64836
+ function generatePhpFpmConfig(options = {}) {
64837
+ const socket = options.socketPath ?? "/tmp/.tscloud-fpm.sock";
64838
+ const maxChildren = options.maxChildren ?? 1;
64839
+ const errorLog = options.errorLog ?? "/tmp/storage/logs/php-fpm.log";
64840
+ return `; ts-cloud php-fpm configuration (generated) — AWS Lambda custom runtime.
64841
+ [global]
64842
+ daemonize = no
64843
+ error_log = ${errorLog}
64844
+ log_level = warning
64845
+
64846
+ [www]
64847
+ listen = ${socket}
64848
+ listen.mode = 0666
64849
+ pm = static
64850
+ pm.max_children = ${maxChildren}
64851
+ catch_workers_output = yes
64852
+ decorate_workers_output = no
64853
+ ; Surface fatal errors / fpm logs to the Lambda log stream.
64854
+ php_admin_value[error_log] = /dev/stderr
64855
+ php_admin_flag[log_errors] = on
64856
+ clear_env = no
64857
+ `;
64858
+ }
64859
+ function assetsDir() {
64860
+ return join72(dirname32(fileURLToPath2(import.meta.url)), "runtime-assets");
64861
+ }
64862
+ function phpRuntimeLayerAssets() {
64863
+ const dir = assetsDir();
64864
+ const read = (f) => readFileSync4(join72(dir, f), "utf-8");
64865
+ return [
64866
+ { path: "bootstrap", contents: read("bootstrap"), mode: 493 },
64867
+ { path: "tscloud/runtime.php", contents: read("runtime.php"), mode: 420 },
64868
+ { path: "tscloud/octane-runtime.php", contents: read("octane-runtime.php"), mode: 420 },
64869
+ { path: "tscloud/cli-runtime.php", contents: read("cli-runtime.php"), mode: 420 },
64870
+ { path: "tscloud/fastcgi-client.php", contents: read("fastcgi-client.php"), mode: 420 },
64871
+ { path: "tscloud/php-fpm.conf", contents: generatePhpFpmConfig(), mode: 420 }
64872
+ ];
64873
+ }
64874
+ function laravelServerlessEnvDefaults(opts = {}) {
64875
+ const cache2 = opts.cacheDriver ?? "dynamodb";
64876
+ return {
64877
+ APP_ENV: "production",
64878
+ LOG_CHANNEL: "stderr",
64879
+ CACHE_STORE: cache2,
64880
+ CACHE_DRIVER: cache2,
64881
+ SESSION_DRIVER: cache2,
64882
+ QUEUE_CONNECTION: "sqs",
64883
+ FILESYSTEM_DISK: "s3",
64884
+ VIEW_COMPILED_PATH: "/tmp/storage/framework/views",
64885
+ APP_SERVICES_CACHE: "/tmp/bootstrap/cache/services.php",
64886
+ APP_PACKAGES_CACHE: "/tmp/bootstrap/cache/packages.php",
64887
+ APP_CONFIG_CACHE: "/tmp/bootstrap/cache/config.php",
64888
+ APP_ROUTES_CACHE: "/tmp/bootstrap/cache/routes.php",
64889
+ APP_EVENTS_CACHE: "/tmp/bootstrap/cache/events.php"
64890
+ };
64891
+ }
64892
+ var LARAVEL_SERVERLESS_BUILD_STEPS = [
64893
+ "composer install --no-dev --optimize-autoloader --no-interaction",
64894
+ "php artisan config:cache",
64895
+ "php artisan route:cache",
64896
+ "php artisan event:cache",
64897
+ "php artisan view:cache"
64898
+ ];
64899
+ function* walk(dir) {
64900
+ for (const entry of readdirSync52(dir)) {
64901
+ const full = join8(dir, entry);
64902
+ if (statSync32(full).isDirectory())
64903
+ yield* walk(full);
64904
+ else
64905
+ yield full;
64906
+ }
64907
+ }
64908
+ function buildPhpRuntimeLayerZip(options = {}) {
64909
+ const architecture = options.architecture ?? "x86_64";
64910
+ const platform = options.platform ?? (architecture === "arm64" ? "linux/arm64" : "linux/amd64");
64911
+ const step = options.onStep ?? (() => {});
64912
+ const stage = mkdtempSync2(join8(tmpdir2(), "tscloud-php-layer-"));
64913
+ const imageTag = "tscloud-php-layer:build";
64914
+ try {
64915
+ writeFileSync42(join8(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
64916
+ step("Building PHP runtime image (docker)");
64917
+ execFileSync("docker", ["build", "--platform", platform, "-t", imageTag, stage], { stdio: "inherit" });
64918
+ step("Extracting /opt from image");
64919
+ const cid = execFileSync("docker", ["create", "--platform", platform, imageTag], { encoding: "utf-8" }).trim();
64920
+ const optDir = join8(stage, "opt");
64921
+ try {
64922
+ execFileSync("docker", ["cp", `${cid}:/opt/.`, optDir], { stdio: "inherit" });
64923
+ } finally {
64924
+ execFileSync("docker", ["rm", cid], { stdio: "ignore" });
64925
+ }
64926
+ if (!existsSync52(optDir))
64927
+ throw new Error("layer build produced no /opt directory");
64928
+ const entries = [];
64929
+ for (const file of walk(optDir)) {
64930
+ const rel = relative22(optDir, file).replace(/\\/g, "/");
64931
+ const mode = statSync32(file).mode & 73 ? 493 : 420;
64932
+ entries.push({ name: rel, data: readFileSync5(file), mode });
64933
+ }
64934
+ step("Injecting runtime assets");
64935
+ const assetPaths = new Set(phpRuntimeLayerAssets().map((a) => a.path));
64936
+ const filtered = entries.filter((e) => !assetPaths.has(e.name));
64937
+ for (const asset of phpRuntimeLayerAssets()) {
64938
+ filtered.push({ name: asset.path, data: asset.contents, mode: asset.mode });
64939
+ }
64940
+ step("Packaging layer ZIP");
64941
+ const zip2 = createZip(filtered);
64942
+ return { zip: zip2, architecture, fileCount: filtered.length };
64943
+ } finally {
64944
+ rmSync2(stage, { recursive: true, force: true });
64945
+ }
64946
+ }
64947
+ var PHP_DEFAULT_EXCLUDES = [
64948
+ ".git",
64949
+ ".github",
64950
+ "node_modules",
64951
+ "tests",
64952
+ "storage/logs",
64953
+ "storage/framework/cache",
64954
+ "storage/framework/sessions",
64955
+ "storage/framework/views",
64956
+ ".env",
64957
+ ".env.local",
64958
+ ".vapor",
64959
+ ".ts-cloud",
64960
+ "dist-lambda"
64961
+ ];
64962
+ function isExcluded(rel, excludes) {
64963
+ return excludes.some((ex) => rel === ex || rel.startsWith(`${ex}/`));
64964
+ }
64965
+ function* walk2(dir, root, excludes) {
64966
+ for (const entry of readdirSync62(dir)) {
64967
+ const full = join9(dir, entry);
64968
+ const rel = relative32(root, full).replace(/\\/g, "/");
64969
+ if (isExcluded(rel, excludes))
64970
+ continue;
64971
+ if (statSync4(full).isDirectory())
64972
+ yield* walk2(full, root, excludes);
64973
+ else
64974
+ yield full;
64975
+ }
64976
+ }
64977
+ function runPhpBuildHooks(opts) {
64978
+ if (opts.skipBuild)
64979
+ return;
64980
+ const projectRoot = resolve22(opts.projectRoot ?? process.cwd());
64981
+ const steps = opts.app.build ?? LARAVEL_SERVERLESS_BUILD_STEPS;
64982
+ for (const step of steps) {
64983
+ opts.onStep?.(`build: ${step}`);
64984
+ execSync2(step, { stdio: "inherit", cwd: projectRoot });
64985
+ }
64986
+ }
64987
+ function collectPhpAppEntries(projectRoot, exclude = []) {
64988
+ const root = resolve22(projectRoot);
64989
+ const excludes = [...PHP_DEFAULT_EXCLUDES, ...exclude];
64990
+ const entries = [];
64991
+ for (const file of walk2(root, root, excludes)) {
64992
+ const rel = relative32(root, file).replace(/\\/g, "/");
64993
+ const executable = (statSync4(file).mode & 73) !== 0;
64994
+ entries.push({ name: rel, data: readFileSync6(file), mode: executable ? 493 : 420 });
64995
+ }
64996
+ return entries;
64997
+ }
64998
+ function packagePhpApp(opts) {
64999
+ const projectRoot = resolve22(opts.projectRoot ?? process.cwd());
65000
+ runPhpBuildHooks(opts);
65001
+ opts.onStep?.("packaging application tree");
65002
+ const entries = collectPhpAppEntries(projectRoot, opts.exclude);
65003
+ if (!entries.length)
65004
+ throw new Error(`No files to package under ${projectRoot}`);
65005
+ const zip2 = createZip(entries);
65006
+ const handler8 = opts.app.handlers?.http ?? "public/index.php";
65007
+ return {
65008
+ zip: zip2,
65009
+ sha256: createHash5("sha256").update(zip2).digest("hex"),
65010
+ handlers: {
65011
+ http: handler8,
65012
+ queue: opts.app.handlers?.queue ?? handler8,
65013
+ cli: opts.app.handlers?.cli ?? handler8
65014
+ },
65015
+ fileCount: entries.length
65016
+ };
65017
+ }
63782
65018
 
63783
65019
  class StaticSiteManager {
63784
65020
  optimizations = new Map;
@@ -68336,7 +69572,7 @@ class EC2Client {
68336
69572
  if (instance?.State?.Name === targetState) {
68337
69573
  return instance;
68338
69574
  }
68339
- await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
69575
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
68340
69576
  }
68341
69577
  return;
68342
69578
  }
@@ -69796,7 +71032,7 @@ class ECSClient {
69796
71032
  return true;
69797
71033
  }
69798
71034
  }
69799
- await new Promise((resolve13) => setTimeout(resolve13, delayMs));
71035
+ await new Promise((resolve14) => setTimeout(resolve14, delayMs));
69800
71036
  }
69801
71037
  return false;
69802
71038
  }
@@ -70337,7 +71573,7 @@ class SSMClient {
70337
71573
  const maxWait = options?.maxWaitMs || 300000;
70338
71574
  const startTime = Date.now();
70339
71575
  while (Date.now() - startTime < maxWait) {
70340
- await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
71576
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
70341
71577
  try {
70342
71578
  const invocation = await this.getCommandInvocation({
70343
71579
  CommandId: sendResult.CommandId,
@@ -70415,7 +71651,7 @@ class SSMClient {
70415
71651
  const terminalStatuses = new Set(["Success", "Failed", "Cancelled", "TimedOut"]);
70416
71652
  let lastInvocations = [];
70417
71653
  while (Date.now() - startTime < maxWait) {
70418
- await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
71654
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
70419
71655
  try {
70420
71656
  lastInvocations = await this.listCommandInvocations({
70421
71657
  CommandId: sendResult.CommandId,
@@ -71696,11 +72932,11 @@ class SQSClient {
71696
72932
  }
71697
72933
  // src/aws/lambda.ts
71698
72934
  init_client();
71699
- import { deflateRawSync } from "zlib";
72935
+ import { deflateRawSync as deflateRawSync2 } from "zlib";
71700
72936
  function createZipFile(filename, content) {
71701
72937
  const data = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
71702
- const compressedData = deflateRawSync(data);
71703
- const crc32 = calculateCrc32(data);
72938
+ const compressedData = deflateRawSync2(data);
72939
+ const crc322 = calculateCrc32(data);
71704
72940
  const now = new Date;
71705
72941
  const dosTime = (now.getHours() << 11 | now.getMinutes() << 5 | now.getSeconds() >> 1) & 65535;
71706
72942
  const dosDate = (now.getFullYear() - 1980 << 9 | now.getMonth() + 1 << 5 | now.getDate()) & 65535;
@@ -71712,7 +72948,7 @@ function createZipFile(filename, content) {
71712
72948
  localHeader.writeUInt16LE(8, 8);
71713
72949
  localHeader.writeUInt16LE(dosTime, 10);
71714
72950
  localHeader.writeUInt16LE(dosDate, 12);
71715
- localHeader.writeUInt32LE(crc32, 14);
72951
+ localHeader.writeUInt32LE(crc322, 14);
71716
72952
  localHeader.writeUInt32LE(compressedData.length, 18);
71717
72953
  localHeader.writeUInt32LE(data.length, 22);
71718
72954
  localHeader.writeUInt16LE(filenameBuffer.length, 26);
@@ -71726,7 +72962,7 @@ function createZipFile(filename, content) {
71726
72962
  centralHeader.writeUInt16LE(8, 10);
71727
72963
  centralHeader.writeUInt16LE(dosTime, 12);
71728
72964
  centralHeader.writeUInt16LE(dosDate, 14);
71729
- centralHeader.writeUInt32LE(crc32, 16);
72965
+ centralHeader.writeUInt32LE(crc322, 16);
71730
72966
  centralHeader.writeUInt32LE(compressedData.length, 20);
71731
72967
  centralHeader.writeUInt32LE(data.length, 24);
71732
72968
  centralHeader.writeUInt16LE(filenameBuffer.length, 28);
@@ -71957,10 +73193,10 @@ class LambdaClient {
71957
73193
  if (state === "Failed") {
71958
73194
  throw new Error(`Function ${functionName} failed: ${response.Configuration?.StateReason}`);
71959
73195
  }
71960
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
73196
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
71961
73197
  } catch (error) {
71962
73198
  if (error.code === "ResourceNotFoundException") {
71963
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
73199
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
71964
73200
  continue;
71965
73201
  }
71966
73202
  throw error;
@@ -73898,7 +75134,7 @@ class RDSClient {
73898
75134
  if (["deleted", "failed", "incompatible-restore", "incompatible-parameters"].includes(instance?.DBInstanceStatus || "")) {
73899
75135
  throw new Error(`DB instance ${dbInstanceIdentifier} is in terminal state: ${instance?.DBInstanceStatus}`);
73900
75136
  }
73901
- await new Promise((resolve13) => setTimeout(resolve13, delayMs));
75137
+ await new Promise((resolve14) => setTimeout(resolve14, delayMs));
73902
75138
  }
73903
75139
  throw new Error(`Timeout waiting for DB instance ${dbInstanceIdentifier} to become available`);
73904
75140
  }
@@ -73907,7 +75143,7 @@ class RDSClient {
73907
75143
  try {
73908
75144
  const instance = await this.describeDBInstance(dbInstanceIdentifier);
73909
75145
  if (instance?.DBInstanceStatus === "deleting") {
73910
- await new Promise((resolve13) => setTimeout(resolve13, delayMs));
75146
+ await new Promise((resolve14) => setTimeout(resolve14, delayMs));
73911
75147
  continue;
73912
75148
  }
73913
75149
  throw new Error(`DB instance ${dbInstanceIdentifier} is in state: ${instance?.DBInstanceStatus}`);
@@ -74678,7 +75914,7 @@ class BedrockClient {
74678
75914
  if (job.status === "Completed" || job.status === "Failed" || job.status === "Stopped") {
74679
75915
  return job;
74680
75916
  }
74681
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
75917
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
74682
75918
  }
74683
75919
  throw new Error(`Timeout waiting for model customization job ${jobIdentifier}`);
74684
75920
  }
@@ -75442,7 +76678,7 @@ class TextractClient {
75442
76678
  if (result.JobStatus === "FAILED") {
75443
76679
  throw new Error(`Textract job ${jobId} failed`);
75444
76680
  }
75445
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
76681
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
75446
76682
  }
75447
76683
  throw new Error(`Timeout waiting for Textract job ${jobId}`);
75448
76684
  }
@@ -75645,7 +76881,7 @@ class PollyClient {
75645
76881
  if (task?.TaskStatus === "failed") {
75646
76882
  throw new Error(`Polly task ${taskId} failed: ${task.TaskStatusReason}`);
75647
76883
  }
75648
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
76884
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
75649
76885
  }
75650
76886
  throw new Error(`Timeout waiting for Polly task ${taskId}`);
75651
76887
  }
@@ -75755,7 +76991,7 @@ class TranslateClient {
75755
76991
  if (job?.JobStatus === "FAILED" || job?.JobStatus === "STOPPED") {
75756
76992
  throw new Error(`Translation job ${jobId} failed: ${job.Message}`);
75757
76993
  }
75758
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
76994
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
75759
76995
  }
75760
76996
  throw new Error(`Timeout waiting for translation job ${jobId}`);
75761
76997
  }
@@ -75857,7 +77093,7 @@ class PersonalizeClient {
75857
77093
  if (sv?.status === "CREATE FAILED") {
75858
77094
  throw new Error(`Solution version failed: ${sv.failureReason}`);
75859
77095
  }
75860
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
77096
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
75861
77097
  }
75862
77098
  throw new Error(`Timeout waiting for solution version ${solutionVersionArn}`);
75863
77099
  }
@@ -75874,7 +77110,7 @@ class PersonalizeClient {
75874
77110
  if (campaign?.status === "CREATE FAILED") {
75875
77111
  throw new Error(`Campaign failed: ${campaign.failureReason}`);
75876
77112
  }
75877
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
77113
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
75878
77114
  }
75879
77115
  throw new Error(`Timeout waiting for campaign ${campaignArn}`);
75880
77116
  }
@@ -75891,7 +77127,7 @@ class PersonalizeClient {
75891
77127
  if (job?.status === "CREATE FAILED") {
75892
77128
  throw new Error(`Dataset import job failed: ${job.failureReason}`);
75893
77129
  }
75894
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
77130
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
75895
77131
  }
75896
77132
  throw new Error(`Timeout waiting for dataset import job ${jobArn}`);
75897
77133
  }
@@ -76046,7 +77282,7 @@ class KendraClient {
76046
77282
  if (result.Status === "FAILED") {
76047
77283
  throw new Error(`Index creation failed: ${result.ErrorMessage}`);
76048
77284
  }
76049
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
77285
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
76050
77286
  }
76051
77287
  throw new Error(`Timeout waiting for index ${indexId}`);
76052
77288
  }
@@ -76065,7 +77301,7 @@ class KendraClient {
76065
77301
  if (result.Status === "FAILED") {
76066
77302
  throw new Error(`Data source creation failed: ${result.ErrorMessage}`);
76067
77303
  }
76068
- await new Promise((resolve13) => setTimeout(resolve13, pollIntervalMs));
77304
+ await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
76069
77305
  }
76070
77306
  throw new Error(`Timeout waiting for data source ${dataSourceId}`);
76071
77307
  }
@@ -77099,7 +78335,7 @@ class ApplicationAutoScalingClient {
77099
78335
  init_s3();
77100
78336
  import * as net from "node:net";
77101
78337
  import * as tls from "node:tls";
77102
- import { readFileSync as readFileSync7 } from "node:fs";
78338
+ import { readFileSync as readFileSync10 } from "node:fs";
77103
78339
  import * as crypto4 from "node:crypto";
77104
78340
 
77105
78341
  class ImapServer {
@@ -77134,8 +78370,8 @@ class ImapServer {
77134
78370
  });
77135
78371
  if (this.config.tls?.key && this.config.tls?.cert) {
77136
78372
  const tlsOptions = {
77137
- key: readFileSync7(this.config.tls.key),
77138
- cert: readFileSync7(this.config.tls.cert)
78373
+ key: readFileSync10(this.config.tls.key),
78374
+ cert: readFileSync10(this.config.tls.cert)
77139
78375
  };
77140
78376
  this.tlsServer = tls.createServer(tlsOptions, (socket) => {
77141
78377
  this.handleConnection(socket);
@@ -77939,8 +79175,8 @@ class ImapServer {
77939
79175
  }
77940
79176
  this.send(session, `${tag} OK Begin TLS negotiation`);
77941
79177
  const tlsOptions = {
77942
- key: readFileSync7(this.config.tls.key),
77943
- cert: readFileSync7(this.config.tls.cert),
79178
+ key: readFileSync10(this.config.tls.key),
79179
+ cert: readFileSync10(this.config.tls.cert),
77944
79180
  isServer: true
77945
79181
  };
77946
79182
  const tlsSocket = new tls.TLSSocket(session.socket, tlsOptions);
@@ -78533,7 +79769,7 @@ init_ses();
78533
79769
  init_s3();
78534
79770
  import * as net2 from "node:net";
78535
79771
  import * as tls2 from "node:tls";
78536
- import { readFileSync as readFileSync8 } from "node:fs";
79772
+ import { readFileSync as readFileSync11 } from "node:fs";
78537
79773
  import * as crypto5 from "node:crypto";
78538
79774
 
78539
79775
  class SmtpServer {
@@ -78564,8 +79800,8 @@ class SmtpServer {
78564
79800
  });
78565
79801
  if (this.config.tls?.key && this.config.tls?.cert) {
78566
79802
  const tlsOptions = {
78567
- key: readFileSync8(this.config.tls.key),
78568
- cert: readFileSync8(this.config.tls.cert)
79803
+ key: readFileSync11(this.config.tls.key),
79804
+ cert: readFileSync11(this.config.tls.cert)
78569
79805
  };
78570
79806
  this.tlsServer = tls2.createServer(tlsOptions, (socket) => {
78571
79807
  this.handleConnection(socket, true);
@@ -78699,8 +79935,8 @@ class SmtpServer {
78699
79935
  }
78700
79936
  this.send(session, "220 Ready to start TLS");
78701
79937
  const tlsOptions = {
78702
- key: readFileSync8(this.config.tls.key),
78703
- cert: readFileSync8(this.config.tls.cert),
79938
+ key: readFileSync11(this.config.tls.key),
79939
+ cert: readFileSync11(this.config.tls.cert),
78704
79940
  isServer: true
78705
79941
  };
78706
79942
  const tlsSocket = new tls2.TLSSocket(session.socket, tlsOptions);
@@ -79469,7 +80705,7 @@ class SmsClient {
79469
80705
  if (receipt && (receipt.status === "delivered" || receipt.status === "failed")) {
79470
80706
  return receipt;
79471
80707
  }
79472
- await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
80708
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
79473
80709
  }
79474
80710
  return await this.getDeliveryReceipt(messageId);
79475
80711
  }
@@ -81041,15 +82277,15 @@ async function cleanupDns01Challenge(options) {
81041
82277
  }
81042
82278
  function needsRenewal(certPath) {
81043
82279
  try {
81044
- const { execSync } = __require("node:child_process");
81045
- const _result = execSync(`openssl x509 -checkend 2592000 -noout -in ${certPath}/cert.pem`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
82280
+ const { execSync: execSync3 } = __require("node:child_process");
82281
+ const _result = execSync3(`openssl x509 -checkend 2592000 -noout -in ${certPath}/cert.pem`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
81046
82282
  return false;
81047
82283
  } catch {
81048
82284
  return true;
81049
82285
  }
81050
82286
  }
81051
82287
  // src/ssl/acme-client.ts
81052
- import { createHash as createHash7, createSign, generateKeyPairSync } from "node:crypto";
82288
+ import { createHash as createHash9, createSign, generateKeyPairSync } from "node:crypto";
81053
82289
  var ACME_DIRECTORIES = {
81054
82290
  production: "https://acme-v02.api.letsencrypt.org/directory",
81055
82291
  staging: "https://acme-staging-v02.api.letsencrypt.org/directory"
@@ -81119,7 +82355,7 @@ class AcmeClient {
81119
82355
  x: jwk.x,
81120
82356
  y: jwk.y
81121
82357
  });
81122
- const hash2 = createHash7("sha256").update(canonical).digest();
82358
+ const hash2 = createHash9("sha256").update(canonical).digest();
81123
82359
  return this.base64UrlEncode(hash2);
81124
82360
  }
81125
82361
  base64UrlEncode(data) {
@@ -81232,7 +82468,7 @@ class AcmeClient {
81232
82468
  identifier: `/.well-known/acme-challenge/${c.token}`
81233
82469
  };
81234
82470
  } else {
81235
- const dnsValue = this.base64UrlEncode(createHash7("sha256").update(keyAuthorization).digest());
82471
+ const dnsValue = this.base64UrlEncode(createHash9("sha256").update(keyAuthorization).digest());
81236
82472
  return {
81237
82473
  type: "dns-01",
81238
82474
  token: c.token,
@@ -81256,7 +82492,7 @@ class AcmeClient {
81256
82492
  if (body.status === "invalid") {
81257
82493
  throw new Error(`Authorization failed: ${JSON.stringify(body)}`);
81258
82494
  }
81259
- await new Promise((resolve13) => setTimeout(resolve13, 2000));
82495
+ await new Promise((resolve14) => setTimeout(resolve14, 2000));
81260
82496
  }
81261
82497
  throw new Error("Authorization timed out");
81262
82498
  }
@@ -81399,11 +82635,11 @@ init_static_site_external_dns();
81399
82635
  init_static_site_external_dns();
81400
82636
  import process19 from "node:process";
81401
82637
  import { existsSync as existsSync16 } from "node:fs";
81402
- import { join as join11 } from "node:path";
82638
+ import { join as join13 } from "node:path";
81403
82639
  async function deploySite(config6) {
81404
82640
  const start = Date.now();
81405
82641
  const sourceDir = config6.sourceDir ?? "dist";
81406
- if (!existsSync16(join11(sourceDir, "index.html"))) {
82642
+ if (!existsSync16(join13(sourceDir, "index.html"))) {
81407
82643
  return fail(start, `${sourceDir}/ has no index.html — build first.`);
81408
82644
  }
81409
82645
  if (!process19.env.AWS_ACCESS_KEY_ID || !process19.env.AWS_SECRET_ACCESS_KEY) {
@@ -81472,10 +82708,46 @@ function fail(start, message) {
81472
82708
  return { success: false, message, durationMs: Date.now() - start };
81473
82709
  }
81474
82710
  // src/drivers/aws/driver.ts
81475
- import { readFileSync as readFileSync9 } from "node:fs";
82711
+ import { readFileSync as readFileSync12 } from "node:fs";
81476
82712
  init_cloudformation();
81477
82713
  init_s3();
81478
82714
 
82715
+ // src/drivers/shared/package-manager.ts
82716
+ var PANTRY_INSTALL_DIR = "/usr/local/bin";
82717
+ var PANTRY_PROJECT_DIR = "/opt/pantry";
82718
+ function sh(value) {
82719
+ return `'${value.split("'").join("'\\''")}'`;
82720
+ }
82721
+ function buildPantryBootstrapScript(options = {}) {
82722
+ const versionLine = options.version ? `export PANTRY_VERSION=${sh(options.version)}` : 'export PANTRY_VERSION="${PANTRY_VERSION:-latest}"';
82723
+ return [
82724
+ "export DEBIAN_FRONTEND=noninteractive",
82725
+ "export PANTRY_SERVICE_SCOPE=system",
82726
+ `export PANTRY_INSTALL_DIR=${PANTRY_INSTALL_DIR}`,
82727
+ versionLine,
82728
+ "command -v curl >/dev/null 2>&1 || (apt-get update -y && apt-get install -y curl ca-certificates)",
82729
+ "command -v unzip >/dev/null 2>&1 || (apt-get update -y && apt-get install -y unzip)",
82730
+ "command -v pantry >/dev/null 2>&1 || curl -fsSL https://pantry.dev | bash",
82731
+ `export PATH="${PANTRY_INSTALL_DIR}:$PATH"`,
82732
+ `mkdir -p ${PANTRY_PROJECT_DIR}`
82733
+ ];
82734
+ }
82735
+ function pantryEnvActivation() {
82736
+ return `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)" || true`;
82737
+ }
82738
+ function buildPantryInstallScript(specs) {
82739
+ if (specs.length === 0)
82740
+ return [];
82741
+ const unique = [...new Set(specs)];
82742
+ return [`(cd ${PANTRY_PROJECT_DIR} && pantry install ${unique.map(sh).join(" ")})`];
82743
+ }
82744
+ function buildPantryServiceScript(services) {
82745
+ return [...new Set(services)].flatMap((name) => [
82746
+ `(cd ${PANTRY_PROJECT_DIR} && pantry start ${sh(name)})`,
82747
+ `(cd ${PANTRY_PROJECT_DIR} && pantry enable ${sh(name)})`
82748
+ ]);
82749
+ }
82750
+
81479
82751
  // src/drivers/shared/db-provision.ts
81480
82752
  function enabled(spec) {
81481
82753
  return spec === true || typeof spec === "object" && spec != null;
@@ -81484,50 +82756,42 @@ function sq(value) {
81484
82756
  const escaped = value.split("'").join("'\\''");
81485
82757
  return `'${escaped}'`;
81486
82758
  }
81487
- function buildServicesProvisionScript(services = {}, options = {}) {
81488
- const out = ["export DEBIAN_FRONTEND=noninteractive"];
81489
- let touchedApt = false;
81490
- const ensureUpdate = () => {
81491
- if (!touchedApt) {
81492
- out.push("apt-get update -y");
81493
- touchedApt = true;
81494
- }
81495
- };
81496
- const bind = options.bindPrivate === true;
81497
- if (enabled(services.mysql) || enabled(services.mariadb)) {
81498
- ensureUpdate();
81499
- const pkg = enabled(services.mysql) ? "mysql-server" : "mariadb-server";
81500
- const svc = enabled(services.mysql) ? "mysql" : "mariadb";
81501
- out.push(`apt-get install -y ${pkg}`);
81502
- if (bind) {
81503
- out.push("find /etc/mysql -name '*.cnf' -exec sed -i 's/^[[:space:]]*bind-address.*/bind-address = 0.0.0.0/; s/^[[:space:]]*mysqlx-bind-address.*/mysqlx-bind-address = 0.0.0.0/' {} + 2>/dev/null || true", `for d in /etc/mysql/mysql.conf.d /etc/mysql/mariadb.conf.d; do [ -d "$d" ] && printf '[mysqld]\\nbind-address = 0.0.0.0\\n' > "$d/zz-ts-cloud-bind.cnf"; done`);
81504
- }
81505
- out.push(`systemctl enable ${svc}`, `systemctl restart ${svc}`);
82759
+ function planServices(services) {
82760
+ const packages = [];
82761
+ const names = [];
82762
+ if (enabled(services.mysql)) {
82763
+ packages.push("mysql.com");
82764
+ names.push("mysql");
82765
+ } else if (enabled(services.mariadb)) {
82766
+ packages.push("mariadb.org");
82767
+ names.push("mariadb");
81506
82768
  }
81507
82769
  if (enabled(services.postgres)) {
81508
- ensureUpdate();
81509
- out.push("apt-get install -y postgresql postgresql-contrib");
81510
- if (bind) {
81511
- out.push(`echo "listen_addresses = '*'" > /etc/postgresql/conf.d-ts-cloud.conf 2>/dev/null || true`, `CONF=$(ls /etc/postgresql/*/main/postgresql.conf 2>/dev/null | head -1); [ -n "$CONF" ] && sed -i "s/^#\\?listen_addresses.*/listen_addresses = '*'/" "$CONF" || true`, 'HBA=$(ls /etc/postgresql/*/main/pg_hba.conf 2>/dev/null | head -1); [ -n "$HBA" ] && echo "host all all 10.0.0.0/8 md5" >> "$HBA" || true');
81512
- }
81513
- out.push("systemctl enable postgresql", "systemctl restart postgresql");
82770
+ packages.push("postgresql.org");
82771
+ names.push("postgres");
81514
82772
  }
81515
82773
  if (enabled(services.redis)) {
81516
- ensureUpdate();
81517
- out.push("apt-get install -y redis-server");
81518
- if (bind) {
81519
- out.push("sed -i 's/^bind .*/bind 0.0.0.0/' /etc/redis/redis.conf || true", "sed -i 's/^protected-mode yes/protected-mode no/' /etc/redis/redis.conf || true");
81520
- }
81521
- out.push("systemctl enable redis-server", "systemctl restart redis-server");
82774
+ packages.push("redis.io");
82775
+ names.push("redis");
81522
82776
  }
81523
82777
  if (enabled(services.memcached)) {
81524
- ensureUpdate();
81525
- out.push("apt-get install -y memcached", "systemctl enable memcached", "systemctl start memcached");
82778
+ packages.push("memcached.org");
82779
+ names.push("memcached");
81526
82780
  }
81527
82781
  if (enabled(services.meilisearch)) {
81528
- out.push("curl -fsSL https://install.meilisearch.com | sh", "mv ./meilisearch /usr/local/bin/meilisearch", "getent passwd meilisearch >/dev/null || useradd --system --home-dir /var/lib/meilisearch --create-home --shell /usr/sbin/nologin meilisearch", "mkdir -p /var/lib/meilisearch", "chown -R meilisearch:meilisearch /var/lib/meilisearch", "cat > /etc/systemd/system/meilisearch.service <<'TS_CLOUD_MEILI_EOF'", "[Unit]", "Description=Meilisearch", "After=network.target", "", "[Service]", "Type=simple", "User=meilisearch", "Group=meilisearch", `ExecStart=/usr/local/bin/meilisearch --db-path /var/lib/meilisearch/data --env production${bind ? " --http-addr 0.0.0.0:7700" : ""}`, "Restart=always", "RestartSec=5", "", "[Install]", "WantedBy=multi-user.target", "TS_CLOUD_MEILI_EOF", "systemctl daemon-reload", "systemctl enable meilisearch", "systemctl start meilisearch");
82782
+ packages.push("meilisearch.com");
82783
+ names.push("meilisearch");
81529
82784
  }
81530
- return out;
82785
+ return { packages, services: names };
82786
+ }
82787
+ function buildServicesProvisionScript(services = {}, _options = {}) {
82788
+ const plan = planServices(services);
82789
+ if (plan.packages.length === 0)
82790
+ return [];
82791
+ return [
82792
+ ...buildPantryInstallScript(plan.packages),
82793
+ ...buildPantryServiceScript(plan.services)
82794
+ ];
81531
82795
  }
81532
82796
  function buildDatabaseSetupScript(database, services = {}) {
81533
82797
  if (!database?.name)
@@ -81541,15 +82805,18 @@ function buildDatabaseSetupScript(database, services = {}) {
81541
82805
  const useMariadb = enabled(services.mariadb) || database.engine === "mariadb";
81542
82806
  const useMysql = enabled(services.mysql) || database.engine === "mysql";
81543
82807
  if (usePostgres && !useMysql && !useMariadb) {
82808
+ const psql = "psql -h 127.0.0.1 -p 5432 -U postgres";
81544
82809
  return [
81545
- `sudo -u postgres psql -tc "SELECT 1 FROM pg_roles WHERE rolname=${sq(user)}" | grep -q 1 ` + `|| sudo -u postgres psql -c "CREATE ROLE ${sq(user)} LOGIN PASSWORD ${sq(pass)};"`,
81546
- `sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname=${sq(name)}" | grep -q 1 ` + `|| sudo -u postgres psql -c "CREATE DATABASE ${sq(name)} OWNER ${sq(user)};"`
82810
+ pantryEnvActivation(),
82811
+ `${psql} -tc "SELECT 1 FROM pg_roles WHERE rolname=${sq(user)}" | grep -q 1 ` + `|| ${psql} -c "CREATE ROLE ${sq(user)} LOGIN PASSWORD ${sq(pass)};"`,
82812
+ `${psql} -tc "SELECT 1 FROM pg_database WHERE datname=${sq(name)}" | grep -q 1 ` + `|| ${psql} -c "CREATE DATABASE ${sq(name)} OWNER ${sq(user)};"`
81547
82813
  ];
81548
82814
  }
81549
82815
  const ident = (v) => v.replace(/`/g, "``");
81550
82816
  const lit = (v) => v.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
81551
82817
  return [
81552
- "mysql <<'TS_CLOUD_SQL_EOF'",
82818
+ pantryEnvActivation(),
82819
+ "mysql -h 127.0.0.1 -P 3306 -u root <<'TS_CLOUD_SQL_EOF'",
81553
82820
  `CREATE DATABASE IF NOT EXISTS \`${ident(name)}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`,
81554
82821
  `CREATE USER IF NOT EXISTS '${lit(user)}'@'%' IDENTIFIED BY '${lit(pass)}';`,
81555
82822
  `GRANT ALL PRIVILEGES ON \`${ident(name)}\`.* TO '${lit(user)}'@'%';`,
@@ -81576,60 +82843,225 @@ function buildManagedDbEnv(database) {
81576
82843
  }
81577
82844
 
81578
82845
  // src/drivers/shared/php-provision.ts
81579
- var LARAVEL_PHP_EXTENSIONS = [
81580
- "fpm",
81581
- "cli",
81582
- "common",
81583
- "mbstring",
81584
- "xml",
81585
- "curl",
81586
- "mysql",
81587
- "pgsql",
81588
- "sqlite3",
81589
- "redis",
81590
- "gd",
81591
- "bcmath",
81592
- "zip",
81593
- "intl",
81594
- "readline",
81595
- "soap",
81596
- "gmp",
81597
- "opcache"
81598
- ];
81599
- function phpPackagesForVersion(version2, extensions) {
81600
- return extensions.map((suffix) => `php${version2}-${suffix}`);
82846
+ var PHP_FPM_LISTEN = "127.0.0.1:9074";
82847
+ function phpFpmSocketPath(_version) {
82848
+ return PHP_FPM_LISTEN;
81601
82849
  }
81602
- function phpFpmSocketPath(version2) {
81603
- return `/run/php/php${version2}-fpm.sock`;
82850
+ function resolveDefaultPhpVersion(options = {}) {
82851
+ const versions = options.versions?.length ? options.versions : ["8.3"];
82852
+ return options.default && versions.includes(options.default) ? options.default : versions[0];
81604
82853
  }
81605
82854
  function buildPhpProvisionScript(options = {}) {
81606
- const versions = options.versions?.length ? [...new Set(options.versions)] : ["8.3"];
81607
- const defaultVersion = options.default && versions.includes(options.default) ? options.default : versions[0];
82855
+ const defaultVersion = resolveDefaultPhpVersion(options);
81608
82856
  const installNginx = options.installNginx !== false;
81609
82857
  const installComposer = options.installComposer !== false;
81610
- const extensions = [...new Set([...LARAVEL_PHP_EXTENSIONS, ...options.extensions || []])];
82858
+ const specs = [`php.net@${defaultVersion}`];
82859
+ if (installComposer)
82860
+ specs.push("getcomposer.org");
82861
+ if (installNginx)
82862
+ specs.push("nginx.org");
82863
+ return [
82864
+ ...buildPantryInstallScript(specs),
82865
+ ...buildPantryServiceScript(["php-fpm"])
82866
+ ];
82867
+ }
82868
+
82869
+ // src/drivers/shared/nginx-vhost.ts
82870
+ function htpasswdPath(siteName) {
82871
+ return `/etc/nginx/.htpasswd-${siteName}`;
82872
+ }
82873
+ var PHP_TYPES = new Set(["laravel", "php", "statamic", "wordpress"]);
82874
+ function isPhpSiteType(type) {
82875
+ return PHP_TYPES.has(type);
82876
+ }
82877
+ function defaultWebDirectory(type) {
82878
+ return type === "laravel" || type === "statamic" || type === "wordpress" ? "public" : "";
82879
+ }
82880
+ function resolveRoot(appDir, webDirectory) {
82881
+ const base = appDir.replace(/\/+$/, "");
82882
+ const sub = webDirectory.replace(/^\/+|\/+$/g, "");
82883
+ return sub ? `${base}/${sub}` : base;
82884
+ }
82885
+ function vhostBody(options) {
82886
+ const type = options.type ?? "laravel";
82887
+ const webDirectory = options.webDirectory ?? defaultWebDirectory(type);
82888
+ const root = resolveRoot(options.appDir, webDirectory);
82889
+ const isPhp = isPhpSiteType(type);
82890
+ const phpVersion = options.phpVersion ?? "8.3";
81611
82891
  const lines = [
81612
- "export DEBIAN_FRONTEND=noninteractive",
81613
- 'export HOME="${HOME:-/root}"',
81614
- 'export COMPOSER_HOME="${COMPOSER_HOME:-/root/.composer}"',
81615
- "apt-get update -y",
81616
- "apt-get install -y software-properties-common ca-certificates apt-transport-https lsb-release gnupg",
81617
- "add-apt-repository -y ppa:ondrej/php",
81618
- "apt-get update -y"
82892
+ ` root ${root};`,
82893
+ "",
82894
+ ' add_header X-Frame-Options "SAMEORIGIN";',
82895
+ ' add_header X-Content-Type-Options "nosniff";',
82896
+ "",
82897
+ ` index ${isPhp ? "index.php index.html" : "index.html index.htm"};`,
82898
+ "",
82899
+ " charset utf-8;",
82900
+ ""
81619
82901
  ];
81620
- if (installNginx) {
81621
- lines.push("apt-get install -y nginx", "systemctl enable nginx");
82902
+ if (options.auth) {
82903
+ lines.push(` auth_basic "${options.auth.realm || "Restricted"}";`, ` auth_basic_user_file ${htpasswdPath(options.siteName)};`, "");
81622
82904
  }
81623
- for (const version2 of versions) {
81624
- const pkgs = phpPackagesForVersion(version2, extensions);
81625
- lines.push(`apt-get install -y ${pkgs.join(" ")}`, `systemctl enable php${version2}-fpm`, `systemctl start php${version2}-fpm`);
82905
+ for (const [from, to] of Object.entries(options.redirects || {})) {
82906
+ lines.push(` location = ${from} { return 301 ${to}; }`);
81626
82907
  }
81627
- lines.push(`update-alternatives --set php /usr/bin/php${defaultVersion}`);
81628
- if (installComposer) {
81629
- lines.push("curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php", "php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer", "rm -f /tmp/composer-setup.php");
82908
+ if (Object.keys(options.redirects || {}).length > 0)
82909
+ lines.push("");
82910
+ if (isPhp) {
82911
+ lines.push(" location / {", " try_files $uri $uri/ /index.php?$query_string;", " }", "", " location = /favicon.ico { access_log off; log_not_found off; }", " location = /robots.txt { access_log off; log_not_found off; }", "", " error_page 404 /index.php;", "", " location ~ \\.php$ {", ` fastcgi_pass ${phpFpmSocketPath(phpVersion)};`, " fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;", " include fastcgi_params;", " }", "", " location ~ /\\.(?!well-known).* {", " deny all;", " }");
82912
+ } else if (type === "spa") {
82913
+ lines.push(" location / {", " try_files $uri $uri/ /index.html;", " }");
82914
+ } else {
82915
+ lines.push(" location / {", " try_files $uri $uri/ =404;", " }");
81630
82916
  }
81631
82917
  return lines;
81632
82918
  }
82919
+ function buildNginxVhost(options) {
82920
+ const serverNames = [options.domain, ...options.aliases || []].filter(Boolean).join(" ");
82921
+ const body = vhostBody(options);
82922
+ if (options.ssl) {
82923
+ const redirect = [
82924
+ "server {",
82925
+ " listen 80;",
82926
+ " listen [::]:80;",
82927
+ ` server_name ${serverNames};`,
82928
+ " return 301 https://$host$request_uri;",
82929
+ "}"
82930
+ ];
82931
+ const tls3 = [
82932
+ "server {",
82933
+ " listen 443 ssl;",
82934
+ " listen [::]:443 ssl;",
82935
+ ` server_name ${serverNames};`,
82936
+ ` ssl_certificate ${options.ssl.certPath};`,
82937
+ ` ssl_certificate_key ${options.ssl.keyPath};`,
82938
+ "",
82939
+ ...body,
82940
+ "}"
82941
+ ];
82942
+ return `${[...redirect, "", ...tls3].join(`
82943
+ `)}
82944
+ `;
82945
+ }
82946
+ const lines = [
82947
+ "server {",
82948
+ " listen 80;",
82949
+ " listen [::]:80;",
82950
+ ` server_name ${serverNames};`,
82951
+ ...body,
82952
+ "}"
82953
+ ];
82954
+ return `${lines.join(`
82955
+ `)}
82956
+ `;
82957
+ }
82958
+ function buildNginxVhostScript(options) {
82959
+ const available = `/etc/nginx/sites-available/${options.siteName}`;
82960
+ const enabled2 = `/etc/nginx/sites-enabled/${options.siteName}`;
82961
+ const vhost = buildNginxVhost(options);
82962
+ const out = [];
82963
+ if (options.auth) {
82964
+ const file = htpasswdPath(options.siteName);
82965
+ const sq2 = (v) => v.split("'").join("'\\''");
82966
+ const pw = sq2(options.auth.password);
82967
+ const user = sq2(options.auth.username);
82968
+ 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`);
82969
+ }
82970
+ 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");
82971
+ return out;
82972
+ }
82973
+ var NGINX_WRAPPER = "/usr/local/bin/ts-cloud-nginx";
82974
+ function buildNginxServiceScript(projectDir = "/opt/pantry") {
82975
+ return [
82976
+ "mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled /var/log/nginx /var/lib/nginx/body /var/lib/nginx/proxy /var/lib/nginx/fastcgi /var/lib/nginx/uwsgi /var/lib/nginx/scgi",
82977
+ "getent passwd www-data >/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin www-data",
82978
+ "chown -R www-data:www-data /var/lib/nginx /var/log/nginx",
82979
+ `cat > ${NGINX_WRAPPER} <<'TS_CLOUD_NGINXBIN_EOF'`,
82980
+ "#!/bin/sh",
82981
+ `eval "$(cd ${projectDir} && pantry env 2>/dev/null)"`,
82982
+ "NGINX_BIN=$(command -v nginx || echo /opt/pantry/pantry/.bin/nginx)",
82983
+ 'exec "$NGINX_BIN" -c /etc/nginx/nginx.conf "$@"',
82984
+ "TS_CLOUD_NGINXBIN_EOF",
82985
+ `chmod +x ${NGINX_WRAPPER}`,
82986
+ "cat > /etc/nginx/fastcgi_params <<'TS_CLOUD_FCGI_EOF'",
82987
+ "fastcgi_param QUERY_STRING $query_string;",
82988
+ "fastcgi_param REQUEST_METHOD $request_method;",
82989
+ "fastcgi_param CONTENT_TYPE $content_type;",
82990
+ "fastcgi_param CONTENT_LENGTH $content_length;",
82991
+ "fastcgi_param SCRIPT_NAME $fastcgi_script_name;",
82992
+ "fastcgi_param REQUEST_URI $request_uri;",
82993
+ "fastcgi_param DOCUMENT_URI $document_uri;",
82994
+ "fastcgi_param DOCUMENT_ROOT $document_root;",
82995
+ "fastcgi_param SERVER_PROTOCOL $server_protocol;",
82996
+ "fastcgi_param REQUEST_SCHEME $scheme;",
82997
+ "fastcgi_param HTTPS $https if_not_empty;",
82998
+ "fastcgi_param GATEWAY_INTERFACE CGI/1.1;",
82999
+ "fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;",
83000
+ "fastcgi_param REMOTE_ADDR $remote_addr;",
83001
+ "fastcgi_param REMOTE_PORT $remote_port;",
83002
+ "fastcgi_param SERVER_ADDR $server_addr;",
83003
+ "fastcgi_param SERVER_PORT $server_port;",
83004
+ "fastcgi_param SERVER_NAME $server_name;",
83005
+ "fastcgi_param REDIRECT_STATUS 200;",
83006
+ "TS_CLOUD_FCGI_EOF",
83007
+ "cat > /etc/nginx/nginx.conf <<'TS_CLOUD_NGINXCONF_EOF'",
83008
+ "user www-data;",
83009
+ "worker_processes auto;",
83010
+ "pid /run/nginx.pid;",
83011
+ "error_log /var/log/nginx/error.log;",
83012
+ "events { worker_connections 1024; }",
83013
+ "http {",
83014
+ " default_type application/octet-stream;",
83015
+ " types {",
83016
+ " text/html html htm;",
83017
+ " text/css css;",
83018
+ " application/javascript js;",
83019
+ " application/json json;",
83020
+ " image/svg+xml svg;",
83021
+ " image/png png;",
83022
+ " image/jpeg jpg jpeg;",
83023
+ " image/gif gif;",
83024
+ " image/x-icon ico;",
83025
+ " image/webp webp;",
83026
+ " font/woff2 woff2;",
83027
+ " font/woff woff;",
83028
+ " text/plain txt;",
83029
+ " }",
83030
+ " access_log /var/log/nginx/access.log;",
83031
+ " sendfile on;",
83032
+ " tcp_nopush on;",
83033
+ " keepalive_timeout 65;",
83034
+ " server_tokens off;",
83035
+ " client_max_body_size 100m;",
83036
+ " client_body_temp_path /var/lib/nginx/body;",
83037
+ " proxy_temp_path /var/lib/nginx/proxy;",
83038
+ " fastcgi_temp_path /var/lib/nginx/fastcgi;",
83039
+ " uwsgi_temp_path /var/lib/nginx/uwsgi;",
83040
+ " scgi_temp_path /var/lib/nginx/scgi;",
83041
+ " include /etc/nginx/sites-enabled/*;",
83042
+ "}",
83043
+ "TS_CLOUD_NGINXCONF_EOF",
83044
+ "cat > /etc/systemd/system/ts-cloud-nginx.service <<'TS_CLOUD_NGINXUNIT_EOF'",
83045
+ "[Unit]",
83046
+ "Description=ts-cloud nginx (pantry)",
83047
+ "After=network.target",
83048
+ "",
83049
+ "[Service]",
83050
+ "Type=simple",
83051
+ `ExecStartPre=${NGINX_WRAPPER} -t`,
83052
+ `ExecStart=${NGINX_WRAPPER} -g 'daemon off;'`,
83053
+ `ExecReload=${NGINX_WRAPPER} -s reload`,
83054
+ "Restart=always",
83055
+ "RestartSec=3",
83056
+ "",
83057
+ "[Install]",
83058
+ "WantedBy=multi-user.target",
83059
+ "TS_CLOUD_NGINXUNIT_EOF",
83060
+ "systemctl daemon-reload",
83061
+ "systemctl enable ts-cloud-nginx",
83062
+ "ls /etc/nginx/sites-enabled/* >/dev/null 2>&1 && systemctl restart ts-cloud-nginx || true"
83063
+ ];
83064
+ }
81633
83065
 
81634
83066
  // src/drivers/shared/ufw.ts
81635
83067
  var UFW_BASE_PORTS = [80, 443];
@@ -81858,6 +83290,17 @@ function backupEntryFor(database) {
81858
83290
  }
81859
83291
  function buildBackupsConfigTs(database, backups) {
81860
83292
  const entry = database ? backupEntryFor(database) : null;
83293
+ const destinations = backups.bucket ? [
83294
+ " destinations: [",
83295
+ " {",
83296
+ " type: 's3',",
83297
+ ` bucket: '${backups.bucket}',`,
83298
+ " prefix: 'db-backups',",
83299
+ ...backups.endpoint ? [` endpoint: '${backups.endpoint}',`] : [],
83300
+ " optional: false,",
83301
+ " },",
83302
+ " ],"
83303
+ ] : [];
81861
83304
  return [
81862
83305
  "import type { BackupConfig } from 'ts-backups'",
81863
83306
  "",
@@ -81871,6 +83314,7 @@ function buildBackupsConfigTs(database, backups) {
81871
83314
  " databases: [",
81872
83315
  ...entry ? [entry] : [],
81873
83316
  " ],",
83317
+ ...destinations,
81874
83318
  "}",
81875
83319
  "",
81876
83320
  "export default config",
@@ -81884,21 +83328,21 @@ function buildBackupProvisionScript(options) {
81884
83328
  return [];
81885
83329
  const schedule = backups.schedule || "0 2 * * *";
81886
83330
  const configTs = buildBackupsConfigTs(database, backups);
81887
- const syncCmd = backups.bucket ? `aws s3 sync ${BACKUP_OUTPUT_DIR} s3://${backups.bucket}/db-backups${backups.endpoint ? ` --endpoint-url ${backups.endpoint}` : ""}` : `echo "ts-cloud: no backup bucket configured; keeping ${BACKUP_OUTPUT_DIR} local only"`;
81888
83331
  return [
81889
83332
  "export DEBIAN_FRONTEND=noninteractive",
81890
83333
  `mkdir -p /etc/ts-cloud ${BACKUP_OUTPUT_DIR}`,
81891
83334
  "command -v bun >/dev/null 2>&1 || (curl -fsSL https://bun.sh/install | bash && ln -sf /root/.bun/bin/bun /usr/local/bin/bun)",
83335
+ "bun add -g ts-backups || true",
81892
83336
  `cat > ${BACKUP_CONFIG_PATH} <<'TS_CLOUD_BACKUP_CFG_EOF'`,
81893
83337
  configTs.replace(/\n$/, ""),
81894
83338
  "TS_CLOUD_BACKUP_CFG_EOF",
81895
83339
  `cat > ${BACKUP_RUNNER_PATH} <<'TS_CLOUD_BACKUP_RUN_EOF'`,
81896
83340
  "#!/bin/bash",
81897
83341
  "set -uo pipefail",
83342
+ 'export PATH="/root/.bun/bin:/usr/local/bin:$PATH"',
81898
83343
  'notify() { [ -x /usr/local/bin/ts-cloud-notify ] && /usr/local/bin/ts-cloud-notify "$1" || true; }',
81899
83344
  "cd /etc/ts-cloud",
81900
- 'if ! bunx ts-backups backup --config /etc/ts-cloud/backups.config.ts; then notify "❌ ts-cloud backup failed (dump)"; exit 1; fi',
81901
- `if ! ${syncCmd}; then notify "❌ ts-cloud backup failed (upload)"; exit 1; fi`,
83345
+ 'if ! ts-backups backup --config /etc/ts-cloud/backups.config.ts; then notify "❌ ts-cloud backup failed"; exit 1; fi',
81902
83346
  "TS_CLOUD_BACKUP_RUN_EOF",
81903
83347
  `chmod +x ${BACKUP_RUNNER_PATH}`,
81904
83348
  `cat > ${BACKUP_CRON_PATH} <<'TS_CLOUD_BACKUP_CRON_EOF'`,
@@ -81912,13 +83356,22 @@ function buildBackupProvisionScript(options) {
81912
83356
  function buildComputeProvisionScripts(config6) {
81913
83357
  const compute = config6.infrastructure?.compute ?? {};
81914
83358
  const phpBox = compute.runtime === "php" || !!compute.php;
81915
- const phpProvision = phpBox ? buildPhpProvisionScript({
81916
- versions: compute.php?.versions,
81917
- default: compute.php?.default,
81918
- extensions: compute.php?.extensions,
81919
- installNginx: compute.webServer !== "rpx"
81920
- }) : undefined;
83359
+ const needsPantry = phpBox || !!compute.managedServices;
83360
+ const pantryBootstrap = needsPantry ? buildPantryBootstrapScript() : [];
83361
+ const useNginx = compute.webServer !== "rpx";
83362
+ const phpProvision = phpBox ? [
83363
+ ...pantryBootstrap,
83364
+ ...buildPhpProvisionScript({
83365
+ versions: compute.php?.versions,
83366
+ default: compute.php?.default,
83367
+ extensions: compute.php?.extensions,
83368
+ installNginx: useNginx
83369
+ }),
83370
+ ...useNginx ? buildNginxServiceScript() : []
83371
+ ] : undefined;
81921
83372
  const extras = [];
83373
+ if (!phpBox && needsPantry)
83374
+ extras.push(...pantryBootstrap);
81922
83375
  extras.push(...buildNotifierScript(config6.notifications));
81923
83376
  if (compute.managedServices) {
81924
83377
  extras.push(...buildServicesProvisionScript(compute.managedServices), ...buildDatabaseSetupScript(config6.infrastructure?.appDatabase, compute.managedServices));
@@ -82165,7 +83618,7 @@ class AwsDriver {
82165
83618
  GroupId: groupId,
82166
83619
  IpPermissions: rules.map((r) => ({ IpProtocol: r.protocol, FromPort: r.port, ToPort: r.port, IpRanges: [{ CidrIp: r.cidr }] }))
82167
83620
  }).catch((e) => {
82168
- if (!/InvalidPermission\.Duplicate/.test(e?.message || ""))
83621
+ if (!/InvalidPermission\.Duplicate/.test(e instanceof Error ? e.message : ""))
82169
83622
  throw e;
82170
83623
  });
82171
83624
  const userData = encodeUserData(buildAwsUserData(config6));
@@ -82245,7 +83698,7 @@ class AwsDriver {
82245
83698
  sshUser: "ec2-user"
82246
83699
  };
82247
83700
  } catch (err) {
82248
- if (!/does not exist|ValidationError/i.test(err?.message || ""))
83701
+ if (!/does not exist|ValidationError/i.test(err instanceof Error ? err.message : ""))
82249
83702
  throw err;
82250
83703
  const targets = await this.findComputeTargets({
82251
83704
  slug: options.config.project.slug,
@@ -82275,7 +83728,7 @@ class AwsDriver {
82275
83728
  await s32.putObject({
82276
83729
  bucket,
82277
83730
  key: options.remoteKey,
82278
- body: readFileSync9(options.localPath),
83731
+ body: readFileSync12(options.localPath),
82279
83732
  contentType: "application/gzip"
82280
83733
  });
82281
83734
  return { artifactRef: `s3://${bucket}/${options.remoteKey}` };
@@ -82355,7 +83808,7 @@ class AwsDriver {
82355
83808
  const terminalStatuses = new Set(["Success", "Failed", "Cancelled", "TimedOut"]);
82356
83809
  let lastInvocations = [];
82357
83810
  while (Date.now() - startTime < maxWait) {
82358
- await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
83811
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
82359
83812
  try {
82360
83813
  const invocations = await ssm2.listCommandInvocations({ CommandId: commandId, Details: true });
82361
83814
  lastInvocations = invocations;
@@ -82381,10 +83834,10 @@ class AwsDriver {
82381
83834
  }
82382
83835
 
82383
83836
  // src/drivers/hetzner/driver.ts
82384
- import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
83837
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
82385
83838
  import { homedir as homedir7 } from "node:os";
82386
- import { join as join13 } from "node:path";
82387
- import { execSync } from "node:child_process";
83839
+ import { join as join15 } from "node:path";
83840
+ import { execSync as execSync3 } from "node:child_process";
82388
83841
 
82389
83842
  // src/drivers/hetzner/client.ts
82390
83843
  var DEFAULT_API_URL = "https://api.hetzner.cloud/v1";
@@ -82483,7 +83936,15 @@ class HetznerClient {
82483
83936
  services: options.services.map((s) => ({
82484
83937
  protocol: s.protocol ?? "tcp",
82485
83938
  listen_port: s.listenPort,
82486
- destination_port: s.destinationPort
83939
+ destination_port: s.destinationPort,
83940
+ health_check: {
83941
+ protocol: "http",
83942
+ port: 80,
83943
+ interval: 15,
83944
+ timeout: 10,
83945
+ retries: 3,
83946
+ http: { path: "/", status_codes: ["2??", "3??"] }
83947
+ }
82487
83948
  }))
82488
83949
  });
82489
83950
  return data.load_balancer;
@@ -82546,7 +84007,7 @@ class HetznerClient {
82546
84007
  if (data.action.status === "error") {
82547
84008
  throw new Error(data.action.error?.message || "Hetzner action failed");
82548
84009
  }
82549
- await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
84010
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
82550
84011
  }
82551
84012
  throw new Error(`Timed out waiting for Hetzner action ${actionId}`);
82552
84013
  }
@@ -82558,7 +84019,7 @@ class HetznerClient {
82558
84019
  const server = await this.getServer(serverId);
82559
84020
  if (server.status === "running")
82560
84021
  return server;
82561
- await new Promise((resolve13) => setTimeout(resolve13, pollInterval));
84022
+ await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
82562
84023
  }
82563
84024
  throw new Error(`Timed out waiting for server ${serverId} to reach running state`);
82564
84025
  }
@@ -82805,10 +84266,10 @@ function matchesTsCloudLabels(labels, slug, environment, role = "app") {
82805
84266
 
82806
84267
  // src/drivers/hetzner/state.ts
82807
84268
  import { mkdir as mkdir4, readFile, writeFile as writeFile4 } from "node:fs/promises";
82808
- import { join as join12 } from "node:path";
84269
+ import { join as join14 } from "node:path";
82809
84270
  var STATE_DIR = ".ts-cloud/state";
82810
84271
  function driverStatePath(stackName) {
82811
- return join12(process.cwd(), STATE_DIR, `${stackName}.json`);
84272
+ return join14(process.cwd(), STATE_DIR, `${stackName}.json`);
82812
84273
  }
82813
84274
  async function readDriverState(stackName) {
82814
84275
  try {
@@ -82820,7 +84281,7 @@ async function readDriverState(stackName) {
82820
84281
  }
82821
84282
  async function writeDriverState(stackName, state) {
82822
84283
  const path = driverStatePath(stackName);
82823
- await mkdir4(join12(process.cwd(), STATE_DIR), { recursive: true });
84284
+ await mkdir4(join14(process.cwd(), STATE_DIR), { recursive: true });
82824
84285
  await writeFile4(path, `${JSON.stringify(state, null, 2)}
82825
84286
  `, "utf8");
82826
84287
  }
@@ -82828,7 +84289,7 @@ async function writeDriverState(stackName, state) {
82828
84289
  // src/drivers/hetzner/driver.ts
82829
84290
  var SSH_MAX_BUFFER = 1024 * 1024 * 256;
82830
84291
  function expandHome(path) {
82831
- return path.startsWith("~/") ? join13(homedir7(), path.slice(2)) : path;
84292
+ return path.startsWith("~/") ? join15(homedir7(), path.slice(2)) : path;
82832
84293
  }
82833
84294
 
82834
84295
  class HetznerDriver {
@@ -82960,12 +84421,12 @@ class HetznerDriver {
82960
84421
  const baked = compute.bakedImage === true;
82961
84422
  const existingState = await readDriverState(stackName);
82962
84423
  if (existingState?.loadBalancerId) {
82963
- const lbs2 = await this.client.listLoadBalancers().catch(() => []);
82964
- const lb2 = lbs2.find((l) => l.id === existingState.loadBalancerId);
82965
- if (lb2) {
84424
+ const lbs = await this.client.listLoadBalancers().catch(() => []);
84425
+ const lb = lbs.find((l) => l.id === existingState.loadBalancerId);
84426
+ if (lb) {
82966
84427
  return {
82967
- appPublicIp: lb2.public_net?.ipv4?.ip,
82968
- loadBalancerIp: lb2.public_net?.ipv4?.ip,
84428
+ appPublicIp: lb.public_net?.ipv4?.ip,
84429
+ loadBalancerIp: lb.public_net?.ipv4?.ip,
82969
84430
  servicesPrivateIp: existingState.servicesPrivateIp,
82970
84431
  deployStoragePath: "/var/ts-cloud/staging",
82971
84432
  sshUser: this.sshUser
@@ -82992,20 +84453,27 @@ class HetznerDriver {
82992
84453
  ...buildAuthorizedKeysScript(compute.sshKeys)
82993
84454
  ];
82994
84455
  const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", servicesProvision, baked }));
82995
- const { server: svcCreate, action: svcAction } = await this.client.createServer({
82996
- name: `${slug}-${environment}-services`,
82997
- serverType: resolveHetznerServerType(typeof compute.servicesServer === "object" ? compute.servicesServer.size : compute.size),
82998
- image,
82999
- location,
83000
- userData: servicesUserData,
83001
- labels: tsCloudLabels(slug, environment, "services"),
83002
- sshKeys: sshKeyId ? [sshKeyId] : undefined,
83003
- firewalls: [{ firewall: svcFw.id }],
83004
- networks: [network.id]
83005
- });
83006
- await this.client.waitForAction(svcAction.id);
83007
- const svcRunning = await this.client.waitForServerRunning(svcCreate.id);
83008
- const servicesPrivateIp = svcRunning.private_net?.[0]?.ip;
84456
+ const all = await this.client.listServers().catch(() => []);
84457
+ const newServerIds = [];
84458
+ let svcServer = all.find((s) => matchesTsCloudLabels(s.labels, slug, environment, "services"));
84459
+ if (!svcServer) {
84460
+ const { server, action } = await this.client.createServer({
84461
+ name: `${slug}-${environment}-services`,
84462
+ serverType: resolveHetznerServerType(typeof compute.servicesServer === "object" ? compute.servicesServer.size : compute.size),
84463
+ image,
84464
+ location,
84465
+ userData: servicesUserData,
84466
+ labels: tsCloudLabels(slug, environment, "services"),
84467
+ sshKeys: sshKeyId ? [sshKeyId] : undefined,
84468
+ firewalls: [{ firewall: svcFw.id }],
84469
+ networks: [network.id]
84470
+ });
84471
+ await this.client.waitForAction(action.id);
84472
+ svcServer = await this.client.waitForServerRunning(server.id);
84473
+ newServerIds.push(server.id);
84474
+ }
84475
+ const servicesServerId = svcServer.id;
84476
+ const servicesPrivateIp = svcServer.private_net?.[0]?.ip ?? (await this.client.getServer(servicesServerId)).private_net?.[0]?.ip;
83009
84477
  const appProvision = [
83010
84478
  ...buildAutoUpdatesScript(true),
83011
84479
  ...buildMonitoringScript(true),
@@ -83019,8 +84487,15 @@ class HetznerDriver {
83019
84487
  installNginx: compute.webServer !== "rpx"
83020
84488
  });
83021
84489
  const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", phpProvision: appPhp, servicesProvision: appProvision, baked }));
83022
- const appServerIds = [];
83023
- for (let i = 0;i < topology.appServers; i++) {
84490
+ const existingApp = all.filter((s) => matchesTsCloudLabels(s.labels, slug, environment, "app"));
84491
+ const appServerIds = existingApp.map((s) => s.id);
84492
+ if (existingApp.length > topology.appServers) {
84493
+ for (const extra of existingApp.slice(topology.appServers)) {
84494
+ await this.client.deleteServer(extra.id).catch(() => {});
84495
+ appServerIds.splice(appServerIds.indexOf(extra.id), 1);
84496
+ }
84497
+ }
84498
+ for (let i = existingApp.length;i < topology.appServers; i++) {
83024
84499
  const { server, action } = await this.client.createServer({
83025
84500
  name: `${slug}-${environment}-app-${i + 1}`,
83026
84501
  serverType,
@@ -83034,10 +84509,10 @@ class HetznerDriver {
83034
84509
  });
83035
84510
  await this.client.waitForAction(action.id);
83036
84511
  appServerIds.push(server.id);
84512
+ newServerIds.push(server.id);
83037
84513
  }
83038
- if (this.waitForBoot) {
83039
- const waitIds = [svcCreate.id, ...appServerIds];
83040
- await Promise.all(waitIds.map(async (id) => {
84514
+ if (this.waitForBoot && newServerIds.length > 0) {
84515
+ await Promise.all(newServerIds.map(async (id) => {
83041
84516
  const running = await this.client.waitForServerRunning(id);
83042
84517
  const ip = running.public_net.ipv4?.ip;
83043
84518
  if (ip) {
@@ -83047,33 +84522,42 @@ class HetznerDriver {
83047
84522
  }));
83048
84523
  }
83049
84524
  const lbName = `${slug}-${environment}-lb`;
83050
- const lbs = await this.client.listLoadBalancers().catch(() => []);
83051
- const lb = lbs.find((l) => l.name === lbName) ?? await this.client.createLoadBalancer({
83052
- name: lbName,
83053
- location,
83054
- network: network.id,
83055
- labels: tsCloudLabels(slug, environment, "lb"),
83056
- labelSelector: `ts-cloud/project=${slug},ts-cloud/environment=${environment},ts-cloud/role=app`,
83057
- services: [
83058
- { listenPort: 80, destinationPort: 80 },
83059
- { listenPort: 443, destinationPort: 443 }
83060
- ]
83061
- });
84525
+ let lbIp;
84526
+ let lbId;
84527
+ if (topology.loadBalancer) {
84528
+ const lbs = await this.client.listLoadBalancers().catch(() => []);
84529
+ const lb = lbs.find((l) => l.name === lbName) ?? await this.client.createLoadBalancer({
84530
+ name: lbName,
84531
+ location,
84532
+ network: network.id,
84533
+ labels: tsCloudLabels(slug, environment, "lb"),
84534
+ labelSelector: `ts-cloud/project=${slug},ts-cloud/environment=${environment},ts-cloud/role=app`,
84535
+ services: [
84536
+ { listenPort: 80, destinationPort: 80 },
84537
+ { listenPort: 443, destinationPort: 443 }
84538
+ ]
84539
+ });
84540
+ lbId = lb.id;
84541
+ lbIp = lb.public_net?.ipv4?.ip;
84542
+ }
84543
+ const appPublicIp = lbIp ?? (await this.client.getServer(appServerIds[0]).catch(() => {
84544
+ return;
84545
+ }))?.public_net.ipv4?.ip;
83062
84546
  const state = {
83063
84547
  provider: "hetzner",
83064
84548
  stackName,
83065
84549
  networkId: network.id,
83066
- loadBalancerId: lb.id,
83067
- servicesServerId: svcCreate.id,
84550
+ loadBalancerId: lbId,
84551
+ servicesServerId,
83068
84552
  servicesPrivateIp,
83069
- publicIp: lb.public_net?.ipv4?.ip,
84553
+ publicIp: appPublicIp,
83070
84554
  deployStoragePath: "/var/ts-cloud/staging",
83071
84555
  sshUser: this.sshUser
83072
84556
  };
83073
84557
  await writeDriverState(stackName, state);
83074
84558
  return {
83075
- appPublicIp: lb.public_net?.ipv4?.ip,
83076
- loadBalancerIp: lb.public_net?.ipv4?.ip,
84559
+ appPublicIp,
84560
+ loadBalancerIp: lbIp,
83077
84561
  servicesPrivateIp,
83078
84562
  deployStoragePath: "/var/ts-cloud/staging",
83079
84563
  sshUser: this.sshUser
@@ -83089,8 +84573,10 @@ class HetznerDriver {
83089
84573
  const lbs = await this.client.listLoadBalancers().catch(() => []);
83090
84574
  const lb = lbs.find((l) => l.name === lbName);
83091
84575
  if (lb) {
83092
- await this.client.deleteLoadBalancer(lb.id).catch(() => {});
83093
- destroyed.push(`load balancer ${lbName}`);
84576
+ try {
84577
+ await this.client.deleteLoadBalancer(lb.id);
84578
+ destroyed.push(`load balancer ${lbName}`);
84579
+ } catch {}
83094
84580
  }
83095
84581
  const allServers = await this.client.listServers().catch(() => []);
83096
84582
  const serverIds = new Set;
@@ -83103,8 +84589,10 @@ class HetznerDriver {
83103
84589
  if (state?.servicesServerId)
83104
84590
  serverIds.add(state.servicesServerId);
83105
84591
  for (const id of serverIds) {
83106
- await this.client.deleteServer(id).catch(() => {});
83107
- destroyed.push(`server ${id}`);
84592
+ try {
84593
+ await this.client.deleteServer(id);
84594
+ destroyed.push(`server ${id}`);
84595
+ } catch {}
83108
84596
  }
83109
84597
  const firewalls = await this.client.listFirewalls().catch(() => []);
83110
84598
  for (const name of [`${slug}-${environment}-app-fw`, `${slug}-${environment}-services-fw`]) {
@@ -83232,7 +84720,7 @@ class HetznerDriver {
83232
84720
  if (!existsSync17(this.sshPublicKeyPath)) {
83233
84721
  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.`);
83234
84722
  }
83235
- const publicKey = readFileSync10(this.sshPublicKeyPath, "utf8").trim();
84723
+ const publicKey = readFileSync13(this.sshPublicKeyPath, "utf8").trim();
83236
84724
  const normalized = normalizeSshPublicKey(publicKey);
83237
84725
  const existing = await this.client.listSshKeys();
83238
84726
  const match = existing.find((key) => normalizeSshPublicKey(key.public_key) === normalized);
@@ -83275,7 +84763,7 @@ class HetznerDriver {
83275
84763
  return [...ports].filter((port) => ![80, 443].includes(port));
83276
84764
  }
83277
84765
  async sleep(ms) {
83278
- await new Promise((resolve13) => setTimeout(resolve13, ms));
84766
+ await new Promise((resolve14) => setTimeout(resolve14, ms));
83279
84767
  }
83280
84768
  async waitForSshReady(host) {
83281
84769
  const { sshIntervalMs, sshTimeoutMs } = this.bootWait;
@@ -83283,7 +84771,7 @@ class HetznerDriver {
83283
84771
  let lastErr;
83284
84772
  while (Date.now() - start < sshTimeoutMs) {
83285
84773
  try {
83286
- execSync(`ssh ${this.sshBaseArgs(host, ["-o", "ConnectTimeout=5"]).map((a) => `"${a.replace(/"/g, "\\\"")}"`).join(" ")} true`, {
84774
+ execSync3(`ssh ${this.sshBaseArgs(host, ["-o", "ConnectTimeout=5"]).map((a) => `"${a.replace(/"/g, "\\\"")}"`).join(" ")} true`, {
83287
84775
  stdio: "pipe",
83288
84776
  maxBuffer: SSH_MAX_BUFFER
83289
84777
  });
@@ -83343,7 +84831,7 @@ ${out}`);
83343
84831
  ];
83344
84832
  }
83345
84833
  scpToHost(host, localPath, remotePath) {
83346
- execSync([
84834
+ execSync3([
83347
84835
  "scp",
83348
84836
  "-i",
83349
84837
  this.sshPrivateKeyPath,
@@ -83356,7 +84844,7 @@ ${out}`);
83356
84844
  }
83357
84845
  sshExec(host, script) {
83358
84846
  const escaped = script.replace(/'/g, `'\\''`);
83359
- return execSync(`ssh ${this.sshBaseArgs(host).map((a) => `"${a.replace(/"/g, "\\\"")}"`).join(" ")} '${escaped}'`, {
84847
+ return execSync3(`ssh ${this.sshBaseArgs(host).map((a) => `"${a.replace(/"/g, "\\\"")}"`).join(" ")} '${escaped}'`, {
83360
84848
  encoding: "utf8",
83361
84849
  stdio: ["pipe", "pipe", "pipe"],
83362
84850
  maxBuffer: SSH_MAX_BUFFER
@@ -83678,8 +85166,7 @@ function buildLaravelDeployScript(options) {
83678
85166
  if (!site.repository?.url)
83679
85167
  throw new Error(`Site '${siteName}' is a PHP/git site but has no repository.url to clone`);
83680
85168
  const base = options.appBase ?? `/var/www/${siteName}`;
83681
- const phpVersion = site.phpVersion ?? options.defaultPhpVersion ?? "8.3";
83682
- const phpBin = `php${phpVersion}`;
85169
+ const phpBin = "php";
83683
85170
  const paths = releasePaths(base, releaseId);
83684
85171
  const sharedPaths = site.sharedPaths ?? DEFAULT_SHARED_PATHS;
83685
85172
  const keepReleases = site.keepReleases ?? DEFAULT_KEEP_RELEASES;
@@ -83688,7 +85175,8 @@ function buildLaravelDeployScript(options) {
83688
85175
  "set -euo pipefail",
83689
85176
  'export HOME="${HOME:-/root}"',
83690
85177
  'export COMPOSER_HOME="${COMPOSER_HOME:-/root/.composer}"',
83691
- "export COMPOSER_ALLOW_SUPERUSER=1"
85178
+ "export COMPOSER_ALLOW_SUPERUSER=1",
85179
+ pantryEnvActivation()
83692
85180
  ];
83693
85181
  out.push(...buildEnsureReleaseLayout(paths, sharedPaths));
83694
85182
  if (site.env && Object.keys(site.env).length > 0)
@@ -83703,7 +85191,7 @@ function buildLaravelDeployScript(options) {
83703
85191
  } else if (line === MACRO_ACTIVATE_RELEASE) {
83704
85192
  out.push(...buildActivateRelease(paths));
83705
85193
  out.push(...buildPruneReleases(paths, keepReleases));
83706
- out.push(`systemctl reload php${phpVersion}-fpm || true`);
85194
+ out.push(`(cd ${PANTRY_PROJECT_DIR} && pantry restart php-fpm) 2>/dev/null || true`);
83707
85195
  } else if (line === MACRO_RESTART_QUEUES) {
83708
85196
  out.push(`${phpBin} artisan queue:restart || true`);
83709
85197
  } else {
@@ -83714,6 +85202,10 @@ function buildLaravelDeployScript(options) {
83714
85202
  }
83715
85203
 
83716
85204
  // src/drivers/shared/laravel-services.ts
85205
+ var PANTRY_ENV_EVAL = `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)"`;
85206
+ function pantryExec(cmd) {
85207
+ return `/bin/sh -lc '${PANTRY_ENV_EVAL}; exec ${cmd}'`;
85208
+ }
83717
85209
  function reEscape(value) {
83718
85210
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83719
85211
  }
@@ -83777,8 +85269,7 @@ function schedulerCronPath(slug, siteName) {
83777
85269
  }
83778
85270
  function buildSiteServicesScript(options) {
83779
85271
  const { slug, siteName, site } = options;
83780
- const phpVersion = site.phpVersion ?? options.phpVersion ?? "8.3";
83781
- const phpBin = `php${phpVersion}`;
85272
+ const phpBin = "php";
83782
85273
  const base = options.appBase ?? `/var/www/${siteName}`;
83783
85274
  const current = `${base}/current`;
83784
85275
  const artisan = `${current}/artisan`;
@@ -83793,7 +85284,7 @@ function buildSiteServicesScript(options) {
83793
85284
  out.push(...writeUnitScript(name, systemdUnit({
83794
85285
  description: `${siteName} queue worker ${qIndex}.${p} (managed by ts-cloud)`,
83795
85286
  workingDir: current,
83796
- execStart: queueExecStart(worker, phpBin, artisan),
85287
+ execStart: pantryExec(queueExecStart(worker, phpBin, artisan)),
83797
85288
  stopWaitSecs: worker.stopWaitSecs ?? 90
83798
85289
  })));
83799
85290
  }
@@ -83807,7 +85298,7 @@ function buildSiteServicesScript(options) {
83807
85298
  out.push(...writeUnitScript(name, systemdUnit({
83808
85299
  description: `${siteName} daemon ${daemon.name || daemon.command} (managed by ts-cloud)`,
83809
85300
  workingDir: daemon.directory || current,
83810
- execStart: daemon.command,
85301
+ execStart: pantryExec(daemon.command),
83811
85302
  restart: daemon.restart,
83812
85303
  user: daemon.user
83813
85304
  })));
@@ -83820,7 +85311,7 @@ function buildSiteServicesScript(options) {
83820
85311
  }
83821
85312
  const cronPath = schedulerCronPath(slug, siteName);
83822
85313
  if (site.scheduler) {
83823
- const cron = `* * * * * root cd ${current} && ${phpBin} artisan schedule:run >> /dev/null 2>&1
85314
+ const cron = `* * * * * root cd ${current} && ${PANTRY_ENV_EVAL} && ${phpBin} artisan schedule:run >> /dev/null 2>&1
83824
85315
  `;
83825
85316
  out.push(`cat > ${cronPath} <<'TS_CLOUD_CRON_EOF'`, cron.replace(/\n$/, ""), "TS_CLOUD_CRON_EOF", `chmod 644 ${cronPath}`);
83826
85317
  } else {
@@ -83832,111 +85323,6 @@ function siteHasServices(site) {
83832
85323
  return !!(site.queues?.length || site.daemons?.length || site.scheduler);
83833
85324
  }
83834
85325
 
83835
- // src/drivers/shared/nginx-vhost.ts
83836
- function htpasswdPath(siteName) {
83837
- return `/etc/nginx/.htpasswd-${siteName}`;
83838
- }
83839
- var PHP_TYPES = new Set(["laravel", "php", "statamic", "wordpress"]);
83840
- function isPhpSiteType(type) {
83841
- return PHP_TYPES.has(type);
83842
- }
83843
- function defaultWebDirectory(type) {
83844
- return type === "laravel" || type === "statamic" || type === "wordpress" ? "public" : "";
83845
- }
83846
- function resolveRoot(appDir, webDirectory) {
83847
- const base = appDir.replace(/\/+$/, "");
83848
- const sub = webDirectory.replace(/^\/+|\/+$/g, "");
83849
- return sub ? `${base}/${sub}` : base;
83850
- }
83851
- function vhostBody(options) {
83852
- const type = options.type ?? "laravel";
83853
- const webDirectory = options.webDirectory ?? defaultWebDirectory(type);
83854
- const root = resolveRoot(options.appDir, webDirectory);
83855
- const isPhp = isPhpSiteType(type);
83856
- const phpVersion = options.phpVersion ?? "8.3";
83857
- const lines = [
83858
- ` root ${root};`,
83859
- "",
83860
- ' add_header X-Frame-Options "SAMEORIGIN";',
83861
- ' add_header X-Content-Type-Options "nosniff";',
83862
- "",
83863
- ` index ${isPhp ? "index.php index.html" : "index.html index.htm"};`,
83864
- "",
83865
- " charset utf-8;",
83866
- ""
83867
- ];
83868
- if (options.auth) {
83869
- lines.push(` auth_basic "${options.auth.realm || "Restricted"}";`, ` auth_basic_user_file ${htpasswdPath(options.siteName)};`, "");
83870
- }
83871
- for (const [from, to] of Object.entries(options.redirects || {})) {
83872
- lines.push(` location = ${from} { return 301 ${to}; }`);
83873
- }
83874
- if (Object.keys(options.redirects || {}).length > 0)
83875
- lines.push("");
83876
- if (isPhp) {
83877
- lines.push(" location / {", " try_files $uri $uri/ /index.php?$query_string;", " }", "", " location = /favicon.ico { access_log off; log_not_found off; }", " location = /robots.txt { access_log off; log_not_found off; }", "", " error_page 404 /index.php;", "", " location ~ \\.php$ {", ` fastcgi_pass unix:${phpFpmSocketPath(phpVersion)};`, " fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;", " include fastcgi_params;", " }", "", " location ~ /\\.(?!well-known).* {", " deny all;", " }");
83878
- } else if (type === "spa") {
83879
- lines.push(" location / {", " try_files $uri $uri/ /index.html;", " }");
83880
- } else {
83881
- lines.push(" location / {", " try_files $uri $uri/ =404;", " }");
83882
- }
83883
- return lines;
83884
- }
83885
- function buildNginxVhost(options) {
83886
- const serverNames = [options.domain, ...options.aliases || []].filter(Boolean).join(" ");
83887
- const body = vhostBody(options);
83888
- if (options.ssl) {
83889
- const redirect = [
83890
- "server {",
83891
- " listen 80;",
83892
- " listen [::]:80;",
83893
- ` server_name ${serverNames};`,
83894
- " return 301 https://$host$request_uri;",
83895
- "}"
83896
- ];
83897
- const tls3 = [
83898
- "server {",
83899
- " listen 443 ssl;",
83900
- " listen [::]:443 ssl;",
83901
- ` server_name ${serverNames};`,
83902
- ` ssl_certificate ${options.ssl.certPath};`,
83903
- ` ssl_certificate_key ${options.ssl.keyPath};`,
83904
- "",
83905
- ...body,
83906
- "}"
83907
- ];
83908
- return `${[...redirect, "", ...tls3].join(`
83909
- `)}
83910
- `;
83911
- }
83912
- const lines = [
83913
- "server {",
83914
- " listen 80;",
83915
- " listen [::]:80;",
83916
- ` server_name ${serverNames};`,
83917
- ...body,
83918
- "}"
83919
- ];
83920
- return `${lines.join(`
83921
- `)}
83922
- `;
83923
- }
83924
- function buildNginxVhostScript(options) {
83925
- const available = `/etc/nginx/sites-available/${options.siteName}`;
83926
- const enabled2 = `/etc/nginx/sites-enabled/${options.siteName}`;
83927
- const vhost = buildNginxVhost(options);
83928
- const out = [];
83929
- if (options.auth) {
83930
- const file = htpasswdPath(options.siteName);
83931
- const sq2 = (v) => v.split("'").join("'\\''");
83932
- const pw = sq2(options.auth.password);
83933
- const user = sq2(options.auth.username);
83934
- 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`);
83935
- }
83936
- 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 -t", "systemctl reload nginx");
83937
- return out;
83938
- }
83939
-
83940
85326
  // src/drivers/shared/compute-deploy.ts
83941
85327
  var noopLogger = {
83942
85328
  info: () => {},
@@ -84210,6 +85596,7 @@ export {
84210
85596
  stackDependencyManager,
84211
85597
  signRequestAsync,
84212
85598
  signRequest,
85599
+ sha256,
84213
85600
  setupDns01Challenge,
84214
85601
  serviceMeshManager,
84215
85602
  sequence,
@@ -84220,8 +85607,11 @@ export {
84220
85607
  secretsManager,
84221
85608
  searchCommands,
84222
85609
  sanitizeName,
85610
+ runPhpBuildHooks,
85611
+ runBuildHooks,
84223
85612
  route53RoutingManager,
84224
85613
  route53ResolverManager,
85614
+ responseToResult,
84225
85615
  resourceManagementManager,
84226
85616
  resolveStorageBucketName,
84227
85617
  resolveSiteStackName,
@@ -84229,7 +85619,11 @@ export {
84229
85619
  resolveSiteKind,
84230
85620
  resolveSiteDeployTarget,
84231
85621
  resolveSiteBucketName,
85622
+ resolveServerlessAssetBucketName,
85623
+ resolveServerlessArtifactBucketName,
85624
+ resolveServerlessAppStackName,
84232
85625
  resolveRegion,
85626
+ resolveQueueNames,
84233
85627
  resolveProjectStackName,
84234
85628
  resolveObjectStorage,
84235
85629
  resolveHetznerApiToken,
@@ -84237,6 +85631,7 @@ export {
84237
85631
  resolveDeployBucketName,
84238
85632
  resolveCredentials,
84239
85633
  resolveCloudProvider,
85634
+ resolveApp,
84240
85635
  requiresReplacement,
84241
85636
  replicaManager,
84242
85637
  remapKey,
@@ -84248,12 +85643,17 @@ export {
84248
85643
  processInChunks,
84249
85644
  previewNotifications,
84250
85645
  previewManager,
85646
+ phpRuntimeLayerAssets,
85647
+ phpLayerPackages,
85648
+ phpLayerBuildStage,
84251
85649
  performanceManager,
84252
85650
  parseXMLResponse,
84253
85651
  parseJSONResponse,
84254
85652
  parallelWithRetry,
84255
85653
  parallelMap,
84256
85654
  parallel,
85655
+ packageServerlessApp,
85656
+ packagePhpApp,
84257
85657
  organizationManager,
84258
85658
  networkSecurityManager,
84259
85659
  needsRenewal,
@@ -84268,6 +85668,7 @@ export {
84268
85668
  makeAWSRequest,
84269
85669
  logsManager,
84270
85670
  loadCloudConfig,
85671
+ laravelServerlessEnvDefaults,
84271
85672
  lambdaVersionsManager,
84272
85673
  lambdaVPCManager,
84273
85674
  lambdaLayersManager,
@@ -84323,6 +85724,8 @@ export {
84323
85724
  generateResourceName,
84324
85725
  generatePreviewWorkflow,
84325
85726
  generatePreviewPipeline,
85727
+ generatePhpLayerDockerfile,
85728
+ generatePhpFpmConfig,
84326
85729
  generateParallelConfig,
84327
85730
  generatePRPreviewWorkflow,
84328
85731
  generateMultiEnvWorkflow,
@@ -84340,7 +85743,9 @@ export {
84340
85743
  generateCrossAccountRoleCF,
84341
85744
  generateCostReportWorkflow,
84342
85745
  generateCleanupWorkflow,
85746
+ generateBootstrap,
84343
85747
  generateApprovalConfig,
85748
+ generateAppImageDockerfile,
84344
85749
  fromWebIdentity,
84345
85750
  fromSharedCredentials,
84346
85751
  fromEnvironment,
@@ -84364,6 +85769,7 @@ export {
84364
85769
  findChangedFiles,
84365
85770
  fifoQueueManager,
84366
85771
  extendPreset,
85772
+ eventToRequest,
84367
85773
  emailTemplateManager,
84368
85774
  emailAnalyticsManager,
84369
85775
  drManager,
@@ -84386,12 +85792,16 @@ export {
84386
85792
  defaultConfig4 as defaultConfig,
84387
85793
  databaseUserManager,
84388
85794
  crossRegionReferenceManager,
85795
+ createZip,
84389
85796
  createWordPressPreset,
84390
85797
  createTraditionalWebAppPreset,
84391
85798
  createStaticSitePreset,
85799
+ createServerlessNodePreset,
85800
+ createServerlessLaravelPreset,
84392
85801
  createS3Client,
84393
85802
  createRoute53Validator,
84394
85803
  createRealtimeAppPreset,
85804
+ createQueueHandler,
84395
85805
  createPresignedUrlAsync,
84396
85806
  createPresignedUrl,
84397
85807
  createPreset,
@@ -84404,6 +85814,8 @@ export {
84404
85814
  createMLApiPreset,
84405
85815
  createLaravelPreset,
84406
85816
  createJamstackPreset,
85817
+ createHttpHandler,
85818
+ createHandlers,
84407
85819
  createGoDaddyValidator,
84408
85820
  createFullStackAppPreset,
84409
85821
  createError,
@@ -84412,10 +85824,13 @@ export {
84412
85824
  createDashboardSite,
84413
85825
  createCredentialProvider,
84414
85826
  createCloudDriver,
85827
+ createCliHandler,
84415
85828
  createApiBackendPreset,
84416
85829
  containerRegistryManager,
84417
85830
  config5 as config,
85831
+ composeServerlessAppTemplate,
84418
85832
  composePresets,
85833
+ collectPhpAppEntries,
84419
85834
  cloudTrailManager,
84420
85835
  cloudDrivers,
84421
85836
  cloud_config_schema_default as cloudConfigSchema,
@@ -84429,6 +85844,7 @@ export {
84429
85844
  canaryManager,
84430
85845
  buildStaticSiteDeployScript,
84431
85846
  buildSiteDeployScript,
85847
+ buildPhpRuntimeLayerZip,
84432
85848
  buildOptimizationManager,
84433
85849
  buildCloudFormationTemplate,
84434
85850
  bounceComplaintHandler,
@@ -84438,6 +85854,7 @@ export {
84438
85854
  backupManager,
84439
85855
  awsConfigManager,
84440
85856
  autocomplete,
85857
+ artifactKey,
84441
85858
  analyzeStackDiff,
84442
85859
  abTestManager,
84443
85860
  XRayManager,
@@ -84517,6 +85934,8 @@ export {
84517
85934
  Permissions,
84518
85935
  PerformanceManager,
84519
85936
  ParameterStore,
85937
+ PHP_LAYER_EXTENSIONS,
85938
+ PHP_DEFAULT_EXCLUDES,
84520
85939
  OrganizationManager,
84521
85940
  OpenSearchClient,
84522
85941
  NetworkSecurityManager,
@@ -84539,6 +85958,7 @@ export {
84539
85958
  LambdaDLQManager,
84540
85959
  LambdaConcurrencyManager,
84541
85960
  LambdaClient,
85961
+ LARAVEL_SERVERLESS_BUILD_STEPS,
84542
85962
  KendraClient,
84543
85963
  JobLoader,
84544
85964
  InfrastructureGenerator,