@stacksjs/ts-cloud 0.5.4 → 0.5.6
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/bin/cli.js +460 -460
- package/dist/deploy/serverless-app.d.ts +6 -0
- package/dist/index.js +51 -15
- package/package.json +3 -3
|
@@ -45,6 +45,12 @@ export type CodeSource = {
|
|
|
45
45
|
kind: 'image';
|
|
46
46
|
imageUri: string;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* AWS caps the aggregate size of a function's environment variables at 4 KB.
|
|
50
|
+
* Check before the API call so we fail with an actionable message (which vars,
|
|
51
|
+
* how far over) instead of a cryptic `InvalidParameterValueException` mid-deploy.
|
|
52
|
+
*/
|
|
53
|
+
export declare function assertEnvWithinLimit(name: string, env: Record<string, string>): void;
|
|
48
54
|
export declare function deployServerlessApp(config: CloudConfig, environment: EnvironmentType, opts?: DeployServerlessOptions): Promise<void>;
|
|
49
55
|
export declare function redeployServerlessApp(config: CloudConfig, environment: EnvironmentType): Promise<void>;
|
|
50
56
|
export declare function rollbackServerlessApp(config: CloudConfig, environment: EnvironmentType): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -25845,16 +25845,21 @@ function resolveServerlessRuntime(app) {
|
|
|
25845
25845
|
layerEnvVar: "TSCLOUD_NODE_LAYER_ARN"
|
|
25846
25846
|
};
|
|
25847
25847
|
}
|
|
25848
|
-
function
|
|
25848
|
+
function resolveQueues(app, slug, env) {
|
|
25849
25849
|
if (app.queues === false)
|
|
25850
25850
|
return [];
|
|
25851
25851
|
if (app.queues === undefined || app.queues === true)
|
|
25852
|
-
return [`${slug}-${env}-default`];
|
|
25852
|
+
return [{ name: `${slug}-${env}-default` }];
|
|
25853
25853
|
return app.queues.map((q) => {
|
|
25854
|
-
|
|
25855
|
-
|
|
25854
|
+
if (typeof q === "string")
|
|
25855
|
+
return { name: `${slug}-${env}-${q}` };
|
|
25856
|
+
const [name, concurrency] = Object.entries(q)[0];
|
|
25857
|
+
return { name: `${slug}-${env}-${name}`, concurrency };
|
|
25856
25858
|
});
|
|
25857
25859
|
}
|
|
25860
|
+
function resolveQueueNames(app, slug, env) {
|
|
25861
|
+
return resolveQueues(app, slug, env).map((q) => q.name);
|
|
25862
|
+
}
|
|
25858
25863
|
function composeServerlessAppTemplate(opts) {
|
|
25859
25864
|
const { app, environment, handlers } = opts;
|
|
25860
25865
|
const slug = opts.config.project.slug;
|
|
@@ -25866,8 +25871,12 @@ function composeServerlessAppTemplate(opts) {
|
|
|
25866
25871
|
queue: `${slug}-${environment}-queue`,
|
|
25867
25872
|
cli: `${slug}-${environment}-cli`
|
|
25868
25873
|
};
|
|
25869
|
-
|
|
25874
|
+
if (app.gatewayVersion === 1)
|
|
25875
|
+
throw new Error("serverless app: `gatewayVersion: 1` (REST API) is not supported — ts-cloud uses API Gateway HTTP API (v2). Remove `gatewayVersion` or set it to 2.");
|
|
25876
|
+
const queues = resolveQueues(app, slug, environment);
|
|
25877
|
+
const queueNames = queues.map((q) => q.name);
|
|
25870
25878
|
const hasQueue = queueNames.length > 0;
|
|
25879
|
+
const logRetention = app.logRetention ?? 14;
|
|
25871
25880
|
const imageMode = app.packaging === "image";
|
|
25872
25881
|
const schedulerEnabled = (app.scheduler ?? "on") !== "off";
|
|
25873
25882
|
const cacheEnabled = (app.cache?.driver ?? "dynamodb") === "dynamodb";
|
|
@@ -25983,7 +25992,7 @@ function composeServerlessAppTemplate(opts) {
|
|
|
25983
25992
|
function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency, tmp = tmpStorage) {
|
|
25984
25993
|
resources[`${logicalId}LogGroup`] = {
|
|
25985
25994
|
Type: "AWS::Logs::LogGroup",
|
|
25986
|
-
Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays:
|
|
25995
|
+
Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: logRetention }
|
|
25987
25996
|
};
|
|
25988
25997
|
const codeProps = imageMode ? {
|
|
25989
25998
|
PackageType: "Image",
|
|
@@ -26124,19 +26133,21 @@ function composeServerlessAppTemplate(opts) {
|
|
|
26124
26133
|
MessageRetentionPeriod: 1209600
|
|
26125
26134
|
}
|
|
26126
26135
|
};
|
|
26127
|
-
|
|
26136
|
+
queues.forEach((q, i) => {
|
|
26128
26137
|
const qId = `AppQueue${i}`;
|
|
26138
|
+
const fnTimeout = app.queueTimeout ?? 120;
|
|
26129
26139
|
resources[qId] = {
|
|
26130
26140
|
Type: "AWS::SQS::Queue",
|
|
26131
26141
|
Properties: {
|
|
26132
|
-
QueueName:
|
|
26133
|
-
VisibilityTimeout: Math.
|
|
26142
|
+
QueueName: q.name,
|
|
26143
|
+
VisibilityTimeout: Math.min(43200, fnTimeout * 6),
|
|
26134
26144
|
RedrivePolicy: {
|
|
26135
26145
|
deadLetterTargetArn: Fn2.getAtt("AppQueueDlq", "Arn"),
|
|
26136
26146
|
maxReceiveCount: app.queueTries ?? 3
|
|
26137
26147
|
}
|
|
26138
26148
|
}
|
|
26139
26149
|
};
|
|
26150
|
+
const concurrency = q.concurrency ?? app.queueConcurrency;
|
|
26140
26151
|
resources[`${qId}Mapping`] = {
|
|
26141
26152
|
Type: "AWS::Lambda::EventSourceMapping",
|
|
26142
26153
|
Properties: {
|
|
@@ -26144,10 +26155,10 @@ function composeServerlessAppTemplate(opts) {
|
|
|
26144
26155
|
FunctionName: Fn2.ref("QueueFunction"),
|
|
26145
26156
|
BatchSize: 1,
|
|
26146
26157
|
FunctionResponseTypes: ["ReportBatchItemFailures"],
|
|
26147
|
-
...
|
|
26158
|
+
...concurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, concurrency) } } : {}
|
|
26148
26159
|
}
|
|
26149
26160
|
};
|
|
26150
|
-
outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${
|
|
26161
|
+
outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${q.name}`, Value: Fn2.ref(qId) };
|
|
26151
26162
|
});
|
|
26152
26163
|
}
|
|
26153
26164
|
if (schedulerEnabled) {
|
|
@@ -26262,7 +26273,7 @@ function composeServerlessAppTemplate(opts) {
|
|
|
26262
26273
|
...app.assetDomain ? {
|
|
26263
26274
|
Aliases: [app.assetDomain],
|
|
26264
26275
|
ViewerCertificate: {
|
|
26265
|
-
AcmCertificateArn: app.assetCertificateArn,
|
|
26276
|
+
AcmCertificateArn: app.assetCertificateArn ?? Fn2.ref("AssetsCertificate"),
|
|
26266
26277
|
SslSupportMethod: "sni-only",
|
|
26267
26278
|
MinimumProtocolVersion: "TLSv1.2_2021"
|
|
26268
26279
|
}
|
|
@@ -26271,8 +26282,20 @@ function composeServerlessAppTemplate(opts) {
|
|
|
26271
26282
|
}
|
|
26272
26283
|
};
|
|
26273
26284
|
if (app.assetDomain) {
|
|
26274
|
-
if (!app.assetCertificateArn)
|
|
26275
|
-
|
|
26285
|
+
if (!app.assetCertificateArn) {
|
|
26286
|
+
if (!app.hostedZoneId)
|
|
26287
|
+
throw new Error("serverless app: `assetDomain` requires either `assetCertificateArn` (a us-east-1 ACM cert) or `hostedZoneId` (to auto-issue + validate one). CloudFront only accepts certs from us-east-1.");
|
|
26288
|
+
if (region !== "us-east-1")
|
|
26289
|
+
throw new Error(`serverless app: auto-issuing an asset-domain cert needs a us-east-1 app (CloudFront certs must be us-east-1); this app is in ${region}. Supply a pre-issued us-east-1 \`assetCertificateArn\` instead.`);
|
|
26290
|
+
resources.AssetsCertificate = {
|
|
26291
|
+
Type: "AWS::CertificateManager::Certificate",
|
|
26292
|
+
Properties: {
|
|
26293
|
+
DomainName: app.assetDomain,
|
|
26294
|
+
ValidationMethod: "DNS",
|
|
26295
|
+
DomainValidationOptions: [{ DomainName: app.assetDomain, HostedZoneId: app.hostedZoneId }]
|
|
26296
|
+
}
|
|
26297
|
+
};
|
|
26298
|
+
}
|
|
26276
26299
|
if (app.hostedZoneId) {
|
|
26277
26300
|
resources.AssetsDomainRecord = {
|
|
26278
26301
|
Type: "AWS::Route53::RecordSet",
|
|
@@ -83282,7 +83305,15 @@ async function uploadAssets(s32, bucket, dir, prefix, includeDotfiles = false) {
|
|
|
83282
83305
|
}
|
|
83283
83306
|
return count;
|
|
83284
83307
|
}
|
|
83308
|
+
function assertEnvWithinLimit(name, env2) {
|
|
83309
|
+
const bytes = Object.entries(env2).reduce((n, [k, v]) => n + Buffer.byteLength(`${k}=${v}`, "utf8"), 0);
|
|
83310
|
+
if (bytes > 4096) {
|
|
83311
|
+
const biggest = Object.entries(env2).map(([k, v]) => [k, Buffer.byteLength(`${k}=${v}`, "utf8")]).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, n]) => `${k} (${n}B)`).join(", ");
|
|
83312
|
+
throw new Error(`${name}: environment variables total ${bytes}B, over AWS's 4096B limit (${bytes - 4096}B too large). ` + `Largest: ${biggest}. Move large/secret values out of env — store them in Secrets Manager and fetch at cold start.`);
|
|
83313
|
+
}
|
|
83314
|
+
}
|
|
83285
83315
|
async function applyFunction(lambda2, name, env2, code) {
|
|
83316
|
+
assertEnvWithinLimit(name, env2);
|
|
83286
83317
|
await withConflictRetry(() => lambda2.updateFunctionConfiguration({ FunctionName: name, Environment: { Variables: env2 } }));
|
|
83287
83318
|
await lambda2.waitForFunctionActive(name, 120);
|
|
83288
83319
|
const codeParams = code.kind === "image" ? { FunctionName: name, ImageUri: code.imageUri } : { FunctionName: name, S3Bucket: code.bucket, S3Key: code.key };
|
|
@@ -83437,6 +83468,7 @@ async function deployServerlessApp(config6, environment, opts = {}) {
|
|
|
83437
83468
|
code: codeSource,
|
|
83438
83469
|
previousSha: prior?.sha,
|
|
83439
83470
|
previousCode: prior?.code,
|
|
83471
|
+
previousFunctionEnv: prior?.functionEnv,
|
|
83440
83472
|
functionEnv,
|
|
83441
83473
|
functionNames: {
|
|
83442
83474
|
http: composed.functionNames.http,
|
|
@@ -83508,14 +83540,17 @@ async function rollbackServerlessApp(config6, environment) {
|
|
|
83508
83540
|
if (!name)
|
|
83509
83541
|
continue;
|
|
83510
83542
|
step(`Restoring ${mode} (${name})`);
|
|
83511
|
-
|
|
83543
|
+
const env2 = release.previousFunctionEnv?.[mode] ?? release.functionEnv[mode] ?? {};
|
|
83544
|
+
await applyFunction(lambda2, name, env2, release.previousCode);
|
|
83512
83545
|
}
|
|
83513
83546
|
await writeRelease(s32, ctx.artifactBucket, ctx.slug, environment, {
|
|
83514
83547
|
...release,
|
|
83515
83548
|
sha: release.previousSha ?? release.sha,
|
|
83516
83549
|
code: release.previousCode,
|
|
83550
|
+
functionEnv: release.previousFunctionEnv ?? release.functionEnv,
|
|
83517
83551
|
previousSha: undefined,
|
|
83518
83552
|
previousCode: undefined,
|
|
83553
|
+
previousFunctionEnv: undefined,
|
|
83519
83554
|
timestamp: new Date().toISOString()
|
|
83520
83555
|
});
|
|
83521
83556
|
success("Rollback complete");
|
|
@@ -86720,6 +86755,7 @@ export {
|
|
|
86720
86755
|
resolveServerlessArtifactBucketName,
|
|
86721
86756
|
resolveServerlessAppStackName,
|
|
86722
86757
|
resolveRegion,
|
|
86758
|
+
resolveQueues,
|
|
86723
86759
|
resolveQueueNames,
|
|
86724
86760
|
resolveProjectStackName,
|
|
86725
86761
|
resolveObjectStorage,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/ts-cloud",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.6",
|
|
5
5
|
"description": "A lightweight, performant infrastructure-as-code library and CLI for deploying both server-based (EC2) and serverless applications.",
|
|
6
6
|
"author": "Chris Breuer <chris@stacksjs.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -89,8 +89,8 @@
|
|
|
89
89
|
"test": "bun test"
|
|
90
90
|
},
|
|
91
91
|
"dependencies": {
|
|
92
|
-
"@ts-cloud/aws-types": "0.5.
|
|
93
|
-
"@ts-cloud/core": "0.5.
|
|
92
|
+
"@ts-cloud/aws-types": "0.5.6",
|
|
93
|
+
"@ts-cloud/core": "0.5.6",
|
|
94
94
|
"@stacksjs/ts-xml": "^0.1.0"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|