@awsless/awsless 0.0.15 → 0.0.16

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.cjs CHANGED
@@ -77,7 +77,7 @@ var flushDebug = () => {
77
77
  // src/util/param.ts
78
78
  var import_client_ssm = require("@aws-sdk/client-ssm");
79
79
  var configParameterPrefix = (config) => {
80
- return `/awsless/${config.name}/${config.stage}`;
80
+ return `/.awsless/${config.name}`;
81
81
  };
82
82
  var Params = class {
83
83
  constructor(config) {
@@ -158,8 +158,18 @@ var Params = class {
158
158
  }
159
159
  };
160
160
 
161
- // src/formation/util.ts
161
+ // src/formation/asset.ts
162
162
  var import_change_case = require("change-case");
163
+ var Asset = class {
164
+ constructor(type, id) {
165
+ this.type = type;
166
+ this.id = (0, import_change_case.paramCase)(id);
167
+ }
168
+ id;
169
+ };
170
+
171
+ // src/formation/util.ts
172
+ var import_change_case2 = require("change-case");
163
173
  var ref = (logicalId) => {
164
174
  return { Ref: logicalId };
165
175
  };
@@ -176,10 +186,10 @@ var importValue = (name) => {
176
186
  return { "Fn::ImportValue": name };
177
187
  };
178
188
  var formatLogicalId = (id) => {
179
- return (0, import_change_case.pascalCase)(id);
189
+ return (0, import_change_case2.pascalCase)(id).replaceAll("_", "");
180
190
  };
181
191
  var formatName = (name) => {
182
- return (0, import_change_case.paramCase)(name);
192
+ return (0, import_change_case2.paramCase)(name);
183
193
  };
184
194
 
185
195
  // src/formation/resource.ts
@@ -315,11 +325,11 @@ var Function = class extends Resource {
315
325
  });
316
326
  role.addInlinePolicy(policy);
317
327
  role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AWSLambdaBasicExecutionRole"));
318
- super("AWS::Lambda::Function", logicalId, [
319
- role,
320
- props.code
321
- ]);
328
+ super("AWS::Lambda::Function", logicalId, [role]);
322
329
  this.props = props;
330
+ if (props.code instanceof Asset) {
331
+ this.children.push(props.code);
332
+ }
323
333
  this.dependsOn(role);
324
334
  this.role = role;
325
335
  this.policy = policy;
@@ -372,16 +382,6 @@ var Function = class extends Resource {
372
382
  }
373
383
  };
374
384
 
375
- // src/formation/asset.ts
376
- var import_change_case2 = require("change-case");
377
- var Asset = class {
378
- constructor(type, id) {
379
- this.type = type;
380
- this.id = (0, import_change_case2.paramCase)(id);
381
- }
382
- id;
383
- };
384
-
385
385
  // src/formation/stack.ts
386
386
  var Stack = class {
387
387
  constructor(name, region) {
@@ -406,10 +406,16 @@ var Stack = class {
406
406
  return this;
407
407
  }
408
408
  export(name, value) {
409
- name = formatName(name);
410
- this.exports.set(name, value);
409
+ this.exports.set(formatName(name), value);
411
410
  return this;
412
411
  }
412
+ get(name) {
413
+ name = formatName(name);
414
+ if (!this.exports.has(name)) {
415
+ throw new Error(`Undefined export value: ${name}`);
416
+ }
417
+ return this.exports.get(name);
418
+ }
413
419
  import(name) {
414
420
  name = formatName(name);
415
421
  if (!this.exports.has(name)) {
@@ -820,55 +826,24 @@ var zipFiles = (files) => {
820
826
  };
821
827
 
822
828
  // src/formation/resource/lambda/code.ts
823
- var import_crypto2 = require("crypto");
824
829
  var Code = class {
825
830
  static fromFile(id, file, bundler) {
826
831
  return new FileCode(id, file, bundler);
827
832
  }
828
- static fromInline(id, code, handler) {
829
- return new InlineCode(id, code, handler);
833
+ static fromInline(code, handler) {
834
+ return new InlineCode(code, handler);
830
835
  }
831
836
  };
832
- var InlineCode = class extends Asset {
833
- constructor(id, code, handler = "index.default") {
834
- super("function", id);
837
+ var InlineCode = class {
838
+ constructor(code, handler = "index.default") {
835
839
  this.code = code;
836
840
  this.handler = handler;
837
841
  }
838
- hash;
839
- bundle;
840
- s3;
841
- async build({ write }) {
842
- const hash = (0, import_crypto2.createHash)("sha1").update(this.code).digest("hex");
843
- const bundle = await zipFiles([{
844
- name: "index.js",
845
- code: this.code
846
- }]);
847
- await Promise.all([
848
- write("HASH", hash),
849
- write("bundle.zip", bundle),
850
- write("files/inline.js", this.code)
851
- ]);
852
- this.bundle = bundle;
853
- this.hash = hash;
854
- return {
855
- size: formatByteSize(bundle.byteLength)
856
- };
857
- }
858
- async publish({ publish }) {
859
- this.s3 = await publish(
860
- `${this.id}.zip`,
861
- this.bundle,
862
- this.hash
863
- );
864
- }
865
842
  toCodeJson() {
866
843
  return {
867
844
  Handler: this.handler,
868
845
  Code: {
869
- S3Bucket: this.s3.bucket,
870
- S3Key: this.s3.key,
871
- S3ObjectVersion: this.s3.version
846
+ ZipFile: this.code
872
847
  }
873
848
  };
874
849
  }
@@ -919,6 +894,44 @@ var FileCode = class extends Asset {
919
894
  }
920
895
  };
921
896
 
897
+ // src/formation/resource/lambda/event-invoke-config.ts
898
+ var EventInvokeConfig = class extends Resource {
899
+ constructor(logicalId, props) {
900
+ super("AWS::Lambda::EventInvokeConfig", logicalId);
901
+ this.props = props;
902
+ }
903
+ setOnFailure(arn) {
904
+ this.props.onFailure = arn;
905
+ return this;
906
+ }
907
+ setOnSuccess(arn) {
908
+ this.props.onSuccess = arn;
909
+ return this;
910
+ }
911
+ properties() {
912
+ return {
913
+ FunctionName: this.props.functionName,
914
+ Qualifier: this.props.qualifier || "$LATEST",
915
+ ...this.attr("MaximumEventAgeInSeconds", this.props.maxEventAge?.toSeconds()),
916
+ ...this.attr("MaximumRetryAttempts", this.props.retryAttempts),
917
+ ...this.props.onFailure || this.props.onSuccess ? {
918
+ DestinationConfig: {
919
+ ...this.props.onFailure ? {
920
+ OnFailure: {
921
+ Destination: this.props.onFailure
922
+ }
923
+ } : {},
924
+ ...this.props.onSuccess ? {
925
+ OnSuccess: {
926
+ Destination: this.props.onSuccess
927
+ }
928
+ } : {}
929
+ }
930
+ } : {}
931
+ };
932
+ }
933
+ };
934
+
922
935
  // src/plugins/function.ts
923
936
  var MemorySizeSchema = SizeSchema.refine(sizeMin(Size.megaBytes(128)), "Minimum memory size is 128 MB").refine(sizeMax(Size.gigaBytes(10)), "Minimum memory size is 10 GB");
924
937
  var TimeoutSchema = DurationSchema.refine(durationMin(Duration.seconds(10)), "Minimum timeout duration is 10 seconds").refine(durationMax(Duration.minutes(15)), "Maximum timeout duration is 15 minutes");
@@ -933,29 +946,104 @@ var RuntimeSchema = import_zod6.z.enum([
933
946
  var FunctionSchema = import_zod6.z.union([
934
947
  LocalFileSchema,
935
948
  import_zod6.z.object({
949
+ /** The file path ofthe function code. */
936
950
  file: LocalFileSchema,
951
+ /** The amount of time that Lambda allows a function to run before stopping it.
952
+ * You can specify a size value from 1 second to 15 minutes.
953
+ * @default '10 seconds'
954
+ */
937
955
  timeout: TimeoutSchema.optional(),
956
+ /** The identifier of the function's runtime.
957
+ * @default 'nodejs18.x'
958
+ */
938
959
  runtime: RuntimeSchema.optional(),
960
+ /** The amount of memory available to the function at runtime.
961
+ * Increasing the function memory also increases its CPU allocation.
962
+ * The value can be any multiple of 1 MB.
963
+ * You can specify a size value from 128 MB to 10 GB.
964
+ * @default '128 MB'
965
+ */
939
966
  memorySize: MemorySizeSchema.optional(),
967
+ /** The instruction set architecture that the function supports.
968
+ * @default 'arm64'
969
+ */
940
970
  architecture: ArchitectureSchema.optional(),
971
+ /** The size of the function's /tmp directory.
972
+ * You can specify a size value from 512 MB to 10 GB.
973
+ * @default 512 MB
974
+ */
941
975
  ephemeralStorageSize: EphemeralStorageSizeSchema.optional(),
976
+ /** The maximum number of times to retry when the function returns an error.
977
+ * You can specify a number from 0 to 2.
978
+ * @default 2
979
+ */
942
980
  retryAttempts: RetryAttemptsSchema.optional(),
981
+ /** Environment variable key-value pairs.
982
+ * @example
983
+ * {
984
+ * environment: {
985
+ * name: 'value'
986
+ * }
987
+ * }
988
+ */
943
989
  environment: EnvironmentSchema.optional()
990
+ // onFailure: ResourceIdSchema.optional(),
944
991
  })
945
992
  ]);
946
993
  var schema = import_zod6.z.object({
947
994
  defaults: import_zod6.z.object({
948
995
  function: import_zod6.z.object({
996
+ /** The amount of time that Lambda allows a function to run before stopping it.
997
+ * You can specify a size value from 1 second to 15 minutes.
998
+ * @default '10 seconds'
999
+ */
949
1000
  timeout: TimeoutSchema.default("10 seconds"),
1001
+ /** The identifier of the function's runtime.
1002
+ * @default 'nodejs18.x'
1003
+ */
950
1004
  runtime: RuntimeSchema.default("nodejs18.x"),
1005
+ /** The amount of memory available to the function at runtime.
1006
+ * Increasing the function memory also increases its CPU allocation.
1007
+ * The value can be any multiple of 1 MB.
1008
+ * You can specify a size value from 128 MB to 10 GB.
1009
+ * @default '128 MB'
1010
+ */
951
1011
  memorySize: MemorySizeSchema.default("128 MB"),
1012
+ /** The instruction set architecture that the function supports.
1013
+ * @default 'arm64'
1014
+ */
952
1015
  architecture: ArchitectureSchema.default("arm64"),
1016
+ /** The size of the function's /tmp directory.
1017
+ * You can specify a size value from 512 MB to 10 GB.
1018
+ * @default 512 MB
1019
+ */
953
1020
  ephemeralStorageSize: EphemeralStorageSizeSchema.default("512 MB"),
1021
+ /** The maximum number of times to retry when the function returns an error.
1022
+ * You can specify a number from 0 to 2.
1023
+ * @default 2
1024
+ */
954
1025
  retryAttempts: RetryAttemptsSchema.default(2),
1026
+ /** Environment variable key-value pairs.
1027
+ * @example
1028
+ * {
1029
+ * environment: {
1030
+ * name: 'value'
1031
+ * }
1032
+ * }
1033
+ */
955
1034
  environment: EnvironmentSchema.optional()
1035
+ // onFailure: ResourceIdSchema.optional(),
956
1036
  }).default({})
957
1037
  }).default({}),
958
1038
  stacks: import_zod6.z.object({
1039
+ /** Define the functions in your stack.
1040
+ * @example
1041
+ * {
1042
+ * functions: {
1043
+ * FUNCTION_NAME: 'function.ts'
1044
+ * }
1045
+ * }
1046
+ */
959
1047
  functions: import_zod6.z.record(
960
1048
  ResourceIdSchema,
961
1049
  FunctionSchema
@@ -981,12 +1069,15 @@ var toLambdaFunction = (ctx, id, fileOrProps) => {
981
1069
  code: Code.fromFile(id, props.file),
982
1070
  ...props
983
1071
  });
984
- lambda.addEnvironment("APP", config.name);
985
- lambda.addEnvironment("STAGE", config.stage);
986
- lambda.addEnvironment("STACK", stack.name);
1072
+ lambda.addEnvironment("APP", config.name).addEnvironment("STAGE", config.stage).addEnvironment("STACK", stack.name);
987
1073
  if (props.runtime.startsWith("nodejs")) {
988
1074
  lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1");
989
1075
  }
1076
+ const invoke = new EventInvokeConfig(id, {
1077
+ functionName: lambda.name,
1078
+ retryAttempts: props.retryAttempts
1079
+ }).dependsOn(lambda);
1080
+ ctx.stack.add(invoke);
990
1081
  return lambda;
991
1082
  };
992
1083
 
@@ -1064,9 +1155,26 @@ var cronPlugin = definePlugin({
1064
1155
  name: "cron",
1065
1156
  schema: import_zod7.z.object({
1066
1157
  stacks: import_zod7.z.object({
1158
+ /** Define the crons in your stack
1159
+ * @example
1160
+ * {
1161
+ * crons: {
1162
+ * CRON_NAME: {
1163
+ * consumer: 'function.ts',
1164
+ * schedule: 'rate(5 minutes)',
1165
+ * }
1166
+ * }
1167
+ * }
1168
+ * */
1067
1169
  crons: import_zod7.z.record(ResourceIdSchema, import_zod7.z.object({
1170
+ /** The consuming lambda function properties */
1068
1171
  consumer: FunctionSchema,
1172
+ /** The scheduling expression.
1173
+ * @example 'cron(0 20 * * ? *)'
1174
+ * @example 'rate(5 minutes)'
1175
+ */
1069
1176
  schedule: ScheduleExpressionSchema,
1177
+ // Valid JSON passed to the consumer.
1070
1178
  payload: import_zod7.z.unknown().optional()
1071
1179
  })).optional()
1072
1180
  }).array()
@@ -1095,6 +1203,10 @@ var Queue = class extends Resource {
1095
1203
  this.name = formatName(this.props.name || logicalId);
1096
1204
  }
1097
1205
  name;
1206
+ setDeadLetter(arn) {
1207
+ this.props.deadLetterArn = arn;
1208
+ return this;
1209
+ }
1098
1210
  get arn() {
1099
1211
  return getAtt(this.logicalId, "Arn");
1100
1212
  }
@@ -1119,7 +1231,12 @@ var Queue = class extends Resource {
1119
1231
  MaximumMessageSize: this.props.maxMessageSize?.toBytes() ?? Size.kiloBytes(256).toBytes(),
1120
1232
  MessageRetentionPeriod: this.props.retentionPeriod?.toSeconds() ?? Duration.days(4).toSeconds(),
1121
1233
  ReceiveMessageWaitTimeSeconds: this.props.receiveMessageWaitTime?.toSeconds() ?? 0,
1122
- VisibilityTimeout: this.props.visibilityTimeout?.toSeconds() ?? 30
1234
+ VisibilityTimeout: this.props.visibilityTimeout?.toSeconds() ?? 30,
1235
+ ...this.props.deadLetterArn ? {
1236
+ RedrivePolicy: {
1237
+ deadLetterTargetArn: this.props.deadLetterArn
1238
+ }
1239
+ } : {}
1123
1240
  };
1124
1241
  }
1125
1242
  };
@@ -1131,6 +1248,10 @@ var EventSourceMapping = class extends Resource {
1131
1248
  super("AWS::Lambda::EventSourceMapping", logicalId);
1132
1249
  this.props = props;
1133
1250
  }
1251
+ setOnFailure(arn) {
1252
+ this.props.onFailure = arn;
1253
+ return this;
1254
+ }
1134
1255
  properties() {
1135
1256
  return {
1136
1257
  Enabled: true,
@@ -1189,7 +1310,7 @@ var queuePlugin = definePlugin({
1189
1310
  name: "queue",
1190
1311
  schema: import_zod8.z.object({
1191
1312
  defaults: import_zod8.z.object({
1192
- /** Define the defaults properties for all queue's in your app */
1313
+ /** Define the defaults properties for all queue's in your app. */
1193
1314
  queue: import_zod8.z.object({
1194
1315
  /** The number of seconds that Amazon SQS retains a message.
1195
1316
  * You can specify a duration value from 1 minute to 14 days.
@@ -1218,20 +1339,20 @@ var queuePlugin = definePlugin({
1218
1339
  }).default({})
1219
1340
  }).default({}),
1220
1341
  stacks: import_zod8.z.object({
1221
- /** Define the queues in your stack
1342
+ /** Define the queues in your stack.
1222
1343
  * @example
1223
1344
  * {
1224
1345
  * queues: {
1225
1346
  * QUEUE_NAME: 'function.ts'
1226
1347
  * }
1227
1348
  * }
1228
- * */
1349
+ */
1229
1350
  queues: import_zod8.z.record(
1230
1351
  ResourceIdSchema,
1231
1352
  import_zod8.z.union([
1232
1353
  LocalFileSchema,
1233
1354
  import_zod8.z.object({
1234
- /** The consuming lambda function properties */
1355
+ /** The consuming lambda function properties. */
1235
1356
  consumer: FunctionSchema,
1236
1357
  /** The number of seconds that Amazon SQS retains a message.
1237
1358
  * You can specify a duration value from 1 minute to 14 days.
@@ -1249,7 +1370,6 @@ var queuePlugin = definePlugin({
1249
1370
  /** Specifies the duration, in seconds,
1250
1371
  * that the ReceiveMessage action call waits until a message is in the queue in order to include it in the response,
1251
1372
  * rather than returning an empty response if a message isn't yet available.
1252
- * You can specify an integer from 1 to 20.
1253
1373
  * You can specify a duration value from 1 to 20 seconds.
1254
1374
  * @default '0 seconds' */
1255
1375
  receiveMessageWaitTime: DurationSchema.optional(),
@@ -1270,7 +1390,7 @@ var queuePlugin = definePlugin({
1270
1390
  name: `${config.name}-${stack.name}-${id}`,
1271
1391
  ...props
1272
1392
  });
1273
- const lambda = toLambdaFunction(ctx, id, props.consumer);
1393
+ const lambda = toLambdaFunction(ctx, `queue-${id}`, props.consumer);
1274
1394
  const source = new SqsEventSource(id, lambda, {
1275
1395
  queueArn: queue2.arn
1276
1396
  });
@@ -1296,12 +1416,21 @@ var Table = class extends Resource {
1296
1416
  }
1297
1417
  name;
1298
1418
  indexes;
1419
+ enableStream(viewType) {
1420
+ this.props.stream = viewType;
1421
+ }
1299
1422
  addIndex(name, props) {
1300
1423
  this.indexes[name] = props;
1301
1424
  }
1302
- get arn() {
1425
+ get id() {
1303
1426
  return ref(this.logicalId);
1304
1427
  }
1428
+ get arn() {
1429
+ return getAtt(this.logicalId, "Arn");
1430
+ }
1431
+ get streamArn() {
1432
+ return getAtt(this.logicalId, "StreamArn");
1433
+ }
1305
1434
  get permissions() {
1306
1435
  return {
1307
1436
  actions: [
@@ -1335,6 +1464,11 @@ var Table = class extends Resource {
1335
1464
  AttributeName: name,
1336
1465
  AttributeType: type[0].toUpperCase()
1337
1466
  })),
1467
+ ...this.props.stream ? {
1468
+ StreamSpecification: {
1469
+ StreamViewType: (0, import_change_case4.constantCase)(this.props.stream)
1470
+ }
1471
+ } : {},
1338
1472
  ...this.props.timeToLiveAttribute ? {
1339
1473
  TimeToLiveSpecification: {
1340
1474
  AttributeName: this.props.timeToLiveAttribute,
@@ -1357,13 +1491,42 @@ var Table = class extends Resource {
1357
1491
  }
1358
1492
  };
1359
1493
 
1494
+ // src/formation/resource/lambda/event-source/dynamodb.ts
1495
+ var DynamoDBEventSource = class extends Group {
1496
+ constructor(id, lambda, props) {
1497
+ const source = new EventSourceMapping(id, {
1498
+ functionArn: lambda.arn,
1499
+ sourceArn: props.tableArn,
1500
+ batchSize: props.batchSize ?? 100,
1501
+ bisectBatchOnError: props.bisectBatchOnError ?? true,
1502
+ maxBatchingWindow: props.maxBatchingWindow,
1503
+ maxRecordAge: props.maxRecordAge,
1504
+ retryAttempts: props.retryAttempts ?? -1,
1505
+ parallelizationFactor: props.parallelizationFactor ?? 1,
1506
+ startingPosition: props.startingPosition,
1507
+ startingPositionTimestamp: props.startingPositionTimestamp,
1508
+ tumblingWindow: props.tumblingWindow
1509
+ });
1510
+ lambda.addPermissions({
1511
+ actions: [
1512
+ "dynamodb:ListStreams",
1513
+ "dynamodb:DescribeStream",
1514
+ "dynamodb:GetRecords",
1515
+ "dynamodb:GetShardIterator"
1516
+ ],
1517
+ resources: [props.tableArn]
1518
+ });
1519
+ super([source]);
1520
+ }
1521
+ };
1522
+
1360
1523
  // src/plugins/table.ts
1361
1524
  var KeySchema = import_zod9.z.string().min(1).max(255);
1362
1525
  var tablePlugin = definePlugin({
1363
1526
  name: "table",
1364
1527
  schema: import_zod9.z.object({
1365
1528
  stacks: import_zod9.z.object({
1366
- /** Define the tables in your stack
1529
+ /** Define the tables in your stack.
1367
1530
  * @example
1368
1531
  * {
1369
1532
  * tables: {
@@ -1390,20 +1553,34 @@ var tablePlugin = definePlugin({
1390
1553
  * id: 'string'
1391
1554
  * }
1392
1555
  * }
1393
- */
1556
+ */
1394
1557
  fields: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.enum(["string", "number", "binary"])),
1395
1558
  /** The table class of the table.
1396
1559
  * @default 'standard'
1397
- */
1560
+ */
1398
1561
  class: import_zod9.z.enum(["standard", "standard-infrequent-access"]).default("standard"),
1399
1562
  /** Indicates whether point in time recovery is enabled on the table.
1400
1563
  * @default false
1401
- */
1564
+ */
1402
1565
  pointInTimeRecovery: import_zod9.z.boolean().default(false),
1403
1566
  /** The name of the TTL attribute used to store the expiration time for items in the table.
1404
1567
  * - To update this property, you must first disable TTL and then enable TTL with the new attribute name.
1405
- */
1568
+ */
1406
1569
  timeToLiveAttribute: KeySchema.optional(),
1570
+ /** The settings for the DynamoDB table stream, which capture changes to items stored in the table. */
1571
+ stream: import_zod9.z.object({
1572
+ /** When an item in the table is modified,
1573
+ * stream.type determines what information is written to the stream for this table.
1574
+ * Valid values are:
1575
+ * - keys-only - Only the key attributes of the modified item are written to the stream.
1576
+ * - new-image - The entire item, as it appears after it was modified, is written to the stream.
1577
+ * - old-image - The entire item, as it appeared before it was modified, is written to the stream.
1578
+ * - new-and-old-images - Both the new and the old item images of the item are written to the stream.
1579
+ */
1580
+ type: import_zod9.z.enum(["keys-only", "new-image", "old-image", "new-and-old-images"]),
1581
+ /** The consuming lambda function for the stream */
1582
+ consumer: FunctionSchema
1583
+ }).optional(),
1407
1584
  /** Specifies the global secondary indexes to be created on the table.
1408
1585
  * @example
1409
1586
  * {
@@ -1413,7 +1590,7 @@ var tablePlugin = definePlugin({
1413
1590
  * }
1414
1591
  * }
1415
1592
  * }
1416
- */
1593
+ */
1417
1594
  indexes: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.object({
1418
1595
  /** Specifies the name of the partition / hash key that makes up the primary key for the global secondary index. */
1419
1596
  hash: KeySchema,
@@ -1441,13 +1618,23 @@ var tablePlugin = definePlugin({
1441
1618
  ).optional()
1442
1619
  }).array()
1443
1620
  }),
1444
- onStack({ config, stack, stackConfig, bind }) {
1621
+ onStack(ctx) {
1622
+ const { config, stack, stackConfig, bind } = ctx;
1445
1623
  for (const [id, props] of Object.entries(stackConfig.tables || {})) {
1446
1624
  const table = new Table(id, {
1625
+ ...props,
1447
1626
  name: `${config.name}-${stack.name}-${id}`,
1448
- ...props
1627
+ stream: props.stream?.type
1449
1628
  });
1450
1629
  stack.add(table);
1630
+ if (props.stream) {
1631
+ const lambda = toLambdaFunction(ctx, `stream-${id}`, props.stream.consumer);
1632
+ const source = new DynamoDBEventSource(id, lambda, {
1633
+ tableArn: table.arn,
1634
+ ...props.stream
1635
+ });
1636
+ stack.add(lambda, source);
1637
+ }
1451
1638
  bind((lambda) => {
1452
1639
  lambda.addPermissions(table.permissions);
1453
1640
  });
@@ -1502,12 +1689,12 @@ var storePlugin = definePlugin({
1502
1689
  name: "store",
1503
1690
  schema: import_zod10.z.object({
1504
1691
  stacks: import_zod10.z.object({
1505
- /** Define the stores in your stack
1692
+ /** Define the stores in your stack.
1506
1693
  * @example
1507
1694
  * {
1508
1695
  * stores: [ 'STORE_NAME' ]
1509
1696
  * }
1510
- * */
1697
+ */
1511
1698
  stores: import_zod10.z.array(ResourceIdSchema).optional()
1512
1699
  }).array()
1513
1700
  }),
@@ -1591,14 +1778,14 @@ var topicPlugin = definePlugin({
1591
1778
  name: "topic",
1592
1779
  schema: import_zod11.z.object({
1593
1780
  stacks: import_zod11.z.object({
1594
- /** Define the topics to listen too in your stack
1781
+ /** Define the topics to listen too in your stack.
1595
1782
  * @example
1596
1783
  * {
1597
1784
  * topics: {
1598
1785
  * TOPIC_NAME: 'function.ts'
1599
1786
  * }
1600
1787
  * }
1601
- * */
1788
+ */
1602
1789
  topics: import_zod11.z.record(ResourceIdSchema, FunctionSchema).optional()
1603
1790
  }).array()
1604
1791
  }),
@@ -1628,7 +1815,7 @@ var topicPlugin = definePlugin({
1628
1815
  onStack(ctx) {
1629
1816
  const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
1630
1817
  for (const [id, props] of Object.entries(stackConfig.topics || {})) {
1631
- const lambda = toLambdaFunction(ctx, id, props);
1818
+ const lambda = toLambdaFunction(ctx, `topic-${id}`, props);
1632
1819
  const source = new SnsEventSource(id, lambda, {
1633
1820
  topicArn: bootstrap2.import(`topic-${id}-arn`)
1634
1821
  });
@@ -1642,10 +1829,10 @@ var import_zod12 = require("zod");
1642
1829
  var extendPlugin = definePlugin({
1643
1830
  name: "extend",
1644
1831
  schema: import_zod12.z.object({
1645
- /** Extend your app with custom resources */
1832
+ /** Extend your app with custom resources. */
1646
1833
  extend: import_zod12.z.custom().optional(),
1647
1834
  stacks: import_zod12.z.object({
1648
- /** Extend your stack with custom resources */
1835
+ /** Extend your stack with custom resources. */
1649
1836
  extend: import_zod12.z.custom().optional()
1650
1837
  }).array()
1651
1838
  }),
@@ -1711,7 +1898,7 @@ var pubsubPlugin = definePlugin({
1711
1898
  name: "pubsub",
1712
1899
  schema: import_zod13.z.object({
1713
1900
  stacks: import_zod13.z.object({
1714
- /** Define the pubsub subscriber in your stack
1901
+ /** Define the pubsub subscriber in your stack.
1715
1902
  * @example
1716
1903
  * {
1717
1904
  * pubsub: {
@@ -1723,11 +1910,11 @@ var pubsubPlugin = definePlugin({
1723
1910
  * }
1724
1911
  */
1725
1912
  pubsub: import_zod13.z.record(ResourceIdSchema, import_zod13.z.object({
1726
- /** The SQL statement used to query the iot topic */
1913
+ /** The SQL statement used to query the iot topic. */
1727
1914
  sql: import_zod13.z.string(),
1728
- /** The version of the SQL rules engine to use when evaluating the rule */
1915
+ /** The version of the SQL rules engine to use when evaluating the rule. */
1729
1916
  sqlVersion: import_zod13.z.enum(["2015-10-08", "2016-03-23", "beta"]).default("2016-03-23"),
1730
- /** The consuming lambda function properties */
1917
+ /** The consuming lambda function properties. */
1731
1918
  consumer: FunctionSchema
1732
1919
  })).optional()
1733
1920
  }).array()
@@ -1743,7 +1930,7 @@ var pubsubPlugin = definePlugin({
1743
1930
  onStack(ctx) {
1744
1931
  const { config, stack, stackConfig } = ctx;
1745
1932
  for (const [id, props] of Object.entries(stackConfig.pubsub || {})) {
1746
- const lambda = toLambdaFunction(ctx, id, props.consumer);
1933
+ const lambda = toLambdaFunction(ctx, `pubsub-${id}`, props.consumer);
1747
1934
  const source = new IotEventSource(id, lambda, {
1748
1935
  name: `${config.name}-${stack.name}-${id}`,
1749
1936
  sql: props.sql,
@@ -1770,34 +1957,6 @@ var import_change_case10 = require("change-case");
1770
1957
 
1771
1958
  // src/formation/resource/appsync/graphql-api.ts
1772
1959
  var import_change_case7 = require("change-case");
1773
- var GraphQL = class extends Group {
1774
- constructor(logicalId, props) {
1775
- const api = new GraphQLApi(logicalId, props);
1776
- const schema2 = new GraphQLSchema(logicalId, {
1777
- apiId: api.id,
1778
- definition: props.schema
1779
- }).dependsOn(api);
1780
- super([api, schema2]);
1781
- this.logicalId = logicalId;
1782
- this.api = api;
1783
- this.schema = schema2;
1784
- }
1785
- api;
1786
- schema;
1787
- attachDomainName(domainName, certificateArn) {
1788
- const id = this.logicalId + domainName;
1789
- const domain = new DomainName(id, {
1790
- domainName,
1791
- certificateArn
1792
- });
1793
- const association = new DomainNameApiAssociation(id, {
1794
- apiId: this.api.id,
1795
- domainName
1796
- }).dependsOn(this.api, domain);
1797
- this.children.push(domain, association);
1798
- return this;
1799
- }
1800
- };
1801
1960
  var GraphQLApi = class extends Resource {
1802
1961
  constructor(logicalId, props) {
1803
1962
  super("AWS::AppSync::GraphQLApi", logicalId);
@@ -1839,44 +1998,6 @@ var GraphQLApi = class extends Resource {
1839
1998
  };
1840
1999
  }
1841
2000
  };
1842
- var GraphQLSchema = class extends Resource {
1843
- constructor(logicalId, props) {
1844
- super("AWS::AppSync::GraphQLSchema", logicalId, [
1845
- props.definition
1846
- ]);
1847
- this.props = props;
1848
- }
1849
- properties() {
1850
- return {
1851
- ApiId: this.props.apiId,
1852
- Definition: this.props.definition.toDefinition()
1853
- };
1854
- }
1855
- };
1856
- var DomainName = class extends Resource {
1857
- constructor(logicalId, props) {
1858
- super("AWS::AppSync::DomainName", logicalId);
1859
- this.props = props;
1860
- }
1861
- properties() {
1862
- return {
1863
- DomainName: this.props.domainName,
1864
- CertificateArn: this.props.certificateArn
1865
- };
1866
- }
1867
- };
1868
- var DomainNameApiAssociation = class extends Resource {
1869
- constructor(logicalId, props) {
1870
- super("AWS::AppSync::DomainNameApiAssociation", logicalId);
1871
- this.props = props;
1872
- }
1873
- properties() {
1874
- return {
1875
- ApiId: this.props.apiId,
1876
- DomainName: this.props.domainName
1877
- };
1878
- }
1879
- };
1880
2001
 
1881
2002
  // src/formation/resource/route53/record-set.ts
1882
2003
  var RecordSet = class extends Resource {
@@ -1897,19 +2018,33 @@ var RecordSet = class extends Resource {
1897
2018
  } : {},
1898
2019
  ...this.props.alias ? {
1899
2020
  AliasTarget: {
1900
- DNSName: this.props.alias,
1901
- HostedZoneId: this.props.hostedZoneId
2021
+ DNSName: this.props.alias.dnsName,
2022
+ HostedZoneId: this.props.alias.hostedZoneId
1902
2023
  }
1903
2024
  } : {}
1904
2025
  };
1905
2026
  }
1906
2027
  };
1907
2028
 
1908
- // src/formation/resource/appsync/schema.ts
2029
+ // src/formation/resource/appsync/graphql-schema.ts
1909
2030
  var import_graphql = require("graphql");
1910
2031
  var import_promises2 = require("fs/promises");
1911
2032
  var import_merge = require("@graphql-tools/merge");
1912
- var Schema = class extends Asset {
2033
+ var GraphQLSchema = class extends Resource {
2034
+ constructor(logicalId, props) {
2035
+ super("AWS::AppSync::GraphQLSchema", logicalId, [
2036
+ props.definition
2037
+ ]);
2038
+ this.props = props;
2039
+ }
2040
+ properties() {
2041
+ return {
2042
+ ApiId: this.props.apiId,
2043
+ Definition: this.props.definition.toString()
2044
+ };
2045
+ }
2046
+ };
2047
+ var Definition = class extends Asset {
1913
2048
  constructor(id, files) {
1914
2049
  super("graphql", id);
1915
2050
  this.files = files;
@@ -1922,10 +2057,14 @@ var Schema = class extends Asset {
1922
2057
  }));
1923
2058
  const defs = (0, import_merge.mergeTypeDefs)(schemas);
1924
2059
  const schema2 = (0, import_graphql.print)(defs);
2060
+ const size = Buffer.from(schema2, "utf8").byteLength;
1925
2061
  await write("schema.gql", schema2);
1926
2062
  this.schema = schema2;
2063
+ return {
2064
+ size: formatByteSize(size)
2065
+ };
1927
2066
  }
1928
- toDefinition() {
2067
+ toString() {
1929
2068
  return this.schema;
1930
2069
  }
1931
2070
  };
@@ -2105,6 +2244,41 @@ var AppsyncEventSource = class extends Group {
2105
2244
  }
2106
2245
  };
2107
2246
 
2247
+ // src/formation/resource/appsync/domain-name.ts
2248
+ var DomainName = class extends Resource {
2249
+ constructor(logicalId, props) {
2250
+ super("AWS::AppSync::DomainName", logicalId);
2251
+ this.props = props;
2252
+ }
2253
+ get appSyncDomainName() {
2254
+ return getAtt(this.logicalId, "AppSyncDomainName");
2255
+ }
2256
+ get domainName() {
2257
+ return getAtt(this.logicalId, "DomainName");
2258
+ }
2259
+ get hostedZoneId() {
2260
+ return getAtt(this.logicalId, "HostedZoneId");
2261
+ }
2262
+ properties() {
2263
+ return {
2264
+ DomainName: this.props.domainName,
2265
+ CertificateArn: this.props.certificateArn
2266
+ };
2267
+ }
2268
+ };
2269
+ var DomainNameApiAssociation = class extends Resource {
2270
+ constructor(logicalId, props) {
2271
+ super("AWS::AppSync::DomainNameApiAssociation", logicalId);
2272
+ this.props = props;
2273
+ }
2274
+ properties() {
2275
+ return {
2276
+ ApiId: this.props.apiId,
2277
+ DomainName: this.props.domainName
2278
+ };
2279
+ }
2280
+ };
2281
+
2108
2282
  // src/plugins/graphql.ts
2109
2283
  var defaultResolver = `
2110
2284
  export function request(ctx) {
@@ -2154,38 +2328,51 @@ var graphqlPlugin = definePlugin({
2154
2328
  }
2155
2329
  }
2156
2330
  for (const id of apis) {
2157
- const schema2 = [];
2331
+ const schemaFiles = [];
2158
2332
  for (const stack of config.stacks) {
2159
2333
  const files = toArray(stack.graphql?.[id]?.schema || []);
2160
- schema2.push(...files);
2334
+ schemaFiles.push(...files);
2161
2335
  }
2162
- const graphql = new GraphQL(id, {
2336
+ const api = new GraphQLApi(id, {
2163
2337
  name: `${config.name}-${id}`,
2164
- authenticationType: "api-key",
2165
- schema: new Schema(id, schema2)
2338
+ authenticationType: "api-key"
2166
2339
  });
2167
- bootstrap2.add(graphql).export(`graphql-${id}`, graphql.api.id);
2340
+ const schema2 = new GraphQLSchema(id, {
2341
+ apiId: api.id,
2342
+ definition: new Definition(id, schemaFiles)
2343
+ }).dependsOn(api);
2344
+ bootstrap2.add(api).add(schema2).export(`graphql-${id}`, api.id);
2168
2345
  const props = config.defaults.graphql?.[id];
2169
2346
  if (!props) {
2170
2347
  continue;
2171
2348
  }
2172
2349
  if (props.authorization) {
2173
2350
  const lambda = toLambdaFunction(ctx, `${id}-authorizer`, props.authorization.authorizer);
2174
- graphql.api.addLambdaAuthProvider(lambda.arn, props.authorization.ttl);
2351
+ api.addLambdaAuthProvider(lambda.arn, props.authorization.ttl);
2175
2352
  bootstrap2.add(lambda);
2176
2353
  }
2177
2354
  if (props.domain) {
2178
2355
  const domainName = props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain;
2179
- const hostedZoneId = ref(`${props.domain}Route53HostedZone`);
2356
+ const hostedZoneId = usEastBootstrap.import(`hosted-zone-${props.domain}-id`);
2180
2357
  const certificateArn = usEastBootstrap.import(`certificate-${props.domain}-arn`);
2181
- graphql.attachDomainName(domainName, certificateArn);
2182
- const record = new RecordSet(id, {
2358
+ const domain = new DomainName(id, {
2359
+ domainName,
2360
+ certificateArn
2361
+ });
2362
+ const association = new DomainNameApiAssociation(id, {
2363
+ apiId: api.id,
2364
+ domainName: domain.domainName
2365
+ }).dependsOn(api, domain);
2366
+ const record = new RecordSet(`${id}-graphql`, {
2183
2367
  hostedZoneId,
2184
2368
  type: "A",
2185
2369
  name: domainName,
2186
- alias: graphql.api.dns
2187
- });
2188
- bootstrap2.add(record);
2370
+ alias: {
2371
+ dnsName: domain.appSyncDomainName,
2372
+ hostedZoneId: domain.hostedZoneId
2373
+ }
2374
+ }).dependsOn(domain, association);
2375
+ bootstrap2.add(domain, association, record);
2189
2376
  }
2190
2377
  }
2191
2378
  },
@@ -2196,7 +2383,7 @@ var graphqlPlugin = definePlugin({
2196
2383
  for (const [typeAndField, functionProps] of Object.entries(props.resolvers || {})) {
2197
2384
  const [typeName, fieldName] = typeAndField.split(/[\s]+/g);
2198
2385
  const entryId = (0, import_change_case10.paramCase)(`${id}-${typeName}-${fieldName}`);
2199
- const lambda = toLambdaFunction(ctx, entryId, functionProps);
2386
+ const lambda = toLambdaFunction(ctx, `graphql-${entryId}`, functionProps);
2200
2387
  const source = new AppsyncEventSource(entryId, lambda, {
2201
2388
  apiId,
2202
2389
  typeName,
@@ -2232,7 +2419,7 @@ var HostedZone = class extends Resource {
2232
2419
 
2233
2420
  // src/formation/resource/certificate-manager/certificate.ts
2234
2421
  var Certificate = class extends Resource {
2235
- constructor(logicalId, props = {}) {
2422
+ constructor(logicalId, props) {
2236
2423
  super("AWS::CertificateManager::Certificate", logicalId);
2237
2424
  this.props = props;
2238
2425
  this.name = this.props.domainName || logicalId;
@@ -2244,8 +2431,12 @@ var Certificate = class extends Resource {
2244
2431
  properties() {
2245
2432
  return {
2246
2433
  DomainName: this.name,
2434
+ SubjectAlternativeNames: this.props.alternativeNames || [],
2247
2435
  ValidationMethod: "DNS",
2248
- SubjectAlternativeNames: this.props.alternativeNames || []
2436
+ DomainValidationOptions: [{
2437
+ DomainName: this.name,
2438
+ HostedZoneId: this.props.hostedZoneId
2439
+ }]
2249
2440
  };
2250
2441
  }
2251
2442
  };
@@ -2277,40 +2468,986 @@ var RecordSetGroup = class extends Resource {
2277
2468
  }
2278
2469
  };
2279
2470
 
2280
- // src/plugins/domain.ts
2281
- var DomainNameSchema = import_zod15.z.string().regex(/[a-z\-\_\.]/g, "Invalid domain name");
2282
- var domainPlugin = definePlugin({
2283
- name: "domain",
2284
- schema: import_zod15.z.object({
2285
- domains: import_zod15.z.record(DomainNameSchema, import_zod15.z.object({
2286
- name: DomainNameSchema.optional(),
2287
- type: import_zod15.z.enum(["A", "AAAA", "CAA", "CNAME", "DS", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"]),
2288
- ttl: DurationSchema,
2289
- records: import_zod15.z.string().array()
2290
- }).array()).optional()
2291
- }),
2292
- onApp({ config, bootstrap: bootstrap2, usEastBootstrap }) {
2293
- for (const [domain, records] of Object.entries(config.domains || {})) {
2294
- const hostedZone = new HostedZone(domain);
2295
- const certificate = new Certificate(domain, {
2296
- alternativeNames: [`*.${domain}`]
2297
- });
2298
- bootstrap2.add(certificate);
2299
- usEastBootstrap.add(hostedZone).add(certificate).export(`certificate-${domain}-arn`, certificate.arn);
2300
- if (records.length > 0) {
2301
- const group = new RecordSetGroup(domain, {
2302
- hostedZoneId: hostedZone.id,
2303
- records
2304
- }).dependsOn(hostedZone);
2305
- usEastBootstrap.add(group);
2306
- }
2307
- }
2308
- }
2309
- });
2471
+ // src/custom/delete-hosted-zone/handler.ts
2472
+ var deleteHostedZoneRecordsHandlerCode = (
2473
+ /* JS */
2474
+ `
2475
+
2476
+ const { Route53Client, ListResourceRecordSetsCommand, ChangeResourceRecordSetsCommand } = require('@aws-sdk/client-route-53')
2477
+
2478
+ const client = new Route53Client({})
2479
+
2480
+ exports.handler = async (event) => {
2481
+ const type = event.RequestType
2482
+ const hostedZoneId = event.ResourceProperties.hostedZoneId
2483
+
2484
+ try {
2485
+ if(type === 'Delete') {
2486
+ const records = await listHostedZoneRecords(hostedZoneId)
2487
+ console.log(records)
2488
+
2489
+ await deleteHostedZoneRecords(hostedZoneId, records)
2490
+ }
2491
+
2492
+ await send(event, hostedZoneId, 'SUCCESS')
2493
+ }
2494
+ catch(error) {
2495
+ if (error instanceof Error) {
2496
+ await send(event, hostedZoneId, 'FAILED', {}, error.message)
2497
+ } else {
2498
+ await send(event, hostedZoneId, 'FAILED', {}, 'Unknown error')
2499
+ }
2500
+ }
2501
+ }
2502
+
2503
+ const send = async (event, id, status, data = {}, reason = '') => {
2504
+ const body = JSON.stringify({
2505
+ Status: status,
2506
+ Reason: reason,
2507
+ PhysicalResourceId: id,
2508
+ StackId: event.StackId,
2509
+ RequestId: event.RequestId,
2510
+ LogicalResourceId: event.LogicalResourceId,
2511
+ NoEcho: false,
2512
+ Data: data
2513
+ })
2514
+
2515
+ await fetch(event.ResponseURL, {
2516
+ method: 'PUT',
2517
+ port: 443,
2518
+ body,
2519
+ headers: {
2520
+ 'content-type': '',
2521
+ 'content-length': Buffer.from(body).byteLength,
2522
+ },
2523
+ })
2524
+ }
2525
+
2526
+ const deleteHostedZoneRecords = async (hostedZoneId, records) => {
2527
+ records = records.filter(record => ![ 'SOA', 'NS' ].includes(record.Type))
2528
+ if(records.length === 0) {
2529
+ return
2530
+ }
2531
+
2532
+ const chunkSize = 100;
2533
+ for (let i = 0; i < records.length; i += chunkSize) {
2534
+ const chunk = records.slice(i, i + chunkSize);
2535
+
2536
+ await client.send(new ChangeResourceRecordSetsCommand({
2537
+ HostedZoneId: hostedZoneId,
2538
+ ChangeBatch: {
2539
+ Changes: chunk.map(record => ({
2540
+ Action: 'DELETE',
2541
+ ResourceRecordSet: record
2542
+ }))
2543
+ }
2544
+ }))
2545
+ }
2546
+ }
2547
+
2548
+ const listHostedZoneRecords = async (hostedZoneId) => {
2549
+
2550
+ const records = []
2551
+ let token
2552
+
2553
+ while(true) {
2554
+ const result = await client.send(new ListResourceRecordSetsCommand({
2555
+ HostedZoneId: hostedZoneId,
2556
+ NextRecordName: token
2557
+ }))
2558
+
2559
+ if(result.ResourceRecordSets && result.ResourceRecordSets.length) {
2560
+ records.push(...result.ResourceRecordSets)
2561
+ }
2562
+
2563
+ if(result.NextRecordName) {
2564
+ token = result.NextRecordName
2565
+ } else {
2566
+ return records
2567
+ }
2568
+ }
2569
+ }
2570
+ `
2571
+ );
2572
+
2573
+ // src/formation/resource/cloud-formation/custom-resource.ts
2574
+ var CustomResource = class extends Resource {
2575
+ constructor(logicalId, props) {
2576
+ super("AWS::CloudFormation::CustomResource", logicalId);
2577
+ this.props = props;
2578
+ }
2579
+ getAtt(name) {
2580
+ return getAtt(this.logicalId, name);
2581
+ }
2582
+ properties() {
2583
+ return {
2584
+ ServiceToken: this.props.serviceToken,
2585
+ ...this.props.properties
2586
+ };
2587
+ }
2588
+ };
2589
+
2590
+ // src/plugins/domain.ts
2591
+ var DomainNameSchema = import_zod15.z.string().regex(/[a-z\-\_\.]/g, "Invalid domain name");
2592
+ var domainPlugin = definePlugin({
2593
+ name: "domain",
2594
+ schema: import_zod15.z.object({
2595
+ /** Define the domains for your application.
2596
+ * @example
2597
+ * {
2598
+ * domains: {
2599
+ * 'example.com': [{
2600
+ * name: 'www',
2601
+ * type: 'TXT',
2602
+ * ttl: '60 seconds',
2603
+ * records: [ 'value' ]
2604
+ * }]
2605
+ * }
2606
+ * }
2607
+ */
2608
+ domains: import_zod15.z.record(DomainNameSchema, import_zod15.z.object({
2609
+ /** Enter a fully qualified domain name, for example, www.example.com.
2610
+ * You can optionally include a trailing dot.
2611
+ * If you omit the trailing dot, Amazon Route 53 assumes that the domain name that you specify is fully qualified.
2612
+ * This means that Route 53 treats www.example.com (without a trailing dot) and www.example.com. (with a trailing dot) as identical.
2613
+ */
2614
+ name: DomainNameSchema.optional(),
2615
+ /** The DNS record type. */
2616
+ type: import_zod15.z.enum(["A", "AAAA", "CAA", "CNAME", "DS", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"]),
2617
+ /** The resource record cache time to live (TTL) */
2618
+ ttl: DurationSchema,
2619
+ /** One or more values that correspond with the value that you specified for the Type property. */
2620
+ records: import_zod15.z.string().array()
2621
+ }).array()).optional()
2622
+ }),
2623
+ onApp({ config, bootstrap: bootstrap2, usEastBootstrap }) {
2624
+ const domains = Object.entries(config.domains || {});
2625
+ if (domains.length === 0) {
2626
+ return;
2627
+ }
2628
+ const lambda = new Function("delete-hosted-zone", {
2629
+ name: `${config.name}-delete-hosted-zone`,
2630
+ code: Code.fromInline(deleteHostedZoneRecordsHandlerCode, "index.handler")
2631
+ });
2632
+ lambda.addPermissions({
2633
+ actions: [
2634
+ "route53:ListResourceRecordSets",
2635
+ "route53:ChangeResourceRecordSets"
2636
+ ],
2637
+ resources: ["*"]
2638
+ });
2639
+ usEastBootstrap.add(lambda);
2640
+ for (const [domain, records] of domains) {
2641
+ const hostedZone = new HostedZone(domain);
2642
+ const usEastCertificate = new Certificate(domain, {
2643
+ hostedZoneId: hostedZone.id,
2644
+ alternativeNames: [`*.${domain}`]
2645
+ });
2646
+ const custom = new CustomResource(domain, {
2647
+ serviceToken: lambda.arn,
2648
+ properties: {
2649
+ hostedZoneId: hostedZone.id
2650
+ }
2651
+ }).dependsOn(hostedZone);
2652
+ usEastBootstrap.add(custom).add(hostedZone).add(usEastCertificate).export(`certificate-${domain}-arn`, usEastCertificate.arn).export(`hosted-zone-${domain}-id`, hostedZone.id);
2653
+ const certificate = new Certificate(domain, {
2654
+ hostedZoneId: usEastBootstrap.import(`hosted-zone-${domain}-id`),
2655
+ alternativeNames: [`*.${domain}`]
2656
+ });
2657
+ bootstrap2.add(certificate).export(`certificate-${domain}-arn`, certificate.arn);
2658
+ if (records.length > 0) {
2659
+ const group = new RecordSetGroup(domain, {
2660
+ hostedZoneId: hostedZone.id,
2661
+ records
2662
+ }).dependsOn(hostedZone);
2663
+ usEastBootstrap.add(group);
2664
+ }
2665
+ }
2666
+ }
2667
+ });
2668
+
2669
+ // src/plugins/on-failure.ts
2670
+ var import_zod16 = require("zod");
2671
+ var hasOnFailure = (config) => {
2672
+ const onFailure = config.stacks.find((stack) => {
2673
+ return typeof stack.onFailure !== "undefined";
2674
+ });
2675
+ return !!onFailure;
2676
+ };
2677
+ var onFailurePlugin = definePlugin({
2678
+ name: "on-failure",
2679
+ schema: import_zod16.z.object({
2680
+ stacks: import_zod16.z.object({
2681
+ /** Defining a onFailure handler will add a global onFailure handler for the following resources:
2682
+ * - Async lambda functions
2683
+ * - SQS queues
2684
+ * - DynamoDB streams
2685
+ * @example
2686
+ * {
2687
+ * onFailure: 'on-failure.ts'
2688
+ * }
2689
+ */
2690
+ onFailure: FunctionSchema.optional()
2691
+ }).array()
2692
+ }),
2693
+ onApp({ config, bootstrap: bootstrap2 }) {
2694
+ if (!hasOnFailure(config)) {
2695
+ return;
2696
+ }
2697
+ const queue2 = new Queue("on-failure", {
2698
+ name: `${config.name}-failure`
2699
+ });
2700
+ bootstrap2.add(queue2).export("on-failure-queue-arn", queue2.arn);
2701
+ },
2702
+ onStack(ctx) {
2703
+ const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
2704
+ const onFailure = stackConfig.onFailure;
2705
+ if (!onFailure) {
2706
+ return;
2707
+ }
2708
+ const queueArn = bootstrap2.import("on-failure-queue-arn");
2709
+ const lambda = toLambdaFunction(ctx, "on-failure", onFailure);
2710
+ const source = new SqsEventSource("on-failure", lambda, {
2711
+ queueArn
2712
+ });
2713
+ lambda.addPermissions({
2714
+ actions: [
2715
+ "sqs:SendMessage",
2716
+ "sqs:ReceiveMessage",
2717
+ "sqs:GetQueueUrl",
2718
+ "sqs:GetQueueAttributes"
2719
+ ],
2720
+ resources: [queueArn]
2721
+ });
2722
+ stack.add(lambda, source);
2723
+ },
2724
+ onResource({ config, resource, bootstrap: bootstrap2 }) {
2725
+ if (!hasOnFailure(config)) {
2726
+ return;
2727
+ }
2728
+ const queueArn = bootstrap2.import("on-failure-queue-arn");
2729
+ if (resource instanceof Queue) {
2730
+ resource.setDeadLetter(queueArn);
2731
+ }
2732
+ if (resource instanceof EventInvokeConfig) {
2733
+ resource.setOnFailure(queueArn);
2734
+ }
2735
+ if (resource instanceof EventSourceMapping) {
2736
+ resource.setOnFailure(queueArn);
2737
+ }
2738
+ }
2739
+ });
2740
+
2741
+ // src/formation/resource/ec2/vpc.ts
2742
+ var Vpc = class extends Resource {
2743
+ constructor(logicalId, props) {
2744
+ super("AWS::EC2::VPC", logicalId);
2745
+ this.props = props;
2746
+ }
2747
+ get id() {
2748
+ return ref(this.logicalId);
2749
+ }
2750
+ properties() {
2751
+ return {
2752
+ CidrBlock: this.props.cidrBlock.ip
2753
+ };
2754
+ }
2755
+ };
2756
+ var RouteTable = class extends Resource {
2757
+ constructor(logicalId, props) {
2758
+ super("AWS::EC2::RouteTable", logicalId);
2759
+ this.props = props;
2760
+ this.name = formatName(props.name || logicalId);
2761
+ }
2762
+ name;
2763
+ get id() {
2764
+ return ref(this.logicalId);
2765
+ }
2766
+ properties() {
2767
+ return {
2768
+ VpcId: this.props.vpcId,
2769
+ Tags: [{
2770
+ Key: "name",
2771
+ Value: this.name
2772
+ }]
2773
+ };
2774
+ }
2775
+ };
2776
+ var InternetGateway = class extends Resource {
2777
+ constructor(logicalId) {
2778
+ super("AWS::EC2::InternetGateway", logicalId);
2779
+ }
2780
+ get id() {
2781
+ return ref(this.logicalId);
2782
+ }
2783
+ properties() {
2784
+ return {};
2785
+ }
2786
+ };
2787
+ var VPCGatewayAttachment = class extends Resource {
2788
+ constructor(logicalId, props) {
2789
+ super("AWS::EC2::VPCGatewayAttachment", logicalId);
2790
+ this.props = props;
2791
+ }
2792
+ get id() {
2793
+ return ref(this.logicalId);
2794
+ }
2795
+ properties() {
2796
+ return {
2797
+ VpcId: this.props.vpcId,
2798
+ InternetGatewayId: this.props.internetGatewayId
2799
+ };
2800
+ }
2801
+ };
2802
+ var Route = class extends Resource {
2803
+ constructor(logicalId, props) {
2804
+ super("AWS::EC2::Route", logicalId);
2805
+ this.props = props;
2806
+ }
2807
+ get id() {
2808
+ return ref(this.logicalId);
2809
+ }
2810
+ properties() {
2811
+ return {
2812
+ GatewayId: this.props.gatewayId,
2813
+ RouteTableId: this.props.routeTableId,
2814
+ DestinationCidrBlock: this.props.destination.ip
2815
+ };
2816
+ }
2817
+ };
2818
+ var Subnet = class extends Resource {
2819
+ constructor(logicalId, props) {
2820
+ super("AWS::EC2::Subnet", logicalId);
2821
+ this.props = props;
2822
+ }
2823
+ get id() {
2824
+ return ref(this.logicalId);
2825
+ }
2826
+ properties() {
2827
+ return {
2828
+ VpcId: this.props.vpcId,
2829
+ CidrBlock: this.props.cidrBlock.ip,
2830
+ AvailabilityZone: this.props.availabilityZone
2831
+ };
2832
+ }
2833
+ };
2834
+ var SubnetRouteTableAssociation = class extends Resource {
2835
+ constructor(logicalId, props) {
2836
+ super("AWS::EC2::SubnetRouteTableAssociation", logicalId);
2837
+ this.props = props;
2838
+ }
2839
+ get id() {
2840
+ return ref(this.logicalId);
2841
+ }
2842
+ properties() {
2843
+ return {
2844
+ SubnetId: this.props.subnetId,
2845
+ RouteTableId: this.props.routeTableId
2846
+ };
2847
+ }
2848
+ };
2849
+
2850
+ // src/formation/resource/ec2/peer.ts
2851
+ var Peer = class {
2852
+ constructor(ip, type) {
2853
+ this.ip = ip;
2854
+ this.type = type;
2855
+ }
2856
+ static ipv4(cidrIp) {
2857
+ const cidrMatch = cidrIp.match(/^(\d{1,3}\.){3}\d{1,3}(\/\d+)?$/);
2858
+ if (!cidrMatch) {
2859
+ throw new Error(`Invalid IPv4 CIDR: "${cidrIp}"`);
2860
+ }
2861
+ if (!cidrMatch[2]) {
2862
+ throw new Error(`CIDR mask is missing in IPv4: "${cidrIp}". Did you mean "${cidrIp}/32"?`);
2863
+ }
2864
+ return new Peer(cidrIp, "v4");
2865
+ }
2866
+ static anyIpv4() {
2867
+ return new Peer("0.0.0.0/0", "v4");
2868
+ }
2869
+ static ipv6(cidrIpv6) {
2870
+ const cidrMatch = cidrIpv6.match(/^([\da-f]{0,4}:){2,7}([\da-f]{0,4})?(\/\d+)?$/);
2871
+ if (!cidrMatch) {
2872
+ throw new Error(`Invalid IPv6 CIDR: "${cidrIpv6}"`);
2873
+ }
2874
+ if (!cidrMatch[3]) {
2875
+ throw new Error(`CIDR mask is missing in IPv6: "${cidrIpv6}". Did you mean "${cidrIpv6}/128"?`);
2876
+ }
2877
+ return new Peer(cidrIpv6, "v6");
2878
+ }
2879
+ static anyIpv6() {
2880
+ return new Peer("::/0", "v6");
2881
+ }
2882
+ toRuleJson() {
2883
+ switch (this.type) {
2884
+ case "v4":
2885
+ return { CidrIp: this.ip };
2886
+ case "v6":
2887
+ return { CidrIpv6: this.ip };
2888
+ }
2889
+ }
2890
+ toString() {
2891
+ return this.ip;
2892
+ }
2893
+ };
2894
+
2895
+ // src/plugins/vpc.ts
2896
+ var vpcPlugin = definePlugin({
2897
+ name: "vpc",
2898
+ // schema: z.object({
2899
+ // defaults: z.object({
2900
+ // vpc: z.boolean().default(false),
2901
+ // }).default({}),
2902
+ // }),
2903
+ onApp({ config, bootstrap: bootstrap2 }) {
2904
+ const vpc = new Vpc("main", {
2905
+ cidrBlock: Peer.ipv4("10.0.0.0/16")
2906
+ });
2907
+ const privateRouteTable = new RouteTable("private", {
2908
+ vpcId: vpc.id,
2909
+ name: "private"
2910
+ }).dependsOn(vpc);
2911
+ const publicRouteTable = new RouteTable("public", {
2912
+ vpcId: vpc.id,
2913
+ name: "public"
2914
+ }).dependsOn(vpc);
2915
+ const gateway = new InternetGateway("");
2916
+ const attachment = new VPCGatewayAttachment("", {
2917
+ vpcId: vpc.id,
2918
+ internetGatewayId: gateway.id
2919
+ }).dependsOn(vpc, gateway);
2920
+ const route = new Route("", {
2921
+ gatewayId: gateway.id,
2922
+ routeTableId: publicRouteTable.id,
2923
+ destination: Peer.anyIpv4()
2924
+ }).dependsOn(gateway, publicRouteTable);
2925
+ bootstrap2.export(`vpc-id`, vpc.id);
2926
+ bootstrap2.add(
2927
+ vpc,
2928
+ privateRouteTable,
2929
+ publicRouteTable,
2930
+ gateway,
2931
+ attachment,
2932
+ route
2933
+ );
2934
+ const zones = ["a", "b"];
2935
+ const tables = [privateRouteTable, publicRouteTable];
2936
+ let block = 0;
2937
+ for (const table of tables) {
2938
+ for (const i in zones) {
2939
+ const id = `${table.name}-${i}`;
2940
+ const subnet = new Subnet(id, {
2941
+ vpcId: vpc.id,
2942
+ cidrBlock: Peer.ipv4(`10.0.${block++}.0/24`),
2943
+ availabilityZone: config.region + zones[i]
2944
+ }).dependsOn(vpc);
2945
+ const association = new SubnetRouteTableAssociation(id, {
2946
+ routeTableId: table.id,
2947
+ subnetId: subnet.id
2948
+ }).dependsOn(subnet, table);
2949
+ bootstrap2.export(`${table.name}-subnet-${Number(i) + 1}`, subnet.id);
2950
+ bootstrap2.add(
2951
+ subnet,
2952
+ association
2953
+ );
2954
+ }
2955
+ }
2956
+ }
2957
+ });
2958
+
2959
+ // src/plugins/http.ts
2960
+ var import_zod17 = require("zod");
2961
+
2962
+ // src/formation/resource/ec2/security-group.ts
2963
+ var SecurityGroup = class extends Resource {
2964
+ constructor(logicalId, props) {
2965
+ super("AWS::EC2::SecurityGroup", logicalId);
2966
+ this.props = props;
2967
+ }
2968
+ ingress = [];
2969
+ egress = [];
2970
+ get id() {
2971
+ return ref(this.logicalId);
2972
+ }
2973
+ addIngressRule(peer, port, description) {
2974
+ this.ingress.push({
2975
+ peer,
2976
+ port,
2977
+ description
2978
+ });
2979
+ return this;
2980
+ }
2981
+ addEgressRule(peer, port, description) {
2982
+ this.egress.push({
2983
+ peer,
2984
+ port,
2985
+ description
2986
+ });
2987
+ return this;
2988
+ }
2989
+ properties() {
2990
+ return {
2991
+ VpcId: this.props.vpcId,
2992
+ GroupName: this.logicalId,
2993
+ GroupDescription: this.props.description,
2994
+ SecurityGroupIngress: this.ingress.map((rule) => ({
2995
+ Description: rule.description || "",
2996
+ ...rule.port.toRuleJson(),
2997
+ ...rule.peer.toRuleJson()
2998
+ })),
2999
+ SecurityGroupEgress: this.egress.map((rule) => ({
3000
+ Description: rule.description || "",
3001
+ ...rule.port.toRuleJson(),
3002
+ ...rule.peer.toRuleJson()
3003
+ }))
3004
+ };
3005
+ }
3006
+ };
3007
+
3008
+ // src/formation/resource/ec2/port.ts
3009
+ var Port = class {
3010
+ static tcp(port) {
3011
+ return new Port({
3012
+ protocol: "tcp" /* TCP */,
3013
+ from: port,
3014
+ to: port
3015
+ });
3016
+ }
3017
+ static tcpRange(startPort, endPort) {
3018
+ return new Port({
3019
+ protocol: "tcp" /* TCP */,
3020
+ from: startPort,
3021
+ to: endPort
3022
+ });
3023
+ }
3024
+ static allTcp() {
3025
+ return new Port({
3026
+ protocol: "tcp" /* TCP */,
3027
+ from: 0,
3028
+ to: 65535
3029
+ });
3030
+ }
3031
+ static allTraffic() {
3032
+ return new Port({
3033
+ protocol: "-1" /* ALL */
3034
+ });
3035
+ }
3036
+ protocol;
3037
+ from;
3038
+ to;
3039
+ constructor(props) {
3040
+ this.protocol = props.protocol;
3041
+ this.from = props.from;
3042
+ this.to = props.to;
3043
+ }
3044
+ toRuleJson() {
3045
+ return {
3046
+ IpProtocol: this.protocol,
3047
+ FromPort: this.from,
3048
+ ToPort: this.to
3049
+ };
3050
+ }
3051
+ };
3052
+
3053
+ // src/formation/resource/elb/load-balancer.ts
3054
+ var LoadBalancer = class extends Resource {
3055
+ constructor(logicalId, props) {
3056
+ super("AWS::ElasticLoadBalancingV2::LoadBalancer", logicalId);
3057
+ this.props = props;
3058
+ this.name = this.props.name || logicalId;
3059
+ }
3060
+ name;
3061
+ get arn() {
3062
+ return ref(this.logicalId);
3063
+ }
3064
+ get dnsName() {
3065
+ return getAtt(this.logicalId, "DNSName");
3066
+ }
3067
+ get hostedZoneId() {
3068
+ return getAtt(this.logicalId, "CanonicalHostedZoneID");
3069
+ }
3070
+ properties() {
3071
+ return {
3072
+ Name: this.name,
3073
+ Type: this.props.type,
3074
+ Scheme: this.props.schema || "internet-facing",
3075
+ SecurityGroups: this.props.securityGroups,
3076
+ Subnets: this.props.subnets
3077
+ };
3078
+ }
3079
+ };
3080
+
3081
+ // src/formation/resource/elb/listener.ts
3082
+ var import_change_case11 = require("change-case");
3083
+ var Listener = class extends Resource {
3084
+ constructor(logicalId, props) {
3085
+ super("AWS::ElasticLoadBalancingV2::Listener", logicalId);
3086
+ this.props = props;
3087
+ }
3088
+ get id() {
3089
+ return ref(this.logicalId);
3090
+ }
3091
+ get arn() {
3092
+ return getAtt(this.logicalId, "ListenerArn");
3093
+ }
3094
+ properties() {
3095
+ return {
3096
+ LoadBalancerArn: this.props.loadBalancerArn,
3097
+ Port: this.props.port,
3098
+ Protocol: (0, import_change_case11.constantCase)(this.props.protocol),
3099
+ Certificates: this.props.certificates.map((arn) => ({
3100
+ CertificateArn: arn
3101
+ })),
3102
+ ...this.attr("DefaultActions", this.props.defaultActions?.map((action) => action.toJSON()))
3103
+ };
3104
+ }
3105
+ };
3106
+ var ListenerAction = class {
3107
+ constructor(props) {
3108
+ this.props = props;
3109
+ }
3110
+ static fixedResponse(statusCode, props = {}) {
3111
+ return new ListenerAction({
3112
+ type: "fixed-response",
3113
+ fixedResponse: {
3114
+ statusCode,
3115
+ ...props
3116
+ }
3117
+ });
3118
+ }
3119
+ static forward(targets) {
3120
+ return new ListenerAction({
3121
+ type: "forward",
3122
+ forward: {
3123
+ targetGroups: targets
3124
+ }
3125
+ });
3126
+ }
3127
+ toJSON() {
3128
+ return {
3129
+ // AuthenticateCognitoConfig: AuthenticateCognitoConfig,
3130
+ // AuthenticateOidcConfig: AuthenticateOidcConfig,
3131
+ // RedirectConfig: RedirectConfig,
3132
+ Type: this.props.type,
3133
+ // Order: Integer,
3134
+ ...this.props.type === "fixed-response" ? {
3135
+ FixedResponseConfig: {
3136
+ StatusCode: this.props.fixedResponse.statusCode,
3137
+ ...this.props.fixedResponse.contentType ? {
3138
+ ContentType: this.props.fixedResponse.contentType
3139
+ } : {},
3140
+ ...this.props.fixedResponse.messageBody ? {
3141
+ MessageBody: this.props.fixedResponse.messageBody
3142
+ } : {}
3143
+ }
3144
+ } : {},
3145
+ ...this.props.type === "forward" ? {
3146
+ ForwardConfig: {
3147
+ TargetGroups: this.props.forward.targetGroups.map((target) => ({
3148
+ TargetGroupArn: target
3149
+ }))
3150
+ }
3151
+ } : {}
3152
+ };
3153
+ }
3154
+ };
3155
+
3156
+ // src/formation/resource/elb/listener-rule.ts
3157
+ var ListenerRule = class extends Resource {
3158
+ constructor(logicalId, props) {
3159
+ super("AWS::ElasticLoadBalancingV2::ListenerRule", logicalId);
3160
+ this.props = props;
3161
+ }
3162
+ get id() {
3163
+ return ref(this.logicalId);
3164
+ }
3165
+ get arn() {
3166
+ return getAtt(this.logicalId, "ListenerArn");
3167
+ }
3168
+ properties() {
3169
+ return {
3170
+ ListenerArn: this.props.listenerArn,
3171
+ Priority: this.props.priority,
3172
+ Conditions: this.props.conditions.map((condition) => condition.toJSON()),
3173
+ Actions: this.props.actions.map((action) => action.toJSON())
3174
+ };
3175
+ }
3176
+ };
3177
+ var ListenerCondition = class {
3178
+ constructor(props) {
3179
+ this.props = props;
3180
+ }
3181
+ static httpRequestMethods(methods) {
3182
+ return new ListenerCondition({
3183
+ field: "http-request-method",
3184
+ methods
3185
+ });
3186
+ }
3187
+ static pathPatterns(paths) {
3188
+ return new ListenerCondition({
3189
+ field: "path-pattern",
3190
+ paths
3191
+ });
3192
+ }
3193
+ toJSON() {
3194
+ return {
3195
+ Field: this.props.field,
3196
+ ...this.props.field === "http-request-method" ? {
3197
+ HttpRequestMethodConfig: {
3198
+ Values: this.props.methods
3199
+ }
3200
+ } : {},
3201
+ ...this.props.field === "path-pattern" ? {
3202
+ PathPatternConfig: {
3203
+ Values: this.props.paths
3204
+ }
3205
+ } : {}
3206
+ };
3207
+ }
3208
+ };
3209
+
3210
+ // src/formation/resource/elb/target-group.ts
3211
+ var TargetGroup = class extends Resource {
3212
+ constructor(logicalId, props) {
3213
+ super("AWS::ElasticLoadBalancingV2::TargetGroup", logicalId);
3214
+ this.props = props;
3215
+ this.name = formatName(this.props.name || logicalId);
3216
+ }
3217
+ name;
3218
+ get arn() {
3219
+ return ref(this.logicalId);
3220
+ }
3221
+ get fullName() {
3222
+ return getAtt(this.logicalId, "TargetGroupFullName");
3223
+ }
3224
+ properties() {
3225
+ return {
3226
+ Name: this.name,
3227
+ TargetType: this.props.type,
3228
+ Targets: this.props.targets.map((target) => ({
3229
+ Id: target
3230
+ }))
3231
+ };
3232
+ }
3233
+ };
3234
+
3235
+ // src/formation/resource/lambda/event-source/elb.ts
3236
+ var ElbEventSource = class extends Group {
3237
+ constructor(id, lambda, props) {
3238
+ const name = formatName(id);
3239
+ const permission = new Permission2(id, {
3240
+ action: "lambda:InvokeFunction",
3241
+ principal: "elasticloadbalancing.amazonaws.com",
3242
+ functionArn: lambda.arn,
3243
+ sourceArn: sub("arn:${AWS::Partition}:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:targetgroup/${name}/*", {
3244
+ name
3245
+ })
3246
+ }).dependsOn(lambda);
3247
+ const target = new TargetGroup(id, {
3248
+ name,
3249
+ type: "lambda",
3250
+ targets: [lambda.arn]
3251
+ }).dependsOn(lambda, permission);
3252
+ const rule = new ListenerRule(id, {
3253
+ listenerArn: props.listenerArn,
3254
+ priority: props.priority,
3255
+ conditions: props.conditions,
3256
+ actions: [
3257
+ ListenerAction.forward([target.arn])
3258
+ ]
3259
+ }).dependsOn(target);
3260
+ super([target, rule, permission]);
3261
+ }
3262
+ };
3263
+
3264
+ // src/plugins/http.ts
3265
+ var RouteSchema = import_zod17.z.custom((route) => {
3266
+ return import_zod17.z.string().regex(/^(POST|GET|PUT|DELETE|HEAD|OPTIONS)(\s\/[a-z0-9\+\_\-\/]*)$/ig).safeParse(route).success;
3267
+ }, "Invalid route");
3268
+ var parseRoute = (route) => {
3269
+ const [method, ...paths] = route.split(" ");
3270
+ const path = paths.join(" ");
3271
+ return { method, path };
3272
+ };
3273
+ var strToInt = (str) => {
3274
+ return parseInt(Buffer.from(str, "utf8").toString("hex"), 16);
3275
+ };
3276
+ var generatePriority = (stackName, route) => {
3277
+ const start = strToInt(stackName) % 500 + 1;
3278
+ const end = strToInt(route) % 100;
3279
+ const priority = start + "" + end;
3280
+ return parseInt(priority, 10);
3281
+ };
3282
+ var httpPlugin = definePlugin({
3283
+ name: "http",
3284
+ schema: import_zod17.z.object({
3285
+ defaults: import_zod17.z.object({
3286
+ /** Define your global http api's.
3287
+ * @example
3288
+ * {
3289
+ * http: {
3290
+ * HTTP_API_NAME: {
3291
+ * domain: 'example.com',
3292
+ * subDomain: 'api',
3293
+ * }
3294
+ * }
3295
+ * }
3296
+ */
3297
+ http: import_zod17.z.record(
3298
+ ResourceIdSchema,
3299
+ import_zod17.z.object({
3300
+ /** The domain to link your api with. */
3301
+ domain: import_zod17.z.string(),
3302
+ subDomain: import_zod17.z.string().optional()
3303
+ })
3304
+ ).optional()
3305
+ }).default({}),
3306
+ stacks: import_zod17.z.object({
3307
+ /** Define routes in your stack for your global http api.
3308
+ * @example
3309
+ * {
3310
+ * http: {
3311
+ * HTTP_API_NAME: {
3312
+ * 'GET /': 'index.ts',
3313
+ * 'POST /posts': 'create-post.ts',
3314
+ * }
3315
+ * }
3316
+ * }
3317
+ */
3318
+ http: import_zod17.z.record(
3319
+ ResourceIdSchema,
3320
+ import_zod17.z.record(RouteSchema, FunctionSchema)
3321
+ ).optional()
3322
+ }).array()
3323
+ }),
3324
+ onApp({ config, bootstrap: bootstrap2, usEastBootstrap }) {
3325
+ if (Object.keys(config.defaults?.http || {}).length === 0) {
3326
+ return;
3327
+ }
3328
+ const vpcId = bootstrap2.get("vpc-id");
3329
+ const securityGroup = new SecurityGroup("http", {
3330
+ description: "http security group",
3331
+ vpcId
3332
+ });
3333
+ securityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(443));
3334
+ securityGroup.addIngressRule(Peer.anyIpv6(), Port.tcp(443));
3335
+ bootstrap2.add(securityGroup);
3336
+ for (const [id, props] of Object.entries(config.defaults?.http || {})) {
3337
+ const loadBalancer = new LoadBalancer(id, {
3338
+ name: `${config.name}-${id}`,
3339
+ type: "application",
3340
+ securityGroups: [securityGroup.id],
3341
+ subnets: [
3342
+ bootstrap2.get("public-subnet-1"),
3343
+ bootstrap2.get("public-subnet-2")
3344
+ ]
3345
+ }).dependsOn(securityGroup);
3346
+ const listener = new Listener(id, {
3347
+ loadBalancerArn: loadBalancer.arn,
3348
+ port: 443,
3349
+ protocol: "https",
3350
+ certificates: [
3351
+ bootstrap2.get(`certificate-${props.domain}-arn`)
3352
+ ],
3353
+ defaultActions: [
3354
+ ListenerAction.fixedResponse(404, {
3355
+ contentType: "application/json",
3356
+ messageBody: JSON.stringify({
3357
+ message: "Route not found"
3358
+ })
3359
+ })
3360
+ ]
3361
+ }).dependsOn(loadBalancer);
3362
+ const record = new RecordSet(`${id}-http`, {
3363
+ hostedZoneId: usEastBootstrap.import(`hosted-zone-${props.domain}-id`),
3364
+ name: props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain,
3365
+ type: "A",
3366
+ alias: {
3367
+ hostedZoneId: loadBalancer.hostedZoneId,
3368
+ dnsName: loadBalancer.dnsName
3369
+ }
3370
+ }).dependsOn(loadBalancer);
3371
+ bootstrap2.add(loadBalancer, listener, record).export(`http-${id}-listener-arn`, listener.arn);
3372
+ }
3373
+ },
3374
+ onStack(ctx) {
3375
+ const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
3376
+ for (const [id, routes] of Object.entries(stackConfig.http || {})) {
3377
+ for (const [route, props] of Object.entries(routes)) {
3378
+ const { method, path } = parseRoute(route);
3379
+ const lambda = toLambdaFunction(ctx, `http-${id}`, props);
3380
+ const source = new ElbEventSource(`http-${id}-${route}`, lambda, {
3381
+ listenerArn: bootstrap2.import(`http-${id}-listener-arn`),
3382
+ priority: generatePriority(stackConfig.name, route),
3383
+ conditions: [
3384
+ ListenerCondition.httpRequestMethods([method]),
3385
+ ListenerCondition.pathPatterns([path])
3386
+ ]
3387
+ });
3388
+ stack.add(lambda, source);
3389
+ }
3390
+ }
3391
+ }
3392
+ });
3393
+
3394
+ // src/plugins/search.ts
3395
+ var import_zod18 = require("zod");
3396
+
3397
+ // src/formation/resource/open-search-serverless/collection.ts
3398
+ var Collection = class extends Resource {
3399
+ constructor(logicalId, props) {
3400
+ super("AWS::OpenSearchServerless::Collection", logicalId);
3401
+ this.props = props;
3402
+ this.name = this.props.name || logicalId;
3403
+ }
3404
+ name;
3405
+ get id() {
3406
+ return ref(this.logicalId);
3407
+ }
3408
+ get arn() {
3409
+ return getAtt(this.logicalId, "Arn");
3410
+ }
3411
+ get endpoint() {
3412
+ return getAtt(this.logicalId, "CollectionEndpoint");
3413
+ }
3414
+ properties() {
3415
+ return {
3416
+ Name: this.name,
3417
+ Type: this.props.type.toUpperCase(),
3418
+ ...this.attr("Description", this.props.description)
3419
+ };
3420
+ }
3421
+ };
3422
+
3423
+ // src/plugins/search.ts
3424
+ var searchPlugin = definePlugin({
3425
+ name: "search",
3426
+ schema: import_zod18.z.object({
3427
+ stacks: import_zod18.z.object({
3428
+ searchs: import_zod18.z.array(ResourceIdSchema).optional()
3429
+ }).array()
3430
+ }),
3431
+ onStack({ config, stack, stackConfig, bind }) {
3432
+ for (const id of stackConfig.searchs || []) {
3433
+ const collection = new Collection(id, {
3434
+ name: `${config.name}-${stack.name}-${id}`,
3435
+ type: "search"
3436
+ });
3437
+ bind((lambda) => {
3438
+ lambda.addPermissions({
3439
+ actions: ["aoss:APIAccessAll"],
3440
+ resources: [collection.arn]
3441
+ });
3442
+ });
3443
+ }
3444
+ }
3445
+ });
2310
3446
 
2311
3447
  // src/plugins/index.ts
2312
3448
  var defaultPlugins = [
2313
3449
  extendPlugin,
3450
+ vpcPlugin,
2314
3451
  functionPlugin,
2315
3452
  cronPlugin,
2316
3453
  queuePlugin,
@@ -2318,10 +3455,11 @@ var defaultPlugins = [
2318
3455
  storePlugin,
2319
3456
  topicPlugin,
2320
3457
  pubsubPlugin,
2321
- // searchPlugin,
3458
+ searchPlugin,
2322
3459
  domainPlugin,
2323
- graphqlPlugin
2324
- // httpPlugin,
3460
+ graphqlPlugin,
3461
+ httpPlugin,
3462
+ onFailurePlugin
2325
3463
  ];
2326
3464
 
2327
3465
  // src/formation/app.ts
@@ -2348,23 +3486,6 @@ var App = class {
2348
3486
  // }
2349
3487
  };
2350
3488
 
2351
- // src/formation/resource/cloud-formation/custom-resource.ts
2352
- var CustomResource = class extends Resource {
2353
- constructor(logicalId, props) {
2354
- super("AWS::CloudFormation::CustomResource", logicalId);
2355
- this.props = props;
2356
- }
2357
- getAtt(name) {
2358
- return getAtt(this.logicalId, name);
2359
- }
2360
- properties() {
2361
- return {
2362
- ServiceToken: this.props.serviceToken,
2363
- ...this.props.properties
2364
- };
2365
- }
2366
- };
2367
-
2368
3489
  // src/custom/global-export/handler.ts
2369
3490
  var globalExportsHandlerCode = (
2370
3491
  /* JS */
@@ -2459,7 +3580,7 @@ var extendWithGlobalExports = (appName, importable, exportable) => {
2459
3580
  region: importable.region
2460
3581
  }
2461
3582
  });
2462
- exportable.add(crossRegionExports);
3583
+ exportable.add(lambda, crossRegionExports);
2463
3584
  }
2464
3585
  return crossRegionExports.getAtt(name);
2465
3586
  };
@@ -2520,6 +3641,20 @@ var toApp = async (config, filters) => {
2520
3641
  app.add(stack);
2521
3642
  stacks.push({ stack, config: stackConfig });
2522
3643
  }
3644
+ for (const plugin of plugins) {
3645
+ for (const stack of app.stacks) {
3646
+ for (const resource of stack) {
3647
+ plugin.onResource?.({
3648
+ config,
3649
+ app,
3650
+ stack,
3651
+ bootstrap: bootstrap2,
3652
+ usEastBootstrap,
3653
+ resource
3654
+ });
3655
+ }
3656
+ }
3657
+ }
2523
3658
  const functions = app.find(Function);
2524
3659
  for (const bind2 of bindings) {
2525
3660
  for (const fn of functions) {
@@ -2566,17 +3701,17 @@ var getCredentials = (profile) => {
2566
3701
  };
2567
3702
 
2568
3703
  // src/schema/app.ts
2569
- var import_zod19 = require("zod");
3704
+ var import_zod22 = require("zod");
2570
3705
 
2571
3706
  // src/schema/stack.ts
2572
- var import_zod16 = require("zod");
2573
- var StackSchema = import_zod16.z.object({
3707
+ var import_zod19 = require("zod");
3708
+ var StackSchema = import_zod19.z.object({
2574
3709
  name: ResourceIdSchema,
2575
- depends: import_zod16.z.array(import_zod16.z.lazy(() => StackSchema)).optional()
3710
+ depends: import_zod19.z.array(import_zod19.z.lazy(() => StackSchema)).optional()
2576
3711
  });
2577
3712
 
2578
3713
  // src/schema/region.ts
2579
- var import_zod17 = require("zod");
3714
+ var import_zod20 = require("zod");
2580
3715
  var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
2581
3716
  var AF = ["af-south-1"];
2582
3717
  var AP = ["ap-east-1", "ap-south-2", "ap-southeast-3", "ap-southeast-4", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1"];
@@ -2593,46 +3728,91 @@ var regions = [
2593
3728
  ...ME,
2594
3729
  ...SA
2595
3730
  ];
2596
- var RegionSchema = import_zod17.z.enum(regions);
3731
+ var RegionSchema = import_zod20.z.enum(regions);
2597
3732
 
2598
3733
  // src/schema/plugin.ts
2599
- var import_zod18 = require("zod");
2600
- var PluginSchema = import_zod18.z.object({
2601
- name: import_zod18.z.string(),
2602
- schema: import_zod18.z.custom().optional(),
3734
+ var import_zod21 = require("zod");
3735
+ var PluginSchema = import_zod21.z.object({
3736
+ name: import_zod21.z.string(),
3737
+ schema: import_zod21.z.custom().optional(),
2603
3738
  // depends: z.array(z.lazy(() => PluginSchema)).optional(),
2604
- onBootstrap: import_zod18.z.function().returns(import_zod18.z.any()).optional(),
2605
- onStack: import_zod18.z.function().returns(import_zod18.z.any()).optional(),
2606
- onApp: import_zod18.z.function().returns(import_zod18.z.void()).optional()
3739
+ onApp: import_zod21.z.function().returns(import_zod21.z.void()).optional(),
3740
+ onStack: import_zod21.z.function().returns(import_zod21.z.any()).optional(),
3741
+ onResource: import_zod21.z.function().returns(import_zod21.z.any()).optional()
2607
3742
  // bind: z.function().optional(),
2608
3743
  });
2609
3744
 
2610
3745
  // src/schema/app.ts
2611
- var AppSchema = import_zod19.z.object({
3746
+ var AppSchema = import_zod22.z.object({
3747
+ /** App name */
2612
3748
  name: ResourceIdSchema,
3749
+ /** The AWS region to deploy to. */
2613
3750
  region: RegionSchema,
2614
- profile: import_zod19.z.string(),
2615
- stage: import_zod19.z.string().regex(/[a-z]+/).default("prod"),
2616
- defaults: import_zod19.z.object({}).default({}),
2617
- stacks: import_zod19.z.array(StackSchema).min(1).refine((stacks) => {
3751
+ /** The AWS profile to deploy to. */
3752
+ profile: import_zod22.z.string(),
3753
+ /** The deployment stage.
3754
+ * @default 'prod'
3755
+ */
3756
+ stage: import_zod22.z.string().regex(/[a-z]+/).default("prod"),
3757
+ /** Default properties. */
3758
+ defaults: import_zod22.z.object({}).default({}),
3759
+ /** The application stacks. */
3760
+ stacks: import_zod22.z.array(StackSchema).min(1).refine((stacks) => {
2618
3761
  const unique = new Set(stacks.map((stack) => stack.name));
2619
3762
  return unique.size === stacks.length;
2620
3763
  }, "Must be an array of unique stacks"),
2621
- plugins: import_zod19.z.array(PluginSchema).optional()
3764
+ /** Custom plugins. */
3765
+ plugins: import_zod22.z.array(PluginSchema).optional()
2622
3766
  });
2623
3767
 
2624
3768
  // src/util/import.ts
2625
3769
  var import_core = require("@swc/core");
2626
3770
  var import_path2 = require("path");
2627
- var import_promises4 = require("fs/promises");
3771
+ var import_promises5 = require("fs/promises");
2628
3772
 
2629
3773
  // src/util/path.ts
3774
+ var import_promises4 = require("fs/promises");
2630
3775
  var import_path = require("path");
2631
- var rootDir = process.cwd();
2632
- var outDir = (0, import_path.join)(rootDir, ".awsless");
2633
- var templateDir = (0, import_path.join)(outDir, "template");
2634
- var assetDir = (0, import_path.join)(outDir, "asset");
2635
- var cacheDir = (0, import_path.join)(outDir, "cache");
3776
+ var root = process.cwd();
3777
+ var directories = {
3778
+ root,
3779
+ get output() {
3780
+ return (0, import_path.join)(this.root, ".awsless");
3781
+ },
3782
+ get cache() {
3783
+ return (0, import_path.join)(this.output, "cache");
3784
+ },
3785
+ get asset() {
3786
+ return (0, import_path.join)(this.output, "asset");
3787
+ },
3788
+ get template() {
3789
+ return (0, import_path.join)(this.output, "template");
3790
+ }
3791
+ };
3792
+ var setRoot = (path = root) => {
3793
+ directories.root = path;
3794
+ };
3795
+ var findRootDir = async (path, configFile, level = 5) => {
3796
+ if (!level) {
3797
+ throw new TypeError("No awsless project found");
3798
+ }
3799
+ const file = (0, import_path.join)(path, configFile);
3800
+ const exists = await fileExist(file);
3801
+ if (exists) {
3802
+ return path;
3803
+ }
3804
+ return findRootDir((0, import_path.normalize)((0, import_path.join)(path, "..")), configFile, level - 1);
3805
+ };
3806
+ var fileExist = async (file) => {
3807
+ try {
3808
+ const stat = await (0, import_promises4.lstat)(file);
3809
+ if (stat.isFile()) {
3810
+ return true;
3811
+ }
3812
+ } catch (error) {
3813
+ }
3814
+ return false;
3815
+ };
2636
3816
 
2637
3817
  // src/util/import.ts
2638
3818
  var resolveFileNameExtension = async (path) => {
@@ -2647,7 +3827,7 @@ var resolveFileNameExtension = async (path) => {
2647
3827
  const file = path.replace(/\.js$/, "") + option;
2648
3828
  let stat;
2649
3829
  try {
2650
- stat = await (0, import_promises4.lstat)(file);
3830
+ stat = await (0, import_promises5.lstat)(file);
2651
3831
  } catch (error) {
2652
3832
  continue;
2653
3833
  }
@@ -2658,11 +3838,11 @@ var resolveFileNameExtension = async (path) => {
2658
3838
  throw new Error(`Failed to load file: ${path}`);
2659
3839
  };
2660
3840
  var resolveDir = (path) => {
2661
- return (0, import_path2.dirname)(path).replace(rootDir + "/", "");
3841
+ return (0, import_path2.dirname)(path).replace(directories.root + "/", "");
2662
3842
  };
2663
3843
  var importFile = async (path) => {
2664
3844
  const load = async (file) => {
2665
- debug("Load file", file);
3845
+ debug("Load file:", style.info(file));
2666
3846
  let { code: code2 } = await (0, import_core.transformFile)(file, {
2667
3847
  isModule: true
2668
3848
  });
@@ -2682,16 +3862,22 @@ var importFile = async (path) => {
2682
3862
  return code2;
2683
3863
  };
2684
3864
  const code = await load(path);
2685
- const outputFile = (0, import_path2.join)(outDir, "config.js");
2686
- await (0, import_promises4.mkdir)(outDir, { recursive: true });
2687
- await (0, import_promises4.writeFile)(outputFile, code);
3865
+ const outputFile = (0, import_path2.join)(directories.cache, "config.js");
3866
+ await (0, import_promises5.mkdir)(directories.cache, { recursive: true });
3867
+ await (0, import_promises5.writeFile)(outputFile, code);
3868
+ debug("Save config file:", style.info(outputFile));
2688
3869
  return import(outputFile);
2689
3870
  };
2690
3871
 
2691
3872
  // src/config.ts
2692
3873
  var importConfig = async (options) => {
3874
+ debug("Find the root directory");
3875
+ const configFile = options.configFile || "awsless.config.ts";
3876
+ const root2 = await findRootDir(process.cwd(), configFile);
3877
+ setRoot(root2);
3878
+ debug("CWD:", style.info(root2));
2693
3879
  debug("Import config file");
2694
- const fileName = (0, import_path4.join)(process.cwd(), options.configFile || "awsless.config.ts");
3880
+ const fileName = (0, import_path4.join)(root2, configFile);
2695
3881
  const module2 = await importFile(fileName);
2696
3882
  const appConfig = typeof module2.default === "function" ? await module2.default(options) : module2.default;
2697
3883
  debug("Validate config file");
@@ -3191,7 +4377,7 @@ var layout = async (cb) => {
3191
4377
  };
3192
4378
 
3193
4379
  // src/cli/ui/complex/builder.ts
3194
- var import_promises5 = require("fs/promises");
4380
+ var import_promises6 = require("fs/promises");
3195
4381
 
3196
4382
  // src/cli/ui/layout/flex-line.ts
3197
4383
  var stripEscapeCode = (str) => {
@@ -3214,7 +4400,7 @@ var flexLine = (term, left, right, reserveSpace = 0) => {
3214
4400
  };
3215
4401
 
3216
4402
  // src/cli/ui/complex/builder.ts
3217
- var import_path6 = require("path");
4403
+ var import_path7 = require("path");
3218
4404
  var assetBuilder = (app) => {
3219
4405
  return async (term) => {
3220
4406
  const assets = [];
@@ -3265,7 +4451,7 @@ var assetBuilder = (app) => {
3265
4451
  derive([details], (details2) => {
3266
4452
  return Object.entries(details2).map(([key, value]) => {
3267
4453
  return `${style.label(key)} ${value}`;
3268
- }).join(" / ");
4454
+ }).join(style.placeholder(" \u2500 "));
3269
4455
  }),
3270
4456
  br()
3271
4457
  ]);
@@ -3273,10 +4459,10 @@ var assetBuilder = (app) => {
3273
4459
  const timer = createTimer();
3274
4460
  const data = await asset.build({
3275
4461
  async write(file, data2) {
3276
- const fullpath = (0, import_path6.join)(assetDir, asset.type, app.name, stack.name, asset.id, file);
3277
- const basepath = (0, import_path6.dirname)(fullpath);
3278
- await (0, import_promises5.mkdir)(basepath, { recursive: true });
3279
- await (0, import_promises5.writeFile)(fullpath, data2);
4462
+ const fullpath = (0, import_path7.join)(directories.asset, asset.type, app.name, stack.name, asset.id, file);
4463
+ const basepath = (0, import_path7.dirname)(fullpath);
4464
+ await (0, import_promises6.mkdir)(basepath, { recursive: true });
4465
+ await (0, import_promises6.writeFile)(fullpath, data2);
3280
4466
  }
3281
4467
  });
3282
4468
  details.set({
@@ -3293,36 +4479,36 @@ var assetBuilder = (app) => {
3293
4479
  };
3294
4480
 
3295
4481
  // src/util/cleanup.ts
3296
- var import_promises6 = require("fs/promises");
4482
+ var import_promises7 = require("fs/promises");
3297
4483
  var cleanUp = async () => {
3298
4484
  debug("Clean up template, cache, and asset files");
3299
4485
  const paths = [
3300
- templateDir,
3301
- assetDir,
3302
- cacheDir
4486
+ directories.asset,
4487
+ directories.cache,
4488
+ directories.template
3303
4489
  ];
3304
- await Promise.all(paths.map((path) => (0, import_promises6.rm)(path, {
4490
+ await Promise.all(paths.map((path) => (0, import_promises7.rm)(path, {
3305
4491
  recursive: true,
3306
4492
  force: true,
3307
4493
  maxRetries: 2
3308
4494
  })));
3309
- await Promise.all(paths.map((path) => (0, import_promises6.mkdir)(path, {
4495
+ await Promise.all(paths.map((path) => (0, import_promises7.mkdir)(path, {
3310
4496
  recursive: true
3311
4497
  })));
3312
4498
  };
3313
4499
 
3314
4500
  // src/cli/ui/complex/template.ts
3315
- var import_promises7 = require("fs/promises");
3316
- var import_path9 = require("path");
4501
+ var import_promises8 = require("fs/promises");
4502
+ var import_path10 = require("path");
3317
4503
  var templateBuilder = (app) => {
3318
4504
  return async (term) => {
3319
4505
  const done = term.out.write(loadingDialog("Building stack templates..."));
3320
4506
  await Promise.all(app.stacks.map(async (stack) => {
3321
4507
  const template = stack.toString(true);
3322
- const path = (0, import_path9.join)(templateDir, app.name);
3323
- const file = (0, import_path9.join)(path, `${stack.name}.json`);
3324
- await (0, import_promises7.mkdir)(path, { recursive: true });
3325
- await (0, import_promises7.writeFile)(file, template);
4508
+ const path = (0, import_path10.join)(directories.template, app.name);
4509
+ const file = (0, import_path10.join)(path, `${stack.name}.json`);
4510
+ await (0, import_promises8.mkdir)(path, { recursive: true });
4511
+ await (0, import_promises8.writeFile)(file, template);
3326
4512
  }));
3327
4513
  done("Done building stack templates");
3328
4514
  };
@@ -3370,7 +4556,7 @@ var shouldDeployBootstrap = async (client, stack) => {
3370
4556
  // src/formation/client.ts
3371
4557
  var import_client_cloudformation = require("@aws-sdk/client-cloudformation");
3372
4558
  var import_client_s3 = require("@aws-sdk/client-s3");
3373
- var import_change_case11 = require("change-case");
4559
+ var import_change_case12 = require("change-case");
3374
4560
  var StackClient = class {
3375
4561
  constructor(app, account, region, credentials) {
3376
4562
  this.app = app;
@@ -3403,7 +4589,7 @@ var StackClient = class {
3403
4589
  };
3404
4590
  }
3405
4591
  stackName(stackName) {
3406
- return (0, import_change_case11.paramCase)(`${this.app.name}-${stackName}`);
4592
+ return (0, import_change_case12.paramCase)(`${this.app.name}-${stackName}`);
3407
4593
  }
3408
4594
  tags(stack) {
3409
4595
  const tags = [];
@@ -3448,19 +4634,26 @@ var StackClient = class {
3448
4634
  async update(stack, capabilities) {
3449
4635
  debug("Update the", style.info(stack.name), "stack");
3450
4636
  const client = this.getClient(stack.region);
3451
- await client.send(new import_client_cloudformation.UpdateStackCommand({
3452
- StackName: this.stackName(stack.name),
3453
- Capabilities: capabilities,
3454
- Tags: this.tags(stack),
3455
- ...this.templateProp(stack)
3456
- }));
3457
- await (0, import_client_cloudformation.waitUntilStackUpdateComplete)({
3458
- client,
3459
- maxWaitTime: this.maxWaitTime,
3460
- maxDelay: this.maxDelay
3461
- }, {
3462
- StackName: this.stackName(stack.name)
3463
- });
4637
+ try {
4638
+ await client.send(new import_client_cloudformation.UpdateStackCommand({
4639
+ StackName: this.stackName(stack.name),
4640
+ Capabilities: capabilities,
4641
+ Tags: this.tags(stack),
4642
+ ...this.templateProp(stack)
4643
+ }));
4644
+ await (0, import_client_cloudformation.waitUntilStackUpdateComplete)({
4645
+ client,
4646
+ maxWaitTime: this.maxWaitTime,
4647
+ maxDelay: this.maxDelay
4648
+ }, {
4649
+ StackName: this.stackName(stack.name)
4650
+ });
4651
+ } catch (error) {
4652
+ if (error instanceof Error && error.name === "ValidationError" && error.message.toLowerCase().includes("no updates")) {
4653
+ return;
4654
+ }
4655
+ throw error;
4656
+ }
3464
4657
  }
3465
4658
  async validate(stack) {
3466
4659
  debug("Validate the", style.info(stack.name), "stack");
@@ -3746,8 +4939,8 @@ var status = (program2) => {
3746
4939
  };
3747
4940
 
3748
4941
  // src/cli/ui/complex/publisher.ts
3749
- var import_promises8 = require("fs/promises");
3750
- var import_path11 = require("path");
4942
+ var import_promises9 = require("fs/promises");
4943
+ var import_path12 = require("path");
3751
4944
  var import_client_s32 = require("@aws-sdk/client-s3");
3752
4945
  var assetPublisher = (config, app) => {
3753
4946
  const client = new import_client_s32.S3Client({
@@ -3760,8 +4953,8 @@ var assetPublisher = (config, app) => {
3760
4953
  await Promise.all([...stack.assets].map(async (asset) => {
3761
4954
  await asset.publish?.({
3762
4955
  async read(file) {
3763
- const path = (0, import_path11.join)(assetDir, asset.type, app.name, stack.name, asset.id, file);
3764
- const data = await (0, import_promises8.readFile)(path);
4956
+ const path = (0, import_path12.join)(directories.asset, asset.type, app.name, stack.name, asset.id, file);
4957
+ const data = await (0, import_promises9.readFile)(path);
3765
4958
  return data;
3766
4959
  },
3767
4960
  async publish(name, data, hash) {