@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.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,35 @@ 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 names = stacks.map(({ stack }) => stack.name);
588
+ const line = [];
589
+ const deps = [];
590
+ let limit = 100;
591
+ while (deps.length < list3.length) {
592
+ const local = [];
593
+ for (const { stack, depends } of list3) {
594
+ if (!deps.includes(stack.name) && depends.filter((dep) => !deps.includes(dep)).length === 0) {
595
+ local.push(stack);
514
596
  }
515
597
  }
516
- if (!rests.length) {
517
- return children.map(({ stack }) => ({
518
- stack,
519
- children: []
520
- }));
598
+ if (limit-- <= 0) {
599
+ const circularNames = names.filter((name) => deps.includes(name));
600
+ throw new Error(`Circular stack dependencies arn't allowed: ${circularNames}`);
521
601
  }
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);
602
+ deps.push(...local.map((stack) => stack.name));
603
+ line.push(local);
604
+ }
543
605
  return line;
544
606
  };
545
607
 
@@ -936,8 +998,12 @@ var RuntimeSchema = z6.enum([
936
998
  var FunctionSchema = z6.union([
937
999
  LocalFileSchema,
938
1000
  z6.object({
939
- /** The file path ofthe function code. */
1001
+ /** The file path of the function code. */
940
1002
  file: LocalFileSchema,
1003
+ /** Put the function inside your global VPC.
1004
+ * @default false
1005
+ */
1006
+ vpc: z6.boolean().optional(),
941
1007
  /** The amount of time that Lambda allows a function to run before stopping it.
942
1008
  * You can specify a size value from 1 second to 15 minutes.
943
1009
  * @default '10 seconds'
@@ -987,6 +1053,10 @@ var FunctionSchema = z6.union([
987
1053
  var schema = z6.object({
988
1054
  defaults: z6.object({
989
1055
  function: z6.object({
1056
+ /** Put the function inside your global VPC.
1057
+ * @default false
1058
+ */
1059
+ vpc: z6.boolean().default(false),
990
1060
  /** The amount of time that Lambda allows a function to run before stopping it.
991
1061
  * You can specify a size value from 1 second to 15 minutes.
992
1062
  * @default '10 seconds'
@@ -1078,12 +1148,36 @@ var toLambdaFunction = (ctx, id, fileOrProps) => {
1078
1148
  const lambda = new Function(id, {
1079
1149
  name: `${config.name}-${stack.name}-${id}`,
1080
1150
  code: Code.fromFile(id, props.file),
1081
- ...props
1151
+ ...props,
1152
+ vpc: void 0
1082
1153
  });
1083
1154
  lambda.addEnvironment("APP", config.name).addEnvironment("STAGE", config.stage).addEnvironment("STACK", stack.name);
1155
+ if (props.vpc) {
1156
+ lambda.setVpc({
1157
+ securityGroupIds: [
1158
+ ctx.bootstrap.import(`vpc-security-group-id`)
1159
+ ],
1160
+ subnetIds: [
1161
+ ctx.bootstrap.import(`public-subnet-1`),
1162
+ ctx.bootstrap.import(`public-subnet-2`)
1163
+ ]
1164
+ }).addPermissions({
1165
+ actions: [
1166
+ "ec2:CreateNetworkInterface",
1167
+ "ec2:DescribeNetworkInterfaces",
1168
+ "ec2:DeleteNetworkInterface",
1169
+ "ec2:AssignPrivateIpAddresses",
1170
+ "ec2:UnassignPrivateIpAddresses"
1171
+ ],
1172
+ resources: ["*"]
1173
+ });
1174
+ }
1084
1175
  if (props.runtime.startsWith("nodejs")) {
1085
1176
  lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1");
1086
1177
  }
1178
+ ctx.bind((other) => {
1179
+ other.addPermissions(lambda.permissions);
1180
+ });
1087
1181
  return lambda;
1088
1182
  };
1089
1183
 
@@ -1207,6 +1301,7 @@ var Queue = class extends Resource {
1207
1301
  super("AWS::SQS::Queue", logicalId);
1208
1302
  this.props = props;
1209
1303
  this.name = formatName(this.props.name || logicalId);
1304
+ this.tag("name", this.name);
1210
1305
  }
1211
1306
  name;
1212
1307
  setDeadLetter(arn) {
@@ -1227,7 +1322,13 @@ var Queue = class extends Resource {
1227
1322
  "sqs:GetQueueUrl",
1228
1323
  "sqs:GetQueueAttributes"
1229
1324
  ],
1230
- resources: [this.arn]
1325
+ resources: [
1326
+ formatArn({
1327
+ service: "sqs",
1328
+ resource: "queue",
1329
+ resourceName: this.name
1330
+ })
1331
+ ]
1231
1332
  };
1232
1333
  }
1233
1334
  properties() {
@@ -1454,6 +1555,7 @@ var Table = class extends Resource {
1454
1555
  this.props = props;
1455
1556
  this.name = formatName(this.props.name || logicalId);
1456
1557
  this.indexes = { ...this.props.indexes || {} };
1558
+ this.tag("name", this.name);
1457
1559
  }
1458
1560
  name;
1459
1561
  indexes;
@@ -1486,7 +1588,13 @@ var Table = class extends Resource {
1486
1588
  "dynamodb:Query",
1487
1589
  "dynamodb:Scan"
1488
1590
  ],
1489
- resources: [this.arn]
1591
+ resources: [
1592
+ formatArn({
1593
+ service: "dynamodb",
1594
+ resource: "table",
1595
+ resourceName: this.name
1596
+ })
1597
+ ]
1490
1598
  };
1491
1599
  }
1492
1600
  attributeDefinitions() {
@@ -1720,6 +1828,7 @@ var Bucket = class extends Resource {
1720
1828
  super("AWS::S3::Bucket", logicalId);
1721
1829
  this.props = props;
1722
1830
  this.name = formatName(this.props.name || logicalId);
1831
+ this.tag("name", this.name);
1723
1832
  }
1724
1833
  name;
1725
1834
  get arn() {
@@ -1736,7 +1845,13 @@ var Bucket = class extends Resource {
1736
1845
  "s3:GetQueueUrl",
1737
1846
  "s3:GetQueueAttributes"
1738
1847
  ],
1739
- resources: [this.arn]
1848
+ resources: [
1849
+ formatArn({
1850
+ service: "s3",
1851
+ resource: "bucket",
1852
+ resourceName: this.name
1853
+ })
1854
+ ]
1740
1855
  };
1741
1856
  }
1742
1857
  properties() {
@@ -1789,6 +1904,7 @@ var Topic = class extends Resource {
1789
1904
  super("AWS::SNS::Topic", logicalId);
1790
1905
  this.props = props;
1791
1906
  this.name = formatName(this.props.name || logicalId);
1907
+ this.tag("name", this.name);
1792
1908
  }
1793
1909
  name;
1794
1910
  get arn() {
@@ -1797,7 +1913,13 @@ var Topic = class extends Resource {
1797
1913
  get permissions() {
1798
1914
  return {
1799
1915
  actions: ["sns:Publish"],
1800
- resources: [this.arn]
1916
+ resources: [
1917
+ formatArn({
1918
+ service: "sns",
1919
+ resource: "topic",
1920
+ resourceName: this.name
1921
+ })
1922
+ ]
1801
1923
  };
1802
1924
  }
1803
1925
  properties() {
@@ -2030,6 +2152,7 @@ var GraphQLApi = class extends Resource {
2030
2152
  super("AWS::AppSync::GraphQLApi", logicalId);
2031
2153
  this.props = props;
2032
2154
  this.name = formatName(this.props.name || logicalId);
2155
+ this.tag("name", this.name);
2033
2156
  }
2034
2157
  name;
2035
2158
  lambdaAuthProviders = [];
@@ -2794,6 +2917,12 @@ var Vpc = class extends Resource {
2794
2917
  get id() {
2795
2918
  return ref(this.logicalId);
2796
2919
  }
2920
+ get defaultNetworkAcl() {
2921
+ return getAtt(this.logicalId, "DefaultNetworkAcl");
2922
+ }
2923
+ get defaultSecurityGroup() {
2924
+ return getAtt(this.logicalId, "DefaultSecurityGroup");
2925
+ }
2797
2926
  properties() {
2798
2927
  return {
2799
2928
  CidrBlock: this.props.cidrBlock.ip
@@ -2805,6 +2934,7 @@ var RouteTable = class extends Resource {
2805
2934
  super("AWS::EC2::RouteTable", logicalId);
2806
2935
  this.props = props;
2807
2936
  this.name = formatName(props.name || logicalId);
2937
+ this.tag("name", this.name);
2808
2938
  }
2809
2939
  name;
2810
2940
  get id() {
@@ -2812,11 +2942,7 @@ var RouteTable = class extends Resource {
2812
2942
  }
2813
2943
  properties() {
2814
2944
  return {
2815
- VpcId: this.props.vpcId,
2816
- Tags: [{
2817
- Key: "name",
2818
- Value: this.name
2819
- }]
2945
+ VpcId: this.props.vpcId
2820
2946
  };
2821
2947
  }
2822
2948
  };
@@ -2969,6 +3095,7 @@ var vpcPlugin = definePlugin({
2969
3095
  routeTableId: publicRouteTable.id,
2970
3096
  destination: Peer.anyIpv4()
2971
3097
  }).dependsOn(gateway, publicRouteTable);
3098
+ bootstrap2.export("vpc-security-group-id", vpc.defaultSecurityGroup);
2972
3099
  bootstrap2.export(`vpc-id`, vpc.id);
2973
3100
  bootstrap2.add(
2974
3101
  vpc,
@@ -3011,7 +3138,9 @@ var SecurityGroup = class extends Resource {
3011
3138
  constructor(logicalId, props) {
3012
3139
  super("AWS::EC2::SecurityGroup", logicalId);
3013
3140
  this.props = props;
3141
+ this.name = formatName(props.name ?? logicalId);
3014
3142
  }
3143
+ name;
3015
3144
  ingress = [];
3016
3145
  egress = [];
3017
3146
  get id() {
@@ -3036,7 +3165,7 @@ var SecurityGroup = class extends Resource {
3036
3165
  properties() {
3037
3166
  return {
3038
3167
  VpcId: this.props.vpcId,
3039
- GroupName: this.logicalId,
3168
+ GroupName: this.name,
3040
3169
  GroupDescription: this.props.description,
3041
3170
  SecurityGroupIngress: this.ingress.map((rule) => ({
3042
3171
  Description: rule.description || "",
@@ -3447,6 +3576,7 @@ var Collection = class extends Resource {
3447
3576
  super("AWS::OpenSearchServerless::Collection", logicalId);
3448
3577
  this.props = props;
3449
3578
  this.name = this.props.name || logicalId;
3579
+ this.tag("name", this.name);
3450
3580
  }
3451
3581
  name;
3452
3582
  get id() {
@@ -3458,6 +3588,18 @@ var Collection = class extends Resource {
3458
3588
  get endpoint() {
3459
3589
  return getAtt(this.logicalId, "CollectionEndpoint");
3460
3590
  }
3591
+ get permissions() {
3592
+ return {
3593
+ actions: ["aoss:APIAccessAll"],
3594
+ resources: [
3595
+ formatArn({
3596
+ service: "aoss",
3597
+ resource: "collection",
3598
+ resourceName: this.name
3599
+ })
3600
+ ]
3601
+ };
3602
+ }
3461
3603
  properties() {
3462
3604
  return {
3463
3605
  Name: this.name,
@@ -3482,10 +3624,155 @@ var searchPlugin = definePlugin({
3482
3624
  type: "search"
3483
3625
  });
3484
3626
  bind((lambda) => {
3485
- lambda.addPermissions({
3486
- actions: ["aoss:APIAccessAll"],
3487
- resources: [collection.arn]
3488
- });
3627
+ lambda.addPermissions(collection.permissions);
3628
+ });
3629
+ }
3630
+ }
3631
+ });
3632
+
3633
+ // src/plugins/cache.ts
3634
+ import { z as z19 } from "zod";
3635
+
3636
+ // src/formation/resource/memorydb/cluster.ts
3637
+ var Cluster = class extends Resource {
3638
+ constructor(logicalId, props) {
3639
+ super("AWS::MemoryDB::Cluster", logicalId);
3640
+ this.props = props;
3641
+ this.name = formatName(this.props.name || logicalId);
3642
+ this.tag("name", this.name);
3643
+ }
3644
+ name;
3645
+ get status() {
3646
+ return this.getAtt("Status");
3647
+ }
3648
+ get arn() {
3649
+ return this.getAtt("ARN");
3650
+ }
3651
+ get address() {
3652
+ return this.getAtt("ClusterEndpoint.Address");
3653
+ }
3654
+ get port() {
3655
+ return this.getAtt("ClusterEndpoint.Port");
3656
+ }
3657
+ properties() {
3658
+ return {
3659
+ ClusterName: this.name,
3660
+ ClusterEndpoint: {
3661
+ Port: this.props.port
3662
+ },
3663
+ Port: this.props.port,
3664
+ ...this.attr("Description", this.props.description),
3665
+ ACLName: this.props.aclName,
3666
+ EngineVersion: this.props.engine ?? "7.0",
3667
+ ...this.attr("SubnetGroupName", this.props.subnetGroupName),
3668
+ ...this.attr("SecurityGroupIds", this.props.securityGroupIds),
3669
+ NodeType: "db." + this.props.type,
3670
+ NumReplicasPerShard: this.props.replicasPerShard ?? 1,
3671
+ NumShards: this.props.shards ?? 1,
3672
+ TLSEnabled: this.props.tls ?? false,
3673
+ DataTiering: this.props.dataTiering ? "true" : "false",
3674
+ AutoMinorVersionUpgrade: this.props.autoMinorVersionUpgrade ?? true,
3675
+ MaintenanceWindow: this.props.maintenanceWindow ?? "Sat:02:00-Sat:05:00"
3676
+ };
3677
+ }
3678
+ };
3679
+
3680
+ // src/formation/resource/memorydb/subnet-group.ts
3681
+ var SubnetGroup = class extends Resource {
3682
+ constructor(logicalId, props) {
3683
+ super("AWS::MemoryDB::SubnetGroup", logicalId);
3684
+ this.props = props;
3685
+ this.name = formatName(this.props.name || logicalId);
3686
+ }
3687
+ name;
3688
+ get arn() {
3689
+ return getAtt(this.logicalId, "Arn");
3690
+ }
3691
+ properties() {
3692
+ return {
3693
+ SubnetGroupName: this.name,
3694
+ SubnetIds: this.props.subnetIds,
3695
+ ...this.attr("Description", this.props.description)
3696
+ };
3697
+ }
3698
+ };
3699
+
3700
+ // src/plugins/cache.ts
3701
+ var TypeSchema = z19.enum([
3702
+ "t4g.small",
3703
+ "t4g.medium",
3704
+ "r6g.large",
3705
+ "r6g.xlarge",
3706
+ "r6g.2xlarge",
3707
+ "r6g.4xlarge",
3708
+ "r6g.8xlarge",
3709
+ "r6g.12xlarge",
3710
+ "r6g.16xlarge",
3711
+ "r6gd.xlarge",
3712
+ "r6gd.2xlarge",
3713
+ "r6gd.4xlarge",
3714
+ "r6gd.8xlarge"
3715
+ ]);
3716
+ var PortSchema = z19.number().int().min(1).max(5e4);
3717
+ var ShardsSchema = z19.number().int().min(0).max(100);
3718
+ var ReplicasPerShardSchema = z19.number().int().min(0).max(5);
3719
+ var EngineSchema = z19.enum(["7.0", "6.2"]);
3720
+ var cachePlugin = definePlugin({
3721
+ name: "cache",
3722
+ schema: z19.object({
3723
+ stacks: z19.object({
3724
+ /** Define the caches in your stack.
3725
+ * For access to the cache put your functions inside the global VPC.
3726
+ * @example
3727
+ * {
3728
+ * caches: {
3729
+ * CACHE_NAME: {
3730
+ * type: 't4g.small'
3731
+ * }
3732
+ * }
3733
+ * }
3734
+ */
3735
+ caches: z19.record(
3736
+ ResourceIdSchema,
3737
+ z19.object({
3738
+ type: TypeSchema.default("t4g.small"),
3739
+ port: PortSchema.default(6379),
3740
+ shards: ShardsSchema.default(1),
3741
+ replicasPerShard: ReplicasPerShardSchema.default(1),
3742
+ engine: EngineSchema.default("7.0"),
3743
+ dataTiering: z19.boolean().default(false)
3744
+ })
3745
+ ).optional()
3746
+ }).array()
3747
+ }),
3748
+ onStack({ config, stack, stackConfig, bootstrap: bootstrap2, bind }) {
3749
+ for (const [id, props] of Object.entries(stackConfig.caches || {})) {
3750
+ const name = `${config.name}-${stack.name}-${id}`;
3751
+ const subnetGroup = new SubnetGroup(id, {
3752
+ name,
3753
+ subnetIds: [
3754
+ bootstrap2.import(`private-subnet-1`),
3755
+ bootstrap2.import(`private-subnet-2`)
3756
+ ]
3757
+ });
3758
+ const securityGroup = new SecurityGroup(id, {
3759
+ name,
3760
+ vpcId: bootstrap2.import(`vpc-id`),
3761
+ description: name
3762
+ });
3763
+ const port = Port.tcp(props.port);
3764
+ securityGroup.addIngressRule(Peer.anyIpv4(), port);
3765
+ securityGroup.addIngressRule(Peer.anyIpv6(), port);
3766
+ const cluster = new Cluster(id, {
3767
+ name,
3768
+ aclName: "open-access",
3769
+ securityGroupIds: [securityGroup.id],
3770
+ subnetGroupName: subnetGroup.name,
3771
+ ...props
3772
+ }).dependsOn(subnetGroup, securityGroup);
3773
+ stack.add(subnetGroup, securityGroup, cluster);
3774
+ bind((lambda) => {
3775
+ lambda.addEnvironment(`CACHE_${stack.name}_${id}_HOST`, cluster.address).addEnvironment(`CACHE_${stack.name}_${id}_PORT`, props.port.toString());
3489
3776
  });
3490
3777
  }
3491
3778
  }
@@ -3496,6 +3783,7 @@ var defaultPlugins = [
3496
3783
  extendPlugin,
3497
3784
  vpcPlugin,
3498
3785
  functionPlugin,
3786
+ cachePlugin,
3499
3787
  cronPlugin,
3500
3788
  queuePlugin,
3501
3789
  tablePlugin,
@@ -3677,7 +3965,7 @@ var toApp = async (config, filters) => {
3677
3965
  config.stacks.filter((stack) => filters.includes(stack.name))
3678
3966
  );
3679
3967
  for (const stackConfig of filterdStacks) {
3680
- const { stack } = toStack({
3968
+ const { stack, bindings: bindings2 } = toStack({
3681
3969
  config,
3682
3970
  stackConfig,
3683
3971
  bootstrap: bootstrap2,
@@ -3686,7 +3974,7 @@ var toApp = async (config, filters) => {
3686
3974
  app
3687
3975
  });
3688
3976
  app.add(stack);
3689
- stacks.push({ stack, config: stackConfig });
3977
+ stacks.push({ stack, config: stackConfig, bindings: bindings2 });
3690
3978
  }
3691
3979
  for (const plugin of plugins) {
3692
3980
  for (const stack of app.stacks) {
@@ -3708,23 +3996,31 @@ var toApp = async (config, filters) => {
3708
3996
  bind2(fn);
3709
3997
  }
3710
3998
  }
3711
- let dependencyTree = createDependencyTree(stacks);
3999
+ for (const entry of stacks) {
4000
+ for (const dep of entry.config.depends || []) {
4001
+ const depStack = stacks.find((entry2) => entry2.config.name === dep.name);
4002
+ if (!depStack) {
4003
+ throw new Error(`Stack dependency not found: ${dep.name}`);
4004
+ }
4005
+ const functions2 = entry.stack.find(Function);
4006
+ for (const bind2 of depStack.bindings) {
4007
+ for (const fn of functions2) {
4008
+ bind2(fn);
4009
+ }
4010
+ }
4011
+ }
4012
+ }
4013
+ const deploymentLine = createDeploymentLine(stacks);
3712
4014
  if (bootstrap2.size > 0) {
3713
- dependencyTree = [{
3714
- stack: bootstrap2,
3715
- children: dependencyTree
3716
- }];
4015
+ deploymentLine.unshift([bootstrap2]);
3717
4016
  }
3718
4017
  if (usEastBootstrap.size > 0) {
3719
- dependencyTree = [{
3720
- stack: usEastBootstrap,
3721
- children: dependencyTree
3722
- }];
4018
+ deploymentLine.unshift([usEastBootstrap]);
3723
4019
  }
3724
4020
  return {
3725
4021
  app,
3726
4022
  plugins,
3727
- dependencyTree
4023
+ deploymentLine
3728
4024
  };
3729
4025
  };
3730
4026
 
@@ -3748,17 +4044,17 @@ var getCredentials = (profile) => {
3748
4044
  };
3749
4045
 
3750
4046
  // src/schema/app.ts
3751
- import { z as z22 } from "zod";
4047
+ import { z as z23 } from "zod";
3752
4048
 
3753
4049
  // src/schema/stack.ts
3754
- import { z as z19 } from "zod";
3755
- var StackSchema = z19.object({
4050
+ import { z as z20 } from "zod";
4051
+ var StackSchema = z20.object({
3756
4052
  name: ResourceIdSchema,
3757
- depends: z19.array(z19.lazy(() => StackSchema)).optional()
4053
+ depends: z20.array(z20.lazy(() => StackSchema)).optional()
3758
4054
  });
3759
4055
 
3760
4056
  // src/schema/region.ts
3761
- import { z as z20 } from "zod";
4057
+ import { z as z21 } from "zod";
3762
4058
  var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
3763
4059
  var AF = ["af-south-1"];
3764
4060
  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 +4071,49 @@ var regions = [
3775
4071
  ...ME,
3776
4072
  ...SA
3777
4073
  ];
3778
- var RegionSchema = z20.enum(regions);
4074
+ var RegionSchema = z21.enum(regions);
3779
4075
 
3780
4076
  // 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(),
4077
+ import { z as z22 } from "zod";
4078
+ var PluginSchema = z22.object({
4079
+ name: z22.string(),
4080
+ schema: z22.custom().optional(),
3785
4081
  // 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()
4082
+ onApp: z22.function().returns(z22.void()).optional(),
4083
+ onStack: z22.function().returns(z22.any()).optional(),
4084
+ onResource: z22.function().returns(z22.any()).optional()
3789
4085
  // bind: z.function().optional(),
3790
4086
  });
3791
4087
 
3792
4088
  // src/schema/app.ts
3793
- var AppSchema = z22.object({
4089
+ var AppSchema = z23.object({
3794
4090
  /** App name */
3795
4091
  name: ResourceIdSchema,
3796
4092
  /** The AWS region to deploy to. */
3797
4093
  region: RegionSchema,
3798
4094
  /** The AWS profile to deploy to. */
3799
- profile: z22.string(),
4095
+ profile: z23.string(),
3800
4096
  /** The deployment stage.
3801
4097
  * @default 'prod'
3802
4098
  */
3803
- stage: z22.string().regex(/[a-z]+/).default("prod"),
4099
+ stage: z23.string().regex(/[a-z]+/).default("prod"),
3804
4100
  /** Default properties. */
3805
- defaults: z22.object({}).default({}),
4101
+ defaults: z23.object({}).default({}),
3806
4102
  /** The application stacks. */
3807
- stacks: z22.array(StackSchema).min(1).refine((stacks) => {
4103
+ stacks: z23.array(StackSchema).min(1).refine((stacks) => {
3808
4104
  const unique = new Set(stacks.map((stack) => stack.name));
3809
4105
  return unique.size === stacks.length;
3810
4106
  }, "Must be an array of unique stacks"),
3811
4107
  /** Custom plugins. */
3812
- plugins: z22.array(PluginSchema).optional()
4108
+ plugins: z23.array(PluginSchema).optional()
3813
4109
  });
3814
4110
 
3815
4111
  // src/util/import.ts
3816
- import { transformFile } from "@swc/core";
4112
+ import { rollup as rollup2 } from "rollup";
4113
+ import { swc as swc2 } from "rollup-plugin-swc3";
4114
+ import replace from "rollup-plugin-replace";
3817
4115
  import { dirname, join as join2 } from "path";
3818
- import { lstat as lstat2, mkdir, writeFile } from "fs/promises";
4116
+ import { mkdir, writeFile } from "fs/promises";
3819
4117
 
3820
4118
  // src/util/path.ts
3821
4119
  import { lstat } from "fs/promises";
@@ -3862,54 +4160,25 @@ var fileExist = async (file) => {
3862
4160
  };
3863
4161
 
3864
4162
  // 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
4163
  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);
4164
+ const bundle = await rollup2({
4165
+ input: path,
4166
+ plugins: [
4167
+ replace({
4168
+ __dirname: (id) => `'${dirname(id)}'`
4169
+ }),
4170
+ swc2({
4171
+ minify: false
4172
+ })
4173
+ ]
4174
+ });
3912
4175
  const outputFile = join2(directories.cache, "config.js");
4176
+ const result = await bundle.generate({
4177
+ format: "esm",
4178
+ exports: "default"
4179
+ });
4180
+ const output = result.output[0];
4181
+ const code = output.code;
3913
4182
  await mkdir(directories.cache, { recursive: true });
3914
4183
  await writeFile(outputFile, code);
3915
4184
  debug("Save config file:", style.info(outputFile));
@@ -4211,7 +4480,7 @@ var Renderer = class {
4211
4480
  flushing = false;
4212
4481
  screen = [];
4213
4482
  width() {
4214
- return this.output.columns;
4483
+ return this.output.columns - 1;
4215
4484
  }
4216
4485
  height() {
4217
4486
  return this.output.rows;
@@ -4904,41 +5173,74 @@ var bootstrap = (program2) => {
4904
5173
  });
4905
5174
  };
4906
5175
 
4907
- // src/cli/ui/complex/stack-tree.ts
4908
- var stackTree = (nodes, statuses) => {
5176
+ // src/cli/ui/complex/deployer.ts
5177
+ var stacksDeployer = (deploymentLine) => {
5178
+ const stackNames = deploymentLine.map((line) => line.map((stack) => stack.name)).flat();
5179
+ const stackNameSize = Math.max(...stackNames.map((name) => name.length));
4909
5180
  return (term) => {
4910
- const render = (nodes2, deep = 0, parents = []) => {
4911
- const size = nodes2.length - 1;
4912
- nodes2.forEach((node, i) => {
4913
- const name = node.stack.name;
4914
- const status2 = statuses[name];
4915
- const first = i === 0 && deep === 0;
4916
- const last = i === size;
4917
- const more = i < size;
4918
- const line = flexLine(term, [
4919
- ...parents.map((parent) => {
4920
- return style.label(
4921
- parent ? "\u2502".padEnd(3) : " ".repeat(3)
4922
- );
4923
- }),
4924
- style.label(
4925
- first && size === 0 ? " " : first ? "\u250C\u2500" : last ? "\u2514\u2500" : "\u251C\u2500"
4926
- ),
5181
+ const ui = {};
5182
+ term.out.gap();
5183
+ for (const i in deploymentLine) {
5184
+ const line = flexLine(
5185
+ term,
5186
+ [" "],
5187
+ [
4927
5188
  " ",
4928
- style.info(name),
4929
- " "
4930
- ], [
5189
+ style.placeholder(Number(i) + 1),
5190
+ style.placeholder(" \u2500\u2500")
5191
+ ]
5192
+ );
5193
+ term.out.write(line);
5194
+ term.out.write(br());
5195
+ for (const stack of deploymentLine[i]) {
5196
+ const icon = new Signal(" ");
5197
+ const name = new Signal(style.label.dim(stack.name));
5198
+ const status2 = new Signal(style.info.dim("waiting"));
5199
+ let stopSpinner;
5200
+ term.out.write([
5201
+ icon,
5202
+ " ",
5203
+ name,
5204
+ " ".repeat(stackNameSize - stack.name.length),
5205
+ " ",
5206
+ style.placeholder(symbol.pointerSmall),
4931
5207
  " ",
4932
5208
  status2,
4933
5209
  br()
4934
5210
  ]);
4935
- term.out.write(line);
4936
- render(node.children, deep + 1, [...parents, more]);
4937
- });
4938
- };
4939
- term.out.gap();
4940
- render(nodes);
5211
+ ui[stack.name] = {
5212
+ start: (value) => {
5213
+ const [spinner, stop] = createSpinner();
5214
+ name.set(style.label(stack.name));
5215
+ icon.set(spinner);
5216
+ status2.set(style.warning(value));
5217
+ stopSpinner = stop;
5218
+ },
5219
+ done(value) {
5220
+ stopSpinner();
5221
+ icon.set(style.success(symbol.success));
5222
+ status2.set(style.success(value));
5223
+ },
5224
+ fail(value) {
5225
+ stopSpinner();
5226
+ icon.set(style.error(symbol.error));
5227
+ status2.set(style.error(value));
5228
+ },
5229
+ warn(value) {
5230
+ stopSpinner();
5231
+ icon.set(style.warning(symbol.warning));
5232
+ status2.set(style.warning(value));
5233
+ }
5234
+ };
5235
+ }
5236
+ }
5237
+ term.out.write(flexLine(term, [" "], [
5238
+ " ",
5239
+ style.warning("\u26A1\uFE0F"),
5240
+ style.placeholder("\u2500\u2500")
5241
+ ]));
4941
5242
  term.out.gap();
5243
+ return ui;
4942
5244
  };
4943
5245
  };
4944
5246
 
@@ -4946,31 +5248,27 @@ var stackTree = (nodes, statuses) => {
4946
5248
  var status = (program2) => {
4947
5249
  program2.command("status").argument("[stacks...]", "Optionally filter stacks to lookup status").description("View the application status").action(async (filters) => {
4948
5250
  await layout(async (config, write) => {
4949
- const { app, dependencyTree } = await toApp(config, filters);
5251
+ const { app, deploymentLine } = await toApp(config, filters);
4950
5252
  await cleanUp();
4951
5253
  await write(assetBuilder(app));
4952
5254
  await write(templateBuilder(app));
4953
5255
  const doneLoading = write(loadingDialog("Loading stack information..."));
4954
5256
  const client = new StackClient(app, config.account, config.region, config.credentials);
4955
5257
  const statuses = [];
4956
- const stackStatuses = {};
4957
- for (const stack of app) {
4958
- stackStatuses[stack.name] = new Signal(style.info("Loading..."));
4959
- }
4960
- write(stackTree(dependencyTree, stackStatuses));
5258
+ const ui = write(stacksDeployer(deploymentLine));
4961
5259
  debug("Load metadata for all deployed stacks on AWS");
4962
5260
  await Promise.all(app.stacks.map(async (stack, i) => {
5261
+ const item = ui[stack.name];
5262
+ item.start("loading");
4963
5263
  const info = await client.get(stack.name, stack.region);
4964
- const signal = stackStatuses[stack.name];
4965
- await new Promise((resolve) => setTimeout(resolve, i * 1e3));
4966
5264
  if (!info) {
4967
- signal.set(style.error("non-existent"));
5265
+ item.fail("NON EXISTENT");
4968
5266
  statuses.push("non-existent");
4969
5267
  } else if (info.template !== stack.toString()) {
4970
- signal.set(style.warning("out-of-date"));
5268
+ item.warn("OUT OF DATE");
4971
5269
  statuses.push("out-of-date");
4972
5270
  } else {
4973
- signal.set(style.success("up-to-date"));
5271
+ item.done("UP TO DATE");
4974
5272
  statuses.push("up-to-date");
4975
5273
  }
4976
5274
  }));
@@ -5054,7 +5352,7 @@ var deploy = (program2) => {
5054
5352
  program2.command("deploy").argument("[stacks...]", "Optionally filter stacks to deploy").description("Deploy your app to AWS").action(async (filters) => {
5055
5353
  await layout(async (config, write) => {
5056
5354
  await write(bootstrapDeployer(config));
5057
- const { app, dependencyTree } = await toApp(config, filters);
5355
+ const { app, deploymentLine } = await toApp(config, filters);
5058
5356
  const stackNames = app.stacks.map((stack) => stack.name);
5059
5357
  const formattedFilter = stackNames.map((i) => style.info(i)).join(style.placeholder(", "));
5060
5358
  debug("Stacks to deploy", formattedFilter);
@@ -5068,26 +5366,21 @@ var deploy = (program2) => {
5068
5366
  await write(assetBuilder(app));
5069
5367
  await write(assetPublisher(config, app));
5070
5368
  await write(templateBuilder(app));
5071
- const statuses = {};
5072
- for (const stack of app) {
5073
- statuses[stack.name] = new Signal(style.info("waiting"));
5074
- }
5075
5369
  const doneDeploying = write(loadingDialog("Deploying stacks to AWS..."));
5076
- write(stackTree(dependencyTree, statuses));
5077
5370
  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"));
5371
+ const ui = write(stacksDeployer(deploymentLine));
5372
+ for (const line of deploymentLine) {
5373
+ const results = await Promise.allSettled(line.map(async (stack) => {
5374
+ const item = ui[stack.name];
5375
+ item.start("deploying");
5083
5376
  try {
5084
5377
  await client.deploy(stack);
5085
5378
  } catch (error) {
5086
5379
  debugError(error);
5087
- signal.set(style.error("failed"));
5380
+ item.fail("failed");
5088
5381
  throw error;
5089
5382
  }
5090
- signal.set(style.success("deployed"));
5383
+ item.done("deployed");
5091
5384
  }));
5092
5385
  for (const result of results) {
5093
5386
  if (result.status === "rejected") {