@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/drivers/types.d.ts +20 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1361 -3
- package/dist/modules/cdn.d.ts +1 -1
- package/dist/modules/compute.d.ts +9 -2
- package/dist/presets/dashboard.d.ts +26 -0
- package/dist/presets/index.d.ts +3 -0
- package/dist/presets/laravel.d.ts +36 -0
- package/dist/presets/serverless-laravel.d.ts +38 -0
- package/dist/presets/serverless-node.d.ts +36 -0
- package/dist/serverless/adapter.test.d.ts +1 -0
- package/dist/serverless/app-image.d.ts +22 -0
- package/dist/serverless/app-image.test.d.ts +1 -0
- package/dist/serverless/bootstrap.d.ts +13 -0
- package/dist/serverless/composer.d.ts +43 -0
- package/dist/serverless/composer.test.d.ts +1 -0
- package/dist/serverless/index.d.ts +10 -0
- package/dist/serverless/naming.test.d.ts +1 -0
- package/dist/serverless/package.d.ts +46 -0
- package/dist/serverless/package.test.d.ts +1 -0
- package/dist/serverless/runtime/adapter.d.ts +122 -0
- package/dist/serverless/zip.d.ts +25 -0
- package/dist/serverless/zip.test.d.ts +1 -0
- package/dist/serverless-php/dockerfile.d.ts +36 -0
- package/dist/serverless-php/index.d.ts +10 -0
- package/dist/serverless-php/layer-build.d.ts +33 -0
- package/dist/serverless-php/package-php.d.ts +43 -0
- package/dist/serverless-php/php-fpm-conf.d.ts +16 -0
- package/dist/serverless-php/runtime-assets.d.ts +34 -0
- package/dist/serverless-php/serverless-php.test.d.ts +1 -0
- package/dist/stack-naming.d.ts +17 -0
- package/dist/types.d.ts +611 -5
- package/package.json +2 -2
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", ".
|
|
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
|
|
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,
|