@awsless/awsless 0.0.43 → 0.0.45

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
@@ -54,90 +54,6 @@ var flushDebug = () => {
54
54
  return queue.splice(0, queue.length);
55
55
  };
56
56
 
57
- // src/util/param.ts
58
- import { DeleteParameterCommand, GetParameterCommand, GetParametersByPathCommand, ParameterType, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
59
- var configParameterPrefix = (config) => {
60
- return `/.awsless/${config.name}`;
61
- };
62
- var Params = class {
63
- constructor(config) {
64
- this.config = config;
65
- this.client = new SSMClient({
66
- credentials: config.credentials,
67
- region: config.region
68
- });
69
- }
70
- client;
71
- getName(name) {
72
- return `${configParameterPrefix(this.config)}/${name}`;
73
- }
74
- async get(name) {
75
- debug("Get remote config value");
76
- debug("Name:", style.info(name));
77
- let result;
78
- try {
79
- result = await this.client.send(new GetParameterCommand({
80
- Name: this.getName(name),
81
- WithDecryption: true
82
- }));
83
- } catch (error) {
84
- if (error instanceof Error && error.name === "ParameterNotFound") {
85
- debug("Parameter not found");
86
- return;
87
- }
88
- throw error;
89
- }
90
- const value = result.Parameter?.Value;
91
- debug("Value:", style.info(value));
92
- debug("Done getting remote config value");
93
- return value;
94
- }
95
- async set(name, value) {
96
- debug("Save remote config value");
97
- debug("Name:", style.info(name));
98
- debug("Value:", style.info(value));
99
- await this.client.send(new PutParameterCommand({
100
- Type: ParameterType.STRING,
101
- Name: this.getName(name),
102
- Value: value,
103
- Overwrite: true
104
- }));
105
- debug("Done saving remote config value");
106
- }
107
- async delete(name) {
108
- debug("Delete remote config value");
109
- debug("Name:", style.info(name));
110
- try {
111
- await this.client.send(new DeleteParameterCommand({
112
- Name: this.getName(name)
113
- }));
114
- } catch (error) {
115
- if (error instanceof Error && error.name === "ParameterNotFound") {
116
- debug("Remote config value was already deleted");
117
- return;
118
- }
119
- throw error;
120
- }
121
- debug("Done deleting remote config value");
122
- }
123
- async list() {
124
- debug("Load remote config values");
125
- const result = await this.client.send(new GetParametersByPathCommand({
126
- Path: configParameterPrefix(this.config),
127
- WithDecryption: true,
128
- MaxResults: 10,
129
- Recursive: true
130
- }));
131
- debug("Done loading remote config values");
132
- const values = {};
133
- result.Parameters?.forEach((param) => {
134
- const name = param.Name.substring(configParameterPrefix(this.config).length).substring(1);
135
- values[name] = param.Value || "";
136
- });
137
- return values;
138
- }
139
- };
140
-
141
57
  // src/formation/asset.ts
142
58
  import { paramCase } from "change-case";
143
59
  var Asset = class {
@@ -159,13 +75,20 @@ var sub = (value, params) => {
159
75
  }
160
76
  return { "Fn::Sub": value };
161
77
  };
78
+ var split = (seperator, value) => {
79
+ return { "Fn::Split": [seperator, value] };
80
+ };
81
+ var select = (index, value) => {
82
+ return { "Fn::Select": [index, value] };
83
+ };
162
84
  var getAtt = (logicalId, attr) => {
163
85
  return { "Fn::GetAtt": [logicalId, attr] };
164
86
  };
87
+ var lazy = (cb) => {
88
+ return new Lazy(cb);
89
+ };
165
90
  var importValue = (name) => {
166
- return new Lazy((stack) => ({
167
- "Fn::ImportValue": `${stack.app.name}-${name}`
168
- }));
91
+ return { "Fn::ImportValue": name };
169
92
  };
170
93
  var formatLogicalId = (id) => {
171
94
  return pascalCase(id).replaceAll("_", "");
@@ -191,6 +114,10 @@ var Resource = class {
191
114
  tags = /* @__PURE__ */ new Map();
192
115
  deps = /* @__PURE__ */ new Set();
193
116
  stack;
117
+ addChild(...children) {
118
+ this.children.push(...children);
119
+ return this;
120
+ }
194
121
  dependsOn(...dependencies) {
195
122
  for (const dependency of dependencies) {
196
123
  this.deps.add(dependency);
@@ -259,13 +186,33 @@ var Lazy = class {
259
186
  }
260
187
  };
261
188
 
189
+ // src/formation/resource/cloud-watch/log-group.ts
190
+ var LogGroup = class extends Resource {
191
+ constructor(logicalId, props) {
192
+ super("AWS::Logs::LogGroup", logicalId);
193
+ this.props = props;
194
+ }
195
+ get arn() {
196
+ return getAtt(this.logicalId, "Arn");
197
+ }
198
+ properties() {
199
+ return {
200
+ LogGroupName: this.props.name,
201
+ ...this.attr("RetentionInDays", this.props.retention?.toDays())
202
+ // KmsKeyId: String
203
+ // DataProtectionPolicy : Json,
204
+ };
205
+ }
206
+ };
207
+
262
208
  // src/formation/resource/iam/inline-policy.ts
263
209
  var InlinePolicy = class {
210
+ name;
211
+ statements;
264
212
  constructor(name, props = {}) {
265
- this.name = name;
266
213
  this.statements = props.statements || [];
214
+ this.name = formatName(name);
267
215
  }
268
- statements;
269
216
  addStatement(...statements) {
270
217
  this.statements.push(...statements.flat());
271
218
  return this;
@@ -285,28 +232,12 @@ var InlinePolicy = class {
285
232
  }
286
233
  };
287
234
 
288
- // src/formation/resource/iam/managed-policy.ts
289
- var ManagedPolicy = class {
290
- constructor(arn) {
291
- this.arn = arn;
292
- }
293
- static fromAwsManagedPolicyName(name) {
294
- const arn = sub("arn:${AWS::Partition}:iam::aws:policy/service-role/" + name);
295
- return new ManagedPolicy(arn);
296
- }
297
- static fromManagedPolicyArn(arn) {
298
- return new ManagedPolicy(arn);
299
- }
300
- };
301
-
302
235
  // src/formation/resource/iam/role.ts
303
236
  var Role = class extends Resource {
304
237
  constructor(logicalId, props = {}) {
305
238
  super("AWS::IAM::Role", logicalId);
306
239
  this.props = props;
307
- this.name = formatName(logicalId);
308
240
  }
309
- name;
310
241
  inlinePolicies = /* @__PURE__ */ new Set();
311
242
  managedPolicies = /* @__PURE__ */ new Set();
312
243
  get arn() {
@@ -344,16 +275,44 @@ var Role = class extends Resource {
344
275
  }
345
276
  };
346
277
 
278
+ // src/formation/resource/lambda/url.ts
279
+ import { constantCase } from "change-case";
280
+ var Url = class extends Resource {
281
+ constructor(logicalId, props) {
282
+ super("AWS::Lambda::Url", logicalId);
283
+ this.props = props;
284
+ }
285
+ get url() {
286
+ return getAtt(this.logicalId, "FunctionUrl");
287
+ }
288
+ properties() {
289
+ return {
290
+ AuthType: constantCase(this.props.authType ?? "none"),
291
+ InvokeMode: constantCase(this.props.invokeMode ?? "buffered"),
292
+ TargetFunctionArn: this.props.target,
293
+ ...this.attr("Qualifier", this.props.qualifier),
294
+ Cors: {
295
+ ...this.attr("AllowCredentials", this.props.cors?.allow?.credentials),
296
+ ...this.attr("AllowHeaders", this.props.cors?.allow?.headers),
297
+ ...this.attr("AllowMethods", this.props.cors?.allow?.methods),
298
+ ...this.attr("AllowOrigins", this.props.cors?.allow?.origins),
299
+ ...this.attr("ExposeHeaders", this.props.cors?.expose?.headers),
300
+ ...this.attr("MaxAge", this.props.cors?.maxAge?.toSeconds())
301
+ }
302
+ };
303
+ }
304
+ };
305
+
347
306
  // src/formation/resource/lambda/function.ts
348
307
  var Function = class extends Resource {
349
- constructor(logicalId, props) {
350
- const policy = new InlinePolicy(logicalId);
351
- const role = new Role(logicalId, {
308
+ constructor(_logicalId, props) {
309
+ const policy = new InlinePolicy(_logicalId);
310
+ const role = new Role(_logicalId, {
352
311
  assumedBy: "lambda.amazonaws.com"
353
312
  });
354
313
  role.addInlinePolicy(policy);
355
- role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AWSLambdaBasicExecutionRole"));
356
- super("AWS::Lambda::Function", logicalId, [role]);
314
+ super("AWS::Lambda::Function", _logicalId, [role]);
315
+ this._logicalId = _logicalId;
357
316
  this.props = props;
358
317
  if (props.code instanceof Asset) {
359
318
  this.children.push(props.code);
@@ -361,7 +320,7 @@ var Function = class extends Resource {
361
320
  this.dependsOn(role);
362
321
  this.role = role;
363
322
  this.policy = policy;
364
- this.name = formatName(this.props.name || logicalId);
323
+ this.name = formatName(this.props.name || _logicalId);
365
324
  this.environmentVariables = props.environment ? { ...props.environment } : {};
366
325
  this.tag("name", this.name);
367
326
  }
@@ -369,6 +328,29 @@ var Function = class extends Resource {
369
328
  role;
370
329
  policy;
371
330
  environmentVariables;
331
+ enableLogs(retention) {
332
+ const logGroup = new LogGroup(this._logicalId, {
333
+ name: sub("/aws/lambda/${name}", {
334
+ name: this.name
335
+ }),
336
+ retention
337
+ });
338
+ this.addChild(logGroup);
339
+ this.addPermissions({
340
+ actions: ["logs:CreateLogStream"],
341
+ resources: [logGroup.arn]
342
+ }, {
343
+ actions: ["logs:PutLogEvents"],
344
+ resources: [sub("${arn}:*", { arn: logGroup.arn })]
345
+ });
346
+ return this;
347
+ }
348
+ addUrl(props = {}) {
349
+ return new Url(this._logicalId, {
350
+ ...props,
351
+ target: this.arn
352
+ }).dependsOn(this);
353
+ }
372
354
  addPermissions(...permissions) {
373
355
  this.policy.addStatement(...permissions);
374
356
  return this;
@@ -477,7 +459,12 @@ var Stack = class {
477
459
  if (!this.exports.has(name)) {
478
460
  throw new Error(`Undefined export value: ${name}`);
479
461
  }
480
- return importValue(name);
462
+ return lazy((stack) => {
463
+ if (stack === this) {
464
+ return this.exports.get(name);
465
+ }
466
+ return importValue(`${stack.app?.name ?? "default"}-${name}`);
467
+ });
481
468
  }
482
469
  tag(name, value) {
483
470
  this.tags.set(name, value);
@@ -498,32 +485,33 @@ var Stack = class {
498
485
  toJSON() {
499
486
  const resources = {};
500
487
  const outputs = {};
501
- const walk = (object) => {
502
- for (const [key, value] of Object.entries(object)) {
503
- if (!object.hasOwnProperty(key)) {
504
- continue;
505
- }
506
- if (value instanceof Lazy) {
507
- object[key] = value.callback(this);
508
- continue;
509
- }
510
- if (typeof value === "object" && value !== null) {
511
- walk(value);
488
+ const walk = (node) => {
489
+ if (node instanceof Lazy) {
490
+ return walk(node.callback(this));
491
+ }
492
+ if (Array.isArray(node)) {
493
+ return node.map(walk);
494
+ }
495
+ if (typeof node === "object" && node !== null) {
496
+ const object = {};
497
+ for (const [key, value] of Object.entries(node)) {
498
+ object[key] = walk(value);
512
499
  }
500
+ return object;
513
501
  }
502
+ return node;
514
503
  };
515
504
  for (const resource of this.resources) {
516
- const json2 = resource.toJSON();
517
- walk(json2);
505
+ const json2 = walk(resource.toJSON());
518
506
  Object.assign(resources, json2);
519
507
  }
520
- for (const [name, value] of this.exports.entries()) {
508
+ for (let [name, value] of this.exports.entries()) {
521
509
  Object.assign(outputs, {
522
510
  [formatLogicalId(name)]: {
523
511
  Export: {
524
- Name: `${this.app?.name || "default"}-${name}`
512
+ Name: `${this.app?.name ?? "default"}-${name}`
525
513
  },
526
- Value: value
514
+ Value: walk(value)
527
515
  }
528
516
  });
529
517
  }
@@ -571,18 +559,6 @@ var toStack = ({ config, app, stackConfig, bootstrap: bootstrap2, usEastBootstra
571
559
  bind2(fn);
572
560
  }
573
561
  }
574
- for (const fn of functions) {
575
- fn.addPermissions({
576
- actions: [
577
- "ssm:GetParameter",
578
- "ssm:GetParameters",
579
- "ssm:GetParametersByPath"
580
- ],
581
- resources: [
582
- sub("arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter" + configParameterPrefix(config))
583
- ]
584
- });
585
- }
586
562
  return {
587
563
  stack,
588
564
  bindings
@@ -735,21 +711,21 @@ var durationMax = (max) => {
735
711
  };
736
712
 
737
713
  // src/schema/local-file.ts
738
- import { access, constants } from "fs/promises";
714
+ import { stat } from "fs/promises";
739
715
  import { z as z3 } from "zod";
740
716
  var LocalFileSchema = z3.string().refine(async (path) => {
741
717
  try {
742
- await access(path, constants.R_OK);
718
+ const s = await stat(path);
719
+ return s.isFile();
743
720
  } catch (error) {
744
721
  return false;
745
722
  }
746
- return true;
747
723
  }, `File doesn't exist`);
748
724
 
749
725
  // src/schema/resource-id.ts
750
726
  import { paramCase as paramCase3 } from "change-case";
751
727
  import { z as z4 } from "zod";
752
- var ResourceIdSchema = z4.string().min(3).max(24).regex(/^[a-z\-]+$/i, "Invalid resource ID").transform((value) => paramCase3(value));
728
+ var ResourceIdSchema = z4.string().min(3).max(24).regex(/^[a-z0-9\-]+$/i, "Invalid resource ID").transform((value) => paramCase3(value));
753
729
 
754
730
  // src/schema/size.ts
755
731
  import { z as z5 } from "zod";
@@ -827,49 +803,54 @@ import json from "@rollup/plugin-json";
827
803
  import commonjs from "@rollup/plugin-commonjs";
828
804
  import nodeResolve from "@rollup/plugin-node-resolve";
829
805
  import { dirname } from "path";
830
- var rollupBundle = async (input) => {
831
- const bundle = await rollup({
832
- input,
833
- external: (importee) => {
834
- return importee.startsWith("@aws-sdk") || importee.startsWith("aws-sdk");
835
- },
836
- onwarn: (error) => {
837
- debugError(error.message);
838
- },
839
- treeshake: {
840
- moduleSideEffects: (id) => input === id
841
- },
842
- plugins: [
843
- commonjs({ sourceMap: true }),
844
- nodeResolve({ preferBuiltins: true }),
845
- swc({
846
- minify: true,
847
- jsc: {
848
- baseUrl: dirname(input),
849
- minify: { sourceMap: true }
850
- },
851
- sourceMaps: true
852
- }),
853
- json()
854
- ]
855
- });
856
- const result = await bundle.generate({
857
- format: "esm",
858
- sourcemap: "hidden",
859
- exports: "default"
860
- });
861
- const output = result.output[0];
862
- const code = output.code;
863
- const map = output.map?.toString();
864
- const hash = createHash("sha1").update(code).digest("hex");
865
- return {
866
- handler: "index.default",
867
- hash,
868
- files: [{
869
- name: "index.mjs",
870
- code,
871
- map
872
- }]
806
+ var rollupBundle = ({ format: format2 = "esm", minify = true, handler = "index.default" } = {}) => {
807
+ return async (input) => {
808
+ const bundle = await rollup({
809
+ input,
810
+ external: (importee) => {
811
+ return importee.startsWith("@aws-sdk") || importee.startsWith("aws-sdk");
812
+ },
813
+ onwarn: (error) => {
814
+ debugError(error.message);
815
+ },
816
+ treeshake: {
817
+ moduleSideEffects: (id) => input === id
818
+ },
819
+ plugins: [
820
+ // @ts-ignore
821
+ commonjs({ sourceMap: true }),
822
+ // @ts-ignore
823
+ nodeResolve({ preferBuiltins: true }),
824
+ swc({
825
+ minify,
826
+ jsc: {
827
+ baseUrl: dirname(input),
828
+ minify: { sourceMap: true }
829
+ },
830
+ sourceMaps: true
831
+ }),
832
+ // @ts-ignore
833
+ json()
834
+ ]
835
+ });
836
+ const result = await bundle.generate({
837
+ format: format2,
838
+ sourcemap: "hidden",
839
+ exports: "auto"
840
+ });
841
+ const output = result.output[0];
842
+ const code = Buffer.from(output.code, "utf8");
843
+ const map = output.map ? Buffer.from(output.map.toString(), "utf8") : void 0;
844
+ const hash = createHash("sha1").update(code).digest("hex");
845
+ return {
846
+ handler,
847
+ hash,
848
+ files: [{
849
+ name: format2 === "esm" ? "index.mjs" : "index.js",
850
+ code,
851
+ map
852
+ }]
853
+ };
873
854
  };
874
855
  };
875
856
 
@@ -890,6 +871,8 @@ var zipFiles = (files) => {
890
871
  };
891
872
 
892
873
  // src/formation/resource/lambda/code.ts
874
+ import { fileURLToPath } from "url";
875
+ import { join } from "path";
893
876
  var Code = class {
894
877
  static fromFile(id, file, bundler) {
895
878
  return new FileCode(id, file, bundler);
@@ -897,6 +880,29 @@ var Code = class {
897
880
  static fromInline(code, handler) {
898
881
  return new InlineCode(code, handler);
899
882
  }
883
+ static fromInlineFile(id, file, bundler) {
884
+ return new InlineFileCode(id, file, bundler);
885
+ }
886
+ // static fromZipFile(id:string, file:string, hash:string, handler?:string) {
887
+ // return new ZipFileCode(id, file, hash, handler)
888
+ // }
889
+ static fromFeature(id) {
890
+ const root2 = fileURLToPath(new URL(".", import.meta.url));
891
+ const file = join(root2, `features/${id}.js`);
892
+ return new FileCode(id, file, rollupBundle({
893
+ minify: false,
894
+ handler: "index.handler"
895
+ }));
896
+ }
897
+ static fromInlineFeature(id) {
898
+ const root2 = fileURLToPath(new URL(".", import.meta.url));
899
+ const file = join(root2, `features/${id}.js`);
900
+ return new InlineFileCode(id, file, rollupBundle({
901
+ format: "cjs",
902
+ minify: false,
903
+ handler: "index.handler"
904
+ }));
905
+ }
900
906
  };
901
907
  var InlineCode = class {
902
908
  constructor(code, handler = "index.default") {
@@ -912,6 +918,36 @@ var InlineCode = class {
912
918
  };
913
919
  }
914
920
  };
921
+ var InlineFileCode = class extends Asset {
922
+ constructor(id, file, bundler) {
923
+ super("function", id);
924
+ this.file = file;
925
+ this.bundler = bundler;
926
+ }
927
+ code;
928
+ handler;
929
+ async build({ write }) {
930
+ const bundler = this.bundler ?? rollupBundle();
931
+ const { hash, files: [file], handler } = await bundler(this.file);
932
+ await Promise.all([
933
+ write("HASH", hash),
934
+ write("file.js", file.code)
935
+ ]);
936
+ this.handler = handler;
937
+ this.code = file.code.toString("utf8");
938
+ return {
939
+ size: formatByteSize(file.code.byteLength)
940
+ };
941
+ }
942
+ toCodeJson() {
943
+ return {
944
+ Handler: this.handler ?? "",
945
+ Code: {
946
+ ZipFile: this.code ?? ""
947
+ }
948
+ };
949
+ }
950
+ };
915
951
  var FileCode = class extends Asset {
916
952
  constructor(id, file, bundler) {
917
953
  super("function", id);
@@ -923,7 +959,7 @@ var FileCode = class extends Asset {
923
959
  bundle;
924
960
  s3;
925
961
  async build({ write }) {
926
- const bundler = this.bundler ?? rollupBundle;
962
+ const bundler = this.bundler ?? rollupBundle();
927
963
  const { hash, files, handler } = await bundler(this.file);
928
964
  const bundle = await zipFiles(files);
929
965
  await Promise.all([
@@ -1012,24 +1048,24 @@ import { camelCase as camelCase2 } from "change-case";
1012
1048
 
1013
1049
  // src/util/path.ts
1014
1050
  import { lstat } from "fs/promises";
1015
- import { join, normalize } from "path";
1051
+ import { join as join2, normalize } from "path";
1016
1052
  var root = process.cwd();
1017
1053
  var directories = {
1018
1054
  root,
1019
1055
  get output() {
1020
- return join(this.root, ".awsless");
1056
+ return join2(this.root, ".awsless");
1021
1057
  },
1022
1058
  get cache() {
1023
- return join(this.output, "cache");
1059
+ return join2(this.output, "cache");
1024
1060
  },
1025
1061
  get asset() {
1026
- return join(this.output, "asset");
1062
+ return join2(this.output, "asset");
1027
1063
  },
1028
1064
  get types() {
1029
- return join(this.output, "types");
1065
+ return join2(this.output, "types");
1030
1066
  },
1031
1067
  get template() {
1032
- return join(this.output, "template");
1068
+ return join2(this.output, "template");
1033
1069
  }
1034
1070
  };
1035
1071
  var setRoot = (path = root) => {
@@ -1039,17 +1075,17 @@ var findRootDir = async (path, configFile, level = 5) => {
1039
1075
  if (!level) {
1040
1076
  throw new TypeError("No awsless project found");
1041
1077
  }
1042
- const file = join(path, configFile);
1078
+ const file = join2(path, configFile);
1043
1079
  const exists = await fileExist(file);
1044
1080
  if (exists) {
1045
1081
  return path;
1046
1082
  }
1047
- return findRootDir(normalize(join(path, "..")), configFile, level - 1);
1083
+ return findRootDir(normalize(join2(path, "..")), configFile, level - 1);
1048
1084
  };
1049
1085
  var fileExist = async (file) => {
1050
1086
  try {
1051
- const stat = await lstat(file);
1052
- if (stat.isFile()) {
1087
+ const stat3 = await lstat(file);
1088
+ if (stat3.isFile()) {
1053
1089
  return true;
1054
1090
  }
1055
1091
  } catch (error) {
@@ -1062,8 +1098,8 @@ import { relative as relative2 } from "path";
1062
1098
 
1063
1099
  // src/util/type-gen.ts
1064
1100
  import { mkdir, writeFile } from "fs/promises";
1065
- import { join as join2, relative } from "path";
1066
- import { camelCase } from "change-case";
1101
+ import { join as join3, relative } from "path";
1102
+ import { camelCase, constantCase as constantCase2 } from "change-case";
1067
1103
  var generateResourceTypes = async (config) => {
1068
1104
  const plugins = [
1069
1105
  ...defaultPlugins,
@@ -1073,7 +1109,7 @@ var generateResourceTypes = async (config) => {
1073
1109
  for (const plugin of plugins) {
1074
1110
  const code = plugin.onTypeGen?.({ config });
1075
1111
  if (code) {
1076
- const file = join2(directories.types, `${plugin.name}.d.ts`);
1112
+ const file = join3(directories.types, `${plugin.name}.d.ts`);
1077
1113
  files.push(relative(directories.root, file));
1078
1114
  await mkdir(directories.types, { recursive: true });
1079
1115
  await writeFile(file, code);
@@ -1081,13 +1117,14 @@ var generateResourceTypes = async (config) => {
1081
1117
  }
1082
1118
  if (files.length) {
1083
1119
  const code = files.map((file) => `/// <reference path='${file}' />`).join("\n");
1084
- await writeFile(join2(directories.root, `awsless.d.ts`), code);
1120
+ await writeFile(join3(directories.root, `awsless.d.ts`), code);
1085
1121
  }
1086
1122
  };
1087
1123
  var TypeGen = class {
1088
- constructor(module, interfaceName) {
1124
+ constructor(module, interfaceName, readonly = true) {
1089
1125
  this.module = module;
1090
1126
  this.interfaceName = interfaceName;
1127
+ this.readonly = readonly;
1091
1128
  }
1092
1129
  codes = /* @__PURE__ */ new Set();
1093
1130
  types = /* @__PURE__ */ new Map();
@@ -1102,7 +1139,13 @@ var TypeGen = class {
1102
1139
  }
1103
1140
  addType(name, type) {
1104
1141
  if (type) {
1105
- this.types.set(name, type);
1142
+ this.types.set(camelCase(name), type);
1143
+ }
1144
+ return this;
1145
+ }
1146
+ addConst(name, type) {
1147
+ if (type) {
1148
+ this.types.set(constantCase2(name), type);
1106
1149
  }
1107
1150
  return this;
1108
1151
  }
@@ -1133,7 +1176,7 @@ var TypeGen = class {
1133
1176
  `declare module '${this.module}' {`,
1134
1177
  ` interface ${this.interfaceName} {`,
1135
1178
  ...Array.from(this.types.entries()).map(([propName, type]) => {
1136
- return ` readonly ${camelCase(propName)}: ${type}`;
1179
+ return ` ${this.readonly ? "readonly " : ""}${propName}: ${type}`;
1137
1180
  }),
1138
1181
  ` }`,
1139
1182
  `}`,
@@ -1146,7 +1189,15 @@ var TypeGen = class {
1146
1189
  var TypeObject = class {
1147
1190
  types = /* @__PURE__ */ new Map();
1148
1191
  addType(name, type) {
1149
- this.types.set(name, type);
1192
+ if (type) {
1193
+ this.types.set(camelCase(name), type);
1194
+ }
1195
+ return this;
1196
+ }
1197
+ addConst(name, type) {
1198
+ if (type) {
1199
+ this.types.set(constantCase2(name), type);
1200
+ }
1150
1201
  return this;
1151
1202
  }
1152
1203
  toString() {
@@ -1156,7 +1207,7 @@ var TypeObject = class {
1156
1207
  return [
1157
1208
  "{",
1158
1209
  ...Array.from(this.types.entries()).map(([propName, type]) => {
1159
- return ` readonly ${camelCase(propName)}: ${type}`;
1210
+ return ` readonly ${propName}: ${type}`;
1160
1211
  }),
1161
1212
  " }"
1162
1213
  ].join("\n");
@@ -1172,9 +1223,12 @@ var EnvironmentSchema = z6.record(z6.string(), z6.string()).optional();
1172
1223
  var ArchitectureSchema = z6.enum(["x86_64", "arm64"]);
1173
1224
  var RetryAttemptsSchema = z6.number().int().min(0).max(2);
1174
1225
  var RuntimeSchema = z6.enum([
1175
- "nodejs16.x",
1176
1226
  "nodejs18.x"
1177
1227
  ]);
1228
+ var LogSchema = z6.union([
1229
+ z6.boolean(),
1230
+ DurationSchema.refine(durationMin(Duration.days(1)), "Minimum log retention is 1 day")
1231
+ ]);
1178
1232
  var FunctionSchema = z6.union([
1179
1233
  LocalFileSchema,
1180
1234
  z6.object({
@@ -1184,6 +1238,11 @@ var FunctionSchema = z6.union([
1184
1238
  * @default false
1185
1239
  */
1186
1240
  vpc: z6.boolean().optional(),
1241
+ /** Enable logging to a CloudWatch log group.
1242
+ * Providing a duration value will set the log retention time.
1243
+ * @default false
1244
+ */
1245
+ log: LogSchema.optional(),
1187
1246
  /** The amount of time that Lambda allows a function to run before stopping it.
1188
1247
  * You can specify a size value from 1 second to 15 minutes.
1189
1248
  * @default '10 seconds'
@@ -1230,6 +1289,9 @@ var FunctionSchema = z6.union([
1230
1289
  // onFailure: ResourceIdSchema.optional(),
1231
1290
  })
1232
1291
  ]);
1292
+ var isFunctionProps = (input) => {
1293
+ return typeof input === "string" || typeof input.file === "string";
1294
+ };
1233
1295
  var schema = z6.object({
1234
1296
  defaults: z6.object({
1235
1297
  function: z6.object({
@@ -1237,6 +1299,10 @@ var schema = z6.object({
1237
1299
  * @default false
1238
1300
  */
1239
1301
  vpc: z6.boolean().default(false),
1302
+ /** Enable logging to a CloudWatch log group.
1303
+ * @default false
1304
+ */
1305
+ log: LogSchema.default(false),
1240
1306
  /** The amount of time that Lambda allows a function to run before stopping it.
1241
1307
  * You can specify a size value from 1 second to 15 minutes.
1242
1308
  * @default '10 seconds'
@@ -1349,22 +1415,26 @@ var functionPlugin = definePlugin({
1349
1415
  var toLambdaFunction = (ctx, id, fileOrProps) => {
1350
1416
  const config = ctx.config;
1351
1417
  const stack = ctx.stack;
1418
+ const bootstrap2 = ctx.bootstrap;
1352
1419
  const props = typeof fileOrProps === "string" ? { ...config.defaults?.function, file: fileOrProps } : { ...config.defaults?.function, ...fileOrProps };
1353
1420
  const lambda = new Function(id, {
1354
- name: `${config.name}--${stack.name}--${id}`,
1421
+ name: `${config.name}-${stack.name}-${id}`,
1355
1422
  code: Code.fromFile(id, props.file),
1356
1423
  ...props,
1357
1424
  vpc: void 0
1358
1425
  });
1359
1426
  lambda.addEnvironment("APP", config.name).addEnvironment("STAGE", config.stage).addEnvironment("STACK", stack.name);
1427
+ if (props.log) {
1428
+ lambda.enableLogs(props.log instanceof Duration ? props.log : void 0);
1429
+ }
1360
1430
  if (props.vpc) {
1361
1431
  lambda.setVpc({
1362
1432
  securityGroupIds: [
1363
- ctx.bootstrap.import(`vpc-security-group-id`)
1433
+ bootstrap2.import(`vpc-security-group-id`)
1364
1434
  ],
1365
1435
  subnetIds: [
1366
- ctx.bootstrap.import(`public-subnet-1`),
1367
- ctx.bootstrap.import(`public-subnet-2`)
1436
+ bootstrap2.import(`public-subnet-1`),
1437
+ bootstrap2.import(`public-subnet-2`)
1368
1438
  ]
1369
1439
  }).addPermissions({
1370
1440
  actions: [
@@ -1377,9 +1447,6 @@ var toLambdaFunction = (ctx, id, fileOrProps) => {
1377
1447
  resources: ["*"]
1378
1448
  });
1379
1449
  }
1380
- if (props.runtime.startsWith("nodejs")) {
1381
- lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1");
1382
- }
1383
1450
  ctx.bind((other) => {
1384
1451
  other.addPermissions(lambda.permissions);
1385
1452
  });
@@ -1419,6 +1486,7 @@ var Rule = class extends Resource {
1419
1486
  };
1420
1487
 
1421
1488
  // src/formation/resource/lambda/permission.ts
1489
+ import { constantCase as constantCase3 } from "change-case";
1422
1490
  var Permission2 = class extends Resource {
1423
1491
  constructor(logicalId, props) {
1424
1492
  super("AWS::Lambda::Permission", logicalId);
@@ -1429,7 +1497,8 @@ var Permission2 = class extends Resource {
1429
1497
  FunctionName: this.props.functionArn,
1430
1498
  Action: this.props.action || "lambda:InvokeFunction",
1431
1499
  Principal: this.props.principal,
1432
- SourceArn: this.props.sourceArn
1500
+ ...this.attr("FunctionUrlAuthType", this.props.urlAuthType && constantCase3(this.props.urlAuthType)),
1501
+ ...this.attr("SourceArn", this.props.sourceArn)
1433
1502
  };
1434
1503
  }
1435
1504
  };
@@ -1555,7 +1624,7 @@ var Queue = class extends Resource {
1555
1624
  };
1556
1625
 
1557
1626
  // src/formation/resource/lambda/event-source-mapping.ts
1558
- import { constantCase } from "change-case";
1627
+ import { constantCase as constantCase4 } from "change-case";
1559
1628
  var EventSourceMapping = class extends Resource {
1560
1629
  constructor(logicalId, props) {
1561
1630
  super("AWS::Lambda::EventSourceMapping", logicalId);
@@ -1577,7 +1646,7 @@ var EventSourceMapping = class extends Resource {
1577
1646
  ...this.attr("ParallelizationFactor", this.props.parallelizationFactor),
1578
1647
  ...this.attr("TumblingWindowInSeconds", this.props.tumblingWindow?.toSeconds()),
1579
1648
  ...this.attr("BisectBatchOnFunctionError", this.props.bisectBatchOnError),
1580
- ...this.attr("StartingPosition", this.props.startingPosition && constantCase(this.props.startingPosition)),
1649
+ ...this.attr("StartingPosition", this.props.startingPosition && constantCase4(this.props.startingPosition)),
1581
1650
  ...this.attr("StartingPositionTimestamp", this.props.startingPositionTimestamp),
1582
1651
  ...this.props.maxConcurrency ? {
1583
1652
  ScalingConfig: {
@@ -1618,7 +1687,7 @@ var SqsEventSource = class extends Group {
1618
1687
  };
1619
1688
 
1620
1689
  // src/plugins/queue.ts
1621
- import { camelCase as camelCase3, constantCase as constantCase2 } from "change-case";
1690
+ import { camelCase as camelCase3, constantCase as constantCase5 } from "change-case";
1622
1691
  import { relative as relative3 } from "path";
1623
1692
  var RetentionPeriodSchema = DurationSchema.refine(durationMin(Duration.minutes(1)), "Minimum retention period is 1 minute").refine(durationMax(Duration.days(14)), "Maximum retention period is 14 days");
1624
1693
  var VisibilityTimeoutSchema = DurationSchema.refine(durationMax(Duration.hours(12)), "Maximum visibility timeout is 12 hours");
@@ -1773,7 +1842,7 @@ var queuePlugin = definePlugin({
1773
1842
  stack.add(queue2, lambda, source);
1774
1843
  bind((lambda2) => {
1775
1844
  lambda2.addPermissions(queue2.permissions);
1776
- lambda2.addEnvironment(`QUEUE_${constantCase2(stack.name)}_${constantCase2(id)}_URL`, queue2.url);
1845
+ lambda2.addEnvironment(`QUEUE_${constantCase5(stack.name)}_${constantCase5(id)}_URL`, queue2.url);
1777
1846
  });
1778
1847
  }
1779
1848
  }
@@ -1783,7 +1852,7 @@ var queuePlugin = definePlugin({
1783
1852
  import { z as z9 } from "zod";
1784
1853
 
1785
1854
  // src/formation/resource/dynamodb/table.ts
1786
- import { constantCase as constantCase3 } from "change-case";
1855
+ import { constantCase as constantCase6 } from "change-case";
1787
1856
  var Table = class extends Resource {
1788
1857
  constructor(logicalId, props) {
1789
1858
  super("AWS::DynamoDB::Table", logicalId);
@@ -1856,7 +1925,7 @@ var Table = class extends Resource {
1856
1925
  return {
1857
1926
  TableName: this.name,
1858
1927
  BillingMode: "PAY_PER_REQUEST",
1859
- TableClass: constantCase3(this.props.class || "standard"),
1928
+ TableClass: constantCase6(this.props.class || "standard"),
1860
1929
  PointInTimeRecoverySpecification: {
1861
1930
  PointInTimeRecoveryEnabled: this.props.pointInTimeRecovery || false
1862
1931
  },
@@ -1867,7 +1936,7 @@ var Table = class extends Resource {
1867
1936
  AttributeDefinitions: this.attributeDefinitions(),
1868
1937
  ...this.props.stream ? {
1869
1938
  StreamSpecification: {
1870
- StreamViewType: constantCase3(this.props.stream)
1939
+ StreamViewType: constantCase6(this.props.stream)
1871
1940
  }
1872
1941
  } : {},
1873
1942
  ...this.props.timeToLiveAttribute ? {
@@ -1884,7 +1953,7 @@ var Table = class extends Resource {
1884
1953
  ...props.sort ? [{ KeyType: "RANGE", AttributeName: props.sort }] : []
1885
1954
  ],
1886
1955
  Projection: {
1887
- ProjectionType: constantCase3(props.projection || "all")
1956
+ ProjectionType: constantCase6(props.projection || "all")
1888
1957
  }
1889
1958
  }))
1890
1959
  } : {}
@@ -2010,21 +2079,6 @@ var tablePlugin = definePlugin({
2010
2079
  projection: z9.enum(["all", "keys-only"]).default("all")
2011
2080
  })).optional()
2012
2081
  })
2013
- // .refine(props => {
2014
- // return (
2015
- // // Check the hash key
2016
- // props.fields.hasOwnProperty(props.hash) &&
2017
- // // Check the sort key
2018
- // (!props.sort || props.fields.hasOwnProperty(props.sort)) &&
2019
- // // Check all indexes
2020
- // !Object.values(props.indexes || {}).map(index => (
2021
- // // Check the index hash key
2022
- // props.fields.hasOwnProperty(index.hash) &&
2023
- // // Check the index sort key
2024
- // (!index.sort || props.fields.hasOwnProperty(index.sort))
2025
- // )).includes(false)
2026
- // )
2027
- // }, 'Hash & Sort keys must be defined inside the table fields')
2028
2082
  ).optional()
2029
2083
  }).array()
2030
2084
  }),
@@ -2079,11 +2133,20 @@ var Bucket = class extends Resource {
2079
2133
  }
2080
2134
  name;
2081
2135
  get arn() {
2082
- return ref(this.logicalId);
2136
+ return getAtt(this.logicalId, "Arn");
2083
2137
  }
2084
2138
  get domainName() {
2085
2139
  return getAtt(this.logicalId, "DomainName");
2086
2140
  }
2141
+ get dealStackDomainName() {
2142
+ return getAtt(this.logicalId, "DualStackDomainName");
2143
+ }
2144
+ get regionalDomainName() {
2145
+ return getAtt(this.logicalId, "RegionalDomainName");
2146
+ }
2147
+ get url() {
2148
+ return getAtt(this.logicalId, "WebsiteURL");
2149
+ }
2087
2150
  get permissions() {
2088
2151
  return {
2089
2152
  actions: [
@@ -2105,15 +2168,38 @@ var Bucket = class extends Resource {
2105
2168
  return {
2106
2169
  BucketName: this.name,
2107
2170
  AccessControl: pascalCase2(this.props.accessControl ?? "private"),
2108
- ...this.props.versioned ? {
2171
+ ...this.props.versioning ? {
2109
2172
  VersioningConfiguration: {
2110
2173
  Status: "Enabled"
2111
2174
  }
2175
+ } : {},
2176
+ ...this.props.website ? {
2177
+ WebsiteConfiguration: {
2178
+ ...this.attr("IndexDocument", this.props.website.indexDocument),
2179
+ ...this.attr("ErrorDocument", this.props.website.errorDocument)
2180
+ }
2112
2181
  } : {}
2113
2182
  };
2114
2183
  }
2115
2184
  };
2116
2185
 
2186
+ // src/formation/resource/cloud-formation/custom-resource.ts
2187
+ var CustomResource = class extends Resource {
2188
+ constructor(logicalId, props) {
2189
+ super("AWS::CloudFormation::CustomResource", logicalId);
2190
+ this.props = props;
2191
+ }
2192
+ getAtt(name) {
2193
+ return getAtt(this.logicalId, name);
2194
+ }
2195
+ properties() {
2196
+ return {
2197
+ ServiceToken: this.props.serviceToken,
2198
+ ...this.props.properties
2199
+ };
2200
+ }
2201
+ };
2202
+
2117
2203
  // src/plugins/store.ts
2118
2204
  var storePlugin = definePlugin({
2119
2205
  name: "store",
@@ -2140,13 +2226,19 @@ var storePlugin = definePlugin({
2140
2226
  }
2141
2227
  return types2.toString();
2142
2228
  },
2143
- onStack({ config, stack, stackConfig, bind }) {
2229
+ onStack({ config, stack, stackConfig, bootstrap: bootstrap2, bind }) {
2144
2230
  for (const id of stackConfig.stores || []) {
2145
2231
  const bucket = new Bucket(id, {
2146
- name: `${config.name}-${stack.name}-${id}`,
2232
+ name: `store-${config.name}-${stack.name}-${id}`,
2147
2233
  accessControl: "private"
2148
2234
  });
2149
- stack.add(bucket);
2235
+ const custom = new CustomResource(id, {
2236
+ serviceToken: bootstrap2.import("feature-delete-bucket"),
2237
+ properties: {
2238
+ bucketName: bucket.name
2239
+ }
2240
+ }).dependsOn(bucket);
2241
+ stack.add(bucket, custom);
2150
2242
  bind((lambda) => {
2151
2243
  lambda.addPermissions(bucket.permissions);
2152
2244
  });
@@ -2234,54 +2326,81 @@ var topicPlugin = definePlugin({
2234
2326
  name: "topic",
2235
2327
  schema: z11.object({
2236
2328
  stacks: z11.object({
2237
- /** Define the topics to listen too in your stack.
2329
+ /** Define the events to publish too in your stack.
2330
+ * @example
2331
+ * {
2332
+ * topics: [ 'TOPIC_NAME' ]
2333
+ * }
2334
+ */
2335
+ topics: z11.array(ResourceIdSchema).refine((topics) => {
2336
+ return topics.length === new Set(topics).size;
2337
+ }, "Must be a list of unique topic names").optional(),
2338
+ /** Define the events to subscribe too in your stack.
2238
2339
  * @example
2239
2340
  * {
2240
- * topics: {
2341
+ * subscribers: {
2241
2342
  * TOPIC_NAME: 'function.ts'
2242
2343
  * }
2243
2344
  * }
2244
2345
  */
2245
- topics: z11.record(ResourceIdSchema, FunctionSchema).optional()
2246
- }).array()
2346
+ subscribers: z11.record(ResourceIdSchema, FunctionSchema).optional()
2347
+ }).array().superRefine((stacks, ctx) => {
2348
+ const topics = [];
2349
+ for (const stack of stacks) {
2350
+ topics.push(...stack.topics || []);
2351
+ }
2352
+ for (const index in stacks) {
2353
+ const stack = stacks[index];
2354
+ for (const sub2 of Object.keys(stack.subscribers || {})) {
2355
+ if (!topics.includes(sub2)) {
2356
+ ctx.addIssue({
2357
+ code: z11.ZodIssueCode.custom,
2358
+ message: `Topic subscription to "${sub2}" is undefined`,
2359
+ path: [Number(index), "subscribers"]
2360
+ });
2361
+ }
2362
+ }
2363
+ }
2364
+ })
2247
2365
  }),
2248
2366
  onTypeGen({ config }) {
2249
2367
  const gen = new TypeGen("@awsless/awsless", "TopicResources");
2250
2368
  gen.addCode(typeGenCode3);
2251
2369
  for (const stack of config.stacks) {
2252
- for (const topic of Object.keys(stack.topics || {})) {
2370
+ for (const topic of stack.topics || []) {
2253
2371
  const name = formatName(`${config.name}-${topic}`);
2254
2372
  gen.addType(topic, `Publish<'${name}'>`);
2255
2373
  }
2256
2374
  }
2257
2375
  return gen.toString();
2258
2376
  },
2259
- onApp({ config, bootstrap: bootstrap2, bind }) {
2260
- const allTopicNames = config.stacks.map((stack) => {
2261
- return Object.keys(stack.topics || {});
2262
- }).flat();
2263
- const uniqueTopicNames = [...new Set(allTopicNames)];
2264
- for (const id of uniqueTopicNames) {
2265
- const topic = new Topic(id, {
2266
- name: `${config.name}-${id}`
2267
- });
2268
- bootstrap2.add(topic);
2269
- bootstrap2.export(`topic-${id}-arn`, topic.arn);
2377
+ onApp({ config, bootstrap: bootstrap2 }) {
2378
+ for (const stack of config.stacks) {
2379
+ for (const id of stack.topics || []) {
2380
+ const topic = new Topic(id, {
2381
+ name: `${config.name}-${id}`
2382
+ });
2383
+ bootstrap2.add(topic);
2384
+ bootstrap2.export(`topic-${id}-arn`, topic.arn);
2385
+ }
2270
2386
  }
2271
- bind((lambda) => {
2272
- lambda.addPermissions({
2273
- actions: ["sns:Publish"],
2274
- resources: [
2275
- sub("arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${app}-*", {
2276
- app: config.name
2277
- })
2278
- ]
2279
- });
2280
- });
2281
2387
  },
2282
2388
  onStack(ctx) {
2283
- const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
2284
- for (const [id, props] of Object.entries(stackConfig.topics || {})) {
2389
+ const { config, stack, stackConfig, bootstrap: bootstrap2, bind } = ctx;
2390
+ for (const id of stackConfig.topics || []) {
2391
+ bind((lambda) => {
2392
+ lambda.addPermissions({
2393
+ actions: ["sns:Publish"],
2394
+ resources: [
2395
+ sub("arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${app}-${topic}", {
2396
+ app: config.name,
2397
+ topic: id
2398
+ })
2399
+ ]
2400
+ });
2401
+ });
2402
+ }
2403
+ for (const [id, props] of Object.entries(stackConfig.subscribers || {})) {
2285
2404
  const lambda = toLambdaFunction(ctx, `topic-${id}`, props);
2286
2405
  const source = new SnsEventSource(id, lambda, {
2287
2406
  topicArn: bootstrap2.import(`topic-${id}-arn`)
@@ -2423,7 +2542,7 @@ var toArray = (value) => {
2423
2542
  import { paramCase as paramCase4 } from "change-case";
2424
2543
 
2425
2544
  // src/formation/resource/appsync/graphql-api.ts
2426
- import { constantCase as constantCase4 } from "change-case";
2545
+ import { constantCase as constantCase7 } from "change-case";
2427
2546
  var GraphQLApi = class extends Resource {
2428
2547
  constructor(logicalId, props) {
2429
2548
  super("AWS::AppSync::GraphQLApi", logicalId);
@@ -2455,7 +2574,7 @@ var GraphQLApi = class extends Resource {
2455
2574
  properties() {
2456
2575
  return {
2457
2576
  Name: this.name,
2458
- AuthenticationType: constantCase4(this.props.authenticationType || "api-key"),
2577
+ AuthenticationType: constantCase7(this.props.authenticationType || "api-key"),
2459
2578
  AdditionalAuthenticationProviders: this.lambdaAuthProviders.map((provider) => ({
2460
2579
  AuthenticationType: "AWS_LAMBDA",
2461
2580
  LambdaAuthorizerConfig: {
@@ -2784,17 +2903,25 @@ var graphqlPlugin = definePlugin({
2784
2903
  z14.array(LocalFileSchema).min(1)
2785
2904
  ]).optional(),
2786
2905
  resolvers: z14.record(
2906
+ // TypeName
2787
2907
  z14.string(),
2788
2908
  z14.record(
2909
+ // FieldName
2789
2910
  z14.string(),
2790
- FunctionSchema
2911
+ z14.union([
2912
+ FunctionSchema,
2913
+ z14.object({
2914
+ consumer: FunctionSchema,
2915
+ resolver: LocalFileSchema
2916
+ })
2917
+ ])
2791
2918
  )
2792
2919
  ).optional()
2793
2920
  })).optional()
2794
2921
  }).array()
2795
2922
  }),
2796
2923
  onApp(ctx) {
2797
- const { config, bootstrap: bootstrap2, usEastBootstrap } = ctx;
2924
+ const { config, bootstrap: bootstrap2 } = ctx;
2798
2925
  const apis = /* @__PURE__ */ new Set();
2799
2926
  for (const stackConfig of config.stacks) {
2800
2927
  for (const id of Object.keys(stackConfig.graphql || {})) {
@@ -2827,8 +2954,9 @@ var graphqlPlugin = definePlugin({
2827
2954
  }
2828
2955
  if (props.domain) {
2829
2956
  const domainName = props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain;
2830
- const hostedZoneId = usEastBootstrap.import(`hosted-zone-${props.domain}-id`);
2831
- const certificateArn = usEastBootstrap.import(`certificate-${props.domain}-arn`);
2957
+ const hostedZoneId = bootstrap2.import(`hosted-zone-${props.domain}-id`);
2958
+ const certificateArn = bootstrap2.import(`us-east-certificate-${props.domain}-arn`);
2959
+ debug("DEBUG CERT", certificateArn);
2832
2960
  const domain = new DomainName(id, {
2833
2961
  domainName,
2834
2962
  certificateArn
@@ -2855,14 +2983,15 @@ var graphqlPlugin = definePlugin({
2855
2983
  for (const [id, props] of Object.entries(stackConfig.graphql || {})) {
2856
2984
  const apiId = bootstrap2.import(`graphql-${id}`);
2857
2985
  for (const [typeName, fields] of Object.entries(props.resolvers || {})) {
2858
- for (const [fieldName, functionProps] of Object.entries(fields || {})) {
2986
+ for (const [fieldName, resolverProps] of Object.entries(fields || {})) {
2987
+ const props2 = isFunctionProps(resolverProps) ? { consumer: resolverProps } : resolverProps;
2859
2988
  const entryId = paramCase4(`${id}-${typeName}-${fieldName}`);
2860
- const lambda = toLambdaFunction(ctx, `graphql-${entryId}`, functionProps);
2989
+ const lambda = toLambdaFunction(ctx, `graphql-${entryId}`, props2.consumer);
2861
2990
  const source = new AppsyncEventSource(entryId, lambda, {
2862
2991
  apiId,
2863
2992
  typeName,
2864
2993
  fieldName,
2865
- code: Code2.fromInline(entryId, defaultResolver)
2994
+ code: Code2.fromInline(entryId, props2.resolver || defaultResolver)
2866
2995
  });
2867
2996
  stack.add(lambda, source);
2868
2997
  }
@@ -2943,122 +3072,31 @@ var RecordSetGroup = class extends Resource {
2943
3072
  }
2944
3073
  };
2945
3074
 
2946
- // src/custom/delete-hosted-zone/handler.ts
2947
- var deleteHostedZoneRecordsHandlerCode = (
2948
- /* JS */
2949
- `
2950
-
2951
- const { Route53Client, ListResourceRecordSetsCommand, ChangeResourceRecordSetsCommand } = require('@aws-sdk/client-route-53')
2952
-
2953
- const client = new Route53Client({})
2954
-
2955
- exports.handler = async (event) => {
2956
- const type = event.RequestType
2957
- const hostedZoneId = event.ResourceProperties.hostedZoneId
2958
-
2959
- try {
2960
- if(type === 'Delete') {
2961
- const records = await listHostedZoneRecords(hostedZoneId)
2962
- console.log(records)
2963
-
2964
- await deleteHostedZoneRecords(hostedZoneId, records)
2965
- }
2966
-
2967
- await send(event, hostedZoneId, 'SUCCESS')
2968
- }
2969
- catch(error) {
2970
- if (error instanceof Error) {
2971
- await send(event, hostedZoneId, 'FAILED', {}, error.message)
2972
- } else {
2973
- await send(event, hostedZoneId, 'FAILED', {}, 'Unknown error')
2974
- }
2975
- }
2976
- }
2977
-
2978
- const send = async (event, id, status, data = {}, reason = '') => {
2979
- const body = JSON.stringify({
2980
- Status: status,
2981
- Reason: reason,
2982
- PhysicalResourceId: id,
2983
- StackId: event.StackId,
2984
- RequestId: event.RequestId,
2985
- LogicalResourceId: event.LogicalResourceId,
2986
- NoEcho: false,
2987
- Data: data
2988
- })
2989
-
2990
- await fetch(event.ResponseURL, {
2991
- method: 'PUT',
2992
- port: 443,
2993
- body,
2994
- headers: {
2995
- 'content-type': '',
2996
- 'content-length': Buffer.from(body).byteLength,
2997
- },
2998
- })
2999
- }
3000
-
3001
- const deleteHostedZoneRecords = async (hostedZoneId, records) => {
3002
- records = records.filter(record => ![ 'SOA', 'NS' ].includes(record.Type))
3003
- if(records.length === 0) {
3004
- return
3005
- }
3006
-
3007
- const chunkSize = 100;
3008
- for (let i = 0; i < records.length; i += chunkSize) {
3009
- const chunk = records.slice(i, i + chunkSize);
3010
-
3011
- await client.send(new ChangeResourceRecordSetsCommand({
3012
- HostedZoneId: hostedZoneId,
3013
- ChangeBatch: {
3014
- Changes: chunk.map(record => ({
3015
- Action: 'DELETE',
3016
- ResourceRecordSet: record
3017
- }))
3018
- }
3019
- }))
3020
- }
3021
- }
3022
-
3023
- const listHostedZoneRecords = async (hostedZoneId) => {
3024
-
3025
- const records = []
3026
- let token
3027
-
3028
- while(true) {
3029
- const result = await client.send(new ListResourceRecordSetsCommand({
3030
- HostedZoneId: hostedZoneId,
3031
- NextRecordName: token
3032
- }))
3033
-
3034
- if(result.ResourceRecordSets && result.ResourceRecordSets.length) {
3035
- records.push(...result.ResourceRecordSets)
3036
- }
3037
-
3038
- if(result.NextRecordName) {
3039
- token = result.NextRecordName
3040
- } else {
3041
- return records
3042
- }
3043
- }
3044
- }
3045
- `
3046
- );
3047
-
3048
- // src/formation/resource/cloud-formation/custom-resource.ts
3049
- var CustomResource = class extends Resource {
3050
- constructor(logicalId, props) {
3051
- super("AWS::CloudFormation::CustomResource", logicalId);
3052
- this.props = props;
3053
- }
3054
- getAtt(name) {
3055
- return getAtt(this.logicalId, name);
3075
+ // src/custom/global-exports.ts
3076
+ var GlobalExports = class extends Group {
3077
+ resource;
3078
+ constructor(id, props) {
3079
+ const lambda = new Function(id, {
3080
+ code: Code.fromFeature("global-exports")
3081
+ });
3082
+ lambda.addPermissions({
3083
+ actions: ["cloudformation:ListExports"],
3084
+ resources: ["*"]
3085
+ });
3086
+ const resource = new CustomResource(id, {
3087
+ serviceToken: lambda.arn,
3088
+ properties: {
3089
+ region: props.region
3090
+ }
3091
+ });
3092
+ super([lambda, resource]);
3093
+ this.resource = resource;
3056
3094
  }
3057
- properties() {
3058
- return {
3059
- ServiceToken: this.props.serviceToken,
3060
- ...this.props.properties
3061
- };
3095
+ import(name) {
3096
+ return lazy((stack) => {
3097
+ const attr = formatName(`${stack.app?.name ?? "default"}-${name}`);
3098
+ return this.resource.getAtt(attr);
3099
+ });
3062
3100
  }
3063
3101
  };
3064
3102
 
@@ -3100,36 +3138,39 @@ var domainPlugin = definePlugin({
3100
3138
  if (domains.length === 0) {
3101
3139
  return;
3102
3140
  }
3103
- const lambda = new Function("delete-hosted-zone", {
3141
+ const deleteHostedZoneLambda = new Function("delete-hosted-zone", {
3104
3142
  name: `${config.name}-delete-hosted-zone`,
3105
- code: Code.fromInline(deleteHostedZoneRecordsHandlerCode, "index.handler")
3106
- });
3107
- lambda.addPermissions({
3143
+ code: Code.fromInlineFeature("delete-hosted-zone")
3144
+ }).enableLogs(Duration.days(3)).addPermissions({
3108
3145
  actions: [
3109
3146
  "route53:ListResourceRecordSets",
3110
3147
  "route53:ChangeResourceRecordSets"
3111
3148
  ],
3112
3149
  resources: ["*"]
3113
3150
  });
3114
- usEastBootstrap.add(lambda);
3151
+ usEastBootstrap.add(deleteHostedZoneLambda);
3152
+ const usEastExports = new GlobalExports("us-east-exports", {
3153
+ region: usEastBootstrap.region
3154
+ });
3155
+ bootstrap2.add(usEastExports);
3115
3156
  for (const [domain, records] of domains) {
3116
3157
  const hostedZone = new HostedZone(domain);
3117
3158
  const usEastCertificate = new Certificate(domain, {
3118
3159
  hostedZoneId: hostedZone.id,
3119
3160
  alternativeNames: [`*.${domain}`]
3120
3161
  });
3121
- const custom = new CustomResource(domain, {
3122
- serviceToken: lambda.arn,
3162
+ const deleteHostedZone = new CustomResource(domain, {
3163
+ serviceToken: deleteHostedZoneLambda.arn,
3123
3164
  properties: {
3124
3165
  hostedZoneId: hostedZone.id
3125
3166
  }
3126
- }).dependsOn(hostedZone);
3127
- usEastBootstrap.add(custom).add(hostedZone).add(usEastCertificate).export(`certificate-${domain}-arn`, usEastCertificate.arn).export(`hosted-zone-${domain}-id`, hostedZone.id);
3167
+ }).dependsOn(deleteHostedZoneLambda, hostedZone);
3168
+ usEastBootstrap.add(hostedZone).add(deleteHostedZone).add(usEastCertificate).export(`certificate-${domain}-arn`, usEastCertificate.arn).export(`hosted-zone-${domain}-id`, hostedZone.id);
3128
3169
  const certificate = new Certificate(domain, {
3129
- hostedZoneId: usEastBootstrap.import(`hosted-zone-${domain}-id`),
3170
+ hostedZoneId: usEastExports.import(`hosted-zone-${domain}-id`),
3130
3171
  alternativeNames: [`*.${domain}`]
3131
3172
  });
3132
- bootstrap2.add(certificate).export(`certificate-${domain}-arn`, certificate.arn);
3173
+ bootstrap2.add(certificate).export(`certificate-${domain}-arn`, certificate.arn).export(`hosted-zone-${domain}-id`, usEastExports.import(`hosted-zone-${domain}-id`)).export(`us-east-certificate-${domain}-arn`, usEastExports.import(`certificate-${domain}-arn`));
3133
3174
  if (records.length > 0) {
3134
3175
  const group = new RecordSetGroup(domain, {
3135
3176
  hostedZoneId: hostedZone.id,
@@ -3541,7 +3582,7 @@ var LoadBalancer = class extends Resource {
3541
3582
  };
3542
3583
 
3543
3584
  // src/formation/resource/elb/listener.ts
3544
- import { constantCase as constantCase5 } from "change-case";
3585
+ import { constantCase as constantCase8 } from "change-case";
3545
3586
  var Listener = class extends Resource {
3546
3587
  constructor(logicalId, props) {
3547
3588
  super("AWS::ElasticLoadBalancingV2::Listener", logicalId);
@@ -3557,7 +3598,7 @@ var Listener = class extends Resource {
3557
3598
  return {
3558
3599
  LoadBalancerArn: this.props.loadBalancerArn,
3559
3600
  Port: this.props.port,
3560
- Protocol: constantCase5(this.props.protocol),
3601
+ Protocol: constantCase8(this.props.protocol),
3561
3602
  Certificates: this.props.certificates.map((arn) => ({
3562
3603
  CertificateArn: arn
3563
3604
  })),
@@ -3783,7 +3824,7 @@ var httpPlugin = definePlugin({
3783
3824
  ).optional()
3784
3825
  }).array()
3785
3826
  }),
3786
- onApp({ config, bootstrap: bootstrap2, usEastBootstrap }) {
3827
+ onApp({ config, bootstrap: bootstrap2 }) {
3787
3828
  if (Object.keys(config.defaults?.http || {}).length === 0) {
3788
3829
  return;
3789
3830
  }
@@ -3822,7 +3863,7 @@ var httpPlugin = definePlugin({
3822
3863
  ]
3823
3864
  }).dependsOn(loadBalancer);
3824
3865
  const record = new RecordSet(`${id}-http`, {
3825
- hostedZoneId: usEastBootstrap.import(`hosted-zone-${props.domain}-id`),
3866
+ hostedZoneId: bootstrap2.import(`hosted-zone-${props.domain}-id`),
3826
3867
  name: props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain,
3827
3868
  type: "A",
3828
3869
  alias: {
@@ -3926,173 +3967,1250 @@ var searchPlugin = definePlugin({
3926
3967
  });
3927
3968
  }
3928
3969
  }
3929
- });
3930
-
3931
- // src/plugins/cache.ts
3932
- import { z as z19 } from "zod";
3970
+ });
3971
+
3972
+ // src/plugins/cache.ts
3973
+ import { z as z19 } from "zod";
3974
+
3975
+ // src/formation/resource/memorydb/cluster.ts
3976
+ var Cluster = class extends Resource {
3977
+ constructor(logicalId, props) {
3978
+ super("AWS::MemoryDB::Cluster", logicalId);
3979
+ this.props = props;
3980
+ this.name = formatName(this.props.name || logicalId);
3981
+ this.tag("name", this.name);
3982
+ }
3983
+ name;
3984
+ get status() {
3985
+ return this.getAtt("Status");
3986
+ }
3987
+ get arn() {
3988
+ return this.getAtt("ARN");
3989
+ }
3990
+ get address() {
3991
+ return this.getAtt("ClusterEndpoint.Address");
3992
+ }
3993
+ get port() {
3994
+ return this.getAtt("ClusterEndpoint.Port");
3995
+ }
3996
+ properties() {
3997
+ return {
3998
+ ClusterName: this.name,
3999
+ ClusterEndpoint: {
4000
+ Port: this.props.port
4001
+ },
4002
+ Port: this.props.port,
4003
+ ...this.attr("Description", this.props.description),
4004
+ ACLName: this.props.aclName,
4005
+ EngineVersion: this.props.engine ?? "7.0",
4006
+ ...this.attr("SubnetGroupName", this.props.subnetGroupName),
4007
+ ...this.attr("SecurityGroupIds", this.props.securityGroupIds),
4008
+ NodeType: "db." + this.props.type,
4009
+ NumReplicasPerShard: this.props.replicasPerShard ?? 1,
4010
+ NumShards: this.props.shards ?? 1,
4011
+ TLSEnabled: this.props.tls ?? false,
4012
+ DataTiering: this.props.dataTiering ? "true" : "false",
4013
+ AutoMinorVersionUpgrade: this.props.autoMinorVersionUpgrade ?? true,
4014
+ MaintenanceWindow: this.props.maintenanceWindow ?? "Sat:02:00-Sat:05:00"
4015
+ };
4016
+ }
4017
+ };
4018
+
4019
+ // src/formation/resource/memorydb/subnet-group.ts
4020
+ var SubnetGroup = class extends Resource {
4021
+ constructor(logicalId, props) {
4022
+ super("AWS::MemoryDB::SubnetGroup", logicalId);
4023
+ this.props = props;
4024
+ this.name = formatName(this.props.name || logicalId);
4025
+ }
4026
+ name;
4027
+ get arn() {
4028
+ return getAtt(this.logicalId, "Arn");
4029
+ }
4030
+ properties() {
4031
+ return {
4032
+ SubnetGroupName: this.name,
4033
+ SubnetIds: this.props.subnetIds,
4034
+ ...this.attr("Description", this.props.description)
4035
+ };
4036
+ }
4037
+ };
4038
+
4039
+ // src/plugins/cache.ts
4040
+ import { constantCase as constantCase9 } from "change-case";
4041
+ var TypeSchema = z19.enum([
4042
+ "t4g.small",
4043
+ "t4g.medium",
4044
+ "r6g.large",
4045
+ "r6g.xlarge",
4046
+ "r6g.2xlarge",
4047
+ "r6g.4xlarge",
4048
+ "r6g.8xlarge",
4049
+ "r6g.12xlarge",
4050
+ "r6g.16xlarge",
4051
+ "r6gd.xlarge",
4052
+ "r6gd.2xlarge",
4053
+ "r6gd.4xlarge",
4054
+ "r6gd.8xlarge"
4055
+ ]);
4056
+ var PortSchema = z19.number().int().min(1).max(5e4);
4057
+ var ShardsSchema = z19.number().int().min(0).max(100);
4058
+ var ReplicasPerShardSchema = z19.number().int().min(0).max(5);
4059
+ var EngineSchema = z19.enum(["7.0", "6.2"]);
4060
+ var cachePlugin = definePlugin({
4061
+ name: "cache",
4062
+ schema: z19.object({
4063
+ stacks: z19.object({
4064
+ /** Define the caches in your stack.
4065
+ * For access to the cache put your functions inside the global VPC.
4066
+ * @example
4067
+ * {
4068
+ * caches: {
4069
+ * CACHE_NAME: {
4070
+ * type: 't4g.small'
4071
+ * }
4072
+ * }
4073
+ * }
4074
+ */
4075
+ caches: z19.record(
4076
+ ResourceIdSchema,
4077
+ z19.object({
4078
+ type: TypeSchema.default("t4g.small"),
4079
+ port: PortSchema.default(6379),
4080
+ shards: ShardsSchema.default(1),
4081
+ replicasPerShard: ReplicasPerShardSchema.default(1),
4082
+ engine: EngineSchema.default("7.0"),
4083
+ dataTiering: z19.boolean().default(false)
4084
+ })
4085
+ ).optional()
4086
+ }).array()
4087
+ }),
4088
+ onTypeGen({ config }) {
4089
+ const gen = new TypeGen("@awsless/awsless", "CacheResources");
4090
+ for (const stack of config.stacks) {
4091
+ const list3 = new TypeObject();
4092
+ for (const name of Object.keys(stack.caches || {})) {
4093
+ list3.addType(name, `{ host: string, port: number }`);
4094
+ }
4095
+ gen.addType(stack.name, list3.toString());
4096
+ }
4097
+ return gen.toString();
4098
+ },
4099
+ onStack({ config, stack, stackConfig, bootstrap: bootstrap2, bind }) {
4100
+ for (const [id, props] of Object.entries(stackConfig.caches || {})) {
4101
+ const name = `${config.name}-${stack.name}-${id}`;
4102
+ const subnetGroup = new SubnetGroup(id, {
4103
+ name,
4104
+ subnetIds: [
4105
+ bootstrap2.import(`private-subnet-1`),
4106
+ bootstrap2.import(`private-subnet-2`)
4107
+ ]
4108
+ });
4109
+ const securityGroup = new SecurityGroup(id, {
4110
+ name,
4111
+ vpcId: bootstrap2.import(`vpc-id`),
4112
+ description: name
4113
+ });
4114
+ const port = Port.tcp(props.port);
4115
+ securityGroup.addIngressRule(Peer.anyIpv4(), port);
4116
+ securityGroup.addIngressRule(Peer.anyIpv6(), port);
4117
+ const cluster = new Cluster(id, {
4118
+ name,
4119
+ aclName: "open-access",
4120
+ securityGroupIds: [securityGroup.id],
4121
+ subnetGroupName: subnetGroup.name,
4122
+ ...props
4123
+ }).dependsOn(subnetGroup, securityGroup);
4124
+ stack.add(subnetGroup, securityGroup, cluster);
4125
+ bind((lambda) => {
4126
+ lambda.addEnvironment(`CACHE_${constantCase9(stack.name)}_${constantCase9(id)}_HOST`, cluster.address).addEnvironment(`CACHE_${constantCase9(stack.name)}_${constantCase9(id)}_PORT`, props.port.toString());
4127
+ });
4128
+ }
4129
+ }
4130
+ });
4131
+
4132
+ // src/plugins/rest.ts
4133
+ import { z as z21 } from "zod";
4134
+
4135
+ // src/schema/route.ts
4136
+ import { z as z20 } from "zod";
4137
+ var RouteSchema2 = z20.custom((route) => {
4138
+ return z20.string().regex(/^(POST|GET|PUT|DELETE|HEAD|OPTIONS)(\s\/[a-z0-9\+\_\-\/\{\}]*)$/ig).safeParse(route).success;
4139
+ }, "Invalid route");
4140
+
4141
+ // src/formation/resource/api-gateway-v2/api.ts
4142
+ var Api = class extends Resource {
4143
+ constructor(logicalId, props) {
4144
+ super("AWS::ApiGatewayV2::Api", logicalId);
4145
+ this.props = props;
4146
+ this.name = formatName(this.props.name || logicalId);
4147
+ }
4148
+ name;
4149
+ get endpoint() {
4150
+ return getAtt(this.logicalId, "ApiEndpoint");
4151
+ }
4152
+ get id() {
4153
+ return getAtt(this.logicalId, "ApiId");
4154
+ }
4155
+ properties() {
4156
+ return {
4157
+ Name: this.name,
4158
+ ProtocolType: this.props.protocolType,
4159
+ ...this.attr("Description", this.props.description),
4160
+ CorsConfiguration: {
4161
+ ...this.attr("AllowCredentials", this.props.cors?.allow?.credentials),
4162
+ ...this.attr("AllowHeaders", this.props.cors?.allow?.headers),
4163
+ ...this.attr("AllowMethods", this.props.cors?.allow?.methods),
4164
+ ...this.attr("AllowOrigins", this.props.cors?.allow?.origins),
4165
+ ...this.attr("ExposeHeaders", this.props.cors?.expose?.headers),
4166
+ ...this.attr("MaxAge", this.props.cors?.maxAge?.toSeconds())
4167
+ }
4168
+ };
4169
+ }
4170
+ };
4171
+
4172
+ // src/formation/resource/api-gateway-v2/integration.ts
4173
+ var Integration = class extends Resource {
4174
+ constructor(logicalId, props) {
4175
+ super("AWS::ApiGatewayV2::Integration", logicalId);
4176
+ this.props = props;
4177
+ }
4178
+ get id() {
4179
+ return ref(this.logicalId);
4180
+ }
4181
+ properties() {
4182
+ return {
4183
+ ApiId: this.props.apiId,
4184
+ IntegrationType: this.props.type,
4185
+ IntegrationUri: this.props.uri,
4186
+ IntegrationMethod: this.props.method,
4187
+ PayloadFormatVersion: this.props.payloadFormatVersion ?? "2.0",
4188
+ ...this.attr("Description", this.props.description)
4189
+ };
4190
+ }
4191
+ };
4192
+
4193
+ // src/formation/resource/api-gateway-v2/route.ts
4194
+ var Route2 = class extends Resource {
4195
+ constructor(logicalId, props) {
4196
+ super("AWS::ApiGatewayV2::Route", logicalId);
4197
+ this.props = props;
4198
+ }
4199
+ get id() {
4200
+ return getAtt(this.logicalId, "RouteId");
4201
+ }
4202
+ properties() {
4203
+ return {
4204
+ ApiId: this.props.apiId,
4205
+ RouteKey: this.props.routeKey,
4206
+ Target: this.props.target
4207
+ };
4208
+ }
4209
+ };
4210
+
4211
+ // src/formation/resource/lambda/event-source/api-gateway-v2.ts
4212
+ var ApiGatewayV2EventSource = class extends Group {
4213
+ constructor(id, lambda, props) {
4214
+ const name = formatName(id);
4215
+ const permission = new Permission2(id, {
4216
+ action: "lambda:InvokeFunction",
4217
+ principal: "apigateway.amazonaws.com",
4218
+ functionArn: lambda.arn
4219
+ }).dependsOn(lambda);
4220
+ const integration = new Integration(id, {
4221
+ apiId: props.apiId,
4222
+ description: name,
4223
+ method: "POST",
4224
+ payloadFormatVersion: "2.0",
4225
+ type: "AWS_PROXY",
4226
+ uri: sub("arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambda}/invocations", {
4227
+ lambda: lambda.arn
4228
+ })
4229
+ });
4230
+ const route = new Route2(id, {
4231
+ apiId: props.apiId,
4232
+ routeKey: props.routeKey,
4233
+ target: sub("integrations/${id}", { id: integration.id })
4234
+ }).dependsOn(lambda, permission, integration);
4235
+ super([integration, route, permission]);
4236
+ }
4237
+ };
4238
+
4239
+ // src/formation/resource/api-gateway-v2/domain-name.ts
4240
+ var DomainName2 = class extends Resource {
4241
+ constructor(logicalId, props) {
4242
+ super("AWS::ApiGatewayV2::DomainName", logicalId);
4243
+ this.props = props;
4244
+ }
4245
+ get name() {
4246
+ return ref(this.logicalId);
4247
+ }
4248
+ get regionalDomainName() {
4249
+ return getAtt(this.logicalId, "RegionalDomainName");
4250
+ }
4251
+ get regionalHostedZoneId() {
4252
+ return getAtt(this.logicalId, "RegionalHostedZoneId");
4253
+ }
4254
+ properties() {
4255
+ return {
4256
+ DomainName: this.props.name,
4257
+ DomainNameConfigurations: [{
4258
+ CertificateArn: this.props.certificateArn
4259
+ }]
4260
+ };
4261
+ }
4262
+ };
4263
+
4264
+ // src/formation/resource/api-gateway-v2/stage.ts
4265
+ var Stage = class extends Resource {
4266
+ constructor(logicalId, props) {
4267
+ super("AWS::ApiGatewayV2::Stage", logicalId);
4268
+ this.props = props;
4269
+ this.name = formatName(this.props.name || logicalId);
4270
+ }
4271
+ name;
4272
+ properties() {
4273
+ return {
4274
+ ApiId: this.props.apiId,
4275
+ StageName: this.name,
4276
+ AutoDeploy: this.props.autoDeploy ?? true,
4277
+ ...this.attr("DeploymentId", this.props.deploymentId),
4278
+ ...this.attr("Description", this.props.description)
4279
+ };
4280
+ }
4281
+ };
4282
+
4283
+ // src/formation/resource/api-gateway-v2/api-mapping.ts
4284
+ var ApiMapping = class extends Resource {
4285
+ constructor(logicalId, props) {
4286
+ super("AWS::ApiGatewayV2::ApiMapping", logicalId);
4287
+ this.props = props;
4288
+ }
4289
+ get id() {
4290
+ return getAtt(this.logicalId, "ApiMappingId");
4291
+ }
4292
+ properties() {
4293
+ return {
4294
+ DomainName: this.props.domainName,
4295
+ ApiId: this.props.apiId,
4296
+ Stage: this.props.stage
4297
+ };
4298
+ }
4299
+ };
4300
+
4301
+ // src/plugins/rest.ts
4302
+ var restPlugin = definePlugin({
4303
+ name: "rest",
4304
+ schema: z21.object({
4305
+ defaults: z21.object({
4306
+ /** Define your global REST API's.
4307
+ * @example
4308
+ * {
4309
+ * rest: {
4310
+ * REST_API_NAME: {
4311
+ * domain: 'example.com',
4312
+ * subDomain: 'api',
4313
+ * }
4314
+ * }
4315
+ * }
4316
+ */
4317
+ rest: z21.record(
4318
+ ResourceIdSchema,
4319
+ z21.object({
4320
+ /** The domain to link your API with. */
4321
+ domain: z21.string(),
4322
+ subDomain: z21.string().optional()
4323
+ })
4324
+ ).optional()
4325
+ }).default({}),
4326
+ stacks: z21.object({
4327
+ /** Define routes in your stack for your global REST API.
4328
+ * @example
4329
+ * {
4330
+ * rest: {
4331
+ * REST_API_NAME: {
4332
+ * 'GET /': 'index.ts',
4333
+ * 'POST /posts': 'create-post.ts',
4334
+ * }
4335
+ * }
4336
+ * }
4337
+ */
4338
+ rest: z21.record(
4339
+ ResourceIdSchema,
4340
+ z21.record(RouteSchema2, FunctionSchema)
4341
+ ).optional()
4342
+ }).array()
4343
+ }),
4344
+ onApp({ config, bootstrap: bootstrap2 }) {
4345
+ for (const [id, props] of Object.entries(config.defaults?.rest || {})) {
4346
+ const api = new Api(id, {
4347
+ name: `${config.name}-${id}`,
4348
+ protocolType: "HTTP"
4349
+ });
4350
+ const stage = new Stage(id, {
4351
+ name: "v1",
4352
+ apiId: api.id
4353
+ }).dependsOn(api);
4354
+ bootstrap2.add(api, stage).export(`rest-${id}-id`, api.id);
4355
+ if (props.domain) {
4356
+ const domainName = props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain;
4357
+ const hostedZoneId = bootstrap2.import(`hosted-zone-${props.domain}-id`);
4358
+ const certificateArn = bootstrap2.import(`certificate-${props.domain}-arn`);
4359
+ const domain = new DomainName2(id, {
4360
+ name: domainName,
4361
+ certificateArn
4362
+ });
4363
+ const mapping = new ApiMapping(id, {
4364
+ apiId: api.id,
4365
+ domainName: domain.name,
4366
+ stage: stage.name
4367
+ }).dependsOn(api, domain, stage);
4368
+ const record = new RecordSet(`rest-${id}`, {
4369
+ hostedZoneId,
4370
+ type: "A",
4371
+ name: domainName,
4372
+ alias: {
4373
+ dnsName: domain.regionalDomainName,
4374
+ hostedZoneId: domain.regionalHostedZoneId
4375
+ }
4376
+ }).dependsOn(domain, mapping);
4377
+ bootstrap2.add(domain, mapping, record);
4378
+ }
4379
+ }
4380
+ },
4381
+ onStack(ctx) {
4382
+ const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
4383
+ for (const [id, routes] of Object.entries(stackConfig.rest || {})) {
4384
+ for (const [routeKey, props] of Object.entries(routes)) {
4385
+ const lambda = toLambdaFunction(ctx, `rest-${id}-${routeKey}`, props);
4386
+ const source = new ApiGatewayV2EventSource(`rest-${id}-${routeKey}`, lambda, {
4387
+ apiId: bootstrap2.import(`rest-${id}-id`),
4388
+ routeKey
4389
+ });
4390
+ stack.add(lambda, source);
4391
+ }
4392
+ }
4393
+ }
4394
+ });
4395
+
4396
+ // src/plugins/config.ts
4397
+ import { z as z22 } from "zod";
4398
+
4399
+ // src/util/param.ts
4400
+ import { DeleteParameterCommand, GetParameterCommand, GetParametersByPathCommand, ParameterType, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
4401
+ var configParameterPrefix = (config) => {
4402
+ return `/.awsless/${config.name}`;
4403
+ };
4404
+ var Params = class {
4405
+ constructor(config) {
4406
+ this.config = config;
4407
+ this.client = new SSMClient({
4408
+ credentials: config.credentials,
4409
+ region: config.region
4410
+ });
4411
+ }
4412
+ client;
4413
+ getName(name) {
4414
+ return `${configParameterPrefix(this.config)}/${name}`;
4415
+ }
4416
+ async get(name) {
4417
+ debug("Get remote config value");
4418
+ debug("Name:", style.info(name));
4419
+ let result;
4420
+ try {
4421
+ result = await this.client.send(new GetParameterCommand({
4422
+ Name: this.getName(name),
4423
+ WithDecryption: true
4424
+ }));
4425
+ } catch (error) {
4426
+ if (error instanceof Error && error.name === "ParameterNotFound") {
4427
+ debug("Parameter not found");
4428
+ return;
4429
+ }
4430
+ throw error;
4431
+ }
4432
+ const value = result.Parameter?.Value;
4433
+ debug("Value:", style.info(value));
4434
+ debug("Done getting remote config value");
4435
+ return value;
4436
+ }
4437
+ async set(name, value) {
4438
+ debug("Save remote config value");
4439
+ debug("Name:", style.info(name));
4440
+ debug("Value:", style.info(value));
4441
+ await this.client.send(new PutParameterCommand({
4442
+ Type: ParameterType.STRING,
4443
+ Name: this.getName(name),
4444
+ Value: value,
4445
+ Overwrite: true
4446
+ }));
4447
+ debug("Done saving remote config value");
4448
+ }
4449
+ async delete(name) {
4450
+ debug("Delete remote config value");
4451
+ debug("Name:", style.info(name));
4452
+ try {
4453
+ await this.client.send(new DeleteParameterCommand({
4454
+ Name: this.getName(name)
4455
+ }));
4456
+ } catch (error) {
4457
+ if (error instanceof Error && error.name === "ParameterNotFound") {
4458
+ debug("Remote config value was already deleted");
4459
+ return;
4460
+ }
4461
+ throw error;
4462
+ }
4463
+ debug("Done deleting remote config value");
4464
+ }
4465
+ async list() {
4466
+ debug("Load remote config values");
4467
+ const result = await this.client.send(new GetParametersByPathCommand({
4468
+ Path: configParameterPrefix(this.config),
4469
+ WithDecryption: true,
4470
+ MaxResults: 10,
4471
+ Recursive: true
4472
+ }));
4473
+ debug("Done loading remote config values");
4474
+ const values = {};
4475
+ result.Parameters?.forEach((param) => {
4476
+ const name = param.Name.substring(configParameterPrefix(this.config).length).substring(1);
4477
+ values[name] = param.Value || "";
4478
+ });
4479
+ return values;
4480
+ }
4481
+ };
4482
+
4483
+ // src/plugins/config.ts
4484
+ import { paramCase as paramCase5 } from "change-case";
4485
+ var ConfigNameSchema = z22.string().regex(/[a-z0-9\-]/g, "Invalid config name");
4486
+ var configPlugin = definePlugin({
4487
+ name: "config",
4488
+ schema: z22.object({
4489
+ stacks: z22.object({
4490
+ /** Define the config values for your stack.
4491
+ * @example
4492
+ * ```
4493
+ * {
4494
+ * configs: [ 'your-secret' ]
4495
+ * }
4496
+ * ```
4497
+ *
4498
+ * You can access the config values via:
4499
+ * @example
4500
+ * ```
4501
+ * import { Config } from '@awsless/awsless'
4502
+ *
4503
+ * Config.YOUR_SECRET
4504
+ * ```
4505
+ */
4506
+ configs: z22.array(ConfigNameSchema).optional()
4507
+ }).array()
4508
+ }),
4509
+ onTypeGen({ config }) {
4510
+ const types2 = new TypeGen("@awsless/awsless", "ConfigResources", false);
4511
+ for (const stack of config.stacks) {
4512
+ for (const name of stack.configs || []) {
4513
+ types2.addConst(name, "string");
4514
+ }
4515
+ }
4516
+ return types2.toString();
4517
+ },
4518
+ onStack({ bind, config, stackConfig }) {
4519
+ const configs = stackConfig.configs;
4520
+ bind((lambda) => {
4521
+ if (configs && configs.length) {
4522
+ lambda.addEnvironment("AWSLESS_CONFIG", configs.join(","));
4523
+ lambda.addPermissions({
4524
+ actions: [
4525
+ "ssm:GetParameter",
4526
+ "ssm:GetParameters",
4527
+ "ssm:GetParametersByPath"
4528
+ ],
4529
+ resources: configs.map((name) => {
4530
+ return formatArn({
4531
+ service: "ssm",
4532
+ resource: "parameter",
4533
+ resourceName: configParameterPrefix(config) + "/" + paramCase5(name),
4534
+ seperator: ""
4535
+ });
4536
+ })
4537
+ });
4538
+ }
4539
+ });
4540
+ }
4541
+ });
4542
+
4543
+ // src/plugins/site.ts
4544
+ import { z as z24 } from "zod";
4545
+
4546
+ // src/formation/resource/cloud-front/distribution.ts
4547
+ var Distribution = class extends Resource {
4548
+ constructor(logicalId, props) {
4549
+ super("AWS::CloudFront::Distribution", logicalId);
4550
+ this.props = props;
4551
+ this.name = formatName(this.props.name || logicalId);
4552
+ this.tag("name", this.name);
4553
+ }
4554
+ name;
4555
+ get id() {
4556
+ return getAtt(this.logicalId, "Id");
4557
+ }
4558
+ get arn() {
4559
+ return sub("arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${id}", {
4560
+ id: this.id
4561
+ });
4562
+ }
4563
+ get domainName() {
4564
+ return getAtt(this.logicalId, "DomainName");
4565
+ }
4566
+ properties() {
4567
+ return {
4568
+ DistributionConfig: {
4569
+ Enabled: true,
4570
+ Aliases: this.props.aliases ?? [],
4571
+ PriceClass: "PriceClass_" + (this.props.priceClass ?? "All"),
4572
+ HttpVersion: this.props.httpVersion ?? "http2and3",
4573
+ ViewerCertificate: this.props.certificateArn ? {
4574
+ SslSupportMethod: "sni-only",
4575
+ AcmCertificateArn: this.props.certificateArn
4576
+ } : {},
4577
+ Origins: this.props.origins?.map((origin) => origin.toJSON()) ?? [],
4578
+ OriginGroups: {
4579
+ Quantity: this.props.originGroups?.length ?? 0,
4580
+ Items: this.props.originGroups?.map((originGroup) => originGroup.toJSON()) ?? []
4581
+ },
4582
+ DefaultCacheBehavior: {
4583
+ TargetOriginId: this.props.targetOriginId,
4584
+ ViewerProtocolPolicy: this.props.viewerProtocol ?? "redirect-to-https",
4585
+ AllowedMethods: this.props.allowMethod ?? ["GET", "HEAD", "OPTIONS"],
4586
+ Compress: this.props.compress ?? false,
4587
+ FunctionAssociations: this.props.associations?.map((association) => ({
4588
+ EventType: association.type,
4589
+ FunctionARN: association.functionArn
4590
+ })) ?? [],
4591
+ LambdaFunctionAssociations: this.props.lambdaAssociations?.map((association) => ({
4592
+ EventType: association.type,
4593
+ IncludeBody: association.includeBody ?? false,
4594
+ FunctionARN: association.functionArn
4595
+ })) ?? [],
4596
+ ...this.attr("CachePolicyId", this.props.cachePolicyId),
4597
+ ...this.attr("OriginRequestPolicyId", this.props.originRequestPolicyId),
4598
+ ...this.attr("ResponseHeadersPolicyId", this.props.responseHeadersPolicyId)
4599
+ }
4600
+ }
4601
+ };
4602
+ }
4603
+ };
4604
+ var Origin = class {
4605
+ constructor(props) {
4606
+ this.props = props;
4607
+ }
4608
+ toJSON() {
4609
+ return {
4610
+ Id: this.props.id,
4611
+ DomainName: this.props.domainName,
4612
+ OriginCustomHeaders: Object.entries(this.props.headers ?? {}).map(([name, value]) => ({
4613
+ HeaderName: name,
4614
+ HeaderValue: value
4615
+ })),
4616
+ ...this.props.path ? {
4617
+ OriginPath: this.props.path
4618
+ } : {},
4619
+ ...this.props.protocol ? {
4620
+ CustomOriginConfig: {
4621
+ OriginProtocolPolicy: this.props.protocol
4622
+ }
4623
+ } : {},
4624
+ ...this.props.originAccessIdentityId ? {
4625
+ S3OriginConfig: {
4626
+ OriginAccessIdentity: sub("origin-access-identity/cloudfront/${id}", {
4627
+ id: this.props.originAccessIdentityId
4628
+ })
4629
+ }
4630
+ } : {},
4631
+ ...this.props.originAccessControlId ? {
4632
+ OriginAccessControlId: this.props.originAccessControlId,
4633
+ S3OriginConfig: {
4634
+ OriginAccessIdentity: ""
4635
+ }
4636
+ } : {}
4637
+ };
4638
+ }
4639
+ };
4640
+ var OriginGroup = class {
4641
+ constructor(props) {
4642
+ this.props = props;
4643
+ }
4644
+ toJSON() {
4645
+ return {
4646
+ Id: this.props.id,
4647
+ Members: {
4648
+ Quantity: this.props.members.length,
4649
+ Items: this.props.members.map((member) => ({
4650
+ OriginId: member
4651
+ }))
4652
+ },
4653
+ FailoverCriteria: {
4654
+ StatusCodes: {
4655
+ Quantity: this.props.statusCodes.length,
4656
+ Items: this.props.statusCodes
4657
+ }
4658
+ }
4659
+ };
4660
+ }
4661
+ };
4662
+
4663
+ // src/schema/local-directory.ts
4664
+ import { stat as stat2 } from "fs/promises";
4665
+ import { z as z23 } from "zod";
4666
+ var LocalDirectorySchema = z23.string().refine(async (path) => {
4667
+ try {
4668
+ const s = await stat2(path);
4669
+ return s.isDirectory();
4670
+ } catch (error) {
4671
+ return false;
4672
+ }
4673
+ }, `Directory doesn't exist`);
4674
+
4675
+ // src/formation/resource/cloud-front/origin-request-policy.ts
4676
+ import { camelCase as camelCase4 } from "change-case";
4677
+ var OriginRequestPolicy = class extends Resource {
4678
+ constructor(logicalId, props = {}) {
4679
+ super("AWS::CloudFront::OriginRequestPolicy", logicalId);
4680
+ this.props = props;
4681
+ this.name = formatName(this.props.name || logicalId);
4682
+ }
4683
+ name;
4684
+ get id() {
4685
+ return getAtt(this.logicalId, "Id");
4686
+ }
4687
+ properties() {
4688
+ return {
4689
+ OriginRequestPolicyConfig: {
4690
+ Name: this.name,
4691
+ CookiesConfig: {
4692
+ CookieBehavior: camelCase4(this.props.cookie?.behavior ?? "all"),
4693
+ ...this.attr("Cookies", this.props.cookie?.values)
4694
+ },
4695
+ HeadersConfig: {
4696
+ HeaderBehavior: camelCase4(this.props.header?.behavior ?? "allViewer"),
4697
+ ...this.attr("Headers", this.props.header?.values)
4698
+ },
4699
+ QueryStringsConfig: {
4700
+ QueryStringBehavior: camelCase4(this.props.query?.behavior ?? "all"),
4701
+ ...this.attr("QueryStrings", this.props.query?.values)
4702
+ }
4703
+ }
4704
+ };
4705
+ }
4706
+ };
4707
+
4708
+ // src/formation/resource/cloud-front/cache-policy.ts
4709
+ var CachePolicy = class extends Resource {
4710
+ constructor(logicalId, props) {
4711
+ super("AWS::CloudFront::CachePolicy", logicalId);
4712
+ this.props = props;
4713
+ this.name = formatName(this.props.name || logicalId);
4714
+ }
4715
+ name;
4716
+ get id() {
4717
+ return getAtt(this.logicalId, "Id");
4718
+ }
4719
+ properties() {
4720
+ return {
4721
+ CachePolicyConfig: {
4722
+ Name: this.name,
4723
+ MinTTL: this.props.minTtl.toSeconds(),
4724
+ MaxTTL: this.props.maxTtl.toSeconds(),
4725
+ DefaultTTL: this.props.defaultTtl.toSeconds(),
4726
+ ParametersInCacheKeyAndForwardedToOrigin: {
4727
+ EnableAcceptEncodingGzip: this.props.acceptGzip ?? false,
4728
+ EnableAcceptEncodingBrotli: this.props.acceptBrotli ?? false,
4729
+ CookiesConfig: {
4730
+ CookieBehavior: this.props.cookies ? "whitelist" : "none",
4731
+ ...this.attr("Cookies", this.props.cookies)
4732
+ },
4733
+ HeadersConfig: {
4734
+ HeaderBehavior: this.props.headers ? "whitelist" : "none",
4735
+ ...this.attr("Headers", this.props.headers)
4736
+ },
4737
+ QueryStringsConfig: {
4738
+ QueryStringBehavior: this.props.queries ? "whitelist" : "none",
4739
+ ...this.attr("QueryStrings", this.props.queries)
4740
+ }
4741
+ }
4742
+ }
4743
+ };
4744
+ }
4745
+ };
4746
+
4747
+ // src/formation/resource/s3/files.ts
4748
+ import { Glob } from "glob";
4749
+ import JSZip2 from "jszip";
4750
+ import { createHash as createHash2 } from "crypto";
4751
+ import { createReadStream } from "fs";
4752
+ import { join as join4 } from "path";
4753
+ var Files = class extends Asset {
4754
+ constructor(id, props) {
4755
+ super("bucket", id);
4756
+ this.props = props;
4757
+ }
4758
+ hash;
4759
+ bundle;
4760
+ s3;
4761
+ async build({ write }) {
4762
+ const glob = new Glob(this.props.pattern ?? "**/*", {
4763
+ nodir: true,
4764
+ cwd: this.props.directory
4765
+ });
4766
+ const zip = new JSZip2();
4767
+ const hashes = [];
4768
+ let count = 0;
4769
+ for await (const path of glob) {
4770
+ const file = join4(this.props.directory, path);
4771
+ const stream = createReadStream(file);
4772
+ const hash2 = createHash2("sha1");
4773
+ stream.pipe(hash2);
4774
+ hashes.push(hash2);
4775
+ zip.file(path, stream);
4776
+ count++;
4777
+ }
4778
+ this.bundle = await zip.generateAsync({
4779
+ type: "nodebuffer",
4780
+ compression: "DEFLATE",
4781
+ compressionOptions: {
4782
+ level: 9
4783
+ }
4784
+ });
4785
+ const hash = createHash2("sha1");
4786
+ for (const item of hashes) {
4787
+ hash.update(item.digest());
4788
+ }
4789
+ this.hash = hash.digest("hex");
4790
+ await write("HASH", this.hash);
4791
+ await write("bundle.zip", this.bundle);
4792
+ return {
4793
+ files: style.success(String(count)),
4794
+ size: formatByteSize(this.bundle.byteLength)
4795
+ };
4796
+ }
4797
+ async publish({ publish }) {
4798
+ this.s3 = await publish(
4799
+ `${this.id}.zip`,
4800
+ this.bundle,
4801
+ this.hash
4802
+ );
4803
+ }
4804
+ get source() {
4805
+ return this.s3;
4806
+ }
4807
+ };
4808
+
4809
+ // src/formation/resource/s3/bucket-policy.ts
4810
+ import { capitalCase } from "change-case";
4811
+ var BucketPolicy = class extends Resource {
4812
+ constructor(logicalId, props) {
4813
+ super("AWS::S3::BucketPolicy", logicalId);
4814
+ this.props = props;
4815
+ }
4816
+ properties() {
4817
+ return {
4818
+ Bucket: formatName(this.props.bucketName),
4819
+ PolicyDocument: {
4820
+ Version: this.props.version ?? "2012-10-17",
4821
+ Statement: this.props.statements.map((statement) => ({
4822
+ Effect: capitalCase(statement.effect ?? "allow"),
4823
+ ...statement.principal ? {
4824
+ Principal: {
4825
+ Service: statement.principal
4826
+ }
4827
+ } : {},
4828
+ Action: statement.actions,
4829
+ Resource: statement.resources,
4830
+ ...statement.sourceArn ? {
4831
+ Condition: {
4832
+ StringEquals: {
4833
+ "AWS:SourceArn": statement.sourceArn
4834
+ }
4835
+ }
4836
+ } : {}
4837
+ }))
4838
+ }
4839
+ };
4840
+ }
4841
+ };
3933
4842
 
3934
- // src/formation/resource/memorydb/cluster.ts
3935
- var Cluster = class extends Resource {
4843
+ // src/formation/resource/cloud-front/origin-access-control.ts
4844
+ var OriginAccessControl = class extends Resource {
3936
4845
  constructor(logicalId, props) {
3937
- super("AWS::MemoryDB::Cluster", logicalId);
4846
+ super("AWS::CloudFront::OriginAccessControl", logicalId);
3938
4847
  this.props = props;
3939
4848
  this.name = formatName(this.props.name || logicalId);
3940
- this.tag("name", this.name);
3941
4849
  }
3942
4850
  name;
3943
- get status() {
3944
- return this.getAtt("Status");
3945
- }
3946
- get arn() {
3947
- return this.getAtt("ARN");
3948
- }
3949
- get address() {
3950
- return this.getAtt("ClusterEndpoint.Address");
3951
- }
3952
- get port() {
3953
- return this.getAtt("ClusterEndpoint.Port");
4851
+ get id() {
4852
+ return getAtt(this.logicalId, "Id");
3954
4853
  }
3955
4854
  properties() {
3956
4855
  return {
3957
- ClusterName: this.name,
3958
- ClusterEndpoint: {
3959
- Port: this.props.port
3960
- },
3961
- Port: this.props.port,
3962
- ...this.attr("Description", this.props.description),
3963
- ACLName: this.props.aclName,
3964
- EngineVersion: this.props.engine ?? "7.0",
3965
- ...this.attr("SubnetGroupName", this.props.subnetGroupName),
3966
- ...this.attr("SecurityGroupIds", this.props.securityGroupIds),
3967
- NodeType: "db." + this.props.type,
3968
- NumReplicasPerShard: this.props.replicasPerShard ?? 1,
3969
- NumShards: this.props.shards ?? 1,
3970
- TLSEnabled: this.props.tls ?? false,
3971
- DataTiering: this.props.dataTiering ? "true" : "false",
3972
- AutoMinorVersionUpgrade: this.props.autoMinorVersionUpgrade ?? true,
3973
- MaintenanceWindow: this.props.maintenanceWindow ?? "Sat:02:00-Sat:05:00"
4856
+ OriginAccessControlConfig: {
4857
+ Name: this.name,
4858
+ OriginAccessControlOriginType: this.props.type,
4859
+ SigningBehavior: this.props.behavior ?? "always",
4860
+ SigningProtocol: this.props.protocol ?? "sigv4"
4861
+ }
3974
4862
  };
3975
4863
  }
3976
4864
  };
3977
4865
 
3978
- // src/formation/resource/memorydb/subnet-group.ts
3979
- var SubnetGroup = class extends Resource {
4866
+ // src/formation/resource/cloud-front/response-headers-policy.ts
4867
+ var ResponseHeadersPolicy = class extends Resource {
3980
4868
  constructor(logicalId, props) {
3981
- super("AWS::MemoryDB::SubnetGroup", logicalId);
4869
+ super("AWS::CloudFront::ResponseHeadersPolicy", logicalId);
3982
4870
  this.props = props;
3983
4871
  this.name = formatName(this.props.name || logicalId);
3984
4872
  }
3985
4873
  name;
3986
- get arn() {
3987
- return getAtt(this.logicalId, "Arn");
4874
+ get id() {
4875
+ return getAtt(this.logicalId, "Id");
3988
4876
  }
3989
4877
  properties() {
3990
4878
  return {
3991
- SubnetGroupName: this.name,
3992
- SubnetIds: this.props.subnetIds,
3993
- ...this.attr("Description", this.props.description)
4879
+ ResponseHeadersPolicyConfig: {
4880
+ Name: this.name,
4881
+ ...this.props.remove && this.props.remove.length > 0 ? {
4882
+ RemoveHeadersConfig: {
4883
+ Items: this.props.remove?.map((value) => ({
4884
+ Header: value
4885
+ }))
4886
+ }
4887
+ } : {},
4888
+ CorsConfig: {
4889
+ OriginOverride: this.props.cors?.override ?? false,
4890
+ AccessControlAllowCredentials: this.props.cors?.credentials ?? false,
4891
+ AccessControlMaxAgeSec: this.props.cors?.maxAge?.toSeconds() ?? Duration.days(365).toSeconds(),
4892
+ AccessControlAllowHeaders: {
4893
+ Items: this.props.cors?.headers ?? ["*"]
4894
+ },
4895
+ AccessControlAllowMethods: {
4896
+ Items: this.props.cors?.methods ?? ["ALL"]
4897
+ },
4898
+ AccessControlAllowOrigins: {
4899
+ Items: this.props.cors?.origins ?? ["*"]
4900
+ },
4901
+ AccessControlExposeHeaders: {
4902
+ Items: this.props.cors?.exposeHeaders ?? ["*"]
4903
+ }
4904
+ },
4905
+ SecurityHeadersConfig: {
4906
+ ...this.props.contentSecurityPolicy ? {
4907
+ ContentSecurityPolicy: {
4908
+ Override: this.props.contentSecurityPolicy?.override ?? false,
4909
+ ContentSecurityPolicy: this.props.contentSecurityPolicy?.contentSecurityPolicy
4910
+ }
4911
+ } : {},
4912
+ ContentTypeOptions: {
4913
+ Override: this.props.contentTypeOptions?.override ?? false
4914
+ },
4915
+ FrameOptions: {
4916
+ Override: this.props.frameOptions?.override ?? false,
4917
+ FrameOption: (this.props.frameOptions?.frameOption ?? "same-origin") === "same-origin" ? "SAMEORIGIN" : "DENY"
4918
+ },
4919
+ ReferrerPolicy: {
4920
+ Override: this.props.referrerPolicy?.override ?? false,
4921
+ ReferrerPolicy: this.props.referrerPolicy?.referrerPolicy ?? "same-origin"
4922
+ },
4923
+ StrictTransportSecurity: {
4924
+ Override: this.props.strictTransportSecurity?.override ?? false,
4925
+ Preload: this.props.strictTransportSecurity?.preload ?? true,
4926
+ AccessControlMaxAgeSec: this.props.strictTransportSecurity?.maxAge?.toSeconds() ?? 31536e3,
4927
+ IncludeSubdomains: this.props.strictTransportSecurity?.includeSubdomains ?? true
4928
+ },
4929
+ XSSProtection: {
4930
+ Override: this.props.xssProtection?.override ?? false,
4931
+ ModeBlock: this.props.xssProtection?.modeBlock ?? true,
4932
+ Protection: this.props.xssProtection?.enable ?? true,
4933
+ ...this.attr("ReportUri", this.props.xssProtection?.reportUri)
4934
+ }
4935
+ }
4936
+ }
3994
4937
  };
3995
4938
  }
3996
4939
  };
3997
4940
 
3998
- // src/plugins/cache.ts
3999
- import { constantCase as constantCase6 } from "change-case";
4000
- var TypeSchema = z19.enum([
4001
- "t4g.small",
4002
- "t4g.medium",
4003
- "r6g.large",
4004
- "r6g.xlarge",
4005
- "r6g.2xlarge",
4006
- "r6g.4xlarge",
4007
- "r6g.8xlarge",
4008
- "r6g.12xlarge",
4009
- "r6g.16xlarge",
4010
- "r6gd.xlarge",
4011
- "r6gd.2xlarge",
4012
- "r6gd.4xlarge",
4013
- "r6gd.8xlarge"
4014
- ]);
4015
- var PortSchema = z19.number().int().min(1).max(5e4);
4016
- var ShardsSchema = z19.number().int().min(0).max(100);
4017
- var ReplicasPerShardSchema = z19.number().int().min(0).max(5);
4018
- var EngineSchema = z19.enum(["7.0", "6.2"]);
4019
- var cachePlugin = definePlugin({
4020
- name: "cache",
4021
- schema: z19.object({
4022
- stacks: z19.object({
4023
- /** Define the caches in your stack.
4024
- * For access to the cache put your functions inside the global VPC.
4941
+ // src/plugins/site.ts
4942
+ var sitePlugin = definePlugin({
4943
+ name: "site",
4944
+ schema: z24.object({
4945
+ stacks: z24.object({
4946
+ /** Define the sites in your stack.
4025
4947
  * @example
4026
4948
  * {
4027
- * caches: {
4028
- * CACHE_NAME: {
4029
- * type: 't4g.small'
4949
+ * sites: {
4950
+ * SITE_NAME: {
4951
+ * static: 'dist/client'
4952
+ * ssr: 'dist/server/index.js'
4030
4953
  * }
4031
4954
  * }
4032
4955
  * }
4033
- */
4034
- caches: z19.record(
4956
+ * */
4957
+ sites: z24.record(
4035
4958
  ResourceIdSchema,
4036
- z19.object({
4037
- type: TypeSchema.default("t4g.small"),
4038
- port: PortSchema.default(6379),
4039
- shards: ShardsSchema.default(1),
4040
- replicasPerShard: ReplicasPerShardSchema.default(1),
4041
- engine: EngineSchema.default("7.0"),
4042
- dataTiering: z19.boolean().default(false)
4959
+ z24.object({
4960
+ /** The domain to link your site with. */
4961
+ domain: z24.string(),
4962
+ subDomain: z24.string().optional(),
4963
+ /** Specifies the path to the static files directory. */
4964
+ static: LocalDirectorySchema.optional(),
4965
+ /** Specifies the ssr file. */
4966
+ ssr: FunctionSchema.optional(),
4967
+ /** Define the cors headers. */
4968
+ cors: z24.object({
4969
+ override: z24.boolean().default(false),
4970
+ maxAge: DurationSchema.default("365 days"),
4971
+ exposeHeaders: z24.string().array().optional(),
4972
+ credentials: z24.boolean().default(false),
4973
+ headers: z24.string().array().default(["*"]),
4974
+ origins: z24.string().array().default(["*"]),
4975
+ methods: z24.enum(["GET", "DELETE", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "ALL"]).array().default(["ALL"])
4976
+ }).optional(),
4977
+ /** Define the cors headers. */
4978
+ security: z24.object({
4979
+ // contentSecurityPolicy: z.object({
4980
+ // override: z.boolean().default(false),
4981
+ // policy: z.string(),
4982
+ // })
4983
+ // contentSecurityPolicy?: {
4984
+ // override?: boolean
4985
+ // contentSecurityPolicy: string
4986
+ // }
4987
+ // contentTypeOptions?: {
4988
+ // override?: boolean
4989
+ // }
4990
+ // frameOptions?: {
4991
+ // override?: boolean
4992
+ // frameOption?: 'deny' | 'same-origin'
4993
+ // }
4994
+ // referrerPolicy?: {
4995
+ // override?: boolean
4996
+ // referrerPolicy?: (
4997
+ // 'no-referrer' |
4998
+ // 'no-referrer-when-downgrade' |
4999
+ // 'origin' |
5000
+ // 'origin-when-cross-origin' |
5001
+ // 'same-origin' |
5002
+ // 'strict-origin' |
5003
+ // 'strict-origin-when-cross-origin' |
5004
+ // 'unsafe-url'
5005
+ // )
5006
+ // }
5007
+ // strictTransportSecurity?: {
5008
+ // maxAge?: Duration
5009
+ // includeSubdomains?: boolean
5010
+ // override?: boolean
5011
+ // preload?: boolean
5012
+ // }
5013
+ // xssProtection?: {
5014
+ // override?: boolean
5015
+ // enable?: boolean
5016
+ // modeBlock?: boolean
5017
+ // reportUri?: string
5018
+ // }
5019
+ }).optional(),
5020
+ /** Specifies the cookies, headers, and query values that CloudFront includes in the cache key. */
5021
+ cache: z24.object({
5022
+ /** Specifies the cookies that CloudFront includes in the cache key. */
5023
+ cookies: z24.string().array().optional(),
5024
+ /** Specifies the headers that CloudFront includes in the cache key. */
5025
+ headers: z24.string().array().optional(),
5026
+ /** Specifies the query values that CloudFront includes in the cache key. */
5027
+ queries: z24.string().array().optional()
5028
+ }).optional()
4043
5029
  })
4044
5030
  ).optional()
4045
5031
  }).array()
4046
5032
  }),
4047
- onTypeGen({ config }) {
4048
- const gen = new TypeGen("@awsless/awsless", "CacheResources");
4049
- for (const stack of config.stacks) {
4050
- const list3 = new TypeObject();
4051
- for (const name of Object.keys(stack.caches || {})) {
4052
- list3.addType(name, `{ host: string, port: number }`);
5033
+ onStack(ctx) {
5034
+ const { config, stack, stackConfig, bootstrap: bootstrap2 } = ctx;
5035
+ for (const [id, props] of Object.entries(stackConfig.sites || {})) {
5036
+ const origins = [];
5037
+ const originGroups = [];
5038
+ const deps = [];
5039
+ let bucket;
5040
+ if (props.ssr) {
5041
+ const lambda = toLambdaFunction(ctx, `site-${id}`, props.ssr);
5042
+ const permissions = new Permission2(`site-${id}`, {
5043
+ principal: "*",
5044
+ // principal: 'cloudfront.amazonaws.com',
5045
+ action: "lambda:InvokeFunctionUrl",
5046
+ functionArn: lambda.arn,
5047
+ urlAuthType: "none"
5048
+ // sourceArn: distribution.arn,
5049
+ }).dependsOn(lambda);
5050
+ const url = lambda.addUrl();
5051
+ stack.add(url, lambda, permissions);
5052
+ origins.push(new Origin({
5053
+ id: "lambda",
5054
+ domainName: select(2, split("/", url.url)),
5055
+ protocol: "https-only"
5056
+ }));
5057
+ deps.push(lambda, url, permissions);
4053
5058
  }
4054
- gen.addType(stack.name, list3.toString());
4055
- }
4056
- return gen.toString();
4057
- },
4058
- onStack({ config, stack, stackConfig, bootstrap: bootstrap2, bind }) {
4059
- for (const [id, props] of Object.entries(stackConfig.caches || {})) {
4060
- const name = `${config.name}-${stack.name}-${id}`;
4061
- const subnetGroup = new SubnetGroup(id, {
4062
- name,
4063
- subnetIds: [
4064
- bootstrap2.import(`private-subnet-1`),
4065
- bootstrap2.import(`private-subnet-2`)
4066
- ]
5059
+ if (props.static) {
5060
+ bucket = new Bucket(`site-${id}`, {
5061
+ // name: props.domain,
5062
+ name: `site-${config.name}-${stack.name}-${id}`,
5063
+ accessControl: "private",
5064
+ website: {
5065
+ indexDocument: "index.html",
5066
+ errorDocument: props.ssr ? void 0 : "error.html"
5067
+ }
5068
+ });
5069
+ const accessControl = new OriginAccessControl(`site-${id}`, {
5070
+ type: "s3"
5071
+ });
5072
+ const files = new Files(`site-${id}`, {
5073
+ directory: props.static
5074
+ });
5075
+ const uploadBucketAsset = new CustomResource(`site-${id}-upload-bucket-asset`, {
5076
+ serviceToken: bootstrap2.import("feature-upload-bucket-asset"),
5077
+ properties: {
5078
+ sourceBucketName: lazy(() => files.source?.bucket ?? ""),
5079
+ sourceObjectKey: lazy(() => files.source?.key ?? ""),
5080
+ sourceObjectVersion: lazy(() => files.source?.version ?? ""),
5081
+ destinationBucketName: bucket.name
5082
+ }
5083
+ }).dependsOn(bucket);
5084
+ const deleteBucket = new CustomResource(id, {
5085
+ serviceToken: bootstrap2.import("feature-delete-bucket"),
5086
+ properties: {
5087
+ bucketName: bucket.name
5088
+ }
5089
+ }).dependsOn(bucket);
5090
+ stack.add(bucket, files, uploadBucketAsset, deleteBucket, accessControl);
5091
+ origins.push(new Origin({
5092
+ id: "bucket",
5093
+ // domainName: select(2, split('/', bucket.url)),
5094
+ domainName: bucket.domainName,
5095
+ originAccessControlId: accessControl.id
5096
+ }));
5097
+ deps.push(bucket, accessControl);
5098
+ }
5099
+ if (props.ssr && props.static) {
5100
+ originGroups.push(new OriginGroup({
5101
+ id: "group",
5102
+ members: ["lambda", "bucket"],
5103
+ statusCodes: [403, 404]
5104
+ }));
5105
+ }
5106
+ const cache = new CachePolicy(id, {
5107
+ name: `site-${config.name}-${stack.name}-${id}`,
5108
+ minTtl: Duration.seconds(1),
5109
+ maxTtl: Duration.days(365),
5110
+ defaultTtl: Duration.days(1),
5111
+ ...props.cache
4067
5112
  });
4068
- const securityGroup = new SecurityGroup(id, {
4069
- name,
4070
- vpcId: bootstrap2.import(`vpc-id`),
4071
- description: name
5113
+ const originRequest = new OriginRequestPolicy(id, {
5114
+ name: `site-${config.name}-${stack.name}-${id}`,
5115
+ header: {
5116
+ behavior: "all-except",
5117
+ values: ["HOST"]
5118
+ }
4072
5119
  });
4073
- const port = Port.tcp(props.port);
4074
- securityGroup.addIngressRule(Peer.anyIpv4(), port);
4075
- securityGroup.addIngressRule(Peer.anyIpv6(), port);
4076
- const cluster = new Cluster(id, {
4077
- name,
4078
- aclName: "open-access",
4079
- securityGroupIds: [securityGroup.id],
4080
- subnetGroupName: subnetGroup.name,
4081
- ...props
4082
- }).dependsOn(subnetGroup, securityGroup);
4083
- stack.add(subnetGroup, securityGroup, cluster);
4084
- bind((lambda) => {
4085
- lambda.addEnvironment(`CACHE_${constantCase6(stack.name)}_${constantCase6(id)}_HOST`, cluster.address).addEnvironment(`CACHE_${constantCase6(stack.name)}_${constantCase6(id)}_PORT`, props.port.toString());
5120
+ const domainName = props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain;
5121
+ const hostedZoneId = bootstrap2.import(`hosted-zone-${props.domain}-id`);
5122
+ const certificateArn = bootstrap2.import(`us-east-certificate-${props.domain}-arn`);
5123
+ const responseHeaders = new ResponseHeadersPolicy(id, {
5124
+ name: `site-${config.name}-${stack.name}-${id}`,
5125
+ cors: props.cors,
5126
+ remove: ["server"]
4086
5127
  });
5128
+ const distribution = new Distribution(id, {
5129
+ name: `site-${config.name}-${stack.name}-${id}`,
5130
+ certificateArn,
5131
+ compress: true,
5132
+ aliases: [domainName],
5133
+ origins,
5134
+ originGroups,
5135
+ targetOriginId: props.ssr && props.static ? "group" : props.ssr ? "lambda" : "bucket",
5136
+ originRequestPolicyId: originRequest.id,
5137
+ cachePolicyId: cache.id,
5138
+ responseHeadersPolicyId: responseHeaders.id
5139
+ }).dependsOn(originRequest, responseHeaders, cache, ...deps);
5140
+ if (props.static) {
5141
+ const bucketPolicy = new BucketPolicy(`site-${id}`, {
5142
+ bucketName: bucket.name,
5143
+ statements: [
5144
+ {
5145
+ principal: "cloudfront.amazonaws.com",
5146
+ actions: ["s3:GetObject"],
5147
+ resources: [sub("${arn}/*", { arn: bucket.arn })],
5148
+ sourceArn: distribution.arn
5149
+ }
5150
+ // {
5151
+ // principal: distribution.id,
5152
+ // actions: [ 's3:GetObject' ],
5153
+ // resources: [ oac.attrId ]
5154
+ // }
5155
+ ]
5156
+ }).dependsOn(bucket, distribution);
5157
+ stack.add(bucketPolicy);
5158
+ }
5159
+ const record = new RecordSet(`site-${id}`, {
5160
+ hostedZoneId,
5161
+ type: "A",
5162
+ name: domainName,
5163
+ alias: {
5164
+ dnsName: distribution.domainName,
5165
+ hostedZoneId: "Z2FDTNDATAQYW2"
5166
+ }
5167
+ }).dependsOn(distribution);
5168
+ stack.add(
5169
+ distribution,
5170
+ responseHeaders,
5171
+ originRequest,
5172
+ cache,
5173
+ record
5174
+ );
4087
5175
  }
4088
5176
  }
4089
5177
  });
4090
5178
 
5179
+ // src/plugins/feature.ts
5180
+ var featurePlugin = definePlugin({
5181
+ name: "feature",
5182
+ onApp({ config, bootstrap: bootstrap2 }) {
5183
+ const deleteBucketLambda = new Function("delete-bucket", {
5184
+ name: `${config.name}-delete-bucket`,
5185
+ code: Code.fromFeature("delete-bucket")
5186
+ }).enableLogs(Duration.days(3)).addPermissions({
5187
+ actions: ["s3:*"],
5188
+ resources: ["*"]
5189
+ });
5190
+ const uploadBucketAssetLambda = new Function("upload-bucket-asset", {
5191
+ name: `${config.name}-upload-bucket-asset`,
5192
+ code: Code.fromFeature("upload-bucket-asset"),
5193
+ memorySize: Size.gigaBytes(2)
5194
+ }).enableLogs(Duration.days(3)).addPermissions({
5195
+ actions: ["s3:*"],
5196
+ resources: ["*"]
5197
+ });
5198
+ bootstrap2.add(
5199
+ deleteBucketLambda,
5200
+ uploadBucketAssetLambda
5201
+ );
5202
+ bootstrap2.export("feature-delete-bucket", deleteBucketLambda.arn).export("feature-upload-bucket-asset", uploadBucketAssetLambda.arn);
5203
+ }
5204
+ });
5205
+
4091
5206
  // src/plugins/index.ts
4092
5207
  var defaultPlugins = [
4093
5208
  extendPlugin,
5209
+ featurePlugin,
4094
5210
  vpcPlugin,
5211
+ domainPlugin,
4095
5212
  functionPlugin,
5213
+ configPlugin,
4096
5214
  cachePlugin,
4097
5215
  cronPlugin,
4098
5216
  queuePlugin,
@@ -4101,9 +5219,10 @@ var defaultPlugins = [
4101
5219
  topicPlugin,
4102
5220
  pubsubPlugin,
4103
5221
  searchPlugin,
4104
- domainPlugin,
4105
5222
  graphqlPlugin,
4106
5223
  httpPlugin,
5224
+ restPlugin,
5225
+ sitePlugin,
4107
5226
  onFailurePlugin
4108
5227
  ];
4109
5228
 
@@ -4134,106 +5253,6 @@ var App = class {
4134
5253
  // }
4135
5254
  };
4136
5255
 
4137
- // src/custom/global-export/handler.ts
4138
- var globalExportsHandlerCode = (
4139
- /* JS */
4140
- `
4141
-
4142
- const { CloudFormationClient, ListExportsCommand } = require('@aws-sdk/client-cloudformation')
4143
-
4144
- exports.handler = async (event) => {
4145
- const region = event.ResourceProperties.region
4146
-
4147
- try {
4148
- const data = await listExports(region)
4149
-
4150
- await send(event, region, 'SUCCESS', data)
4151
- } catch(error) {
4152
- if (error instanceof Error) {
4153
- await send(event, region, 'FAILED', {}, error.message)
4154
- } else {
4155
- await send(event, region, 'FAILED', {}, 'Unknown error')
4156
- }
4157
- }
4158
- }
4159
-
4160
- const send = async (event, id, status, data, reason = '') => {
4161
- const body = JSON.stringify({
4162
- Status: status,
4163
- Reason: reason,
4164
- PhysicalResourceId: id,
4165
- StackId: event.StackId,
4166
- RequestId: event.RequestId,
4167
- LogicalResourceId: event.LogicalResourceId,
4168
- NoEcho: false,
4169
- Data: data
4170
- })
4171
-
4172
- await fetch(event.ResponseURL, {
4173
- method: 'PUT',
4174
- port: 443,
4175
- body,
4176
- headers: {
4177
- 'content-type': '',
4178
- 'content-length': Buffer.from(body).byteLength,
4179
- },
4180
- })
4181
- }
4182
-
4183
- const listExports = async (region) => {
4184
- const client = new CloudFormationClient({ region })
4185
- const data = {}
4186
-
4187
- let token
4188
-
4189
- while(true) {
4190
- const result = await client.send(new ListExportsCommand({
4191
- NextToken: token
4192
- }))
4193
-
4194
- result.Exports?.forEach(item => {
4195
- data[item.Name] = item.Value
4196
- })
4197
-
4198
- if(result.NextToken) {
4199
- token = result.NextToken
4200
- } else {
4201
- return data
4202
- }
4203
- }
4204
- }
4205
- `
4206
- );
4207
-
4208
- // src/custom/global-export/extend.ts
4209
- var extendWithGlobalExports = (appName, importable, exportable) => {
4210
- let crossRegionExports;
4211
- importable.import = (name) => {
4212
- name = formatName(name);
4213
- if (!importable.exports.has(name)) {
4214
- throw new TypeError(`Undefined global export value: ${name}`);
4215
- }
4216
- if (!crossRegionExports) {
4217
- const lambda = new Function("global-exports", {
4218
- name: `${appName}-global-exports`,
4219
- code: Code.fromInline(globalExportsHandlerCode, "index.handler")
4220
- });
4221
- lambda.addPermissions({
4222
- actions: ["cloudformation:ListExports"],
4223
- resources: ["*"]
4224
- });
4225
- crossRegionExports = new CustomResource("global-exports", {
4226
- serviceToken: lambda.arn,
4227
- properties: {
4228
- region: importable.region
4229
- }
4230
- });
4231
- exportable.add(lambda, crossRegionExports);
4232
- }
4233
- return crossRegionExports.getAtt(name);
4234
- };
4235
- };
4236
-
4237
5256
  // src/app.ts
4238
5257
  var getAllDepends = (filters) => {
4239
5258
  const list3 = [];
@@ -4256,7 +5275,6 @@ var toApp = async (config, filters) => {
4256
5275
  debug("Plugins detected:", plugins.map((plugin) => style.info(plugin.name)).join(", "));
4257
5276
  const bootstrap2 = new Stack("bootstrap", config.region);
4258
5277
  const usEastBootstrap = new Stack("us-east-bootstrap", "us-east-1");
4259
- extendWithGlobalExports(config.name, usEastBootstrap, bootstrap2);
4260
5278
  app.add(bootstrap2, usEastBootstrap);
4261
5279
  debug("Run plugin onApp listeners");
4262
5280
  const bindings = [];
@@ -4338,7 +5356,7 @@ var toApp = async (config, filters) => {
4338
5356
  };
4339
5357
 
4340
5358
  // src/config.ts
4341
- import { join as join4 } from "path";
5359
+ import { join as join6 } from "path";
4342
5360
 
4343
5361
  // src/util/account.ts
4344
5362
  import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
@@ -4357,17 +5375,17 @@ var getCredentials = (profile) => {
4357
5375
  };
4358
5376
 
4359
5377
  // src/schema/app.ts
4360
- import { z as z23 } from "zod";
5378
+ import { z as z28 } from "zod";
4361
5379
 
4362
5380
  // src/schema/stack.ts
4363
- import { z as z20 } from "zod";
4364
- var StackSchema = z20.object({
5381
+ import { z as z25 } from "zod";
5382
+ var StackSchema = z25.object({
4365
5383
  name: ResourceIdSchema,
4366
- depends: z20.array(z20.lazy(() => StackSchema)).optional()
5384
+ depends: z25.array(z25.lazy(() => StackSchema)).optional()
4367
5385
  });
4368
5386
 
4369
5387
  // src/schema/region.ts
4370
- import { z as z21 } from "zod";
5388
+ import { z as z26 } from "zod";
4371
5389
  var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
4372
5390
  var AF = ["af-south-1"];
4373
5391
  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"];
@@ -4384,41 +5402,41 @@ var regions = [
4384
5402
  ...ME,
4385
5403
  ...SA
4386
5404
  ];
4387
- var RegionSchema = z21.enum(regions);
5405
+ var RegionSchema = z26.enum(regions);
4388
5406
 
4389
5407
  // src/schema/plugin.ts
4390
- import { z as z22 } from "zod";
4391
- var PluginSchema = z22.object({
4392
- name: z22.string(),
4393
- schema: z22.custom().optional(),
5408
+ import { z as z27 } from "zod";
5409
+ var PluginSchema = z27.object({
5410
+ name: z27.string(),
5411
+ schema: z27.custom().optional(),
4394
5412
  // depends: z.array(z.lazy(() => PluginSchema)).optional(),
4395
- onApp: z22.function().returns(z22.void()).optional(),
4396
- onStack: z22.function().returns(z22.any()).optional(),
4397
- onResource: z22.function().returns(z22.any()).optional()
5413
+ onApp: z27.function().returns(z27.void()).optional(),
5414
+ onStack: z27.function().returns(z27.any()).optional(),
5415
+ onResource: z27.function().returns(z27.any()).optional()
4398
5416
  // bind: z.function().optional(),
4399
5417
  });
4400
5418
 
4401
5419
  // src/schema/app.ts
4402
- var AppSchema = z23.object({
5420
+ var AppSchema = z28.object({
4403
5421
  /** App name */
4404
5422
  name: ResourceIdSchema,
4405
5423
  /** The AWS region to deploy to. */
4406
5424
  region: RegionSchema,
4407
5425
  /** The AWS profile to deploy to. */
4408
- profile: z23.string(),
5426
+ profile: z28.string(),
4409
5427
  /** The deployment stage.
4410
5428
  * @default 'prod'
4411
5429
  */
4412
- stage: z23.string().regex(/^[a-z]+$/).default("prod"),
5430
+ stage: z28.string().regex(/^[a-z]+$/).default("prod"),
4413
5431
  /** Default properties. */
4414
- defaults: z23.object({}).default({}),
5432
+ defaults: z28.object({}).default({}),
4415
5433
  /** The application stacks. */
4416
- stacks: z23.array(StackSchema).min(1).refine((stacks) => {
5434
+ stacks: z28.array(StackSchema).min(1).refine((stacks) => {
4417
5435
  const unique = new Set(stacks.map((stack) => stack.name));
4418
5436
  return unique.size === stacks.length;
4419
5437
  }, "Must be an array of unique stacks"),
4420
5438
  /** Custom plugins. */
4421
- plugins: z23.array(PluginSchema).optional()
5439
+ plugins: z28.array(PluginSchema).optional()
4422
5440
  });
4423
5441
 
4424
5442
  // src/util/import.ts
@@ -4426,7 +5444,7 @@ import { rollup as rollup2, watch } from "rollup";
4426
5444
  import { swc as swc2 } from "rollup-plugin-swc3";
4427
5445
  import replace from "rollup-plugin-replace";
4428
5446
  import { EventIterator } from "event-iterator";
4429
- import { dirname as dirname2, join as join3 } from "path";
5447
+ import { dirname as dirname2, join as join5 } from "path";
4430
5448
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
4431
5449
  var importFile = async (path) => {
4432
5450
  const bundle = await rollup2({
@@ -4446,7 +5464,7 @@ var importFile = async (path) => {
4446
5464
  })
4447
5465
  ]
4448
5466
  });
4449
- const outputFile = join3(directories.cache, "config.js");
5467
+ const outputFile = join5(directories.cache, "config.js");
4450
5468
  const result = await bundle.generate({
4451
5469
  format: "esm",
4452
5470
  exports: "default"
@@ -4497,7 +5515,7 @@ var watchFile = (path) => {
4497
5515
  event.result.close();
4498
5516
  const output = result.output[0];
4499
5517
  const code = output.code;
4500
- const outputFile = join3(directories.cache, "config.js");
5518
+ const outputFile = join5(directories.cache, "config.js");
4501
5519
  await mkdir2(directories.cache, { recursive: true });
4502
5520
  await writeFile2(outputFile, code);
4503
5521
  debug("Save config file:", style.info(outputFile));
@@ -4515,7 +5533,7 @@ var watchFile = (path) => {
4515
5533
  };
4516
5534
 
4517
5535
  // src/config.ts
4518
- import { z as z24 } from "zod";
5536
+ import { z as z29 } from "zod";
4519
5537
  var ConfigError = class extends Error {
4520
5538
  constructor(error, data) {
4521
5539
  super(error.message);
@@ -4530,7 +5548,7 @@ var importConfig = async (options) => {
4530
5548
  setRoot(root2);
4531
5549
  debug("CWD:", style.info(root2));
4532
5550
  debug("Import config file");
4533
- const fileName = join4(root2, configFile);
5551
+ const fileName = join6(root2, configFile);
4534
5552
  const module = await importFile(fileName);
4535
5553
  const appConfig = typeof module.default === "function" ? await module.default(options) : module.default;
4536
5554
  debug("Validate config file");
@@ -4548,7 +5566,7 @@ var importConfig = async (options) => {
4548
5566
  try {
4549
5567
  config = await schema2.parseAsync(appConfig);
4550
5568
  } catch (error) {
4551
- if (error instanceof z24.ZodError) {
5569
+ if (error instanceof z29.ZodError) {
4552
5570
  throw new ConfigError(error, appConfig);
4553
5571
  }
4554
5572
  throw error;
@@ -4571,7 +5589,7 @@ var watchConfig = async function* (options) {
4571
5589
  setRoot(root2);
4572
5590
  debug("CWD:", style.info(root2));
4573
5591
  debug("Import config file");
4574
- const fileName = join4(root2, configFile);
5592
+ const fileName = join6(root2, configFile);
4575
5593
  for await (const module of watchFile(fileName)) {
4576
5594
  const appConfig = typeof module.default === "function" ? await module.default(options) : module.default;
4577
5595
  debug("Validate config file");
@@ -4589,7 +5607,7 @@ var watchConfig = async function* (options) {
4589
5607
  try {
4590
5608
  config = await schema2.parseAsync(appConfig);
4591
5609
  } catch (error) {
4592
- if (error instanceof z24.ZodError) {
5610
+ if (error instanceof z29.ZodError) {
4593
5611
  throw new ConfigError(error, appConfig);
4594
5612
  }
4595
5613
  throw error;
@@ -5080,7 +6098,6 @@ var format = (value) => {
5080
6098
  };
5081
6099
  var zodError = (error, data) => {
5082
6100
  return (term) => {
5083
- term.out.write(JSON.stringify(error.errors));
5084
6101
  for (const issue of error.issues) {
5085
6102
  term.out.gap();
5086
6103
  term.out.write(dialog("error", [
@@ -5187,7 +6204,7 @@ var flexLine = (term, left, right, reserveSpace = 0) => {
5187
6204
  };
5188
6205
 
5189
6206
  // src/cli/ui/complex/builder.ts
5190
- import { dirname as dirname3, join as join5 } from "path";
6207
+ import { dirname as dirname3, join as join7 } from "path";
5191
6208
  var assetBuilder = (app) => {
5192
6209
  return async (term) => {
5193
6210
  const assets = [];
@@ -5250,7 +6267,7 @@ var assetBuilder = (app) => {
5250
6267
  try {
5251
6268
  const data = await asset.build({
5252
6269
  async write(file, data2) {
5253
- const fullpath = join5(directories.asset, asset.type, app.name, stack.name, asset.id, file);
6270
+ const fullpath = join7(directories.asset, asset.type, app.name, stack.name, asset.id, file);
5254
6271
  const basepath = dirname3(fullpath);
5255
6272
  await mkdir3(basepath, { recursive: true });
5256
6273
  await writeFile3(fullpath, data2);
@@ -5298,14 +6315,14 @@ var cleanUp = async () => {
5298
6315
 
5299
6316
  // src/cli/ui/complex/template.ts
5300
6317
  import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
5301
- import { join as join6 } from "path";
6318
+ import { join as join8 } from "path";
5302
6319
  var templateBuilder = (app) => {
5303
6320
  return async (term) => {
5304
6321
  const done = term.out.write(loadingDialog("Building stack templates..."));
5305
6322
  await Promise.all(app.stacks.map(async (stack) => {
5306
6323
  const template = stack.toString(true);
5307
- const path = join6(directories.template, app.name);
5308
- const file = join6(path, `${stack.name}.json`);
6324
+ const path = join8(directories.template, app.name);
6325
+ const file = join8(path, `${stack.name}.json`);
5309
6326
  await mkdir5(path, { recursive: true });
5310
6327
  await writeFile4(file, template);
5311
6328
  }));
@@ -5350,7 +6367,7 @@ var bootstrapStack = (account, region) => {
5350
6367
  stack.add(new Bucket("assets", {
5351
6368
  name: assetBucketName(account, region),
5352
6369
  accessControl: "private",
5353
- versioned: true
6370
+ versioning: true
5354
6371
  }));
5355
6372
  stack.export("version", version);
5356
6373
  app.add(stack);
@@ -5365,7 +6382,7 @@ var shouldDeployBootstrap = async (client, stack) => {
5365
6382
  // src/formation/client.ts
5366
6383
  import { CloudFormationClient, CreateStackCommand, DeleteStackCommand, DescribeStackEventsCommand, DescribeStacksCommand, GetTemplateCommand, OnFailure, TemplateStage, UpdateStackCommand, ValidateTemplateCommand, waitUntilStackCreateComplete, waitUntilStackDeleteComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
5367
6384
  import { S3Client, PutObjectCommand, ObjectCannedACL, StorageClass } from "@aws-sdk/client-s3";
5368
- import { paramCase as paramCase5 } from "change-case";
6385
+ import { paramCase as paramCase6 } from "change-case";
5369
6386
  var StackClient = class {
5370
6387
  constructor(app, account, region, credentials) {
5371
6388
  this.app = app;
@@ -5398,7 +6415,7 @@ var StackClient = class {
5398
6415
  };
5399
6416
  }
5400
6417
  stackName(stackName) {
5401
- return paramCase5(`${this.app.name}-${stackName}`);
6418
+ return paramCase6(`${this.app.name}-${stackName}`);
5402
6419
  }
5403
6420
  tags(stack) {
5404
6421
  const tags = [];
@@ -5411,7 +6428,8 @@ var StackClient = class {
5411
6428
  debug("Upload the", style.info(stack.name), "stack to awsless assets bucket");
5412
6429
  const client = new S3Client({
5413
6430
  credentials: this.credentials,
5414
- region: stack.region
6431
+ region: stack.region,
6432
+ maxAttempts: 5
5415
6433
  });
5416
6434
  await client.send(new PutObjectCommand({
5417
6435
  Bucket: this.assetBucketName,
@@ -5519,6 +6537,7 @@ var StackClient = class {
5519
6537
  });
5520
6538
  debug("Status for:", style.info(name), "is", style.attr(stack.StackStatus));
5521
6539
  return {
6540
+ id: stack.StackId,
5522
6541
  status: stack.StackStatus,
5523
6542
  reason: stack.StackStatusReason,
5524
6543
  outputs,
@@ -5565,10 +6584,10 @@ var StackClient = class {
5565
6584
  maxWaitTime: this.maxWaitTime,
5566
6585
  maxDelay: this.maxDelay
5567
6586
  }, {
5568
- StackName: this.stackName(name)
6587
+ StackName: data.id
5569
6588
  });
5570
6589
  } catch (_) {
5571
- const reason = await this.getFailureReason(name, region);
6590
+ const reason = await this.getFailureReason(data.id, region);
5572
6591
  throw new Error(reason);
5573
6592
  }
5574
6593
  }
@@ -5826,12 +6845,13 @@ var status = (program2) => {
5826
6845
 
5827
6846
  // src/cli/ui/complex/publisher.ts
5828
6847
  import { readFile as readFile3 } from "fs/promises";
5829
- import { join as join7 } from "path";
6848
+ import { join as join9 } from "path";
5830
6849
  import { GetObjectCommand, ObjectCannedACL as ObjectCannedACL2, PutObjectCommand as PutObjectCommand2, S3Client as S3Client2, StorageClass as StorageClass2 } from "@aws-sdk/client-s3";
5831
6850
  var assetPublisher = (config, app) => {
5832
6851
  const client = new S3Client2({
5833
6852
  credentials: config.credentials,
5834
- region: config.region
6853
+ region: config.region,
6854
+ maxAttempts: 5
5835
6855
  });
5836
6856
  return async (term) => {
5837
6857
  const done = term.out.write(loadingDialog("Publishing stack assets to AWS..."));
@@ -5839,12 +6859,12 @@ var assetPublisher = (config, app) => {
5839
6859
  await Promise.all([...stack.assets].map(async (asset) => {
5840
6860
  await asset.publish?.({
5841
6861
  async read(file) {
5842
- const path = join7(directories.asset, asset.type, app.name, stack.name, asset.id, file);
6862
+ const path = join9(directories.asset, asset.type, app.name, stack.name, asset.id, file);
5843
6863
  const data = await readFile3(path);
5844
6864
  return data;
5845
6865
  },
5846
6866
  async publish(name, data, hash) {
5847
- const key = `${app.name}/${stack.name}/function/${name}`;
6867
+ const key = `${app.name}/${stack.name}/${asset.type}/${name}`;
5848
6868
  const bucket = assetBucketName(config.account, config.region);
5849
6869
  let getResult;
5850
6870
  try {
@@ -6084,16 +7104,6 @@ var secrets = (program2) => {
6084
7104
  commands.forEach((cb) => cb(command));
6085
7105
  };
6086
7106
 
6087
- // src/cli/command/test.ts
6088
- var test = (program2) => {
6089
- program2.command("test").action(async () => {
6090
- await layout(async (config) => {
6091
- const app = new App("test");
6092
- const name = "test5";
6093
- });
6094
- });
6095
- };
6096
-
6097
7107
  // src/cli/command/types.ts
6098
7108
  var types = (program2) => {
6099
7109
  program2.command("types").description("Generate type definition files").action(async () => {
@@ -6117,6 +7127,48 @@ var dev = (program2) => {
6117
7127
  });
6118
7128
  };
6119
7129
 
7130
+ // src/cli/command/delete.ts
7131
+ var del2 = (program2) => {
7132
+ program2.command("delete").argument("[stacks...]", "Optionally filter stacks to delete").description("Delete your app from AWS").action(async (filters) => {
7133
+ await layout(async (config, write) => {
7134
+ const { app, deploymentLine } = await toApp(config, filters);
7135
+ const deletingLine = deploymentLine.reverse();
7136
+ const stackNames = app.stacks.map((stack) => stack.name);
7137
+ const formattedFilter = stackNames.map((i) => style.info(i)).join(style.placeholder(", "));
7138
+ debug("Stacks to delete", formattedFilter);
7139
+ const deployAll = filters.length === 0;
7140
+ const deploySingle = filters.length === 1;
7141
+ const confirm = await write(confirmPrompt(deployAll ? `Are you sure you want to ${style.error("delete")} ${style.warning("all")} stacks?` : deploySingle ? `Are you sure you want to ${style.error("delete")} the ${formattedFilter} stack?` : `Are you sure you want to ${style.error("delete")} the [ ${formattedFilter} ] stacks?`));
7142
+ if (!confirm) {
7143
+ throw new Cancelled();
7144
+ }
7145
+ const doneDeploying = write(loadingDialog("Deleting stacks from AWS..."));
7146
+ const client = new StackClient(app, config.account, config.region, config.credentials);
7147
+ const ui = write(stacksDeployer(deletingLine));
7148
+ for (const line2 of deletingLine) {
7149
+ const results = await Promise.allSettled(line2.map(async (stack) => {
7150
+ const item = ui[stack.name];
7151
+ item.start("deleting");
7152
+ try {
7153
+ await client.delete(stack.name, stack.region);
7154
+ } catch (error) {
7155
+ debugError(error);
7156
+ item.fail("failed");
7157
+ throw error;
7158
+ }
7159
+ item.done("deleted");
7160
+ }));
7161
+ for (const result of results) {
7162
+ if (result.status === "rejected") {
7163
+ throw result.reason;
7164
+ }
7165
+ }
7166
+ }
7167
+ doneDeploying("Done deleting stacks from AWS");
7168
+ });
7169
+ });
7170
+ };
7171
+
6120
7172
  // src/cli/program.ts
6121
7173
  var program = new Command();
6122
7174
  program.name(logo().join("").replace(/\s+/, ""));
@@ -6138,9 +7190,10 @@ var commands2 = [
6138
7190
  types,
6139
7191
  build,
6140
7192
  deploy,
7193
+ del2,
6141
7194
  dev,
6142
- secrets,
6143
- test
7195
+ secrets
7196
+ // test,
6144
7197
  // diff,
6145
7198
  // remove,
6146
7199
  ];