@awsless/awsless 0.0.19 → 0.0.21

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
@@ -191,6 +191,12 @@ var formatLogicalId = (id) => {
191
191
  var formatName = (name) => {
192
192
  return (0, import_change_case2.paramCase)(name);
193
193
  };
194
+ var formatArn = (props) => {
195
+ return sub("arn:${AWS::Partition}:${service}:${AWS::Region}:${AWS::AccountId}:${resource}${seperator}${resourceName}", {
196
+ seperator: "/",
197
+ ...props
198
+ });
199
+ };
194
200
 
195
201
  // src/formation/resource.ts
196
202
  var Resource = class {
@@ -200,13 +206,19 @@ var Resource = class {
200
206
  this.logicalId = formatLogicalId(`${logicalId}-${type.replace(/^AWS::/, "")}`);
201
207
  }
202
208
  logicalId;
209
+ tags = /* @__PURE__ */ new Map();
203
210
  deps = /* @__PURE__ */ new Set();
211
+ stack;
204
212
  dependsOn(...dependencies) {
205
213
  for (const dependency of dependencies) {
206
214
  this.deps.add(dependency);
207
215
  }
208
216
  return this;
209
217
  }
218
+ tag(key, value) {
219
+ this.tags.set(key, value);
220
+ return this;
221
+ }
210
222
  attr(name, value) {
211
223
  if (typeof value === "undefined") {
212
224
  return {};
@@ -215,12 +227,41 @@ var Resource = class {
215
227
  [name]: value
216
228
  };
217
229
  }
230
+ setStack(stack) {
231
+ this.stack = stack;
232
+ return this;
233
+ }
234
+ ref() {
235
+ return this.getAtt("ref");
236
+ }
237
+ getAtt(attr) {
238
+ return new Lazy((stack) => {
239
+ if (!this.stack) {
240
+ throw new TypeError("Resource stack not defined before building template");
241
+ }
242
+ const value = attr === "ref" ? ref(this.logicalId) : getAtt(this.logicalId, attr);
243
+ if (stack === this.stack) {
244
+ return value;
245
+ }
246
+ const name = `${this.stack.name}-${this.logicalId}-${attr}`;
247
+ this.stack.export(name, value);
248
+ return this.stack.import(name);
249
+ });
250
+ }
218
251
  toJSON() {
219
252
  return {
220
253
  [this.logicalId]: {
221
254
  Type: this.type,
222
255
  DependsOn: [...this.deps].map((dep) => dep.logicalId),
223
- Properties: this.properties()
256
+ Properties: {
257
+ ...this.tags.size ? {
258
+ Tags: Array.from(this.tags.entries()).map(([key, value]) => ({
259
+ Key: key,
260
+ Value: value
261
+ }))
262
+ } : {},
263
+ ...this.properties()
264
+ }
224
265
  }
225
266
  };
226
267
  }
@@ -230,6 +271,11 @@ var Group = class {
230
271
  this.children = children;
231
272
  }
232
273
  };
274
+ var Lazy = class {
275
+ constructor(callback) {
276
+ this.callback = callback;
277
+ }
278
+ };
233
279
 
234
280
  // src/formation/resource/iam/inline-policy.ts
235
281
  var InlinePolicy = class {
@@ -335,6 +381,7 @@ var Function = class extends Resource {
335
381
  this.policy = policy;
336
382
  this.name = formatName(this.props.name || logicalId);
337
383
  this.environmentVariables = props.environment ? { ...props.environment } : {};
384
+ this.tag("name", this.name);
338
385
  }
339
386
  name;
340
387
  role;
@@ -348,11 +395,15 @@ var Function = class extends Resource {
348
395
  this.environmentVariables[name] = value;
349
396
  return this;
350
397
  }
398
+ setVpc(vpc) {
399
+ this.props.vpc = vpc;
400
+ return this;
401
+ }
351
402
  get id() {
352
- return ref(this.logicalId);
403
+ return this.ref();
353
404
  }
354
405
  get arn() {
355
- return getAtt(this.logicalId, "Arn");
406
+ return this.getAtt("Arn");
356
407
  }
357
408
  get permissions() {
358
409
  return {
@@ -360,7 +411,14 @@ var Function = class extends Resource {
360
411
  "lambda:InvokeFunction",
361
412
  "lambda:InvokeAsync"
362
413
  ],
363
- resources: [this.arn]
414
+ resources: [
415
+ formatArn({
416
+ service: "lambda",
417
+ resource: "function",
418
+ resourceName: this.name,
419
+ seperator: ":"
420
+ })
421
+ ]
364
422
  };
365
423
  }
366
424
  properties() {
@@ -376,6 +434,12 @@ var Function = class extends Resource {
376
434
  EphemeralStorage: {
377
435
  Size: this.props.ephemeralStorageSize?.toMegaBytes() ?? 512
378
436
  },
437
+ ...this.props.vpc ? {
438
+ VpcConfig: {
439
+ SecurityGroupIds: this.props.vpc.securityGroupIds,
440
+ SubnetIds: this.props.vpc.subnetIds
441
+ }
442
+ } : {},
379
443
  Environment: {
380
444
  Variables: this.environmentVariables
381
445
  }
@@ -400,6 +464,7 @@ var Stack = class {
400
464
  } else {
401
465
  this.add(...item.children);
402
466
  if (item instanceof Resource) {
467
+ item.setStack(this);
403
468
  this.resources.add(item);
404
469
  }
405
470
  }
@@ -443,8 +508,24 @@ var Stack = class {
443
508
  toJSON() {
444
509
  const resources = {};
445
510
  const outputs = {};
511
+ const walk = (object) => {
512
+ for (const [key, value] of Object.entries(object)) {
513
+ if (!object.hasOwnProperty(key)) {
514
+ continue;
515
+ }
516
+ if (value instanceof Lazy) {
517
+ object[key] = value.callback(this);
518
+ continue;
519
+ }
520
+ if (typeof value === "object" && value !== null) {
521
+ walk(value);
522
+ }
523
+ }
524
+ };
446
525
  for (const resource of this) {
447
- Object.assign(resources, resource.toJSON());
526
+ const json2 = resource.toJSON();
527
+ walk(json2);
528
+ Object.assign(resources, json2);
448
529
  }
449
530
  for (const [name, value] of this.exports.entries()) {
450
531
  Object.assign(outputs, {
@@ -512,54 +593,35 @@ var toStack = ({ config, app, stackConfig, bootstrap: bootstrap2, usEastBootstra
512
593
  }
513
594
  return {
514
595
  stack,
515
- depends: stackConfig.depends
596
+ bindings
597
+ // depends: stackConfig.depends,
516
598
  };
517
599
  };
518
600
 
519
601
  // src/util/deployment.ts
520
- var createDependencyTree = (stacks) => {
602
+ var createDeploymentLine = (stacks) => {
521
603
  const list3 = stacks.map(({ stack, config }) => ({
522
604
  stack,
523
605
  depends: config?.depends?.map((dep) => dep.name) || []
524
606
  }));
525
- const findChildren = (list4, parents) => {
526
- const children = [];
527
- const rests = [];
528
- for (const item of list4) {
529
- const isChild = item.depends.filter((dep) => !parents.includes(dep)).length === 0;
530
- if (isChild) {
531
- children.push(item);
532
- } else {
533
- rests.push(item);
607
+ const names = stacks.map(({ stack }) => stack.name);
608
+ const line = [];
609
+ const deps = [];
610
+ let limit = 100;
611
+ while (deps.length < list3.length) {
612
+ const local = [];
613
+ for (const { stack, depends } of list3) {
614
+ if (!deps.includes(stack.name) && depends.filter((dep) => !deps.includes(dep)).length === 0) {
615
+ local.push(stack);
534
616
  }
535
617
  }
536
- if (!rests.length) {
537
- return children.map(({ stack }) => ({
538
- stack,
539
- children: []
540
- }));
618
+ if (limit-- <= 0) {
619
+ const circularNames = names.filter((name) => deps.includes(name));
620
+ throw new Error(`Circular stack dependencies arn't allowed: ${circularNames}`);
541
621
  }
542
- return children.map(({ stack }) => {
543
- return {
544
- stack,
545
- children: findChildren(rests, [...parents, stack.name])
546
- };
547
- });
548
- };
549
- return findChildren(list3, []);
550
- };
551
- var createDeploymentLine = (stacks) => {
552
- const line = [];
553
- const walk = (stacks2, level) => {
554
- stacks2.forEach((node) => {
555
- if (!line[level]) {
556
- line[level] = [];
557
- }
558
- line[level].push(node.stack);
559
- walk(node.children, level + 1);
560
- });
561
- };
562
- walk(stacks, 0);
622
+ deps.push(...local.map((stack) => stack.name));
623
+ line.push(local);
624
+ }
563
625
  return line;
564
626
  };
565
627
 
@@ -959,8 +1021,12 @@ var RuntimeSchema = import_zod6.z.enum([
959
1021
  var FunctionSchema = import_zod6.z.union([
960
1022
  LocalFileSchema,
961
1023
  import_zod6.z.object({
962
- /** The file path ofthe function code. */
1024
+ /** The file path of the function code. */
963
1025
  file: LocalFileSchema,
1026
+ /** Put the function inside your global VPC.
1027
+ * @default false
1028
+ */
1029
+ vpc: import_zod6.z.boolean().optional(),
964
1030
  /** The amount of time that Lambda allows a function to run before stopping it.
965
1031
  * You can specify a size value from 1 second to 15 minutes.
966
1032
  * @default '10 seconds'
@@ -1010,6 +1076,10 @@ var FunctionSchema = import_zod6.z.union([
1010
1076
  var schema = import_zod6.z.object({
1011
1077
  defaults: import_zod6.z.object({
1012
1078
  function: import_zod6.z.object({
1079
+ /** Put the function inside your global VPC.
1080
+ * @default false
1081
+ */
1082
+ vpc: import_zod6.z.boolean().default(false),
1013
1083
  /** The amount of time that Lambda allows a function to run before stopping it.
1014
1084
  * You can specify a size value from 1 second to 15 minutes.
1015
1085
  * @default '10 seconds'
@@ -1101,12 +1171,36 @@ var toLambdaFunction = (ctx, id, fileOrProps) => {
1101
1171
  const lambda = new Function(id, {
1102
1172
  name: `${config.name}-${stack.name}-${id}`,
1103
1173
  code: Code.fromFile(id, props.file),
1104
- ...props
1174
+ ...props,
1175
+ vpc: void 0
1105
1176
  });
1106
1177
  lambda.addEnvironment("APP", config.name).addEnvironment("STAGE", config.stage).addEnvironment("STACK", stack.name);
1178
+ if (props.vpc) {
1179
+ lambda.setVpc({
1180
+ securityGroupIds: [
1181
+ ctx.bootstrap.import(`vpc-security-group-id`)
1182
+ ],
1183
+ subnetIds: [
1184
+ ctx.bootstrap.import(`public-subnet-1`),
1185
+ ctx.bootstrap.import(`public-subnet-2`)
1186
+ ]
1187
+ }).addPermissions({
1188
+ actions: [
1189
+ "ec2:CreateNetworkInterface",
1190
+ "ec2:DescribeNetworkInterfaces",
1191
+ "ec2:DeleteNetworkInterface",
1192
+ "ec2:AssignPrivateIpAddresses",
1193
+ "ec2:UnassignPrivateIpAddresses"
1194
+ ],
1195
+ resources: ["*"]
1196
+ });
1197
+ }
1107
1198
  if (props.runtime.startsWith("nodejs")) {
1108
1199
  lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1");
1109
1200
  }
1201
+ ctx.bind((other) => {
1202
+ other.addPermissions(lambda.permissions);
1203
+ });
1110
1204
  return lambda;
1111
1205
  };
1112
1206
 
@@ -1230,6 +1324,7 @@ var Queue = class extends Resource {
1230
1324
  super("AWS::SQS::Queue", logicalId);
1231
1325
  this.props = props;
1232
1326
  this.name = formatName(this.props.name || logicalId);
1327
+ this.tag("name", this.name);
1233
1328
  }
1234
1329
  name;
1235
1330
  setDeadLetter(arn) {
@@ -1250,7 +1345,13 @@ var Queue = class extends Resource {
1250
1345
  "sqs:GetQueueUrl",
1251
1346
  "sqs:GetQueueAttributes"
1252
1347
  ],
1253
- resources: [this.arn]
1348
+ resources: [
1349
+ formatArn({
1350
+ service: "sqs",
1351
+ resource: "queue",
1352
+ resourceName: this.name
1353
+ })
1354
+ ]
1254
1355
  };
1255
1356
  }
1256
1357
  properties() {
@@ -1477,6 +1578,7 @@ var Table = class extends Resource {
1477
1578
  this.props = props;
1478
1579
  this.name = formatName(this.props.name || logicalId);
1479
1580
  this.indexes = { ...this.props.indexes || {} };
1581
+ this.tag("name", this.name);
1480
1582
  }
1481
1583
  name;
1482
1584
  indexes;
@@ -1509,7 +1611,13 @@ var Table = class extends Resource {
1509
1611
  "dynamodb:Query",
1510
1612
  "dynamodb:Scan"
1511
1613
  ],
1512
- resources: [this.arn]
1614
+ resources: [
1615
+ formatArn({
1616
+ service: "dynamodb",
1617
+ resource: "table",
1618
+ resourceName: this.name
1619
+ })
1620
+ ]
1513
1621
  };
1514
1622
  }
1515
1623
  attributeDefinitions() {
@@ -1743,6 +1851,7 @@ var Bucket = class extends Resource {
1743
1851
  super("AWS::S3::Bucket", logicalId);
1744
1852
  this.props = props;
1745
1853
  this.name = formatName(this.props.name || logicalId);
1854
+ this.tag("name", this.name);
1746
1855
  }
1747
1856
  name;
1748
1857
  get arn() {
@@ -1759,7 +1868,13 @@ var Bucket = class extends Resource {
1759
1868
  "s3:GetQueueUrl",
1760
1869
  "s3:GetQueueAttributes"
1761
1870
  ],
1762
- resources: [this.arn]
1871
+ resources: [
1872
+ formatArn({
1873
+ service: "s3",
1874
+ resource: "bucket",
1875
+ resourceName: this.name
1876
+ })
1877
+ ]
1763
1878
  };
1764
1879
  }
1765
1880
  properties() {
@@ -1812,6 +1927,7 @@ var Topic = class extends Resource {
1812
1927
  super("AWS::SNS::Topic", logicalId);
1813
1928
  this.props = props;
1814
1929
  this.name = formatName(this.props.name || logicalId);
1930
+ this.tag("name", this.name);
1815
1931
  }
1816
1932
  name;
1817
1933
  get arn() {
@@ -1820,7 +1936,13 @@ var Topic = class extends Resource {
1820
1936
  get permissions() {
1821
1937
  return {
1822
1938
  actions: ["sns:Publish"],
1823
- resources: [this.arn]
1939
+ resources: [
1940
+ formatArn({
1941
+ service: "sns",
1942
+ resource: "topic",
1943
+ resourceName: this.name
1944
+ })
1945
+ ]
1824
1946
  };
1825
1947
  }
1826
1948
  properties() {
@@ -2053,6 +2175,7 @@ var GraphQLApi = class extends Resource {
2053
2175
  super("AWS::AppSync::GraphQLApi", logicalId);
2054
2176
  this.props = props;
2055
2177
  this.name = formatName(this.props.name || logicalId);
2178
+ this.tag("name", this.name);
2056
2179
  }
2057
2180
  name;
2058
2181
  lambdaAuthProviders = [];
@@ -2817,6 +2940,12 @@ var Vpc = class extends Resource {
2817
2940
  get id() {
2818
2941
  return ref(this.logicalId);
2819
2942
  }
2943
+ get defaultNetworkAcl() {
2944
+ return getAtt(this.logicalId, "DefaultNetworkAcl");
2945
+ }
2946
+ get defaultSecurityGroup() {
2947
+ return getAtt(this.logicalId, "DefaultSecurityGroup");
2948
+ }
2820
2949
  properties() {
2821
2950
  return {
2822
2951
  CidrBlock: this.props.cidrBlock.ip
@@ -2828,6 +2957,7 @@ var RouteTable = class extends Resource {
2828
2957
  super("AWS::EC2::RouteTable", logicalId);
2829
2958
  this.props = props;
2830
2959
  this.name = formatName(props.name || logicalId);
2960
+ this.tag("name", this.name);
2831
2961
  }
2832
2962
  name;
2833
2963
  get id() {
@@ -2835,11 +2965,7 @@ var RouteTable = class extends Resource {
2835
2965
  }
2836
2966
  properties() {
2837
2967
  return {
2838
- VpcId: this.props.vpcId,
2839
- Tags: [{
2840
- Key: "name",
2841
- Value: this.name
2842
- }]
2968
+ VpcId: this.props.vpcId
2843
2969
  };
2844
2970
  }
2845
2971
  };
@@ -2992,6 +3118,7 @@ var vpcPlugin = definePlugin({
2992
3118
  routeTableId: publicRouteTable.id,
2993
3119
  destination: Peer.anyIpv4()
2994
3120
  }).dependsOn(gateway, publicRouteTable);
3121
+ bootstrap2.export("vpc-security-group-id", vpc.defaultSecurityGroup);
2995
3122
  bootstrap2.export(`vpc-id`, vpc.id);
2996
3123
  bootstrap2.add(
2997
3124
  vpc,
@@ -3034,7 +3161,9 @@ var SecurityGroup = class extends Resource {
3034
3161
  constructor(logicalId, props) {
3035
3162
  super("AWS::EC2::SecurityGroup", logicalId);
3036
3163
  this.props = props;
3164
+ this.name = formatName(props.name ?? logicalId);
3037
3165
  }
3166
+ name;
3038
3167
  ingress = [];
3039
3168
  egress = [];
3040
3169
  get id() {
@@ -3059,7 +3188,7 @@ var SecurityGroup = class extends Resource {
3059
3188
  properties() {
3060
3189
  return {
3061
3190
  VpcId: this.props.vpcId,
3062
- GroupName: this.logicalId,
3191
+ GroupName: this.name,
3063
3192
  GroupDescription: this.props.description,
3064
3193
  SecurityGroupIngress: this.ingress.map((rule) => ({
3065
3194
  Description: rule.description || "",
@@ -3470,6 +3599,7 @@ var Collection = class extends Resource {
3470
3599
  super("AWS::OpenSearchServerless::Collection", logicalId);
3471
3600
  this.props = props;
3472
3601
  this.name = this.props.name || logicalId;
3602
+ this.tag("name", this.name);
3473
3603
  }
3474
3604
  name;
3475
3605
  get id() {
@@ -3481,6 +3611,18 @@ var Collection = class extends Resource {
3481
3611
  get endpoint() {
3482
3612
  return getAtt(this.logicalId, "CollectionEndpoint");
3483
3613
  }
3614
+ get permissions() {
3615
+ return {
3616
+ actions: ["aoss:APIAccessAll"],
3617
+ resources: [
3618
+ formatArn({
3619
+ service: "aoss",
3620
+ resource: "collection",
3621
+ resourceName: this.name
3622
+ })
3623
+ ]
3624
+ };
3625
+ }
3484
3626
  properties() {
3485
3627
  return {
3486
3628
  Name: this.name,
@@ -3505,10 +3647,155 @@ var searchPlugin = definePlugin({
3505
3647
  type: "search"
3506
3648
  });
3507
3649
  bind((lambda) => {
3508
- lambda.addPermissions({
3509
- actions: ["aoss:APIAccessAll"],
3510
- resources: [collection.arn]
3511
- });
3650
+ lambda.addPermissions(collection.permissions);
3651
+ });
3652
+ }
3653
+ }
3654
+ });
3655
+
3656
+ // src/plugins/cache.ts
3657
+ var import_zod19 = require("zod");
3658
+
3659
+ // src/formation/resource/memorydb/cluster.ts
3660
+ var Cluster = class extends Resource {
3661
+ constructor(logicalId, props) {
3662
+ super("AWS::MemoryDB::Cluster", logicalId);
3663
+ this.props = props;
3664
+ this.name = formatName(this.props.name || logicalId);
3665
+ this.tag("name", this.name);
3666
+ }
3667
+ name;
3668
+ get status() {
3669
+ return this.getAtt("Status");
3670
+ }
3671
+ get arn() {
3672
+ return this.getAtt("ARN");
3673
+ }
3674
+ get address() {
3675
+ return this.getAtt("ClusterEndpoint.Address");
3676
+ }
3677
+ get port() {
3678
+ return this.getAtt("ClusterEndpoint.Port");
3679
+ }
3680
+ properties() {
3681
+ return {
3682
+ ClusterName: this.name,
3683
+ ClusterEndpoint: {
3684
+ Port: this.props.port
3685
+ },
3686
+ Port: this.props.port,
3687
+ ...this.attr("Description", this.props.description),
3688
+ ACLName: this.props.aclName,
3689
+ EngineVersion: this.props.engine ?? "7.0",
3690
+ ...this.attr("SubnetGroupName", this.props.subnetGroupName),
3691
+ ...this.attr("SecurityGroupIds", this.props.securityGroupIds),
3692
+ NodeType: "db." + this.props.type,
3693
+ NumReplicasPerShard: this.props.replicasPerShard ?? 1,
3694
+ NumShards: this.props.shards ?? 1,
3695
+ TLSEnabled: this.props.tls ?? false,
3696
+ DataTiering: this.props.dataTiering ? "true" : "false",
3697
+ AutoMinorVersionUpgrade: this.props.autoMinorVersionUpgrade ?? true,
3698
+ MaintenanceWindow: this.props.maintenanceWindow ?? "Sat:02:00-Sat:05:00"
3699
+ };
3700
+ }
3701
+ };
3702
+
3703
+ // src/formation/resource/memorydb/subnet-group.ts
3704
+ var SubnetGroup = class extends Resource {
3705
+ constructor(logicalId, props) {
3706
+ super("AWS::MemoryDB::SubnetGroup", logicalId);
3707
+ this.props = props;
3708
+ this.name = formatName(this.props.name || logicalId);
3709
+ }
3710
+ name;
3711
+ get arn() {
3712
+ return getAtt(this.logicalId, "Arn");
3713
+ }
3714
+ properties() {
3715
+ return {
3716
+ SubnetGroupName: this.name,
3717
+ SubnetIds: this.props.subnetIds,
3718
+ ...this.attr("Description", this.props.description)
3719
+ };
3720
+ }
3721
+ };
3722
+
3723
+ // src/plugins/cache.ts
3724
+ var TypeSchema = import_zod19.z.enum([
3725
+ "t4g.small",
3726
+ "t4g.medium",
3727
+ "r6g.large",
3728
+ "r6g.xlarge",
3729
+ "r6g.2xlarge",
3730
+ "r6g.4xlarge",
3731
+ "r6g.8xlarge",
3732
+ "r6g.12xlarge",
3733
+ "r6g.16xlarge",
3734
+ "r6gd.xlarge",
3735
+ "r6gd.2xlarge",
3736
+ "r6gd.4xlarge",
3737
+ "r6gd.8xlarge"
3738
+ ]);
3739
+ var PortSchema = import_zod19.z.number().int().min(1).max(5e4);
3740
+ var ShardsSchema = import_zod19.z.number().int().min(0).max(100);
3741
+ var ReplicasPerShardSchema = import_zod19.z.number().int().min(0).max(5);
3742
+ var EngineSchema = import_zod19.z.enum(["7.0", "6.2"]);
3743
+ var cachePlugin = definePlugin({
3744
+ name: "cache",
3745
+ schema: import_zod19.z.object({
3746
+ stacks: import_zod19.z.object({
3747
+ /** Define the caches in your stack.
3748
+ * For access to the cache put your functions inside the global VPC.
3749
+ * @example
3750
+ * {
3751
+ * caches: {
3752
+ * CACHE_NAME: {
3753
+ * type: 't4g.small'
3754
+ * }
3755
+ * }
3756
+ * }
3757
+ */
3758
+ caches: import_zod19.z.record(
3759
+ ResourceIdSchema,
3760
+ import_zod19.z.object({
3761
+ type: TypeSchema.default("t4g.small"),
3762
+ port: PortSchema.default(6379),
3763
+ shards: ShardsSchema.default(1),
3764
+ replicasPerShard: ReplicasPerShardSchema.default(1),
3765
+ engine: EngineSchema.default("7.0"),
3766
+ dataTiering: import_zod19.z.boolean().default(false)
3767
+ })
3768
+ ).optional()
3769
+ }).array()
3770
+ }),
3771
+ onStack({ config, stack, stackConfig, bootstrap: bootstrap2, bind }) {
3772
+ for (const [id, props] of Object.entries(stackConfig.caches || {})) {
3773
+ const name = `${config.name}-${stack.name}-${id}`;
3774
+ const subnetGroup = new SubnetGroup(id, {
3775
+ name,
3776
+ subnetIds: [
3777
+ bootstrap2.import(`private-subnet-1`),
3778
+ bootstrap2.import(`private-subnet-2`)
3779
+ ]
3780
+ });
3781
+ const securityGroup = new SecurityGroup(id, {
3782
+ name,
3783
+ vpcId: bootstrap2.import(`vpc-id`),
3784
+ description: name
3785
+ });
3786
+ const port = Port.tcp(props.port);
3787
+ securityGroup.addIngressRule(Peer.anyIpv4(), port);
3788
+ securityGroup.addIngressRule(Peer.anyIpv6(), port);
3789
+ const cluster = new Cluster(id, {
3790
+ name,
3791
+ aclName: "open-access",
3792
+ securityGroupIds: [securityGroup.id],
3793
+ subnetGroupName: subnetGroup.name,
3794
+ ...props
3795
+ }).dependsOn(subnetGroup, securityGroup);
3796
+ stack.add(subnetGroup, securityGroup, cluster);
3797
+ bind((lambda) => {
3798
+ lambda.addEnvironment(`CACHE_${stack.name}_${id}_HOST`, cluster.address).addEnvironment(`CACHE_${stack.name}_${id}_PORT`, props.port.toString());
3512
3799
  });
3513
3800
  }
3514
3801
  }
@@ -3519,6 +3806,7 @@ var defaultPlugins = [
3519
3806
  extendPlugin,
3520
3807
  vpcPlugin,
3521
3808
  functionPlugin,
3809
+ cachePlugin,
3522
3810
  cronPlugin,
3523
3811
  queuePlugin,
3524
3812
  tablePlugin,
@@ -3700,7 +3988,7 @@ var toApp = async (config, filters) => {
3700
3988
  config.stacks.filter((stack) => filters.includes(stack.name))
3701
3989
  );
3702
3990
  for (const stackConfig of filterdStacks) {
3703
- const { stack } = toStack({
3991
+ const { stack, bindings: bindings2 } = toStack({
3704
3992
  config,
3705
3993
  stackConfig,
3706
3994
  bootstrap: bootstrap2,
@@ -3709,7 +3997,7 @@ var toApp = async (config, filters) => {
3709
3997
  app
3710
3998
  });
3711
3999
  app.add(stack);
3712
- stacks.push({ stack, config: stackConfig });
4000
+ stacks.push({ stack, config: stackConfig, bindings: bindings2 });
3713
4001
  }
3714
4002
  for (const plugin of plugins) {
3715
4003
  for (const stack of app.stacks) {
@@ -3731,23 +4019,31 @@ var toApp = async (config, filters) => {
3731
4019
  bind2(fn);
3732
4020
  }
3733
4021
  }
3734
- let dependencyTree = createDependencyTree(stacks);
4022
+ for (const entry of stacks) {
4023
+ for (const dep of entry.config.depends || []) {
4024
+ const depStack = stacks.find((entry2) => entry2.config.name === dep.name);
4025
+ if (!depStack) {
4026
+ throw new Error(`Stack dependency not found: ${dep.name}`);
4027
+ }
4028
+ const functions2 = entry.stack.find(Function);
4029
+ for (const bind2 of depStack.bindings) {
4030
+ for (const fn of functions2) {
4031
+ bind2(fn);
4032
+ }
4033
+ }
4034
+ }
4035
+ }
4036
+ const deploymentLine = createDeploymentLine(stacks);
3735
4037
  if (bootstrap2.size > 0) {
3736
- dependencyTree = [{
3737
- stack: bootstrap2,
3738
- children: dependencyTree
3739
- }];
4038
+ deploymentLine.unshift([bootstrap2]);
3740
4039
  }
3741
4040
  if (usEastBootstrap.size > 0) {
3742
- dependencyTree = [{
3743
- stack: usEastBootstrap,
3744
- children: dependencyTree
3745
- }];
4041
+ deploymentLine.unshift([usEastBootstrap]);
3746
4042
  }
3747
4043
  return {
3748
4044
  app,
3749
4045
  plugins,
3750
- dependencyTree
4046
+ deploymentLine
3751
4047
  };
3752
4048
  };
3753
4049
 
@@ -3771,17 +4067,17 @@ var getCredentials = (profile) => {
3771
4067
  };
3772
4068
 
3773
4069
  // src/schema/app.ts
3774
- var import_zod22 = require("zod");
4070
+ var import_zod23 = require("zod");
3775
4071
 
3776
4072
  // src/schema/stack.ts
3777
- var import_zod19 = require("zod");
3778
- var StackSchema = import_zod19.z.object({
4073
+ var import_zod20 = require("zod");
4074
+ var StackSchema = import_zod20.z.object({
3779
4075
  name: ResourceIdSchema,
3780
- depends: import_zod19.z.array(import_zod19.z.lazy(() => StackSchema)).optional()
4076
+ depends: import_zod20.z.array(import_zod20.z.lazy(() => StackSchema)).optional()
3781
4077
  });
3782
4078
 
3783
4079
  // src/schema/region.ts
3784
- var import_zod20 = require("zod");
4080
+ var import_zod21 = require("zod");
3785
4081
  var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
3786
4082
  var AF = ["af-south-1"];
3787
4083
  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"];
@@ -3798,45 +4094,47 @@ var regions = [
3798
4094
  ...ME,
3799
4095
  ...SA
3800
4096
  ];
3801
- var RegionSchema = import_zod20.z.enum(regions);
4097
+ var RegionSchema = import_zod21.z.enum(regions);
3802
4098
 
3803
4099
  // src/schema/plugin.ts
3804
- var import_zod21 = require("zod");
3805
- var PluginSchema = import_zod21.z.object({
3806
- name: import_zod21.z.string(),
3807
- schema: import_zod21.z.custom().optional(),
4100
+ var import_zod22 = require("zod");
4101
+ var PluginSchema = import_zod22.z.object({
4102
+ name: import_zod22.z.string(),
4103
+ schema: import_zod22.z.custom().optional(),
3808
4104
  // depends: z.array(z.lazy(() => PluginSchema)).optional(),
3809
- onApp: import_zod21.z.function().returns(import_zod21.z.void()).optional(),
3810
- onStack: import_zod21.z.function().returns(import_zod21.z.any()).optional(),
3811
- onResource: import_zod21.z.function().returns(import_zod21.z.any()).optional()
4105
+ onApp: import_zod22.z.function().returns(import_zod22.z.void()).optional(),
4106
+ onStack: import_zod22.z.function().returns(import_zod22.z.any()).optional(),
4107
+ onResource: import_zod22.z.function().returns(import_zod22.z.any()).optional()
3812
4108
  // bind: z.function().optional(),
3813
4109
  });
3814
4110
 
3815
4111
  // src/schema/app.ts
3816
- var AppSchema = import_zod22.z.object({
4112
+ var AppSchema = import_zod23.z.object({
3817
4113
  /** App name */
3818
4114
  name: ResourceIdSchema,
3819
4115
  /** The AWS region to deploy to. */
3820
4116
  region: RegionSchema,
3821
4117
  /** The AWS profile to deploy to. */
3822
- profile: import_zod22.z.string(),
4118
+ profile: import_zod23.z.string(),
3823
4119
  /** The deployment stage.
3824
4120
  * @default 'prod'
3825
4121
  */
3826
- stage: import_zod22.z.string().regex(/[a-z]+/).default("prod"),
4122
+ stage: import_zod23.z.string().regex(/[a-z]+/).default("prod"),
3827
4123
  /** Default properties. */
3828
- defaults: import_zod22.z.object({}).default({}),
4124
+ defaults: import_zod23.z.object({}).default({}),
3829
4125
  /** The application stacks. */
3830
- stacks: import_zod22.z.array(StackSchema).min(1).refine((stacks) => {
4126
+ stacks: import_zod23.z.array(StackSchema).min(1).refine((stacks) => {
3831
4127
  const unique = new Set(stacks.map((stack) => stack.name));
3832
4128
  return unique.size === stacks.length;
3833
4129
  }, "Must be an array of unique stacks"),
3834
4130
  /** Custom plugins. */
3835
- plugins: import_zod22.z.array(PluginSchema).optional()
4131
+ plugins: import_zod23.z.array(PluginSchema).optional()
3836
4132
  });
3837
4133
 
3838
4134
  // src/util/import.ts
3839
- var import_core = require("@swc/core");
4135
+ var import_rollup3 = require("rollup");
4136
+ var import_rollup_plugin_swc32 = require("rollup-plugin-swc3");
4137
+ var import_rollup_plugin_replace = __toESM(require("rollup-plugin-replace"), 1);
3840
4138
  var import_path2 = require("path");
3841
4139
  var import_promises5 = require("fs/promises");
3842
4140
 
@@ -3885,54 +4183,25 @@ var fileExist = async (file) => {
3885
4183
  };
3886
4184
 
3887
4185
  // src/util/import.ts
3888
- var resolveFileNameExtension = async (path) => {
3889
- const options = [
3890
- "",
3891
- ".ts",
3892
- ".js",
3893
- "/index.ts",
3894
- "/index.js"
3895
- ];
3896
- for (const option of options) {
3897
- const file = path.replace(/\.js$/, "") + option;
3898
- let stat;
3899
- try {
3900
- stat = await (0, import_promises5.lstat)(file);
3901
- } catch (error) {
3902
- continue;
3903
- }
3904
- if (stat.isFile()) {
3905
- return file;
3906
- }
3907
- }
3908
- throw new Error(`Failed to load file: ${path}`);
3909
- };
3910
- var resolveDir = (path) => {
3911
- return (0, import_path2.dirname)(path).replace(directories.root + "/", "");
3912
- };
3913
4186
  var importFile = async (path) => {
3914
- const load = async (file) => {
3915
- debug("Load file:", style.info(file));
3916
- let { code: code2 } = await (0, import_core.transformFile)(file, {
3917
- isModule: true
3918
- });
3919
- const path2 = (0, import_path2.dirname)(file);
3920
- const dir = resolveDir(file);
3921
- code2 = code2.replaceAll("__dirname", `"${dir}"`);
3922
- const matches = code2.match(/(import|export)\s*{\s*[a-z0-9\_\,\s\*]+\s*}\s*from\s*('|")(\.\.?[\/a-z0-9\_\-\.]+)('|");?/ig);
3923
- if (!matches)
3924
- return code2;
3925
- await Promise.all(matches?.map(async (match) => {
3926
- const parts = /('|")(\.\.?[\/a-z0-9\_\-\.]+)('|")/ig.exec(match);
3927
- const from = parts[2];
3928
- const file2 = await resolveFileNameExtension((0, import_path2.join)(path2, from));
3929
- const result = await load(file2);
3930
- code2 = code2.replace(match, result);
3931
- }));
3932
- return code2;
3933
- };
3934
- const code = await load(path);
4187
+ const bundle = await (0, import_rollup3.rollup)({
4188
+ input: path,
4189
+ plugins: [
4190
+ (0, import_rollup_plugin_replace.default)({
4191
+ __dirname: (id) => `'${(0, import_path2.dirname)(id)}'`
4192
+ }),
4193
+ (0, import_rollup_plugin_swc32.swc)({
4194
+ minify: false
4195
+ })
4196
+ ]
4197
+ });
3935
4198
  const outputFile = (0, import_path2.join)(directories.cache, "config.js");
4199
+ const result = await bundle.generate({
4200
+ format: "esm",
4201
+ exports: "default"
4202
+ });
4203
+ const output = result.output[0];
4204
+ const code = output.code;
3936
4205
  await (0, import_promises5.mkdir)(directories.cache, { recursive: true });
3937
4206
  await (0, import_promises5.writeFile)(outputFile, code);
3938
4207
  debug("Save config file:", style.info(outputFile));
@@ -4234,7 +4503,7 @@ var Renderer = class {
4234
4503
  flushing = false;
4235
4504
  screen = [];
4236
4505
  width() {
4237
- return this.output.columns;
4506
+ return this.output.columns - 1;
4238
4507
  }
4239
4508
  height() {
4240
4509
  return this.output.rows;
@@ -4927,41 +5196,74 @@ var bootstrap = (program2) => {
4927
5196
  });
4928
5197
  };
4929
5198
 
4930
- // src/cli/ui/complex/stack-tree.ts
4931
- var stackTree = (nodes, statuses) => {
5199
+ // src/cli/ui/complex/deployer.ts
5200
+ var stacksDeployer = (deploymentLine) => {
5201
+ const stackNames = deploymentLine.map((line) => line.map((stack) => stack.name)).flat();
5202
+ const stackNameSize = Math.max(...stackNames.map((name) => name.length));
4932
5203
  return (term) => {
4933
- const render = (nodes2, deep = 0, parents = []) => {
4934
- const size = nodes2.length - 1;
4935
- nodes2.forEach((node, i) => {
4936
- const name = node.stack.name;
4937
- const status2 = statuses[name];
4938
- const first = i === 0 && deep === 0;
4939
- const last = i === size;
4940
- const more = i < size;
4941
- const line = flexLine(term, [
4942
- ...parents.map((parent) => {
4943
- return style.label(
4944
- parent ? "\u2502".padEnd(3) : " ".repeat(3)
4945
- );
4946
- }),
4947
- style.label(
4948
- first && size === 0 ? " " : first ? "\u250C\u2500" : last ? "\u2514\u2500" : "\u251C\u2500"
4949
- ),
5204
+ const ui = {};
5205
+ term.out.gap();
5206
+ for (const i in deploymentLine) {
5207
+ const line = flexLine(
5208
+ term,
5209
+ [" "],
5210
+ [
4950
5211
  " ",
4951
- style.info(name),
4952
- " "
4953
- ], [
5212
+ style.placeholder(Number(i) + 1),
5213
+ style.placeholder(" \u2500\u2500")
5214
+ ]
5215
+ );
5216
+ term.out.write(line);
5217
+ term.out.write(br());
5218
+ for (const stack of deploymentLine[i]) {
5219
+ const icon = new Signal(" ");
5220
+ const name = new Signal(style.label.dim(stack.name));
5221
+ const status2 = new Signal(style.info.dim("waiting"));
5222
+ let stopSpinner;
5223
+ term.out.write([
5224
+ icon,
5225
+ " ",
5226
+ name,
5227
+ " ".repeat(stackNameSize - stack.name.length),
5228
+ " ",
5229
+ style.placeholder(symbol.pointerSmall),
4954
5230
  " ",
4955
5231
  status2,
4956
5232
  br()
4957
5233
  ]);
4958
- term.out.write(line);
4959
- render(node.children, deep + 1, [...parents, more]);
4960
- });
4961
- };
4962
- term.out.gap();
4963
- render(nodes);
5234
+ ui[stack.name] = {
5235
+ start: (value) => {
5236
+ const [spinner, stop] = createSpinner();
5237
+ name.set(style.label(stack.name));
5238
+ icon.set(spinner);
5239
+ status2.set(style.warning(value));
5240
+ stopSpinner = stop;
5241
+ },
5242
+ done(value) {
5243
+ stopSpinner();
5244
+ icon.set(style.success(symbol.success));
5245
+ status2.set(style.success(value));
5246
+ },
5247
+ fail(value) {
5248
+ stopSpinner();
5249
+ icon.set(style.error(symbol.error));
5250
+ status2.set(style.error(value));
5251
+ },
5252
+ warn(value) {
5253
+ stopSpinner();
5254
+ icon.set(style.warning(symbol.warning));
5255
+ status2.set(style.warning(value));
5256
+ }
5257
+ };
5258
+ }
5259
+ }
5260
+ term.out.write(flexLine(term, [" "], [
5261
+ " ",
5262
+ style.warning("\u26A1\uFE0F"),
5263
+ style.placeholder("\u2500\u2500")
5264
+ ]));
4964
5265
  term.out.gap();
5266
+ return ui;
4965
5267
  };
4966
5268
  };
4967
5269
 
@@ -4969,31 +5271,27 @@ var stackTree = (nodes, statuses) => {
4969
5271
  var status = (program2) => {
4970
5272
  program2.command("status").argument("[stacks...]", "Optionally filter stacks to lookup status").description("View the application status").action(async (filters) => {
4971
5273
  await layout(async (config, write) => {
4972
- const { app, dependencyTree } = await toApp(config, filters);
5274
+ const { app, deploymentLine } = await toApp(config, filters);
4973
5275
  await cleanUp();
4974
5276
  await write(assetBuilder(app));
4975
5277
  await write(templateBuilder(app));
4976
5278
  const doneLoading = write(loadingDialog("Loading stack information..."));
4977
5279
  const client = new StackClient(app, config.account, config.region, config.credentials);
4978
5280
  const statuses = [];
4979
- const stackStatuses = {};
4980
- for (const stack of app) {
4981
- stackStatuses[stack.name] = new Signal(style.info("Loading..."));
4982
- }
4983
- write(stackTree(dependencyTree, stackStatuses));
5281
+ const ui = write(stacksDeployer(deploymentLine));
4984
5282
  debug("Load metadata for all deployed stacks on AWS");
4985
5283
  await Promise.all(app.stacks.map(async (stack, i) => {
5284
+ const item = ui[stack.name];
5285
+ item.start("loading");
4986
5286
  const info = await client.get(stack.name, stack.region);
4987
- const signal = stackStatuses[stack.name];
4988
- await new Promise((resolve) => setTimeout(resolve, i * 1e3));
4989
5287
  if (!info) {
4990
- signal.set(style.error("non-existent"));
5288
+ item.fail("NON EXISTENT");
4991
5289
  statuses.push("non-existent");
4992
5290
  } else if (info.template !== stack.toString()) {
4993
- signal.set(style.warning("out-of-date"));
5291
+ item.warn("OUT OF DATE");
4994
5292
  statuses.push("out-of-date");
4995
5293
  } else {
4996
- signal.set(style.success("up-to-date"));
5294
+ item.done("UP TO DATE");
4997
5295
  statuses.push("up-to-date");
4998
5296
  }
4999
5297
  }));
@@ -5077,7 +5375,7 @@ var deploy = (program2) => {
5077
5375
  program2.command("deploy").argument("[stacks...]", "Optionally filter stacks to deploy").description("Deploy your app to AWS").action(async (filters) => {
5078
5376
  await layout(async (config, write) => {
5079
5377
  await write(bootstrapDeployer(config));
5080
- const { app, dependencyTree } = await toApp(config, filters);
5378
+ const { app, deploymentLine } = await toApp(config, filters);
5081
5379
  const stackNames = app.stacks.map((stack) => stack.name);
5082
5380
  const formattedFilter = stackNames.map((i) => style.info(i)).join(style.placeholder(", "));
5083
5381
  debug("Stacks to deploy", formattedFilter);
@@ -5091,26 +5389,21 @@ var deploy = (program2) => {
5091
5389
  await write(assetBuilder(app));
5092
5390
  await write(assetPublisher(config, app));
5093
5391
  await write(templateBuilder(app));
5094
- const statuses = {};
5095
- for (const stack of app) {
5096
- statuses[stack.name] = new Signal(style.info("waiting"));
5097
- }
5098
5392
  const doneDeploying = write(loadingDialog("Deploying stacks to AWS..."));
5099
- write(stackTree(dependencyTree, statuses));
5100
5393
  const client = new StackClient(app, config.account, config.region, config.credentials);
5101
- const deploymentLine = createDeploymentLine(dependencyTree);
5102
- for (const stacks of deploymentLine) {
5103
- const results = await Promise.allSettled(stacks.map(async (stack) => {
5104
- const signal = statuses[stack.name];
5105
- signal.set(style.warning("deploying"));
5394
+ const ui = write(stacksDeployer(deploymentLine));
5395
+ for (const line of deploymentLine) {
5396
+ const results = await Promise.allSettled(line.map(async (stack) => {
5397
+ const item = ui[stack.name];
5398
+ item.start("deploying");
5106
5399
  try {
5107
5400
  await client.deploy(stack);
5108
5401
  } catch (error) {
5109
5402
  debugError(error);
5110
- signal.set(style.error("failed"));
5403
+ item.fail("failed");
5111
5404
  throw error;
5112
5405
  }
5113
- signal.set(style.success("deployed"));
5406
+ item.done("deployed");
5114
5407
  }));
5115
5408
  for (const result of results) {
5116
5409
  if (result.status === "rejected") {