@awsless/awsless 0.0.19 → 0.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -171,6 +171,12 @@ var formatLogicalId = (id) => {
171
171
  var formatName = (name) => {
172
172
  return paramCase2(name);
173
173
  };
174
+ var formatArn = (props) => {
175
+ return sub("arn:${AWS::Partition}:${service}:${AWS::Region}:${AWS::AccountId}:${resource}${seperator}${resourceName}", {
176
+ seperator: "/",
177
+ ...props
178
+ });
179
+ };
174
180
 
175
181
  // src/formation/resource.ts
176
182
  var Resource = class {
@@ -180,13 +186,19 @@ var Resource = class {
180
186
  this.logicalId = formatLogicalId(`${logicalId}-${type.replace(/^AWS::/, "")}`);
181
187
  }
182
188
  logicalId;
189
+ tags = /* @__PURE__ */ new Map();
183
190
  deps = /* @__PURE__ */ new Set();
191
+ stack;
184
192
  dependsOn(...dependencies) {
185
193
  for (const dependency of dependencies) {
186
194
  this.deps.add(dependency);
187
195
  }
188
196
  return this;
189
197
  }
198
+ tag(key, value) {
199
+ this.tags.set(key, value);
200
+ return this;
201
+ }
190
202
  attr(name, value) {
191
203
  if (typeof value === "undefined") {
192
204
  return {};
@@ -195,12 +207,41 @@ var Resource = class {
195
207
  [name]: value
196
208
  };
197
209
  }
210
+ setStack(stack) {
211
+ this.stack = stack;
212
+ return this;
213
+ }
214
+ ref() {
215
+ return this.getAtt("ref");
216
+ }
217
+ getAtt(attr) {
218
+ return new Lazy((stack) => {
219
+ if (!this.stack) {
220
+ throw new TypeError("Resource stack not defined before building template");
221
+ }
222
+ const value = attr === "ref" ? ref(this.logicalId) : getAtt(this.logicalId, attr);
223
+ if (stack === this.stack) {
224
+ return value;
225
+ }
226
+ const name = `${this.stack.name}-${this.logicalId}-${attr}`;
227
+ this.stack.export(name, value);
228
+ return this.stack.import(name);
229
+ });
230
+ }
198
231
  toJSON() {
199
232
  return {
200
233
  [this.logicalId]: {
201
234
  Type: this.type,
202
235
  DependsOn: [...this.deps].map((dep) => dep.logicalId),
203
- Properties: this.properties()
236
+ Properties: {
237
+ ...this.tags.size ? {
238
+ Tags: Array.from(this.tags.entries()).map(([key, value]) => ({
239
+ Key: key,
240
+ Value: value
241
+ }))
242
+ } : {},
243
+ ...this.properties()
244
+ }
204
245
  }
205
246
  };
206
247
  }
@@ -210,6 +251,11 @@ var Group = class {
210
251
  this.children = children;
211
252
  }
212
253
  };
254
+ var Lazy = class {
255
+ constructor(callback) {
256
+ this.callback = callback;
257
+ }
258
+ };
213
259
 
214
260
  // src/formation/resource/iam/inline-policy.ts
215
261
  var InlinePolicy = class {
@@ -315,6 +361,7 @@ var Function = class extends Resource {
315
361
  this.policy = policy;
316
362
  this.name = formatName(this.props.name || logicalId);
317
363
  this.environmentVariables = props.environment ? { ...props.environment } : {};
364
+ this.tag("name", this.name);
318
365
  }
319
366
  name;
320
367
  role;
@@ -328,11 +375,15 @@ var Function = class extends Resource {
328
375
  this.environmentVariables[name] = value;
329
376
  return this;
330
377
  }
378
+ setVpc(vpc) {
379
+ this.props.vpc = vpc;
380
+ return this;
381
+ }
331
382
  get id() {
332
- return ref(this.logicalId);
383
+ return this.ref();
333
384
  }
334
385
  get arn() {
335
- return getAtt(this.logicalId, "Arn");
386
+ return this.getAtt("Arn");
336
387
  }
337
388
  get permissions() {
338
389
  return {
@@ -340,7 +391,14 @@ var Function = class extends Resource {
340
391
  "lambda:InvokeFunction",
341
392
  "lambda:InvokeAsync"
342
393
  ],
343
- resources: [this.arn]
394
+ resources: [
395
+ formatArn({
396
+ service: "lambda",
397
+ resource: "function",
398
+ resourceName: this.name,
399
+ seperator: ":"
400
+ })
401
+ ]
344
402
  };
345
403
  }
346
404
  properties() {
@@ -356,6 +414,12 @@ var Function = class extends Resource {
356
414
  EphemeralStorage: {
357
415
  Size: this.props.ephemeralStorageSize?.toMegaBytes() ?? 512
358
416
  },
417
+ ...this.props.vpc ? {
418
+ VpcConfig: {
419
+ SecurityGroupIds: this.props.vpc.securityGroupIds,
420
+ SubnetIds: this.props.vpc.subnetIds
421
+ }
422
+ } : {},
359
423
  Environment: {
360
424
  Variables: this.environmentVariables
361
425
  }
@@ -380,6 +444,7 @@ var Stack = class {
380
444
  } else {
381
445
  this.add(...item.children);
382
446
  if (item instanceof Resource) {
447
+ item.setStack(this);
383
448
  this.resources.add(item);
384
449
  }
385
450
  }
@@ -423,8 +488,24 @@ var Stack = class {
423
488
  toJSON() {
424
489
  const resources = {};
425
490
  const outputs = {};
491
+ const walk = (object) => {
492
+ for (const [key, value] of Object.entries(object)) {
493
+ if (!object.hasOwnProperty(key)) {
494
+ continue;
495
+ }
496
+ if (value instanceof Lazy) {
497
+ object[key] = value.callback(this);
498
+ continue;
499
+ }
500
+ if (typeof value === "object" && value !== null) {
501
+ walk(value);
502
+ }
503
+ }
504
+ };
426
505
  for (const resource of this) {
427
- Object.assign(resources, resource.toJSON());
506
+ const json2 = resource.toJSON();
507
+ walk(json2);
508
+ Object.assign(resources, json2);
428
509
  }
429
510
  for (const [name, value] of this.exports.entries()) {
430
511
  Object.assign(outputs, {
@@ -492,54 +573,33 @@ var toStack = ({ config, app, stackConfig, bootstrap: bootstrap2, usEastBootstra
492
573
  }
493
574
  return {
494
575
  stack,
495
- depends: stackConfig.depends
576
+ bindings
577
+ // depends: stackConfig.depends,
496
578
  };
497
579
  };
498
580
 
499
581
  // src/util/deployment.ts
500
- var createDependencyTree = (stacks) => {
582
+ var createDeploymentLine = (stacks) => {
501
583
  const list3 = stacks.map(({ stack, config }) => ({
502
584
  stack,
503
585
  depends: config?.depends?.map((dep) => dep.name) || []
504
586
  }));
505
- const findChildren = (list4, parents) => {
506
- const children = [];
507
- const rests = [];
508
- for (const item of list4) {
509
- const isChild = item.depends.filter((dep) => !parents.includes(dep)).length === 0;
510
- if (isChild) {
511
- children.push(item);
512
- } else {
513
- rests.push(item);
587
+ const line = [];
588
+ const deps = [];
589
+ let limit = 10;
590
+ while (deps.length < list3.length) {
591
+ const local = [];
592
+ for (const { stack, depends } of list3) {
593
+ if (!deps.includes(stack.name) && depends.filter((dep) => !deps.includes(dep)).length === 0) {
594
+ local.push(stack);
514
595
  }
515
596
  }
516
- if (!rests.length) {
517
- return children.map(({ stack }) => ({
518
- stack,
519
- children: []
520
- }));
597
+ if (limit-- <= 0) {
598
+ throw new Error(`Circular stack dependencies arn't allowed.`);
521
599
  }
522
- return children.map(({ stack }) => {
523
- return {
524
- stack,
525
- children: findChildren(rests, [...parents, stack.name])
526
- };
527
- });
528
- };
529
- return findChildren(list3, []);
530
- };
531
- var createDeploymentLine = (stacks) => {
532
- const line = [];
533
- const walk = (stacks2, level) => {
534
- stacks2.forEach((node) => {
535
- if (!line[level]) {
536
- line[level] = [];
537
- }
538
- line[level].push(node.stack);
539
- walk(node.children, level + 1);
540
- });
541
- };
542
- walk(stacks, 0);
600
+ deps.push(...local.map((stack) => stack.name));
601
+ line.push(local);
602
+ }
543
603
  return line;
544
604
  };
545
605
 
@@ -936,8 +996,12 @@ var RuntimeSchema = z6.enum([
936
996
  var FunctionSchema = z6.union([
937
997
  LocalFileSchema,
938
998
  z6.object({
939
- /** The file path ofthe function code. */
999
+ /** The file path of the function code. */
940
1000
  file: LocalFileSchema,
1001
+ /** Put the function inside your global VPC.
1002
+ * @default false
1003
+ */
1004
+ vpc: z6.boolean().optional(),
941
1005
  /** The amount of time that Lambda allows a function to run before stopping it.
942
1006
  * You can specify a size value from 1 second to 15 minutes.
943
1007
  * @default '10 seconds'
@@ -987,6 +1051,10 @@ var FunctionSchema = z6.union([
987
1051
  var schema = z6.object({
988
1052
  defaults: z6.object({
989
1053
  function: z6.object({
1054
+ /** Put the function inside your global VPC.
1055
+ * @default false
1056
+ */
1057
+ vpc: z6.boolean().default(false),
990
1058
  /** The amount of time that Lambda allows a function to run before stopping it.
991
1059
  * You can specify a size value from 1 second to 15 minutes.
992
1060
  * @default '10 seconds'
@@ -1078,12 +1146,36 @@ var toLambdaFunction = (ctx, id, fileOrProps) => {
1078
1146
  const lambda = new Function(id, {
1079
1147
  name: `${config.name}-${stack.name}-${id}`,
1080
1148
  code: Code.fromFile(id, props.file),
1081
- ...props
1149
+ ...props,
1150
+ vpc: void 0
1082
1151
  });
1083
1152
  lambda.addEnvironment("APP", config.name).addEnvironment("STAGE", config.stage).addEnvironment("STACK", stack.name);
1153
+ if (props.vpc) {
1154
+ lambda.setVpc({
1155
+ securityGroupIds: [
1156
+ ctx.bootstrap.import(`vpc-security-group-id`)
1157
+ ],
1158
+ subnetIds: [
1159
+ ctx.bootstrap.import(`public-subnet-1`),
1160
+ ctx.bootstrap.import(`public-subnet-2`)
1161
+ ]
1162
+ }).addPermissions({
1163
+ actions: [
1164
+ "ec2:CreateNetworkInterface",
1165
+ "ec2:DescribeNetworkInterfaces",
1166
+ "ec2:DeleteNetworkInterface",
1167
+ "ec2:AssignPrivateIpAddresses",
1168
+ "ec2:UnassignPrivateIpAddresses"
1169
+ ],
1170
+ resources: ["*"]
1171
+ });
1172
+ }
1084
1173
  if (props.runtime.startsWith("nodejs")) {
1085
1174
  lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1");
1086
1175
  }
1176
+ ctx.bind((other) => {
1177
+ other.addPermissions(lambda.permissions);
1178
+ });
1087
1179
  return lambda;
1088
1180
  };
1089
1181
 
@@ -1207,6 +1299,7 @@ var Queue = class extends Resource {
1207
1299
  super("AWS::SQS::Queue", logicalId);
1208
1300
  this.props = props;
1209
1301
  this.name = formatName(this.props.name || logicalId);
1302
+ this.tag("name", this.name);
1210
1303
  }
1211
1304
  name;
1212
1305
  setDeadLetter(arn) {
@@ -1227,7 +1320,13 @@ var Queue = class extends Resource {
1227
1320
  "sqs:GetQueueUrl",
1228
1321
  "sqs:GetQueueAttributes"
1229
1322
  ],
1230
- resources: [this.arn]
1323
+ resources: [
1324
+ formatArn({
1325
+ service: "sqs",
1326
+ resource: "queue",
1327
+ resourceName: this.name
1328
+ })
1329
+ ]
1231
1330
  };
1232
1331
  }
1233
1332
  properties() {
@@ -1454,6 +1553,7 @@ var Table = class extends Resource {
1454
1553
  this.props = props;
1455
1554
  this.name = formatName(this.props.name || logicalId);
1456
1555
  this.indexes = { ...this.props.indexes || {} };
1556
+ this.tag("name", this.name);
1457
1557
  }
1458
1558
  name;
1459
1559
  indexes;
@@ -1486,7 +1586,13 @@ var Table = class extends Resource {
1486
1586
  "dynamodb:Query",
1487
1587
  "dynamodb:Scan"
1488
1588
  ],
1489
- resources: [this.arn]
1589
+ resources: [
1590
+ formatArn({
1591
+ service: "dynamodb",
1592
+ resource: "table",
1593
+ resourceName: this.name
1594
+ })
1595
+ ]
1490
1596
  };
1491
1597
  }
1492
1598
  attributeDefinitions() {
@@ -1720,6 +1826,7 @@ var Bucket = class extends Resource {
1720
1826
  super("AWS::S3::Bucket", logicalId);
1721
1827
  this.props = props;
1722
1828
  this.name = formatName(this.props.name || logicalId);
1829
+ this.tag("name", this.name);
1723
1830
  }
1724
1831
  name;
1725
1832
  get arn() {
@@ -1736,7 +1843,13 @@ var Bucket = class extends Resource {
1736
1843
  "s3:GetQueueUrl",
1737
1844
  "s3:GetQueueAttributes"
1738
1845
  ],
1739
- resources: [this.arn]
1846
+ resources: [
1847
+ formatArn({
1848
+ service: "s3",
1849
+ resource: "bucket",
1850
+ resourceName: this.name
1851
+ })
1852
+ ]
1740
1853
  };
1741
1854
  }
1742
1855
  properties() {
@@ -1789,6 +1902,7 @@ var Topic = class extends Resource {
1789
1902
  super("AWS::SNS::Topic", logicalId);
1790
1903
  this.props = props;
1791
1904
  this.name = formatName(this.props.name || logicalId);
1905
+ this.tag("name", this.name);
1792
1906
  }
1793
1907
  name;
1794
1908
  get arn() {
@@ -1797,7 +1911,13 @@ var Topic = class extends Resource {
1797
1911
  get permissions() {
1798
1912
  return {
1799
1913
  actions: ["sns:Publish"],
1800
- resources: [this.arn]
1914
+ resources: [
1915
+ formatArn({
1916
+ service: "sns",
1917
+ resource: "topic",
1918
+ resourceName: this.name
1919
+ })
1920
+ ]
1801
1921
  };
1802
1922
  }
1803
1923
  properties() {
@@ -2030,6 +2150,7 @@ var GraphQLApi = class extends Resource {
2030
2150
  super("AWS::AppSync::GraphQLApi", logicalId);
2031
2151
  this.props = props;
2032
2152
  this.name = formatName(this.props.name || logicalId);
2153
+ this.tag("name", this.name);
2033
2154
  }
2034
2155
  name;
2035
2156
  lambdaAuthProviders = [];
@@ -2794,6 +2915,12 @@ var Vpc = class extends Resource {
2794
2915
  get id() {
2795
2916
  return ref(this.logicalId);
2796
2917
  }
2918
+ get defaultNetworkAcl() {
2919
+ return getAtt(this.logicalId, "DefaultNetworkAcl");
2920
+ }
2921
+ get defaultSecurityGroup() {
2922
+ return getAtt(this.logicalId, "DefaultSecurityGroup");
2923
+ }
2797
2924
  properties() {
2798
2925
  return {
2799
2926
  CidrBlock: this.props.cidrBlock.ip
@@ -2805,6 +2932,7 @@ var RouteTable = class extends Resource {
2805
2932
  super("AWS::EC2::RouteTable", logicalId);
2806
2933
  this.props = props;
2807
2934
  this.name = formatName(props.name || logicalId);
2935
+ this.tag("name", this.name);
2808
2936
  }
2809
2937
  name;
2810
2938
  get id() {
@@ -2812,11 +2940,7 @@ var RouteTable = class extends Resource {
2812
2940
  }
2813
2941
  properties() {
2814
2942
  return {
2815
- VpcId: this.props.vpcId,
2816
- Tags: [{
2817
- Key: "name",
2818
- Value: this.name
2819
- }]
2943
+ VpcId: this.props.vpcId
2820
2944
  };
2821
2945
  }
2822
2946
  };
@@ -2969,6 +3093,7 @@ var vpcPlugin = definePlugin({
2969
3093
  routeTableId: publicRouteTable.id,
2970
3094
  destination: Peer.anyIpv4()
2971
3095
  }).dependsOn(gateway, publicRouteTable);
3096
+ bootstrap2.export("vpc-security-group-id", vpc.defaultSecurityGroup);
2972
3097
  bootstrap2.export(`vpc-id`, vpc.id);
2973
3098
  bootstrap2.add(
2974
3099
  vpc,
@@ -3011,7 +3136,9 @@ var SecurityGroup = class extends Resource {
3011
3136
  constructor(logicalId, props) {
3012
3137
  super("AWS::EC2::SecurityGroup", logicalId);
3013
3138
  this.props = props;
3139
+ this.name = formatName(props.name ?? logicalId);
3014
3140
  }
3141
+ name;
3015
3142
  ingress = [];
3016
3143
  egress = [];
3017
3144
  get id() {
@@ -3036,7 +3163,7 @@ var SecurityGroup = class extends Resource {
3036
3163
  properties() {
3037
3164
  return {
3038
3165
  VpcId: this.props.vpcId,
3039
- GroupName: this.logicalId,
3166
+ GroupName: this.name,
3040
3167
  GroupDescription: this.props.description,
3041
3168
  SecurityGroupIngress: this.ingress.map((rule) => ({
3042
3169
  Description: rule.description || "",
@@ -3447,6 +3574,7 @@ var Collection = class extends Resource {
3447
3574
  super("AWS::OpenSearchServerless::Collection", logicalId);
3448
3575
  this.props = props;
3449
3576
  this.name = this.props.name || logicalId;
3577
+ this.tag("name", this.name);
3450
3578
  }
3451
3579
  name;
3452
3580
  get id() {
@@ -3458,6 +3586,18 @@ var Collection = class extends Resource {
3458
3586
  get endpoint() {
3459
3587
  return getAtt(this.logicalId, "CollectionEndpoint");
3460
3588
  }
3589
+ get permissions() {
3590
+ return {
3591
+ actions: ["aoss:APIAccessAll"],
3592
+ resources: [
3593
+ formatArn({
3594
+ service: "aoss",
3595
+ resource: "collection",
3596
+ resourceName: this.name
3597
+ })
3598
+ ]
3599
+ };
3600
+ }
3461
3601
  properties() {
3462
3602
  return {
3463
3603
  Name: this.name,
@@ -3482,10 +3622,155 @@ var searchPlugin = definePlugin({
3482
3622
  type: "search"
3483
3623
  });
3484
3624
  bind((lambda) => {
3485
- lambda.addPermissions({
3486
- actions: ["aoss:APIAccessAll"],
3487
- resources: [collection.arn]
3488
- });
3625
+ lambda.addPermissions(collection.permissions);
3626
+ });
3627
+ }
3628
+ }
3629
+ });
3630
+
3631
+ // src/plugins/cache.ts
3632
+ import { z as z19 } from "zod";
3633
+
3634
+ // src/formation/resource/memorydb/cluster.ts
3635
+ var Cluster = class extends Resource {
3636
+ constructor(logicalId, props) {
3637
+ super("AWS::MemoryDB::Cluster", logicalId);
3638
+ this.props = props;
3639
+ this.name = formatName(this.props.name || logicalId);
3640
+ this.tag("name", this.name);
3641
+ }
3642
+ name;
3643
+ get status() {
3644
+ return this.getAtt("Status");
3645
+ }
3646
+ get arn() {
3647
+ return this.getAtt("ARN");
3648
+ }
3649
+ get address() {
3650
+ return this.getAtt("ClusterEndpoint.Address");
3651
+ }
3652
+ get port() {
3653
+ return this.getAtt("ClusterEndpoint.Port");
3654
+ }
3655
+ properties() {
3656
+ return {
3657
+ ClusterName: this.name,
3658
+ ClusterEndpoint: {
3659
+ Port: this.props.port
3660
+ },
3661
+ Port: this.props.port,
3662
+ ...this.attr("Description", this.props.description),
3663
+ ACLName: this.props.aclName,
3664
+ EngineVersion: this.props.engine ?? "7.0",
3665
+ ...this.attr("SubnetGroupName", this.props.subnetGroupName),
3666
+ ...this.attr("SecurityGroupIds", this.props.securityGroupIds),
3667
+ NodeType: "db." + this.props.type,
3668
+ NumReplicasPerShard: this.props.replicasPerShard ?? 1,
3669
+ NumShards: this.props.shards ?? 1,
3670
+ TLSEnabled: this.props.tls ?? false,
3671
+ DataTiering: this.props.dataTiering ? "true" : "false",
3672
+ AutoMinorVersionUpgrade: this.props.autoMinorVersionUpgrade ?? true,
3673
+ MaintenanceWindow: this.props.maintenanceWindow ?? "Sat:02:00-Sat:05:00"
3674
+ };
3675
+ }
3676
+ };
3677
+
3678
+ // src/formation/resource/memorydb/subnet-group.ts
3679
+ var SubnetGroup = class extends Resource {
3680
+ constructor(logicalId, props) {
3681
+ super("AWS::MemoryDB::SubnetGroup", logicalId);
3682
+ this.props = props;
3683
+ this.name = formatName(this.props.name || logicalId);
3684
+ }
3685
+ name;
3686
+ get arn() {
3687
+ return getAtt(this.logicalId, "Arn");
3688
+ }
3689
+ properties() {
3690
+ return {
3691
+ SubnetGroupName: this.name,
3692
+ SubnetIds: this.props.subnetIds,
3693
+ ...this.attr("Description", this.props.description)
3694
+ };
3695
+ }
3696
+ };
3697
+
3698
+ // src/plugins/cache.ts
3699
+ var TypeSchema = z19.enum([
3700
+ "t4g.small",
3701
+ "t4g.medium",
3702
+ "r6g.large",
3703
+ "r6g.xlarge",
3704
+ "r6g.2xlarge",
3705
+ "r6g.4xlarge",
3706
+ "r6g.8xlarge",
3707
+ "r6g.12xlarge",
3708
+ "r6g.16xlarge",
3709
+ "r6gd.xlarge",
3710
+ "r6gd.2xlarge",
3711
+ "r6gd.4xlarge",
3712
+ "r6gd.8xlarge"
3713
+ ]);
3714
+ var PortSchema = z19.number().int().min(1).max(5e4);
3715
+ var ShardsSchema = z19.number().int().min(0).max(100);
3716
+ var ReplicasPerShardSchema = z19.number().int().min(0).max(5);
3717
+ var EngineSchema = z19.enum(["7.0", "6.2"]);
3718
+ var cachePlugin = definePlugin({
3719
+ name: "cache",
3720
+ schema: z19.object({
3721
+ stacks: z19.object({
3722
+ /** Define the caches in your stack.
3723
+ * For access to the cache put your functions inside the global VPC.
3724
+ * @example
3725
+ * {
3726
+ * caches: {
3727
+ * CACHE_NAME: {
3728
+ * type: 't4g.small'
3729
+ * }
3730
+ * }
3731
+ * }
3732
+ */
3733
+ caches: z19.record(
3734
+ ResourceIdSchema,
3735
+ z19.object({
3736
+ type: TypeSchema.default("t4g.small"),
3737
+ port: PortSchema.default(6379),
3738
+ shards: ShardsSchema.default(1),
3739
+ replicasPerShard: ReplicasPerShardSchema.default(1),
3740
+ engine: EngineSchema.default("7.0"),
3741
+ dataTiering: z19.boolean().default(false)
3742
+ })
3743
+ ).optional()
3744
+ }).array()
3745
+ }),
3746
+ onStack({ config, stack, stackConfig, bootstrap: bootstrap2, bind }) {
3747
+ for (const [id, props] of Object.entries(stackConfig.caches || {})) {
3748
+ const name = `${config.name}-${stack.name}-${id}`;
3749
+ const subnetGroup = new SubnetGroup(id, {
3750
+ name,
3751
+ subnetIds: [
3752
+ bootstrap2.import(`private-subnet-1`),
3753
+ bootstrap2.import(`private-subnet-2`)
3754
+ ]
3755
+ });
3756
+ const securityGroup = new SecurityGroup(id, {
3757
+ name,
3758
+ vpcId: bootstrap2.import(`vpc-id`),
3759
+ description: name
3760
+ });
3761
+ const port = Port.tcp(props.port);
3762
+ securityGroup.addIngressRule(Peer.anyIpv4(), port);
3763
+ securityGroup.addIngressRule(Peer.anyIpv6(), port);
3764
+ const cluster = new Cluster(id, {
3765
+ name,
3766
+ aclName: "open-access",
3767
+ securityGroupIds: [securityGroup.id],
3768
+ subnetGroupName: subnetGroup.name,
3769
+ ...props
3770
+ }).dependsOn(subnetGroup, securityGroup);
3771
+ stack.add(subnetGroup, securityGroup, cluster);
3772
+ bind((lambda) => {
3773
+ lambda.addEnvironment(`CACHE_${stack.name}_${id}_HOST`, cluster.address).addEnvironment(`CACHE_${stack.name}_${id}_PORT`, props.port.toString());
3489
3774
  });
3490
3775
  }
3491
3776
  }
@@ -3496,6 +3781,7 @@ var defaultPlugins = [
3496
3781
  extendPlugin,
3497
3782
  vpcPlugin,
3498
3783
  functionPlugin,
3784
+ cachePlugin,
3499
3785
  cronPlugin,
3500
3786
  queuePlugin,
3501
3787
  tablePlugin,
@@ -3677,7 +3963,7 @@ var toApp = async (config, filters) => {
3677
3963
  config.stacks.filter((stack) => filters.includes(stack.name))
3678
3964
  );
3679
3965
  for (const stackConfig of filterdStacks) {
3680
- const { stack } = toStack({
3966
+ const { stack, bindings: bindings2 } = toStack({
3681
3967
  config,
3682
3968
  stackConfig,
3683
3969
  bootstrap: bootstrap2,
@@ -3686,7 +3972,7 @@ var toApp = async (config, filters) => {
3686
3972
  app
3687
3973
  });
3688
3974
  app.add(stack);
3689
- stacks.push({ stack, config: stackConfig });
3975
+ stacks.push({ stack, config: stackConfig, bindings: bindings2 });
3690
3976
  }
3691
3977
  for (const plugin of plugins) {
3692
3978
  for (const stack of app.stacks) {
@@ -3708,23 +3994,31 @@ var toApp = async (config, filters) => {
3708
3994
  bind2(fn);
3709
3995
  }
3710
3996
  }
3711
- let dependencyTree = createDependencyTree(stacks);
3997
+ for (const entry of stacks) {
3998
+ for (const dep of entry.config.depends || []) {
3999
+ const depStack = stacks.find((entry2) => entry2.config.name === dep.name);
4000
+ if (!depStack) {
4001
+ throw new Error(`Stack dependency not found: ${dep.name}`);
4002
+ }
4003
+ const functions2 = entry.stack.find(Function);
4004
+ for (const bind2 of depStack.bindings) {
4005
+ for (const fn of functions2) {
4006
+ bind2(fn);
4007
+ }
4008
+ }
4009
+ }
4010
+ }
4011
+ const deploymentLine = createDeploymentLine(stacks);
3712
4012
  if (bootstrap2.size > 0) {
3713
- dependencyTree = [{
3714
- stack: bootstrap2,
3715
- children: dependencyTree
3716
- }];
4013
+ deploymentLine.unshift([bootstrap2]);
3717
4014
  }
3718
4015
  if (usEastBootstrap.size > 0) {
3719
- dependencyTree = [{
3720
- stack: usEastBootstrap,
3721
- children: dependencyTree
3722
- }];
4016
+ deploymentLine.unshift([usEastBootstrap]);
3723
4017
  }
3724
4018
  return {
3725
4019
  app,
3726
4020
  plugins,
3727
- dependencyTree
4021
+ deploymentLine
3728
4022
  };
3729
4023
  };
3730
4024
 
@@ -3748,17 +4042,17 @@ var getCredentials = (profile) => {
3748
4042
  };
3749
4043
 
3750
4044
  // src/schema/app.ts
3751
- import { z as z22 } from "zod";
4045
+ import { z as z23 } from "zod";
3752
4046
 
3753
4047
  // src/schema/stack.ts
3754
- import { z as z19 } from "zod";
3755
- var StackSchema = z19.object({
4048
+ import { z as z20 } from "zod";
4049
+ var StackSchema = z20.object({
3756
4050
  name: ResourceIdSchema,
3757
- depends: z19.array(z19.lazy(() => StackSchema)).optional()
4051
+ depends: z20.array(z20.lazy(() => StackSchema)).optional()
3758
4052
  });
3759
4053
 
3760
4054
  // src/schema/region.ts
3761
- import { z as z20 } from "zod";
4055
+ import { z as z21 } from "zod";
3762
4056
  var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
3763
4057
  var AF = ["af-south-1"];
3764
4058
  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"];
@@ -3775,47 +4069,49 @@ var regions = [
3775
4069
  ...ME,
3776
4070
  ...SA
3777
4071
  ];
3778
- var RegionSchema = z20.enum(regions);
4072
+ var RegionSchema = z21.enum(regions);
3779
4073
 
3780
4074
  // src/schema/plugin.ts
3781
- import { z as z21 } from "zod";
3782
- var PluginSchema = z21.object({
3783
- name: z21.string(),
3784
- schema: z21.custom().optional(),
4075
+ import { z as z22 } from "zod";
4076
+ var PluginSchema = z22.object({
4077
+ name: z22.string(),
4078
+ schema: z22.custom().optional(),
3785
4079
  // depends: z.array(z.lazy(() => PluginSchema)).optional(),
3786
- onApp: z21.function().returns(z21.void()).optional(),
3787
- onStack: z21.function().returns(z21.any()).optional(),
3788
- onResource: z21.function().returns(z21.any()).optional()
4080
+ onApp: z22.function().returns(z22.void()).optional(),
4081
+ onStack: z22.function().returns(z22.any()).optional(),
4082
+ onResource: z22.function().returns(z22.any()).optional()
3789
4083
  // bind: z.function().optional(),
3790
4084
  });
3791
4085
 
3792
4086
  // src/schema/app.ts
3793
- var AppSchema = z22.object({
4087
+ var AppSchema = z23.object({
3794
4088
  /** App name */
3795
4089
  name: ResourceIdSchema,
3796
4090
  /** The AWS region to deploy to. */
3797
4091
  region: RegionSchema,
3798
4092
  /** The AWS profile to deploy to. */
3799
- profile: z22.string(),
4093
+ profile: z23.string(),
3800
4094
  /** The deployment stage.
3801
4095
  * @default 'prod'
3802
4096
  */
3803
- stage: z22.string().regex(/[a-z]+/).default("prod"),
4097
+ stage: z23.string().regex(/[a-z]+/).default("prod"),
3804
4098
  /** Default properties. */
3805
- defaults: z22.object({}).default({}),
4099
+ defaults: z23.object({}).default({}),
3806
4100
  /** The application stacks. */
3807
- stacks: z22.array(StackSchema).min(1).refine((stacks) => {
4101
+ stacks: z23.array(StackSchema).min(1).refine((stacks) => {
3808
4102
  const unique = new Set(stacks.map((stack) => stack.name));
3809
4103
  return unique.size === stacks.length;
3810
4104
  }, "Must be an array of unique stacks"),
3811
4105
  /** Custom plugins. */
3812
- plugins: z22.array(PluginSchema).optional()
4106
+ plugins: z23.array(PluginSchema).optional()
3813
4107
  });
3814
4108
 
3815
4109
  // src/util/import.ts
3816
- import { transformFile } from "@swc/core";
4110
+ import { rollup as rollup2 } from "rollup";
4111
+ import { swc as swc2 } from "rollup-plugin-swc3";
4112
+ import replace from "rollup-plugin-replace";
3817
4113
  import { dirname, join as join2 } from "path";
3818
- import { lstat as lstat2, mkdir, writeFile } from "fs/promises";
4114
+ import { mkdir, writeFile } from "fs/promises";
3819
4115
 
3820
4116
  // src/util/path.ts
3821
4117
  import { lstat } from "fs/promises";
@@ -3862,54 +4158,25 @@ var fileExist = async (file) => {
3862
4158
  };
3863
4159
 
3864
4160
  // src/util/import.ts
3865
- var resolveFileNameExtension = async (path) => {
3866
- const options = [
3867
- "",
3868
- ".ts",
3869
- ".js",
3870
- "/index.ts",
3871
- "/index.js"
3872
- ];
3873
- for (const option of options) {
3874
- const file = path.replace(/\.js$/, "") + option;
3875
- let stat;
3876
- try {
3877
- stat = await lstat2(file);
3878
- } catch (error) {
3879
- continue;
3880
- }
3881
- if (stat.isFile()) {
3882
- return file;
3883
- }
3884
- }
3885
- throw new Error(`Failed to load file: ${path}`);
3886
- };
3887
- var resolveDir = (path) => {
3888
- return dirname(path).replace(directories.root + "/", "");
3889
- };
3890
4161
  var importFile = async (path) => {
3891
- const load = async (file) => {
3892
- debug("Load file:", style.info(file));
3893
- let { code: code2 } = await transformFile(file, {
3894
- isModule: true
3895
- });
3896
- const path2 = dirname(file);
3897
- const dir = resolveDir(file);
3898
- code2 = code2.replaceAll("__dirname", `"${dir}"`);
3899
- const matches = code2.match(/(import|export)\s*{\s*[a-z0-9\_\,\s\*]+\s*}\s*from\s*('|")(\.\.?[\/a-z0-9\_\-\.]+)('|");?/ig);
3900
- if (!matches)
3901
- return code2;
3902
- await Promise.all(matches?.map(async (match) => {
3903
- const parts = /('|")(\.\.?[\/a-z0-9\_\-\.]+)('|")/ig.exec(match);
3904
- const from = parts[2];
3905
- const file2 = await resolveFileNameExtension(join2(path2, from));
3906
- const result = await load(file2);
3907
- code2 = code2.replace(match, result);
3908
- }));
3909
- return code2;
3910
- };
3911
- const code = await load(path);
4162
+ const bundle = await rollup2({
4163
+ input: path,
4164
+ plugins: [
4165
+ replace({
4166
+ __dirname: (id) => `'${dirname(id)}'`
4167
+ }),
4168
+ swc2({
4169
+ minify: false
4170
+ })
4171
+ ]
4172
+ });
3912
4173
  const outputFile = join2(directories.cache, "config.js");
4174
+ const result = await bundle.generate({
4175
+ format: "esm",
4176
+ exports: "default"
4177
+ });
4178
+ const output = result.output[0];
4179
+ const code = output.code;
3913
4180
  await mkdir(directories.cache, { recursive: true });
3914
4181
  await writeFile(outputFile, code);
3915
4182
  debug("Save config file:", style.info(outputFile));
@@ -4211,7 +4478,7 @@ var Renderer = class {
4211
4478
  flushing = false;
4212
4479
  screen = [];
4213
4480
  width() {
4214
- return this.output.columns;
4481
+ return this.output.columns - 1;
4215
4482
  }
4216
4483
  height() {
4217
4484
  return this.output.rows;
@@ -5049,12 +5316,73 @@ var assetPublisher = (config, app) => {
5049
5316
  };
5050
5317
  };
5051
5318
 
5319
+ // src/cli/ui/complex/deployer.ts
5320
+ var stacksDeployer = (deploymentLine) => {
5321
+ const stackNames = deploymentLine.map((line) => line.map((stack) => stack.name)).flat();
5322
+ const stackNameSize = Math.max(...stackNames.map((name) => name.length));
5323
+ return (term) => {
5324
+ const ui = {};
5325
+ term.out.gap();
5326
+ for (const i in deploymentLine) {
5327
+ const line = flexLine(
5328
+ term,
5329
+ [" "],
5330
+ [
5331
+ " ",
5332
+ style.placeholder(Number(i) + 1),
5333
+ style.placeholder(" \u2500\u2500")
5334
+ ]
5335
+ );
5336
+ term.out.write(line);
5337
+ term.out.write(br());
5338
+ for (const stack of deploymentLine[i]) {
5339
+ const icon = new Signal(" ");
5340
+ const name = new Signal(style.label.dim(stack.name));
5341
+ const status2 = new Signal(style.info.dim("waiting"));
5342
+ let stopSpinner;
5343
+ term.out.write([
5344
+ icon,
5345
+ " ",
5346
+ name,
5347
+ " ".repeat(stackNameSize - stack.name.length),
5348
+ " ",
5349
+ style.placeholder(symbol.pointerSmall),
5350
+ " ",
5351
+ status2,
5352
+ br()
5353
+ ]);
5354
+ ui[stack.name] = {
5355
+ start: (value) => {
5356
+ const [spinner, stop] = createSpinner();
5357
+ name.set(style.label(stack.name));
5358
+ icon.set(spinner);
5359
+ status2.set(style.warning(value));
5360
+ stopSpinner = stop;
5361
+ },
5362
+ done(value) {
5363
+ stopSpinner();
5364
+ icon.set(style.success(symbol.success));
5365
+ status2.set(style.success(value));
5366
+ },
5367
+ fail(value) {
5368
+ stopSpinner();
5369
+ icon.set(style.error(symbol.error));
5370
+ status2.set(style.error(value));
5371
+ }
5372
+ };
5373
+ }
5374
+ }
5375
+ term.out.gap();
5376
+ return ui;
5377
+ };
5378
+ };
5379
+
5052
5380
  // src/cli/command/deploy.ts
5053
5381
  var deploy = (program2) => {
5054
5382
  program2.command("deploy").argument("[stacks...]", "Optionally filter stacks to deploy").description("Deploy your app to AWS").action(async (filters) => {
5055
5383
  await layout(async (config, write) => {
5056
5384
  await write(bootstrapDeployer(config));
5057
- const { app, dependencyTree } = await toApp(config, filters);
5385
+ const { app, deploymentLine } = await toApp(config, filters);
5058
5386
  const stackNames = app.stacks.map((stack) => stack.name);
5059
5387
  const formattedFilter = stackNames.map((i) => style.info(i)).join(style.placeholder(", "));
5060
5388
  debug("Stacks to deploy", formattedFilter);
@@ -5068,26 +5396,21 @@ var deploy = (program2) => {
5068
5396
  await write(assetBuilder(app));
5069
5397
  await write(assetPublisher(config, app));
5070
5398
  await write(templateBuilder(app));
5071
- const statuses = {};
5072
- for (const stack of app) {
5073
- statuses[stack.name] = new Signal(style.info("waiting"));
5074
- }
5075
5399
  const doneDeploying = write(loadingDialog("Deploying stacks to AWS..."));
5076
- write(stackTree(dependencyTree, statuses));
5077
5400
  const client = new StackClient(app, config.account, config.region, config.credentials);
5078
- const deploymentLine = createDeploymentLine(dependencyTree);
5079
- for (const stacks of deploymentLine) {
5080
- const results = await Promise.allSettled(stacks.map(async (stack) => {
5081
- const signal = statuses[stack.name];
5082
- signal.set(style.warning("deploying"));
5401
+ const ui = write(stacksDeployer(deploymentLine));
5402
+ for (const line of deploymentLine) {
5403
+ const results = await Promise.allSettled(line.map(async (stack) => {
5404
+ const item = ui[stack.name];
5405
+ item.start("deploying");
5083
5406
  try {
5084
5407
  await client.deploy(stack);
5085
5408
  } catch (error) {
5086
5409
  debugError(error);
5087
- signal.set(style.error("failed"));
5410
+ item.fail("failed");
5088
5411
  throw error;
5089
5412
  }
5090
- signal.set(style.success("deployed"));
5413
+ item.done("deployed");
5091
5414
  }));
5092
5415
  for (const result of results) {
5093
5416
  if (result.status === "rejected") {