@awsless/cli 0.0.46-next.0 → 0.0.46-next.10
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/app.json +1 -1
- package/dist/app.stage.json +1 -1
- package/dist/bin.js +774 -285
- package/dist/build-json-schema.js +109 -8
- package/dist/handlers/{bundle.mjs → bundle.js} +2 -138
- package/dist/handlers/{on-failure.mjs → on-failure.js} +86 -8
- package/dist/handlers/{pubsub-server.mjs → pubsub-server.js} +31410 -37611
- package/dist/handlers/{rpc.mjs → rpc.js} +0 -7
- package/dist/stack.json +1 -1
- package/dist/stack.stage.json +1 -1
- package/package.json +13 -13
- /package/dist/handlers/{icon.mjs → icon.js} +0 -0
- /package/dist/handlers/{image.mjs → image.js} +0 -0
- /package/dist/handlers/{on-error-log.mjs → on-error-log.js} +0 -0
- /package/dist/handlers/{pubsub-publisher.mjs → pubsub-publisher.js} +0 -0
package/dist/bin.js
CHANGED
|
@@ -173,7 +173,7 @@ import { DynamoDBClient as DynamoDBClient2, migrate } from "@awsless/dynamodb";
|
|
|
173
173
|
// src/util/deployment.ts
|
|
174
174
|
import { CloudFrontClient as CloudFrontClient2 } from "@aws-sdk/client-cloudfront";
|
|
175
175
|
import { CloudFrontKeyValueStoreClient as CloudFrontKeyValueStoreClient2 } from "@aws-sdk/client-cloudfront-keyvaluestore";
|
|
176
|
-
import { GetFunctionCommand, LambdaClient as LambdaClient3 } from "@aws-sdk/client-lambda";
|
|
176
|
+
import { DeleteFunctionCommand, GetFunctionCommand, LambdaClient as LambdaClient3 } from "@aws-sdk/client-lambda";
|
|
177
177
|
import {
|
|
178
178
|
define,
|
|
179
179
|
deleteItem,
|
|
@@ -187,7 +187,7 @@ import {
|
|
|
187
187
|
string,
|
|
188
188
|
updateItem
|
|
189
189
|
} from "@awsless/dynamodb";
|
|
190
|
-
import {
|
|
190
|
+
import { isAfter, subHours } from "date-fns";
|
|
191
191
|
import { userInfo as userInfo2 } from "os";
|
|
192
192
|
|
|
193
193
|
// src/formation/cloudfront-kvs.ts
|
|
@@ -290,17 +290,16 @@ var getRouteStoreArn = async (cloudfront, name) => {
|
|
|
290
290
|
return;
|
|
291
291
|
}
|
|
292
292
|
};
|
|
293
|
-
var stageRoutes = async (kvs, state2
|
|
293
|
+
var stageRoutes = async (kvs, state2) => {
|
|
294
294
|
const routes = sortRoutes(state2.routes);
|
|
295
295
|
const table2 = getRouteTableId(routes);
|
|
296
|
-
const priorTable = prior ? getRouteTableId(sortRoutes(prior.routes)) : void 0;
|
|
297
296
|
const deployment = {
|
|
298
297
|
id: state2.deploymentId,
|
|
299
298
|
table: table2,
|
|
300
299
|
functionVersion: state2.functionVersion
|
|
301
300
|
};
|
|
302
301
|
const mutations = [
|
|
303
|
-
...
|
|
302
|
+
...getTableMutations(table2, routes),
|
|
304
303
|
{
|
|
305
304
|
type: "put",
|
|
306
305
|
key: `${DEPLOY_KEY_PREFIX}${deployment.id}`,
|
|
@@ -324,9 +323,7 @@ var createCloudFrontKvsProvider = ({ credentials, region }) => {
|
|
|
324
323
|
return stageRoutes(kvs, routeDeploymentInputSchema.parse(props.state));
|
|
325
324
|
},
|
|
326
325
|
async updateResource(props) {
|
|
327
|
-
|
|
328
|
-
const prior = routeDeploymentInputSchema.parse(props.priorState);
|
|
329
|
-
return stageRoutes(kvs, state2, prior.storeArn === state2.storeArn ? prior : void 0);
|
|
326
|
+
return stageRoutes(kvs, routeDeploymentInputSchema.parse(props.proposedState));
|
|
330
327
|
}
|
|
331
328
|
}
|
|
332
329
|
});
|
|
@@ -359,6 +356,42 @@ var pruneStoreDeployments = async (kvs, storeArn, deploymentIds) => {
|
|
|
359
356
|
});
|
|
360
357
|
};
|
|
361
358
|
|
|
359
|
+
// src/util/git.ts
|
|
360
|
+
import { execFileSync, execSync } from "child_process";
|
|
361
|
+
var git = (command) => {
|
|
362
|
+
try {
|
|
363
|
+
return execSync(`git ${command}`, { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" }).trim();
|
|
364
|
+
} catch {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
var checkedOutBranch = () => {
|
|
369
|
+
return git("rev-parse --abbrev-ref HEAD");
|
|
370
|
+
};
|
|
371
|
+
var currentBranch = () => {
|
|
372
|
+
const branch = checkedOutBranch();
|
|
373
|
+
if (branch && branch !== "HEAD") {
|
|
374
|
+
return branch;
|
|
375
|
+
}
|
|
376
|
+
return process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_REF_NAME || process.env.BRANCH_NAME || branch;
|
|
377
|
+
};
|
|
378
|
+
var currentCommit = () => {
|
|
379
|
+
return git("rev-parse HEAD");
|
|
380
|
+
};
|
|
381
|
+
var currentCommitMessage = () => {
|
|
382
|
+
return git("log -1 --pretty=%s");
|
|
383
|
+
};
|
|
384
|
+
var isCommitMerged = (commit, branch) => {
|
|
385
|
+
try {
|
|
386
|
+
execFileSync("git", ["merge-base", "--is-ancestor", commit, branch], {
|
|
387
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
388
|
+
});
|
|
389
|
+
return true;
|
|
390
|
+
} catch {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
362
395
|
// src/util/lambda.ts
|
|
363
396
|
import {
|
|
364
397
|
CreateAliasCommand,
|
|
@@ -366,6 +399,7 @@ import {
|
|
|
366
399
|
DeleteFunctionUrlConfigCommand,
|
|
367
400
|
GetAliasCommand,
|
|
368
401
|
LambdaClient,
|
|
402
|
+
ListAliasesCommand,
|
|
369
403
|
UpdateAliasCommand,
|
|
370
404
|
UpdateFunctionCodeCommand
|
|
371
405
|
} from "@aws-sdk/client-lambda";
|
|
@@ -386,6 +420,22 @@ var getLambdaAlias = async (lambda, functionName, name) => {
|
|
|
386
420
|
return;
|
|
387
421
|
}
|
|
388
422
|
};
|
|
423
|
+
var listLambdaAliases = async (lambda, functionName, functionVersion) => {
|
|
424
|
+
const aliases = [];
|
|
425
|
+
let marker;
|
|
426
|
+
do {
|
|
427
|
+
const result = await lambda.send(
|
|
428
|
+
new ListAliasesCommand({
|
|
429
|
+
FunctionName: functionName,
|
|
430
|
+
FunctionVersion: functionVersion,
|
|
431
|
+
Marker: marker
|
|
432
|
+
})
|
|
433
|
+
);
|
|
434
|
+
aliases.push(...result.Aliases ?? []);
|
|
435
|
+
marker = result.NextMarker;
|
|
436
|
+
} while (marker);
|
|
437
|
+
return aliases;
|
|
438
|
+
};
|
|
389
439
|
var upsertLambdaAlias = async (lambda, props) => {
|
|
390
440
|
const input = {
|
|
391
441
|
FunctionName: props.functionName,
|
|
@@ -528,6 +578,8 @@ import {
|
|
|
528
578
|
CreateAliasCommand as CreateAliasCommand2,
|
|
529
579
|
CreateFunctionUrlConfigCommand,
|
|
530
580
|
GetFunctionUrlConfigCommand,
|
|
581
|
+
RemovePermissionCommand,
|
|
582
|
+
UpdateFunctionUrlConfigCommand,
|
|
531
583
|
LambdaClient as LambdaClient2,
|
|
532
584
|
PutFunctionEventInvokeConfigCommand
|
|
533
585
|
} from "@aws-sdk/client-lambda";
|
|
@@ -559,7 +611,8 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
559
611
|
deploymentId: z2.string(),
|
|
560
612
|
functionName: z2.string(),
|
|
561
613
|
functionVersion: z2.string(),
|
|
562
|
-
onFailureArn: z2.string()
|
|
614
|
+
onFailureArn: z2.string(),
|
|
615
|
+
sourceAccount: z2.string().optional()
|
|
563
616
|
});
|
|
564
617
|
const bundleDeploymentStateSchema = bundleDeploymentInputSchema.extend({
|
|
565
618
|
deploymentAlias: z2.string(),
|
|
@@ -596,14 +649,15 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
596
649
|
})
|
|
597
650
|
);
|
|
598
651
|
};
|
|
599
|
-
const
|
|
652
|
+
const createDeploymentUrl = async (functionName, alias, sourceAccount) => {
|
|
653
|
+
const authType = sourceAccount ? "AWS_IAM" : "NONE";
|
|
600
654
|
let url;
|
|
601
655
|
try {
|
|
602
656
|
const result = await lambda.send(
|
|
603
657
|
new CreateFunctionUrlConfigCommand({
|
|
604
658
|
FunctionName: functionName,
|
|
605
659
|
Qualifier: alias,
|
|
606
|
-
AuthType:
|
|
660
|
+
AuthType: authType
|
|
607
661
|
})
|
|
608
662
|
);
|
|
609
663
|
url = result.FunctionUrl;
|
|
@@ -612,21 +666,39 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
612
666
|
throw error;
|
|
613
667
|
}
|
|
614
668
|
const result = await lambda.send(
|
|
615
|
-
new
|
|
669
|
+
new UpdateFunctionUrlConfigCommand({
|
|
616
670
|
FunctionName: functionName,
|
|
617
|
-
Qualifier: alias
|
|
671
|
+
Qualifier: alias,
|
|
672
|
+
AuthType: authType
|
|
618
673
|
})
|
|
619
674
|
);
|
|
620
675
|
url = result.FunctionUrl;
|
|
621
676
|
}
|
|
622
|
-
const permissions = [
|
|
677
|
+
const permissions = sourceAccount ? [
|
|
678
|
+
{
|
|
679
|
+
StatementId: "cloudfront-url",
|
|
680
|
+
Principal: "cloudfront.amazonaws.com",
|
|
681
|
+
SourceAccount: sourceAccount,
|
|
682
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
683
|
+
FunctionUrlAuthType: "AWS_IAM"
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
StatementId: "cloudfront-invoke",
|
|
687
|
+
Principal: "cloudfront.amazonaws.com",
|
|
688
|
+
SourceAccount: sourceAccount,
|
|
689
|
+
Action: "lambda:InvokeFunction",
|
|
690
|
+
InvokedViaFunctionUrl: true
|
|
691
|
+
}
|
|
692
|
+
] : [
|
|
623
693
|
{
|
|
624
694
|
StatementId: "public-url",
|
|
695
|
+
Principal: "*",
|
|
625
696
|
Action: "lambda:InvokeFunctionUrl",
|
|
626
697
|
FunctionUrlAuthType: "NONE"
|
|
627
698
|
},
|
|
628
699
|
{
|
|
629
700
|
StatementId: "public-invoke",
|
|
701
|
+
Principal: "*",
|
|
630
702
|
Action: "lambda:InvokeFunction",
|
|
631
703
|
InvokedViaFunctionUrl: true
|
|
632
704
|
}
|
|
@@ -637,7 +709,6 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
637
709
|
new AddPermissionCommand({
|
|
638
710
|
FunctionName: functionName,
|
|
639
711
|
Qualifier: alias,
|
|
640
|
-
Principal: "*",
|
|
641
712
|
...permission
|
|
642
713
|
})
|
|
643
714
|
);
|
|
@@ -647,6 +718,23 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
647
718
|
}
|
|
648
719
|
}
|
|
649
720
|
}
|
|
721
|
+
if (sourceAccount) {
|
|
722
|
+
for (const statementId of ["public-url", "public-invoke"]) {
|
|
723
|
+
try {
|
|
724
|
+
await lambda.send(
|
|
725
|
+
new RemovePermissionCommand({
|
|
726
|
+
FunctionName: functionName,
|
|
727
|
+
Qualifier: alias,
|
|
728
|
+
StatementId: statementId
|
|
729
|
+
})
|
|
730
|
+
);
|
|
731
|
+
} catch (error) {
|
|
732
|
+
if (!isError(error, "ResourceNotFoundException")) {
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
650
738
|
return url;
|
|
651
739
|
};
|
|
652
740
|
const createBundleDeployment = async (state2) => {
|
|
@@ -658,7 +746,7 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
658
746
|
name: deploymentAlias
|
|
659
747
|
});
|
|
660
748
|
await configureVersion(state2);
|
|
661
|
-
const url = await
|
|
749
|
+
const url = await createDeploymentUrl(state2.functionName, deploymentAlias, state2.sourceAccount);
|
|
662
750
|
return {
|
|
663
751
|
...state2,
|
|
664
752
|
deploymentAlias,
|
|
@@ -769,7 +857,21 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
769
857
|
deploymentAliases.push(deploymentAlias);
|
|
770
858
|
}
|
|
771
859
|
await configureVersion(proposed);
|
|
772
|
-
const url = await
|
|
860
|
+
const url = await createDeploymentUrl(proposed.functionName, deploymentAlias, proposed.sourceAccount);
|
|
861
|
+
if (proposed.sourceAccount && prior.sourceAccount !== proposed.sourceAccount) {
|
|
862
|
+
for (const name of deploymentAliases) {
|
|
863
|
+
if (name === deploymentAlias) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
await createDeploymentUrl(proposed.functionName, name, proposed.sourceAccount);
|
|
868
|
+
} catch (error) {
|
|
869
|
+
if (!isError(error, "ResourceNotFoundException")) {
|
|
870
|
+
throw error;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
773
875
|
return {
|
|
774
876
|
...proposed,
|
|
775
877
|
deploymentAlias,
|
|
@@ -1134,7 +1236,7 @@ var pullRemoteState = async (app, stateBackend) => {
|
|
|
1134
1236
|
await rm(file);
|
|
1135
1237
|
}
|
|
1136
1238
|
} else {
|
|
1137
|
-
await writeFile(file, JSON.stringify(state2, void 0, 2));
|
|
1239
|
+
await writeFile(file, JSON.stringify(state2, void 0, 2), { mode: 384 });
|
|
1138
1240
|
}
|
|
1139
1241
|
};
|
|
1140
1242
|
var pushRemoteState = async (app, stateBackend) => {
|
|
@@ -1148,16 +1250,6 @@ var pushRemoteState = async (app, stateBackend) => {
|
|
|
1148
1250
|
var slugifyBranch = (branch) => {
|
|
1149
1251
|
return (branch ?? "").replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "local";
|
|
1150
1252
|
};
|
|
1151
|
-
var git = (command) => {
|
|
1152
|
-
try {
|
|
1153
|
-
return execSync(`git ${command}`, { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" }).trim();
|
|
1154
|
-
} catch {
|
|
1155
|
-
return;
|
|
1156
|
-
}
|
|
1157
|
-
};
|
|
1158
|
-
var isCommitMerged = (commit, branch) => {
|
|
1159
|
-
return git(`merge-base --is-ancestor ${JSON.stringify(commit)} ${JSON.stringify(branch)}`) !== void 0;
|
|
1160
|
-
};
|
|
1161
1253
|
var table = define("awsless-deployments", {
|
|
1162
1254
|
hash: "appId",
|
|
1163
1255
|
sort: "id",
|
|
@@ -1177,10 +1269,13 @@ var table = define("awsless-deployments", {
|
|
|
1177
1269
|
var deploymentsTable = table;
|
|
1178
1270
|
var latestBranchDeployment = async (client, appId, branch) => {
|
|
1179
1271
|
const items = await listDeployments(client, appId, branch);
|
|
1180
|
-
return items.reduce(
|
|
1272
|
+
return items.reduce(
|
|
1273
|
+
(latest, item) => (latest?.seq ?? 0) >= item.seq ? latest : item,
|
|
1274
|
+
void 0
|
|
1275
|
+
);
|
|
1181
1276
|
};
|
|
1182
1277
|
var claimDeployment = async (props) => {
|
|
1183
|
-
const branch = slugifyBranch(
|
|
1278
|
+
const branch = slugifyBranch(currentBranch());
|
|
1184
1279
|
while (true) {
|
|
1185
1280
|
const latest = await latestBranchDeployment(props.client, props.appId, branch);
|
|
1186
1281
|
const seq = (latest?.seq ?? 0) + 1;
|
|
@@ -1191,8 +1286,8 @@ var claimDeployment = async (props) => {
|
|
|
1191
1286
|
seq,
|
|
1192
1287
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1193
1288
|
user: userInfo2().username,
|
|
1194
|
-
commit:
|
|
1195
|
-
message:
|
|
1289
|
+
commit: currentCommit(),
|
|
1290
|
+
message: currentCommitMessage()
|
|
1196
1291
|
};
|
|
1197
1292
|
try {
|
|
1198
1293
|
await putItem(table, deployment, {
|
|
@@ -1209,7 +1304,7 @@ var claimDeployment = async (props) => {
|
|
|
1209
1304
|
}
|
|
1210
1305
|
};
|
|
1211
1306
|
var currentDeployment = async (client, appId) => {
|
|
1212
|
-
return latestBranchDeployment(client, appId, slugifyBranch(
|
|
1307
|
+
return latestBranchDeployment(client, appId, slugifyBranch(currentBranch()));
|
|
1213
1308
|
};
|
|
1214
1309
|
var getDeployment = async (client, appId, id) => {
|
|
1215
1310
|
return getItem(table, { appId, id }, { client });
|
|
@@ -1259,6 +1354,63 @@ var markPromoted = async (client, appId, id) => {
|
|
|
1259
1354
|
var removeDeployment = async (client, appId, id) => {
|
|
1260
1355
|
await deleteItem(table, { appId, id }, { client });
|
|
1261
1356
|
};
|
|
1357
|
+
var BUSY_WINDOW_HOURS = 24;
|
|
1358
|
+
var isDeploymentBusy = (item, now = /* @__PURE__ */ new Date()) => {
|
|
1359
|
+
return !item.functionVersion && isAfter(new Date(item.createdAt), subHours(now, BUSY_WINDOW_HOURS));
|
|
1360
|
+
};
|
|
1361
|
+
var selectPrunableDeployments = (items, liveId, options) => {
|
|
1362
|
+
const rollbackTarget = items.filter((item) => item.promotedAt && item.id !== liveId).sort((a, b) => b.promotedAt.localeCompare(a.promotedAt))[0];
|
|
1363
|
+
const keep = Math.max(1, Number(options.keep) || 10);
|
|
1364
|
+
const mainSlug = slugifyBranch(options.main);
|
|
1365
|
+
const keptMain = new Set(
|
|
1366
|
+
items.filter((item) => item.branch === mainSlug && item.functionVersion).map((item) => item.seq).sort((a, b) => b - a).slice(0, keep)
|
|
1367
|
+
);
|
|
1368
|
+
return items.filter((item) => {
|
|
1369
|
+
if (item.id === liveId || item.id === rollbackTarget?.id || isDeploymentBusy(item)) {
|
|
1370
|
+
return false;
|
|
1371
|
+
}
|
|
1372
|
+
if (options.branch) {
|
|
1373
|
+
return item.branch === slugifyBranch(options.branch);
|
|
1374
|
+
}
|
|
1375
|
+
if (!item.functionVersion) {
|
|
1376
|
+
return true;
|
|
1377
|
+
}
|
|
1378
|
+
if (item.branch === mainSlug) {
|
|
1379
|
+
return !keptMain.has(item.seq);
|
|
1380
|
+
}
|
|
1381
|
+
return item.commit ? isCommitMerged(item.commit, options.main) : false;
|
|
1382
|
+
});
|
|
1383
|
+
};
|
|
1384
|
+
var pruneFunctionVersion = async (lambda, functionName, version) => {
|
|
1385
|
+
for (const alias of await listLambdaAliases(lambda, functionName, version)) {
|
|
1386
|
+
if (alias.Name && alias.Name !== LIVE_LAMBDA_ALIAS) {
|
|
1387
|
+
await deleteLambdaAlias(lambda, functionName, alias.Name);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
try {
|
|
1391
|
+
await lambda.send(
|
|
1392
|
+
new DeleteFunctionCommand({
|
|
1393
|
+
FunctionName: functionName,
|
|
1394
|
+
Qualifier: version
|
|
1395
|
+
})
|
|
1396
|
+
);
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
if (!isError(error, "ResourceNotFoundException")) {
|
|
1399
|
+
throw error;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
var selectPrunableVersions = async (props) => {
|
|
1404
|
+
const surviving = props.items.filter((item) => !props.prunable.includes(item));
|
|
1405
|
+
const kept = new Set(surviving.map((item) => item.functionVersion));
|
|
1406
|
+
const live = await getLambdaAlias(props.lambda, props.functionName, LIVE_LAMBDA_ALIAS);
|
|
1407
|
+
if (live?.FunctionVersion) {
|
|
1408
|
+
kept.add(live.FunctionVersion);
|
|
1409
|
+
}
|
|
1410
|
+
return new Set(
|
|
1411
|
+
props.prunable.map((item) => item.functionVersion).filter((version) => Boolean(version && !kept.has(version)))
|
|
1412
|
+
);
|
|
1413
|
+
};
|
|
1262
1414
|
var readLiveDeploymentId = async (lambda, functionName) => {
|
|
1263
1415
|
return (await getLambdaAlias(lambda, functionName, LIVE_LAMBDA_ALIAS))?.Description || void 0;
|
|
1264
1416
|
};
|
|
@@ -1275,21 +1427,21 @@ var readDeployedFunctionVersion = (state2) => {
|
|
|
1275
1427
|
return;
|
|
1276
1428
|
};
|
|
1277
1429
|
var formatDeploymentSummary = (props) => {
|
|
1278
|
-
|
|
1279
|
-
let lambdaUrl;
|
|
1430
|
+
const previewUrls = /* @__PURE__ */ new Map();
|
|
1280
1431
|
for (const [urn, node] of readStateNodes(props.state)) {
|
|
1281
1432
|
if (node.type === "aws_cloudfront_distribution" && urn.endsWith(":{preview}")) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1433
|
+
const router = urn.match(/router:\{([^}]+)\}/)?.[1];
|
|
1434
|
+
if (router) {
|
|
1435
|
+
previewUrls.set(router, `https://${node.output.domainName}`);
|
|
1436
|
+
}
|
|
1286
1437
|
}
|
|
1287
1438
|
}
|
|
1288
|
-
return Object.keys(props.appConfig.defaults.router ?? {}).map((routerId
|
|
1439
|
+
return Object.keys(props.appConfig.defaults.router ?? {}).map((routerId) => {
|
|
1440
|
+
const previewUrl = previewUrls.get(routerId);
|
|
1289
1441
|
return [
|
|
1290
1442
|
`${routerId}: deployment #${props.id}`,
|
|
1291
|
-
|
|
1292
|
-
|
|
1443
|
+
previewUrl,
|
|
1444
|
+
previewUrl ? `${previewUrl}/?awsless-deployment=${props.id}` : void 0
|
|
1293
1445
|
].filter(Boolean).join("\n");
|
|
1294
1446
|
});
|
|
1295
1447
|
};
|
|
@@ -1371,7 +1523,10 @@ var promoteDeployment = async (props) => {
|
|
|
1371
1523
|
];
|
|
1372
1524
|
const failures = (await Promise.allSettled(rollback2)).filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
1373
1525
|
if (failures.length > 0) {
|
|
1374
|
-
throw new AggregateError(
|
|
1526
|
+
throw new AggregateError(
|
|
1527
|
+
[error, ...failures],
|
|
1528
|
+
`Deployment promotion failed and couldn't be fully reverted.`
|
|
1529
|
+
);
|
|
1375
1530
|
}
|
|
1376
1531
|
throw error;
|
|
1377
1532
|
}
|
|
@@ -1418,6 +1573,9 @@ var activateDeployment = async (props) => {
|
|
|
1418
1573
|
});
|
|
1419
1574
|
return id;
|
|
1420
1575
|
};
|
|
1576
|
+
var promoteAppDeployment = (props) => {
|
|
1577
|
+
return activateDeployment({ ...props, rejectStale: true });
|
|
1578
|
+
};
|
|
1421
1579
|
var rollbackAppDeployment = (props) => {
|
|
1422
1580
|
return withAppReleaseLock(props.appConfig, () => activateDeployment(props));
|
|
1423
1581
|
};
|
|
@@ -1830,7 +1988,7 @@ var CodeSchema = z14.union([
|
|
|
1830
1988
|
var FnSchema = z14.object({
|
|
1831
1989
|
code: CodeSchema,
|
|
1832
1990
|
handler: HandlerSchema.optional()
|
|
1833
|
-
});
|
|
1991
|
+
}).strict();
|
|
1834
1992
|
var FunctionSchema = z14.union([
|
|
1835
1993
|
LocalFileSchema.transform((code) => ({
|
|
1836
1994
|
code
|
|
@@ -2076,6 +2234,66 @@ var InstanceDefaultSchema = z19.object({
|
|
|
2076
2234
|
// src/feature/router/schema.ts
|
|
2077
2235
|
import { days as days3, minutes as minutes2, parse as parse3 } from "@awsless/duration";
|
|
2078
2236
|
import { z as z20 } from "zod";
|
|
2237
|
+
|
|
2238
|
+
// src/feature/router/pattern.ts
|
|
2239
|
+
var PARAM_TOKEN = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}|\*/g;
|
|
2240
|
+
var escapeRegex = (value) => {
|
|
2241
|
+
return value.replace(/[|\\{}()[\]^$+*?.\-]/g, "\\$&");
|
|
2242
|
+
};
|
|
2243
|
+
var compileRoutePattern = (pattern) => {
|
|
2244
|
+
if (!pattern.startsWith("/")) {
|
|
2245
|
+
throw new ExpectedError(`Route pattern "${pattern}" must start with a slash (/)`);
|
|
2246
|
+
}
|
|
2247
|
+
if (pattern === "/*") {
|
|
2248
|
+
return { key: pattern };
|
|
2249
|
+
}
|
|
2250
|
+
const params = [];
|
|
2251
|
+
let regex = "";
|
|
2252
|
+
let stars = 0;
|
|
2253
|
+
let last = 0;
|
|
2254
|
+
let token;
|
|
2255
|
+
PARAM_TOKEN.lastIndex = 0;
|
|
2256
|
+
while (token = PARAM_TOKEN.exec(pattern)) {
|
|
2257
|
+
regex += escapeRegex(pattern.slice(last, token.index));
|
|
2258
|
+
const param = token[1];
|
|
2259
|
+
if (param) {
|
|
2260
|
+
if (params.includes(param)) {
|
|
2261
|
+
throw new ExpectedError(`Duplicate param "${param}" in route pattern "${pattern}"`);
|
|
2262
|
+
}
|
|
2263
|
+
params.push(param);
|
|
2264
|
+
regex += "([^/]+)";
|
|
2265
|
+
} else {
|
|
2266
|
+
stars++;
|
|
2267
|
+
regex += ".*";
|
|
2268
|
+
}
|
|
2269
|
+
last = PARAM_TOKEN.lastIndex;
|
|
2270
|
+
}
|
|
2271
|
+
if (params.length === 0 && stars === 0) {
|
|
2272
|
+
return { key: pattern };
|
|
2273
|
+
}
|
|
2274
|
+
regex += escapeRegex(pattern.slice(last));
|
|
2275
|
+
const root2 = pattern.split("/")[1] ?? "";
|
|
2276
|
+
if (root2 === "" || root2.includes("*") || root2.includes("{")) {
|
|
2277
|
+
throw new ExpectedError(
|
|
2278
|
+
`The first path segment of route pattern "${pattern}" must be static when the pattern contains params or wildcards.`
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
if (root2.includes(".")) {
|
|
2282
|
+
throw new ExpectedError(
|
|
2283
|
+
`The first path segment of route pattern "${pattern}" can't contain a dot when the pattern contains params or wildcards.`
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
if (params.length === 0 && pattern === `/${root2}/*`) {
|
|
2287
|
+
return { key: pattern };
|
|
2288
|
+
}
|
|
2289
|
+
return {
|
|
2290
|
+
key: `/${root2}/*`,
|
|
2291
|
+
match: `^${regex}$`,
|
|
2292
|
+
params: params.length > 0 ? params : void 0
|
|
2293
|
+
};
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
// src/feature/router/schema.ts
|
|
2079
2297
|
var ErrorResponsePathSchema = z20.string().describe(
|
|
2080
2298
|
[
|
|
2081
2299
|
"The path to the custom error page that you want to return to the viewer when your origin returns the HTTP status code specified.",
|
|
@@ -2104,7 +2322,29 @@ var ErrorResponseSchema = z20.union([
|
|
|
2104
2322
|
minTTL: MinTTLSchema.optional()
|
|
2105
2323
|
})
|
|
2106
2324
|
]).optional();
|
|
2107
|
-
var RouteSchema = z20.string().regex(/^\//, "Route must start with a slash (/)");
|
|
2325
|
+
var RouteSchema = z20.string().regex(/^\//, "Route must start with a slash (/)").regex(/^\/([^/*.]+)?$/, 'Router paths mount a single segment without dots, like "/api".');
|
|
2326
|
+
var RoutesSchema = z20.record(
|
|
2327
|
+
ResourceIdSchema.describe("The router id to add your routes to."),
|
|
2328
|
+
z20.record(z20.string().regex(/^\//, "Route must start with a slash (/)"), FunctionSchema).superRefine((routes, ctx) => {
|
|
2329
|
+
for (const pattern of Object.keys(routes)) {
|
|
2330
|
+
try {
|
|
2331
|
+
compileRoutePattern(pattern);
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
ctx.addIssue({
|
|
2334
|
+
code: z20.ZodIssueCode.custom,
|
|
2335
|
+
path: [pattern],
|
|
2336
|
+
message: error instanceof Error ? error.message : `Invalid route pattern: ${pattern}`
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}).describe(
|
|
2341
|
+
[
|
|
2342
|
+
"Define the routes and the lambda function that should handle them.",
|
|
2343
|
+
'Routes can be an exact path like "/sitemap.xml", a wildcard like "/sitemap/*", or contain params like "/sitemap/{locale}/{page}.xml".',
|
|
2344
|
+
'Param values are passed to the function as "x-param-[NAME]" request headers.'
|
|
2345
|
+
].join("\n")
|
|
2346
|
+
)
|
|
2347
|
+
).optional().describe("Add routes to your global Router that link a path pattern to a lambda function.");
|
|
2108
2348
|
var VisibilitySchema = z20.boolean().default(false).describe("Whether to enable CloudWatch metrics for the WAF rule.");
|
|
2109
2349
|
var WafSettingsSchema = z20.object({
|
|
2110
2350
|
rateLimiter: z20.object({
|
|
@@ -2153,6 +2393,7 @@ var RouterDefaultSchema = z20.record(
|
|
|
2153
2393
|
z20.object({
|
|
2154
2394
|
domain: ResourceIdSchema.describe("The domain id to link your Router.").optional(),
|
|
2155
2395
|
subDomain: z20.string().optional(),
|
|
2396
|
+
redirectWww: z20.boolean().default(false).describe("Redirect all www subdomain requests to your root domain."),
|
|
2156
2397
|
waf: WafSettingsSchema.optional(),
|
|
2157
2398
|
geoRestrictions: z20.array(z20.string().length(2).toUpperCase()).default([]).describe("Specifies a blacklist of countries that should be blocked."),
|
|
2158
2399
|
errors: z20.object({
|
|
@@ -2241,6 +2482,21 @@ var RouterDefaultSchema = z20.record(
|
|
|
2241
2482
|
}).optional().describe(
|
|
2242
2483
|
"Specifies the cookies, headers, and query values that CloudFront includes in the cache key."
|
|
2243
2484
|
)
|
|
2485
|
+
}).superRefine((props, ctx) => {
|
|
2486
|
+
if (props.redirectWww && !props.domain) {
|
|
2487
|
+
ctx.addIssue({
|
|
2488
|
+
code: z20.ZodIssueCode.custom,
|
|
2489
|
+
path: ["redirectWww"],
|
|
2490
|
+
message: "The redirectWww option requires a domain to be set."
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
if (props.redirectWww && props.subDomain) {
|
|
2494
|
+
ctx.addIssue({
|
|
2495
|
+
code: z20.ZodIssueCode.custom,
|
|
2496
|
+
path: ["redirectWww"],
|
|
2497
|
+
message: `The redirectWww option can't be combined with a subDomain, because the domain certificate only covers single level subdomains.`
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2244
2500
|
})
|
|
2245
2501
|
).optional().describe(`Define the global Router. Backed by AWS CloudFront.`);
|
|
2246
2502
|
|
|
@@ -2587,8 +2843,8 @@ var AppSchema = z29.object({
|
|
|
2587
2843
|
layers: LayerSchema,
|
|
2588
2844
|
router: RouterDefaultSchema
|
|
2589
2845
|
// dataRetention: z.boolean().describe('Configure how your resources are handled on delete.').default(false),
|
|
2590
|
-
}).default({}).describe("Default properties")
|
|
2591
|
-
});
|
|
2846
|
+
}).strict().default({}).describe("Default properties")
|
|
2847
|
+
}).strict();
|
|
2592
2848
|
|
|
2593
2849
|
// src/config/stack.ts
|
|
2594
2850
|
import { z as z45 } from "zod";
|
|
@@ -2644,7 +2900,7 @@ var CommandsSchema = z31.record(ResourceIdSchema, CommandSchema).optional().desc
|
|
|
2644
2900
|
|
|
2645
2901
|
// src/feature/config/schema.ts
|
|
2646
2902
|
import { z as z32 } from "zod";
|
|
2647
|
-
var ConfigNameSchema = z32.string().regex(
|
|
2903
|
+
var ConfigNameSchema = z32.string().regex(/^[a-z0-9-]+$/, "Invalid config name");
|
|
2648
2904
|
var ConfigsSchema = z32.array(ConfigNameSchema).optional().describe("Define the config values for your stack.");
|
|
2649
2905
|
|
|
2650
2906
|
// src/feature/cron/schema/index.ts
|
|
@@ -3197,14 +3453,13 @@ var TestsSchema = z44.union([
|
|
|
3197
3453
|
]).describe("Define the location of your tests for your stack.").optional();
|
|
3198
3454
|
|
|
3199
3455
|
// src/config/stack.ts
|
|
3200
|
-
var DependsSchema = ResourceIdSchema.array().optional().describe("Define the stacks that this stack is depended on.");
|
|
3201
3456
|
var NameSchema = ResourceIdSchema.refine((name) => !["base", "hostedzones"].includes(name), {
|
|
3202
3457
|
message: `Stack name can't be a reserved name.`
|
|
3203
3458
|
}).describe("Stack name.");
|
|
3204
3459
|
var StackSchema = z45.object({
|
|
3205
3460
|
$schema: z45.string().optional(),
|
|
3206
3461
|
name: NameSchema,
|
|
3207
|
-
|
|
3462
|
+
routes: RoutesSchema,
|
|
3208
3463
|
commands: CommandsSchema,
|
|
3209
3464
|
// auth: AuthSchema,
|
|
3210
3465
|
// http: HttpSchema,
|
|
@@ -3230,7 +3485,7 @@ var StackSchema = z45.object({
|
|
|
3230
3485
|
images: ImagesSchema,
|
|
3231
3486
|
icons: IconsSchema,
|
|
3232
3487
|
metrics: MetricsSchema
|
|
3233
|
-
});
|
|
3488
|
+
}).strict();
|
|
3234
3489
|
|
|
3235
3490
|
// src/config/load/read.ts
|
|
3236
3491
|
import { readFile as readFile3 } from "fs/promises";
|
|
@@ -3689,7 +3944,7 @@ import { readdir as readdir2, readFile as readFile7, writeFile as writeFile4 } f
|
|
|
3689
3944
|
import { join as join10 } from "path";
|
|
3690
3945
|
|
|
3691
3946
|
// src/build/index.ts
|
|
3692
|
-
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
3947
|
+
import { mkdir as mkdir2, readFile as readFile4, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
3693
3948
|
import { dirname as dirname4, join as join7 } from "path";
|
|
3694
3949
|
|
|
3695
3950
|
// src/util/timer.ts
|
|
@@ -3731,6 +3986,7 @@ var build = (type, name, builder, props) => {
|
|
|
3731
3986
|
cached: true
|
|
3732
3987
|
};
|
|
3733
3988
|
}
|
|
3989
|
+
await rm2(cacheFile, { force: true });
|
|
3734
3990
|
const time = createTimer();
|
|
3735
3991
|
const meta = await callback(async (file, data2) => {
|
|
3736
3992
|
const path = getBuildPath(type, name, file);
|
|
@@ -3783,7 +4039,7 @@ var zipFiles = (files) => {
|
|
|
3783
4039
|
import { generateFileHash } from "@awsless/ts-file-cache";
|
|
3784
4040
|
import { kebabCase as kebabCase5 } from "change-case";
|
|
3785
4041
|
import { createHash as createHash5 } from "crypto";
|
|
3786
|
-
import { readFile as readFile6, rm as
|
|
4042
|
+
import { readFile as readFile6, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
3787
4043
|
import { dirname as dirname5, join as join9 } from "path";
|
|
3788
4044
|
import { fileURLToPath } from "url";
|
|
3789
4045
|
|
|
@@ -3795,13 +4051,13 @@ var formatByteSize = (size) => {
|
|
|
3795
4051
|
};
|
|
3796
4052
|
|
|
3797
4053
|
// src/util/temp.ts
|
|
3798
|
-
import { mkdir as mkdir3, readdir, rm as
|
|
4054
|
+
import { mkdir as mkdir3, readdir, rm as rm3 } from "fs/promises";
|
|
3799
4055
|
import { join as join8 } from "path";
|
|
3800
4056
|
var createTempFolder = async (name) => {
|
|
3801
4057
|
const path = join8(directories.temp, name);
|
|
3802
4058
|
await mkdir3(join8(directories.temp, name), { recursive: true });
|
|
3803
4059
|
process.on("SIGTERM", async () => {
|
|
3804
|
-
await
|
|
4060
|
+
await rm3(path, { recursive: true });
|
|
3805
4061
|
});
|
|
3806
4062
|
return {
|
|
3807
4063
|
path,
|
|
@@ -3809,7 +4065,7 @@ var createTempFolder = async (name) => {
|
|
|
3809
4065
|
return readdir(path, { recursive: true });
|
|
3810
4066
|
},
|
|
3811
4067
|
async delete() {
|
|
3812
|
-
await
|
|
4068
|
+
await rm3(path, { recursive: true });
|
|
3813
4069
|
}
|
|
3814
4070
|
};
|
|
3815
4071
|
};
|
|
@@ -3824,7 +4080,6 @@ var bundleTypeScriptWithRolldown = async ({
|
|
|
3824
4080
|
format: format3 = "esm",
|
|
3825
4081
|
minify = true,
|
|
3826
4082
|
file,
|
|
3827
|
-
nativeDir,
|
|
3828
4083
|
external,
|
|
3829
4084
|
importAsString: importAsStringList
|
|
3830
4085
|
}) => {
|
|
@@ -3835,9 +4090,15 @@ var bundleTypeScriptWithRolldown = async ({
|
|
|
3835
4090
|
return importee.startsWith("@aws-sdk") || importee.startsWith("aws-sdk") || external?.includes(importee);
|
|
3836
4091
|
},
|
|
3837
4092
|
treeshake: {
|
|
3838
|
-
|
|
4093
|
+
// Dependencies are treated as side-effect free, so unused imports
|
|
4094
|
+
// like the local test servers never reach the production bundle.
|
|
4095
|
+
// The bundle guard below fails the build if one slips through.
|
|
4096
|
+
moduleSideEffects: (id, isExternal) => isExternal ? external?.includes(id) === true : id.startsWith(`${directories.root}/`) && !id.includes("/node_modules/")
|
|
3839
4097
|
},
|
|
3840
4098
|
onwarn: (error) => {
|
|
4099
|
+
if (error.code === "UNRESOLVED_IMPORT") {
|
|
4100
|
+
throw new ExpectedError(error.message);
|
|
4101
|
+
}
|
|
3841
4102
|
debugError(error.message);
|
|
3842
4103
|
},
|
|
3843
4104
|
plugins: [
|
|
@@ -3882,6 +4143,7 @@ var bundleTypeScriptWithRolldown = async ({
|
|
|
3882
4143
|
chunkFileNames: `[name].${ext}`,
|
|
3883
4144
|
minify
|
|
3884
4145
|
});
|
|
4146
|
+
assertNoTestOnlyModules(result.output);
|
|
3885
4147
|
const hash = createHash4("sha1");
|
|
3886
4148
|
const files = [];
|
|
3887
4149
|
for (const item of result.output) {
|
|
@@ -3903,6 +4165,25 @@ var bundleTypeScriptWithRolldown = async ({
|
|
|
3903
4165
|
files
|
|
3904
4166
|
};
|
|
3905
4167
|
};
|
|
4168
|
+
var TEST_ONLY_MODULES = ["dynamo-db-local", "@awsless/dynamodb-server", "redis-memory-server", "aws-sdk-vitest-mock"];
|
|
4169
|
+
var findPackage = (id, names) => {
|
|
4170
|
+
return names.find((name) => id.includes(`/${name.replace(/^@[^/]+\//, "")}/`));
|
|
4171
|
+
};
|
|
4172
|
+
var assertNoTestOnlyModules = (output) => {
|
|
4173
|
+
for (const item of output) {
|
|
4174
|
+
if (item.type !== "chunk") {
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
for (const id of item.moduleIds ?? []) {
|
|
4178
|
+
const found = findPackage(id, TEST_ONLY_MODULES);
|
|
4179
|
+
if (found) {
|
|
4180
|
+
throw new Error(
|
|
4181
|
+
`The test-only package "${found}" was bundled into a production build through "${id}". Remove the import from the handler, or keep the package tree-shakeable.`
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
};
|
|
3906
4187
|
|
|
3907
4188
|
// src/feature/bundle/util.ts
|
|
3908
4189
|
var ROUTE_HEADER = "x-awsless-route";
|
|
@@ -3925,7 +4206,7 @@ var registerBundleFunction = (ctx, routeKey, props) => {
|
|
|
3925
4206
|
};
|
|
3926
4207
|
var buildBundle = (props) => {
|
|
3927
4208
|
return async (build3, { workspace }) => {
|
|
3928
|
-
const runtime = props.runtime ?? join9(dirname5(fileURLToPath(import.meta.url)), "/handlers/bundle.
|
|
4209
|
+
const runtime = props.runtime ?? join9(dirname5(fileURLToPath(import.meta.url)), "/handlers/bundle.js");
|
|
3929
4210
|
const handlers = [...props.handlers].sort((a, b) => a.routeKey.localeCompare(b.routeKey));
|
|
3930
4211
|
const entries = handlers.map(({ routeKey, file, exportName }) => {
|
|
3931
4212
|
const virtualFile = JSON.stringify(`${file}?awsless-route=${encodeURIComponent(routeKey)}`);
|
|
@@ -3974,7 +4255,7 @@ ${entries.join("\n")}
|
|
|
3974
4255
|
importAsString: importAsString2.length > 0 ? importAsString2 : void 0
|
|
3975
4256
|
});
|
|
3976
4257
|
await temp.delete();
|
|
3977
|
-
await
|
|
4258
|
+
await rm4(getBuildPath("bundle", props.name, "files"), { recursive: true, force: true });
|
|
3978
4259
|
await Promise.all([
|
|
3979
4260
|
write("HASH", bundle.hash),
|
|
3980
4261
|
...bundle.files.map((file) => write(`files/${file.name}`, file.code)),
|
|
@@ -4019,6 +4300,11 @@ var bundleFeature = defineFeature({
|
|
|
4019
4300
|
const addLayer = (layer) => {
|
|
4020
4301
|
layers.push(layer);
|
|
4021
4302
|
};
|
|
4303
|
+
const layerIds = Object.keys(ctx.appConfig.defaults.layers ?? {});
|
|
4304
|
+
const layerPackages = layerIds.flatMap((id) => ctx.shared.entry("layer", "packages", id));
|
|
4305
|
+
for (const id of layerIds) {
|
|
4306
|
+
addLayer(ctx.shared.entry("layer", "arn", id));
|
|
4307
|
+
}
|
|
4022
4308
|
const name = getBundleFunctionName(ctx.app.name);
|
|
4023
4309
|
const shortName = formatGlobalResourceName({
|
|
4024
4310
|
appName: ctx.app.name,
|
|
@@ -4032,7 +4318,7 @@ var bundleFeature = defineFeature({
|
|
|
4032
4318
|
name,
|
|
4033
4319
|
handlers,
|
|
4034
4320
|
minify: defaults.minify,
|
|
4035
|
-
external: defaults.external
|
|
4321
|
+
external: [...defaults.external ?? [], ...layerPackages]
|
|
4036
4322
|
})
|
|
4037
4323
|
);
|
|
4038
4324
|
const sourceHash = new Output3(envDeps, async (resolve2) => {
|
|
@@ -4188,7 +4474,8 @@ var bundleFeature = defineFeature({
|
|
|
4188
4474
|
deploymentId: ctx.deploymentId ?? "local-0",
|
|
4189
4475
|
functionName: lambda.functionName,
|
|
4190
4476
|
functionVersion: lambda.version,
|
|
4191
|
-
onFailureArn: onFailure
|
|
4477
|
+
onFailureArn: onFailure,
|
|
4478
|
+
sourceAccount: ctx.accountId
|
|
4192
4479
|
},
|
|
4193
4480
|
{
|
|
4194
4481
|
// Make sure the permissions are in place before any event source is wired up.
|
|
@@ -4274,11 +4561,6 @@ var bundleFeature = defineFeature({
|
|
|
4274
4561
|
addLayer,
|
|
4275
4562
|
addPermission
|
|
4276
4563
|
});
|
|
4277
|
-
},
|
|
4278
|
-
onStack(ctx) {
|
|
4279
|
-
const bundle = ctx.shared.get("bundle", "main");
|
|
4280
|
-
ctx.onEnv(bundle.addEnv);
|
|
4281
|
-
ctx.onPermission(bundle.addPermission);
|
|
4282
4564
|
}
|
|
4283
4565
|
});
|
|
4284
4566
|
|
|
@@ -4329,6 +4611,7 @@ var cacheFeature = defineFeature({
|
|
|
4329
4611
|
{
|
|
4330
4612
|
name,
|
|
4331
4613
|
engine: "valkey",
|
|
4614
|
+
networkType: "dual_stack",
|
|
4332
4615
|
dailySnapshotTime: "02:00",
|
|
4333
4616
|
majorEngineVersion: "8",
|
|
4334
4617
|
snapshotRetentionLimit: props.snapshotRetentionLimit,
|
|
@@ -4354,7 +4637,9 @@ var cacheFeature = defineFeature({
|
|
|
4354
4637
|
},
|
|
4355
4638
|
{
|
|
4356
4639
|
retainOnDelete: ctx.appConfig.removal === "retain",
|
|
4357
|
-
import: ctx.import ? name : void 0
|
|
4640
|
+
import: ctx.import ? name : void 0,
|
|
4641
|
+
// The network type can only be set at creation time.
|
|
4642
|
+
replaceOnChanges: ["networkType"]
|
|
4358
4643
|
}
|
|
4359
4644
|
);
|
|
4360
4645
|
const masterHost = cache.endpoint.pipe((v) => v.at(0).address);
|
|
@@ -4487,7 +4772,7 @@ var SsmStore = class {
|
|
|
4487
4772
|
debug("Value:", color.info(value));
|
|
4488
4773
|
await this.client.send(
|
|
4489
4774
|
new PutParameterCommand({
|
|
4490
|
-
Type: ParameterType.
|
|
4775
|
+
Type: ParameterType.SECURE_STRING,
|
|
4491
4776
|
Name: this.getName(name),
|
|
4492
4777
|
Value: value,
|
|
4493
4778
|
Overwrite: true
|
|
@@ -4586,7 +4871,7 @@ var configFeature = defineFeature({
|
|
|
4586
4871
|
ctx.addEnv(`CONFIG_${constantCase4(name)}`, name);
|
|
4587
4872
|
}
|
|
4588
4873
|
if (configs.length) {
|
|
4589
|
-
ctx.
|
|
4874
|
+
ctx.addGlobalPermission({
|
|
4590
4875
|
actions: [
|
|
4591
4876
|
"ssm:GetParameter",
|
|
4592
4877
|
"ssm:GetParameters",
|
|
@@ -4923,10 +5208,12 @@ var domainFeature = defineFeature({
|
|
|
4923
5208
|
}
|
|
4924
5209
|
}
|
|
4925
5210
|
ctx.addGlobalPermission({
|
|
4926
|
-
actions: ["ses
|
|
5211
|
+
actions: ["ses:SendEmail", "ses:SendRawEmail"],
|
|
4927
5212
|
resources: [
|
|
4928
|
-
|
|
4929
|
-
|
|
5213
|
+
`arn:aws:ses:${ctx.appConfig.region}:${ctx.accountId}:identity/*`,
|
|
5214
|
+
// Sending through the app configuration set is authorized against
|
|
5215
|
+
// its own ARN, not just the identity.
|
|
5216
|
+
`arn:aws:ses:${ctx.appConfig.region}:${ctx.accountId}:configuration-set/${ctx.app.name}`
|
|
4930
5217
|
]
|
|
4931
5218
|
});
|
|
4932
5219
|
}
|
|
@@ -5016,7 +5303,9 @@ var functionFeature = defineFeature({
|
|
|
5016
5303
|
// 'lambda:ListFunctions',
|
|
5017
5304
|
// 'lambda:GetFunction',
|
|
5018
5305
|
],
|
|
5019
|
-
resources: [
|
|
5306
|
+
resources: [
|
|
5307
|
+
`arn:aws:lambda:${ctx.appConfig.region}:${ctx.accountId}:function:${ctx.appConfig.name}--*`
|
|
5308
|
+
]
|
|
5020
5309
|
});
|
|
5021
5310
|
},
|
|
5022
5311
|
onStack(ctx) {
|
|
@@ -5058,7 +5347,7 @@ var onErrorLogFeature = defineFeature({
|
|
|
5058
5347
|
const consumerRoute = formatRouteKey(ctx.app.name, "on-error-log", "consumer");
|
|
5059
5348
|
bundle.addHandler({
|
|
5060
5349
|
routeKey: handlerRoute,
|
|
5061
|
-
file: join11(dirname6(fileURLToPath2(import.meta.url)), "/handlers/on-error-log.
|
|
5350
|
+
file: join11(dirname6(fileURLToPath2(import.meta.url)), "/handlers/on-error-log.js"),
|
|
5062
5351
|
exportName: "default"
|
|
5063
5352
|
});
|
|
5064
5353
|
bundle.addEnv(formatRouteEnvName(handlerRoute, "CONSUMER"), consumerRoute);
|
|
@@ -5283,7 +5572,7 @@ var onFailureFeature = defineFeature({
|
|
|
5283
5572
|
const consumer = props.consumer;
|
|
5284
5573
|
bundle.addHandler({
|
|
5285
5574
|
routeKey: normalizerRoute,
|
|
5286
|
-
file: join12(dirname7(fileURLToPath3(import.meta.url)), "/handlers/on-failure.
|
|
5575
|
+
file: join12(dirname7(fileURLToPath3(import.meta.url)), "/handlers/on-failure.js"),
|
|
5287
5576
|
exportName: "default"
|
|
5288
5577
|
});
|
|
5289
5578
|
registerBundleFunction(ctx, consumerRoute, consumer);
|
|
@@ -5438,7 +5727,7 @@ var createPubSubService = (parentGroup, ctx, id, props, inputs) => {
|
|
|
5438
5727
|
});
|
|
5439
5728
|
const shortName = shortId(`${ctx.app.name}:pubsub:${id}:${ctx.appId}`);
|
|
5440
5729
|
const image2 = "public.ecr.aws/aws-cli/aws-cli:arm64";
|
|
5441
|
-
const bundleFile = join14(__dirname, "handlers/pubsub-server.
|
|
5730
|
+
const bundleFile = join14(__dirname, "handlers/pubsub-server.js");
|
|
5442
5731
|
ctx.registerBuild("pubsub", name, async (build3) => {
|
|
5443
5732
|
const hash = createHash8("sha1").update(await readFile9(bundleFile)).digest("hex");
|
|
5444
5733
|
const fingerprint = `${hash}-${ARCHITECTURE}`;
|
|
@@ -5539,7 +5828,8 @@ var createPubSubService = (parentGroup, ctx, id, props, inputs) => {
|
|
|
5539
5828
|
Statement: list3.map((statement) => ({
|
|
5540
5829
|
Effect: pascalCase2(statement.effect ?? "allow"),
|
|
5541
5830
|
Action: statement.actions,
|
|
5542
|
-
Resource: statement.resources
|
|
5831
|
+
Resource: statement.resources,
|
|
5832
|
+
Condition: statement.conditions
|
|
5543
5833
|
}))
|
|
5544
5834
|
})
|
|
5545
5835
|
);
|
|
@@ -5893,12 +6183,15 @@ var pubsubFeature = defineFeature({
|
|
|
5893
6183
|
{
|
|
5894
6184
|
name: cacheName,
|
|
5895
6185
|
engine: "valkey",
|
|
6186
|
+
networkType: "dual_stack",
|
|
5896
6187
|
majorEngineVersion: "8",
|
|
5897
6188
|
securityGroupIds: [cacheSecurityGroup.id],
|
|
5898
6189
|
subnetIds: ctx.shared.get("vpc", "private-subnets")
|
|
5899
6190
|
},
|
|
5900
6191
|
{
|
|
5901
|
-
import: ctx.import ? cacheName : void 0
|
|
6192
|
+
import: ctx.import ? cacheName : void 0,
|
|
6193
|
+
// The network type can only be set at creation time.
|
|
6194
|
+
replaceOnChanges: ["networkType"]
|
|
5902
6195
|
}
|
|
5903
6196
|
);
|
|
5904
6197
|
const redisHost = cache.endpoint.pipe((v) => v.at(0).address);
|
|
@@ -6027,7 +6320,7 @@ var pubsubFeature = defineFeature({
|
|
|
6027
6320
|
const publisherRouteKey = formatRouteKey(ctx.app.name, "pubsub", `${id}-publisher`);
|
|
6028
6321
|
bundle.addHandler({
|
|
6029
6322
|
routeKey: publisherRouteKey,
|
|
6030
|
-
file: join15(__dirname2, "/handlers/pubsub-publisher.
|
|
6323
|
+
file: join15(__dirname2, "/handlers/pubsub-publisher.js"),
|
|
6031
6324
|
exportName: "default"
|
|
6032
6325
|
});
|
|
6033
6326
|
bundle.addEnv(formatRouteEnvName3(publisherRouteKey, "REDIS_HOST"), redisHost);
|
|
@@ -6180,7 +6473,7 @@ var queueFeature = defineFeature({
|
|
|
6180
6473
|
});
|
|
6181
6474
|
}
|
|
6182
6475
|
ctx.addEnv(`QUEUE_${constantCase6(ctx.stack.name)}_${constantCase6(id)}_URL`, queue2.url);
|
|
6183
|
-
ctx.
|
|
6476
|
+
ctx.addGlobalPermission({
|
|
6184
6477
|
actions: [
|
|
6185
6478
|
"sqs:SendMessage",
|
|
6186
6479
|
"sqs:ReceiveMessage",
|
|
@@ -6376,7 +6669,7 @@ var rpcFeature = defineFeature({
|
|
|
6376
6669
|
const serverRouteKey = formatRouteKey(ctx.app.name, "rpc", id);
|
|
6377
6670
|
bundle.addHandler({
|
|
6378
6671
|
routeKey: serverRouteKey,
|
|
6379
|
-
file: join16(dirname10(fileURLToPath6(import.meta.url)), "/handlers/rpc.
|
|
6672
|
+
file: join16(dirname10(fileURLToPath6(import.meta.url)), "/handlers/rpc.js"),
|
|
6380
6673
|
exportName: "default"
|
|
6381
6674
|
});
|
|
6382
6675
|
bundle.addEnv(
|
|
@@ -6484,7 +6777,7 @@ var searchFeature = defineFeature({
|
|
|
6484
6777
|
{
|
|
6485
6778
|
domainName: name,
|
|
6486
6779
|
engineVersion: `OpenSearch_${props.version}`,
|
|
6487
|
-
ipAddressType: "
|
|
6780
|
+
ipAddressType: "dualstack",
|
|
6488
6781
|
clusterConfig: {
|
|
6489
6782
|
instanceType: `${props.type}.search`,
|
|
6490
6783
|
instanceCount: props.count
|
|
@@ -6539,8 +6832,8 @@ var searchFeature = defineFeature({
|
|
|
6539
6832
|
import: ctx.import ? name : void 0
|
|
6540
6833
|
}
|
|
6541
6834
|
);
|
|
6542
|
-
ctx.addEnv(`SEARCH_${constantCase8(ctx.stack.name)}_${constantCase8(id)}_DOMAIN`, openSearch.
|
|
6543
|
-
ctx.
|
|
6835
|
+
ctx.addEnv(`SEARCH_${constantCase8(ctx.stack.name)}_${constantCase8(id)}_DOMAIN`, openSearch.endpointV2);
|
|
6836
|
+
ctx.addGlobalPermission({
|
|
6544
6837
|
actions: ["es:ESHttp*"],
|
|
6545
6838
|
resources: [
|
|
6546
6839
|
//
|
|
@@ -6897,14 +7190,18 @@ var siteFeature = defineFeature({
|
|
|
6897
7190
|
env,
|
|
6898
7191
|
stdout: "pipe",
|
|
6899
7192
|
stderr: "pipe"
|
|
6900
|
-
// stdout: 'ignore',
|
|
6901
|
-
// stderr: ''
|
|
6902
|
-
// stdout: 'inherit',
|
|
6903
|
-
// stderr: 'inherit',
|
|
6904
7193
|
});
|
|
6905
|
-
await
|
|
6906
|
-
|
|
6907
|
-
|
|
7194
|
+
const [output, errors] = await Promise.all([
|
|
7195
|
+
new Response(instance.stdout).text(),
|
|
7196
|
+
new Response(instance.stderr).text(),
|
|
7197
|
+
instance.exited
|
|
7198
|
+
]);
|
|
7199
|
+
if (instance.exitCode !== 0) {
|
|
7200
|
+
const reason = instance.signalCode ? ` (${instance.signalCode})` : "";
|
|
7201
|
+
throw new ExpectedError(
|
|
7202
|
+
`Site build failed${reason}:
|
|
7203
|
+
${(errors.trim() || output.trim()).slice(-2e3)}`
|
|
7204
|
+
);
|
|
6908
7205
|
}
|
|
6909
7206
|
await write("HASH", fingerprint);
|
|
6910
7207
|
return {
|
|
@@ -7745,7 +8042,7 @@ var imageFeature = defineFeature({
|
|
|
7745
8042
|
const serverRouteKey = formatRouteKey(ctx.stack.name, "image", id);
|
|
7746
8043
|
bundle.addHandler({
|
|
7747
8044
|
routeKey: serverRouteKey,
|
|
7748
|
-
file: join20(dirname12(fileURLToPath7(import.meta.url)), "/handlers/image.
|
|
8045
|
+
file: join20(dirname12(fileURLToPath7(import.meta.url)), "/handlers/image.js"),
|
|
7749
8046
|
exportName: "default",
|
|
7750
8047
|
external: ["sharp"]
|
|
7751
8048
|
});
|
|
@@ -7844,7 +8141,7 @@ var iconFeature = defineFeature({
|
|
|
7844
8141
|
const serverRouteKey = formatRouteKey(ctx.stack.name, "icon", id);
|
|
7845
8142
|
bundle.addHandler({
|
|
7846
8143
|
routeKey: serverRouteKey,
|
|
7847
|
-
file: join21(dirname13(fileURLToPath8(import.meta.url)), "/handlers/icon.
|
|
8144
|
+
file: join21(dirname13(fileURLToPath8(import.meta.url)), "/handlers/icon.js"),
|
|
7848
8145
|
exportName: "default"
|
|
7849
8146
|
});
|
|
7850
8147
|
addRoutes({
|
|
@@ -8103,7 +8400,8 @@ var createFargateJob = (parentGroup, ctx, ns, id, local) => {
|
|
|
8103
8400
|
Statement: list3.map((statement) => ({
|
|
8104
8401
|
Effect: pascalCase3(statement.effect ?? "allow"),
|
|
8105
8402
|
Action: statement.actions,
|
|
8106
|
-
Resource: statement.resources
|
|
8403
|
+
Resource: statement.resources,
|
|
8404
|
+
Condition: statement.conditions
|
|
8107
8405
|
}))
|
|
8108
8406
|
})
|
|
8109
8407
|
);
|
|
@@ -8455,13 +8753,13 @@ var jobFeature = defineFeature({
|
|
|
8455
8753
|
const group = new Group25(ctx.stack, "job", id);
|
|
8456
8754
|
createFargateJob(group, ctx, "job", id, props);
|
|
8457
8755
|
}
|
|
8458
|
-
ctx.
|
|
8756
|
+
ctx.addGlobalPermission({
|
|
8459
8757
|
actions: ["ecs:RunTask"],
|
|
8460
8758
|
resources: [
|
|
8461
8759
|
`arn:aws:ecs:${ctx.appConfig.region}:*:task-definition/${ctx.app.name}--${ctx.stackConfig.name}--*`
|
|
8462
8760
|
]
|
|
8463
8761
|
});
|
|
8464
|
-
ctx.
|
|
8762
|
+
ctx.addGlobalPermission({
|
|
8465
8763
|
actions: ["iam:PassRole"],
|
|
8466
8764
|
resources: ["*"],
|
|
8467
8765
|
conditions: {
|
|
@@ -8601,7 +8899,8 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
|
|
|
8601
8899
|
Statement: list3.map((statement) => ({
|
|
8602
8900
|
Effect: pascalCase4(statement.effect ?? "allow"),
|
|
8603
8901
|
Action: statement.actions,
|
|
8604
|
-
Resource: statement.resources
|
|
8902
|
+
Resource: statement.resources,
|
|
8903
|
+
Condition: statement.conditions
|
|
8605
8904
|
}))
|
|
8606
8905
|
})
|
|
8607
8906
|
);
|
|
@@ -8997,7 +9296,7 @@ var metricFeature = defineFeature({
|
|
|
8997
9296
|
onStack(ctx) {
|
|
8998
9297
|
const bundle = ctx.shared.get("bundle", "main");
|
|
8999
9298
|
const namespace = `awsless/${kebabCase11(ctx.app.name)}/${kebabCase11(ctx.stack.name)}`;
|
|
9000
|
-
ctx.
|
|
9299
|
+
ctx.addGlobalPermission({
|
|
9001
9300
|
actions: ["cloudwatch:PutMetricData"],
|
|
9002
9301
|
resources: ["*"],
|
|
9003
9302
|
conditions: {
|
|
@@ -9073,16 +9372,20 @@ var metricFeature = defineFeature({
|
|
|
9073
9372
|
});
|
|
9074
9373
|
|
|
9075
9374
|
// src/feature/router/index.ts
|
|
9076
|
-
import { days as days10, seconds as
|
|
9375
|
+
import { days as days10, seconds as seconds7, toSeconds as toSeconds13, years } from "@awsless/duration";
|
|
9077
9376
|
import { Group as Group29 } from "@terraforge/core";
|
|
9078
9377
|
import { aws as aws30 } from "@terraforge/aws";
|
|
9079
|
-
import { camelCase as camelCase9, constantCase as constantCase15 } from "change-case";
|
|
9378
|
+
import { camelCase as camelCase9, constantCase as constantCase15, kebabCase as kebabCase12 } from "change-case";
|
|
9080
9379
|
|
|
9081
9380
|
// src/feature/router/router-code.ts
|
|
9381
|
+
import { minutes as minutes8, seconds as seconds6, toSeconds as toSeconds12 } from "@awsless/duration";
|
|
9382
|
+
var ORIGIN_READ_TIMEOUT = toSeconds12(minutes8(2));
|
|
9383
|
+
var ORIGIN_CONNECTION_TIMEOUT = toSeconds12(seconds6(10));
|
|
9082
9384
|
var getViewerRequestFunctionCode = (props) => {
|
|
9083
9385
|
return CODE(
|
|
9084
9386
|
[
|
|
9085
9387
|
props.blockDirectAccess ? BLOCK_DIRECT_ACCESS_TO_CLOUDFRONT : "",
|
|
9388
|
+
props.redirectWww ? REDIRECT_WWW : "",
|
|
9086
9389
|
props.passwordAuth ?? props.basicAuth ? AUTH_WRAPPER(
|
|
9087
9390
|
[
|
|
9088
9391
|
//
|
|
@@ -9091,7 +9394,7 @@ var getViewerRequestFunctionCode = (props) => {
|
|
|
9091
9394
|
].join("\n")
|
|
9092
9395
|
) : ""
|
|
9093
9396
|
],
|
|
9094
|
-
ACTIVE_PREFIX(props.router)
|
|
9397
|
+
props.preview ? PREVIEW_PREFIX(props.router) : ACTIVE_PREFIX(props.router)
|
|
9095
9398
|
);
|
|
9096
9399
|
};
|
|
9097
9400
|
var BLOCK_DIRECT_ACCESS_TO_CLOUDFRONT = `
|
|
@@ -9101,6 +9404,38 @@ if (headers.host && headers.host.value.includes('cloudfront.net')) {
|
|
|
9101
9404
|
statusDescription: 'Forbidden'
|
|
9102
9405
|
};
|
|
9103
9406
|
}`;
|
|
9407
|
+
var REDIRECT_WWW = `
|
|
9408
|
+
if (headers.host && headers.host.value.startsWith('www.')) {
|
|
9409
|
+
let location = 'https://' + headers.host.value.slice(4) + request.uri;
|
|
9410
|
+
const query = [];
|
|
9411
|
+
|
|
9412
|
+
for(const key in request.querystring) {
|
|
9413
|
+
const item = request.querystring[key];
|
|
9414
|
+
|
|
9415
|
+
if(item.multiValue) {
|
|
9416
|
+
for(const i in item.multiValue) {
|
|
9417
|
+
query.push(key + '=' + item.multiValue[i].value);
|
|
9418
|
+
}
|
|
9419
|
+
} else if(item.value === '') {
|
|
9420
|
+
query.push(key);
|
|
9421
|
+
} else {
|
|
9422
|
+
query.push(key + '=' + item.value);
|
|
9423
|
+
}
|
|
9424
|
+
}
|
|
9425
|
+
|
|
9426
|
+
if(query.length > 0) {
|
|
9427
|
+
location += '?' + query.join('&');
|
|
9428
|
+
}
|
|
9429
|
+
|
|
9430
|
+
return {
|
|
9431
|
+
statusCode: 301,
|
|
9432
|
+
statusDescription: 'Moved Permanently',
|
|
9433
|
+
headers: {
|
|
9434
|
+
'location': { value: location },
|
|
9435
|
+
'strict-transport-security': { value: 'max-age=31536000; includeSubdomains; preload' }
|
|
9436
|
+
}
|
|
9437
|
+
};
|
|
9438
|
+
}`;
|
|
9104
9439
|
var BASIC_AUTH_CHECK = (username, password) => `
|
|
9105
9440
|
authMethods.push('Basic realm="Protected"');
|
|
9106
9441
|
|
|
@@ -9114,11 +9449,64 @@ var PASSWORD_AUTH_CHECK = (password) => `
|
|
|
9114
9449
|
authMethods.push('Password realm="Protected"');
|
|
9115
9450
|
|
|
9116
9451
|
if(!isAuthorized) {
|
|
9117
|
-
if(authHeader && authHeader.startsWith('Password ') && authHeader.slice(9) ===
|
|
9452
|
+
if(authHeader && authHeader.startsWith('Password ') && authHeader.slice(9) === ${JSON.stringify(password)}) {
|
|
9118
9453
|
isAuthorized = true;
|
|
9119
9454
|
}
|
|
9120
9455
|
}
|
|
9121
9456
|
`;
|
|
9457
|
+
var PREVIEW_PREFIX = (router) => `
|
|
9458
|
+
const router = ${JSON.stringify(router)};
|
|
9459
|
+
let deployment;
|
|
9460
|
+
|
|
9461
|
+
if (request.querystring['awsless-deployment'] && request.querystring['awsless-deployment'].value) {
|
|
9462
|
+
deployment = request.querystring['awsless-deployment'].value;
|
|
9463
|
+
} else if (request.cookies && request.cookies['awsless-deployment'] && request.cookies['awsless-deployment'].value) {
|
|
9464
|
+
deployment = request.cookies['awsless-deployment'].value;
|
|
9465
|
+
}
|
|
9466
|
+
|
|
9467
|
+
let prefix;
|
|
9468
|
+
|
|
9469
|
+
try {
|
|
9470
|
+
const pointer = deployment ? '$deploy:' + deployment : '$active';
|
|
9471
|
+
prefix = (await cf.kvs().get(pointer)).split(':')[0] + ':' + router + ':';
|
|
9472
|
+
} catch (e) {
|
|
9473
|
+
return deployment
|
|
9474
|
+
? { statusCode: 404, statusDescription: 'Unknown Deployment' }
|
|
9475
|
+
: { statusCode: 503, statusDescription: 'Service Unavailable' };
|
|
9476
|
+
}
|
|
9477
|
+
|
|
9478
|
+
if (deployment && request.querystring['awsless-deployment']) {
|
|
9479
|
+
delete request.querystring['awsless-deployment'];
|
|
9480
|
+
|
|
9481
|
+
const query = [];
|
|
9482
|
+
|
|
9483
|
+
for (const key in request.querystring) {
|
|
9484
|
+
const entry = request.querystring[key];
|
|
9485
|
+
|
|
9486
|
+
if (entry.multiValue) {
|
|
9487
|
+
// The CloudFront js runtime doesn't support for...of.
|
|
9488
|
+
for (const i in entry.multiValue) {
|
|
9489
|
+
query.push(key + '=' + entry.multiValue[i].value);
|
|
9490
|
+
}
|
|
9491
|
+
} else {
|
|
9492
|
+
query.push(key + '=' + entry.value);
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
|
|
9496
|
+
return {
|
|
9497
|
+
statusCode: 302,
|
|
9498
|
+
statusDescription: 'Found',
|
|
9499
|
+
headers: {
|
|
9500
|
+
location: { value: request.uri + (query.length ? '?' + query.join('&') : '') }
|
|
9501
|
+
},
|
|
9502
|
+
cookies: {
|
|
9503
|
+
'awsless-deployment': {
|
|
9504
|
+
value: deployment,
|
|
9505
|
+
attributes: 'Path=/; Secure; SameSite=Lax'
|
|
9506
|
+
}
|
|
9507
|
+
}
|
|
9508
|
+
};
|
|
9509
|
+
}`;
|
|
9122
9510
|
var ACTIVE_PREFIX = (router) => `
|
|
9123
9511
|
const router = ${JSON.stringify(router)};
|
|
9124
9512
|
let prefix;
|
|
@@ -9186,20 +9574,77 @@ function isValidRoute(route, method) {
|
|
|
9186
9574
|
return true;
|
|
9187
9575
|
}
|
|
9188
9576
|
|
|
9577
|
+
function matchRoute(value, path, method) {
|
|
9578
|
+
const list = Array.isArray(value) ? value : [value];
|
|
9579
|
+
|
|
9580
|
+
for(const i in list) {
|
|
9581
|
+
const route = list[i];
|
|
9582
|
+
|
|
9583
|
+
if(!isValidRoute(route, method)) {
|
|
9584
|
+
continue;
|
|
9585
|
+
}
|
|
9586
|
+
|
|
9587
|
+
if(route.match) {
|
|
9588
|
+
const found = path.match(new RegExp(route.match));
|
|
9589
|
+
|
|
9590
|
+
if(!found) {
|
|
9591
|
+
continue;
|
|
9592
|
+
}
|
|
9593
|
+
|
|
9594
|
+
const params = {};
|
|
9595
|
+
|
|
9596
|
+
if(route.params) {
|
|
9597
|
+
for(const p in route.params) {
|
|
9598
|
+
params[route.params[p]] = found[Number(p) + 1];
|
|
9599
|
+
}
|
|
9600
|
+
}
|
|
9601
|
+
|
|
9602
|
+
return { route: route, params: params };
|
|
9603
|
+
}
|
|
9604
|
+
|
|
9605
|
+
return { route: route };
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
|
|
9189
9609
|
async function findRoute(path, method, prefix) {
|
|
9610
|
+
// only route selection is normalized, the forwarded uri stays untouched
|
|
9611
|
+
if (path.length > 1 && path.slice(-1) === '/') {
|
|
9612
|
+
path = path.slice(0, -1);
|
|
9613
|
+
}
|
|
9614
|
+
|
|
9190
9615
|
const store = cf.kvs();
|
|
9191
9616
|
const keys = getPossibleRouteKeys(path);
|
|
9192
9617
|
|
|
9193
9618
|
for(const i in keys) {
|
|
9194
9619
|
const key = keys[i];
|
|
9620
|
+
let value;
|
|
9195
9621
|
|
|
9196
9622
|
try {
|
|
9197
|
-
|
|
9623
|
+
value = await store.get(prefix + key, { format: 'json' });
|
|
9624
|
+
} catch (e) {
|
|
9625
|
+
continue;
|
|
9626
|
+
}
|
|
9627
|
+
|
|
9628
|
+
// Route lists that are too big for a single key value pair
|
|
9629
|
+
// are sharded over multiple entries behind a route index.
|
|
9630
|
+
if(value && value.list) {
|
|
9631
|
+
for(let n = 0; n < value.list; n++) {
|
|
9632
|
+
try {
|
|
9633
|
+
const route = await store.get(prefix + key + '#' + n, { format: 'json' });
|
|
9634
|
+
const result = matchRoute(route, path, method);
|
|
9635
|
+
|
|
9636
|
+
if(result) {
|
|
9637
|
+
return result;
|
|
9638
|
+
}
|
|
9639
|
+
} catch (e) {}
|
|
9640
|
+
}
|
|
9641
|
+
} else {
|
|
9642
|
+
const result = matchRoute(value, path, method);
|
|
9198
9643
|
|
|
9199
|
-
if(
|
|
9200
|
-
return
|
|
9644
|
+
if(result) {
|
|
9645
|
+
return result;
|
|
9201
9646
|
}
|
|
9202
|
-
}
|
|
9647
|
+
}
|
|
9203
9648
|
}
|
|
9204
9649
|
}
|
|
9205
9650
|
|
|
@@ -9268,13 +9713,12 @@ function setS3Origin(route) {
|
|
|
9268
9713
|
function setLambdaOrigin(route) {
|
|
9269
9714
|
const config = getRequestOriginConfig(route);
|
|
9270
9715
|
|
|
9271
|
-
// CloudFront caps the origin response timeout at 60s without a quota increase.
|
|
9272
9716
|
if(typeof config.timeouts.readTimeout !== 'number') {
|
|
9273
|
-
config.timeouts.readTimeout =
|
|
9717
|
+
config.timeouts.readTimeout = ${ORIGIN_READ_TIMEOUT};
|
|
9274
9718
|
}
|
|
9275
9719
|
|
|
9276
9720
|
if(typeof config.timeouts.connectionTimeout !== 'number') {
|
|
9277
|
-
config.timeouts.connectionTimeout =
|
|
9721
|
+
config.timeouts.connectionTimeout = ${ORIGIN_CONNECTION_TIMEOUT};
|
|
9278
9722
|
}
|
|
9279
9723
|
|
|
9280
9724
|
cf.updateRequestOrigin(Object.assign(config, {
|
|
@@ -9323,15 +9767,36 @@ async function handler(event) {
|
|
|
9323
9767
|
|
|
9324
9768
|
${prefixCode}
|
|
9325
9769
|
|
|
9326
|
-
const
|
|
9770
|
+
const result = await findRoute(path, request.method, prefix);
|
|
9327
9771
|
|
|
9328
|
-
if(!
|
|
9772
|
+
if(!result) {
|
|
9329
9773
|
return {
|
|
9330
9774
|
statusCode: 404,
|
|
9331
9775
|
statusDescription: 'Not Found'
|
|
9332
9776
|
};
|
|
9333
9777
|
}
|
|
9334
9778
|
|
|
9779
|
+
const route = result.route;
|
|
9780
|
+
|
|
9781
|
+
// A client provided param header can never reach the origin.
|
|
9782
|
+
const spoofed = [];
|
|
9783
|
+
|
|
9784
|
+
for(const name in headers) {
|
|
9785
|
+
if(name.indexOf('x-param-') === 0) {
|
|
9786
|
+
spoofed.push(name);
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9789
|
+
|
|
9790
|
+
for(const i in spoofed) {
|
|
9791
|
+
delete headers[spoofed[i]];
|
|
9792
|
+
}
|
|
9793
|
+
|
|
9794
|
+
if(result.params) {
|
|
9795
|
+
for(const name in result.params) {
|
|
9796
|
+
headers['x-param-' + name.toLowerCase()] = { value: encodeURIComponent(result.params[name]) };
|
|
9797
|
+
}
|
|
9798
|
+
}
|
|
9799
|
+
|
|
9335
9800
|
if(route.requestHeaders) {
|
|
9336
9801
|
for(const name in route.requestHeaders) {
|
|
9337
9802
|
headers[name] = { value: route.requestHeaders[name] };
|
|
@@ -9350,6 +9815,8 @@ async function handler(event) {
|
|
|
9350
9815
|
|
|
9351
9816
|
if(route.forwardHost && headers.host && headers.host.value) {
|
|
9352
9817
|
headers['x-forwarded-host'] = { value: headers.host.value };
|
|
9818
|
+
} else {
|
|
9819
|
+
delete headers['x-forwarded-host'];
|
|
9353
9820
|
}
|
|
9354
9821
|
|
|
9355
9822
|
headers['x-origin'] = { value: route.domainName };
|
|
@@ -9382,6 +9849,31 @@ async function handler(event) {
|
|
|
9382
9849
|
`;
|
|
9383
9850
|
|
|
9384
9851
|
// src/feature/router/index.ts
|
|
9852
|
+
var MAX_VALUE_SIZE = 1e3;
|
|
9853
|
+
var ORIGIN_PLACEHOLDER = "x".repeat(64);
|
|
9854
|
+
var assertRouteValueSize = (key, route) => {
|
|
9855
|
+
const withOrigin = (entry) => {
|
|
9856
|
+
return entry.type === "lambda" ? { ...entry, domainName: ORIGIN_PLACEHOLDER } : entry;
|
|
9857
|
+
};
|
|
9858
|
+
for (const entry of Array.isArray(route) ? route : [route]) {
|
|
9859
|
+
if (Buffer.byteLength(JSON.stringify(withOrigin(entry)), "utf8") > MAX_VALUE_SIZE) {
|
|
9860
|
+
throw new ExpectedError(`The route value of the "${key}" route key is too large.`);
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
};
|
|
9864
|
+
var createRouteStoreEntries = (key, route) => {
|
|
9865
|
+
const value = JSON.stringify(route);
|
|
9866
|
+
if (!Array.isArray(route) || Buffer.byteLength(value, "utf8") <= MAX_VALUE_SIZE) {
|
|
9867
|
+
return [{ key, value }];
|
|
9868
|
+
}
|
|
9869
|
+
return [
|
|
9870
|
+
{ key, value: JSON.stringify({ list: route.length }) },
|
|
9871
|
+
...route.map((entry, index) => ({
|
|
9872
|
+
key: `${key}#${index}`,
|
|
9873
|
+
value: JSON.stringify(entry)
|
|
9874
|
+
}))
|
|
9875
|
+
];
|
|
9876
|
+
};
|
|
9385
9877
|
var routerFeature = defineFeature({
|
|
9386
9878
|
name: "router",
|
|
9387
9879
|
onApp(ctx) {
|
|
@@ -9392,7 +9884,6 @@ var routerFeature = defineFeature({
|
|
|
9392
9884
|
const distributionIds = [];
|
|
9393
9885
|
let hasLambdaRoutes = false;
|
|
9394
9886
|
let routeStore;
|
|
9395
|
-
let previewDistribution;
|
|
9396
9887
|
for (const [id, props] of routers) {
|
|
9397
9888
|
const group = new Group29(ctx.base, "router", id);
|
|
9398
9889
|
const name = formatGlobalResourceName({
|
|
@@ -9416,6 +9907,7 @@ var routerFeature = defineFeature({
|
|
|
9416
9907
|
code: getViewerRequestFunctionCode({
|
|
9417
9908
|
router: id,
|
|
9418
9909
|
blockDirectAccess: !!props.domain,
|
|
9910
|
+
redirectWww: !!props.domain && props.redirectWww,
|
|
9419
9911
|
basicAuth: props.basicAuth,
|
|
9420
9912
|
passwordAuth: props.passwordAuth
|
|
9421
9913
|
}),
|
|
@@ -9427,20 +9919,21 @@ var routerFeature = defineFeature({
|
|
|
9427
9919
|
if (Object.hasOwn(routes, `${id}:${key}`)) {
|
|
9428
9920
|
throw new ExpectedError(`Duplicate route key: ${key} in the "${id}" router`);
|
|
9429
9921
|
}
|
|
9922
|
+
assertRouteValueSize(`${id}:${key}`, route);
|
|
9430
9923
|
routes[`${id}:${key}`] = route;
|
|
9431
9924
|
}
|
|
9432
9925
|
for (const dependency of options?.dependsOn ?? []) {
|
|
9433
9926
|
routeDependencies.add(dependency);
|
|
9434
9927
|
}
|
|
9435
|
-
if (Object.values(newRoutes).some((route) => route.type === "lambda")) {
|
|
9928
|
+
if (Object.values(newRoutes).flat().some((route) => route.type === "lambda")) {
|
|
9436
9929
|
hasLambdaRoutes = true;
|
|
9437
9930
|
}
|
|
9438
9931
|
});
|
|
9439
9932
|
const cache = new aws30.cloudfront.CachePolicy(group, "cache", {
|
|
9440
9933
|
name,
|
|
9441
|
-
minTtl:
|
|
9442
|
-
maxTtl:
|
|
9443
|
-
defaultTtl:
|
|
9934
|
+
minTtl: toSeconds13(seconds7(0)),
|
|
9935
|
+
maxTtl: toSeconds13(days10(365)),
|
|
9936
|
+
defaultTtl: toSeconds13(days10(0)),
|
|
9444
9937
|
parametersInCacheKeyAndForwardedToOrigin: {
|
|
9445
9938
|
enableAcceptEncodingBrotli: true,
|
|
9446
9939
|
enableAcceptEncodingGzip: true,
|
|
@@ -9493,7 +9986,7 @@ var routerFeature = defineFeature({
|
|
|
9493
9986
|
name,
|
|
9494
9987
|
corsConfig: {
|
|
9495
9988
|
originOverride: props.cors?.override ?? true,
|
|
9496
|
-
accessControlMaxAgeSec:
|
|
9989
|
+
accessControlMaxAgeSec: toSeconds13(props.cors?.maxAge ?? years(1)),
|
|
9497
9990
|
accessControlAllowHeaders: { items: props.cors?.headers ?? ["*"] },
|
|
9498
9991
|
accessControlAllowMethods: { items: props.cors?.methods ?? ["ALL"] },
|
|
9499
9992
|
accessControlAllowOrigins: { items: props.cors?.origins ?? ["*"] },
|
|
@@ -9518,7 +10011,7 @@ var routerFeature = defineFeature({
|
|
|
9518
10011
|
strictTransportSecurity: {
|
|
9519
10012
|
override: true,
|
|
9520
10013
|
preload: true,
|
|
9521
|
-
accessControlMaxAgeSec:
|
|
10014
|
+
accessControlMaxAgeSec: toSeconds13(years(1)),
|
|
9522
10015
|
includeSubdomains: true
|
|
9523
10016
|
},
|
|
9524
10017
|
xssProtection: {
|
|
@@ -9538,7 +10031,7 @@ var routerFeature = defineFeature({
|
|
|
9538
10031
|
rateBasedStatement: {
|
|
9539
10032
|
limit: wafSettingsConfig.rateLimiter.limit,
|
|
9540
10033
|
aggregateKeyType: "IP",
|
|
9541
|
-
evaluationWindowSec:
|
|
10034
|
+
evaluationWindowSec: toSeconds13(wafSettingsConfig.rateLimiter.window)
|
|
9542
10035
|
}
|
|
9543
10036
|
},
|
|
9544
10037
|
action: {
|
|
@@ -9628,12 +10121,12 @@ var routerFeature = defineFeature({
|
|
|
9628
10121
|
rule: wafRules,
|
|
9629
10122
|
captchaConfig: {
|
|
9630
10123
|
immunityTimeProperty: {
|
|
9631
|
-
immunityTime:
|
|
10124
|
+
immunityTime: toSeconds13(wafSettingsConfig.captchaImmunityTime)
|
|
9632
10125
|
}
|
|
9633
10126
|
},
|
|
9634
10127
|
challengeConfig: {
|
|
9635
10128
|
immunityTimeProperty: {
|
|
9636
|
-
immunityTime:
|
|
10129
|
+
immunityTime: toSeconds13(wafSettingsConfig.challengeImmunityTime)
|
|
9637
10130
|
}
|
|
9638
10131
|
},
|
|
9639
10132
|
visibilityConfig: {
|
|
@@ -9689,7 +10182,7 @@ var routerFeature = defineFeature({
|
|
|
9689
10182
|
}
|
|
9690
10183
|
return {
|
|
9691
10184
|
errorCode: Number(errorCode),
|
|
9692
|
-
errorCachingMinTtl: item.minTTL ?
|
|
10185
|
+
errorCachingMinTtl: item.minTTL ? toSeconds13(item.minTTL) : void 0,
|
|
9693
10186
|
responseCode: item.statusCode?.toString() ?? errorCode,
|
|
9694
10187
|
responsePagePath: item.path
|
|
9695
10188
|
};
|
|
@@ -9728,24 +10221,26 @@ var routerFeature = defineFeature({
|
|
|
9728
10221
|
],
|
|
9729
10222
|
webAclId: waf?.arn
|
|
9730
10223
|
});
|
|
9731
|
-
|
|
10224
|
+
{
|
|
9732
10225
|
const previewFunction = new aws30.cloudfront.Function(group, "preview-function", {
|
|
9733
10226
|
name: `${name.slice(0, 55)}--preview`,
|
|
9734
10227
|
runtime: "cloudfront-js-2.0",
|
|
9735
10228
|
code: getViewerRequestFunctionCode({
|
|
9736
10229
|
router: id,
|
|
10230
|
+
preview: true,
|
|
9737
10231
|
basicAuth: props.basicAuth,
|
|
9738
10232
|
passwordAuth: props.passwordAuth
|
|
9739
10233
|
}),
|
|
9740
10234
|
publish: true,
|
|
9741
10235
|
keyValueStoreAssociations: [routeStore.arn]
|
|
9742
10236
|
});
|
|
9743
|
-
previewDistribution = new aws30.cloudfront.Distribution(group, "preview", {
|
|
10237
|
+
const previewDistribution = new aws30.cloudfront.Distribution(group, "preview", {
|
|
9744
10238
|
tags: {
|
|
9745
10239
|
name: `${name}-preview`
|
|
9746
10240
|
},
|
|
9747
10241
|
comment: `${name} preview`,
|
|
9748
10242
|
enabled: true,
|
|
10243
|
+
isIpv6Enabled: true,
|
|
9749
10244
|
waitForDeployment: true,
|
|
9750
10245
|
origin: [
|
|
9751
10246
|
{
|
|
@@ -9770,7 +10265,7 @@ var routerFeature = defineFeature({
|
|
|
9770
10265
|
}
|
|
9771
10266
|
return {
|
|
9772
10267
|
errorCode: Number(errorCode),
|
|
9773
|
-
errorCachingMinTtl: item.minTTL ?
|
|
10268
|
+
errorCachingMinTtl: item.minTTL ? toSeconds13(item.minTTL) : void 0,
|
|
9774
10269
|
responseCode: item.statusCode ?? Number(errorCode),
|
|
9775
10270
|
responsePagePath: item.path
|
|
9776
10271
|
};
|
|
@@ -9803,6 +10298,9 @@ var routerFeature = defineFeature({
|
|
|
9803
10298
|
webAclId: waf?.arn
|
|
9804
10299
|
});
|
|
9805
10300
|
distributionIds.push(previewDistribution.id);
|
|
10301
|
+
ctx.shared.add("router", "preview-id", id, previewDistribution.id);
|
|
10302
|
+
}
|
|
10303
|
+
if (id === defaultRouter) {
|
|
9806
10304
|
ctx.onReadyLast(() => {
|
|
9807
10305
|
const bundle = ctx.shared.get("bundle", "main");
|
|
9808
10306
|
let lambdaUrlHost;
|
|
@@ -9829,66 +10327,108 @@ var routerFeature = defineFeature({
|
|
|
9829
10327
|
storeArn: routeStore.arn,
|
|
9830
10328
|
functionVersion: bundle.lambda.version,
|
|
9831
10329
|
routes: $resolve([routes, lambdaUrlHost], (routes2, lambdaUrlHost2) => {
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
10330
|
+
const withOrigin = (route) => {
|
|
10331
|
+
return route.type === "lambda" ? { ...route, domainName: lambdaUrlHost2 } : route;
|
|
10332
|
+
};
|
|
10333
|
+
return Object.entries(routes2).flatMap(
|
|
10334
|
+
([key, route]) => createRouteStoreEntries(
|
|
10335
|
+
key,
|
|
10336
|
+
Array.isArray(route) ? route.map(withOrigin) : withOrigin(route)
|
|
9836
10337
|
)
|
|
9837
|
-
|
|
10338
|
+
);
|
|
9838
10339
|
})
|
|
9839
10340
|
},
|
|
9840
10341
|
{
|
|
9841
10342
|
dependsOn: Array.from(routeDependencies)
|
|
9842
10343
|
}
|
|
9843
10344
|
);
|
|
9844
|
-
if (!(props.basicAuth ?? props.passwordAuth)) {
|
|
9845
|
-
bundle.addEnv(
|
|
9846
|
-
"AWSLESS_PREVIEW",
|
|
9847
|
-
$resolve(
|
|
9848
|
-
[routes],
|
|
9849
|
-
(routes2) => JSON.stringify({
|
|
9850
|
-
router: id,
|
|
9851
|
-
routes: Object.fromEntries(
|
|
9852
|
-
Object.entries(routes2).filter(
|
|
9853
|
-
([key, route]) => key.startsWith(`${id}:`) && route.type !== "url"
|
|
9854
|
-
)
|
|
9855
|
-
)
|
|
9856
|
-
})
|
|
9857
|
-
)
|
|
9858
|
-
);
|
|
9859
|
-
}
|
|
9860
10345
|
});
|
|
9861
10346
|
}
|
|
9862
10347
|
ctx.shared.add("router", "id", id, distribution.id);
|
|
9863
|
-
ctx.shared.add("router", "preview-id", id, previewDistribution.id);
|
|
9864
10348
|
distributionIds.push(distribution.id);
|
|
9865
10349
|
if (props.domain) {
|
|
9866
10350
|
const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
|
|
10351
|
+
const wwwDomainName = props.redirectWww ? `www.${domainName}` : void 0;
|
|
9867
10352
|
const certificateArn = ctx.shared.entry("domain", `global-certificate-arn`, props.domain);
|
|
9868
10353
|
const zoneId = ctx.shared.entry("domain", "zone-id", props.domain);
|
|
9869
10354
|
const connectionGroup = new aws30.cloudfront.ConnectionGroup(group, "connection-group", {
|
|
9870
|
-
name
|
|
10355
|
+
name,
|
|
10356
|
+
ipv6Enabled: true
|
|
9871
10357
|
});
|
|
9872
10358
|
new aws30.cloudfront.DistributionTenant(group, `tenant`, {
|
|
9873
10359
|
name,
|
|
9874
10360
|
enabled: true,
|
|
9875
10361
|
distributionId: distribution.id,
|
|
9876
10362
|
connectionGroupId: connectionGroup.id,
|
|
9877
|
-
domain: [
|
|
10363
|
+
domain: [
|
|
10364
|
+
//
|
|
10365
|
+
{ domain: domainName },
|
|
10366
|
+
...wwwDomainName ? [{ domain: wwwDomainName }] : []
|
|
10367
|
+
],
|
|
9878
10368
|
customizations: [{ certificate: [{ arn: certificateArn }] }]
|
|
9879
10369
|
});
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
10370
|
+
for (const [recordId, recordName] of [
|
|
10371
|
+
["record", domainName],
|
|
10372
|
+
...wwwDomainName ? [["www-record", wwwDomainName]] : []
|
|
10373
|
+
]) {
|
|
10374
|
+
new aws30.route53.Record(group, recordId, {
|
|
10375
|
+
zoneId,
|
|
10376
|
+
type: "A",
|
|
10377
|
+
name: recordName,
|
|
10378
|
+
alias: {
|
|
10379
|
+
name: connectionGroup.routingEndpoint,
|
|
10380
|
+
zoneId: "Z2FDTNDATAQYW2",
|
|
10381
|
+
evaluateTargetHealth: false
|
|
10382
|
+
}
|
|
10383
|
+
});
|
|
10384
|
+
new aws30.route53.Record(group, `${recordId}-ipv6`, {
|
|
10385
|
+
zoneId,
|
|
10386
|
+
type: "AAAA",
|
|
10387
|
+
name: recordName,
|
|
10388
|
+
alias: {
|
|
10389
|
+
name: connectionGroup.routingEndpoint,
|
|
10390
|
+
zoneId: "Z2FDTNDATAQYW2",
|
|
10391
|
+
evaluateTargetHealth: false
|
|
10392
|
+
}
|
|
10393
|
+
});
|
|
10394
|
+
}
|
|
10395
|
+
ctx.bind(`ROUTER_${constantCase15(id)}_ENDPOINT`, domainName);
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
},
|
|
10399
|
+
onStack(ctx) {
|
|
10400
|
+
for (const [id, patterns] of Object.entries(ctx.stackConfig.routes ?? {})) {
|
|
10401
|
+
if (!ctx.appConfig.defaults.router?.[id]) {
|
|
10402
|
+
throw new FileError(ctx.stackConfig.file, `Router "${id}" is not defined on the app level.`);
|
|
10403
|
+
}
|
|
10404
|
+
const addRoutes = ctx.shared.entry("router", "addRoutes", id);
|
|
10405
|
+
const grouped = {};
|
|
10406
|
+
for (const [pattern, props] of Object.entries(patterns)) {
|
|
10407
|
+
const compiled = compileRoutePattern(pattern);
|
|
10408
|
+
const slug = kebabCase12(pattern).slice(0, 20);
|
|
10409
|
+
const routeKey = formatRouteKey(ctx.stack.name, "route", `${slug || "root"}-${shortId(pattern)}`);
|
|
10410
|
+
registerBundleFunction(ctx, routeKey, props);
|
|
10411
|
+
grouped[compiled.key] ??= [];
|
|
10412
|
+
grouped[compiled.key].push({
|
|
10413
|
+
type: "lambda",
|
|
10414
|
+
forwardHost: true,
|
|
10415
|
+
urlEncodedQueryString: true,
|
|
10416
|
+
match: compiled.match,
|
|
10417
|
+
params: compiled.params,
|
|
10418
|
+
requestHeaders: {
|
|
10419
|
+
[ROUTE_HEADER]: routeKey
|
|
9888
10420
|
}
|
|
9889
10421
|
});
|
|
9890
|
-
ctx.bind(`ROUTER_${constantCase15(id)}_ENDPOINT`, domainName);
|
|
9891
10422
|
}
|
|
10423
|
+
const merged = {};
|
|
10424
|
+
for (const [key, list3] of Object.entries(grouped)) {
|
|
10425
|
+
if (list3.length === 1) {
|
|
10426
|
+
merged[key] = list3[0];
|
|
10427
|
+
} else {
|
|
10428
|
+
merged[key] = [...list3.filter((route) => route.match), ...list3.filter((route) => !route.match)];
|
|
10429
|
+
}
|
|
10430
|
+
}
|
|
10431
|
+
addRoutes(merged);
|
|
9892
10432
|
}
|
|
9893
10433
|
}
|
|
9894
10434
|
});
|
|
@@ -10183,6 +10723,12 @@ var logo = () => {
|
|
|
10183
10723
|
var layout = async (command, cb) => {
|
|
10184
10724
|
console.log();
|
|
10185
10725
|
log9.intro(`${logo()} ${color.line(command)}`);
|
|
10726
|
+
let completed = false;
|
|
10727
|
+
process.on("exit", (code) => {
|
|
10728
|
+
if (code === 0 && !completed) {
|
|
10729
|
+
process.exitCode = 130;
|
|
10730
|
+
}
|
|
10731
|
+
});
|
|
10186
10732
|
try {
|
|
10187
10733
|
const options = program.optsWithGlobals();
|
|
10188
10734
|
const appConfig = await loadAppConfig(options);
|
|
@@ -10197,9 +10743,11 @@ var layout = async (command, cb) => {
|
|
|
10197
10743
|
appConfig,
|
|
10198
10744
|
stackConfigs
|
|
10199
10745
|
});
|
|
10746
|
+
completed = true;
|
|
10200
10747
|
log9.outro(result ?? void 0);
|
|
10201
10748
|
process.exit(0);
|
|
10202
10749
|
} catch (error) {
|
|
10750
|
+
completed = true;
|
|
10203
10751
|
playErrorSound();
|
|
10204
10752
|
logError(error);
|
|
10205
10753
|
log9.outro();
|
|
@@ -10267,14 +10815,6 @@ var SharedData = class {
|
|
|
10267
10815
|
};
|
|
10268
10816
|
|
|
10269
10817
|
// src/app.ts
|
|
10270
|
-
var assertDepsExists = (stack, stacks) => {
|
|
10271
|
-
for (const dep of stack.depends ?? []) {
|
|
10272
|
-
const found = stacks.find((i) => i.name === dep);
|
|
10273
|
-
if (!found) {
|
|
10274
|
-
throw new FileError(stack.file, `Stack "${stack.name}" depends on a stack "${dep}" that doesn't exist.`);
|
|
10275
|
-
}
|
|
10276
|
-
}
|
|
10277
|
-
};
|
|
10278
10818
|
var createApp = (props) => {
|
|
10279
10819
|
const app = new App2(props.appConfig.name);
|
|
10280
10820
|
const zones = new Stack(app, "zones");
|
|
@@ -10298,17 +10838,10 @@ var createApp = (props) => {
|
|
|
10298
10838
|
const bindListeners = [];
|
|
10299
10839
|
const globalEnv = [];
|
|
10300
10840
|
const globalEnvListeners = [];
|
|
10301
|
-
const allLocalEnv = {};
|
|
10302
|
-
const allLocalEnvListeners = {};
|
|
10303
10841
|
const globalPermissions = [];
|
|
10304
10842
|
const globalPermissionCallbacks = [];
|
|
10305
10843
|
const appPermissions = [];
|
|
10306
10844
|
const appPermissionCallbacks = [];
|
|
10307
|
-
const allStackPermissions = {};
|
|
10308
|
-
const allStackPermissionCallbacks = {};
|
|
10309
|
-
for (const stackConfig of props.stackConfigs) {
|
|
10310
|
-
assertDepsExists(stackConfig, props.stackConfigs);
|
|
10311
|
-
}
|
|
10312
10845
|
for (const feature of features) {
|
|
10313
10846
|
feature.onBefore?.({
|
|
10314
10847
|
...props,
|
|
@@ -10381,14 +10914,6 @@ var createApp = (props) => {
|
|
|
10381
10914
|
}
|
|
10382
10915
|
for (const stackConfig of props.stackConfigs) {
|
|
10383
10916
|
const stack = new Stack(app, stackConfig.name);
|
|
10384
|
-
const localEnvListeners = [];
|
|
10385
|
-
const localEnv = [];
|
|
10386
|
-
const stackPermissions = [];
|
|
10387
|
-
const stackPermissionCallbacks = [];
|
|
10388
|
-
allStackPermissions[stack.name] = stackPermissions;
|
|
10389
|
-
allStackPermissionCallbacks[stack.name] = stackPermissionCallbacks;
|
|
10390
|
-
allLocalEnvListeners[stack.name] = localEnvListeners;
|
|
10391
|
-
allLocalEnv[stack.name] = localEnv;
|
|
10392
10917
|
for (const feature of features) {
|
|
10393
10918
|
feature.onStack?.({
|
|
10394
10919
|
...props,
|
|
@@ -10402,7 +10927,6 @@ var createApp = (props) => {
|
|
|
10402
10927
|
shared,
|
|
10403
10928
|
onPermission(callback) {
|
|
10404
10929
|
globalPermissionCallbacks.push(callback);
|
|
10405
|
-
stackPermissionCallbacks.push(callback);
|
|
10406
10930
|
},
|
|
10407
10931
|
addGlobalPermission(permission) {
|
|
10408
10932
|
globalPermissions.push(permission);
|
|
@@ -10410,9 +10934,6 @@ var createApp = (props) => {
|
|
|
10410
10934
|
addAppPermission(permission) {
|
|
10411
10935
|
appPermissions.push(permission);
|
|
10412
10936
|
},
|
|
10413
|
-
addStackPermission(permission) {
|
|
10414
|
-
stackPermissions.push(permission);
|
|
10415
|
-
},
|
|
10416
10937
|
addWarning(props2) {
|
|
10417
10938
|
warnings.push(props2);
|
|
10418
10939
|
},
|
|
@@ -10476,10 +10997,10 @@ var createApp = (props) => {
|
|
|
10476
10997
|
bindListeners.push(cb);
|
|
10477
10998
|
},
|
|
10478
10999
|
addEnv(name, value) {
|
|
10479
|
-
|
|
11000
|
+
globalEnv.push({ name, value });
|
|
10480
11001
|
},
|
|
10481
11002
|
onEnv(cb) {
|
|
10482
|
-
|
|
11003
|
+
globalEnvListeners.push(cb);
|
|
10483
11004
|
},
|
|
10484
11005
|
onReady(cb) {
|
|
10485
11006
|
readyListeners.push(cb);
|
|
@@ -10489,16 +11010,6 @@ var createApp = (props) => {
|
|
|
10489
11010
|
}
|
|
10490
11011
|
});
|
|
10491
11012
|
}
|
|
10492
|
-
for (const callback of stackPermissionCallbacks) {
|
|
10493
|
-
for (const permission of stackPermissions) {
|
|
10494
|
-
callback(permission);
|
|
10495
|
-
}
|
|
10496
|
-
}
|
|
10497
|
-
for (const listener of localEnvListeners) {
|
|
10498
|
-
for (const env of localEnv) {
|
|
10499
|
-
listener(env.name, env.value);
|
|
10500
|
-
}
|
|
10501
|
-
}
|
|
10502
11013
|
}
|
|
10503
11014
|
for (const callback of appPermissionCallbacks) {
|
|
10504
11015
|
for (const permission of appPermissions) {
|
|
@@ -10520,24 +11031,6 @@ var createApp = (props) => {
|
|
|
10520
11031
|
listener(name, value);
|
|
10521
11032
|
}
|
|
10522
11033
|
}
|
|
10523
|
-
for (const stackConfig of props.stackConfigs) {
|
|
10524
|
-
const envListeners = allLocalEnvListeners[stackConfig.name];
|
|
10525
|
-
const permissionCallbacks = allStackPermissionCallbacks[stackConfig.name];
|
|
10526
|
-
for (const dependency of stackConfig.depends ?? []) {
|
|
10527
|
-
const permissions = allStackPermissions[dependency];
|
|
10528
|
-
const env = allLocalEnv[dependency];
|
|
10529
|
-
for (const permission of permissions) {
|
|
10530
|
-
for (const callback of permissionCallbacks) {
|
|
10531
|
-
callback(permission);
|
|
10532
|
-
}
|
|
10533
|
-
}
|
|
10534
|
-
for (const entry of env) {
|
|
10535
|
-
for (const listener of envListeners) {
|
|
10536
|
-
listener(entry.name, entry.value);
|
|
10537
|
-
}
|
|
10538
|
-
}
|
|
10539
|
-
}
|
|
10540
|
-
}
|
|
10541
11034
|
const ready = () => {
|
|
10542
11035
|
for (const listener of readyListeners) {
|
|
10543
11036
|
listener();
|
|
@@ -10583,6 +11076,7 @@ var buildAssets = async (builders, stackFilters, showResult = false) => {
|
|
|
10583
11076
|
if (filteredBuilders.length === 0) {
|
|
10584
11077
|
return;
|
|
10585
11078
|
}
|
|
11079
|
+
filteredBuilders.sort((a, b) => Number(a.type === "bundle") - Number(b.type === "bundle"));
|
|
10586
11080
|
const results = [];
|
|
10587
11081
|
await log10.task({
|
|
10588
11082
|
initialMessage: `Building assets...`,
|
|
@@ -11391,6 +11885,10 @@ var deploy = (program2) => {
|
|
|
11391
11885
|
appConfig,
|
|
11392
11886
|
id: deployment.id
|
|
11393
11887
|
});
|
|
11888
|
+
await promoteAppDeployment({
|
|
11889
|
+
appConfig,
|
|
11890
|
+
id: deployment.id
|
|
11891
|
+
});
|
|
11394
11892
|
return deployments3;
|
|
11395
11893
|
} finally {
|
|
11396
11894
|
await release();
|
|
@@ -11398,9 +11896,10 @@ var deploy = (program2) => {
|
|
|
11398
11896
|
}
|
|
11399
11897
|
});
|
|
11400
11898
|
playSuccessSound();
|
|
11401
|
-
const
|
|
11402
|
-
|
|
11403
|
-
|
|
11899
|
+
for (const summary of deployments2) {
|
|
11900
|
+
log20.message(summary);
|
|
11901
|
+
}
|
|
11902
|
+
return `Deployment #${deployment.id} is live.`;
|
|
11404
11903
|
});
|
|
11405
11904
|
});
|
|
11406
11905
|
};
|
|
@@ -11891,7 +12390,7 @@ var bind = (program2) => {
|
|
|
11891
12390
|
stderr: "inherit"
|
|
11892
12391
|
});
|
|
11893
12392
|
await instance.exited;
|
|
11894
|
-
process.exit(
|
|
12393
|
+
process.exit(instance.exitCode ?? 1);
|
|
11895
12394
|
});
|
|
11896
12395
|
});
|
|
11897
12396
|
};
|
|
@@ -12011,7 +12510,7 @@ var resources = (program2) => {
|
|
|
12011
12510
|
return `${color.dim("{")}${color.warning(v)}${color.dim("}")}`;
|
|
12012
12511
|
}).replaceAll(":", color.dim(":"));
|
|
12013
12512
|
};
|
|
12014
|
-
const
|
|
12513
|
+
const formatStatus2 = (status) => {
|
|
12015
12514
|
if (status === "created") {
|
|
12016
12515
|
return color.success(status);
|
|
12017
12516
|
}
|
|
@@ -12036,7 +12535,7 @@ var resources = (program2) => {
|
|
|
12036
12535
|
stack.resources.map((r) => {
|
|
12037
12536
|
return [
|
|
12038
12537
|
//
|
|
12039
|
-
|
|
12538
|
+
formatStatus2(r.status),
|
|
12040
12539
|
color.dim(icon.arrow.right),
|
|
12041
12540
|
formatResource(stack.urn, r.urn)
|
|
12042
12541
|
].join(" ");
|
|
@@ -12280,9 +12779,12 @@ var test = (program2) => {
|
|
|
12280
12779
|
if (tests.length === 0) {
|
|
12281
12780
|
return "No tests found.";
|
|
12282
12781
|
}
|
|
12283
|
-
await runTests(tests, stacks, options?.filters, {
|
|
12782
|
+
const passed = await runTests(tests, stacks, options?.filters, {
|
|
12284
12783
|
showLogs: true
|
|
12285
12784
|
});
|
|
12785
|
+
if (!passed) {
|
|
12786
|
+
throw new Cancelled();
|
|
12787
|
+
}
|
|
12286
12788
|
return "All tests finished.";
|
|
12287
12789
|
});
|
|
12288
12790
|
});
|
|
@@ -12507,14 +13009,14 @@ var parseJsonLog = (message) => {
|
|
|
12507
13009
|
json = JSON.parse(message);
|
|
12508
13010
|
} catch (error) {
|
|
12509
13011
|
}
|
|
12510
|
-
if ("level" in json && typeof json.level === "string" && "timestamp" in json && typeof json.timestamp === "string" && "message" in json) {
|
|
13012
|
+
if (typeof json === "object" && json !== null && "level" in json && typeof json.level === "string" && "timestamp" in json && typeof json.timestamp === "string" && "message" in json) {
|
|
12511
13013
|
return {
|
|
12512
13014
|
level: json.level,
|
|
12513
13015
|
message: typeof json.message === "string" ? json.message : JSON.stringify(json.message, void 0, 2),
|
|
12514
13016
|
date: new Date(json.timestamp)
|
|
12515
13017
|
};
|
|
12516
13018
|
}
|
|
12517
|
-
if ("type" in json && typeof json.type === "string" && json.type.startsWith("platform") && "time" in json && typeof json.time === "string" && "record" in json) {
|
|
13019
|
+
if (typeof json === "object" && json !== null && "type" in json && typeof json.type === "string" && json.type.startsWith("platform") && "time" in json && typeof json.time === "string" && "record" in json) {
|
|
12518
13020
|
return {
|
|
12519
13021
|
level: "SYSTEM",
|
|
12520
13022
|
message: JSON.stringify(json.record, void 0, 2),
|
|
@@ -12962,7 +13464,7 @@ var activity = (program2) => {
|
|
|
12962
13464
|
// src/cli/command/deployment.ts
|
|
12963
13465
|
import { CloudFrontClient as CloudFrontClient6 } from "@aws-sdk/client-cloudfront";
|
|
12964
13466
|
import { CloudFrontKeyValueStoreClient as CloudFrontKeyValueStoreClient3 } from "@aws-sdk/client-cloudfront-keyvaluestore";
|
|
12965
|
-
import {
|
|
13467
|
+
import { LambdaClient as LambdaClient7 } from "@aws-sdk/client-lambda";
|
|
12966
13468
|
import { log as log35, prompt as prompt19 } from "@awsless/clui";
|
|
12967
13469
|
import { DynamoDBClient as DynamoDBClient6 } from "@awsless/dynamodb";
|
|
12968
13470
|
var createClients = async (appConfig) => {
|
|
@@ -12979,11 +13481,17 @@ var createClients = async (appConfig) => {
|
|
|
12979
13481
|
};
|
|
12980
13482
|
};
|
|
12981
13483
|
var formatAge = (iso) => {
|
|
12982
|
-
const
|
|
12983
|
-
if (
|
|
12984
|
-
if (
|
|
12985
|
-
if (
|
|
12986
|
-
return `${Math.floor(
|
|
13484
|
+
const minutes9 = Math.floor((Date.now() - Date.parse(iso)) / 6e4);
|
|
13485
|
+
if (minutes9 < 1) return "just now";
|
|
13486
|
+
if (minutes9 < 60) return `${minutes9}m ago`;
|
|
13487
|
+
if (minutes9 < 60 * 24) return `${Math.floor(minutes9 / 60)}h ago`;
|
|
13488
|
+
return `${Math.floor(minutes9 / (60 * 24))}d ago`;
|
|
13489
|
+
};
|
|
13490
|
+
var formatStatus = (item, liveId) => {
|
|
13491
|
+
if (item.id === liveId) return color.success("live ");
|
|
13492
|
+
if (item.promotedAt) return "promoted";
|
|
13493
|
+
if (item.functionVersion) return color.info("staged ");
|
|
13494
|
+
return color.dim("pending ");
|
|
12987
13495
|
};
|
|
12988
13496
|
var deployments = (program2) => {
|
|
12989
13497
|
program2.command("deployments").description("List the deployment history of your app").action(async () => {
|
|
@@ -12998,17 +13506,16 @@ var deployments = (program2) => {
|
|
|
12998
13506
|
}
|
|
12999
13507
|
const idWidth = Math.max(...items.map((item) => item.id.length));
|
|
13000
13508
|
log35.message(
|
|
13001
|
-
items.map(
|
|
13002
|
-
|
|
13003
|
-
return [
|
|
13509
|
+
items.map(
|
|
13510
|
+
(item) => [
|
|
13004
13511
|
color.label(item.id.padEnd(idWidth)),
|
|
13005
|
-
|
|
13512
|
+
formatStatus(item, liveId),
|
|
13006
13513
|
formatAge(item.createdAt).padEnd(8),
|
|
13007
13514
|
color.dim(item.commit?.slice(0, 7) ?? "-------"),
|
|
13008
13515
|
(item.message ?? "").slice(0, 50).padEnd(50),
|
|
13009
13516
|
color.dim(item.user ?? "")
|
|
13010
|
-
].join(" ")
|
|
13011
|
-
|
|
13517
|
+
].join(" ")
|
|
13518
|
+
).join("\n")
|
|
13012
13519
|
);
|
|
13013
13520
|
return `Found ${items.length} deployments.`;
|
|
13014
13521
|
});
|
|
@@ -13022,28 +13529,7 @@ var prune = (program2) => {
|
|
|
13022
13529
|
listDeployments(dynamo, appId),
|
|
13023
13530
|
readLiveDeploymentId(lambda, functionName)
|
|
13024
13531
|
]);
|
|
13025
|
-
const
|
|
13026
|
-
const keep = Math.max(1, Number(options.keep) || 10);
|
|
13027
|
-
const mainSlug = slugifyBranch(options.main);
|
|
13028
|
-
const keptMain = new Set(
|
|
13029
|
-
items.filter((item) => item.branch === mainSlug && item.functionVersion).map((item) => item.seq).sort((a, b) => b - a).slice(0, keep)
|
|
13030
|
-
);
|
|
13031
|
-
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
13032
|
-
const prunable = items.filter((item) => {
|
|
13033
|
-
if (item.id === liveId || item.id === rollbackTarget?.id) {
|
|
13034
|
-
return false;
|
|
13035
|
-
}
|
|
13036
|
-
if (options.branch) {
|
|
13037
|
-
return item.branch === slugifyBranch(options.branch);
|
|
13038
|
-
}
|
|
13039
|
-
if (!item.functionVersion) {
|
|
13040
|
-
return item.createdAt < dayAgo;
|
|
13041
|
-
}
|
|
13042
|
-
if (item.branch === mainSlug) {
|
|
13043
|
-
return !keptMain.has(item.seq);
|
|
13044
|
-
}
|
|
13045
|
-
return item.commit ? isCommitMerged(item.commit, options.main) : false;
|
|
13046
|
-
});
|
|
13532
|
+
const prunable = selectPrunableDeployments(items, liveId, options);
|
|
13047
13533
|
if (prunable.length === 0) {
|
|
13048
13534
|
return `Nothing to prune.`;
|
|
13049
13535
|
}
|
|
@@ -13060,31 +13546,25 @@ var prune = (program2) => {
|
|
|
13060
13546
|
initialMessage: "Pruning the deployments",
|
|
13061
13547
|
successMessage: "Done pruning the deployments.",
|
|
13062
13548
|
task: () => withAppReleaseLock(appConfig, async () => {
|
|
13063
|
-
|
|
13549
|
+
const [freshItems, freshLiveId] = await Promise.all([
|
|
13550
|
+
listDeployments(dynamo, appId),
|
|
13551
|
+
readLiveDeploymentId(lambda, functionName)
|
|
13552
|
+
]);
|
|
13553
|
+
const confirmed = new Set(prunable.map((item) => item.id));
|
|
13554
|
+
const prune2 = selectPrunableDeployments(freshItems, freshLiveId, options).filter(
|
|
13555
|
+
(item) => confirmed.has(item.id)
|
|
13556
|
+
);
|
|
13557
|
+
for (const item of prune2) {
|
|
13064
13558
|
await deleteLambdaAlias(lambda, functionName, getDeploymentLambdaAliasName(item.id));
|
|
13065
13559
|
}
|
|
13066
|
-
const
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
}
|
|
13072
|
-
const versions = new Set(
|
|
13073
|
-
prunable.map((item) => item.functionVersion).filter((version) => version && !keepVersions.has(version))
|
|
13074
|
-
);
|
|
13560
|
+
const versions = await selectPrunableVersions({
|
|
13561
|
+
lambda,
|
|
13562
|
+
functionName,
|
|
13563
|
+
items: freshItems,
|
|
13564
|
+
prunable: prune2
|
|
13565
|
+
});
|
|
13075
13566
|
for (const version of versions) {
|
|
13076
|
-
|
|
13077
|
-
await lambda.send(
|
|
13078
|
-
new DeleteFunctionCommand({
|
|
13079
|
-
FunctionName: functionName,
|
|
13080
|
-
Qualifier: version
|
|
13081
|
-
})
|
|
13082
|
-
);
|
|
13083
|
-
} catch (error) {
|
|
13084
|
-
if (!isError(error, "ResourceNotFoundException") && !isError(error, "ResourceConflictException")) {
|
|
13085
|
-
throw error;
|
|
13086
|
-
}
|
|
13087
|
-
}
|
|
13567
|
+
await pruneFunctionVersion(lambda, functionName, version);
|
|
13088
13568
|
}
|
|
13089
13569
|
const storeArn = await getRouteStoreArn(
|
|
13090
13570
|
cloudfront,
|
|
@@ -13098,10 +13578,10 @@ var prune = (program2) => {
|
|
|
13098
13578
|
await pruneStoreDeployments(
|
|
13099
13579
|
kvs,
|
|
13100
13580
|
storeArn,
|
|
13101
|
-
|
|
13581
|
+
prune2.map((item) => item.id)
|
|
13102
13582
|
);
|
|
13103
13583
|
}
|
|
13104
|
-
for (const item of
|
|
13584
|
+
for (const item of prune2) {
|
|
13105
13585
|
await removeDeployment(dynamo, appId, item.id);
|
|
13106
13586
|
}
|
|
13107
13587
|
})
|
|
@@ -13170,8 +13650,8 @@ program.option("--stage <string>", "The stage to use");
|
|
|
13170
13650
|
program.option("-c --no-cache", "Always build & test without the cache");
|
|
13171
13651
|
program.option("-s --skip-prompt", "Skip prompts");
|
|
13172
13652
|
program.option("-v --verbose", "Print verbose logs");
|
|
13173
|
-
program.exitOverride(() => {
|
|
13174
|
-
process.exit(
|
|
13653
|
+
program.exitOverride((error) => {
|
|
13654
|
+
process.exit(error.exitCode);
|
|
13175
13655
|
});
|
|
13176
13656
|
program.on("option:verbose", () => {
|
|
13177
13657
|
process.env.VERBOSE = program.opts().verbose ? "1" : void 0;
|
|
@@ -13185,4 +13665,13 @@ program.on("option:no-cache", () => {
|
|
|
13185
13665
|
commands10.forEach((fn) => fn(program));
|
|
13186
13666
|
|
|
13187
13667
|
// src/bin.ts
|
|
13668
|
+
var interrupt = (signal, code) => () => {
|
|
13669
|
+
process.stdout.write("\x1B[?25h");
|
|
13670
|
+
if (signal === "SIGINT" && process.listenerCount(signal) > 1) {
|
|
13671
|
+
return;
|
|
13672
|
+
}
|
|
13673
|
+
process.exit(code);
|
|
13674
|
+
};
|
|
13675
|
+
process.on("SIGINT", interrupt("SIGINT", 130));
|
|
13676
|
+
process.on("SIGTERM", interrupt("SIGTERM", 143));
|
|
13188
13677
|
program.parse(process.argv);
|