@ts-cloud/core 0.2.26 → 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
@@ -1670,6 +1670,15 @@ function resolveStorageBucketName(slug, environment, bucketKey, explicitBucket)
1670
1670
  function resolveDeployBucketName(slug, environment) {
1671
1671
  return `${slug}-${environment}-deploy`;
1672
1672
  }
1673
+ function resolveServerlessAppStackName(config, environment) {
1674
+ return `${config.project.slug}-${environment}-app`;
1675
+ }
1676
+ function resolveServerlessArtifactBucketName(slug, environment) {
1677
+ return `${slug}-${environment}-deployments`;
1678
+ }
1679
+ function resolveServerlessAssetBucketName(slug, environment) {
1680
+ return `${slug}-${environment}-assets`;
1681
+ }
1673
1682
  // src/dependency-graph.ts
1674
1683
  class DependencyGraph {
1675
1684
  nodes = new Map;
@@ -2567,7 +2576,7 @@ class Storage {
2567
2576
  for (const docsPath of docsPaths) {
2568
2577
  const fullPath = join(projectRoot, docsPath);
2569
2578
  if (existsSync(fullPath)) {
2570
- const distPaths = ["dist", "build", ".vitepress/dist", "_site", "out", "public"];
2579
+ const distPaths = ["dist", "build", ".bunpress/dist", "_site", "out", "public"];
2571
2580
  for (const distPath of distPaths) {
2572
2581
  const fullDistPath = join(fullPath, distPath);
2573
2582
  if (existsSync(fullDistPath)) {
@@ -3408,7 +3417,7 @@ exports.handler = async (event) => {
3408
3417
  }
3409
3418
 
3410
3419
  // Try to determine if this is a directory or a file
3411
- // First, try appending .html (for VitePress clean URLs)
3420
+ // First, try appending .html (for BunPress clean URLs)
3412
3421
  // If the file doesn't exist, CloudFront will try the directory with index.html
3413
3422
 
3414
3423
  // Check if the URI looks like a file path without extension
@@ -6041,7 +6050,8 @@ echo "Database tools installed!"
6041
6050
  java21: "java21",
6042
6051
  java17: "java17",
6043
6052
  go: "provided.al2023",
6044
- rust: "provided.al2023"
6053
+ rust: "provided.al2023",
6054
+ bun: "provided.al2023"
6045
6055
  },
6046
6056
  presets: {
6047
6057
  api: {
@@ -20631,6 +20641,45 @@ function createNodeJsServerlessPreset(options) {
20631
20641
  }
20632
20642
  };
20633
20643
  }
20644
+ // src/presets/serverless-node.ts
20645
+ function createServerlessNodePreset(options) {
20646
+ const {
20647
+ name,
20648
+ slug,
20649
+ entry,
20650
+ domain,
20651
+ runtime = "nodejs20.x",
20652
+ memory = 1024,
20653
+ build,
20654
+ deploy,
20655
+ assets,
20656
+ queues = true,
20657
+ scheduler = "on",
20658
+ region = "us-east-1"
20659
+ } = options;
20660
+ return {
20661
+ project: { name, slug, region },
20662
+ mode: "serverless",
20663
+ environments: {
20664
+ production: {
20665
+ type: "production",
20666
+ domain,
20667
+ app: {
20668
+ kind: "node",
20669
+ runtime,
20670
+ entry,
20671
+ memory,
20672
+ build,
20673
+ deploy,
20674
+ assets,
20675
+ queues,
20676
+ scheduler,
20677
+ domain
20678
+ }
20679
+ }
20680
+ }
20681
+ };
20682
+ }
20634
20683
  // src/presets/fullstack-app.ts
20635
20684
  function createFullStackAppPreset(options) {
20636
20685
  const {
@@ -22145,6 +22194,119 @@ function createTraditionalWebAppPreset(options) {
22145
22194
  }
22146
22195
  };
22147
22196
  }
22197
+ // src/presets/laravel.ts
22198
+ function createLaravelPreset(options) {
22199
+ const {
22200
+ name,
22201
+ slug,
22202
+ domain,
22203
+ repository,
22204
+ phpVersion = "8.3",
22205
+ size = "small",
22206
+ database: database2 = slug.replace(/-/g, "_"),
22207
+ databasePassword,
22208
+ sslEmail,
22209
+ deployStrategy = "push",
22210
+ provider = "hetzner"
22211
+ } = options;
22212
+ return {
22213
+ project: { name, slug, region: "us-east-1" },
22214
+ mode: "server",
22215
+ cloud: { provider },
22216
+ environments: {
22217
+ production: { type: "production", domain }
22218
+ },
22219
+ infrastructure: {
22220
+ compute: {
22221
+ mode: "server",
22222
+ size,
22223
+ runtime: "php",
22224
+ webServer: "nginx",
22225
+ php: { versions: [phpVersion], default: phpVersion },
22226
+ managedServices: { mysql: true, redis: true },
22227
+ firewall: { enabled: true },
22228
+ autoUpdates: true,
22229
+ monitoring: true,
22230
+ backups: { enabled: true, schedule: "0 2 * * *", retentionCount: 7 }
22231
+ },
22232
+ appDatabase: {
22233
+ engine: "mysql",
22234
+ name: database2,
22235
+ username: database2,
22236
+ password: databasePassword
22237
+ }
22238
+ },
22239
+ sites: {
22240
+ main: {
22241
+ root: ".",
22242
+ type: "laravel",
22243
+ domain,
22244
+ phpVersion,
22245
+ repository: { ...repository, strategy: deployStrategy },
22246
+ scheduler: true,
22247
+ queues: [{ connection: "redis", queue: "default", processes: 1 }],
22248
+ ssl: { provider: "letsencrypt", email: sslEmail }
22249
+ }
22250
+ }
22251
+ };
22252
+ }
22253
+ // src/presets/serverless-laravel.ts
22254
+ function createServerlessLaravelPreset(options) {
22255
+ const {
22256
+ name,
22257
+ slug,
22258
+ domain,
22259
+ layers,
22260
+ phpVersion = "8.3",
22261
+ architecture = "x86_64",
22262
+ memory = 1024,
22263
+ build,
22264
+ deploy = ["migrate --force"],
22265
+ cache: cache2 = "dynamodb",
22266
+ scheduler = "on",
22267
+ region = "us-east-1"
22268
+ } = options;
22269
+ return {
22270
+ project: { name, slug, region },
22271
+ mode: "serverless",
22272
+ environments: {
22273
+ production: {
22274
+ type: "production",
22275
+ domain,
22276
+ app: {
22277
+ kind: "php",
22278
+ runtime: "provided.al2023",
22279
+ phpVersion,
22280
+ architecture,
22281
+ layers,
22282
+ memory,
22283
+ build,
22284
+ deploy,
22285
+ assets: "public",
22286
+ queues: true,
22287
+ scheduler,
22288
+ cache: { driver: cache2 },
22289
+ domain
22290
+ }
22291
+ }
22292
+ }
22293
+ };
22294
+ }
22295
+ // src/presets/dashboard.ts
22296
+ function createDashboardSite(options) {
22297
+ return {
22298
+ root: options.root ?? "ui/dist",
22299
+ deploy: "server",
22300
+ type: "static",
22301
+ domain: options.domain,
22302
+ build: options.build ?? "cd ui && bun install && bun run build",
22303
+ ssl: { provider: "letsencrypt" },
22304
+ auth: {
22305
+ username: options.username ?? "admin",
22306
+ password: options.password
22307
+ }
22308
+ };
22309
+ }
22148
22310
  // src/presets/extend.ts
22149
22311
  function deepMerge(target, source) {
22150
22312
  const result = { ...target };
@@ -25003,6 +25165,27 @@ var cloud_config_schema_default = {
25003
25165
  region: {
25004
25166
  type: "string",
25005
25167
  description: "AWS region override for this environment"
25168
+ },
25169
+ app: {
25170
+ type: "object",
25171
+ 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).",
25172
+ properties: {
25173
+ runtime: { type: "string", description: "Lambda runtime (e.g. nodejs20.x, provided.al2023)" },
25174
+ kind: { type: "string", enum: ["node", "bun", "php"], description: "Application kind (drives packaging + runtime)" },
25175
+ entry: { type: "string", description: "Entry file exporting the request handler" },
25176
+ memory: { type: "number", description: "HTTP function memory in MB" },
25177
+ timeout: { type: "number", description: "HTTP request timeout in seconds" },
25178
+ gatewayVersion: { type: "number", enum: [1, 2], description: "API Gateway version (2 = HTTP API, 1 = REST)" },
25179
+ warm: { type: "number", description: "Keep-warm / provisioned concurrency count" },
25180
+ queues: { description: "Queue names (true = single default queue, false = disabled)" },
25181
+ scheduler: { type: "string", enum: ["off", "on", "sub-minute"], description: "Task scheduler mode" },
25182
+ build: { type: "array", items: { type: "string" }, description: "Commands run locally before packaging" },
25183
+ deploy: { type: "array", items: { type: "string" }, description: "Commands run remotely after activation (e.g. migrations)" },
25184
+ octane: { type: "boolean", description: "Persistent application mode (Laravel Octane)" },
25185
+ packaging: { type: "string", enum: ["zip", "image"], description: "Deployment package format (zip or container image)" },
25186
+ phpVersion: { type: "string", description: "PHP version for the runtime layer (kind: php)" },
25187
+ architecture: { type: "string", enum: ["x86_64", "arm64"], description: "CPU architecture" }
25188
+ }
25006
25189
  }
25007
25190
  }
25008
25191
  },
@@ -44953,6 +45136,1145 @@ class QueueManagementManager {
44953
45136
  }
44954
45137
  }
44955
45138
  var queueManagementManager = new QueueManagementManager;
45139
+ // src/serverless/zip.ts
45140
+ import { deflateRawSync } from "node:zlib";
45141
+ var CRC_TABLE = (() => {
45142
+ const table2 = [];
45143
+ for (let i = 0;i < 256; i++) {
45144
+ let c = i;
45145
+ for (let j = 0;j < 8; j++)
45146
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
45147
+ table2[i] = c >>> 0;
45148
+ }
45149
+ return table2;
45150
+ })();
45151
+ function crc32(data) {
45152
+ let crc = 4294967295;
45153
+ for (let i = 0;i < data.length; i++)
45154
+ crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
45155
+ return (crc ^ 4294967295) >>> 0;
45156
+ }
45157
+ function toBuffer(data) {
45158
+ if (typeof data === "string")
45159
+ return Buffer.from(data, "utf-8");
45160
+ if (Buffer.isBuffer(data))
45161
+ return data;
45162
+ return Buffer.from(data);
45163
+ }
45164
+ function dosTimeDate(date) {
45165
+ const year = Math.max(1980, date.getFullYear());
45166
+ const time = (date.getHours() << 11 | date.getMinutes() << 5 | date.getSeconds() >> 1) & 65535;
45167
+ const d = (year - 1980 << 9 | date.getMonth() + 1 << 5 | date.getDate()) & 65535;
45168
+ return { time, date: d };
45169
+ }
45170
+ function createZip(entries) {
45171
+ const localParts = [];
45172
+ const centralParts = [];
45173
+ let offset = 0;
45174
+ for (const entry of entries) {
45175
+ const raw = toBuffer(entry.data);
45176
+ const compressed = deflateRawSync(raw);
45177
+ const crc = crc32(raw);
45178
+ const nameBuf = Buffer.from(entry.name.replace(/\\/g, "/"), "utf-8");
45179
+ const { time, date } = dosTimeDate(entry.date ?? new Date(0));
45180
+ const mode = entry.mode ?? 420;
45181
+ const local = Buffer.alloc(30 + nameBuf.length);
45182
+ local.writeUInt32LE(67324752, 0);
45183
+ local.writeUInt16LE(20, 4);
45184
+ local.writeUInt16LE(0, 6);
45185
+ local.writeUInt16LE(8, 8);
45186
+ local.writeUInt16LE(time, 10);
45187
+ local.writeUInt16LE(date, 12);
45188
+ local.writeUInt32LE(crc, 14);
45189
+ local.writeUInt32LE(compressed.length, 18);
45190
+ local.writeUInt32LE(raw.length, 22);
45191
+ local.writeUInt16LE(nameBuf.length, 26);
45192
+ local.writeUInt16LE(0, 28);
45193
+ nameBuf.copy(local, 30);
45194
+ const central = Buffer.alloc(46 + nameBuf.length);
45195
+ central.writeUInt32LE(33639248, 0);
45196
+ central.writeUInt16LE(798, 4);
45197
+ central.writeUInt16LE(20, 6);
45198
+ central.writeUInt16LE(0, 8);
45199
+ central.writeUInt16LE(8, 10);
45200
+ central.writeUInt16LE(time, 12);
45201
+ central.writeUInt16LE(date, 14);
45202
+ central.writeUInt32LE(crc, 16);
45203
+ central.writeUInt32LE(compressed.length, 20);
45204
+ central.writeUInt32LE(raw.length, 24);
45205
+ central.writeUInt16LE(nameBuf.length, 28);
45206
+ central.writeUInt16LE(0, 30);
45207
+ central.writeUInt16LE(0, 32);
45208
+ central.writeUInt16LE(0, 34);
45209
+ central.writeUInt16LE(0, 36);
45210
+ central.writeUInt32LE((mode & 65535) << 16, 38);
45211
+ central.writeUInt32LE(offset, 42);
45212
+ nameBuf.copy(central, 46);
45213
+ localParts.push(local, compressed);
45214
+ centralParts.push(central);
45215
+ offset += local.length + compressed.length;
45216
+ }
45217
+ const centralDir = Buffer.concat(centralParts);
45218
+ const end = Buffer.alloc(22);
45219
+ end.writeUInt32LE(101010256, 0);
45220
+ end.writeUInt16LE(0, 4);
45221
+ end.writeUInt16LE(0, 6);
45222
+ end.writeUInt16LE(entries.length, 8);
45223
+ end.writeUInt16LE(entries.length, 10);
45224
+ end.writeUInt32LE(centralDir.length, 12);
45225
+ end.writeUInt32LE(offset, 16);
45226
+ end.writeUInt16LE(0, 20);
45227
+ return Buffer.concat([...localParts, centralDir, end]);
45228
+ }
45229
+ // src/serverless/bootstrap.ts
45230
+ function generateBootstrap(opts) {
45231
+ const adapter = opts.adapterImport ?? "./adapter";
45232
+ const entry = opts.entryImport.replace(/\\/g, "/");
45233
+ return `// Generated by ts-cloud — serverless app bootstrap. Do not edit.
45234
+ import * as __userModule from ${JSON.stringify(entry)}
45235
+ import { resolveApp, createHandlers } from ${JSON.stringify(adapter)}
45236
+
45237
+ const __app = resolveApp(__userModule)
45238
+ const __handlers = createHandlers(__app)
45239
+
45240
+ export const http = __handlers.http
45241
+ export const queue = __handlers.queue
45242
+ export const cli = __handlers.cli
45243
+ `;
45244
+ }
45245
+ // src/serverless/package.ts
45246
+ import { execSync } from "node:child_process";
45247
+ import { createHash as createHash4 } from "node:crypto";
45248
+ import { cpSync, mkdtempSync, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
45249
+ import { tmpdir } from "node:os";
45250
+ import { dirname as dirname2, isAbsolute, join as join6, resolve } from "node:path";
45251
+ import { fileURLToPath } from "node:url";
45252
+ function adapterSourcePath() {
45253
+ return join6(dirname2(fileURLToPath(import.meta.url)), "runtime", "adapter.ts");
45254
+ }
45255
+ function runBuildHooks(hooks, cwd, onStep) {
45256
+ for (const hook of hooks ?? []) {
45257
+ onStep?.(`build: ${hook}`);
45258
+ execSync(hook, { stdio: "inherit", cwd });
45259
+ }
45260
+ }
45261
+ function sha256(data) {
45262
+ return createHash4("sha256").update(data).digest("hex");
45263
+ }
45264
+ function artifactKey(slug, environment, hash2) {
45265
+ return `deployments/${slug}/${environment}/${hash2}.zip`;
45266
+ }
45267
+ async function packageServerlessApp(opts) {
45268
+ const projectRoot = resolve(opts.projectRoot ?? process.cwd());
45269
+ const { app } = opts;
45270
+ if (!opts.skipBuild)
45271
+ runBuildHooks(app.build, projectRoot, opts.onStep);
45272
+ const entry = app.entry;
45273
+ if (!entry)
45274
+ throw new Error("serverless app: `entry` is required to package a Node/Bun application");
45275
+ const entryPath = isAbsolute(entry) ? entry : join6(projectRoot, entry);
45276
+ const stage = mkdtempSync(join6(tmpdir(), "tscloud-pkg-"));
45277
+ try {
45278
+ cpSync(adapterSourcePath(), join6(stage, "adapter.ts"));
45279
+ const bootstrapPath = join6(stage, "bootstrap.ts");
45280
+ writeFileSync3(bootstrapPath, generateBootstrap({ entryImport: entryPath, adapterImport: "./adapter" }));
45281
+ opts.onStep?.("bundling application");
45282
+ const result = await Bun.build({
45283
+ entrypoints: [bootstrapPath],
45284
+ target: "node",
45285
+ format: "esm",
45286
+ minify: false,
45287
+ sourcemap: "none"
45288
+ });
45289
+ if (!result.success) {
45290
+ const logs = result.logs.map((l) => String(l)).join(`
45291
+ `);
45292
+ throw new Error(`serverless app bundle failed:
45293
+ ${logs}`);
45294
+ }
45295
+ const output = result.outputs[0];
45296
+ const bundle = Buffer.from(await output.arrayBuffer());
45297
+ const handlerFile = "index";
45298
+ const zip = createZip([{ name: `${handlerFile}.mjs`, data: bundle }]);
45299
+ return {
45300
+ zip,
45301
+ bundle,
45302
+ sha256: sha256(zip),
45303
+ handlerFile,
45304
+ handlers: {
45305
+ http: app.handlers?.http ?? `${handlerFile}.http`,
45306
+ queue: app.handlers?.queue ?? `${handlerFile}.queue`,
45307
+ cli: app.handlers?.cli ?? `${handlerFile}.cli`
45308
+ },
45309
+ bundleBytes: bundle.length
45310
+ };
45311
+ } finally {
45312
+ rmSync(stage, { recursive: true, force: true });
45313
+ }
45314
+ }
45315
+ // src/serverless/composer.ts
45316
+ function resolveQueueNames(app, slug, env) {
45317
+ if (app.queues === false)
45318
+ return [];
45319
+ if (app.queues === undefined || app.queues === true)
45320
+ return [`${slug}-${env}-default`];
45321
+ return app.queues.map((q) => {
45322
+ const name = typeof q === "string" ? q : Object.keys(q)[0];
45323
+ return `${slug}-${env}-${name}`;
45324
+ });
45325
+ }
45326
+ function composeServerlessAppTemplate(opts) {
45327
+ const { app, environment, handlers } = opts;
45328
+ const slug = opts.config.project.slug;
45329
+ const runtime = app.runtime ?? "nodejs20.x";
45330
+ const architecture = app.architecture ?? "x86_64";
45331
+ const region = opts.config.project.region;
45332
+ const functionNames = {
45333
+ http: `${slug}-${environment}-http`,
45334
+ queue: `${slug}-${environment}-queue`,
45335
+ cli: `${slug}-${environment}-cli`
45336
+ };
45337
+ const queueNames = resolveQueueNames(app, slug, environment);
45338
+ const hasQueue = queueNames.length > 0;
45339
+ const imageMode = app.packaging === "image";
45340
+ const schedulerEnabled = (app.scheduler ?? "on") !== "off";
45341
+ const cacheEnabled = (app.cache?.driver ?? "dynamodb") === "dynamodb";
45342
+ const assetsEnabled = Boolean(app.assets);
45343
+ const assetsBucket = resolveServerlessAssetBucketName(slug, environment);
45344
+ const tmpStorage = app.tmpStorage ?? 512;
45345
+ const resources = {};
45346
+ const outputs = {};
45347
+ const inlinePolicies = [
45348
+ {
45349
+ PolicyName: "tscloud-serverless-app",
45350
+ PolicyDocument: {
45351
+ Version: "2012-10-17",
45352
+ Statement: [
45353
+ {
45354
+ Effect: "Allow",
45355
+ Action: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
45356
+ Resource: Fn2.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*")
45357
+ },
45358
+ {
45359
+ Effect: "Allow",
45360
+ Action: ["lambda:InvokeFunction"],
45361
+ Resource: Fn2.sub("arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:" + `${slug}-${environment}-*`)
45362
+ },
45363
+ {
45364
+ Effect: "Allow",
45365
+ Action: ["secretsmanager:GetSecretValue", "ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
45366
+ Resource: [
45367
+ Fn2.sub("arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:" + `${slug}/${environment}/*`),
45368
+ Fn2.sub("arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/" + `${slug}/${environment}/*`)
45369
+ ]
45370
+ },
45371
+ {
45372
+ Effect: "Allow",
45373
+ Action: ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
45374
+ Resource: [
45375
+ Fn2.sub(`arn:aws:s3:::${assetsBucket}`),
45376
+ Fn2.sub(`arn:aws:s3:::${assetsBucket}/*`),
45377
+ ...app.storage?.bucket ? [Fn2.sub(`arn:aws:s3:::${app.storage.bucket}`), Fn2.sub(`arn:aws:s3:::${app.storage.bucket}/*`)] : []
45378
+ ]
45379
+ }
45380
+ ]
45381
+ }
45382
+ }
45383
+ ];
45384
+ if (hasQueue) {
45385
+ inlinePolicies[0].PolicyDocument.Statement.push({
45386
+ Effect: "Allow",
45387
+ Action: ["sqs:SendMessage", "sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes", "sqs:GetQueueUrl"],
45388
+ Resource: Fn2.sub("arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:" + `${slug}-${environment}-*`)
45389
+ });
45390
+ }
45391
+ if (cacheEnabled) {
45392
+ inlinePolicies[0].PolicyDocument.Statement.push({
45393
+ Effect: "Allow",
45394
+ Action: ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:BatchGetItem", "dynamodb:BatchWriteItem"],
45395
+ Resource: Fn2.sub("arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/" + `${slug}-${environment}-cache*`)
45396
+ });
45397
+ }
45398
+ const managedPolicies = ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"];
45399
+ if (app.vpc?.subnets?.length)
45400
+ managedPolicies.push("arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole");
45401
+ resources.AppRole = {
45402
+ Type: "AWS::IAM::Role",
45403
+ Properties: {
45404
+ RoleName: `${slug}-${environment}-app-role`,
45405
+ AssumeRolePolicyDocument: {
45406
+ Version: "2012-10-17",
45407
+ Statement: [{ Effect: "Allow", Principal: { Service: "lambda.amazonaws.com" }, Action: "sts:AssumeRole" }]
45408
+ },
45409
+ ManagedPolicyArns: managedPolicies,
45410
+ Policies: inlinePolicies
45411
+ }
45412
+ };
45413
+ const baseEnv = (mode) => ({
45414
+ TSCLOUD_LAMBDA_MODE: mode,
45415
+ TSCLOUD_ENV: environment,
45416
+ ...app.octane ? { TSCLOUD_OCTANE: "1" } : {},
45417
+ ...cacheEnabled ? { TSCLOUD_CACHE_TABLE: `${slug}-${environment}-cache` } : {},
45418
+ ...hasQueue ? { TSCLOUD_QUEUE: queueNames[0] } : {},
45419
+ ...app.env ?? {}
45420
+ });
45421
+ const subnets = app.vpc?.subnets ?? [];
45422
+ const hasVpc = subnets.length > 0;
45423
+ const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy);
45424
+ if (needsDataVpc && !hasVpc) {
45425
+ throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy require app.vpc.subnets (private subnets) to be set.");
45426
+ }
45427
+ const vpcConfig = hasVpc ? {
45428
+ VpcConfig: {
45429
+ SubnetIds: subnets,
45430
+ SecurityGroupIds: [
45431
+ ...app.vpc?.securityGroups ?? [],
45432
+ ...needsDataVpc ? [Fn2.getAtt("DataSecurityGroup", "GroupId")] : []
45433
+ ]
45434
+ }
45435
+ } : {};
45436
+ function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency) {
45437
+ resources[`${logicalId}LogGroup`] = {
45438
+ Type: "AWS::Logs::LogGroup",
45439
+ Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: 14 }
45440
+ };
45441
+ const codeProps = imageMode ? {
45442
+ PackageType: "Image",
45443
+ Code: { ImageUri: Fn2.ref("ImageUri") },
45444
+ ...app.kind === "php" ? {} : { ImageConfig: { Command: [handler8] } }
45445
+ } : {
45446
+ Runtime: runtime,
45447
+ Handler: handler8,
45448
+ Code: { S3Bucket: Fn2.ref("ArtifactBucket"), S3Key: Fn2.ref("ArtifactKey") },
45449
+ ...opts.runtimeLayers?.length ? { Layers: opts.runtimeLayers } : {}
45450
+ };
45451
+ resources[logicalId] = {
45452
+ Type: "AWS::Lambda::Function",
45453
+ DependsOn: [`${logicalId}LogGroup`],
45454
+ Properties: {
45455
+ FunctionName: name,
45456
+ Architectures: [architecture],
45457
+ MemorySize: memory,
45458
+ Timeout: timeout,
45459
+ Role: Fn2.getAtt("AppRole", "Arn"),
45460
+ Environment: { Variables: baseEnv(mode) },
45461
+ EphemeralStorage: { Size: tmpStorage },
45462
+ ...codeProps,
45463
+ ...reservedConcurrency !== undefined ? { ReservedConcurrentExecutions: reservedConcurrency } : {},
45464
+ ...vpcConfig
45465
+ }
45466
+ };
45467
+ }
45468
+ addFunction("HttpFunction", functionNames.http, handlers.http, "http", app.memory ?? 1024, app.timeout ?? 28, app.concurrency);
45469
+ addFunction("CliFunction", functionNames.cli, handlers.cli, "cli", app.cliMemory ?? 1024, app.cliTimeout ?? 900);
45470
+ if (hasQueue)
45471
+ addFunction("QueueFunction", functionNames.queue, handlers.queue, "queue", app.queueMemory ?? 1024, app.queueTimeout ?? 120);
45472
+ resources.HttpApi = {
45473
+ Type: "AWS::ApiGatewayV2::Api",
45474
+ Properties: {
45475
+ Name: `${slug}-${environment}`,
45476
+ ProtocolType: "HTTP"
45477
+ }
45478
+ };
45479
+ resources.HttpIntegration = {
45480
+ Type: "AWS::ApiGatewayV2::Integration",
45481
+ Properties: {
45482
+ ApiId: Fn2.ref("HttpApi"),
45483
+ IntegrationType: "AWS_PROXY",
45484
+ IntegrationUri: Fn2.getAtt("HttpFunction", "Arn"),
45485
+ PayloadFormatVersion: "2.0"
45486
+ }
45487
+ };
45488
+ resources.HttpRoute = {
45489
+ Type: "AWS::ApiGatewayV2::Route",
45490
+ Properties: {
45491
+ ApiId: Fn2.ref("HttpApi"),
45492
+ RouteKey: "$default",
45493
+ Target: Fn2.join("/", ["integrations", Fn2.ref("HttpIntegration")])
45494
+ }
45495
+ };
45496
+ resources.HttpStage = {
45497
+ Type: "AWS::ApiGatewayV2::Stage",
45498
+ Properties: {
45499
+ ApiId: Fn2.ref("HttpApi"),
45500
+ StageName: "$default",
45501
+ AutoDeploy: true
45502
+ }
45503
+ };
45504
+ resources.HttpPermission = {
45505
+ Type: "AWS::Lambda::Permission",
45506
+ Properties: {
45507
+ FunctionName: Fn2.ref("HttpFunction"),
45508
+ Action: "lambda:InvokeFunction",
45509
+ Principal: "apigateway.amazonaws.com",
45510
+ SourceArn: Fn2.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*/*")
45511
+ }
45512
+ };
45513
+ outputs.HttpApiEndpoint = {
45514
+ Description: "HTTP API endpoint",
45515
+ Value: Fn2.getAtt("HttpApi", "ApiEndpoint")
45516
+ };
45517
+ outputs.HttpApiId = { Description: "HTTP API id", Value: Fn2.ref("HttpApi") };
45518
+ if (hasQueue) {
45519
+ resources.AppQueueDlq = {
45520
+ Type: "AWS::SQS::Queue",
45521
+ Properties: {
45522
+ QueueName: `${slug}-${environment}-dlq`,
45523
+ MessageRetentionPeriod: 1209600
45524
+ }
45525
+ };
45526
+ queueNames.forEach((qName, i) => {
45527
+ const qId = `AppQueue${i}`;
45528
+ resources[qId] = {
45529
+ Type: "AWS::SQS::Queue",
45530
+ Properties: {
45531
+ QueueName: qName,
45532
+ VisibilityTimeout: Math.max(app.queueTimeout ?? 120, app.queueTimeout ?? 120),
45533
+ RedrivePolicy: {
45534
+ deadLetterTargetArn: Fn2.getAtt("AppQueueDlq", "Arn"),
45535
+ maxReceiveCount: app.queueTries ?? 3
45536
+ }
45537
+ }
45538
+ };
45539
+ resources[`${qId}Mapping`] = {
45540
+ Type: "AWS::Lambda::EventSourceMapping",
45541
+ Properties: {
45542
+ EventSourceArn: Fn2.getAtt(qId, "Arn"),
45543
+ FunctionName: Fn2.ref("QueueFunction"),
45544
+ BatchSize: 1,
45545
+ FunctionResponseTypes: ["ReportBatchItemFailures"],
45546
+ ...app.queueConcurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, app.queueConcurrency) } } : {}
45547
+ }
45548
+ };
45549
+ outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${qName}`, Value: Fn2.ref(qId) };
45550
+ });
45551
+ }
45552
+ if (schedulerEnabled) {
45553
+ resources.SchedulerRule = {
45554
+ Type: "AWS::Events::Rule",
45555
+ Properties: {
45556
+ Name: `${slug}-${environment}-scheduler`,
45557
+ ScheduleExpression: "rate(1 minute)",
45558
+ State: "ENABLED",
45559
+ Targets: [{
45560
+ Id: "cli",
45561
+ Arn: Fn2.getAtt("CliFunction", "Arn"),
45562
+ Input: JSON.stringify({ command: "schedule:run" })
45563
+ }]
45564
+ }
45565
+ };
45566
+ resources.SchedulerPermission = {
45567
+ Type: "AWS::Lambda::Permission",
45568
+ Properties: {
45569
+ FunctionName: Fn2.ref("CliFunction"),
45570
+ Action: "lambda:InvokeFunction",
45571
+ Principal: "events.amazonaws.com",
45572
+ SourceArn: Fn2.getAtt("SchedulerRule", "Arn")
45573
+ }
45574
+ };
45575
+ }
45576
+ if (app.warm && app.warm > 0) {
45577
+ const TARGETS_PER_RULE = 5;
45578
+ const ruleCount = Math.ceil(app.warm / TARGETS_PER_RULE);
45579
+ let warmed = 0;
45580
+ for (let r = 0;r < ruleCount; r++) {
45581
+ const targets = Math.min(TARGETS_PER_RULE, app.warm - warmed);
45582
+ resources[`WarmerRule${r}`] = {
45583
+ Type: "AWS::Events::Rule",
45584
+ Properties: {
45585
+ Name: `${slug}-${environment}-warmer-${r}`,
45586
+ ScheduleExpression: "rate(5 minutes)",
45587
+ State: "ENABLED",
45588
+ Targets: Array.from({ length: targets }, (_, i) => ({
45589
+ Id: `warm-${r}-${i}`,
45590
+ Arn: Fn2.getAtt("HttpFunction", "Arn"),
45591
+ Input: JSON.stringify({ warmer: true })
45592
+ }))
45593
+ }
45594
+ };
45595
+ warmed += targets;
45596
+ }
45597
+ resources.WarmerPermission = {
45598
+ Type: "AWS::Lambda::Permission",
45599
+ Properties: {
45600
+ FunctionName: Fn2.ref("HttpFunction"),
45601
+ Action: "lambda:InvokeFunction",
45602
+ Principal: "events.amazonaws.com",
45603
+ SourceArn: Fn2.sub("arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/" + `${slug}-${environment}-warmer-*`)
45604
+ }
45605
+ };
45606
+ }
45607
+ if (cacheEnabled) {
45608
+ resources.CacheTable = {
45609
+ Type: "AWS::DynamoDB::Table",
45610
+ Properties: {
45611
+ TableName: `${slug}-${environment}-cache`,
45612
+ BillingMode: "PAY_PER_REQUEST",
45613
+ AttributeDefinitions: [{ AttributeName: "key", AttributeType: "S" }],
45614
+ KeySchema: [{ AttributeName: "key", KeyType: "HASH" }],
45615
+ TimeToLiveSpecification: { AttributeName: "expires_at", Enabled: true }
45616
+ }
45617
+ };
45618
+ outputs.CacheTableName = { Description: "DynamoDB cache table", Value: Fn2.ref("CacheTable") };
45619
+ }
45620
+ if (assetsEnabled) {
45621
+ resources.AssetsBucket = {
45622
+ Type: "AWS::S3::Bucket",
45623
+ Properties: {
45624
+ BucketName: assetsBucket,
45625
+ PublicAccessBlockConfiguration: {
45626
+ BlockPublicAcls: true,
45627
+ BlockPublicPolicy: true,
45628
+ IgnorePublicAcls: true,
45629
+ RestrictPublicBuckets: true
45630
+ }
45631
+ }
45632
+ };
45633
+ resources.AssetsOAC = {
45634
+ Type: "AWS::CloudFront::OriginAccessControl",
45635
+ Properties: {
45636
+ OriginAccessControlConfig: {
45637
+ Name: `${slug}-${environment}-assets-oac`,
45638
+ OriginAccessControlOriginType: "s3",
45639
+ SigningBehavior: "always",
45640
+ SigningProtocol: "sigv4"
45641
+ }
45642
+ }
45643
+ };
45644
+ resources.AssetsDistribution = {
45645
+ Type: "AWS::CloudFront::Distribution",
45646
+ Properties: {
45647
+ DistributionConfig: {
45648
+ Enabled: true,
45649
+ DefaultCacheBehavior: {
45650
+ TargetOriginId: "assets",
45651
+ ViewerProtocolPolicy: "redirect-to-https",
45652
+ Compress: true,
45653
+ CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6"
45654
+ },
45655
+ Origins: [{
45656
+ Id: "assets",
45657
+ DomainName: Fn2.getAtt("AssetsBucket", "RegionalDomainName"),
45658
+ OriginAccessControlId: Fn2.ref("AssetsOAC"),
45659
+ S3OriginConfig: { OriginAccessIdentity: "" }
45660
+ }]
45661
+ }
45662
+ }
45663
+ };
45664
+ resources.AssetsBucketPolicy = {
45665
+ Type: "AWS::S3::BucketPolicy",
45666
+ Properties: {
45667
+ Bucket: Fn2.ref("AssetsBucket"),
45668
+ PolicyDocument: {
45669
+ Version: "2012-10-17",
45670
+ Statement: [{
45671
+ Effect: "Allow",
45672
+ Principal: { Service: "cloudfront.amazonaws.com" },
45673
+ Action: "s3:GetObject",
45674
+ Resource: Fn2.sub(`arn:aws:s3:::${assetsBucket}/*`),
45675
+ Condition: { StringEquals: { "AWS:SourceArn": Fn2.sub("arn:aws:cloudfront::${AWS::AccountId}:distribution/${AssetsDistribution}") } }
45676
+ }]
45677
+ }
45678
+ }
45679
+ };
45680
+ outputs.AssetsBucketName = { Description: "Assets bucket", Value: Fn2.ref("AssetsBucket") };
45681
+ outputs.AssetsCdnDomain = { Description: "Assets CloudFront domain", Value: Fn2.getAtt("AssetsDistribution", "DomainName") };
45682
+ }
45683
+ if (app.firewall?.enabled) {
45684
+ const wafRules = [];
45685
+ let priority = 0;
45686
+ if (app.firewall.rateLimit) {
45687
+ wafRules.push({
45688
+ Name: "rate-limit",
45689
+ Priority: priority++,
45690
+ Action: { Block: {} },
45691
+ Statement: { RateBasedStatement: { Limit: app.firewall.rateLimit, AggregateKeyType: "IP" } },
45692
+ VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-rate` }
45693
+ });
45694
+ }
45695
+ const managed = {
45696
+ sqlInjection: "AWSManagedRulesSQLiRuleSet",
45697
+ xss: "AWSManagedRulesCommonRuleSet",
45698
+ common: "AWSManagedRulesCommonRuleSet",
45699
+ botControl: "AWSManagedRulesBotControlRuleSet",
45700
+ ipReputation: "AWSManagedRulesAmazonIpReputationList"
45701
+ };
45702
+ for (const rule of app.firewall.rules ?? []) {
45703
+ const name = managed[rule];
45704
+ if (!name)
45705
+ continue;
45706
+ wafRules.push({
45707
+ Name: `managed-${rule}`,
45708
+ Priority: priority++,
45709
+ OverrideAction: { None: {} },
45710
+ Statement: { ManagedRuleGroupStatement: { VendorName: "AWS", Name: name } },
45711
+ VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-${rule}` }
45712
+ });
45713
+ }
45714
+ resources.WebAcl = {
45715
+ Type: "AWS::WAFv2::WebACL",
45716
+ Properties: {
45717
+ Name: `${slug}-${environment}-waf`,
45718
+ Scope: "REGIONAL",
45719
+ DefaultAction: { Allow: {} },
45720
+ Rules: wafRules,
45721
+ VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-waf` }
45722
+ }
45723
+ };
45724
+ resources.WebAclAssociation = {
45725
+ Type: "AWS::WAFv2::WebACLAssociation",
45726
+ DependsOn: ["HttpStage"],
45727
+ Properties: {
45728
+ ResourceArn: Fn2.sub("arn:aws:apigateway:${AWS::Region}::/apis/${HttpApi}/stages/$default"),
45729
+ WebACLArn: Fn2.getAtt("WebAcl", "Arn")
45730
+ }
45731
+ };
45732
+ }
45733
+ if (hasVpc && needsDataVpc) {
45734
+ resources.DataSecurityGroup = {
45735
+ Type: "AWS::EC2::SecurityGroup",
45736
+ Properties: {
45737
+ GroupDescription: `${slug}-${environment} serverless data access`,
45738
+ SecurityGroupIngress: [{ IpProtocol: "-1", CidrIp: "10.0.0.0/8" }]
45739
+ }
45740
+ };
45741
+ }
45742
+ if (app.cache?.driver === "elasticache") {
45743
+ resources.CacheSubnetGroup = {
45744
+ Type: "AWS::ElastiCache::SubnetGroup",
45745
+ Properties: { Description: `${slug}-${environment} cache subnets`, SubnetIds: subnets }
45746
+ };
45747
+ resources.CacheCluster = {
45748
+ Type: "AWS::ElastiCache::ReplicationGroup",
45749
+ Properties: {
45750
+ ReplicationGroupId: `${slug}-${environment}-cache`,
45751
+ ReplicationGroupDescription: `${slug}-${environment} redis`,
45752
+ Engine: "redis",
45753
+ CacheNodeType: "cache.t4g.micro",
45754
+ NumCacheClusters: 1,
45755
+ AutomaticFailoverEnabled: false,
45756
+ CacheSubnetGroupName: Fn2.ref("CacheSubnetGroup"),
45757
+ SecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")],
45758
+ TransitEncryptionEnabled: false
45759
+ }
45760
+ };
45761
+ outputs.CacheEndpoint = { Description: "Redis primary endpoint", Value: Fn2.getAtt("CacheCluster", "PrimaryEndPoint.Address") };
45762
+ }
45763
+ if (app.database?.connection === "aurora-serverless") {
45764
+ resources.DbSubnetGroup = {
45765
+ Type: "AWS::RDS::DBSubnetGroup",
45766
+ Properties: { DBSubnetGroupDescription: `${slug}-${environment} db subnets`, SubnetIds: subnets }
45767
+ };
45768
+ resources.DbSecret = {
45769
+ Type: "AWS::SecretsManager::Secret",
45770
+ Properties: {
45771
+ Name: `${slug}/${environment}/db`,
45772
+ GenerateSecretString: {
45773
+ SecretStringTemplate: JSON.stringify({ username: "app" }),
45774
+ GenerateStringKey: "password",
45775
+ PasswordLength: 32,
45776
+ ExcludePunctuation: true
45777
+ }
45778
+ }
45779
+ };
45780
+ resources.DbCluster = {
45781
+ Type: "AWS::RDS::DBCluster",
45782
+ Properties: {
45783
+ Engine: "aurora-mysql",
45784
+ EngineMode: "provisioned",
45785
+ DBClusterIdentifier: `${slug}-${environment}-db`,
45786
+ MasterUsername: Fn2.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:username}}"),
45787
+ MasterUserPassword: Fn2.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:password}}"),
45788
+ ServerlessV2ScalingConfiguration: { MinCapacity: 0.5, MaxCapacity: 4 },
45789
+ DBSubnetGroupName: Fn2.ref("DbSubnetGroup"),
45790
+ VpcSecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")]
45791
+ }
45792
+ };
45793
+ resources.DbInstance = {
45794
+ Type: "AWS::RDS::DBInstance",
45795
+ Properties: {
45796
+ Engine: "aurora-mysql",
45797
+ DBInstanceClass: "db.serverless",
45798
+ DBClusterIdentifier: Fn2.ref("DbCluster")
45799
+ }
45800
+ };
45801
+ outputs.DbEndpoint = { Description: "Aurora cluster endpoint", Value: Fn2.getAtt("DbCluster", "Endpoint.Address") };
45802
+ }
45803
+ if (app.rdsProxy && app.database?.connection === "aurora-serverless") {
45804
+ resources.DbProxyRole = {
45805
+ Type: "AWS::IAM::Role",
45806
+ Properties: {
45807
+ AssumeRolePolicyDocument: {
45808
+ Version: "2012-10-17",
45809
+ Statement: [{ Effect: "Allow", Principal: { Service: "rds.amazonaws.com" }, Action: "sts:AssumeRole" }]
45810
+ },
45811
+ Policies: [{
45812
+ PolicyName: "read-db-secret",
45813
+ PolicyDocument: {
45814
+ Version: "2012-10-17",
45815
+ Statement: [{ Effect: "Allow", Action: ["secretsmanager:GetSecretValue"], Resource: Fn2.ref("DbSecret") }]
45816
+ }
45817
+ }]
45818
+ }
45819
+ };
45820
+ resources.DbProxy = {
45821
+ Type: "AWS::RDS::DBProxy",
45822
+ Properties: {
45823
+ DBProxyName: typeof app.rdsProxy === "object" && app.rdsProxy.name ? app.rdsProxy.name : `${slug}-${environment}-proxy`,
45824
+ EngineFamily: "MYSQL",
45825
+ RoleArn: Fn2.getAtt("DbProxyRole", "Arn"),
45826
+ Auth: [{ AuthScheme: "SECRETS", SecretArn: Fn2.ref("DbSecret"), IAMAuth: "DISABLED" }],
45827
+ VpcSubnetIds: subnets,
45828
+ VpcSecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")],
45829
+ RequireTLS: false
45830
+ }
45831
+ };
45832
+ outputs.DbProxyEndpoint = { Description: "RDS Proxy endpoint", Value: Fn2.getAtt("DbProxy", "Endpoint") };
45833
+ }
45834
+ outputs.HttpFunctionName = { Description: "HTTP function name", Value: Fn2.ref("HttpFunction") };
45835
+ outputs.CliFunctionName = { Description: "CLI function name", Value: Fn2.ref("CliFunction") };
45836
+ if (hasQueue)
45837
+ outputs.QueueFunctionName = { Description: "Queue function name", Value: Fn2.ref("QueueFunction") };
45838
+ const template = {
45839
+ AWSTemplateFormatVersion: "2010-09-09",
45840
+ Description: `Serverless application for ${opts.config.project.name} (${slug}-${environment})`,
45841
+ Parameters: imageMode ? {
45842
+ ImageUri: { Type: "String", Description: "ECR image URI of the deployment artifact" }
45843
+ } : {
45844
+ ArtifactBucket: { Type: "String", Description: "S3 bucket holding the deployment artifact" },
45845
+ ArtifactKey: { Type: "String", Description: "S3 key of the deployment artifact (zip)" }
45846
+ },
45847
+ Resources: resources,
45848
+ Outputs: outputs
45849
+ };
45850
+ const resourceSummary = {};
45851
+ for (const r of Object.values(resources)) {
45852
+ resourceSummary[r.Type] = (resourceSummary[r.Type] ?? 0) + 1;
45853
+ }
45854
+ return { template, functionNames, queueNames, resourceSummary };
45855
+ }
45856
+ // src/serverless-php/dockerfile.ts
45857
+ var PHP_LAYER_EXTENSIONS = [
45858
+ "cli",
45859
+ "fpm",
45860
+ "mbstring",
45861
+ "xml",
45862
+ "pdo",
45863
+ "mysqlnd",
45864
+ "gd",
45865
+ "bcmath",
45866
+ "intl",
45867
+ "opcache",
45868
+ "sodium",
45869
+ "process",
45870
+ "pecl-redis6",
45871
+ "pecl-apcu",
45872
+ "pgsql"
45873
+ ];
45874
+ function phpLayerPackages(phpVersion) {
45875
+ const scl = `php${phpVersion.replace(".", "")}`;
45876
+ return PHP_LAYER_EXTENSIONS.map((ext) => `${scl}-php-${ext}`);
45877
+ }
45878
+ function phpLayerBuildStage(phpVersion, asName) {
45879
+ const scl = `php${phpVersion.replace(".", "")}`;
45880
+ const packages = phpLayerPackages(phpVersion).join(" \\\n ");
45881
+ const sclRoot = `/opt/remi/${scl}/root`;
45882
+ const from = asName ? `FROM amazonlinux:2023 AS ${asName}` : "FROM amazonlinux:2023";
45883
+ return `${from}
45884
+
45885
+ # Remi provides version-isolated PHP SCL packages for EL9 (AL2023 compatible).
45886
+ RUN dnf -y install dnf-plugins-core 'dnf-command(config-manager)' && \\
45887
+ dnf -y install https://rpms.remirepo.net/enterprise/remi-release-9.rpm && \\
45888
+ dnf -y update && \\
45889
+ dnf -y install \\
45890
+ ${packages} \\
45891
+ findutils tar gzip && \\
45892
+ dnf clean all
45893
+
45894
+ # Relocate PHP + php-fpm + extensions and their shared libs under /opt.
45895
+ RUN set -eux; \\
45896
+ mkdir -p /opt/php/bin /opt/php/sbin /opt/php/lib /opt/php/lib/php/modules /opt/php/etc/php.d /opt/tscloud; \\
45897
+ cp ${sclRoot}/usr/bin/php /opt/php/bin/php; \\
45898
+ cp ${sclRoot}/usr/sbin/php-fpm /opt/php/sbin/php-fpm; \\
45899
+ EXT_DIR="$(${sclRoot}/usr/bin/php -r 'echo ini_get("extension_dir");')"; \\
45900
+ cp -a "$EXT_DIR"/*.so /opt/php/lib/php/modules/ || true; \\
45901
+ cp -a ${sclRoot}/etc/php.d/*.ini /opt/php/etc/php.d/ 2>/dev/null || true; \\
45902
+ # Copy shared-library dependencies of php, php-fpm, and the extension modules.
45903
+ for bin in /opt/php/bin/php /opt/php/sbin/php-fpm /opt/php/lib/php/modules/*.so; do \\
45904
+ ldd "$bin" 2>/dev/null | awk '/=>/{print $3}/ld-linux/{print $1}' | sort -u | while read -r lib; do \\
45905
+ [ -f "$lib" ] && cp -Ln "$lib" /opt/php/lib/ || true; \\
45906
+ done; \\
45907
+ done
45908
+
45909
+ # Point PHP at the relocated config + extension dir.
45910
+ RUN printf 'extension_dir=/opt/php/lib/php/modules\\n' > /opt/php/etc/php.ini && \\
45911
+ cat /opt/php/etc/php.d/*.ini >> /opt/php/etc/php.ini 2>/dev/null || true
45912
+ ENV PHP_INI_SCAN_DIR=/opt/php/etc/php.d
45913
+
45914
+ # Runtime assets (bootstrap, runtime loops, fpm config) are added by the build
45915
+ # orchestrator after the image is produced. Export /opt as the layer payload.
45916
+ CMD ["true"]
45917
+ `;
45918
+ }
45919
+ function generatePhpLayerDockerfile(options = {}) {
45920
+ const phpVersion = options.phpVersion ?? "8.3";
45921
+ return `# ts-cloud PHP ${phpVersion} runtime layer (generated) — AWS Lambda provided.al2023.
45922
+ ${phpLayerBuildStage(phpVersion)}`;
45923
+ }
45924
+
45925
+ // src/serverless/app-image.ts
45926
+ function generateAppImageDockerfile(options) {
45927
+ if (options.kind === "php") {
45928
+ const phpVersion = options.phpVersion ?? "8.3";
45929
+ return `# ts-cloud serverless PHP app image (generated, multi-stage).
45930
+ ${phpLayerBuildStage(phpVersion, "phpbuild")}
45931
+
45932
+ FROM public.ecr.aws/lambda/provided:al2023
45933
+ # Bake the relocated PHP runtime from the build stage.
45934
+ COPY --from=phpbuild /opt/ /opt/
45935
+ # ts-cloud runtime assets (bootstrap, runtime loops, fpm config).
45936
+ COPY runtime/ /opt/
45937
+ # The application source tree.
45938
+ COPY app/ /var/task/
45939
+ # /opt/bootstrap is the runtime entrypoint; mode comes from TSCLOUD_LAMBDA_MODE.
45940
+ ENTRYPOINT [ "/opt/bootstrap" ]
45941
+ `;
45942
+ }
45943
+ const nodeMajor = options.nodeMajor ?? "20";
45944
+ return `# ts-cloud serverless Node app image (generated).
45945
+ FROM public.ecr.aws/lambda/nodejs:${nodeMajor}
45946
+
45947
+ # The bundled handler artifact (index.mjs) at the Lambda task root.
45948
+ COPY app/ \${LAMBDA_TASK_ROOT}/
45949
+
45950
+ # Per-function CMD (e.g. index.http) is supplied via ImageConfig.Command.
45951
+ CMD [ "index.http" ]
45952
+ `;
45953
+ }
45954
+ // src/serverless/runtime/adapter.ts
45955
+ function resolveApp(mod) {
45956
+ const m = mod;
45957
+ const def = m?.default ?? m;
45958
+ if (typeof def === "function")
45959
+ return { fetch: def };
45960
+ return {
45961
+ fetch: def?.fetch ?? m?.fetch,
45962
+ queue: def?.queue ?? m?.queue,
45963
+ cli: def?.cli ?? m?.cli
45964
+ };
45965
+ }
45966
+ var TEXT_CONTENT = /^(?:text\/|application\/(?:json|xml|javascript|graphql|x-www-form-urlencoded|.*\+json|.*\+xml)|image\/svg)/i;
45967
+ function isTextContentType(contentType) {
45968
+ if (!contentType)
45969
+ return true;
45970
+ return TEXT_CONTENT.test(contentType);
45971
+ }
45972
+ function readMaintenance(opts) {
45973
+ if (opts?.maintenance)
45974
+ return opts.maintenance;
45975
+ const env = globalThis.process?.env ?? {};
45976
+ return {
45977
+ enabled: env.MAINTENANCE_MODE === "1" || env.MAINTENANCE_MODE === "true",
45978
+ bypassSecret: env.MAINTENANCE_BYPASS_SECRET
45979
+ };
45980
+ }
45981
+ function eventToRequest(event) {
45982
+ const host = event.requestContext?.domainName ?? "localhost";
45983
+ const query = event.rawQueryString ? `?${event.rawQueryString}` : "";
45984
+ const url = `https://${host}${event.rawPath || "/"}${query}`;
45985
+ const headers = new Headers;
45986
+ for (const [key, value] of Object.entries(event.headers ?? {})) {
45987
+ if (value !== undefined)
45988
+ headers.set(key, value);
45989
+ }
45990
+ if (event.cookies?.length)
45991
+ headers.set("cookie", event.cookies.join("; "));
45992
+ const method = event.requestContext.http.method;
45993
+ let body;
45994
+ if (event.body !== undefined && method !== "GET" && method !== "HEAD") {
45995
+ body = event.isBase64Encoded ? new Uint8Array(Buffer.from(event.body, "base64")) : new TextEncoder().encode(event.body);
45996
+ }
45997
+ return new Request(url, { method, headers, body });
45998
+ }
45999
+ async function responseToResult(response) {
46000
+ const headers = {};
46001
+ const cookies = [];
46002
+ const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
46003
+ for (const c of setCookies)
46004
+ cookies.push(c);
46005
+ response.headers.forEach((value, key) => {
46006
+ if (key.toLowerCase() === "set-cookie")
46007
+ return;
46008
+ headers[key] = value;
46009
+ });
46010
+ const buffer = Buffer.from(await response.arrayBuffer());
46011
+ const textual = isTextContentType(response.headers.get("content-type"));
46012
+ return {
46013
+ statusCode: response.status,
46014
+ headers,
46015
+ ...cookies.length ? { cookies } : {},
46016
+ body: textual ? buffer.toString("utf-8") : buffer.toString("base64"),
46017
+ isBase64Encoded: !textual
46018
+ };
46019
+ }
46020
+ function createHttpHandler(handler8, opts) {
46021
+ return async (event) => {
46022
+ if (event.warmer) {
46023
+ return { statusCode: 200, headers: { "content-type": "text/plain" }, body: "warm", isBase64Encoded: false };
46024
+ }
46025
+ if (!handler8) {
46026
+ return { statusCode: 501, headers: { "content-type": "text/plain" }, body: "No HTTP handler configured", isBase64Encoded: false };
46027
+ }
46028
+ const maintenance = readMaintenance(opts);
46029
+ if (maintenance.enabled) {
46030
+ const bypass = event.headers?.["x-maintenance-bypass"] ?? event.cookies?.find((c) => c.startsWith("tscloud_bypass="))?.split("=")[1];
46031
+ if (!maintenance.bypassSecret || bypass !== maintenance.bypassSecret) {
46032
+ return {
46033
+ statusCode: 503,
46034
+ headers: { "content-type": "text/plain", "retry-after": "120" },
46035
+ body: "Service temporarily unavailable (maintenance mode)",
46036
+ isBase64Encoded: false
46037
+ };
46038
+ }
46039
+ }
46040
+ const request = eventToRequest(event);
46041
+ const response = await handler8(request);
46042
+ return responseToResult(response);
46043
+ };
46044
+ }
46045
+ function parseRecordBody(body) {
46046
+ try {
46047
+ return JSON.parse(body);
46048
+ } catch {
46049
+ return body;
46050
+ }
46051
+ }
46052
+ function createQueueHandler(handler8) {
46053
+ return async (event) => {
46054
+ const batchItemFailures = [];
46055
+ if (!handler8)
46056
+ return { batchItemFailures };
46057
+ for (const record of event.Records ?? []) {
46058
+ try {
46059
+ await handler8(parseRecordBody(record.body), record);
46060
+ } catch {
46061
+ batchItemFailures.push({ itemIdentifier: record.messageId });
46062
+ }
46063
+ }
46064
+ return { batchItemFailures };
46065
+ };
46066
+ }
46067
+ function createCliHandler(handler8) {
46068
+ return async (event) => {
46069
+ if (!handler8)
46070
+ return { statusCode: 501, output: "No CLI handler configured" };
46071
+ return handler8(event);
46072
+ };
46073
+ }
46074
+ function createHandlers(app, opts) {
46075
+ return {
46076
+ http: createHttpHandler(app.fetch, opts),
46077
+ queue: createQueueHandler(app.queue),
46078
+ cli: createCliHandler(app.cli)
46079
+ };
46080
+ }
46081
+ // src/serverless-php/php-fpm-conf.ts
46082
+ function generatePhpFpmConfig(options = {}) {
46083
+ const socket = options.socketPath ?? "/tmp/.tscloud-fpm.sock";
46084
+ const maxChildren = options.maxChildren ?? 1;
46085
+ const errorLog = options.errorLog ?? "/tmp/storage/logs/php-fpm.log";
46086
+ return `; ts-cloud php-fpm configuration (generated) — AWS Lambda custom runtime.
46087
+ [global]
46088
+ daemonize = no
46089
+ error_log = ${errorLog}
46090
+ log_level = warning
46091
+
46092
+ [www]
46093
+ listen = ${socket}
46094
+ listen.mode = 0666
46095
+ pm = static
46096
+ pm.max_children = ${maxChildren}
46097
+ catch_workers_output = yes
46098
+ decorate_workers_output = no
46099
+ ; Surface fatal errors / fpm logs to the Lambda log stream.
46100
+ php_admin_value[error_log] = /dev/stderr
46101
+ php_admin_flag[log_errors] = on
46102
+ clear_env = no
46103
+ `;
46104
+ }
46105
+ // src/serverless-php/runtime-assets.ts
46106
+ import { readFileSync as readFileSync4 } from "node:fs";
46107
+ import { dirname as dirname3, join as join7 } from "node:path";
46108
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
46109
+ function assetsDir() {
46110
+ return join7(dirname3(fileURLToPath2(import.meta.url)), "runtime-assets");
46111
+ }
46112
+ function phpRuntimeLayerAssets() {
46113
+ const dir = assetsDir();
46114
+ const read = (f) => readFileSync4(join7(dir, f), "utf-8");
46115
+ return [
46116
+ { path: "bootstrap", contents: read("bootstrap"), mode: 493 },
46117
+ { path: "tscloud/runtime.php", contents: read("runtime.php"), mode: 420 },
46118
+ { path: "tscloud/octane-runtime.php", contents: read("octane-runtime.php"), mode: 420 },
46119
+ { path: "tscloud/cli-runtime.php", contents: read("cli-runtime.php"), mode: 420 },
46120
+ { path: "tscloud/fastcgi-client.php", contents: read("fastcgi-client.php"), mode: 420 },
46121
+ { path: "tscloud/php-fpm.conf", contents: generatePhpFpmConfig(), mode: 420 }
46122
+ ];
46123
+ }
46124
+ function laravelServerlessEnvDefaults(opts = {}) {
46125
+ const cache2 = opts.cacheDriver ?? "dynamodb";
46126
+ return {
46127
+ APP_ENV: "production",
46128
+ LOG_CHANNEL: "stderr",
46129
+ CACHE_STORE: cache2,
46130
+ CACHE_DRIVER: cache2,
46131
+ SESSION_DRIVER: cache2,
46132
+ QUEUE_CONNECTION: "sqs",
46133
+ FILESYSTEM_DISK: "s3",
46134
+ VIEW_COMPILED_PATH: "/tmp/storage/framework/views",
46135
+ APP_SERVICES_CACHE: "/tmp/bootstrap/cache/services.php",
46136
+ APP_PACKAGES_CACHE: "/tmp/bootstrap/cache/packages.php",
46137
+ APP_CONFIG_CACHE: "/tmp/bootstrap/cache/config.php",
46138
+ APP_ROUTES_CACHE: "/tmp/bootstrap/cache/routes.php",
46139
+ APP_EVENTS_CACHE: "/tmp/bootstrap/cache/events.php"
46140
+ };
46141
+ }
46142
+ var LARAVEL_SERVERLESS_BUILD_STEPS = [
46143
+ "composer install --no-dev --optimize-autoloader --no-interaction",
46144
+ "php artisan config:cache",
46145
+ "php artisan route:cache",
46146
+ "php artisan event:cache",
46147
+ "php artisan view:cache"
46148
+ ];
46149
+ // src/serverless-php/layer-build.ts
46150
+ import { execFileSync } from "node:child_process";
46151
+ import { existsSync as existsSync5, mkdtempSync as mkdtempSync2, readdirSync as readdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
46152
+ import { tmpdir as tmpdir2 } from "node:os";
46153
+ import { join as join8, relative as relative2 } from "node:path";
46154
+ function* walk(dir) {
46155
+ for (const entry of readdirSync5(dir)) {
46156
+ const full = join8(dir, entry);
46157
+ if (statSync3(full).isDirectory())
46158
+ yield* walk(full);
46159
+ else
46160
+ yield full;
46161
+ }
46162
+ }
46163
+ function buildPhpRuntimeLayerZip(options = {}) {
46164
+ const architecture = options.architecture ?? "x86_64";
46165
+ const platform = options.platform ?? (architecture === "arm64" ? "linux/arm64" : "linux/amd64");
46166
+ const step = options.onStep ?? (() => {});
46167
+ const stage = mkdtempSync2(join8(tmpdir2(), "tscloud-php-layer-"));
46168
+ const imageTag = "tscloud-php-layer:build";
46169
+ try {
46170
+ writeFileSync4(join8(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
46171
+ step("Building PHP runtime image (docker)");
46172
+ execFileSync("docker", ["build", "--platform", platform, "-t", imageTag, stage], { stdio: "inherit" });
46173
+ step("Extracting /opt from image");
46174
+ const cid = execFileSync("docker", ["create", "--platform", platform, imageTag], { encoding: "utf-8" }).trim();
46175
+ const optDir = join8(stage, "opt");
46176
+ try {
46177
+ execFileSync("docker", ["cp", `${cid}:/opt/.`, optDir], { stdio: "inherit" });
46178
+ } finally {
46179
+ execFileSync("docker", ["rm", cid], { stdio: "ignore" });
46180
+ }
46181
+ if (!existsSync5(optDir))
46182
+ throw new Error("layer build produced no /opt directory");
46183
+ const entries = [];
46184
+ for (const file of walk(optDir)) {
46185
+ const rel = relative2(optDir, file).replace(/\\/g, "/");
46186
+ const mode = statSync3(file).mode & 73 ? 493 : 420;
46187
+ entries.push({ name: rel, data: readFileSync5(file), mode });
46188
+ }
46189
+ step("Injecting runtime assets");
46190
+ const assetPaths = new Set(phpRuntimeLayerAssets().map((a) => a.path));
46191
+ const filtered = entries.filter((e) => !assetPaths.has(e.name));
46192
+ for (const asset of phpRuntimeLayerAssets()) {
46193
+ filtered.push({ name: asset.path, data: asset.contents, mode: asset.mode });
46194
+ }
46195
+ step("Packaging layer ZIP");
46196
+ const zip2 = createZip(filtered);
46197
+ return { zip: zip2, architecture, fileCount: filtered.length };
46198
+ } finally {
46199
+ rmSync2(stage, { recursive: true, force: true });
46200
+ }
46201
+ }
46202
+ // src/serverless-php/package-php.ts
46203
+ import { execSync as execSync2 } from "node:child_process";
46204
+ import { createHash as createHash5 } from "node:crypto";
46205
+ import { readdirSync as readdirSync6, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
46206
+ import { join as join9, relative as relative3, resolve as resolve2 } from "node:path";
46207
+ var PHP_DEFAULT_EXCLUDES = [
46208
+ ".git",
46209
+ ".github",
46210
+ "node_modules",
46211
+ "tests",
46212
+ "storage/logs",
46213
+ "storage/framework/cache",
46214
+ "storage/framework/sessions",
46215
+ "storage/framework/views",
46216
+ ".env",
46217
+ ".env.local",
46218
+ ".vapor",
46219
+ ".ts-cloud",
46220
+ "dist-lambda"
46221
+ ];
46222
+ function isExcluded(rel, excludes) {
46223
+ return excludes.some((ex) => rel === ex || rel.startsWith(`${ex}/`));
46224
+ }
46225
+ function* walk2(dir, root, excludes) {
46226
+ for (const entry of readdirSync6(dir)) {
46227
+ const full = join9(dir, entry);
46228
+ const rel = relative3(root, full).replace(/\\/g, "/");
46229
+ if (isExcluded(rel, excludes))
46230
+ continue;
46231
+ if (statSync4(full).isDirectory())
46232
+ yield* walk2(full, root, excludes);
46233
+ else
46234
+ yield full;
46235
+ }
46236
+ }
46237
+ function runPhpBuildHooks(opts) {
46238
+ if (opts.skipBuild)
46239
+ return;
46240
+ const projectRoot = resolve2(opts.projectRoot ?? process.cwd());
46241
+ const steps = opts.app.build ?? LARAVEL_SERVERLESS_BUILD_STEPS;
46242
+ for (const step of steps) {
46243
+ opts.onStep?.(`build: ${step}`);
46244
+ execSync2(step, { stdio: "inherit", cwd: projectRoot });
46245
+ }
46246
+ }
46247
+ function collectPhpAppEntries(projectRoot, exclude = []) {
46248
+ const root = resolve2(projectRoot);
46249
+ const excludes = [...PHP_DEFAULT_EXCLUDES, ...exclude];
46250
+ const entries = [];
46251
+ for (const file of walk2(root, root, excludes)) {
46252
+ const rel = relative3(root, file).replace(/\\/g, "/");
46253
+ const executable = (statSync4(file).mode & 73) !== 0;
46254
+ entries.push({ name: rel, data: readFileSync6(file), mode: executable ? 493 : 420 });
46255
+ }
46256
+ return entries;
46257
+ }
46258
+ function packagePhpApp(opts) {
46259
+ const projectRoot = resolve2(opts.projectRoot ?? process.cwd());
46260
+ runPhpBuildHooks(opts);
46261
+ opts.onStep?.("packaging application tree");
46262
+ const entries = collectPhpAppEntries(projectRoot, opts.exclude);
46263
+ if (!entries.length)
46264
+ throw new Error(`No files to package under ${projectRoot}`);
46265
+ const zip2 = createZip(entries);
46266
+ const handler8 = opts.app.handlers?.http ?? "public/index.php";
46267
+ return {
46268
+ zip: zip2,
46269
+ sha256: createHash5("sha256").update(zip2).digest("hex"),
46270
+ handlers: {
46271
+ http: handler8,
46272
+ queue: opts.app.handlers?.queue ?? handler8,
46273
+ cli: opts.app.handlers?.cli ?? handler8
46274
+ },
46275
+ fileCount: entries.length
46276
+ };
46277
+ }
44956
46278
  // src/static-site/index.ts
44957
46279
  class StaticSiteManager {
44958
46280
  optimizations = new Map;
@@ -45421,6 +46743,7 @@ export {
45421
46743
  stackDependencyManager,
45422
46744
  signRequestAsync,
45423
46745
  signRequest,
46746
+ sha256,
45424
46747
  serviceMeshManager,
45425
46748
  sequence,
45426
46749
  senderReputationManager,
@@ -45430,18 +46753,26 @@ export {
45430
46753
  secretsManager,
45431
46754
  searchCommands,
45432
46755
  sanitizeName,
46756
+ runPhpBuildHooks,
46757
+ runBuildHooks,
45433
46758
  route53RoutingManager,
45434
46759
  route53ResolverManager,
46760
+ responseToResult,
45435
46761
  resourceManagementManager,
45436
46762
  resolveStorageBucketName,
45437
46763
  resolveSiteStackName,
45438
46764
  resolveSiteResourceName,
45439
46765
  resolveSiteBucketName,
46766
+ resolveServerlessAssetBucketName,
46767
+ resolveServerlessArtifactBucketName,
46768
+ resolveServerlessAppStackName,
45440
46769
  resolveRegion,
46770
+ resolveQueueNames,
45441
46771
  resolveProjectStackName,
45442
46772
  resolveDeployBucketName,
45443
46773
  resolveCredentials,
45444
46774
  resolveCloudProvider,
46775
+ resolveApp,
45445
46776
  requiresReplacement,
45446
46777
  replicaManager,
45447
46778
  regionPairManager,
@@ -45451,12 +46782,17 @@ export {
45451
46782
  processInChunks,
45452
46783
  previewNotifications,
45453
46784
  previewManager,
46785
+ phpRuntimeLayerAssets,
46786
+ phpLayerPackages,
46787
+ phpLayerBuildStage,
45454
46788
  performanceManager,
45455
46789
  parseXMLResponse,
45456
46790
  parseJSONResponse,
45457
46791
  parallelWithRetry,
45458
46792
  parallelMap,
45459
46793
  parallel,
46794
+ packageServerlessApp,
46795
+ packagePhpApp,
45460
46796
  organizationManager,
45461
46797
  networkSecurityManager,
45462
46798
  multiRegionManager,
@@ -45468,6 +46804,7 @@ export {
45468
46804
  makeAWSRequestAsync,
45469
46805
  makeAWSRequest,
45470
46806
  logsManager,
46807
+ laravelServerlessEnvDefaults,
45471
46808
  lambdaVersionsManager,
45472
46809
  lambdaVPCManager,
45473
46810
  lambdaLayersManager,
@@ -45518,6 +46855,8 @@ export {
45518
46855
  generateResourceName,
45519
46856
  generatePreviewWorkflow,
45520
46857
  generatePreviewPipeline,
46858
+ generatePhpLayerDockerfile,
46859
+ generatePhpFpmConfig,
45521
46860
  generateParallelConfig,
45522
46861
  generatePRPreviewWorkflow,
45523
46862
  generateMultiEnvWorkflow,
@@ -45532,7 +46871,9 @@ export {
45532
46871
  generateCrossAccountRoleCF,
45533
46872
  generateCostReportWorkflow,
45534
46873
  generateCleanupWorkflow,
46874
+ generateBootstrap,
45535
46875
  generateApprovalConfig,
46876
+ generateAppImageDockerfile,
45536
46877
  fromWebIdentity,
45537
46878
  fromSharedCredentials,
45538
46879
  fromEnvironment,
@@ -45556,6 +46897,7 @@ export {
45556
46897
  findChangedFiles,
45557
46898
  fifoQueueManager,
45558
46899
  extendPreset,
46900
+ eventToRequest,
45559
46901
  emailTemplateManager,
45560
46902
  emailAnalyticsManager,
45561
46903
  drManager,
@@ -45567,11 +46909,15 @@ export {
45567
46909
  defaultLocalConfig,
45568
46910
  databaseUserManager,
45569
46911
  crossRegionReferenceManager,
46912
+ createZip,
45570
46913
  createWordPressPreset,
45571
46914
  createTraditionalWebAppPreset,
45572
46915
  createStaticSitePreset,
46916
+ createServerlessNodePreset,
46917
+ createServerlessLaravelPreset,
45573
46918
  createS3Client,
45574
46919
  createRealtimeAppPreset,
46920
+ createQueueHandler,
45575
46921
  createPresignedUrlAsync,
45576
46922
  createPresignedUrl,
45577
46923
  createPreset,
@@ -45580,14 +46926,21 @@ export {
45580
46926
  createMockAWS,
45581
46927
  createMicroservicesPreset,
45582
46928
  createMLApiPreset,
46929
+ createLaravelPreset,
45583
46930
  createJamstackPreset,
46931
+ createHttpHandler,
46932
+ createHandlers,
45584
46933
  createFullStackAppPreset,
45585
46934
  createError,
45586
46935
  createDataPipelinePreset,
46936
+ createDashboardSite,
45587
46937
  createCredentialProvider,
46938
+ createCliHandler,
45588
46939
  createApiBackendPreset,
45589
46940
  containerRegistryManager,
46941
+ composeServerlessAppTemplate,
45590
46942
  composePresets,
46943
+ collectPhpAppEntries,
45591
46944
  cloudTrailManager,
45592
46945
  cloud_config_schema_default as cloudConfigSchema,
45593
46946
  clearSigningKeyCache,
@@ -45597,6 +46950,7 @@ export {
45597
46950
  certificateManager,
45598
46951
  categorizeChanges,
45599
46952
  canaryManager,
46953
+ buildPhpRuntimeLayerZip,
45600
46954
  buildOptimizationManager,
45601
46955
  buildCloudFormationTemplate,
45602
46956
  bounceComplaintHandler,
@@ -45606,6 +46960,7 @@ export {
45606
46960
  backupManager,
45607
46961
  awsConfigManager,
45608
46962
  autocomplete,
46963
+ artifactKey,
45609
46964
  analyzeStackDiff,
45610
46965
  abTestManager,
45611
46966
  XRayManager,
@@ -45663,6 +47018,8 @@ export {
45663
47018
  Permissions,
45664
47019
  PerformanceManager,
45665
47020
  ParameterStore,
47021
+ PHP_LAYER_EXTENSIONS,
47022
+ PHP_DEFAULT_EXCLUDES,
45666
47023
  OrganizationManager,
45667
47024
  NetworkSecurityManager,
45668
47025
  Network,
@@ -45683,6 +47040,7 @@ export {
45683
47040
  LambdaDestinationsManager,
45684
47041
  LambdaDLQManager,
45685
47042
  LambdaConcurrencyManager,
47043
+ LARAVEL_SERVERLESS_BUILD_STEPS,
45686
47044
  JobLoader,
45687
47045
  ImageScanningManager,
45688
47046
  HealthCheckManager,