@awsless/cli 0.0.46-next.1 → 0.0.46-next.3
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.js +263 -109
- package/dist/handlers/bundle.mjs +2 -138
- package/dist/handlers/on-failure.mjs +86 -8
- package/package.json +7 -7
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,
|
|
@@ -366,6 +366,7 @@ import {
|
|
|
366
366
|
DeleteFunctionUrlConfigCommand,
|
|
367
367
|
GetAliasCommand,
|
|
368
368
|
LambdaClient,
|
|
369
|
+
ListAliasesCommand,
|
|
369
370
|
UpdateAliasCommand,
|
|
370
371
|
UpdateFunctionCodeCommand
|
|
371
372
|
} from "@aws-sdk/client-lambda";
|
|
@@ -386,6 +387,22 @@ var getLambdaAlias = async (lambda, functionName, name) => {
|
|
|
386
387
|
return;
|
|
387
388
|
}
|
|
388
389
|
};
|
|
390
|
+
var listLambdaAliases = async (lambda, functionName, functionVersion) => {
|
|
391
|
+
const aliases = [];
|
|
392
|
+
let marker;
|
|
393
|
+
do {
|
|
394
|
+
const result = await lambda.send(
|
|
395
|
+
new ListAliasesCommand({
|
|
396
|
+
FunctionName: functionName,
|
|
397
|
+
FunctionVersion: functionVersion,
|
|
398
|
+
Marker: marker
|
|
399
|
+
})
|
|
400
|
+
);
|
|
401
|
+
aliases.push(...result.Aliases ?? []);
|
|
402
|
+
marker = result.NextMarker;
|
|
403
|
+
} while (marker);
|
|
404
|
+
return aliases;
|
|
405
|
+
};
|
|
389
406
|
var upsertLambdaAlias = async (lambda, props) => {
|
|
390
407
|
const input = {
|
|
391
408
|
FunctionName: props.functionName,
|
|
@@ -528,6 +545,8 @@ import {
|
|
|
528
545
|
CreateAliasCommand as CreateAliasCommand2,
|
|
529
546
|
CreateFunctionUrlConfigCommand,
|
|
530
547
|
GetFunctionUrlConfigCommand,
|
|
548
|
+
RemovePermissionCommand,
|
|
549
|
+
UpdateFunctionUrlConfigCommand,
|
|
531
550
|
LambdaClient as LambdaClient2,
|
|
532
551
|
PutFunctionEventInvokeConfigCommand
|
|
533
552
|
} from "@aws-sdk/client-lambda";
|
|
@@ -559,7 +578,8 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
559
578
|
deploymentId: z2.string(),
|
|
560
579
|
functionName: z2.string(),
|
|
561
580
|
functionVersion: z2.string(),
|
|
562
|
-
onFailureArn: z2.string()
|
|
581
|
+
onFailureArn: z2.string(),
|
|
582
|
+
sourceAccount: z2.string().optional()
|
|
563
583
|
});
|
|
564
584
|
const bundleDeploymentStateSchema = bundleDeploymentInputSchema.extend({
|
|
565
585
|
deploymentAlias: z2.string(),
|
|
@@ -596,14 +616,15 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
596
616
|
})
|
|
597
617
|
);
|
|
598
618
|
};
|
|
599
|
-
const
|
|
619
|
+
const createDeploymentUrl = async (functionName, alias, sourceAccount) => {
|
|
620
|
+
const authType = sourceAccount ? "AWS_IAM" : "NONE";
|
|
600
621
|
let url;
|
|
601
622
|
try {
|
|
602
623
|
const result = await lambda.send(
|
|
603
624
|
new CreateFunctionUrlConfigCommand({
|
|
604
625
|
FunctionName: functionName,
|
|
605
626
|
Qualifier: alias,
|
|
606
|
-
AuthType:
|
|
627
|
+
AuthType: authType
|
|
607
628
|
})
|
|
608
629
|
);
|
|
609
630
|
url = result.FunctionUrl;
|
|
@@ -612,21 +633,39 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
612
633
|
throw error;
|
|
613
634
|
}
|
|
614
635
|
const result = await lambda.send(
|
|
615
|
-
new
|
|
636
|
+
new UpdateFunctionUrlConfigCommand({
|
|
616
637
|
FunctionName: functionName,
|
|
617
|
-
Qualifier: alias
|
|
638
|
+
Qualifier: alias,
|
|
639
|
+
AuthType: authType
|
|
618
640
|
})
|
|
619
641
|
);
|
|
620
642
|
url = result.FunctionUrl;
|
|
621
643
|
}
|
|
622
|
-
const permissions = [
|
|
644
|
+
const permissions = sourceAccount ? [
|
|
645
|
+
{
|
|
646
|
+
StatementId: "cloudfront-url",
|
|
647
|
+
Principal: "cloudfront.amazonaws.com",
|
|
648
|
+
SourceAccount: sourceAccount,
|
|
649
|
+
Action: "lambda:InvokeFunctionUrl",
|
|
650
|
+
FunctionUrlAuthType: "AWS_IAM"
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
StatementId: "cloudfront-invoke",
|
|
654
|
+
Principal: "cloudfront.amazonaws.com",
|
|
655
|
+
SourceAccount: sourceAccount,
|
|
656
|
+
Action: "lambda:InvokeFunction",
|
|
657
|
+
InvokedViaFunctionUrl: true
|
|
658
|
+
}
|
|
659
|
+
] : [
|
|
623
660
|
{
|
|
624
661
|
StatementId: "public-url",
|
|
662
|
+
Principal: "*",
|
|
625
663
|
Action: "lambda:InvokeFunctionUrl",
|
|
626
664
|
FunctionUrlAuthType: "NONE"
|
|
627
665
|
},
|
|
628
666
|
{
|
|
629
667
|
StatementId: "public-invoke",
|
|
668
|
+
Principal: "*",
|
|
630
669
|
Action: "lambda:InvokeFunction",
|
|
631
670
|
InvokedViaFunctionUrl: true
|
|
632
671
|
}
|
|
@@ -637,7 +676,6 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
637
676
|
new AddPermissionCommand({
|
|
638
677
|
FunctionName: functionName,
|
|
639
678
|
Qualifier: alias,
|
|
640
|
-
Principal: "*",
|
|
641
679
|
...permission
|
|
642
680
|
})
|
|
643
681
|
);
|
|
@@ -647,6 +685,23 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
647
685
|
}
|
|
648
686
|
}
|
|
649
687
|
}
|
|
688
|
+
if (sourceAccount) {
|
|
689
|
+
for (const statementId of ["public-url", "public-invoke"]) {
|
|
690
|
+
try {
|
|
691
|
+
await lambda.send(
|
|
692
|
+
new RemovePermissionCommand({
|
|
693
|
+
FunctionName: functionName,
|
|
694
|
+
Qualifier: alias,
|
|
695
|
+
StatementId: statementId
|
|
696
|
+
})
|
|
697
|
+
);
|
|
698
|
+
} catch (error) {
|
|
699
|
+
if (!isError(error, "ResourceNotFoundException")) {
|
|
700
|
+
throw error;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
650
705
|
return url;
|
|
651
706
|
};
|
|
652
707
|
const createBundleDeployment = async (state2) => {
|
|
@@ -658,7 +713,7 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
658
713
|
name: deploymentAlias
|
|
659
714
|
});
|
|
660
715
|
await configureVersion(state2);
|
|
661
|
-
const url = await
|
|
716
|
+
const url = await createDeploymentUrl(state2.functionName, deploymentAlias, state2.sourceAccount);
|
|
662
717
|
return {
|
|
663
718
|
...state2,
|
|
664
719
|
deploymentAlias,
|
|
@@ -769,7 +824,21 @@ var createLambdaProvider = ({ credentials, region }) => {
|
|
|
769
824
|
deploymentAliases.push(deploymentAlias);
|
|
770
825
|
}
|
|
771
826
|
await configureVersion(proposed);
|
|
772
|
-
const url = await
|
|
827
|
+
const url = await createDeploymentUrl(proposed.functionName, deploymentAlias, proposed.sourceAccount);
|
|
828
|
+
if (proposed.sourceAccount && prior.sourceAccount !== proposed.sourceAccount) {
|
|
829
|
+
for (const name of deploymentAliases) {
|
|
830
|
+
if (name === deploymentAlias) {
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
await createDeploymentUrl(proposed.functionName, name, proposed.sourceAccount);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
if (!isError(error, "ResourceNotFoundException")) {
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
773
842
|
return {
|
|
774
843
|
...proposed,
|
|
775
844
|
deploymentAlias,
|
|
@@ -1259,6 +1328,60 @@ var markPromoted = async (client, appId, id) => {
|
|
|
1259
1328
|
var removeDeployment = async (client, appId, id) => {
|
|
1260
1329
|
await deleteItem(table, { appId, id }, { client });
|
|
1261
1330
|
};
|
|
1331
|
+
var selectPrunableDeployments = (items, liveId, options) => {
|
|
1332
|
+
const rollbackTarget = items.filter((item) => item.promotedAt && item.id !== liveId).sort((a, b) => b.promotedAt.localeCompare(a.promotedAt))[0];
|
|
1333
|
+
const keep = Math.max(1, Number(options.keep) || 10);
|
|
1334
|
+
const mainSlug = slugifyBranch(options.main);
|
|
1335
|
+
const keptMain = new Set(
|
|
1336
|
+
items.filter((item) => item.branch === mainSlug && item.functionVersion).map((item) => item.seq).sort((a, b) => b - a).slice(0, keep)
|
|
1337
|
+
);
|
|
1338
|
+
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
1339
|
+
return items.filter((item) => {
|
|
1340
|
+
if (item.id === liveId || item.id === rollbackTarget?.id) {
|
|
1341
|
+
return false;
|
|
1342
|
+
}
|
|
1343
|
+
if (options.branch) {
|
|
1344
|
+
return item.branch === slugifyBranch(options.branch);
|
|
1345
|
+
}
|
|
1346
|
+
if (!item.functionVersion) {
|
|
1347
|
+
return item.createdAt < dayAgo;
|
|
1348
|
+
}
|
|
1349
|
+
if (item.branch === mainSlug) {
|
|
1350
|
+
return !keptMain.has(item.seq);
|
|
1351
|
+
}
|
|
1352
|
+
return item.commit ? isCommitMerged(item.commit, options.main) : false;
|
|
1353
|
+
});
|
|
1354
|
+
};
|
|
1355
|
+
var pruneFunctionVersion = async (lambda, functionName, version) => {
|
|
1356
|
+
for (const alias of await listLambdaAliases(lambda, functionName, version)) {
|
|
1357
|
+
if (alias.Name && alias.Name !== LIVE_LAMBDA_ALIAS) {
|
|
1358
|
+
await deleteLambdaAlias(lambda, functionName, alias.Name);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
try {
|
|
1362
|
+
await lambda.send(
|
|
1363
|
+
new DeleteFunctionCommand({
|
|
1364
|
+
FunctionName: functionName,
|
|
1365
|
+
Qualifier: version
|
|
1366
|
+
})
|
|
1367
|
+
);
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
if (!isError(error, "ResourceNotFoundException")) {
|
|
1370
|
+
throw error;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
var selectPrunableVersions = async (props) => {
|
|
1375
|
+
const surviving = props.items.filter((item) => !props.prunable.includes(item));
|
|
1376
|
+
const kept = new Set(surviving.map((item) => item.functionVersion));
|
|
1377
|
+
const live = await getLambdaAlias(props.lambda, props.functionName, LIVE_LAMBDA_ALIAS);
|
|
1378
|
+
if (live?.FunctionVersion) {
|
|
1379
|
+
kept.add(live.FunctionVersion);
|
|
1380
|
+
}
|
|
1381
|
+
return new Set(
|
|
1382
|
+
props.prunable.map((item) => item.functionVersion).filter((version) => Boolean(version && !kept.has(version)))
|
|
1383
|
+
);
|
|
1384
|
+
};
|
|
1262
1385
|
var readLiveDeploymentId = async (lambda, functionName) => {
|
|
1263
1386
|
return (await getLambdaAlias(lambda, functionName, LIVE_LAMBDA_ALIAS))?.Description || void 0;
|
|
1264
1387
|
};
|
|
@@ -1275,21 +1398,21 @@ var readDeployedFunctionVersion = (state2) => {
|
|
|
1275
1398
|
return;
|
|
1276
1399
|
};
|
|
1277
1400
|
var formatDeploymentSummary = (props) => {
|
|
1278
|
-
|
|
1279
|
-
let lambdaUrl;
|
|
1401
|
+
const previewUrls = /* @__PURE__ */ new Map();
|
|
1280
1402
|
for (const [urn, node] of readStateNodes(props.state)) {
|
|
1281
1403
|
if (node.type === "aws_cloudfront_distribution" && urn.endsWith(":{preview}")) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1404
|
+
const router = urn.match(/router:\{([^}]+)\}/)?.[1];
|
|
1405
|
+
if (router) {
|
|
1406
|
+
previewUrls.set(router, `https://${node.output.domainName}`);
|
|
1407
|
+
}
|
|
1286
1408
|
}
|
|
1287
1409
|
}
|
|
1288
|
-
return Object.keys(props.appConfig.defaults.router ?? {}).map((routerId
|
|
1410
|
+
return Object.keys(props.appConfig.defaults.router ?? {}).map((routerId) => {
|
|
1411
|
+
const previewUrl = previewUrls.get(routerId);
|
|
1289
1412
|
return [
|
|
1290
1413
|
`${routerId}: deployment #${props.id}`,
|
|
1291
|
-
|
|
1292
|
-
|
|
1414
|
+
previewUrl,
|
|
1415
|
+
previewUrl ? `${previewUrl}/?awsless-deployment=${props.id}` : void 0
|
|
1293
1416
|
].filter(Boolean).join("\n");
|
|
1294
1417
|
});
|
|
1295
1418
|
};
|
|
@@ -3843,7 +3966,6 @@ var bundleTypeScriptWithRolldown = async ({
|
|
|
3843
3966
|
format: format3 = "esm",
|
|
3844
3967
|
minify = true,
|
|
3845
3968
|
file,
|
|
3846
|
-
nativeDir,
|
|
3847
3969
|
external,
|
|
3848
3970
|
importAsString: importAsStringList
|
|
3849
3971
|
}) => {
|
|
@@ -3854,7 +3976,10 @@ var bundleTypeScriptWithRolldown = async ({
|
|
|
3854
3976
|
return importee.startsWith("@aws-sdk") || importee.startsWith("aws-sdk") || external?.includes(importee);
|
|
3855
3977
|
},
|
|
3856
3978
|
treeshake: {
|
|
3857
|
-
|
|
3979
|
+
// Dependencies are treated as side-effect free, so unused imports
|
|
3980
|
+
// like the local test servers never reach the production bundle.
|
|
3981
|
+
// The bundle guard below fails the build if one slips through.
|
|
3982
|
+
moduleSideEffects: (id, isExternal) => isExternal ? external?.includes(id) === true : id.startsWith(`${directories.root}/`) && !id.includes("/node_modules/")
|
|
3858
3983
|
},
|
|
3859
3984
|
onwarn: (error) => {
|
|
3860
3985
|
debugError(error.message);
|
|
@@ -3901,6 +4026,7 @@ var bundleTypeScriptWithRolldown = async ({
|
|
|
3901
4026
|
chunkFileNames: `[name].${ext}`,
|
|
3902
4027
|
minify
|
|
3903
4028
|
});
|
|
4029
|
+
assertNoTestOnlyModules(result.output);
|
|
3904
4030
|
const hash = createHash4("sha1");
|
|
3905
4031
|
const files = [];
|
|
3906
4032
|
for (const item of result.output) {
|
|
@@ -3922,6 +4048,22 @@ var bundleTypeScriptWithRolldown = async ({
|
|
|
3922
4048
|
files
|
|
3923
4049
|
};
|
|
3924
4050
|
};
|
|
4051
|
+
var TEST_ONLY_MODULES = ["dynamo-db-local", "@awsless/dynamodb-server", "redis-memory-server"];
|
|
4052
|
+
var assertNoTestOnlyModules = (output) => {
|
|
4053
|
+
for (const item of output) {
|
|
4054
|
+
if (item.type !== "chunk") {
|
|
4055
|
+
continue;
|
|
4056
|
+
}
|
|
4057
|
+
for (const id of item.moduleIds ?? []) {
|
|
4058
|
+
const found = TEST_ONLY_MODULES.find((name) => id.includes(`/node_modules/${name}/`));
|
|
4059
|
+
if (found) {
|
|
4060
|
+
throw new Error(
|
|
4061
|
+
`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.`
|
|
4062
|
+
);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
};
|
|
3925
4067
|
|
|
3926
4068
|
// src/feature/bundle/util.ts
|
|
3927
4069
|
var ROUTE_HEADER = "x-awsless-route";
|
|
@@ -4038,6 +4180,11 @@ var bundleFeature = defineFeature({
|
|
|
4038
4180
|
const addLayer = (layer) => {
|
|
4039
4181
|
layers.push(layer);
|
|
4040
4182
|
};
|
|
4183
|
+
const layerIds = Object.keys(ctx.appConfig.defaults.layers ?? {});
|
|
4184
|
+
const layerPackages = layerIds.flatMap((id) => ctx.shared.entry("layer", "packages", id));
|
|
4185
|
+
for (const id of layerIds) {
|
|
4186
|
+
addLayer(ctx.shared.entry("layer", "arn", id));
|
|
4187
|
+
}
|
|
4041
4188
|
const name = getBundleFunctionName(ctx.app.name);
|
|
4042
4189
|
const shortName = formatGlobalResourceName({
|
|
4043
4190
|
appName: ctx.app.name,
|
|
@@ -4051,7 +4198,7 @@ var bundleFeature = defineFeature({
|
|
|
4051
4198
|
name,
|
|
4052
4199
|
handlers,
|
|
4053
4200
|
minify: defaults.minify,
|
|
4054
|
-
external: defaults.external
|
|
4201
|
+
external: [...defaults.external ?? [], ...layerPackages]
|
|
4055
4202
|
})
|
|
4056
4203
|
);
|
|
4057
4204
|
const sourceHash = new Output3(envDeps, async (resolve2) => {
|
|
@@ -4207,7 +4354,8 @@ var bundleFeature = defineFeature({
|
|
|
4207
4354
|
deploymentId: ctx.deploymentId ?? "local-0",
|
|
4208
4355
|
functionName: lambda.functionName,
|
|
4209
4356
|
functionVersion: lambda.version,
|
|
4210
|
-
onFailureArn: onFailure
|
|
4357
|
+
onFailureArn: onFailure,
|
|
4358
|
+
sourceAccount: ctx.accountId
|
|
4211
4359
|
},
|
|
4212
4360
|
{
|
|
4213
4361
|
// Make sure the permissions are in place before any event source is wired up.
|
|
@@ -6922,14 +7070,15 @@ var siteFeature = defineFeature({
|
|
|
6922
7070
|
env,
|
|
6923
7071
|
stdout: "pipe",
|
|
6924
7072
|
stderr: "pipe"
|
|
6925
|
-
// stdout: 'ignore',
|
|
6926
|
-
// stderr: ''
|
|
6927
|
-
// stdout: 'inherit',
|
|
6928
|
-
// stderr: 'inherit',
|
|
6929
7073
|
});
|
|
6930
|
-
await
|
|
7074
|
+
const [output, errors] = await Promise.all([
|
|
7075
|
+
new Response(instance.stdout).text(),
|
|
7076
|
+
new Response(instance.stderr).text(),
|
|
7077
|
+
instance.exited
|
|
7078
|
+
]);
|
|
6931
7079
|
if (instance.exitCode !== null && instance.exitCode > 0) {
|
|
6932
|
-
throw new
|
|
7080
|
+
throw new ExpectedError(`Site build failed:
|
|
7081
|
+
${(errors.trim() || output.trim()).slice(-2e3)}`);
|
|
6933
7082
|
}
|
|
6934
7083
|
await write("HASH", fingerprint);
|
|
6935
7084
|
return {
|
|
@@ -9117,7 +9266,7 @@ var getViewerRequestFunctionCode = (props) => {
|
|
|
9117
9266
|
].join("\n")
|
|
9118
9267
|
) : ""
|
|
9119
9268
|
],
|
|
9120
|
-
ACTIVE_PREFIX(props.router)
|
|
9269
|
+
props.preview ? PREVIEW_PREFIX(props.router) : ACTIVE_PREFIX(props.router)
|
|
9121
9270
|
);
|
|
9122
9271
|
};
|
|
9123
9272
|
var BLOCK_DIRECT_ACCESS_TO_CLOUDFRONT = `
|
|
@@ -9172,11 +9321,64 @@ var PASSWORD_AUTH_CHECK = (password) => `
|
|
|
9172
9321
|
authMethods.push('Password realm="Protected"');
|
|
9173
9322
|
|
|
9174
9323
|
if(!isAuthorized) {
|
|
9175
|
-
if(authHeader && authHeader.startsWith('Password ') && authHeader.slice(9) ===
|
|
9324
|
+
if(authHeader && authHeader.startsWith('Password ') && authHeader.slice(9) === ${JSON.stringify(password)}) {
|
|
9176
9325
|
isAuthorized = true;
|
|
9177
9326
|
}
|
|
9178
9327
|
}
|
|
9179
9328
|
`;
|
|
9329
|
+
var PREVIEW_PREFIX = (router) => `
|
|
9330
|
+
const router = ${JSON.stringify(router)};
|
|
9331
|
+
let deployment;
|
|
9332
|
+
|
|
9333
|
+
if (request.querystring['awsless-deployment'] && request.querystring['awsless-deployment'].value) {
|
|
9334
|
+
deployment = request.querystring['awsless-deployment'].value;
|
|
9335
|
+
} else if (request.cookies && request.cookies['awsless-deployment'] && request.cookies['awsless-deployment'].value) {
|
|
9336
|
+
deployment = request.cookies['awsless-deployment'].value;
|
|
9337
|
+
}
|
|
9338
|
+
|
|
9339
|
+
let prefix;
|
|
9340
|
+
|
|
9341
|
+
try {
|
|
9342
|
+
const pointer = deployment ? '$deploy:' + deployment : '$active';
|
|
9343
|
+
prefix = (await cf.kvs().get(pointer)).split(':')[0] + ':' + router + ':';
|
|
9344
|
+
} catch (e) {
|
|
9345
|
+
return deployment
|
|
9346
|
+
? { statusCode: 404, statusDescription: 'Unknown Deployment' }
|
|
9347
|
+
: { statusCode: 503, statusDescription: 'Service Unavailable' };
|
|
9348
|
+
}
|
|
9349
|
+
|
|
9350
|
+
if (deployment && request.querystring['awsless-deployment']) {
|
|
9351
|
+
delete request.querystring['awsless-deployment'];
|
|
9352
|
+
|
|
9353
|
+
const query = [];
|
|
9354
|
+
|
|
9355
|
+
for (const key in request.querystring) {
|
|
9356
|
+
const entry = request.querystring[key];
|
|
9357
|
+
|
|
9358
|
+
if (entry.multiValue) {
|
|
9359
|
+
// The CloudFront js runtime doesn't support for...of.
|
|
9360
|
+
for (const i in entry.multiValue) {
|
|
9361
|
+
query.push(key + '=' + entry.multiValue[i].value);
|
|
9362
|
+
}
|
|
9363
|
+
} else {
|
|
9364
|
+
query.push(key + '=' + entry.value);
|
|
9365
|
+
}
|
|
9366
|
+
}
|
|
9367
|
+
|
|
9368
|
+
return {
|
|
9369
|
+
statusCode: 302,
|
|
9370
|
+
statusDescription: 'Found',
|
|
9371
|
+
headers: {
|
|
9372
|
+
location: { value: request.uri + (query.length ? '?' + query.join('&') : '') }
|
|
9373
|
+
},
|
|
9374
|
+
cookies: {
|
|
9375
|
+
'awsless-deployment': {
|
|
9376
|
+
value: deployment,
|
|
9377
|
+
attributes: 'Path=/; Secure; SameSite=Lax'
|
|
9378
|
+
}
|
|
9379
|
+
}
|
|
9380
|
+
};
|
|
9381
|
+
}`;
|
|
9180
9382
|
var ACTIVE_PREFIX = (router) => `
|
|
9181
9383
|
const router = ${JSON.stringify(router)};
|
|
9182
9384
|
let prefix;
|
|
@@ -9450,7 +9652,6 @@ var routerFeature = defineFeature({
|
|
|
9450
9652
|
const distributionIds = [];
|
|
9451
9653
|
let hasLambdaRoutes = false;
|
|
9452
9654
|
let routeStore;
|
|
9453
|
-
let previewDistribution;
|
|
9454
9655
|
for (const [id, props] of routers) {
|
|
9455
9656
|
const group = new Group29(ctx.base, "router", id);
|
|
9456
9657
|
const name = formatGlobalResourceName({
|
|
@@ -9787,19 +9988,20 @@ var routerFeature = defineFeature({
|
|
|
9787
9988
|
],
|
|
9788
9989
|
webAclId: waf?.arn
|
|
9789
9990
|
});
|
|
9790
|
-
|
|
9991
|
+
{
|
|
9791
9992
|
const previewFunction = new aws30.cloudfront.Function(group, "preview-function", {
|
|
9792
9993
|
name: `${name.slice(0, 55)}--preview`,
|
|
9793
9994
|
runtime: "cloudfront-js-2.0",
|
|
9794
9995
|
code: getViewerRequestFunctionCode({
|
|
9795
9996
|
router: id,
|
|
9997
|
+
preview: true,
|
|
9796
9998
|
basicAuth: props.basicAuth,
|
|
9797
9999
|
passwordAuth: props.passwordAuth
|
|
9798
10000
|
}),
|
|
9799
10001
|
publish: true,
|
|
9800
10002
|
keyValueStoreAssociations: [routeStore.arn]
|
|
9801
10003
|
});
|
|
9802
|
-
previewDistribution = new aws30.cloudfront.Distribution(group, "preview", {
|
|
10004
|
+
const previewDistribution = new aws30.cloudfront.Distribution(group, "preview", {
|
|
9803
10005
|
tags: {
|
|
9804
10006
|
name: `${name}-preview`
|
|
9805
10007
|
},
|
|
@@ -9863,6 +10065,9 @@ var routerFeature = defineFeature({
|
|
|
9863
10065
|
webAclId: waf?.arn
|
|
9864
10066
|
});
|
|
9865
10067
|
distributionIds.push(previewDistribution.id);
|
|
10068
|
+
ctx.shared.add("router", "preview-id", id, previewDistribution.id);
|
|
10069
|
+
}
|
|
10070
|
+
if (id === defaultRouter) {
|
|
9866
10071
|
ctx.onReadyLast(() => {
|
|
9867
10072
|
const bundle = ctx.shared.get("bundle", "main");
|
|
9868
10073
|
let lambdaUrlHost;
|
|
@@ -9901,26 +10106,9 @@ var routerFeature = defineFeature({
|
|
|
9901
10106
|
dependsOn: Array.from(routeDependencies)
|
|
9902
10107
|
}
|
|
9903
10108
|
);
|
|
9904
|
-
if (!(props.basicAuth ?? props.passwordAuth)) {
|
|
9905
|
-
bundle.addEnv(
|
|
9906
|
-
"AWSLESS_PREVIEW",
|
|
9907
|
-
$resolve(
|
|
9908
|
-
[routes],
|
|
9909
|
-
(routes2) => JSON.stringify({
|
|
9910
|
-
router: id,
|
|
9911
|
-
routes: Object.fromEntries(
|
|
9912
|
-
Object.entries(routes2).filter(
|
|
9913
|
-
([key, route]) => key.startsWith(`${id}:`) && route.type !== "url"
|
|
9914
|
-
)
|
|
9915
|
-
)
|
|
9916
|
-
})
|
|
9917
|
-
)
|
|
9918
|
-
);
|
|
9919
|
-
}
|
|
9920
10109
|
});
|
|
9921
10110
|
}
|
|
9922
10111
|
ctx.shared.add("router", "id", id, distribution.id);
|
|
9923
|
-
ctx.shared.add("router", "preview-id", id, previewDistribution.id);
|
|
9924
10112
|
distributionIds.push(distribution.id);
|
|
9925
10113
|
if (props.domain) {
|
|
9926
10114
|
const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
|
|
@@ -11483,9 +11671,10 @@ var deploy = (program2) => {
|
|
|
11483
11671
|
}
|
|
11484
11672
|
});
|
|
11485
11673
|
playSuccessSound();
|
|
11486
|
-
const
|
|
11487
|
-
|
|
11488
|
-
|
|
11674
|
+
for (const summary of deployments2) {
|
|
11675
|
+
log20.message(summary);
|
|
11676
|
+
}
|
|
11677
|
+
return `Deployment #${deployment.id} is live.`;
|
|
11489
11678
|
});
|
|
11490
11679
|
});
|
|
11491
11680
|
};
|
|
@@ -11976,7 +12165,7 @@ var bind = (program2) => {
|
|
|
11976
12165
|
stderr: "inherit"
|
|
11977
12166
|
});
|
|
11978
12167
|
await instance.exited;
|
|
11979
|
-
process.exit(
|
|
12168
|
+
process.exit(instance.exitCode ?? 1);
|
|
11980
12169
|
});
|
|
11981
12170
|
});
|
|
11982
12171
|
};
|
|
@@ -12096,7 +12285,7 @@ var resources = (program2) => {
|
|
|
12096
12285
|
return `${color.dim("{")}${color.warning(v)}${color.dim("}")}`;
|
|
12097
12286
|
}).replaceAll(":", color.dim(":"));
|
|
12098
12287
|
};
|
|
12099
|
-
const
|
|
12288
|
+
const formatStatus2 = (status) => {
|
|
12100
12289
|
if (status === "created") {
|
|
12101
12290
|
return color.success(status);
|
|
12102
12291
|
}
|
|
@@ -12121,7 +12310,7 @@ var resources = (program2) => {
|
|
|
12121
12310
|
stack.resources.map((r) => {
|
|
12122
12311
|
return [
|
|
12123
12312
|
//
|
|
12124
|
-
|
|
12313
|
+
formatStatus2(r.status),
|
|
12125
12314
|
color.dim(icon.arrow.right),
|
|
12126
12315
|
formatResource(stack.urn, r.urn)
|
|
12127
12316
|
].join(" ");
|
|
@@ -13047,7 +13236,7 @@ var activity = (program2) => {
|
|
|
13047
13236
|
// src/cli/command/deployment.ts
|
|
13048
13237
|
import { CloudFrontClient as CloudFrontClient6 } from "@aws-sdk/client-cloudfront";
|
|
13049
13238
|
import { CloudFrontKeyValueStoreClient as CloudFrontKeyValueStoreClient3 } from "@aws-sdk/client-cloudfront-keyvaluestore";
|
|
13050
|
-
import {
|
|
13239
|
+
import { LambdaClient as LambdaClient7 } from "@aws-sdk/client-lambda";
|
|
13051
13240
|
import { log as log35, prompt as prompt19 } from "@awsless/clui";
|
|
13052
13241
|
import { DynamoDBClient as DynamoDBClient6 } from "@awsless/dynamodb";
|
|
13053
13242
|
var createClients = async (appConfig) => {
|
|
@@ -13070,6 +13259,12 @@ var formatAge = (iso) => {
|
|
|
13070
13259
|
if (minutes8 < 60 * 24) return `${Math.floor(minutes8 / 60)}h ago`;
|
|
13071
13260
|
return `${Math.floor(minutes8 / (60 * 24))}d ago`;
|
|
13072
13261
|
};
|
|
13262
|
+
var formatStatus = (item, liveId) => {
|
|
13263
|
+
if (item.id === liveId) return color.success("live ");
|
|
13264
|
+
if (item.promotedAt) return "promoted";
|
|
13265
|
+
if (item.functionVersion) return color.info("staged ");
|
|
13266
|
+
return color.dim("pending ");
|
|
13267
|
+
};
|
|
13073
13268
|
var deployments = (program2) => {
|
|
13074
13269
|
program2.command("deployments").description("List the deployment history of your app").action(async () => {
|
|
13075
13270
|
await layout("deployments", async ({ appConfig }) => {
|
|
@@ -13083,17 +13278,16 @@ var deployments = (program2) => {
|
|
|
13083
13278
|
}
|
|
13084
13279
|
const idWidth = Math.max(...items.map((item) => item.id.length));
|
|
13085
13280
|
log35.message(
|
|
13086
|
-
items.map(
|
|
13087
|
-
|
|
13088
|
-
return [
|
|
13281
|
+
items.map(
|
|
13282
|
+
(item) => [
|
|
13089
13283
|
color.label(item.id.padEnd(idWidth)),
|
|
13090
|
-
|
|
13284
|
+
formatStatus(item, liveId),
|
|
13091
13285
|
formatAge(item.createdAt).padEnd(8),
|
|
13092
13286
|
color.dim(item.commit?.slice(0, 7) ?? "-------"),
|
|
13093
13287
|
(item.message ?? "").slice(0, 50).padEnd(50),
|
|
13094
13288
|
color.dim(item.user ?? "")
|
|
13095
|
-
].join(" ")
|
|
13096
|
-
|
|
13289
|
+
].join(" ")
|
|
13290
|
+
).join("\n")
|
|
13097
13291
|
);
|
|
13098
13292
|
return `Found ${items.length} deployments.`;
|
|
13099
13293
|
});
|
|
@@ -13107,28 +13301,7 @@ var prune = (program2) => {
|
|
|
13107
13301
|
listDeployments(dynamo, appId),
|
|
13108
13302
|
readLiveDeploymentId(lambda, functionName)
|
|
13109
13303
|
]);
|
|
13110
|
-
const
|
|
13111
|
-
const keep = Math.max(1, Number(options.keep) || 10);
|
|
13112
|
-
const mainSlug = slugifyBranch(options.main);
|
|
13113
|
-
const keptMain = new Set(
|
|
13114
|
-
items.filter((item) => item.branch === mainSlug && item.functionVersion).map((item) => item.seq).sort((a, b) => b - a).slice(0, keep)
|
|
13115
|
-
);
|
|
13116
|
-
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
13117
|
-
const prunable = items.filter((item) => {
|
|
13118
|
-
if (item.id === liveId || item.id === rollbackTarget?.id) {
|
|
13119
|
-
return false;
|
|
13120
|
-
}
|
|
13121
|
-
if (options.branch) {
|
|
13122
|
-
return item.branch === slugifyBranch(options.branch);
|
|
13123
|
-
}
|
|
13124
|
-
if (!item.functionVersion) {
|
|
13125
|
-
return item.createdAt < dayAgo;
|
|
13126
|
-
}
|
|
13127
|
-
if (item.branch === mainSlug) {
|
|
13128
|
-
return !keptMain.has(item.seq);
|
|
13129
|
-
}
|
|
13130
|
-
return item.commit ? isCommitMerged(item.commit, options.main) : false;
|
|
13131
|
-
});
|
|
13304
|
+
const prunable = selectPrunableDeployments(items, liveId, options);
|
|
13132
13305
|
if (prunable.length === 0) {
|
|
13133
13306
|
return `Nothing to prune.`;
|
|
13134
13307
|
}
|
|
@@ -13148,28 +13321,9 @@ var prune = (program2) => {
|
|
|
13148
13321
|
for (const item of prunable) {
|
|
13149
13322
|
await deleteLambdaAlias(lambda, functionName, getDeploymentLambdaAliasName(item.id));
|
|
13150
13323
|
}
|
|
13151
|
-
const
|
|
13152
|
-
const keepVersions = new Set(surviving.map((item) => item.functionVersion));
|
|
13153
|
-
const live = await getLambdaAlias(lambda, functionName, LIVE_LAMBDA_ALIAS);
|
|
13154
|
-
if (live?.FunctionVersion) {
|
|
13155
|
-
keepVersions.add(live.FunctionVersion);
|
|
13156
|
-
}
|
|
13157
|
-
const versions = new Set(
|
|
13158
|
-
prunable.map((item) => item.functionVersion).filter((version) => version && !keepVersions.has(version))
|
|
13159
|
-
);
|
|
13324
|
+
const versions = await selectPrunableVersions({ lambda, functionName, items, prunable });
|
|
13160
13325
|
for (const version of versions) {
|
|
13161
|
-
|
|
13162
|
-
await lambda.send(
|
|
13163
|
-
new DeleteFunctionCommand({
|
|
13164
|
-
FunctionName: functionName,
|
|
13165
|
-
Qualifier: version
|
|
13166
|
-
})
|
|
13167
|
-
);
|
|
13168
|
-
} catch (error) {
|
|
13169
|
-
if (!isError(error, "ResourceNotFoundException") && !isError(error, "ResourceConflictException")) {
|
|
13170
|
-
throw error;
|
|
13171
|
-
}
|
|
13172
|
-
}
|
|
13326
|
+
await pruneFunctionVersion(lambda, functionName, version);
|
|
13173
13327
|
}
|
|
13174
13328
|
const storeArn = await getRouteStoreArn(
|
|
13175
13329
|
cloudfront,
|
|
@@ -13255,8 +13409,8 @@ program.option("--stage <string>", "The stage to use");
|
|
|
13255
13409
|
program.option("-c --no-cache", "Always build & test without the cache");
|
|
13256
13410
|
program.option("-s --skip-prompt", "Skip prompts");
|
|
13257
13411
|
program.option("-v --verbose", "Print verbose logs");
|
|
13258
|
-
program.exitOverride(() => {
|
|
13259
|
-
process.exit(
|
|
13412
|
+
program.exitOverride((error) => {
|
|
13413
|
+
process.exit(error.exitCode);
|
|
13260
13414
|
});
|
|
13261
13415
|
program.on("option:verbose", () => {
|
|
13262
13416
|
process.env.VERBOSE = program.opts().verbose ? "1" : void 0;
|
package/dist/handlers/bundle.mjs
CHANGED
|
@@ -3,128 +3,6 @@ import { patch, unpatch } from "@awsless/json";
|
|
|
3
3
|
import { ExpectedError, invoke, isErrorResponse } from "@awsless/lambda";
|
|
4
4
|
import { formatRoutePayload, getCurrentRoute, withRoute } from "awsless";
|
|
5
5
|
|
|
6
|
-
// src/feature/bundle/server/preview.ts
|
|
7
|
-
import { GetObjectCommand, NoSuchKey } from "@aws-sdk/client-s3";
|
|
8
|
-
import { s3Client } from "@awsless/s3";
|
|
9
|
-
var getPossibleRouteKeys = (path) => {
|
|
10
|
-
if (path === "" || path === "/") {
|
|
11
|
-
return ["/", "/*"];
|
|
12
|
-
}
|
|
13
|
-
const parts = path.split("/");
|
|
14
|
-
const root = path.startsWith("/") ? parts[1] : parts[0];
|
|
15
|
-
const file = parts[parts.length - 1].includes(".");
|
|
16
|
-
if (root.includes(".")) {
|
|
17
|
-
return [path, "/*.", "/*"];
|
|
18
|
-
}
|
|
19
|
-
if (file) {
|
|
20
|
-
return [path, "/" + root + "/*.", "/" + root + "/*", "/*.", "/*"];
|
|
21
|
-
}
|
|
22
|
-
return [path, "/" + root + "/*", "/*"];
|
|
23
|
-
};
|
|
24
|
-
var findRoute = (props, path, method) => {
|
|
25
|
-
for (const key of getPossibleRouteKeys(path)) {
|
|
26
|
-
const route = props.routes[`${props.router}:${key}`];
|
|
27
|
-
if (!route) {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
if (route.type === "s3" && method !== "GET" && method !== "HEAD") {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
return route;
|
|
34
|
-
}
|
|
35
|
-
return;
|
|
36
|
-
};
|
|
37
|
-
var rewritePath = (route, path) => {
|
|
38
|
-
if (!route.rewrite) {
|
|
39
|
-
return path;
|
|
40
|
-
}
|
|
41
|
-
if (route.rewrite.regex) {
|
|
42
|
-
return path.replace(new RegExp(route.rewrite.regex), route.rewrite.to);
|
|
43
|
-
}
|
|
44
|
-
return route.rewrite.to;
|
|
45
|
-
};
|
|
46
|
-
var serveObject = async (route, path, method) => {
|
|
47
|
-
const bucket = route.domainName.split(".s3")[0];
|
|
48
|
-
const key = rewritePath(route, path).replace(/^\//, "");
|
|
49
|
-
let result;
|
|
50
|
-
try {
|
|
51
|
-
result = await s3Client().send(new GetObjectCommand({
|
|
52
|
-
Bucket: bucket,
|
|
53
|
-
Key: key
|
|
54
|
-
}));
|
|
55
|
-
} catch (error) {
|
|
56
|
-
if (error instanceof NoSuchKey) {
|
|
57
|
-
return {
|
|
58
|
-
statusCode: 404
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
const headers = {};
|
|
64
|
-
if (result.ContentType) {
|
|
65
|
-
headers["content-type"] = result.ContentType;
|
|
66
|
-
}
|
|
67
|
-
if (result.CacheControl) {
|
|
68
|
-
headers["cache-control"] = result.CacheControl;
|
|
69
|
-
}
|
|
70
|
-
if (result.ETag) {
|
|
71
|
-
headers["etag"] = result.ETag;
|
|
72
|
-
}
|
|
73
|
-
if (method === "HEAD") {
|
|
74
|
-
return { statusCode: 200, headers };
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
statusCode: 200,
|
|
78
|
-
headers,
|
|
79
|
-
body: await result.Body.transformToString("base64"),
|
|
80
|
-
isBase64Encoded: true
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
var createPreviewHandler = (props) => {
|
|
84
|
-
return async (event) => {
|
|
85
|
-
const method = event.requestContext.http.method;
|
|
86
|
-
const headers = event.headers ?? {};
|
|
87
|
-
let path = event.rawPath;
|
|
88
|
-
try {
|
|
89
|
-
path = decodeURIComponent(path);
|
|
90
|
-
} catch {}
|
|
91
|
-
if (method === "OPTIONS") {
|
|
92
|
-
return {
|
|
93
|
-
statusCode: 204,
|
|
94
|
-
headers: {
|
|
95
|
-
"access-control-allow-origin": "*",
|
|
96
|
-
"access-control-allow-methods": "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
97
|
-
"access-control-allow-headers": "*",
|
|
98
|
-
"access-control-max-age": "86400"
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
const route = findRoute(props, path, method);
|
|
103
|
-
if (!route) {
|
|
104
|
-
return {
|
|
105
|
-
statusCode: 404
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
if (route.type === "s3") {
|
|
109
|
-
return serveObject(route, path, method);
|
|
110
|
-
}
|
|
111
|
-
for (const [name, value] of Object.entries(route.requestHeaders ?? {})) {
|
|
112
|
-
headers[name] = value;
|
|
113
|
-
}
|
|
114
|
-
if (headers.authorization) {
|
|
115
|
-
headers["x-awsless-authorization"] = headers.authorization;
|
|
116
|
-
} else {
|
|
117
|
-
delete headers["x-awsless-authorization"];
|
|
118
|
-
}
|
|
119
|
-
if (route.forwardHost && headers.host) {
|
|
120
|
-
headers["x-forwarded-host"] = headers.host;
|
|
121
|
-
}
|
|
122
|
-
event.headers = headers;
|
|
123
|
-
event.rawPath = rewritePath(route, path);
|
|
124
|
-
return props.dispatch(event);
|
|
125
|
-
};
|
|
126
|
-
};
|
|
127
|
-
|
|
128
6
|
// src/feature/bundle/server/resource/util.ts
|
|
129
7
|
var asyncRoute = (key, payload) => {
|
|
130
8
|
process.env.THROW_EXPECTED_ERRORS = "1";
|
|
@@ -434,7 +312,6 @@ var topicHandler = (event, routes) => {
|
|
|
434
312
|
// src/feature/bundle/server/handle.ts
|
|
435
313
|
var createBundle = (handlers) => {
|
|
436
314
|
const routes = Object.keys(handlers);
|
|
437
|
-
let previewConfig;
|
|
438
315
|
const matchers = [
|
|
439
316
|
functionHandler,
|
|
440
317
|
cronHandler,
|
|
@@ -473,7 +350,8 @@ var createBundle = (handlers) => {
|
|
|
473
350
|
process.env.AWSLESS_ROUTE = match2.key;
|
|
474
351
|
return withRoute(match2.key, invokeRoute, async () => {
|
|
475
352
|
const handle = await load();
|
|
476
|
-
|
|
353
|
+
const routedContext = { ...context, route: match2.key };
|
|
354
|
+
return handle(match2.payload ?? {}, routedContext);
|
|
477
355
|
});
|
|
478
356
|
};
|
|
479
357
|
const invokeRoute = async (key, payload) => {
|
|
@@ -502,20 +380,6 @@ var createBundle = (handlers) => {
|
|
|
502
380
|
}
|
|
503
381
|
return response;
|
|
504
382
|
};
|
|
505
|
-
const raw = event;
|
|
506
|
-
if (process.env.AWSLESS_PREVIEW && raw?.requestContext?.http && !event?.headers?.["x-awsless-route"]) {
|
|
507
|
-
previewConfig ??= JSON.parse(process.env.AWSLESS_PREVIEW);
|
|
508
|
-
return createPreviewHandler({
|
|
509
|
-
...previewConfig,
|
|
510
|
-
dispatch: async (event2) => {
|
|
511
|
-
const match2 = matchRoute(event2);
|
|
512
|
-
if (Array.isArray(match2)) {
|
|
513
|
-
throw new Error("Unknown bundle route");
|
|
514
|
-
}
|
|
515
|
-
return handleRoute(match2);
|
|
516
|
-
}
|
|
517
|
-
})(event);
|
|
518
|
-
}
|
|
519
383
|
const match = matchRoute(event);
|
|
520
384
|
if (Array.isArray(match)) {
|
|
521
385
|
const name = `${process.env.AWS_LAMBDA_FUNCTION_NAME}:${process.env.AWS_LAMBDA_FUNCTION_VERSION}`;
|
|
@@ -1,8 +1,81 @@
|
|
|
1
1
|
// src/feature/on-failure/server/handle.ts
|
|
2
|
-
import { parse, patch } from "@awsless/json";
|
|
2
|
+
import { parse as parse2, patch } from "@awsless/json";
|
|
3
3
|
import { invoke } from "@awsless/lambda";
|
|
4
4
|
import { deleteObject, getObject } from "@awsless/s3";
|
|
5
5
|
import { formatRoutePayload, getRouteEnv } from "awsless";
|
|
6
|
+
|
|
7
|
+
// src/feature/on-failure/server/util.ts
|
|
8
|
+
import { parse } from "@awsless/json";
|
|
9
|
+
var isDynamoDBFailureEvent = (event) => {
|
|
10
|
+
return "DDBStreamBatchInfo" in event;
|
|
11
|
+
};
|
|
12
|
+
var logicalResourceName = (physical) => {
|
|
13
|
+
const segments = physical.replace(/\.fifo$/, "").split("--");
|
|
14
|
+
if (segments.length === 4) {
|
|
15
|
+
return `${segments[1]}:${segments[2]}:${segments[3]}`;
|
|
16
|
+
}
|
|
17
|
+
if (segments.length === 3) {
|
|
18
|
+
return `${segments[1]}:${segments[2]}`;
|
|
19
|
+
}
|
|
20
|
+
return physical;
|
|
21
|
+
};
|
|
22
|
+
var getFailureSource = (payload) => {
|
|
23
|
+
const record = getDeliveryRecord(payload);
|
|
24
|
+
if (!record) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (isTopicRecord(record)) {
|
|
28
|
+
return {
|
|
29
|
+
resource: record.Sns.TopicArn ? logicalResourceName(lastArnSegment(record.Sns.TopicArn)) : "topic",
|
|
30
|
+
event: parseEvent(record.Sns.Message)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (isStreamRecord(record)) {
|
|
34
|
+
const table = record.eventSourceARN.split("/")[1];
|
|
35
|
+
return {
|
|
36
|
+
resource: table ? logicalResourceName(table) : "table-stream"
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (isQueueRecord(record)) {
|
|
40
|
+
return {
|
|
41
|
+
resource: logicalResourceName(lastArnSegment(record.eventSourceARN)),
|
|
42
|
+
event: parseEvent(record.body)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
};
|
|
47
|
+
var getDeliveryRecord = (payload) => {
|
|
48
|
+
if (!payload || typeof payload !== "object") {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const records = payload.Records;
|
|
52
|
+
const record = Array.isArray(records) ? records[0] : undefined;
|
|
53
|
+
return record && typeof record === "object" ? record : undefined;
|
|
54
|
+
};
|
|
55
|
+
var isTopicRecord = (record) => {
|
|
56
|
+
return "Sns" in record && typeof record.Sns === "object" && record.Sns !== null;
|
|
57
|
+
};
|
|
58
|
+
var isStreamRecord = (record) => {
|
|
59
|
+
return "eventSource" in record && record.eventSource === "aws:dynamodb" && "eventSourceARN" in record;
|
|
60
|
+
};
|
|
61
|
+
var isQueueRecord = (record) => {
|
|
62
|
+
return "eventSource" in record && record.eventSource === "aws:sqs" && "eventSourceARN" in record;
|
|
63
|
+
};
|
|
64
|
+
var lastArnSegment = (arn) => {
|
|
65
|
+
return arn.split(":").at(-1);
|
|
66
|
+
};
|
|
67
|
+
var parseEvent = (value) => {
|
|
68
|
+
if (typeof value !== "string") {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
return parse(value);
|
|
73
|
+
} catch {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/feature/on-failure/server/handle.ts
|
|
6
79
|
var handle_default = async (event, context) => {
|
|
7
80
|
if (!Array.isArray(event.Records)) {
|
|
8
81
|
throw new TypeError(`Unknown Event Type: ${JSON.stringify(event)}`);
|
|
@@ -28,13 +101,16 @@ var sqsRecord = async (record, context) => {
|
|
|
28
101
|
await Promise.all(s3Records.map((record2) => s3Record(record2, context)));
|
|
29
102
|
return;
|
|
30
103
|
}
|
|
104
|
+
const queueName = record.messageAttributes.queueName?.stringValue;
|
|
105
|
+
const body = parsePayload(record.body);
|
|
31
106
|
const payload = {
|
|
32
107
|
type: "queue",
|
|
33
108
|
id: record.messageId,
|
|
34
109
|
date: new Date(Number(record.attributes.SentTimestamp)),
|
|
35
|
-
payload:
|
|
110
|
+
payload: body,
|
|
111
|
+
source: queueName ? { resource: logicalResourceName(queueName), event: body } : undefined,
|
|
36
112
|
queue: {
|
|
37
|
-
name:
|
|
113
|
+
name: queueName,
|
|
38
114
|
url: record.messageAttributes.queueUrl?.stringValue
|
|
39
115
|
}
|
|
40
116
|
};
|
|
@@ -70,9 +146,6 @@ var s3Record = async (record, context) => {
|
|
|
70
146
|
key
|
|
71
147
|
});
|
|
72
148
|
};
|
|
73
|
-
var isDynamoDBFailureEvent = (event) => {
|
|
74
|
-
return "DDBStreamBatchInfo" in event;
|
|
75
|
-
};
|
|
76
149
|
var formatUnknownFailureEvent = (event) => {
|
|
77
150
|
if (isDynamoDBFailureEvent(event)) {
|
|
78
151
|
return formatDynamoDBStreamFailureEvent(event);
|
|
@@ -90,6 +163,7 @@ var formatAsyncLambdaFailureEvent = (event) => {
|
|
|
90
163
|
name: typeof route === "string" ? route : event.requestContext.functionArn.split(":")[6]
|
|
91
164
|
},
|
|
92
165
|
payload: typeof route === "string" ? payload.event ?? {} : payload,
|
|
166
|
+
source: typeof route === "string" ? { resource: route, event: payload.event ?? {} } : getFailureSource(payload),
|
|
93
167
|
error: {
|
|
94
168
|
type: event.responsePayload.errorType,
|
|
95
169
|
message: event.responsePayload.errorMessage,
|
|
@@ -98,6 +172,9 @@ var formatAsyncLambdaFailureEvent = (event) => {
|
|
|
98
172
|
};
|
|
99
173
|
};
|
|
100
174
|
var formatDynamoDBStreamFailureEvent = (event) => {
|
|
175
|
+
const payload = parsePayload(event.payload);
|
|
176
|
+
const streamArn = event.DDBStreamBatchInfo?.streamArn;
|
|
177
|
+
const table = streamArn?.split("/")[1];
|
|
101
178
|
return {
|
|
102
179
|
type: "dynamodb-stream",
|
|
103
180
|
date: new Date(event.timestamp),
|
|
@@ -105,12 +182,13 @@ var formatDynamoDBStreamFailureEvent = (event) => {
|
|
|
105
182
|
function: {
|
|
106
183
|
name: event.requestContext.functionArn.split(":")[6]
|
|
107
184
|
},
|
|
108
|
-
payload
|
|
185
|
+
payload,
|
|
186
|
+
source: table ? { resource: logicalResourceName(table) } : getFailureSource(payload)
|
|
109
187
|
};
|
|
110
188
|
};
|
|
111
189
|
var parsePayload = (payload) => {
|
|
112
190
|
try {
|
|
113
|
-
return
|
|
191
|
+
return parse2(payload);
|
|
114
192
|
} catch {
|
|
115
193
|
return payload;
|
|
116
194
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awsless/cli",
|
|
3
|
-
"version": "0.0.46-next.
|
|
3
|
+
"version": "0.0.46-next.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -83,23 +83,23 @@
|
|
|
83
83
|
"zod": "^3.24.2",
|
|
84
84
|
"zod-to-json-schema": "^3.24.3",
|
|
85
85
|
"@awsless/big-float": "^0.1.7",
|
|
86
|
-
"@awsless/clui": "^0.0.8",
|
|
87
86
|
"@awsless/cloudwatch": "^0.0.1",
|
|
88
87
|
"@awsless/duration": "^0.0.4",
|
|
88
|
+
"@awsless/clui": "^0.0.9",
|
|
89
89
|
"@awsless/dynamodb": "^0.3.21",
|
|
90
90
|
"@awsless/iot": "^0.0.5",
|
|
91
91
|
"@awsless/json": "^0.0.11",
|
|
92
|
-
"@awsless/lambda": "^0.0.44",
|
|
93
92
|
"@awsless/s3": "^0.0.21",
|
|
94
93
|
"@awsless/redis": "^0.1.13",
|
|
95
94
|
"@awsless/size": "^0.0.2",
|
|
96
|
-
"@awsless/
|
|
95
|
+
"@awsless/scheduler": "^0.0.4",
|
|
96
|
+
"@awsless/lambda": "^0.0.45",
|
|
97
97
|
"@awsless/sqs": "^0.0.24",
|
|
98
|
-
"@awsless/
|
|
99
|
-
"@awsless/weak-cache": "^0.0.1",
|
|
98
|
+
"@awsless/sns": "^0.0.10",
|
|
100
99
|
"@awsless/validate": "^0.1.7",
|
|
101
100
|
"awsless": "^0.0.15-next.0",
|
|
102
|
-
"@awsless/
|
|
101
|
+
"@awsless/weak-cache": "^0.0.1",
|
|
102
|
+
"@awsless/ts-file-cache": "^0.0.16"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
105
|
"@hono/node-server": "1.19.9",
|