@awsless/awsless 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -26,13 +26,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/cli/program.ts
27
27
  var import_commander = require("commander");
28
28
 
29
- // src/app.ts
30
- var import_aws_cdk_lib7 = require("aws-cdk-lib");
31
-
32
- // src/stack.ts
33
- var import_aws_cdk_lib = require("aws-cdk-lib");
34
- var import_aws_iam = require("aws-cdk-lib/aws-iam");
35
-
36
29
  // src/cli/style.ts
37
30
  var import_chalk = __toESM(require("chalk"), 1);
38
31
  var symbol = {
@@ -84,7 +77,7 @@ var flushDebug = () => {
84
77
  // src/util/param.ts
85
78
  var import_client_ssm = require("@aws-sdk/client-ssm");
86
79
  var configParameterPrefix = (config) => {
87
- return `/${config.stage}/awsless/${config.name}`;
80
+ return `/awsless/${config.name}/${config.stage}`;
88
81
  };
89
82
  var Params = class {
90
83
  constructor(config) {
@@ -165,81 +158,411 @@ var Params = class {
165
158
  }
166
159
  };
167
160
 
168
- // src/stack.ts
169
- var toStack = ({ config, assets, app, stackConfig, plugins }) => {
170
- const stackName = `${config.name}-${stackConfig.name}`;
171
- const stack = new import_aws_cdk_lib.Stack(app, stackConfig.name, {
172
- stackName,
173
- tags: {
174
- APP: config.name,
175
- STAGE: config.stage,
176
- STACK: stackConfig.name
161
+ // src/formation/util.ts
162
+ var import_change_case = require("change-case");
163
+ var ref = (logicalId) => {
164
+ return { Ref: logicalId };
165
+ };
166
+ var sub = (value, params) => {
167
+ if (params) {
168
+ return { "Fn::Sub": [value, params] };
169
+ }
170
+ return { "Fn::Sub": value };
171
+ };
172
+ var getAtt = (logicalId, attr) => {
173
+ return { "Fn::GetAtt": [logicalId, attr] };
174
+ };
175
+ var importValue = (name) => {
176
+ return { "Fn::ImportValue": name };
177
+ };
178
+ var formatLogicalId = (id) => {
179
+ return (0, import_change_case.pascalCase)(id);
180
+ };
181
+ var formatName = (name) => {
182
+ return (0, import_change_case.paramCase)(name);
183
+ };
184
+
185
+ // src/formation/resource.ts
186
+ var Resource = class {
187
+ constructor(type, logicalId, children = []) {
188
+ this.type = type;
189
+ this.children = children;
190
+ this.logicalId = formatLogicalId(`${logicalId}-${type.replace(/^AWS::/, "")}`);
191
+ }
192
+ logicalId;
193
+ deps = /* @__PURE__ */ new Set();
194
+ dependsOn(...dependencies) {
195
+ for (const dependency of dependencies) {
196
+ this.deps.add(dependency);
177
197
  }
178
- });
179
- debug("Define stack:", style.info(stackConfig.name));
198
+ return this;
199
+ }
200
+ attr(name, value) {
201
+ if (typeof value === "undefined") {
202
+ return {};
203
+ }
204
+ return {
205
+ [name]: value
206
+ };
207
+ }
208
+ toJSON() {
209
+ return {
210
+ [this.logicalId]: {
211
+ Type: this.type,
212
+ DependsOn: [...this.deps].map((dep) => dep.logicalId),
213
+ Properties: this.properties()
214
+ }
215
+ };
216
+ }
217
+ };
218
+ var Group = class {
219
+ constructor(children) {
220
+ this.children = children;
221
+ }
222
+ };
223
+
224
+ // src/formation/resource/iam/inline-policy.ts
225
+ var InlinePolicy = class {
226
+ constructor(name, props = {}) {
227
+ this.name = name;
228
+ this.statements = props.statements || [];
229
+ }
230
+ statements;
231
+ addStatement(...statements) {
232
+ this.statements.push(...statements.flat());
233
+ return this;
234
+ }
235
+ toJSON() {
236
+ return {
237
+ PolicyName: this.name,
238
+ PolicyDocument: {
239
+ Version: "2012-10-17",
240
+ Statement: this.statements.map((statement) => ({
241
+ Effect: statement.effect || "Allow",
242
+ Action: statement.actions,
243
+ Resource: statement.resources
244
+ }))
245
+ }
246
+ };
247
+ }
248
+ };
249
+
250
+ // src/formation/resource/iam/managed-policy.ts
251
+ var ManagedPolicy = class {
252
+ constructor(arn) {
253
+ this.arn = arn;
254
+ }
255
+ static fromAwsManagedPolicyName(name) {
256
+ const arn = sub("arn:${AWS::Partition}:iam::aws:policy/service-role/" + name);
257
+ return new ManagedPolicy(arn);
258
+ }
259
+ static fromManagedPolicyArn(arn) {
260
+ return new ManagedPolicy(arn);
261
+ }
262
+ };
263
+
264
+ // src/formation/resource/iam/role.ts
265
+ var Role = class extends Resource {
266
+ constructor(logicalId, props = {}) {
267
+ super("AWS::IAM::Role", logicalId);
268
+ this.props = props;
269
+ this.name = formatName(logicalId);
270
+ }
271
+ name;
272
+ inlinePolicies = /* @__PURE__ */ new Set();
273
+ managedPolicies = /* @__PURE__ */ new Set();
274
+ get arn() {
275
+ return getAtt(this.logicalId, "Arn");
276
+ }
277
+ addManagedPolicy(...policies) {
278
+ for (const policy of policies) {
279
+ this.managedPolicies.add(policy);
280
+ }
281
+ return this;
282
+ }
283
+ addInlinePolicy(...policies) {
284
+ for (const policy of policies) {
285
+ this.inlinePolicies.add(policy);
286
+ }
287
+ return this;
288
+ }
289
+ properties() {
290
+ return {
291
+ ...this.props.assumedBy ? {
292
+ AssumeRolePolicyDocument: {
293
+ Version: "2012-10-17",
294
+ Statement: [{
295
+ Action: "sts:AssumeRole",
296
+ Effect: "Allow",
297
+ Principal: {
298
+ Service: this.props.assumedBy
299
+ }
300
+ }]
301
+ }
302
+ } : {},
303
+ ManagedPolicyArns: [...this.managedPolicies].map((policy) => policy.arn),
304
+ Policies: [...this.inlinePolicies].map((policy) => policy.toJSON())
305
+ };
306
+ }
307
+ };
308
+
309
+ // src/formation/resource/lambda/function.ts
310
+ var Function = class extends Resource {
311
+ constructor(logicalId, props) {
312
+ const policy = new InlinePolicy(logicalId);
313
+ const role = new Role(logicalId, {
314
+ assumedBy: "lambda.amazonaws.com"
315
+ });
316
+ role.addInlinePolicy(policy);
317
+ role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AWSLambdaBasicExecutionRole"));
318
+ super("AWS::Lambda::Function", logicalId, [
319
+ role,
320
+ props.code
321
+ ]);
322
+ this.props = props;
323
+ this.dependsOn(role);
324
+ this.role = role;
325
+ this.policy = policy;
326
+ this.name = formatName(this.props.name || logicalId);
327
+ this.environmentVariables = props.environment ? { ...props.environment } : {};
328
+ }
329
+ name;
330
+ role;
331
+ policy;
332
+ environmentVariables;
333
+ addPermissions(...permissions) {
334
+ this.policy.addStatement(...permissions);
335
+ return this;
336
+ }
337
+ addEnvironment(name, value) {
338
+ this.environmentVariables[name] = value;
339
+ return this;
340
+ }
341
+ get id() {
342
+ return ref(this.logicalId);
343
+ }
344
+ get arn() {
345
+ return getAtt(this.logicalId, "Arn");
346
+ }
347
+ get permissions() {
348
+ return {
349
+ actions: [
350
+ "lambda:InvokeFunction",
351
+ "lambda:InvokeAsync"
352
+ ],
353
+ resources: [this.arn]
354
+ };
355
+ }
356
+ properties() {
357
+ return {
358
+ FunctionName: this.name,
359
+ MemorySize: this.props.memorySize?.toMegaBytes() ?? 128,
360
+ Runtime: this.props.runtime ?? "nodejs18.x",
361
+ Timeout: this.props.timeout?.toSeconds() ?? 10,
362
+ Architectures: [this.props.architecture ?? "arm64"],
363
+ Role: this.role.arn,
364
+ ...this.props.code.toCodeJson(),
365
+ EphemeralStorage: {
366
+ Size: this.props.ephemeralStorageSize?.toMegaBytes() ?? 512
367
+ },
368
+ Environment: {
369
+ Variables: this.environmentVariables
370
+ }
371
+ };
372
+ }
373
+ };
374
+
375
+ // src/formation/asset.ts
376
+ var import_change_case2 = require("change-case");
377
+ var Asset = class {
378
+ constructor(type, id) {
379
+ this.type = type;
380
+ this.id = (0, import_change_case2.paramCase)(id);
381
+ }
382
+ id;
383
+ };
384
+
385
+ // src/formation/stack.ts
386
+ var Stack = class {
387
+ constructor(name, region) {
388
+ this.name = name;
389
+ this.region = region;
390
+ }
391
+ exports = /* @__PURE__ */ new Map();
392
+ resources = /* @__PURE__ */ new Set();
393
+ tags = /* @__PURE__ */ new Map();
394
+ assets = /* @__PURE__ */ new Set();
395
+ add(...resources) {
396
+ for (const item of resources) {
397
+ if (item instanceof Asset) {
398
+ this.assets.add(item);
399
+ } else {
400
+ this.add(...item.children);
401
+ if (item instanceof Resource) {
402
+ this.resources.add(item);
403
+ }
404
+ }
405
+ }
406
+ return this;
407
+ }
408
+ export(name, value) {
409
+ name = formatName(name);
410
+ this.exports.set(name, value);
411
+ return this;
412
+ }
413
+ import(name) {
414
+ name = formatName(name);
415
+ if (!this.exports.has(name)) {
416
+ throw new Error(`Undefined export value: ${name}`);
417
+ }
418
+ return importValue(name);
419
+ }
420
+ tag(name, value) {
421
+ this.tags.set(name, value);
422
+ return this;
423
+ }
424
+ find(resourceType) {
425
+ return [...this.resources].filter((resource) => resource instanceof resourceType);
426
+ }
427
+ [Symbol.iterator]() {
428
+ return this.resources.values();
429
+ }
430
+ // get resources() {
431
+ // return [ ...this.list.values() ]
432
+ // }
433
+ get size() {
434
+ return this.resources.size;
435
+ }
436
+ toJSON() {
437
+ const resources = {};
438
+ const outputs = {};
439
+ for (const resource of this) {
440
+ Object.assign(resources, resource.toJSON());
441
+ }
442
+ for (const [name, value] of this.exports.entries()) {
443
+ Object.assign(outputs, {
444
+ [formatLogicalId(name)]: {
445
+ Export: { Name: name },
446
+ Value: value
447
+ }
448
+ });
449
+ }
450
+ return {
451
+ Resources: resources,
452
+ Outputs: outputs
453
+ };
454
+ }
455
+ toString(pretty = false) {
456
+ return JSON.stringify(
457
+ this.toJSON(),
458
+ void 0,
459
+ pretty ? 4 : void 0
460
+ );
461
+ }
462
+ };
463
+
464
+ // src/stack.ts
465
+ var toStack = ({ config, app, stackConfig, bootstrap: bootstrap2, usEastBootstrap, plugins }) => {
466
+ const name = stackConfig.name;
467
+ const stack = new Stack(name, config.region).tag("app", config.name).tag("stage", config.stage).tag("stack", name);
468
+ debug("Define stack:", style.info(name));
469
+ debug("Run plugin onStack listeners");
180
470
  const bindings = [];
181
471
  const bind = (cb) => {
182
472
  bindings.push(cb);
183
473
  };
184
- debug("Run plugin onStack listeners");
185
- const functions = plugins.map((plugin) => plugin.onStack?.({
186
- config,
187
- assets,
188
- app,
189
- stack,
190
- stackConfig,
191
- bind
192
- })).filter(Boolean).flat().filter(Boolean);
193
- if (stack.node.children.length === 0) {
194
- throw new Error(`Stack ${style.info(stackConfig.name)} has no resources defined`);
195
- }
196
- bindings.forEach((cb) => functions.forEach(cb));
197
- const allowConfigParameters = new import_aws_iam.PolicyStatement({
198
- actions: [
199
- "ssm:GetParameter",
200
- "ssm:GetParameters",
201
- "ssm:GetParametersByPath"
202
- ],
203
- resources: [
204
- import_aws_cdk_lib.Arn.format({
205
- region: config.region,
206
- account: config.account,
207
- partition: "aws",
208
- service: "ssm",
209
- resource: "parameter",
210
- resourceName: configParameterPrefix(config)
211
- })
212
- // Fn.sub('arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter' + configParameterPrefix(config)),
213
- ]
214
- });
215
- functions.forEach((lambda) => lambda.addToRolePolicy(allowConfigParameters));
474
+ for (const plugin of plugins) {
475
+ plugin.onStack?.({
476
+ config,
477
+ app,
478
+ stack,
479
+ stackConfig,
480
+ bootstrap: bootstrap2,
481
+ usEastBootstrap,
482
+ bind
483
+ });
484
+ }
485
+ if (stack.size === 0) {
486
+ throw new Error(`Stack ${style.info(name)} has no resources defined`);
487
+ }
488
+ const functions = stack.find(Function);
489
+ for (const bind2 of bindings) {
490
+ for (const fn of functions) {
491
+ bind2(fn);
492
+ }
493
+ }
494
+ for (const fn of functions) {
495
+ fn.addPermissions({
496
+ actions: [
497
+ "ssm:GetParameter",
498
+ "ssm:GetParameters",
499
+ "ssm:GetParametersByPath"
500
+ ],
501
+ resources: [
502
+ sub("arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter" + configParameterPrefix(config))
503
+ ]
504
+ });
505
+ }
216
506
  return {
217
507
  stack,
218
- functions,
219
- bindings,
220
508
  depends: stackConfig.depends
221
509
  };
222
510
  };
223
511
 
224
- // src/util/path.ts
225
- var import_path = require("path");
226
- var rootDir = process.cwd();
227
- var outDir = (0, import_path.join)(rootDir, ".awsless");
228
- var assemblyDir = (0, import_path.join)(outDir, "assembly");
229
- var assetDir = (0, import_path.join)(outDir, "asset");
230
- var cacheDir = (0, import_path.join)(outDir, "cache");
512
+ // src/util/deployment.ts
513
+ var createDependencyTree = (stacks) => {
514
+ const list3 = stacks.map(({ stack, config }) => ({
515
+ stack,
516
+ depends: config?.depends?.map((dep) => dep.name) || []
517
+ }));
518
+ const findChildren = (list4, parents) => {
519
+ const children = [];
520
+ const rests = [];
521
+ for (const item of list4) {
522
+ const isChild = item.depends.filter((dep) => !parents.includes(dep)).length === 0;
523
+ if (isChild) {
524
+ children.push(item);
525
+ } else {
526
+ rests.push(item);
527
+ }
528
+ }
529
+ if (!rests.length) {
530
+ return children.map(({ stack }) => ({
531
+ stack,
532
+ children: []
533
+ }));
534
+ }
535
+ return children.map(({ stack }) => {
536
+ return {
537
+ stack,
538
+ children: findChildren(rests, [...parents, stack.name])
539
+ };
540
+ });
541
+ };
542
+ return findChildren(list3, []);
543
+ };
544
+ var createDeploymentLine = (stacks) => {
545
+ const line = [];
546
+ const walk = (stacks2, level) => {
547
+ stacks2.forEach((node) => {
548
+ if (!line[level]) {
549
+ line[level] = [];
550
+ }
551
+ line[level].push(node.stack);
552
+ walk(node.children, level + 1);
553
+ });
554
+ };
555
+ walk(stacks, 0);
556
+ return line;
557
+ };
231
558
 
232
- // src/stack/app-bootstrap.ts
233
- var import_aws_cdk_lib6 = require("aws-cdk-lib");
559
+ // src/plugins/cron/index.ts
560
+ var import_zod7 = require("zod");
234
561
 
235
562
  // src/plugin.ts
236
563
  var definePlugin = (plugin) => plugin;
237
564
 
238
- // src/plugins/cron/index.ts
239
- var import_zod10 = require("zod");
240
-
241
565
  // src/plugins/cron/schema/schedule.ts
242
- var import_aws_events = require("aws-cdk-lib/aws-events");
243
566
  var import_zod = require("zod");
244
567
  var import_aws_cron_expression_validator = require("aws-cron-expression-validator");
245
568
  var RateExpressionSchema = import_zod.z.custom((value) => {
@@ -248,7 +571,7 @@ var RateExpressionSchema = import_zod.z.custom((value) => {
248
571
  const number = parseInt(str);
249
572
  return number > 0;
250
573
  }).safeParse(value).success;
251
- }, "Invalid rate expression").transform(import_aws_events.Schedule.expression);
574
+ }, "Invalid rate expression");
252
575
  var CronExpressionSchema = import_zod.z.custom((value) => {
253
576
  return import_zod.z.string().startsWith("cron(").endsWith(")").safeParse(value).success;
254
577
  }, "Invalid cron expression").superRefine((value, ctx) => {
@@ -268,55 +591,81 @@ var CronExpressionSchema = import_zod.z.custom((value) => {
268
591
  });
269
592
  }
270
593
  }
271
- }).transform(import_aws_events.Schedule.expression);
594
+ });
272
595
  var ScheduleExpressionSchema = RateExpressionSchema.or(CronExpressionSchema);
273
596
 
274
- // src/plugins/cron/index.ts
275
- var import_aws_events2 = require("aws-cdk-lib/aws-events");
597
+ // src/plugins/function.ts
598
+ var import_zod6 = require("zod");
276
599
 
277
- // src/util/resource.ts
278
- var import_change_case = require("change-case");
279
- var toId = (resource, id) => {
280
- return (0, import_change_case.pascalCase)(`${resource}-${id}`);
281
- };
282
- var toName = (stack, id) => {
283
- return (0, import_change_case.paramCase)(`${stack.stackName}-${id}`);
284
- };
285
- var toEnvKey = (resource, id) => {
286
- return `RESOURCE_${resource.toUpperCase()}_${id}`;
287
- };
288
- var addResourceEnvironment = (stack, resource, id, lambda) => {
289
- const key = toEnvKey(resource, id);
290
- const value = toName(stack, id);
291
- lambda.addEnvironment(key, value, {
292
- removeInEdge: true
293
- });
294
- };
600
+ // src/schema/duration.ts
601
+ var import_zod2 = require("zod");
295
602
 
296
- // src/plugins/function/index.ts
297
- var import_zod9 = require("zod");
603
+ // src/formation/property/duration.ts
604
+ var Duration = class {
605
+ constructor(value) {
606
+ this.value = value;
607
+ }
608
+ static milliseconds(value) {
609
+ return new Duration(value);
610
+ }
611
+ static seconds(value) {
612
+ return new Duration(value * 1e3 /* seconds */);
613
+ }
614
+ static minutes(value) {
615
+ return new Duration(value * 6e4 /* minutes */);
616
+ }
617
+ static hours(value) {
618
+ return new Duration(value * 36e5 /* hours */);
619
+ }
620
+ static days(value) {
621
+ return new Duration(value * 864e5 /* days */);
622
+ }
623
+ toMilliseconds() {
624
+ return this.value;
625
+ }
626
+ toSeconds() {
627
+ return Math.floor(this.value / 1e3 /* seconds */);
628
+ }
629
+ toMinutes() {
630
+ return Math.floor(this.value / 6e4 /* minutes */);
631
+ }
632
+ toHours() {
633
+ return Math.floor(this.value / 36e5 /* hours */);
634
+ }
635
+ toDays() {
636
+ return Math.floor(this.value / 864e5 /* days */);
637
+ }
638
+ };
298
639
 
299
640
  // src/schema/duration.ts
300
- var import_zod2 = require("zod");
301
- var import_core = require("aws-cdk-lib/core");
302
641
  function toDuration(duration) {
303
642
  const [count, unit] = duration.split(" ");
304
643
  const countNum = parseInt(count);
305
644
  const unitLower = unit.toLowerCase();
306
645
  if (unitLower.startsWith("second")) {
307
- return import_core.Duration.seconds(countNum);
646
+ return Duration.seconds(countNum);
308
647
  } else if (unitLower.startsWith("minute")) {
309
- return import_core.Duration.minutes(countNum);
648
+ return Duration.minutes(countNum);
310
649
  } else if (unitLower.startsWith("hour")) {
311
- return import_core.Duration.hours(countNum);
650
+ return Duration.hours(countNum);
312
651
  } else if (unitLower.startsWith("day")) {
313
- return import_core.Duration.days(countNum);
652
+ return Duration.days(countNum);
314
653
  }
315
- return import_core.Duration.days(0);
654
+ return Duration.days(0);
316
655
  }
317
656
  var DurationSchema = import_zod2.z.custom((value) => {
318
657
  return import_zod2.z.string().regex(/[0-9]+ (seconds?|minutes?|hours?|days?)/).safeParse(value).success;
319
658
  }, "Invalid duration").transform(toDuration);
659
+ var durationMin = (min) => {
660
+ return (duration) => {
661
+ return duration.toSeconds() >= min.toSeconds();
662
+ };
663
+ };
664
+ var durationMax = (max) => {
665
+ return (duration) => {
666
+ return duration.toSeconds() <= max.toSeconds();
667
+ };
668
+ };
320
669
 
321
670
  // src/schema/local-file.ts
322
671
  var import_promises = require("fs/promises");
@@ -330,202 +679,86 @@ var LocalFileSchema = import_zod3.z.string().refine(async (path) => {
330
679
  return true;
331
680
  }, `File doesn't exist`);
332
681
 
333
- // src/plugins/function/index.ts
334
- var import_aws_lambda3 = require("aws-cdk-lib/aws-lambda");
335
-
336
- // src/plugins/function/schema/runtime.ts
337
- var import_aws_lambda = require("aws-cdk-lib/aws-lambda");
682
+ // src/schema/resource-id.ts
338
683
  var import_zod4 = require("zod");
339
- var runtimes = {
340
- "container": import_aws_lambda.Runtime.FROM_IMAGE,
341
- "rust": import_aws_lambda.Runtime.PROVIDED_AL2,
342
- "nodejs16.x": import_aws_lambda.Runtime.NODEJS_16_X,
343
- "nodejs18.x": import_aws_lambda.Runtime.NODEJS_18_X,
344
- "python3.9": import_aws_lambda.Runtime.PYTHON_3_9,
345
- "python3.10": import_aws_lambda.Runtime.PYTHON_3_10,
346
- "go1.x": import_aws_lambda.Runtime.PROVIDED_AL2,
347
- "go": import_aws_lambda.Runtime.PROVIDED_AL2
348
- };
349
- var toRuntime = (runtime) => {
350
- return runtimes[runtime];
351
- };
352
- var RuntimeSchema = import_zod4.z.enum(Object.keys(runtimes)).transform(toRuntime);
353
-
354
- // src/plugins/function/schema/architecture.ts
355
- var import_aws_lambda2 = require("aws-cdk-lib/aws-lambda");
684
+ var ResourceIdSchema = import_zod4.z.string().min(3).max(24).regex(/[a-z\-]+/, "Invalid resource ID");
685
+
686
+ // src/schema/size.ts
356
687
  var import_zod5 = require("zod");
357
- var toArchitecture = (architecture) => {
358
- return architecture === "x86_64" ? import_aws_lambda2.Architecture.X86_64 : import_aws_lambda2.Architecture.ARM_64;
359
- };
360
- var ArchitectureSchema = import_zod5.z.enum(["x86_64", "arm_64"]).transform(toArchitecture);
361
688
 
362
- // src/schema/resource-id.ts
363
- var import_zod6 = require("zod");
364
- var ResourceIdSchema = import_zod6.z.string().min(3).max(24).regex(/[a-z\-]+/, "Invalid resource ID");
689
+ // src/formation/property/size.ts
690
+ var Size = class {
691
+ constructor(bytes) {
692
+ this.bytes = bytes;
693
+ }
694
+ static bytes(value) {
695
+ return new Size(value);
696
+ }
697
+ static kiloBytes(value) {
698
+ return new Size(value * 1024 /* kilo */);
699
+ }
700
+ static megaBytes(value) {
701
+ return new Size(value * 1048576 /* mega */);
702
+ }
703
+ static gigaBytes(value) {
704
+ return new Size(value * 1073741824 /* giga */);
705
+ }
706
+ toBytes() {
707
+ return this.bytes;
708
+ }
709
+ toKiloBytes() {
710
+ return Math.floor(this.bytes / 1024 /* kilo */);
711
+ }
712
+ toMegaBytes() {
713
+ return Math.floor(this.bytes / 1048576 /* mega */);
714
+ }
715
+ toGigaBytes() {
716
+ return Math.floor(this.bytes / 1073741824 /* giga */);
717
+ }
718
+ };
365
719
 
366
720
  // src/schema/size.ts
367
- var import_core2 = require("aws-cdk-lib/core");
368
- var import_zod7 = require("zod");
369
721
  function toSize(size) {
370
722
  const [count, unit] = size.split(" ");
371
723
  const countNum = parseInt(count);
372
724
  if (unit === "KB") {
373
- return import_core2.Size.kibibytes(countNum);
725
+ return Size.kiloBytes(countNum);
374
726
  } else if (unit === "MB") {
375
- return import_core2.Size.mebibytes(countNum);
727
+ return Size.megaBytes(countNum);
376
728
  } else if (unit === "GB") {
377
- return import_core2.Size.gibibytes(countNum);
729
+ return Size.gigaBytes(countNum);
378
730
  }
379
731
  throw new TypeError(`Invalid size ${size}`);
380
732
  }
381
- var SizeSchema = import_zod7.z.custom((value) => {
382
- return import_zod7.z.string().regex(/[0-9]+ (KB|MB|GB)/).safeParse(value).success;
733
+ var SizeSchema = import_zod5.z.custom((value) => {
734
+ return import_zod5.z.string().regex(/[0-9]+ (KB|MB|GB)/).safeParse(value).success;
383
735
  }, "Invalid size").transform(toSize);
384
-
385
- // src/plugins/function/util/build.ts
386
- var import_jszip = __toESM(require("jszip"), 1);
387
- var import_path3 = require("path");
388
- var import_promises2 = require("fs/promises");
389
- var import_filesize = require("filesize");
390
- var zipFiles = (files) => {
391
- const zip = new import_jszip.default();
392
- for (const file of files) {
393
- zip.file(file.name, file.code);
394
- }
395
- return zip.generateAsync({
396
- type: "nodebuffer",
397
- compression: "DEFLATE",
398
- compressionOptions: {
399
- level: 9
400
- }
401
- });
402
- };
403
- var writeBuildHash = async (config, stack, id, hash) => {
404
- const funcPath = (0, import_path3.join)(assetDir, "function", config.name, stack.artifactId, id);
405
- const versionFile = (0, import_path3.join)(funcPath, "HASH");
406
- await (0, import_promises2.writeFile)(versionFile, hash);
407
- };
408
- var writeBuildFiles = async (config, stack, id, files) => {
409
- const bundle = await zipFiles(files);
410
- const funcPath = (0, import_path3.join)(assetDir, "function", config.name, stack.artifactId, id);
411
- const filesPath = (0, import_path3.join)(funcPath, "files");
412
- const bundleFile = (0, import_path3.join)(funcPath, "bundle.zip");
413
- debug("Bundle size of", style.info((0, import_path3.join)(config.name, stack.artifactId, id)), "is", style.attr((0, import_filesize.filesize)(bundle.byteLength)));
414
- await (0, import_promises2.mkdir)(filesPath, { recursive: true });
415
- await (0, import_promises2.writeFile)(bundleFile, bundle);
416
- await Promise.all(files.map(async (file) => {
417
- const fileName = (0, import_path3.join)(filesPath, file.name);
418
- await (0, import_promises2.mkdir)((0, import_path3.basename)(fileName), { recursive: true });
419
- await (0, import_promises2.writeFile)(fileName, file.code);
420
- if (file.map) {
421
- const mapName = (0, import_path3.join)(filesPath, `${file.name}.map`);
422
- await (0, import_promises2.writeFile)(mapName, file.map);
423
- }
424
- }));
425
- return {
426
- file: bundleFile,
427
- size: bundle.byteLength
736
+ var sizeMin = (min) => {
737
+ return (size) => {
738
+ return size.toBytes() >= min.toBytes();
428
739
  };
429
740
  };
430
-
431
- // src/plugins/function/util/publish.ts
432
- var import_path5 = require("path");
433
- var import_promises3 = require("fs/promises");
434
- var import_client_s3 = require("@aws-sdk/client-s3");
435
-
436
- // src/stack/bootstrap.ts
437
- var import_aws_cdk_lib2 = require("aws-cdk-lib");
438
- var import_aws_s3 = require("aws-cdk-lib/aws-s3");
439
- var assetBucketName = (config) => {
440
- return `awsless-bootstrap-${config.account}-${config.region}`;
441
- };
442
- var assetBucketUrl = (config, stackName) => {
443
- const bucket = assetBucketName(config);
444
- return `https://s3-${config.region}.amazonaws.com/${bucket}/${stackName}/cloudformation.json`;
445
- };
446
- var version = "2";
447
- var bootstrapStack = (config, app) => {
448
- const stack = new import_aws_cdk_lib2.Stack(app, "bootstrap", {
449
- stackName: `awsless-bootstrap`
450
- });
451
- new import_aws_s3.Bucket(stack, "assets", {
452
- bucketName: assetBucketName(config),
453
- versioned: true,
454
- accessControl: import_aws_s3.BucketAccessControl.PRIVATE,
455
- removalPolicy: import_aws_cdk_lib2.RemovalPolicy.DESTROY
456
- });
457
- new import_aws_cdk_lib2.CfnOutput(stack, "version", {
458
- exportName: "version",
459
- value: version
460
- });
461
- return stack;
462
- };
463
- var shouldDeployBootstrap = async (client, name) => {
464
- debug("Check bootstrap status");
465
- const info = await client.get(name);
466
- return !info || info.outputs.version !== version || !["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(info.status);
467
- };
468
-
469
- // src/plugins/function/util/publish.ts
470
- var publishFunctionAsset = async (config, stack, id) => {
471
- const bucket = assetBucketName(config);
472
- const key = `${config.name}/${stack.artifactId}/function/${id}.zip`;
473
- const funcPath = (0, import_path5.join)(assetDir, "function", config.name, stack.artifactId, id);
474
- const bundleFile = (0, import_path5.join)(funcPath, "bundle.zip");
475
- const hashFile = (0, import_path5.join)(funcPath, "HASH");
476
- const hash = await (0, import_promises3.readFile)(hashFile, "utf8");
477
- const file = await (0, import_promises3.readFile)(bundleFile);
478
- const client = new import_client_s3.S3Client({
479
- credentials: config.credentials,
480
- region: config.region
481
- });
482
- let getResult;
483
- try {
484
- getResult = await client.send(new import_client_s3.GetObjectCommand({
485
- Bucket: bucket,
486
- Key: key
487
- }));
488
- } catch (error) {
489
- if (error instanceof Error && error.name === "NoSuchKey") {
490
- } else {
491
- throw error;
492
- }
493
- }
494
- if (getResult?.Metadata?.hash === hash) {
495
- return getResult.VersionId;
496
- }
497
- const putResult = await client.send(new import_client_s3.PutObjectCommand({
498
- Bucket: bucket,
499
- Key: key,
500
- Body: file,
501
- ACL: import_client_s3.ObjectCannedACL.private,
502
- StorageClass: import_client_s3.StorageClass.STANDARD,
503
- Metadata: {
504
- hash
505
- }
506
- }));
507
- return putResult.VersionId;
741
+ var sizeMax = (max) => {
742
+ return (size) => {
743
+ return size.toBytes() <= max.toBytes();
744
+ };
508
745
  };
509
746
 
510
- // src/plugins/function/schema/retry-attempts.ts
511
- var import_zod8 = require("zod");
512
- var RetryAttempts = import_zod8.z.number().int().min(0).max(2);
513
-
514
747
  // src/util/byte-size.ts
515
- var import_filesize2 = require("filesize");
748
+ var import_filesize = require("filesize");
516
749
  var formatByteSize = (size) => {
517
- const [number, unit] = (0, import_filesize2.filesize)(size).toString().split(" ");
750
+ const [number, unit] = (0, import_filesize.filesize)(size).toString().split(" ");
518
751
  return style.attr(number) + style.attr.dim(unit);
519
752
  };
520
753
 
521
- // src/plugins/function/util/bundler/rollup.ts
754
+ // src/formation/resource/lambda/util/rollup.ts
522
755
  var import_rollup = require("rollup");
523
756
  var import_crypto = require("crypto");
524
757
  var import_rollup_plugin_swc3 = require("rollup-plugin-swc3");
525
758
  var import_plugin_json = __toESM(require("@rollup/plugin-json"), 1);
526
759
  var import_plugin_commonjs = __toESM(require("@rollup/plugin-commonjs"), 1);
527
760
  var import_plugin_node_resolve = __toESM(require("@rollup/plugin-node-resolve"), 1);
528
- var rollupBuild = async (input) => {
761
+ var rollupBundle = async (input) => {
529
762
  const bundle = await (0, import_rollup.rollup)({
530
763
  input,
531
764
  external: (importee) => {
@@ -570,34 +803,160 @@ var rollupBuild = async (input) => {
570
803
  };
571
804
  };
572
805
 
573
- // src/plugins/function/index.ts
574
- var FunctionSchema = import_zod9.z.union([
806
+ // src/formation/resource/lambda/util/zip.ts
807
+ var import_jszip = __toESM(require("jszip"), 1);
808
+ var zipFiles = (files) => {
809
+ const zip = new import_jszip.default();
810
+ for (const file of files) {
811
+ zip.file(file.name, file.code);
812
+ }
813
+ return zip.generateAsync({
814
+ type: "nodebuffer",
815
+ compression: "DEFLATE",
816
+ compressionOptions: {
817
+ level: 9
818
+ }
819
+ });
820
+ };
821
+
822
+ // src/formation/resource/lambda/code.ts
823
+ var import_crypto2 = require("crypto");
824
+ var Code = class {
825
+ static fromFile(id, file, bundler) {
826
+ return new FileCode(id, file, bundler);
827
+ }
828
+ static fromInline(id, code, handler) {
829
+ return new InlineCode(id, code, handler);
830
+ }
831
+ };
832
+ var InlineCode = class extends Asset {
833
+ constructor(id, code, handler = "index.default") {
834
+ super("function", id);
835
+ this.code = code;
836
+ this.handler = handler;
837
+ }
838
+ hash;
839
+ bundle;
840
+ s3;
841
+ async build({ write }) {
842
+ const hash = (0, import_crypto2.createHash)("sha1").update(this.code).digest("hex");
843
+ const bundle = await zipFiles([{
844
+ name: "index.js",
845
+ code: this.code
846
+ }]);
847
+ await Promise.all([
848
+ write("HASH", hash),
849
+ write("bundle.zip", bundle),
850
+ write("files/inline.js", this.code)
851
+ ]);
852
+ this.bundle = bundle;
853
+ this.hash = hash;
854
+ return {
855
+ size: formatByteSize(bundle.byteLength)
856
+ };
857
+ }
858
+ async publish({ publish }) {
859
+ this.s3 = await publish(
860
+ `${this.id}.zip`,
861
+ this.bundle,
862
+ this.hash
863
+ );
864
+ }
865
+ toCodeJson() {
866
+ return {
867
+ Handler: this.handler,
868
+ Code: {
869
+ S3Bucket: this.s3.bucket,
870
+ S3Key: this.s3.key,
871
+ S3ObjectVersion: this.s3.version
872
+ }
873
+ };
874
+ }
875
+ };
876
+ var FileCode = class extends Asset {
877
+ constructor(id, file, bundler) {
878
+ super("function", id);
879
+ this.file = file;
880
+ this.bundler = bundler;
881
+ }
882
+ handler;
883
+ hash;
884
+ bundle;
885
+ s3;
886
+ async build({ write }) {
887
+ const bundler = this.bundler ?? rollupBundle;
888
+ const { hash, files, handler } = await bundler(this.file);
889
+ const bundle = await zipFiles(files);
890
+ await Promise.all([
891
+ write("HASH", hash),
892
+ write("bundle.zip", bundle),
893
+ ...files.map((file) => write(`files/${file.name}`, file.code)),
894
+ ...files.map((file) => file.map ? write(`files/${file.name}.map`, file.map) : void 0)
895
+ ]);
896
+ this.handler = handler;
897
+ this.bundle = bundle;
898
+ this.hash = hash;
899
+ return {
900
+ size: formatByteSize(bundle.byteLength)
901
+ };
902
+ }
903
+ async publish({ publish }) {
904
+ this.s3 = await publish(
905
+ `${this.id}.zip`,
906
+ this.bundle,
907
+ this.hash
908
+ );
909
+ }
910
+ toCodeJson() {
911
+ return {
912
+ Handler: this.handler,
913
+ Code: {
914
+ S3Bucket: this.s3?.bucket ?? "",
915
+ S3Key: this.s3?.key ?? "",
916
+ S3ObjectVersion: this.s3?.version ?? ""
917
+ }
918
+ };
919
+ }
920
+ };
921
+
922
+ // src/plugins/function.ts
923
+ var MemorySizeSchema = SizeSchema.refine(sizeMin(Size.megaBytes(128)), "Minimum memory size is 128 MB").refine(sizeMax(Size.gigaBytes(10)), "Minimum memory size is 10 GB");
924
+ var TimeoutSchema = DurationSchema.refine(durationMin(Duration.seconds(10)), "Minimum timeout duration is 10 seconds").refine(durationMax(Duration.minutes(15)), "Maximum timeout duration is 15 minutes");
925
+ var EphemeralStorageSizeSchema = SizeSchema.refine(sizeMin(Size.megaBytes(512)), "Minimum ephemeral storage size is 512 MB").refine(sizeMax(Size.gigaBytes(10)), "Minimum ephemeral storage size is 10 GB");
926
+ var EnvironmentSchema = import_zod6.z.record(import_zod6.z.string(), import_zod6.z.string()).optional();
927
+ var ArchitectureSchema = import_zod6.z.enum(["x86_64", "arm64"]);
928
+ var RetryAttemptsSchema = import_zod6.z.number().int().min(0).max(2);
929
+ var RuntimeSchema = import_zod6.z.enum([
930
+ "nodejs16.x",
931
+ "nodejs18.x"
932
+ ]);
933
+ var FunctionSchema = import_zod6.z.union([
575
934
  LocalFileSchema,
576
- import_zod9.z.object({
935
+ import_zod6.z.object({
577
936
  file: LocalFileSchema,
578
- timeout: DurationSchema.optional(),
937
+ timeout: TimeoutSchema.optional(),
579
938
  runtime: RuntimeSchema.optional(),
580
- memorySize: SizeSchema.optional(),
939
+ memorySize: MemorySizeSchema.optional(),
581
940
  architecture: ArchitectureSchema.optional(),
582
- ephemeralStorageSize: SizeSchema.optional(),
583
- retryAttempts: RetryAttempts,
584
- environment: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.string()).optional()
941
+ ephemeralStorageSize: EphemeralStorageSizeSchema.optional(),
942
+ retryAttempts: RetryAttemptsSchema.optional(),
943
+ environment: EnvironmentSchema.optional()
585
944
  })
586
945
  ]);
587
- var schema = import_zod9.z.object({
588
- defaults: import_zod9.z.object({
589
- function: import_zod9.z.object({
590
- timeout: DurationSchema.default("10 seconds"),
946
+ var schema = import_zod6.z.object({
947
+ defaults: import_zod6.z.object({
948
+ function: import_zod6.z.object({
949
+ timeout: TimeoutSchema.default("10 seconds"),
591
950
  runtime: RuntimeSchema.default("nodejs18.x"),
592
- memorySize: SizeSchema.default("128 MB"),
593
- architecture: ArchitectureSchema.default("arm_64"),
594
- ephemeralStorageSize: SizeSchema.default("512 MB"),
595
- retryAttempts: RetryAttempts.default(2),
596
- environment: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.string()).optional()
951
+ memorySize: MemorySizeSchema.default("128 MB"),
952
+ architecture: ArchitectureSchema.default("arm64"),
953
+ ephemeralStorageSize: EphemeralStorageSizeSchema.default("512 MB"),
954
+ retryAttempts: RetryAttemptsSchema.default(2),
955
+ environment: EnvironmentSchema.optional()
597
956
  }).default({})
598
957
  }).default({}),
599
- stacks: import_zod9.z.object({
600
- functions: import_zod9.z.record(
958
+ stacks: import_zod6.z.object({
959
+ functions: import_zod6.z.record(
601
960
  ResourceIdSchema,
602
961
  FunctionSchema
603
962
  ).optional()
@@ -606,200 +965,466 @@ var schema = import_zod9.z.object({
606
965
  var functionPlugin = definePlugin({
607
966
  name: "function",
608
967
  schema,
609
- onStack(context) {
610
- return Object.entries(context.stackConfig.functions || {}).map(([id, fileOrProps]) => {
611
- return toFunction(context, id, fileOrProps);
612
- });
968
+ onStack(ctx) {
969
+ for (const [id, props] of Object.entries(ctx.stackConfig.functions || {})) {
970
+ const lambda = toLambdaFunction(ctx, id, props);
971
+ ctx.stack.add(lambda);
972
+ }
613
973
  }
614
974
  });
615
- var toFunction = ({ config, stack, assets }, id, fileOrProps) => {
975
+ var toLambdaFunction = (ctx, id, fileOrProps) => {
976
+ const config = ctx.config;
977
+ const stack = ctx.stack;
616
978
  const props = typeof fileOrProps === "string" ? { ...config.defaults?.function, file: fileOrProps } : { ...config.defaults?.function, ...fileOrProps };
617
- const lambda = new import_aws_lambda3.Function(stack, toId("function", id), {
618
- functionName: toName(stack, id),
619
- handler: "index.default",
620
- code: import_aws_lambda3.Code.fromInline("export default () => {}"),
621
- ...props,
622
- memorySize: props.memorySize.toMebibytes()
979
+ const lambda = new Function(id, {
980
+ name: `${config.name}-${stack.name}-${id}`,
981
+ code: Code.fromFile(id, props.file),
982
+ ...props
623
983
  });
624
- lambda.addEnvironment("APP", config.name, { removeInEdge: true });
625
- lambda.addEnvironment("STAGE", config.stage, { removeInEdge: true });
626
- lambda.addEnvironment("STACK", stack.artifactId, { removeInEdge: true });
627
- if (lambda.runtime.toString().startsWith("nodejs")) {
628
- lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1", {
629
- removeInEdge: true
630
- });
984
+ lambda.addEnvironment("APP", config.name);
985
+ lambda.addEnvironment("STAGE", config.stage);
986
+ lambda.addEnvironment("STACK", stack.name);
987
+ if (props.runtime.startsWith("nodejs")) {
988
+ lambda.addEnvironment("AWS_NODEJS_CONNECTION_REUSE_ENABLED", "1");
631
989
  }
632
- assets.add({
633
- stackName: stack.artifactId,
634
- resource: "function",
635
- resourceName: id,
636
- async build() {
637
- const result = await rollupBuild(props.file);
638
- const bundle = await writeBuildFiles(config, stack, id, result.files);
639
- await writeBuildHash(config, stack, id, result.hash);
640
- const func = lambda.node.defaultChild;
641
- func.handler = result.handler;
642
- return {
643
- size: formatByteSize(bundle.size)
644
- };
645
- },
646
- async publish() {
647
- const version2 = await publishFunctionAsset(config, stack, id);
648
- const func = lambda.node.defaultChild;
649
- func.code = {
650
- s3Bucket: assetBucketName(config),
651
- s3Key: `${config.name}/${stack.artifactId}/function/${id}.zip`,
652
- s3ObjectVersion: version2
653
- };
654
- }
655
- });
656
990
  return lambda;
657
991
  };
658
992
 
993
+ // src/formation/resource/events/rule.ts
994
+ var Rule = class extends Resource {
995
+ constructor(logicalId, props) {
996
+ super("AWS::Events::Rule", logicalId);
997
+ this.props = props;
998
+ this.name = formatName(this.props.name || logicalId);
999
+ }
1000
+ name;
1001
+ get id() {
1002
+ return ref(this.logicalId);
1003
+ }
1004
+ get arn() {
1005
+ return getAtt(this.logicalId, "Arn");
1006
+ }
1007
+ properties() {
1008
+ return {
1009
+ Name: this.name,
1010
+ ...this.attr("State", "ENABLED"),
1011
+ ...this.attr("Description", this.props.description),
1012
+ ...this.attr("ScheduleExpression", this.props.schedule),
1013
+ ...this.attr("RoleArn", this.props.roleArn),
1014
+ ...this.attr("EventBusName", this.props.eventBusName),
1015
+ ...this.attr("EventPattern", this.props.eventPattern),
1016
+ Targets: this.props.targets.map((target) => ({
1017
+ Arn: target.arn,
1018
+ Id: target.id,
1019
+ ...this.attr("Input", target.input && JSON.stringify(target.input))
1020
+ }))
1021
+ };
1022
+ }
1023
+ };
1024
+
1025
+ // src/formation/resource/lambda/permission.ts
1026
+ var Permission2 = class extends Resource {
1027
+ constructor(logicalId, props) {
1028
+ super("AWS::Lambda::Permission", logicalId);
1029
+ this.props = props;
1030
+ }
1031
+ properties() {
1032
+ return {
1033
+ FunctionName: this.props.functionArn,
1034
+ Action: this.props.action || "lambda:InvokeFunction",
1035
+ Principal: this.props.principal,
1036
+ SourceArn: this.props.sourceArn
1037
+ };
1038
+ }
1039
+ };
1040
+
1041
+ // src/formation/resource/lambda/event-source/events.ts
1042
+ var EventsEventSource = class extends Group {
1043
+ constructor(id, lambda, props) {
1044
+ const rule = new Rule(id, {
1045
+ schedule: props.schedule,
1046
+ targets: [{
1047
+ id,
1048
+ arn: lambda.arn,
1049
+ input: props.payload
1050
+ }]
1051
+ });
1052
+ const permission = new Permission2(id, {
1053
+ action: "lambda:InvokeFunction",
1054
+ principal: "events.amazonaws.com",
1055
+ functionArn: lambda.arn,
1056
+ sourceArn: rule.arn
1057
+ });
1058
+ super([rule, permission]);
1059
+ }
1060
+ };
1061
+
659
1062
  // src/plugins/cron/index.ts
660
- var import_aws_events_targets = require("aws-cdk-lib/aws-events-targets");
661
1063
  var cronPlugin = definePlugin({
662
1064
  name: "cron",
663
- schema: import_zod10.z.object({
664
- stacks: import_zod10.z.object({
665
- crons: import_zod10.z.record(ResourceIdSchema, import_zod10.z.object({
1065
+ schema: import_zod7.z.object({
1066
+ stacks: import_zod7.z.object({
1067
+ crons: import_zod7.z.record(ResourceIdSchema, import_zod7.z.object({
666
1068
  consumer: FunctionSchema,
667
1069
  schedule: ScheduleExpressionSchema,
668
- description: import_zod10.z.string().max(512).optional()
1070
+ payload: import_zod7.z.unknown().optional()
669
1071
  })).optional()
670
1072
  }).array()
671
1073
  }),
672
- onStack(context) {
673
- return Object.entries(context.stackConfig.crons || {}).map(([id, props]) => {
674
- const lambda = toFunction(context, id, props.consumer);
675
- const target = new import_aws_events_targets.LambdaFunction(lambda);
676
- new import_aws_events2.Rule(context.stack, toId("cron", id), {
677
- ruleName: toName(context.stack, id),
1074
+ onStack(ctx) {
1075
+ const { stack, stackConfig } = ctx;
1076
+ for (const [id, props] of Object.entries(stackConfig.crons || {})) {
1077
+ const lambda = toLambdaFunction(ctx, id, props.consumer);
1078
+ const source = new EventsEventSource(id, lambda, {
678
1079
  schedule: props.schedule,
679
- description: props.description,
680
- targets: [target]
1080
+ payload: props.payload
681
1081
  });
682
- return lambda;
683
- });
1082
+ stack.add(lambda, source);
1083
+ }
684
1084
  }
685
1085
  });
686
1086
 
687
1087
  // src/plugins/queue.ts
688
- var import_zod11 = require("zod");
689
- var import_aws_sqs = require("aws-cdk-lib/aws-sqs");
690
- var import_aws_lambda_event_sources = require("aws-cdk-lib/aws-lambda-event-sources");
1088
+ var import_zod8 = require("zod");
1089
+
1090
+ // src/formation/resource/sqs/queue.ts
1091
+ var Queue = class extends Resource {
1092
+ constructor(logicalId, props = {}) {
1093
+ super("AWS::SQS::Queue", logicalId);
1094
+ this.props = props;
1095
+ this.name = formatName(this.props.name || logicalId);
1096
+ }
1097
+ name;
1098
+ get arn() {
1099
+ return getAtt(this.logicalId, "Arn");
1100
+ }
1101
+ get url() {
1102
+ return getAtt(this.logicalId, "QueueUrl");
1103
+ }
1104
+ get permissions() {
1105
+ return {
1106
+ actions: [
1107
+ "sqs:SendMessage",
1108
+ "sqs:ReceiveMessage",
1109
+ "sqs:GetQueueUrl",
1110
+ "sqs:GetQueueAttributes"
1111
+ ],
1112
+ resources: [this.arn]
1113
+ };
1114
+ }
1115
+ properties() {
1116
+ return {
1117
+ QueueName: this.name,
1118
+ DelaySeconds: this.props.deliveryDelay?.toSeconds() ?? 0,
1119
+ MaximumMessageSize: this.props.maxMessageSize?.toBytes() ?? Size.kiloBytes(256).toBytes(),
1120
+ MessageRetentionPeriod: this.props.retentionPeriod?.toSeconds() ?? Duration.days(4).toSeconds(),
1121
+ ReceiveMessageWaitTimeSeconds: this.props.receiveMessageWaitTime?.toSeconds() ?? 0,
1122
+ VisibilityTimeout: this.props.visibilityTimeout?.toSeconds() ?? 30
1123
+ };
1124
+ }
1125
+ };
1126
+
1127
+ // src/formation/resource/lambda/event-source-mapping.ts
1128
+ var import_change_case3 = require("change-case");
1129
+ var EventSourceMapping = class extends Resource {
1130
+ constructor(logicalId, props) {
1131
+ super("AWS::Lambda::EventSourceMapping", logicalId);
1132
+ this.props = props;
1133
+ }
1134
+ properties() {
1135
+ return {
1136
+ Enabled: true,
1137
+ FunctionName: this.props.functionArn,
1138
+ EventSourceArn: this.props.sourceArn,
1139
+ ...this.attr("BatchSize", this.props.batchSize),
1140
+ ...this.attr("MaximumBatchingWindowInSeconds", this.props.maxBatchingWindow?.toSeconds()),
1141
+ ...this.attr("MaximumRecordAgeInSeconds", this.props.maxRecordAge?.toSeconds()),
1142
+ ...this.attr("MaximumRetryAttempts", this.props.retryAttempts),
1143
+ ...this.attr("ParallelizationFactor", this.props.parallelizationFactor),
1144
+ ...this.attr("TumblingWindowInSeconds", this.props.tumblingWindow?.toSeconds()),
1145
+ ...this.attr("BisectBatchOnFunctionError", this.props.bisectBatchOnError),
1146
+ ...this.attr("StartingPosition", this.props.startingPosition && (0, import_change_case3.constantCase)(this.props.startingPosition)),
1147
+ ...this.attr("StartingPositionTimestamp", this.props.startingPositionTimestamp),
1148
+ ...this.props.maxConcurrency ? {
1149
+ ScalingConfig: {
1150
+ MaximumConcurrency: this.props.maxConcurrency
1151
+ }
1152
+ } : {},
1153
+ ...this.props.onFailure ? {
1154
+ DestinationConfig: {
1155
+ OnFailure: {
1156
+ Destination: this.props.onFailure
1157
+ }
1158
+ }
1159
+ } : {}
1160
+ };
1161
+ }
1162
+ };
1163
+
1164
+ // src/formation/resource/lambda/event-source/sqs.ts
1165
+ var SqsEventSource = class extends Group {
1166
+ constructor(id, lambda, props) {
1167
+ const source = new EventSourceMapping(id, {
1168
+ functionArn: lambda.arn,
1169
+ sourceArn: props.queueArn,
1170
+ batchSize: props.batchSize ?? 10,
1171
+ maxBatchingWindow: props.maxBatchingWindow,
1172
+ maxConcurrency: props.maxConcurrency,
1173
+ onFailure: props.onFailure
1174
+ });
1175
+ lambda.addPermissions({
1176
+ actions: [
1177
+ "sqs:ReceiveMessage",
1178
+ "sqs:DeleteMessage",
1179
+ "sqs:GetQueueAttributes"
1180
+ ],
1181
+ resources: [props.queueArn]
1182
+ });
1183
+ super([source]);
1184
+ }
1185
+ };
1186
+
1187
+ // src/plugins/queue.ts
691
1188
  var queuePlugin = definePlugin({
692
1189
  name: "queue",
693
- schema: import_zod11.z.object({
694
- defaults: import_zod11.z.object({
695
- queue: import_zod11.z.object({
696
- // fifo: z.boolean().default(false),
1190
+ schema: import_zod8.z.object({
1191
+ defaults: import_zod8.z.object({
1192
+ /** Define the defaults properties for all queue's in your app */
1193
+ queue: import_zod8.z.object({
1194
+ /** The number of seconds that Amazon SQS retains a message.
1195
+ * You can specify a duration value from 1 minute to 14 days.
1196
+ * @default '7 days' */
697
1197
  retentionPeriod: DurationSchema.default("7 days"),
1198
+ /** The length of time during which a message will be unavailable after a message is delivered from the queue.
1199
+ * This blocks other components from receiving the same message and gives the initial component time to process and delete the message from the queue.
1200
+ * You can specify a duration value from 0 to 12 hours.
1201
+ * @default '30 seconds' */
698
1202
  visibilityTimeout: DurationSchema.default("30 seconds"),
1203
+ /** The time in seconds for which the delivery of all messages in the queue is delayed.
1204
+ * You can specify a duration value from 0 to 15 minutes.
1205
+ * @default '0 seconds' */
699
1206
  deliveryDelay: DurationSchema.default("0 seconds"),
1207
+ /** Specifies the duration, in seconds,
1208
+ * that the ReceiveMessage action call waits until a message is in the queue in order to include it in the response,
1209
+ * rather than returning an empty response if a message isn't yet available.
1210
+ * You can specify an integer from 1 to 20.
1211
+ * You can specify a duration value from 1 to 20 seconds.
1212
+ * @default '0 seconds' */
700
1213
  receiveMessageWaitTime: DurationSchema.default("0 seconds"),
1214
+ /** The limit of how many bytes that a message can contain before Amazon SQS rejects it.
1215
+ * You can specify an size value from 1 KB to 256 KB.
1216
+ * @default '256 KB' */
701
1217
  maxMessageSize: SizeSchema.default("256 KB")
702
1218
  }).default({})
703
1219
  }).default({}),
704
- stacks: import_zod11.z.object({
705
- queues: import_zod11.z.record(ResourceIdSchema, import_zod11.z.union([
706
- LocalFileSchema,
707
- import_zod11.z.object({
708
- consumer: FunctionSchema,
709
- // fifo: z.boolean().optional(),
710
- retentionPeriod: DurationSchema.optional(),
711
- visibilityTimeout: DurationSchema.optional(),
712
- deliveryDelay: DurationSchema.optional(),
713
- receiveMessageWaitTime: DurationSchema.optional(),
714
- maxMessageSize: SizeSchema.optional()
715
- })
716
- ])).optional()
1220
+ stacks: import_zod8.z.object({
1221
+ /** Define the queues in your stack
1222
+ * @example
1223
+ * {
1224
+ * queues: {
1225
+ * QUEUE_NAME: 'function.ts'
1226
+ * }
1227
+ * }
1228
+ * */
1229
+ queues: import_zod8.z.record(
1230
+ ResourceIdSchema,
1231
+ import_zod8.z.union([
1232
+ LocalFileSchema,
1233
+ import_zod8.z.object({
1234
+ /** The consuming lambda function properties */
1235
+ consumer: FunctionSchema,
1236
+ /** The number of seconds that Amazon SQS retains a message.
1237
+ * You can specify a duration value from 1 minute to 14 days.
1238
+ * @default '7 days' */
1239
+ retentionPeriod: DurationSchema.optional(),
1240
+ /** The length of time during which a message will be unavailable after a message is delivered from the queue.
1241
+ * This blocks other components from receiving the same message and gives the initial component time to process and delete the message from the queue.
1242
+ * You can specify a duration value from 0 to 12 hours.
1243
+ * @default '30 seconds' */
1244
+ visibilityTimeout: DurationSchema.optional(),
1245
+ /** The time in seconds for which the delivery of all messages in the queue is delayed.
1246
+ * You can specify a duration value from 0 to 15 minutes.
1247
+ * @default '0 seconds' */
1248
+ deliveryDelay: DurationSchema.optional(),
1249
+ /** Specifies the duration, in seconds,
1250
+ * that the ReceiveMessage action call waits until a message is in the queue in order to include it in the response,
1251
+ * rather than returning an empty response if a message isn't yet available.
1252
+ * You can specify an integer from 1 to 20.
1253
+ * You can specify a duration value from 1 to 20 seconds.
1254
+ * @default '0 seconds' */
1255
+ receiveMessageWaitTime: DurationSchema.optional(),
1256
+ /** The limit of how many bytes that a message can contain before Amazon SQS rejects it.
1257
+ * You can specify an size value from 1 KB to 256 KB.
1258
+ * @default '256 KB' */
1259
+ maxMessageSize: SizeSchema.optional()
1260
+ })
1261
+ ])
1262
+ ).optional()
717
1263
  }).array()
718
1264
  }),
719
1265
  onStack(ctx) {
720
1266
  const { stack, config, stackConfig, bind } = ctx;
721
- return Object.entries(stackConfig.queues || {}).map(([id, functionOrProps]) => {
1267
+ for (const [id, functionOrProps] of Object.entries(stackConfig.queues || {})) {
722
1268
  const props = typeof functionOrProps === "string" ? { ...config.defaults.queue, consumer: functionOrProps } : { ...config.defaults.queue, ...functionOrProps };
723
- const queue2 = new import_aws_sqs.Queue(stack, toId("queue", id), {
724
- queueName: toName(stack, id),
725
- ...props,
726
- maxMessageSizeBytes: props.maxMessageSize.toBytes()
1269
+ const queue2 = new Queue(id, {
1270
+ name: `${config.name}-${stack.name}-${id}`,
1271
+ ...props
1272
+ });
1273
+ const lambda = toLambdaFunction(ctx, id, props.consumer);
1274
+ const source = new SqsEventSource(id, lambda, {
1275
+ queueArn: queue2.arn
727
1276
  });
728
- const lambda = toFunction(ctx, id, props.consumer);
729
- lambda.addEventSource(new import_aws_lambda_event_sources.SqsEventSource(queue2));
1277
+ stack.add(queue2, lambda, source);
730
1278
  bind((lambda2) => {
731
- queue2.grantSendMessages(lambda2);
732
- addResourceEnvironment(stack, "queue", id, lambda2);
1279
+ lambda2.addPermissions(queue2.permissions);
733
1280
  });
734
- return lambda;
735
- });
1281
+ }
736
1282
  }
737
1283
  });
738
1284
 
739
- // src/plugins/table/index.ts
740
- var import_zod16 = require("zod");
741
- var import_aws_dynamodb4 = require("aws-cdk-lib/aws-dynamodb");
742
-
743
- // src/plugins/table/schema/class-type.ts
744
- var import_aws_dynamodb = require("aws-cdk-lib/aws-dynamodb");
745
- var import_zod12 = require("zod");
746
- var types = {
747
- "standard": import_aws_dynamodb.TableClass.STANDARD,
748
- "standard-infrequent-access": import_aws_dynamodb.TableClass.STANDARD_INFREQUENT_ACCESS
749
- };
750
- var TableClassSchema = import_zod12.z.enum(Object.keys(types)).transform((value) => {
751
- return types[value];
752
- });
1285
+ // src/plugins/table.ts
1286
+ var import_zod9 = require("zod");
753
1287
 
754
- // src/plugins/table/schema/attribute.ts
755
- var import_aws_dynamodb2 = require("aws-cdk-lib/aws-dynamodb");
756
- var import_zod13 = require("zod");
757
- var types2 = {
758
- string: import_aws_dynamodb2.AttributeType.STRING,
759
- number: import_aws_dynamodb2.AttributeType.NUMBER,
760
- binary: import_aws_dynamodb2.AttributeType.BINARY
1288
+ // src/formation/resource/dynamodb/table.ts
1289
+ var import_change_case4 = require("change-case");
1290
+ var Table = class extends Resource {
1291
+ constructor(logicalId, props) {
1292
+ super("AWS::DynamoDB::Table", logicalId);
1293
+ this.props = props;
1294
+ this.name = formatName(this.props.name || logicalId);
1295
+ this.indexes = { ...this.props.indexes || {} };
1296
+ }
1297
+ name;
1298
+ indexes;
1299
+ addIndex(name, props) {
1300
+ this.indexes[name] = props;
1301
+ }
1302
+ get arn() {
1303
+ return ref(this.logicalId);
1304
+ }
1305
+ get permissions() {
1306
+ return {
1307
+ actions: [
1308
+ "dynamodb:DescribeTable",
1309
+ "dynamodb:PutItem",
1310
+ "dynamodb:GetItem",
1311
+ "dynamodb:DeleteItem",
1312
+ "dynamodb:TransactWrite",
1313
+ "dynamodb:BatchWriteItem",
1314
+ "dynamodb:BatchGetItem",
1315
+ "dynamodb:ConditionCheckItem",
1316
+ "dynamodb:Query",
1317
+ "dynamodb:Scan"
1318
+ ],
1319
+ resources: [this.arn]
1320
+ };
1321
+ }
1322
+ properties() {
1323
+ return {
1324
+ TableName: this.name,
1325
+ BillingMode: "PAY_PER_REQUEST",
1326
+ TableClass: (0, import_change_case4.constantCase)(this.props.class || "standard"),
1327
+ PointInTimeRecoverySpecification: {
1328
+ PointInTimeRecoveryEnabled: this.props.pointInTimeRecovery || false
1329
+ },
1330
+ KeySchema: [
1331
+ { KeyType: "HASH", AttributeName: this.props.hash },
1332
+ ...this.props.sort ? [{ KeyType: "RANGE", AttributeName: this.props.sort }] : []
1333
+ ],
1334
+ AttributeDefinitions: Object.entries(this.props.fields).map(([name, type]) => ({
1335
+ AttributeName: name,
1336
+ AttributeType: type[0].toUpperCase()
1337
+ })),
1338
+ ...this.props.timeToLiveAttribute ? {
1339
+ TimeToLiveSpecification: {
1340
+ AttributeName: this.props.timeToLiveAttribute,
1341
+ Enabled: true
1342
+ }
1343
+ } : {},
1344
+ ...Object.keys(this.indexes).length ? {
1345
+ GlobalSecondaryIndexes: Object.entries(this.indexes).map(([name, props]) => ({
1346
+ IndexName: name,
1347
+ KeySchema: [
1348
+ { KeyType: "HASH", AttributeName: props.hash },
1349
+ ...props.sort ? [{ KeyType: "RANGE", AttributeName: props.sort }] : []
1350
+ ],
1351
+ Projection: {
1352
+ ProjectionType: (0, import_change_case4.constantCase)(props.projection || "all")
1353
+ }
1354
+ }))
1355
+ } : {}
1356
+ };
1357
+ }
761
1358
  };
762
- var AttributeSchema = import_zod13.z.enum(Object.keys(types2)).transform((value) => types2[value]);
763
-
764
- // src/plugins/table/schema/key.ts
765
- var import_zod14 = require("zod");
766
- var KeySchema = import_zod14.z.string().min(1).max(255);
767
-
768
- // src/plugins/table/schema/projection-type.ts
769
- var import_aws_dynamodb3 = require("aws-cdk-lib/aws-dynamodb");
770
- var import_zod15 = require("zod");
771
- var types3 = {
772
- "all": import_aws_dynamodb3.ProjectionType.ALL,
773
- "keys-only": import_aws_dynamodb3.ProjectionType.KEYS_ONLY
774
- };
775
- var ProjectionTypeSchema = import_zod15.z.union([
776
- import_zod15.z.enum(Object.keys(types3)).transform((value) => ({
777
- ProjectionType: types3[value]
778
- })),
779
- import_zod15.z.array(KeySchema).min(0).max(20).transform((keys) => ({
780
- ProjectionType: import_aws_dynamodb3.ProjectionType.INCLUDE,
781
- NonKeyAttributes: keys
782
- }))
783
- ]);
784
1359
 
785
- // src/plugins/table/index.ts
1360
+ // src/plugins/table.ts
1361
+ var KeySchema = import_zod9.z.string().min(1).max(255);
786
1362
  var tablePlugin = definePlugin({
787
1363
  name: "table",
788
- schema: import_zod16.z.object({
789
- stacks: import_zod16.z.object({
790
- tables: import_zod16.z.record(
1364
+ schema: import_zod9.z.object({
1365
+ stacks: import_zod9.z.object({
1366
+ /** Define the tables in your stack
1367
+ * @example
1368
+ * {
1369
+ * tables: {
1370
+ * TABLE_NAME: {
1371
+ * hash: 'id',
1372
+ * fields: {
1373
+ * id: 'number'
1374
+ * }
1375
+ * }
1376
+ * }
1377
+ * }
1378
+ * */
1379
+ tables: import_zod9.z.record(
791
1380
  ResourceIdSchema,
792
- import_zod16.z.object({
1381
+ import_zod9.z.object({
1382
+ /** Specifies the name of the partition / hash key that makes up the primary key for the table. */
793
1383
  hash: KeySchema,
1384
+ /** Specifies the name of the range / sort key that makes up the primary key for the table. */
794
1385
  sort: KeySchema.optional(),
795
- fields: import_zod16.z.record(import_zod16.z.string(), AttributeSchema),
796
- class: TableClassSchema.default("standard"),
797
- pointInTimeRecovery: import_zod16.z.boolean().default(false),
798
- timeToLiveAttribute: import_zod16.z.string().optional(),
799
- indexes: import_zod16.z.record(import_zod16.z.string(), import_zod16.z.object({
1386
+ /** A list of attributes that describe the key schema for the table and indexes.
1387
+ * @example
1388
+ * {
1389
+ * fields: {
1390
+ * id: 'string'
1391
+ * }
1392
+ * }
1393
+ */
1394
+ fields: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.enum(["string", "number", "binary"])),
1395
+ /** The table class of the table.
1396
+ * @default 'standard'
1397
+ */
1398
+ class: import_zod9.z.enum(["standard", "standard-infrequent-access"]).default("standard"),
1399
+ /** Indicates whether point in time recovery is enabled on the table.
1400
+ * @default false
1401
+ */
1402
+ pointInTimeRecovery: import_zod9.z.boolean().default(false),
1403
+ /** The name of the TTL attribute used to store the expiration time for items in the table.
1404
+ * - To update this property, you must first disable TTL and then enable TTL with the new attribute name.
1405
+ */
1406
+ timeToLiveAttribute: KeySchema.optional(),
1407
+ /** Specifies the global secondary indexes to be created on the table.
1408
+ * @example
1409
+ * {
1410
+ * indexes: {
1411
+ * INDEX_NAME: {
1412
+ * hash: 'other'
1413
+ * }
1414
+ * }
1415
+ * }
1416
+ */
1417
+ indexes: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.object({
1418
+ /** Specifies the name of the partition / hash key that makes up the primary key for the global secondary index. */
800
1419
  hash: KeySchema,
1420
+ /** Specifies the name of the range / sort key that makes up the primary key for the global secondary index. */
801
1421
  sort: KeySchema.optional(),
802
- projection: ProjectionTypeSchema.default("all")
1422
+ /** The set of attributes that are projected into the index:
1423
+ * - all - All of the table attributes are projected into the index.
1424
+ * - keys-only - Only the index and primary keys are projected into the index.
1425
+ * @default 'all'
1426
+ */
1427
+ projection: import_zod9.z.enum(["all", "keys-only"]).default("all")
803
1428
  })).optional()
804
1429
  }).refine((props) => {
805
1430
  return (
@@ -816,140 +1441,321 @@ var tablePlugin = definePlugin({
816
1441
  ).optional()
817
1442
  }).array()
818
1443
  }),
819
- onStack({ stack, stackConfig, bind }) {
820
- Object.entries(stackConfig.tables || {}).map(([id, props]) => {
821
- const buildKey = (attr) => {
822
- return { name: attr, type: props.fields[attr] };
823
- };
824
- const table = new import_aws_dynamodb4.Table(stack, toId("table", id), {
825
- tableName: toName(stack, id),
826
- partitionKey: buildKey(props.hash),
827
- sortKey: props.sort ? buildKey(props.sort) : void 0,
828
- billingMode: import_aws_dynamodb4.BillingMode.PAY_PER_REQUEST,
829
- pointInTimeRecovery: props.pointInTimeRecovery,
830
- timeToLiveAttribute: props.timeToLiveAttribute,
831
- tableClass: props.class
832
- });
833
- Object.entries(props.indexes || {}).forEach(([indexName, entry]) => {
834
- table.addGlobalSecondaryIndex({
835
- indexName,
836
- partitionKey: buildKey(entry.hash),
837
- sortKey: entry.sort ? buildKey(entry.sort) : void 0,
838
- ...entry.projection
839
- });
1444
+ onStack({ config, stack, stackConfig, bind }) {
1445
+ for (const [id, props] of Object.entries(stackConfig.tables || {})) {
1446
+ const table = new Table(id, {
1447
+ name: `${config.name}-${stack.name}-${id}`,
1448
+ ...props
840
1449
  });
1450
+ stack.add(table);
841
1451
  bind((lambda) => {
842
- table.grantReadWriteData(lambda);
843
- addResourceEnvironment(stack, "table", id, lambda);
1452
+ lambda.addPermissions(table.permissions);
844
1453
  });
845
- });
1454
+ }
846
1455
  }
847
1456
  });
848
1457
 
849
1458
  // src/plugins/store.ts
850
- var import_zod17 = require("zod");
851
- var import_aws_s32 = require("aws-cdk-lib/aws-s3");
852
- var import_aws_cdk_lib3 = require("aws-cdk-lib");
1459
+ var import_zod10 = require("zod");
1460
+
1461
+ // src/formation/resource/s3/bucket.ts
1462
+ var import_change_case5 = require("change-case");
1463
+ var Bucket = class extends Resource {
1464
+ constructor(logicalId, props = {}) {
1465
+ super("AWS::S3::Bucket", logicalId);
1466
+ this.props = props;
1467
+ this.name = formatName(this.props.name || logicalId);
1468
+ }
1469
+ name;
1470
+ get arn() {
1471
+ return ref(this.logicalId);
1472
+ }
1473
+ get domainName() {
1474
+ return getAtt(this.logicalId, "DomainName");
1475
+ }
1476
+ get permissions() {
1477
+ return {
1478
+ actions: [
1479
+ "s3:SendMessage",
1480
+ "s3:ReceiveMessage",
1481
+ "s3:GetQueueUrl",
1482
+ "s3:GetQueueAttributes"
1483
+ ],
1484
+ resources: [this.arn]
1485
+ };
1486
+ }
1487
+ properties() {
1488
+ return {
1489
+ BucketName: this.name,
1490
+ AccessControl: (0, import_change_case5.pascalCase)(this.props.accessControl ?? "private"),
1491
+ ...this.props.versioned ? {
1492
+ VersioningConfiguration: {
1493
+ Status: "Enabled"
1494
+ }
1495
+ } : {}
1496
+ };
1497
+ }
1498
+ };
1499
+
1500
+ // src/plugins/store.ts
853
1501
  var storePlugin = definePlugin({
854
1502
  name: "store",
855
- schema: import_zod17.z.object({
856
- stacks: import_zod17.z.object({
857
- stores: import_zod17.z.array(ResourceIdSchema).optional()
1503
+ schema: import_zod10.z.object({
1504
+ stacks: import_zod10.z.object({
1505
+ /** Define the stores in your stack
1506
+ * @example
1507
+ * {
1508
+ * stores: [ 'STORE_NAME' ]
1509
+ * }
1510
+ * */
1511
+ stores: import_zod10.z.array(ResourceIdSchema).optional()
858
1512
  }).array()
859
1513
  }),
860
- onStack({ stack, stackConfig, bind }) {
861
- (stackConfig.stores || []).forEach((id) => {
862
- const bucket = new import_aws_s32.Bucket(stack, toId("store", id), {
863
- bucketName: toName(stack, id),
864
- accessControl: import_aws_s32.BucketAccessControl.PRIVATE,
865
- removalPolicy: import_aws_cdk_lib3.RemovalPolicy.DESTROY
1514
+ onStack({ config, stack, stackConfig, bind }) {
1515
+ for (const id of stackConfig.stores || []) {
1516
+ const bucket = new Bucket(id, {
1517
+ name: `${config.name}-${stack.name}-${id}`,
1518
+ accessControl: "private"
866
1519
  });
1520
+ stack.add(bucket);
867
1521
  bind((lambda) => {
868
- bucket.grantReadWrite(lambda), addResourceEnvironment(stack, "store", id, lambda);
1522
+ lambda.addPermissions(bucket.permissions);
869
1523
  });
870
- });
1524
+ }
871
1525
  }
872
1526
  });
873
1527
 
874
1528
  // src/plugins/topic.ts
875
- var import_zod18 = require("zod");
876
- var import_aws_sns = require("aws-cdk-lib/aws-sns");
877
- var import_aws_lambda_event_sources2 = require("aws-cdk-lib/aws-lambda-event-sources");
878
- var import_aws_cdk_lib4 = require("aws-cdk-lib");
1529
+ var import_zod11 = require("zod");
1530
+
1531
+ // src/formation/resource/sns/topic.ts
1532
+ var Topic = class extends Resource {
1533
+ constructor(logicalId, props = {}) {
1534
+ super("AWS::SNS::Topic", logicalId);
1535
+ this.props = props;
1536
+ this.name = formatName(this.props.name || logicalId);
1537
+ }
1538
+ name;
1539
+ get arn() {
1540
+ return ref(this.logicalId);
1541
+ }
1542
+ get permissions() {
1543
+ return {
1544
+ actions: ["sns:Publish"],
1545
+ resources: [this.arn]
1546
+ };
1547
+ }
1548
+ properties() {
1549
+ return {
1550
+ TopicName: this.name,
1551
+ DisplayName: this.name
1552
+ };
1553
+ }
1554
+ };
1555
+
1556
+ // src/formation/resource/sns/subscription.ts
1557
+ var Subscription = class extends Resource {
1558
+ constructor(logicalId, props) {
1559
+ super("AWS::SNS::Subscription", logicalId);
1560
+ this.props = props;
1561
+ }
1562
+ properties() {
1563
+ return {
1564
+ TopicArn: this.props.topicArn,
1565
+ Protocol: this.props.protocol,
1566
+ Endpoint: this.props.endpoint
1567
+ };
1568
+ }
1569
+ };
1570
+
1571
+ // src/formation/resource/lambda/event-source/sns.ts
1572
+ var SnsEventSource = class extends Group {
1573
+ constructor(id, lambda, props) {
1574
+ const topic = new Subscription(id, {
1575
+ topicArn: props.topicArn,
1576
+ protocol: "lambda",
1577
+ endpoint: lambda.arn
1578
+ });
1579
+ const permission = new Permission2(id, {
1580
+ action: "lambda:InvokeFunction",
1581
+ principal: "sns.amazonaws.com",
1582
+ functionArn: lambda.arn,
1583
+ sourceArn: props.topicArn
1584
+ });
1585
+ super([topic, permission]);
1586
+ }
1587
+ };
1588
+
1589
+ // src/plugins/topic.ts
879
1590
  var topicPlugin = definePlugin({
880
1591
  name: "topic",
881
- schema: import_zod18.z.object({
882
- stacks: import_zod18.z.object({
883
- topics: import_zod18.z.record(ResourceIdSchema, FunctionSchema).optional()
1592
+ schema: import_zod11.z.object({
1593
+ stacks: import_zod11.z.object({
1594
+ /** Define the topics to listen too in your stack
1595
+ * @example
1596
+ * {
1597
+ * topics: {
1598
+ * TOPIC_NAME: 'function.ts'
1599
+ * }
1600
+ * }
1601
+ * */
1602
+ topics: import_zod11.z.record(ResourceIdSchema, FunctionSchema).optional()
884
1603
  }).array()
885
1604
  }),
886
- onBootstrap({ config, stack }) {
887
- const allTopicNames = config.stacks.map((stack2) => {
888
- return Object.keys(stack2.topics || {});
1605
+ onApp({ config, bootstrap: bootstrap2, bind }) {
1606
+ const allTopicNames = config.stacks.map((stack) => {
1607
+ return Object.keys(stack.topics || {});
889
1608
  }).flat();
890
1609
  const uniqueTopicNames = [...new Set(allTopicNames)];
891
- uniqueTopicNames.forEach((id) => {
892
- new import_aws_sns.Topic(stack, toId("topic", id), {
893
- topicName: `${config.name}-${id}`,
894
- displayName: id
1610
+ for (const id of uniqueTopicNames) {
1611
+ const topic = new Topic(id, {
1612
+ name: `${config.name}-${id}`
1613
+ });
1614
+ bootstrap2.add(topic);
1615
+ bootstrap2.export(`topic-${id}-arn`, topic.arn);
1616
+ }
1617
+ bind((lambda) => {
1618
+ lambda.addPermissions({
1619
+ actions: ["sns:Publish"],
1620
+ resources: [
1621
+ sub("arn:${AWS::Partition}:sns:${AWS::Region}:${AWS::AccountId}:${app}-*", {
1622
+ app: config.name
1623
+ })
1624
+ ]
895
1625
  });
896
1626
  });
897
1627
  },
898
1628
  onStack(ctx) {
899
- const { config, stack, stackConfig, bind } = ctx;
900
- return Object.entries(stackConfig.topics || {}).map(([id, props]) => {
901
- const lambda = toFunction(ctx, id, props);
902
- const topic = import_aws_sns.Topic.fromTopicArn(
903
- stack,
904
- toId("topic", id),
905
- import_aws_cdk_lib4.Arn.format({
906
- arnFormat: import_aws_cdk_lib4.ArnFormat.NO_RESOURCE_NAME,
907
- service: "sns",
908
- resource: `${config.name}-${id}`
909
- }, stack)
910
- );
911
- lambda.addEventSource(new import_aws_lambda_event_sources2.SnsEventSource(topic));
912
- bind((lambda2) => {
913
- addResourceEnvironment(stack, "topic", id, lambda2);
914
- topic.grantPublish(lambda2);
1629
+ const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
1630
+ for (const [id, props] of Object.entries(stackConfig.topics || {})) {
1631
+ const lambda = toLambdaFunction(ctx, id, props);
1632
+ const source = new SnsEventSource(id, lambda, {
1633
+ topicArn: bootstrap2.import(`topic-${id}-arn`)
915
1634
  });
916
- return lambda;
917
- });
1635
+ stack.add(lambda, source);
1636
+ }
918
1637
  }
919
1638
  });
920
1639
 
921
- // src/plugins/search.ts
922
- var import_zod19 = require("zod");
923
- var import_aws_opensearchserverless = require("aws-cdk-lib/aws-opensearchserverless");
924
- var import_aws_iam2 = require("aws-cdk-lib/aws-iam");
925
- var searchPlugin = definePlugin({
926
- name: "search",
927
- schema: import_zod19.z.object({
928
- stacks: import_zod19.z.object({
929
- searchs: import_zod19.z.array(ResourceIdSchema).optional()
1640
+ // src/plugins/extend.ts
1641
+ var import_zod12 = require("zod");
1642
+ var extendPlugin = definePlugin({
1643
+ name: "extend",
1644
+ schema: import_zod12.z.object({
1645
+ /** Extend your app with custom resources */
1646
+ extend: import_zod12.z.custom().optional(),
1647
+ stacks: import_zod12.z.object({
1648
+ /** Extend your stack with custom resources */
1649
+ extend: import_zod12.z.custom().optional()
930
1650
  }).array()
931
1651
  }),
932
- onStack({ stack, stackConfig, bind }) {
933
- (stackConfig.searchs || []).forEach((id) => {
934
- const collection = new import_aws_opensearchserverless.CfnCollection(stack, toId("search", id), {
935
- name: toName(stack, id),
936
- type: "SEARCH"
937
- });
938
- bind((lambda) => {
939
- lambda.addToRolePolicy(new import_aws_iam2.PolicyStatement({
940
- actions: ["aoss:APIAccessAll"],
941
- resources: [collection.attrArn]
942
- }));
1652
+ onApp(ctx) {
1653
+ ctx.config.extend?.(ctx);
1654
+ },
1655
+ onStack(ctx) {
1656
+ ctx.stackConfig.extend?.(ctx);
1657
+ }
1658
+ });
1659
+
1660
+ // src/plugins/pubsub.ts
1661
+ var import_zod13 = require("zod");
1662
+
1663
+ // src/formation/resource/iot/topic-rule.ts
1664
+ var import_change_case6 = require("change-case");
1665
+ var TopicRule = class extends Resource {
1666
+ constructor(logicalId, props) {
1667
+ super("AWS::IoT::TopicRule", logicalId);
1668
+ this.props = props;
1669
+ this.name = (0, import_change_case6.snakeCase)(this.props.name || logicalId);
1670
+ }
1671
+ name;
1672
+ get arn() {
1673
+ return getAtt(this.logicalId, "Arn");
1674
+ }
1675
+ properties() {
1676
+ return {
1677
+ RuleName: this.name,
1678
+ TopicRulePayload: {
1679
+ Sql: this.props.sql,
1680
+ AwsIotSqlVersion: this.props.sqlVersion ?? "2016-03-23",
1681
+ RuleDisabled: false,
1682
+ Actions: this.props.actions.map((action) => ({
1683
+ Lambda: { FunctionArn: action.lambda.functionArn }
1684
+ }))
1685
+ }
1686
+ };
1687
+ }
1688
+ };
1689
+
1690
+ // src/formation/resource/lambda/event-source/iot.ts
1691
+ var IotEventSource = class extends Group {
1692
+ constructor(id, lambda, props) {
1693
+ const topic = new TopicRule(id, {
1694
+ name: props.name,
1695
+ sql: props.sql,
1696
+ sqlVersion: props.sqlVersion,
1697
+ actions: [{ lambda: { functionArn: lambda.arn } }]
1698
+ });
1699
+ const permission = new Permission2(id, {
1700
+ action: "lambda:InvokeFunction",
1701
+ principal: "iot.amazonaws.com",
1702
+ functionArn: lambda.arn,
1703
+ sourceArn: topic.arn
1704
+ });
1705
+ super([topic, permission]);
1706
+ }
1707
+ };
1708
+
1709
+ // src/plugins/pubsub.ts
1710
+ var pubsubPlugin = definePlugin({
1711
+ name: "pubsub",
1712
+ schema: import_zod13.z.object({
1713
+ stacks: import_zod13.z.object({
1714
+ /** Define the pubsub subscriber in your stack
1715
+ * @example
1716
+ * {
1717
+ * pubsub: {
1718
+ * NAME: {
1719
+ * sql: 'SELECT * FROM "table"',
1720
+ * consumer: 'function.ts',
1721
+ * }
1722
+ * }
1723
+ * }
1724
+ */
1725
+ pubsub: import_zod13.z.record(ResourceIdSchema, import_zod13.z.object({
1726
+ /** The SQL statement used to query the iot topic */
1727
+ sql: import_zod13.z.string(),
1728
+ /** The version of the SQL rules engine to use when evaluating the rule */
1729
+ sqlVersion: import_zod13.z.enum(["2015-10-08", "2016-03-23", "beta"]).default("2016-03-23"),
1730
+ /** The consuming lambda function properties */
1731
+ consumer: FunctionSchema
1732
+ })).optional()
1733
+ }).array()
1734
+ }),
1735
+ onApp({ bind }) {
1736
+ bind((lambda) => {
1737
+ lambda.addPermissions({
1738
+ actions: ["iot:publish"],
1739
+ resources: ["*"]
943
1740
  });
944
1741
  });
1742
+ },
1743
+ onStack(ctx) {
1744
+ const { config, stack, stackConfig } = ctx;
1745
+ for (const [id, props] of Object.entries(stackConfig.pubsub || {})) {
1746
+ const lambda = toLambdaFunction(ctx, id, props.consumer);
1747
+ const source = new IotEventSource(id, lambda, {
1748
+ name: `${config.name}-${stack.name}-${id}`,
1749
+ sql: props.sql,
1750
+ sqlVersion: props.sqlVersion
1751
+ });
1752
+ stack.add(lambda, source);
1753
+ }
945
1754
  }
946
1755
  });
947
1756
 
948
- // src/plugins/graphql/index.ts
949
- var import_zod21 = require("zod");
950
- var import_aws_appsync = require("aws-cdk-lib/aws-appsync");
951
- var import_merge = require("@graphql-tools/merge");
952
- var import_promises4 = require("fs/promises");
1757
+ // src/plugins/graphql.ts
1758
+ var import_zod14 = require("zod");
953
1759
 
954
1760
  // src/util/array.ts
955
1761
  var toArray = (value) => {
@@ -959,298 +1765,707 @@ var toArray = (value) => {
959
1765
  return [value];
960
1766
  };
961
1767
 
962
- // src/plugins/graphql/index.ts
963
- var import_path6 = require("path");
1768
+ // src/plugins/graphql.ts
1769
+ var import_change_case10 = require("change-case");
1770
+
1771
+ // src/formation/resource/appsync/graphql-api.ts
1772
+ var import_change_case7 = require("change-case");
1773
+ var GraphQL = class extends Group {
1774
+ constructor(logicalId, props) {
1775
+ const api = new GraphQLApi(logicalId, props);
1776
+ const schema2 = new GraphQLSchema(logicalId, {
1777
+ apiId: api.id,
1778
+ definition: props.schema
1779
+ }).dependsOn(api);
1780
+ super([api, schema2]);
1781
+ this.logicalId = logicalId;
1782
+ this.api = api;
1783
+ this.schema = schema2;
1784
+ }
1785
+ api;
1786
+ schema;
1787
+ attachDomainName(domainName, certificateArn) {
1788
+ const id = this.logicalId + domainName;
1789
+ const domain = new DomainName(id, {
1790
+ domainName,
1791
+ certificateArn
1792
+ });
1793
+ const association = new DomainNameApiAssociation(id, {
1794
+ apiId: this.api.id,
1795
+ domainName
1796
+ }).dependsOn(this.api, domain);
1797
+ this.children.push(domain, association);
1798
+ return this;
1799
+ }
1800
+ };
1801
+ var GraphQLApi = class extends Resource {
1802
+ constructor(logicalId, props) {
1803
+ super("AWS::AppSync::GraphQLApi", logicalId);
1804
+ this.props = props;
1805
+ this.name = formatName(this.props.name || logicalId);
1806
+ }
1807
+ name;
1808
+ lambdaAuthProviders = [];
1809
+ get arn() {
1810
+ return ref(this.logicalId);
1811
+ }
1812
+ get id() {
1813
+ return getAtt(this.logicalId, "ApiId");
1814
+ }
1815
+ get url() {
1816
+ return getAtt(this.logicalId, "GraphQLUrl");
1817
+ }
1818
+ get dns() {
1819
+ return getAtt(this.logicalId, "GraphQLDns");
1820
+ }
1821
+ addLambdaAuthProvider(lambdaAuthorizerArn, resultTTL = Duration.seconds(0)) {
1822
+ this.lambdaAuthProviders.push({
1823
+ arn: lambdaAuthorizerArn,
1824
+ ttl: resultTTL
1825
+ });
1826
+ return this;
1827
+ }
1828
+ properties() {
1829
+ return {
1830
+ Name: this.name,
1831
+ AuthenticationType: (0, import_change_case7.constantCase)(this.props.authenticationType || "api-key"),
1832
+ AdditionalAuthenticationProviders: this.lambdaAuthProviders.map((provider) => ({
1833
+ AuthenticationType: "AWS_LAMBDA",
1834
+ LambdaAuthorizerConfig: {
1835
+ AuthorizerUri: provider.arn,
1836
+ AuthorizerResultTtlInSeconds: provider.ttl.toSeconds()
1837
+ }
1838
+ }))
1839
+ };
1840
+ }
1841
+ };
1842
+ var GraphQLSchema = class extends Resource {
1843
+ constructor(logicalId, props) {
1844
+ super("AWS::AppSync::GraphQLSchema", logicalId, [
1845
+ props.definition
1846
+ ]);
1847
+ this.props = props;
1848
+ }
1849
+ properties() {
1850
+ return {
1851
+ ApiId: this.props.apiId,
1852
+ Definition: this.props.definition.toDefinition()
1853
+ };
1854
+ }
1855
+ };
1856
+ var DomainName = class extends Resource {
1857
+ constructor(logicalId, props) {
1858
+ super("AWS::AppSync::DomainName", logicalId);
1859
+ this.props = props;
1860
+ }
1861
+ properties() {
1862
+ return {
1863
+ DomainName: this.props.domainName,
1864
+ CertificateArn: this.props.certificateArn
1865
+ };
1866
+ }
1867
+ };
1868
+ var DomainNameApiAssociation = class extends Resource {
1869
+ constructor(logicalId, props) {
1870
+ super("AWS::AppSync::DomainNameApiAssociation", logicalId);
1871
+ this.props = props;
1872
+ }
1873
+ properties() {
1874
+ return {
1875
+ ApiId: this.props.apiId,
1876
+ DomainName: this.props.domainName
1877
+ };
1878
+ }
1879
+ };
1880
+
1881
+ // src/formation/resource/route53/record-set.ts
1882
+ var RecordSet = class extends Resource {
1883
+ constructor(logicalId, props) {
1884
+ super("AWS::Route53::RecordSet", logicalId);
1885
+ this.props = props;
1886
+ this.name = this.props.name || this.logicalId;
1887
+ }
1888
+ name;
1889
+ properties() {
1890
+ return {
1891
+ HostedZoneId: this.props.hostedZoneId,
1892
+ Name: this.name + ".",
1893
+ Type: this.props.type,
1894
+ TTL: this.props.ttl,
1895
+ ...this.props.records ? {
1896
+ ResourceRecords: this.props.records
1897
+ } : {},
1898
+ ...this.props.alias ? {
1899
+ AliasTarget: {
1900
+ DNSName: this.props.alias,
1901
+ HostedZoneId: this.props.hostedZoneId
1902
+ }
1903
+ } : {}
1904
+ };
1905
+ }
1906
+ };
1907
+
1908
+ // src/formation/resource/appsync/schema.ts
964
1909
  var import_graphql = require("graphql");
965
- var import_change_case2 = require("change-case");
1910
+ var import_promises2 = require("fs/promises");
1911
+ var import_merge = require("@graphql-tools/merge");
1912
+ var Schema = class extends Asset {
1913
+ constructor(id, files) {
1914
+ super("graphql", id);
1915
+ this.files = files;
1916
+ }
1917
+ schema;
1918
+ async build({ write }) {
1919
+ const files = [this.files].flat();
1920
+ const schemas = await Promise.all(files.map((file) => {
1921
+ return (0, import_promises2.readFile)(file, "utf8");
1922
+ }));
1923
+ const defs = (0, import_merge.mergeTypeDefs)(schemas);
1924
+ const schema2 = (0, import_graphql.print)(defs);
1925
+ await write("schema.gql", schema2);
1926
+ this.schema = schema2;
1927
+ }
1928
+ toDefinition() {
1929
+ return this.schema;
1930
+ }
1931
+ };
966
1932
 
967
- // src/plugins/graphql/schema/resolver-field.ts
968
- var import_zod20 = require("zod");
969
- var ResolverFieldSchema = import_zod20.z.custom((value) => {
970
- return import_zod20.z.string().regex(/([a-z0-9\_]+)(\s){1}([a-z0-9\_]+)/gi).safeParse(value).success;
971
- }, `Invalid resolver field. Valid example: "Query list"`);
1933
+ // src/formation/resource/appsync/code.ts
1934
+ var import_promises3 = require("fs/promises");
1935
+ var Code2 = class {
1936
+ static fromFile(id, file) {
1937
+ return new FileCode2(id, file);
1938
+ }
1939
+ static fromInline(id, code) {
1940
+ return new InlineCode2(id, code);
1941
+ }
1942
+ };
1943
+ var InlineCode2 = class extends Asset {
1944
+ constructor(id, code) {
1945
+ super("resolver", id);
1946
+ this.code = code;
1947
+ }
1948
+ toCodeJson() {
1949
+ return {
1950
+ Code: this.code
1951
+ };
1952
+ }
1953
+ };
1954
+ var FileCode2 = class extends Asset {
1955
+ constructor(id, file) {
1956
+ super("resolver", id);
1957
+ this.file = file;
1958
+ }
1959
+ code;
1960
+ async build() {
1961
+ const code = await (0, import_promises3.readFile)(this.file);
1962
+ this.code = code.toString("utf8");
1963
+ return {
1964
+ size: formatByteSize(code.byteLength)
1965
+ };
1966
+ }
1967
+ toCodeJson() {
1968
+ return {
1969
+ Code: this.code
1970
+ };
1971
+ }
1972
+ };
1973
+
1974
+ // src/formation/resource/appsync/data-source.ts
1975
+ var import_change_case8 = require("change-case");
1976
+ var DataSource = class extends Resource {
1977
+ constructor(logicalId, props) {
1978
+ super("AWS::AppSync::DataSource", logicalId);
1979
+ this.props = props;
1980
+ this.name = (0, import_change_case8.snakeCase)(this.props.name || logicalId);
1981
+ }
1982
+ static fromLambda(logicalId, apiId, props) {
1983
+ return new DataSource(logicalId, {
1984
+ apiId,
1985
+ type: "AWS_LAMBDA",
1986
+ serviceRoleArn: props.serviceRoleArn,
1987
+ config: {
1988
+ lambda: {
1989
+ functionArn: props.functionArn
1990
+ }
1991
+ }
1992
+ });
1993
+ }
1994
+ static fromNone(logicalId, apiId) {
1995
+ return new DataSource(logicalId, {
1996
+ apiId,
1997
+ type: "NONE"
1998
+ });
1999
+ }
2000
+ name;
2001
+ get arn() {
2002
+ return ref(this.logicalId);
2003
+ }
2004
+ properties() {
2005
+ return {
2006
+ ApiId: this.props.apiId,
2007
+ Name: this.name,
2008
+ Type: this.props.type,
2009
+ ServiceRoleArn: this.props.serviceRoleArn,
2010
+ ...this.props.config?.lambda ? {
2011
+ LambdaConfig: {
2012
+ LambdaFunctionArn: this.props.config.lambda.functionArn
2013
+ }
2014
+ } : {}
2015
+ };
2016
+ }
2017
+ };
2018
+
2019
+ // src/formation/resource/appsync/function-configuration.ts
2020
+ var import_change_case9 = require("change-case");
2021
+ var FunctionConfiguration = class extends Resource {
2022
+ constructor(logicalId, props) {
2023
+ super("AWS::AppSync::FunctionConfiguration", logicalId, [
2024
+ props.code
2025
+ ]);
2026
+ this.props = props;
2027
+ this.name = (0, import_change_case9.snakeCase)(this.props.name || logicalId);
2028
+ }
2029
+ name;
2030
+ get id() {
2031
+ return getAtt(this.logicalId, "FunctionId");
2032
+ }
2033
+ get arn() {
2034
+ return ref(this.logicalId);
2035
+ }
2036
+ properties() {
2037
+ return {
2038
+ ApiId: this.props.apiId,
2039
+ Name: this.name,
2040
+ DataSourceName: this.props.dataSourceName,
2041
+ ...this.props.code.toCodeJson(),
2042
+ FunctionVersion: "2018-05-29",
2043
+ Runtime: {
2044
+ Name: "APPSYNC_JS",
2045
+ RuntimeVersion: "1.0.0"
2046
+ }
2047
+ };
2048
+ }
2049
+ };
2050
+
2051
+ // src/formation/resource/appsync/resolver.ts
2052
+ var Resolver = class extends Resource {
2053
+ constructor(logicalId, props) {
2054
+ super("AWS::AppSync::Resolver", logicalId);
2055
+ this.props = props;
2056
+ }
2057
+ properties() {
2058
+ return {
2059
+ ApiId: this.props.apiId,
2060
+ Kind: "PIPELINE",
2061
+ TypeName: this.props.typeName,
2062
+ FieldName: this.props.fieldName,
2063
+ PipelineConfig: {
2064
+ Functions: this.props.functions
2065
+ },
2066
+ // DataSourceName: this.props.dataSourceName,
2067
+ ...this.props.code.toCodeJson(),
2068
+ Runtime: {
2069
+ Name: "APPSYNC_JS",
2070
+ RuntimeVersion: "1.0.0"
2071
+ }
2072
+ };
2073
+ }
2074
+ };
972
2075
 
973
- // src/plugins/graphql/index.ts
974
- var import_aws_cdk_lib5 = require("aws-cdk-lib");
2076
+ // src/formation/resource/lambda/event-source/appsync.ts
2077
+ var AppsyncEventSource = class extends Group {
2078
+ constructor(id, lambda, props) {
2079
+ const role = new Role(id + "AppSync", {
2080
+ assumedBy: "appsync.amazonaws.com"
2081
+ }).dependsOn(lambda);
2082
+ role.addInlinePolicy(new InlinePolicy(id, {
2083
+ statements: [{
2084
+ actions: ["lambda:InvokeFunction"],
2085
+ resources: [lambda.arn]
2086
+ }]
2087
+ }));
2088
+ const source = DataSource.fromLambda(id, props.apiId, {
2089
+ functionArn: lambda.arn,
2090
+ serviceRoleArn: role.arn
2091
+ }).dependsOn(role).dependsOn(lambda);
2092
+ const config = new FunctionConfiguration(id, {
2093
+ apiId: props.apiId,
2094
+ code: props.code,
2095
+ dataSourceName: source.name
2096
+ }).dependsOn(source);
2097
+ const resolver = new Resolver(id, {
2098
+ apiId: props.apiId,
2099
+ typeName: props.typeName,
2100
+ fieldName: props.fieldName,
2101
+ functions: [config.id],
2102
+ code: props.code
2103
+ }).dependsOn(config);
2104
+ super([role, source, config, resolver]);
2105
+ }
2106
+ };
2107
+
2108
+ // src/plugins/graphql.ts
2109
+ var defaultResolver = `
2110
+ export function request(ctx) {
2111
+ return {
2112
+ operation: 'Invoke',
2113
+ payload: ctx,
2114
+ };
2115
+ }
2116
+
2117
+ export function response(ctx) {
2118
+ return ctx.result
2119
+ }
2120
+ `;
2121
+ var ResolverFieldSchema = import_zod14.z.custom((value) => {
2122
+ return import_zod14.z.string().regex(/([a-z0-9\_]+)(\s){1}([a-z0-9\_]+)/gi).safeParse(value).success;
2123
+ }, `Invalid resolver field. Valid example: "Query list"`);
975
2124
  var graphqlPlugin = definePlugin({
976
2125
  name: "graphql",
977
- schema: import_zod21.z.object({
978
- defaults: import_zod21.z.object({
979
- graphql: import_zod21.z.record(ResourceIdSchema, import_zod21.z.object({
980
- authorization: import_zod21.z.object({
2126
+ schema: import_zod14.z.object({
2127
+ defaults: import_zod14.z.object({
2128
+ graphql: import_zod14.z.record(ResourceIdSchema, import_zod14.z.object({
2129
+ domain: import_zod14.z.string().optional(),
2130
+ subDomain: import_zod14.z.string().optional(),
2131
+ authorization: import_zod14.z.object({
981
2132
  authorizer: FunctionSchema,
982
2133
  ttl: DurationSchema.default("1 hour")
983
2134
  }).optional(),
984
- mappingTemplate: import_zod21.z.object({
985
- request: LocalFileSchema.optional(),
986
- response: LocalFileSchema.optional()
987
- }).optional()
2135
+ resolver: LocalFileSchema.optional()
988
2136
  })).optional()
989
2137
  }).default({}),
990
- stacks: import_zod21.z.object({
991
- graphql: import_zod21.z.record(ResourceIdSchema, import_zod21.z.object({
992
- schema: import_zod21.z.union([
2138
+ stacks: import_zod14.z.object({
2139
+ graphql: import_zod14.z.record(ResourceIdSchema, import_zod14.z.object({
2140
+ schema: import_zod14.z.union([
993
2141
  LocalFileSchema,
994
- import_zod21.z.array(LocalFileSchema).min(1)
2142
+ import_zod14.z.array(LocalFileSchema).min(1)
995
2143
  ]).optional(),
996
- resolvers: import_zod21.z.record(ResolverFieldSchema, FunctionSchema).optional()
2144
+ resolvers: import_zod14.z.record(ResolverFieldSchema, FunctionSchema).optional()
997
2145
  })).optional()
998
2146
  }).array()
999
2147
  }),
1000
- onBootstrap({ config, stack, assets }) {
1001
- const list3 = /* @__PURE__ */ new Set();
1002
- Object.values(config.stacks).forEach((stackConfig) => {
1003
- Object.keys(stackConfig.graphql || {}).forEach((id) => {
1004
- list3.add(id);
1005
- });
1006
- });
1007
- list3.forEach((id) => {
1008
- const file = (0, import_path6.join)(assetDir, "graphql", config.name, id, "schema.graphql");
1009
- const authorization = config.defaults.graphql?.[id]?.authorization;
1010
- const authProps = {};
1011
- if (authorization) {
1012
- const authorizer = toFunction({ config, assets, stack }, `${id}-authorizer`, authorization.authorizer);
1013
- authProps.additionalAuthenticationProviders = [{
1014
- authenticationType: import_aws_appsync.AuthorizationType.LAMBDA,
1015
- lambdaAuthorizerConfig: {
1016
- authorizerUri: authorizer.functionArn,
1017
- authorizerResultTtlInSeconds: authorization.ttl.toSeconds()
1018
- }
1019
- }];
2148
+ onApp(ctx) {
2149
+ const { config, bootstrap: bootstrap2, usEastBootstrap } = ctx;
2150
+ const apis = /* @__PURE__ */ new Set();
2151
+ for (const stackConfig of config.stacks) {
2152
+ for (const id of Object.keys(stackConfig.graphql || {})) {
2153
+ apis.add(id);
1020
2154
  }
1021
- const api = new import_aws_appsync.CfnGraphQLApi(stack, toId("graphql", id), {
1022
- ...authProps,
1023
- name: toName(stack, id),
1024
- authenticationType: import_aws_appsync.AuthorizationType.API_KEY
1025
- });
1026
- new import_aws_cdk_lib5.CfnOutput(stack, toId("output", id), {
1027
- exportName: toId("graphql", id),
1028
- value: api.attrApiId
1029
- });
1030
- assets.add({
1031
- stackName: stack.artifactId,
1032
- resource: "schema",
1033
- resourceName: id,
1034
- async build() {
1035
- const schemas = [];
1036
- await Promise.all(Object.values(config.stacks).map(async (stackConfig) => {
1037
- const schemaFiles = toArray(stackConfig.graphql?.[id].schema || []);
1038
- await Promise.all(schemaFiles.map(async (schemaFile) => {
1039
- const schema3 = await (0, import_promises4.readFile)(schemaFile, "utf8");
1040
- schemas.push(schema3);
1041
- }));
1042
- }));
1043
- const schema2 = (0, import_graphql.print)((0, import_merge.mergeTypeDefs)(schemas));
1044
- await (0, import_promises4.mkdir)((0, import_path6.dirname)(file), { recursive: true });
1045
- await (0, import_promises4.writeFile)(file, schema2);
1046
- new import_aws_appsync.CfnGraphQLSchema(stack, toId("schema", id), {
1047
- apiId: api.attrApiId,
1048
- definition: schema2
1049
- });
1050
- }
2155
+ }
2156
+ for (const id of apis) {
2157
+ const schema2 = [];
2158
+ for (const stack of config.stacks) {
2159
+ const files = toArray(stack.graphql?.[id]?.schema || []);
2160
+ schema2.push(...files);
2161
+ }
2162
+ const graphql = new GraphQL(id, {
2163
+ name: `${config.name}-${id}`,
2164
+ authenticationType: "api-key",
2165
+ schema: new Schema(id, schema2)
1051
2166
  });
1052
- });
2167
+ bootstrap2.add(graphql).export(`graphql-${id}`, graphql.api.id);
2168
+ const props = config.defaults.graphql?.[id];
2169
+ if (!props) {
2170
+ continue;
2171
+ }
2172
+ if (props.authorization) {
2173
+ const lambda = toLambdaFunction(ctx, `${id}-authorizer`, props.authorization.authorizer);
2174
+ graphql.api.addLambdaAuthProvider(lambda.arn, props.authorization.ttl);
2175
+ bootstrap2.add(lambda);
2176
+ }
2177
+ if (props.domain) {
2178
+ const domainName = props.subDomain ? `${props.subDomain}.${props.domain}` : props.domain;
2179
+ const hostedZoneId = ref(`${props.domain}Route53HostedZone`);
2180
+ const certificateArn = usEastBootstrap.import(`certificate-${props.domain}-arn`);
2181
+ graphql.attachDomainName(domainName, certificateArn);
2182
+ const record = new RecordSet(id, {
2183
+ hostedZoneId,
2184
+ type: "A",
2185
+ name: domainName,
2186
+ alias: graphql.api.dns
2187
+ });
2188
+ bootstrap2.add(record);
2189
+ }
2190
+ }
1053
2191
  },
1054
2192
  onStack(ctx) {
1055
- const { config, stack, stackConfig } = ctx;
1056
- return Object.entries(stackConfig.graphql || {}).map(([id, props]) => {
1057
- const defaults = config.defaults.graphql?.[id] || {};
1058
- return Object.entries(props.resolvers || {}).map(([typeAndField, functionProps]) => {
1059
- const api = import_aws_appsync.GraphqlApi.fromGraphqlApiAttributes(stack, toId("graphql", id), {
1060
- graphqlApiId: import_aws_cdk_lib5.Fn.importValue(toId("graphql", id))
1061
- });
2193
+ const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
2194
+ for (const [id, props] of Object.entries(stackConfig.graphql || {})) {
2195
+ const apiId = bootstrap2.import(`graphql-${id}`);
2196
+ for (const [typeAndField, functionProps] of Object.entries(props.resolvers || {})) {
1062
2197
  const [typeName, fieldName] = typeAndField.split(/[\s]+/g);
1063
- const functionId = (0, import_change_case2.paramCase)(`${id}-${typeName}-${fieldName}`);
1064
- const lambda = toFunction(ctx, functionId, functionProps);
1065
- const source = api.addLambdaDataSource(toId("data-source", functionId), lambda, {
1066
- name: toId("data-source", functionId)
1067
- });
1068
- source.createResolver(toId("resolver", functionId), {
2198
+ const entryId = (0, import_change_case10.paramCase)(`${id}-${typeName}-${fieldName}`);
2199
+ const lambda = toLambdaFunction(ctx, entryId, functionProps);
2200
+ const source = new AppsyncEventSource(entryId, lambda, {
2201
+ apiId,
1069
2202
  typeName,
1070
2203
  fieldName,
1071
- requestMappingTemplate: defaults.mappingTemplate?.request ? import_aws_appsync.MappingTemplate.fromFile(defaults.mappingTemplate.request) : import_aws_appsync.MappingTemplate.lambdaRequest(),
1072
- responseMappingTemplate: defaults.mappingTemplate?.response ? import_aws_appsync.MappingTemplate.fromFile(defaults.mappingTemplate.response) : import_aws_appsync.MappingTemplate.lambdaResult()
2204
+ code: Code2.fromInline(entryId, defaultResolver)
1073
2205
  });
1074
- return lambda;
1075
- });
1076
- }).flat();
2206
+ stack.add(lambda, source);
2207
+ }
2208
+ }
1077
2209
  }
1078
2210
  });
1079
2211
 
1080
- // src/plugins/pubsub.ts
1081
- var import_zod22 = require("zod");
1082
- var import_aws_iot = require("aws-cdk-lib/aws-iot");
1083
- var import_aws_iam3 = require("aws-cdk-lib/aws-iam");
1084
- var pubsubPlugin = definePlugin({
1085
- name: "pubsub",
1086
- schema: import_zod22.z.object({
1087
- stacks: import_zod22.z.object({
1088
- pubsub: import_zod22.z.record(ResourceIdSchema, import_zod22.z.object({
1089
- sql: import_zod22.z.string(),
1090
- sqlVersion: import_zod22.z.enum(["2015-10-08", "2016-03-23", "beta"]).default("2016-03-23"),
1091
- consumer: FunctionSchema
1092
- })).optional()
1093
- }).array()
2212
+ // src/plugins/domain.ts
2213
+ var import_zod15 = require("zod");
2214
+
2215
+ // src/formation/resource/route53/hosted-zone.ts
2216
+ var HostedZone = class extends Resource {
2217
+ constructor(logicalId, props = {}) {
2218
+ super("AWS::Route53::HostedZone", logicalId);
2219
+ this.props = props;
2220
+ this.name = this.props.domainName || logicalId;
2221
+ }
2222
+ name;
2223
+ get id() {
2224
+ return ref(this.logicalId);
2225
+ }
2226
+ properties() {
2227
+ return {
2228
+ Name: this.name + "."
2229
+ };
2230
+ }
2231
+ };
2232
+
2233
+ // src/formation/resource/certificate-manager/certificate.ts
2234
+ var Certificate = class extends Resource {
2235
+ constructor(logicalId, props = {}) {
2236
+ super("AWS::CertificateManager::Certificate", logicalId);
2237
+ this.props = props;
2238
+ this.name = this.props.domainName || logicalId;
2239
+ }
2240
+ name;
2241
+ get arn() {
2242
+ return ref(this.logicalId);
2243
+ }
2244
+ properties() {
2245
+ return {
2246
+ DomainName: this.name,
2247
+ ValidationMethod: "DNS",
2248
+ SubjectAlternativeNames: this.props.alternativeNames || []
2249
+ };
2250
+ }
2251
+ };
2252
+
2253
+ // src/formation/resource/route53/record-set-group.ts
2254
+ var RecordSetGroup = class extends Resource {
2255
+ constructor(logicalId, props) {
2256
+ super("AWS::Route53::RecordSetGroup", logicalId);
2257
+ this.props = props;
2258
+ }
2259
+ properties() {
2260
+ return {
2261
+ HostedZoneId: this.props.hostedZoneId,
2262
+ RecordSets: this.props.records.map((props) => ({
2263
+ Name: props.name + ".",
2264
+ Type: props.type,
2265
+ TTL: props.ttl,
2266
+ ...props.records ? {
2267
+ ResourceRecords: props.records
2268
+ } : {},
2269
+ ...props.alias ? {
2270
+ AliasTarget: {
2271
+ DNSName: props.alias,
2272
+ HostedZoneId: this.props.hostedZoneId
2273
+ }
2274
+ } : {}
2275
+ }))
2276
+ };
2277
+ }
2278
+ };
2279
+
2280
+ // src/plugins/domain.ts
2281
+ var DomainNameSchema = import_zod15.z.string().regex(/[a-z\-\_\.]/g, "Invalid domain name");
2282
+ var domainPlugin = definePlugin({
2283
+ name: "domain",
2284
+ schema: import_zod15.z.object({
2285
+ domains: import_zod15.z.record(DomainNameSchema, import_zod15.z.object({
2286
+ name: DomainNameSchema.optional(),
2287
+ type: import_zod15.z.enum(["A", "AAAA", "CAA", "CNAME", "DS", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"]),
2288
+ ttl: DurationSchema,
2289
+ records: import_zod15.z.string().array()
2290
+ }).array()).optional()
1094
2291
  }),
1095
- onStack(ctx) {
1096
- const { stack, stackConfig, bind } = ctx;
1097
- bind((lambda) => {
1098
- lambda.addToRolePolicy(new import_aws_iam3.PolicyStatement({
1099
- actions: ["iot:publish"],
1100
- resources: ["*"]
1101
- }));
1102
- });
1103
- return Object.entries(stackConfig.pubsub || {}).map(([id, props]) => {
1104
- const lambda = toFunction(ctx, id, props.consumer);
1105
- new import_aws_iot.CfnTopicRule(stack, toId("pubsub", id), {
1106
- ruleName: toName(stack, id),
1107
- topicRulePayload: {
1108
- sql: props.sql,
1109
- awsIotSqlVersion: props.sqlVersion,
1110
- actions: [{
1111
- lambda: {
1112
- functionArn: lambda.functionArn
1113
- }
1114
- }]
1115
- }
2292
+ onApp({ config, bootstrap: bootstrap2, usEastBootstrap }) {
2293
+ for (const [domain, records] of Object.entries(config.domains || {})) {
2294
+ const hostedZone = new HostedZone(domain);
2295
+ const certificate = new Certificate(domain, {
2296
+ alternativeNames: [`*.${domain}`]
1116
2297
  });
1117
- return lambda;
1118
- });
2298
+ bootstrap2.add(certificate);
2299
+ usEastBootstrap.add(hostedZone).add(certificate).export(`certificate-${domain}-arn`, certificate.arn);
2300
+ if (records.length > 0) {
2301
+ const group = new RecordSetGroup(domain, {
2302
+ hostedZoneId: hostedZone.id,
2303
+ records
2304
+ }).dependsOn(hostedZone);
2305
+ usEastBootstrap.add(group);
2306
+ }
2307
+ }
1119
2308
  }
1120
2309
  });
1121
2310
 
1122
2311
  // src/plugins/index.ts
1123
2312
  var defaultPlugins = [
2313
+ extendPlugin,
1124
2314
  functionPlugin,
1125
2315
  cronPlugin,
1126
2316
  queuePlugin,
1127
2317
  tablePlugin,
1128
2318
  storePlugin,
1129
2319
  topicPlugin,
1130
- searchPlugin,
1131
- graphqlPlugin,
1132
- pubsubPlugin
2320
+ pubsubPlugin,
2321
+ // searchPlugin,
2322
+ domainPlugin,
2323
+ graphqlPlugin
2324
+ // httpPlugin,
1133
2325
  ];
1134
2326
 
1135
- // src/stack/app-bootstrap.ts
1136
- var appBootstrapStack = ({ config, app, assets }) => {
1137
- const stack = new import_aws_cdk_lib6.Stack(app, "bootstrap", {
1138
- stackName: `${config.name}-bootstrap`
1139
- });
1140
- const plugins = [
1141
- ...defaultPlugins,
1142
- ...config.plugins || []
1143
- ];
1144
- debug("Run plugin onBootstrap listeners");
1145
- const functions = plugins.map((plugin) => plugin.onBootstrap?.({
1146
- config,
1147
- app,
1148
- stack,
1149
- assets
1150
- })).filter(Boolean).flat().filter(Boolean);
1151
- return {
1152
- stack,
1153
- functions
1154
- };
2327
+ // src/formation/app.ts
2328
+ var App = class {
2329
+ constructor(name) {
2330
+ this.name = name;
2331
+ }
2332
+ list = /* @__PURE__ */ new Map();
2333
+ add(...stacks) {
2334
+ stacks.forEach((stack) => this.list.set(stack.name, stack));
2335
+ return this;
2336
+ }
2337
+ find(resourceType) {
2338
+ return this.stacks.map((stack) => stack.find(resourceType)).flat();
2339
+ }
2340
+ [Symbol.iterator]() {
2341
+ return this.list.values();
2342
+ }
2343
+ get stacks() {
2344
+ return [...this.list.values()];
2345
+ }
2346
+ // get resources() {
2347
+ // return this.stacks.map(stack => stack.resources).flat()
2348
+ // }
1155
2349
  };
1156
2350
 
1157
- // src/util/deployment.ts
1158
- var flattenDependencyTree = (stacks) => {
1159
- const list3 = [];
1160
- const walk = (stacks2) => {
1161
- stacks2.forEach((node) => {
1162
- list3.push(node);
1163
- walk(node.children);
1164
- });
1165
- };
1166
- walk(stacks);
1167
- return list3;
1168
- };
1169
- var createDependencyTree = (stacks, startingLevel) => {
1170
- const list3 = stacks.map(({ stack, config }) => ({
1171
- stack,
1172
- depends: config?.depends?.map((dep) => dep.name) || []
1173
- }));
1174
- const findChildren = (list4, parents, level) => {
1175
- const children = [];
1176
- const rests = [];
1177
- for (const item of list4) {
1178
- const isChild = item.depends.filter((dep) => !parents.includes(dep)).length === 0;
1179
- if (isChild) {
1180
- children.push(item);
1181
- } else {
1182
- rests.push(item);
1183
- }
1184
- }
1185
- if (!rests.length) {
1186
- return children.map(({ stack }) => ({
1187
- stack,
1188
- level,
1189
- children: []
1190
- }));
1191
- }
1192
- return children.map(({ stack }) => {
1193
- return {
1194
- stack,
1195
- level,
1196
- children: findChildren(rests, [...parents, stack.artifactId], level + 1)
1197
- };
1198
- });
1199
- };
1200
- return findChildren(list3, [], startingLevel);
1201
- };
1202
- var createDeploymentLine = (stacks) => {
1203
- const flat = flattenDependencyTree(stacks);
1204
- const line = [];
1205
- flat.forEach((node) => {
1206
- const level = node.level;
1207
- if (!line[level]) {
1208
- line[level] = [];
1209
- }
1210
- line[level].push(node.stack);
1211
- });
1212
- return line;
2351
+ // src/formation/resource/cloud-formation/custom-resource.ts
2352
+ var CustomResource = class extends Resource {
2353
+ constructor(logicalId, props) {
2354
+ super("AWS::CloudFormation::CustomResource", logicalId);
2355
+ this.props = props;
2356
+ }
2357
+ getAtt(name) {
2358
+ return getAtt(this.logicalId, name);
2359
+ }
2360
+ properties() {
2361
+ return {
2362
+ ServiceToken: this.props.serviceToken,
2363
+ ...this.props.properties
2364
+ };
2365
+ }
1213
2366
  };
1214
2367
 
1215
- // src/util/assets.ts
1216
- var Assets = class {
1217
- assets = {};
1218
- id = 0;
1219
- add(opts) {
1220
- if (!this.assets[opts.stackName]) {
1221
- this.assets[opts.stackName] = [];
2368
+ // src/custom/global-export/handler.ts
2369
+ var globalExportsHandlerCode = (
2370
+ /* JS */
2371
+ `
2372
+
2373
+ const { CloudFormationClient, ListExportsCommand } = require('@aws-sdk/client-cloudformation')
2374
+
2375
+ exports.handler = async (event) => {
2376
+ const region = event.ResourceProperties.region
2377
+
2378
+ try {
2379
+ const data = await listExports(region)
2380
+
2381
+ await send(event, region, 'SUCCESS', data)
2382
+ } catch(error) {
2383
+ if (error instanceof Error) {
2384
+ await send(event, region, 'FAILED', {}, error.message)
2385
+ } else {
2386
+ await send(event, region, 'FAILED', {}, 'Unknown error')
2387
+ }
2388
+ }
2389
+ }
2390
+
2391
+ const send = async (event, id, status, data, reason = '') => {
2392
+ const body = JSON.stringify({
2393
+ Status: status,
2394
+ Reason: reason,
2395
+ PhysicalResourceId: id,
2396
+ StackId: event.StackId,
2397
+ RequestId: event.RequestId,
2398
+ LogicalResourceId: event.LogicalResourceId,
2399
+ NoEcho: false,
2400
+ Data: data
2401
+ })
2402
+
2403
+ await fetch(event.ResponseURL, {
2404
+ method: 'PUT',
2405
+ port: 443,
2406
+ body,
2407
+ headers: {
2408
+ 'content-type': '',
2409
+ 'content-length': Buffer.from(body).byteLength,
2410
+ },
2411
+ })
2412
+ }
2413
+
2414
+ const listExports = async (region) => {
2415
+ const client = new CloudFormationClient({ region })
2416
+ const data = {}
2417
+
2418
+ let token
2419
+
2420
+ while(true) {
2421
+ const result = await client.send(new ListExportsCommand({
2422
+ NextToken: token
2423
+ }))
2424
+
2425
+ result.Exports?.forEach(item => {
2426
+ data[item.Name] = item.Value
2427
+ })
2428
+
2429
+ if(result.NextToken) {
2430
+ token = result.NextToken
2431
+ } else {
2432
+ return data
2433
+ }
2434
+ }
2435
+ }
2436
+ `
2437
+ );
2438
+
2439
+ // src/custom/global-export/extend.ts
2440
+ var extendWithGlobalExports = (appName, importable, exportable) => {
2441
+ let crossRegionExports;
2442
+ importable.import = (name) => {
2443
+ name = formatName(name);
2444
+ if (!importable.exports.has(name)) {
2445
+ throw new TypeError(`Undefined global export value: ${name}`);
2446
+ }
2447
+ if (!crossRegionExports) {
2448
+ const lambda = new Function("global-exports", {
2449
+ name: `${appName}-global-exports`,
2450
+ code: Code.fromInline(globalExportsHandlerCode, "index.handler")
2451
+ });
2452
+ lambda.addPermissions({
2453
+ actions: ["cloudformation:ListExports"],
2454
+ resources: ["*"]
2455
+ });
2456
+ crossRegionExports = new CustomResource("global-exports", {
2457
+ serviceToken: lambda.arn,
2458
+ properties: {
2459
+ region: importable.region
2460
+ }
2461
+ });
2462
+ exportable.add(crossRegionExports);
1222
2463
  }
1223
- this.assets[opts.stackName].push({
1224
- ...opts,
1225
- id: this.id++
1226
- });
1227
- }
1228
- list() {
1229
- return this.assets;
1230
- }
1231
- forEach(cb) {
1232
- Object.values(this.assets).forEach((assets) => {
1233
- cb(assets[0].stackName, assets);
1234
- });
1235
- }
1236
- map(cb) {
1237
- return Object.values(this.assets).map((assets) => {
1238
- return cb(assets[0].stackName, assets);
1239
- });
1240
- }
2464
+ return crossRegionExports.getAtt(name);
2465
+ };
1241
2466
  };
1242
2467
 
1243
2468
  // src/app.ts
1244
- var makeApp = (config) => {
1245
- return new import_aws_cdk_lib7.App({
1246
- outdir: assemblyDir,
1247
- defaultStackSynthesizer: new import_aws_cdk_lib7.DefaultStackSynthesizer({
1248
- fileAssetsBucketName: assetBucketName(config),
1249
- fileAssetPublishingRoleArn: "",
1250
- generateBootstrapVersionRule: false
1251
- })
1252
- });
1253
- };
1254
2469
  var getAllDepends = (filters) => {
1255
2470
  const list3 = [];
1256
2471
  const walk = (deps) => {
@@ -1263,111 +2478,76 @@ var getAllDepends = (filters) => {
1263
2478
  return list3;
1264
2479
  };
1265
2480
  var toApp = async (config, filters) => {
1266
- const assets = new Assets();
1267
- const app = makeApp(config);
2481
+ const app = new App(config.name);
1268
2482
  const stacks = [];
1269
2483
  const plugins = [
1270
2484
  ...defaultPlugins,
1271
2485
  ...config.plugins || []
1272
2486
  ];
1273
2487
  debug("Plugins detected:", plugins.map((plugin) => style.info(plugin.name)).join(", "));
2488
+ const bootstrap2 = new Stack("bootstrap", config.region);
2489
+ const usEastBootstrap = new Stack("us-east-bootstrap", "us-east-1");
2490
+ extendWithGlobalExports(config.name, usEastBootstrap, bootstrap2);
2491
+ app.add(bootstrap2, usEastBootstrap);
1274
2492
  debug("Run plugin onApp listeners");
1275
- plugins.forEach((plugin) => plugin.onApp?.({ config, app, assets }));
1276
- const bootstrap2 = appBootstrapStack({ config, app, assets });
2493
+ const bindings = [];
2494
+ const bind = (cb) => {
2495
+ bindings.push(cb);
2496
+ };
2497
+ for (const plugin of plugins) {
2498
+ plugin.onApp?.({
2499
+ config,
2500
+ app,
2501
+ bootstrap: bootstrap2,
2502
+ usEastBootstrap,
2503
+ bind
2504
+ });
2505
+ }
1277
2506
  debug("Stack filters:", filters.map((filter) => style.info(filter)).join(", "));
1278
2507
  const filterdStacks = filters.length === 0 ? config.stacks : getAllDepends(
1279
2508
  // config.stacks,
1280
2509
  config.stacks.filter((stack) => filters.includes(stack.name))
1281
2510
  );
1282
2511
  for (const stackConfig of filterdStacks) {
1283
- const { stack, bindings } = toStack({
2512
+ const { stack } = toStack({
1284
2513
  config,
1285
2514
  stackConfig,
1286
- assets,
2515
+ bootstrap: bootstrap2,
2516
+ usEastBootstrap,
1287
2517
  plugins,
1288
2518
  app
1289
2519
  });
2520
+ app.add(stack);
1290
2521
  stacks.push({ stack, config: stackConfig });
1291
- bindings.forEach((cb) => bootstrap2.functions.forEach(cb));
1292
2522
  }
1293
- let dependencyTree;
1294
- if (bootstrap2.stack.node.children.length === 0) {
1295
- dependencyTree = createDependencyTree(stacks, 0);
1296
- } else {
2523
+ const functions = app.find(Function);
2524
+ for (const bind2 of bindings) {
2525
+ for (const fn of functions) {
2526
+ bind2(fn);
2527
+ }
2528
+ }
2529
+ let dependencyTree = createDependencyTree(stacks);
2530
+ if (bootstrap2.size > 0) {
2531
+ dependencyTree = [{
2532
+ stack: bootstrap2,
2533
+ children: dependencyTree
2534
+ }];
2535
+ }
2536
+ if (usEastBootstrap.size > 0) {
1297
2537
  dependencyTree = [{
1298
- stack: bootstrap2.stack,
1299
- level: 0,
1300
- children: createDependencyTree(stacks, 1)
2538
+ stack: usEastBootstrap,
2539
+ children: dependencyTree
1301
2540
  }];
1302
2541
  }
1303
2542
  return {
1304
2543
  app,
1305
- assets,
1306
2544
  plugins,
1307
- stackNames: filterdStacks.map((stack) => stack.name),
1308
2545
  dependencyTree
1309
2546
  };
1310
2547
  };
1311
2548
 
1312
- // src/cli/ui/layout/basic.ts
1313
- var br = () => {
1314
- return "\n";
1315
- };
1316
- var hr = () => {
1317
- return (term) => {
1318
- term.out.write([
1319
- style.placeholder("\u2500".repeat(term.out.width())),
1320
- br()
1321
- ]);
1322
- };
1323
- };
1324
-
1325
- // src/cli/ui/layout/logs.ts
1326
- var import_wrap_ansi = __toESM(require("wrap-ansi"), 1);
1327
- var previous = /* @__PURE__ */ new Date();
1328
- var logs = () => {
1329
- if (!process.env.VERBOSE) {
1330
- return [];
1331
- }
1332
- const logs2 = flushDebug();
1333
- return (term) => {
1334
- term.out.write([
1335
- hr(),
1336
- br(),
1337
- " ".repeat(3),
1338
- style.label("Debug Logs:"),
1339
- br(),
1340
- br(),
1341
- logs2.map((log) => {
1342
- const diff = log.date.getTime() - previous.getTime();
1343
- const time = `+${diff}`.padStart(8);
1344
- previous = log.date;
1345
- return (0, import_wrap_ansi.default)([
1346
- style.attr(`${time}${style.attr.dim("ms")}`),
1347
- " [ ",
1348
- log.type,
1349
- " ] ",
1350
- log.message,
1351
- br(),
1352
- log.type === "error" ? br() : ""
1353
- ].join(""), term.out.width(), { hard: true, trim: false });
1354
- }),
1355
- br(),
1356
- hr()
1357
- ]);
1358
- };
1359
- };
1360
-
1361
- // src/cli/ui/layout/footer.ts
1362
- var footer = () => {
1363
- return [
1364
- br(),
1365
- logs()
1366
- ];
1367
- };
1368
-
1369
2549
  // src/config.ts
1370
- var import_path11 = require("path");
2550
+ var import_path4 = require("path");
1371
2551
 
1372
2552
  // src/util/account.ts
1373
2553
  var import_client_sts = require("@aws-sdk/client-sts");
@@ -1386,17 +2566,17 @@ var getCredentials = (profile) => {
1386
2566
  };
1387
2567
 
1388
2568
  // src/schema/app.ts
1389
- var import_zod26 = require("zod");
2569
+ var import_zod19 = require("zod");
1390
2570
 
1391
2571
  // src/schema/stack.ts
1392
- var import_zod23 = require("zod");
1393
- var StackSchema = import_zod23.z.object({
2572
+ var import_zod16 = require("zod");
2573
+ var StackSchema = import_zod16.z.object({
1394
2574
  name: ResourceIdSchema,
1395
- depends: import_zod23.z.array(import_zod23.z.lazy(() => StackSchema)).optional()
2575
+ depends: import_zod16.z.array(import_zod16.z.lazy(() => StackSchema)).optional()
1396
2576
  });
1397
2577
 
1398
2578
  // src/schema/region.ts
1399
- var import_zod24 = require("zod");
2579
+ var import_zod17 = require("zod");
1400
2580
  var US = ["us-east-2", "us-east-1", "us-west-1", "us-west-2"];
1401
2581
  var AF = ["af-south-1"];
1402
2582
  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"];
@@ -1413,35 +2593,48 @@ var regions = [
1413
2593
  ...ME,
1414
2594
  ...SA
1415
2595
  ];
1416
- var RegionSchema = import_zod24.z.enum(regions);
2596
+ var RegionSchema = import_zod17.z.enum(regions);
1417
2597
 
1418
2598
  // src/schema/plugin.ts
1419
- var import_zod25 = require("zod");
1420
- var PluginSchema = import_zod25.z.object({
1421
- name: import_zod25.z.string(),
1422
- schema: import_zod25.z.custom().optional(),
2599
+ var import_zod18 = require("zod");
2600
+ var PluginSchema = import_zod18.z.object({
2601
+ name: import_zod18.z.string(),
2602
+ schema: import_zod18.z.custom().optional(),
1423
2603
  // depends: z.array(z.lazy(() => PluginSchema)).optional(),
1424
- onBootstrap: import_zod25.z.function().returns(import_zod25.z.any()).optional(),
1425
- onStack: import_zod25.z.function().returns(import_zod25.z.any()).optional(),
1426
- onApp: import_zod25.z.function().returns(import_zod25.z.void()).optional()
2604
+ onBootstrap: import_zod18.z.function().returns(import_zod18.z.any()).optional(),
2605
+ onStack: import_zod18.z.function().returns(import_zod18.z.any()).optional(),
2606
+ onApp: import_zod18.z.function().returns(import_zod18.z.void()).optional()
1427
2607
  // bind: z.function().optional(),
1428
2608
  });
1429
2609
 
1430
2610
  // src/schema/app.ts
1431
- var AppSchema = import_zod26.z.object({
2611
+ var AppSchema = import_zod19.z.object({
1432
2612
  name: ResourceIdSchema,
1433
2613
  region: RegionSchema,
1434
- profile: import_zod26.z.string(),
1435
- stage: import_zod26.z.string().regex(/[a-z]+/).default("prod"),
1436
- defaults: import_zod26.z.object({}).default({}),
1437
- stacks: import_zod26.z.array(StackSchema).min(1),
1438
- plugins: import_zod26.z.array(PluginSchema).optional()
2614
+ profile: import_zod19.z.string(),
2615
+ stage: import_zod19.z.string().regex(/[a-z]+/).default("prod"),
2616
+ defaults: import_zod19.z.object({}).default({}),
2617
+ stacks: import_zod19.z.array(StackSchema).min(1).refine((stacks) => {
2618
+ const unique = new Set(stacks.map((stack) => stack.name));
2619
+ return unique.size === stacks.length;
2620
+ }, "Must be an array of unique stacks"),
2621
+ plugins: import_zod19.z.array(PluginSchema).optional()
1439
2622
  });
1440
2623
 
1441
2624
  // src/util/import.ts
1442
- var import_core3 = require("@swc/core");
1443
- var import_path9 = require("path");
1444
- var import_promises5 = require("fs/promises");
2625
+ var import_core = require("@swc/core");
2626
+ var import_path2 = require("path");
2627
+ var import_promises4 = require("fs/promises");
2628
+
2629
+ // src/util/path.ts
2630
+ var import_path = require("path");
2631
+ var rootDir = process.cwd();
2632
+ var outDir = (0, import_path.join)(rootDir, ".awsless");
2633
+ var templateDir = (0, import_path.join)(outDir, "template");
2634
+ var assetDir = (0, import_path.join)(outDir, "asset");
2635
+ var cacheDir = (0, import_path.join)(outDir, "cache");
2636
+
2637
+ // src/util/import.ts
1445
2638
  var resolveFileNameExtension = async (path) => {
1446
2639
  const options = [
1447
2640
  "",
@@ -1451,10 +2644,10 @@ var resolveFileNameExtension = async (path) => {
1451
2644
  "/index.js"
1452
2645
  ];
1453
2646
  for (const option of options) {
1454
- const file = path + option;
2647
+ const file = path.replace(/\.js$/, "") + option;
1455
2648
  let stat;
1456
2649
  try {
1457
- stat = await (0, import_promises5.lstat)(file);
2650
+ stat = await (0, import_promises4.lstat)(file);
1458
2651
  } catch (error) {
1459
2652
  continue;
1460
2653
  }
@@ -1465,45 +2658,42 @@ var resolveFileNameExtension = async (path) => {
1465
2658
  throw new Error(`Failed to load file: ${path}`);
1466
2659
  };
1467
2660
  var resolveDir = (path) => {
1468
- return (0, import_path9.dirname)(path).replace(rootDir + "/", "");
2661
+ return (0, import_path2.dirname)(path).replace(rootDir + "/", "");
1469
2662
  };
1470
2663
  var importFile = async (path) => {
1471
2664
  const load = async (file) => {
1472
- let { code: code2 } = await (0, import_core3.transformFile)(file, {
2665
+ debug("Load file", file);
2666
+ let { code: code2 } = await (0, import_core.transformFile)(file, {
1473
2667
  isModule: true
1474
2668
  });
1475
- const path2 = (0, import_path9.dirname)(file);
2669
+ const path2 = (0, import_path2.dirname)(file);
1476
2670
  const dir = resolveDir(file);
1477
2671
  code2 = code2.replaceAll("__dirname", `"${dir}"`);
1478
- const matches = code2.match(/import\s*{\s*[a-z0-9\_]+\s*}\s*from\s*('|")(\.[\/a-z0-9\_\-]+)('|");?/ig);
2672
+ const matches = code2.match(/(import|export)\s*{\s*[a-z0-9\_\,\s\*]+\s*}\s*from\s*('|")(\.\.?[\/a-z0-9\_\-\.]+)('|");?/ig);
1479
2673
  if (!matches)
1480
2674
  return code2;
1481
2675
  await Promise.all(matches?.map(async (match) => {
1482
- const parts = /('|")(\.[\/a-z0-9\_\-]+)('|")/ig.exec(match);
2676
+ const parts = /('|")(\.\.?[\/a-z0-9\_\-\.]+)('|")/ig.exec(match);
1483
2677
  const from = parts[2];
1484
- const file2 = await resolveFileNameExtension((0, import_path9.join)(path2, from));
2678
+ const file2 = await resolveFileNameExtension((0, import_path2.join)(path2, from));
1485
2679
  const result = await load(file2);
1486
2680
  code2 = code2.replace(match, result);
1487
2681
  }));
1488
2682
  return code2;
1489
2683
  };
1490
2684
  const code = await load(path);
1491
- const outputFile = (0, import_path9.join)(outDir, "config.js");
1492
- await (0, import_promises5.mkdir)(outDir, { recursive: true });
1493
- await (0, import_promises5.writeFile)(outputFile, code);
2685
+ const outputFile = (0, import_path2.join)(outDir, "config.js");
2686
+ await (0, import_promises4.mkdir)(outDir, { recursive: true });
2687
+ await (0, import_promises4.writeFile)(outputFile, code);
1494
2688
  return import(outputFile);
1495
2689
  };
1496
2690
 
1497
2691
  // src/config.ts
1498
2692
  var importConfig = async (options) => {
1499
2693
  debug("Import config file");
1500
- const fileName = (0, import_path11.join)(process.cwd(), options.configFile || "awsless.config.ts");
2694
+ const fileName = (0, import_path4.join)(process.cwd(), options.configFile || "awsless.config.ts");
1501
2695
  const module2 = await importFile(fileName);
1502
- const appConfig = typeof module2.default === "function" ? await module2.default({
1503
- profile: options.profile,
1504
- region: options.region,
1505
- stage: options.stage
1506
- }) : module2.default;
2696
+ const appConfig = typeof module2.default === "function" ? await module2.default(options) : module2.default;
1507
2697
  debug("Validate config file");
1508
2698
  const plugins = [
1509
2699
  ...defaultPlugins,
@@ -1528,6 +2718,19 @@ var importConfig = async (options) => {
1528
2718
  };
1529
2719
  };
1530
2720
 
2721
+ // src/cli/ui/layout/basic.ts
2722
+ var br = () => {
2723
+ return "\n";
2724
+ };
2725
+ var hr = () => {
2726
+ return (term) => {
2727
+ term.out.write([
2728
+ style.placeholder("\u2500".repeat(term.out.width())),
2729
+ br()
2730
+ ]);
2731
+ };
2732
+ };
2733
+
1531
2734
  // src/cli/ui/layout/list.ts
1532
2735
  var list = (data) => {
1533
2736
  const padding = 3;
@@ -1535,26 +2738,26 @@ var list = (data) => {
1535
2738
  const size = Object.keys(data).reduce((total, name) => {
1536
2739
  return name.length > total ? name.length : total;
1537
2740
  }, 0);
1538
- return Object.entries(data).map(([name, value]) => [
1539
- " ".repeat(padding),
1540
- style.label((name + ":").padEnd(size + gap + 1)),
1541
- value,
1542
- br()
1543
- ]);
2741
+ return (term) => {
2742
+ term.out.gap();
2743
+ term.out.write(Object.entries(data).map(([name, value]) => [
2744
+ " ".repeat(padding),
2745
+ style.label((name + ":").padEnd(size + gap + 1)),
2746
+ value,
2747
+ br()
2748
+ ]));
2749
+ term.out.gap();
2750
+ };
1544
2751
  };
1545
2752
 
1546
2753
  // src/cli/ui/layout/header.ts
1547
2754
  var header = (config) => {
1548
- return [
1549
- br(),
1550
- list({
1551
- App: config.name,
1552
- Stage: config.stage,
1553
- Region: config.region,
1554
- Profile: config.profile
1555
- }),
1556
- br()
1557
- ];
2755
+ return list({
2756
+ App: config.name,
2757
+ Stage: config.stage,
2758
+ Region: config.region,
2759
+ Profile: config.profile
2760
+ });
1558
2761
  };
1559
2762
 
1560
2763
  // src/util/timer.ts
@@ -1579,7 +2782,7 @@ var Signal = class {
1579
2782
  }
1580
2783
  set(value) {
1581
2784
  this.value = value;
1582
- this.subs.forEach((sub) => sub(value));
2785
+ this.subs.forEach((sub2) => sub2(value));
1583
2786
  }
1584
2787
  update(cb) {
1585
2788
  this.set(cb(this.value));
@@ -1621,16 +2824,16 @@ var createSpinner = () => {
1621
2824
  };
1622
2825
 
1623
2826
  // src/cli/ui/layout/dialog.ts
1624
- var import_wrap_ansi2 = __toESM(require("wrap-ansi"), 1);
2827
+ var import_wrap_ansi = __toESM(require("wrap-ansi"), 1);
1625
2828
  var dialog = (type, lines) => {
1626
2829
  const padding = 3;
1627
2830
  const icon = style[type](symbol[type].padEnd(padding));
1628
2831
  return (term) => {
1629
2832
  term.out.write(lines.map((line, i) => {
1630
2833
  if (i === 0) {
1631
- return icon + (0, import_wrap_ansi2.default)(line, term.out.width(), { hard: true });
2834
+ return icon + (0, import_wrap_ansi.default)(line, term.out.width(), { hard: true });
1632
2835
  }
1633
- return (0, import_wrap_ansi2.default)(" ".repeat(padding) + line, term.out.width(), { hard: true });
2836
+ return (0, import_wrap_ansi.default)(" ".repeat(padding) + line, term.out.width(), { hard: true });
1634
2837
  }).join(br()) + br());
1635
2838
  };
1636
2839
  };
@@ -1772,6 +2975,7 @@ var Renderer = class {
1772
2975
  fragments = [];
1773
2976
  unsubs = [];
1774
2977
  timeout;
2978
+ flushing = false;
1775
2979
  screen = [];
1776
2980
  width() {
1777
2981
  return this.output.columns;
@@ -1791,14 +2995,61 @@ var Renderer = class {
1791
2995
  this.update();
1792
2996
  return fragment;
1793
2997
  }
2998
+ gap() {
2999
+ const walk = (fragment) => {
3000
+ if (typeof fragment === "string") {
3001
+ return fragment;
3002
+ }
3003
+ if (Array.isArray(fragment)) {
3004
+ return fragment.map(walk).join("");
3005
+ }
3006
+ return walk(fragment.get());
3007
+ };
3008
+ const end = walk(this.fragments.slice(-2));
3009
+ if (end.endsWith("\n\n")) {
3010
+ } else if (end.endsWith("\n")) {
3011
+ this.fragments.push("\n");
3012
+ } else {
3013
+ this.fragments.push("\n\n");
3014
+ }
3015
+ this.update();
3016
+ }
1794
3017
  update() {
1795
3018
  clearTimeout(this.timeout);
1796
3019
  this.timeout = setTimeout(() => {
1797
3020
  this.flush();
1798
3021
  }, 0);
1799
3022
  }
1800
- flush() {
3023
+ async end() {
3024
+ this.gap();
3025
+ await this.flush();
3026
+ clearTimeout(this.timeout);
3027
+ this.unsubs.forEach((unsub) => unsub());
3028
+ this.unsubs = [];
3029
+ const y = this.screen.length - 1;
3030
+ await this.setCursor(0, y);
3031
+ }
3032
+ setCursor(x, y) {
3033
+ return new Promise((resolve) => {
3034
+ this.output.cursorTo?.(x, y, () => resolve(void 0));
3035
+ });
3036
+ }
3037
+ writeString(value) {
3038
+ return new Promise((resolve) => {
3039
+ this.output.write?.(value, () => resolve(void 0));
3040
+ });
3041
+ }
3042
+ clearLine() {
3043
+ return new Promise((resolve) => {
3044
+ this.output.clearLine?.(1, () => resolve(void 0));
3045
+ });
3046
+ }
3047
+ async flush() {
1801
3048
  clearTimeout(this.timeout);
3049
+ if (this.flushing) {
3050
+ this.update();
3051
+ return;
3052
+ }
1802
3053
  const walk = (fragment) => {
1803
3054
  if (typeof fragment === "string") {
1804
3055
  return fragment;
@@ -1814,34 +3065,40 @@ var Renderer = class {
1814
3065
  this.unsubs.forEach((unsub) => unsub());
1815
3066
  this.unsubs = [];
1816
3067
  const screen = walk(this.fragments).split("\n");
3068
+ const height = this.height();
1817
3069
  const oldSize = this.screen.length;
1818
3070
  const newSize = screen.length;
1819
3071
  const size = Math.max(oldSize, newSize);
1820
- const height = this.height();
1821
3072
  const start = Math.max(oldSize - height, 0);
3073
+ this.flushing = true;
1822
3074
  for (let y = start; y < size; y++) {
1823
- const line = screen[y];
1824
- if (line !== this.screen[y]) {
1825
- if (y > oldSize) {
1826
- const x = (this.screen[y - 1]?.length || 0) - 1;
1827
- this.output.cursorTo?.(x, y - 1 - start);
1828
- this.output.write?.("\n" + line);
3075
+ const newLine = screen[y];
3076
+ const oldLine = this.screen[y];
3077
+ if (newLine !== oldLine) {
3078
+ if (y >= oldSize && y !== 0) {
3079
+ const p = y - start - 1;
3080
+ const x = screen[y - 1]?.length || 0;
3081
+ await this.setCursor(x, p);
3082
+ await this.writeString("\n" + newLine);
1829
3083
  } else {
1830
- this.output.cursorTo?.(0, y - start);
1831
- this.output.write?.(line);
3084
+ await this.setCursor(0, y - start);
3085
+ await this.writeString(newLine);
3086
+ await this.clearLine();
1832
3087
  }
1833
- this.output.clearLine?.(1);
1834
3088
  }
1835
3089
  }
1836
3090
  this.screen = screen;
1837
- }
1838
- clear() {
1839
- let count = this.output.rows;
1840
- while (count--) {
1841
- this.output.write("\n");
3091
+ this.flushing = false;
3092
+ }
3093
+ async clear() {
3094
+ await this.setCursor(0, 0);
3095
+ await this.writeString("\n".repeat(this.height()));
3096
+ await this.setCursor(0, 0);
3097
+ if (this.output.clearScreenDown) {
3098
+ await new Promise((resolve) => {
3099
+ this.output.clearScreenDown(() => resolve(void 0));
3100
+ });
1842
3101
  }
1843
- this.output.cursorTo?.(0, 0);
1844
- this.output.clearScreenDown?.();
1845
3102
  }
1846
3103
  };
1847
3104
 
@@ -1857,22 +3114,62 @@ var logo = () => {
1857
3114
  return [
1858
3115
  style.warning("\u26A1\uFE0F "),
1859
3116
  style.primary("AWS"),
1860
- style.primary.dim("LESS"),
1861
- br()
3117
+ style.primary.dim("LESS")
1862
3118
  ];
1863
3119
  };
1864
3120
 
3121
+ // src/cli/ui/layout/logs.ts
3122
+ var import_wrap_ansi2 = __toESM(require("wrap-ansi"), 1);
3123
+ var previous = /* @__PURE__ */ new Date();
3124
+ var logs = () => {
3125
+ if (!process.env.VERBOSE) {
3126
+ return [];
3127
+ }
3128
+ const logs2 = flushDebug();
3129
+ return (term) => {
3130
+ term.out.gap();
3131
+ term.out.write([
3132
+ hr(),
3133
+ br(),
3134
+ " ".repeat(3),
3135
+ style.label("Debug Logs:"),
3136
+ br(),
3137
+ br(),
3138
+ logs2.map((log) => {
3139
+ const diff = log.date.getTime() - previous.getTime();
3140
+ const time = `+${diff}`.padStart(8);
3141
+ previous = log.date;
3142
+ return (0, import_wrap_ansi2.default)([
3143
+ style.attr(`${time}${style.attr.dim("ms")}`),
3144
+ " [ ",
3145
+ log.type,
3146
+ " ] ",
3147
+ log.message,
3148
+ br(),
3149
+ log.type === "error" ? br() : ""
3150
+ ].join(""), term.out.width(), { hard: true, trim: false });
3151
+ }),
3152
+ br(),
3153
+ hr()
3154
+ ]);
3155
+ };
3156
+ };
3157
+
1865
3158
  // src/cli/ui/layout/layout.ts
1866
3159
  var layout = async (cb) => {
1867
3160
  const term = createTerminal();
1868
- term.out.clear();
3161
+ await term.out.clear();
3162
+ term.out.write(br());
1869
3163
  term.out.write(logo());
3164
+ term.out.gap();
1870
3165
  try {
1871
3166
  const options = program.optsWithGlobals();
1872
3167
  const config = await importConfig(options);
1873
3168
  term.out.write(header(config));
3169
+ term.out.gap();
1874
3170
  await cb(config, term.out.write.bind(term.out), term);
1875
3171
  } catch (error) {
3172
+ term.out.gap();
1876
3173
  if (error instanceof Error) {
1877
3174
  term.out.write(dialog("error", [error.message]));
1878
3175
  } else if (typeof error === "string") {
@@ -1883,7 +3180,9 @@ var layout = async (cb) => {
1883
3180
  debugError(error);
1884
3181
  } finally {
1885
3182
  debug("Exit");
1886
- term.out.write(footer());
3183
+ term.out.gap();
3184
+ term.out.write(logs());
3185
+ await term.out.end();
1887
3186
  term.in.unref();
1888
3187
  setTimeout(() => {
1889
3188
  process.exit(0);
@@ -1891,6 +3190,9 @@ var layout = async (cb) => {
1891
3190
  }
1892
3191
  };
1893
3192
 
3193
+ // src/cli/ui/complex/builder.ts
3194
+ var import_promises5 = require("fs/promises");
3195
+
1894
3196
  // src/cli/ui/layout/flex-line.ts
1895
3197
  var stripEscapeCode = (str) => {
1896
3198
  return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
@@ -1911,34 +3213,52 @@ var flexLine = (term, left, right, reserveSpace = 0) => {
1911
3213
  ]);
1912
3214
  };
1913
3215
 
1914
- // src/cli/ui/complex/asset.ts
1915
- var assetBuilder = (assets) => {
3216
+ // src/cli/ui/complex/builder.ts
3217
+ var import_path6 = require("path");
3218
+ var assetBuilder = (app) => {
1916
3219
  return async (term) => {
3220
+ const assets = [];
3221
+ const stacks = [];
3222
+ for (const stack of app) {
3223
+ for (const asset of stack.assets) {
3224
+ if (asset.build) {
3225
+ assets.push(asset);
3226
+ stacks.push(stack);
3227
+ }
3228
+ }
3229
+ }
3230
+ if (assets.length === 0) {
3231
+ return;
3232
+ }
1917
3233
  const done = term.out.write(loadingDialog("Building stack assets..."));
1918
- const groups = new Signal([br()]);
3234
+ const groups = new Signal([""]);
3235
+ term.out.gap();
1919
3236
  term.out.write(groups);
1920
- const stackNameSize = Math.max(...Object.keys(assets.list()).map((stack) => stack.length));
1921
- const resourceSize = Math.max(...Object.values(assets.list()).map((assets2) => assets2.map((asset) => asset.resource.length)).flat());
1922
- await Promise.all(assets.map(async (stackName, assets2) => {
3237
+ const stackNameSize = Math.max(...stacks.map((stack) => stack.name.length));
3238
+ const assetTypeSize = Math.max(...assets.map((asset) => asset.type.length));
3239
+ await Promise.all(app.stacks.map(async (stack) => {
1923
3240
  const group = new Signal([]);
1924
3241
  groups.update((groups2) => [...groups2, group]);
1925
- await Promise.all(assets2.map(async (asset) => {
3242
+ await Promise.all([...stack.assets].map(async (asset) => {
3243
+ if (!asset.build) {
3244
+ return;
3245
+ }
1926
3246
  const [icon, stop] = createSpinner();
1927
3247
  const details = new Signal({});
1928
3248
  const line = flexLine(term, [
1929
3249
  icon,
1930
3250
  " ",
1931
- style.label(stackName),
1932
- " ".repeat(stackNameSize - stackName.length),
3251
+ style.label(stack.name),
3252
+ " ".repeat(stackNameSize - stack.name.length),
1933
3253
  " ",
1934
3254
  style.placeholder(symbol.pointerSmall),
1935
3255
  " ",
1936
- style.warning(asset.resource),
1937
- " ".repeat(resourceSize - asset.resource.length),
3256
+ style.warning(asset.type),
3257
+ " ".repeat(assetTypeSize - asset.type.length),
1938
3258
  " ",
1939
3259
  style.placeholder(symbol.pointerSmall),
1940
3260
  " ",
1941
- style.info(asset.resourceName),
3261
+ style.info(asset.id),
1942
3262
  " "
1943
3263
  ], [
1944
3264
  " ",
@@ -1951,7 +3271,14 @@ var assetBuilder = (assets) => {
1951
3271
  ]);
1952
3272
  group.update((group2) => [...group2, line]);
1953
3273
  const timer = createTimer();
1954
- const data = await asset.build?.();
3274
+ const data = await asset.build({
3275
+ async write(file, data2) {
3276
+ const fullpath = (0, import_path6.join)(assetDir, asset.type, app.name, stack.name, asset.id, file);
3277
+ const basepath = (0, import_path6.dirname)(fullpath);
3278
+ await (0, import_promises5.mkdir)(basepath, { recursive: true });
3279
+ await (0, import_promises5.writeFile)(fullpath, data2);
3280
+ }
3281
+ });
1955
3282
  details.set({
1956
3283
  ...data,
1957
3284
  time: timer()
@@ -1961,15 +3288,16 @@ var assetBuilder = (assets) => {
1961
3288
  }));
1962
3289
  }));
1963
3290
  done("Done building stack assets");
3291
+ term.out.gap();
1964
3292
  };
1965
3293
  };
1966
3294
 
1967
3295
  // src/util/cleanup.ts
1968
3296
  var import_promises6 = require("fs/promises");
1969
3297
  var cleanUp = async () => {
1970
- debug("Clean up assembly & asset files");
3298
+ debug("Clean up template, cache, and asset files");
1971
3299
  const paths = [
1972
- assemblyDir,
3300
+ templateDir,
1973
3301
  assetDir,
1974
3302
  cacheDir
1975
3303
  ];
@@ -1983,108 +3311,176 @@ var cleanUp = async () => {
1983
3311
  })));
1984
3312
  };
1985
3313
 
3314
+ // src/cli/ui/complex/template.ts
3315
+ var import_promises7 = require("fs/promises");
3316
+ var import_path9 = require("path");
3317
+ var templateBuilder = (app) => {
3318
+ return async (term) => {
3319
+ const done = term.out.write(loadingDialog("Building stack templates..."));
3320
+ await Promise.all(app.stacks.map(async (stack) => {
3321
+ const template = stack.toString(true);
3322
+ const path = (0, import_path9.join)(templateDir, app.name);
3323
+ const file = (0, import_path9.join)(path, `${stack.name}.json`);
3324
+ await (0, import_promises7.mkdir)(path, { recursive: true });
3325
+ await (0, import_promises7.writeFile)(file, template);
3326
+ }));
3327
+ done("Done building stack templates");
3328
+ };
3329
+ };
3330
+
1986
3331
  // src/cli/command/build.ts
1987
3332
  var build = (program2) => {
1988
3333
  program2.command("build").argument("[stack...]", "Optionally filter stacks to build").description("Build your app").action(async (filters) => {
1989
3334
  await layout(async (config, write) => {
1990
- const { app, assets } = await toApp(config, filters);
3335
+ const { app } = await toApp(config, filters);
1991
3336
  await cleanUp();
1992
- await write(assetBuilder(assets));
1993
- app.synth();
3337
+ await write(assetBuilder(app));
3338
+ await write(templateBuilder(app));
1994
3339
  });
1995
3340
  });
1996
3341
  };
1997
3342
 
1998
- // src/stack/client.ts
3343
+ // src/formation/bootstrap.ts
3344
+ var assetBucketName = (account, region) => {
3345
+ return `awsless-bootstrap-${account}-${region}`;
3346
+ };
3347
+ var assetBucketUrl = (account, region, stack) => {
3348
+ const bucket = assetBucketName(account, region);
3349
+ return `https://s3-${region}.amazonaws.com/${bucket}/${stack.name}/cloudformation.json`;
3350
+ };
3351
+ var version = "1";
3352
+ var bootstrapStack = (account, region) => {
3353
+ const app = new App("awsless");
3354
+ const stack = new Stack("bootstrap", region);
3355
+ stack.add(new Bucket("assets", {
3356
+ name: assetBucketName(account, region),
3357
+ accessControl: "private",
3358
+ versioned: true
3359
+ }));
3360
+ stack.export("version", version);
3361
+ app.add(stack);
3362
+ return { app, stack };
3363
+ };
3364
+ var shouldDeployBootstrap = async (client, stack) => {
3365
+ debug("Check bootstrap status");
3366
+ const info = await client.get(stack.name, stack.region);
3367
+ return !info || info.outputs.version !== version || !["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(info.status);
3368
+ };
3369
+
3370
+ // src/formation/client.ts
1999
3371
  var import_client_cloudformation = require("@aws-sdk/client-cloudformation");
2000
- var import_client_s32 = require("@aws-sdk/client-s3");
3372
+ var import_client_s3 = require("@aws-sdk/client-s3");
3373
+ var import_change_case11 = require("change-case");
2001
3374
  var StackClient = class {
2002
- // 30 seconds
2003
- constructor(config) {
2004
- this.config = config;
2005
- this.client = new import_client_cloudformation.CloudFormationClient({
2006
- credentials: config.credentials,
2007
- region: config.region
2008
- });
3375
+ constructor(app, account, region, credentials) {
3376
+ this.app = app;
3377
+ this.account = account;
3378
+ this.region = region;
3379
+ this.credentials = credentials;
3380
+ this.assetBucketName = assetBucketName(this.account, this.region);
2009
3381
  }
2010
- client;
2011
3382
  maxWaitTime = 60 * 30;
2012
3383
  // 30 minutes
2013
3384
  maxDelay = 30;
2014
- shouldUploadTemplate(stack) {
2015
- const body = JSON.stringify(stack.template);
2016
- const size = Buffer.byteLength(body, "utf8");
3385
+ // 30 seconds
3386
+ assetBucketName;
3387
+ getClient(region) {
3388
+ return new import_client_cloudformation.CloudFormationClient({
3389
+ credentials: this.credentials,
3390
+ region
3391
+ });
3392
+ }
3393
+ shouldUploadTemplate(template) {
3394
+ const size = Buffer.byteLength(template, "utf8");
2017
3395
  return size > 5e4;
2018
3396
  }
2019
3397
  templateProp(stack) {
2020
- return this.shouldUploadTemplate(stack) ? {
2021
- TemplateUrl: assetBucketUrl(this.config, stack.stackName)
3398
+ const template = stack.toString();
3399
+ return this.shouldUploadTemplate(template) ? {
3400
+ TemplateUrl: assetBucketUrl(this.account, this.region, stack)
2022
3401
  } : {
2023
- TemplateBody: JSON.stringify(stack.template)
3402
+ TemplateBody: template
2024
3403
  };
2025
3404
  }
2026
- async upload(stack) {
2027
- debug("Upload the", style.info(stack.id), "stack to awsless assets bucket");
2028
- const client = new import_client_s32.S3Client({
2029
- credentials: this.config.credentials,
2030
- region: this.config.region
3405
+ stackName(stackName) {
3406
+ return (0, import_change_case11.paramCase)(`${this.app.name}-${stackName}`);
3407
+ }
3408
+ tags(stack) {
3409
+ const tags = [];
3410
+ for (const [name, value] of stack.tags.entries()) {
3411
+ tags.push({ Key: name, Value: value });
3412
+ }
3413
+ return tags;
3414
+ }
3415
+ async upload(stack, template) {
3416
+ debug("Upload the", style.info(stack.name), "stack to awsless assets bucket");
3417
+ const client = new import_client_s3.S3Client({
3418
+ credentials: this.credentials,
3419
+ region: stack.region
2031
3420
  });
2032
- await client.send(new import_client_s32.PutObjectCommand({
2033
- Bucket: assetBucketName(this.config),
2034
- Key: `${stack.stackName}/cloudformation.json`,
2035
- Body: JSON.stringify(stack.template),
2036
- ACL: import_client_s32.ObjectCannedACL.private,
2037
- StorageClass: import_client_s32.StorageClass.STANDARD_IA
3421
+ await client.send(new import_client_s3.PutObjectCommand({
3422
+ Bucket: this.assetBucketName,
3423
+ Key: `${this.app.name}/${stack.name}/cloudformation.json`,
3424
+ Body: template,
3425
+ ACL: import_client_s3.ObjectCannedACL.private,
3426
+ StorageClass: import_client_s3.StorageClass.STANDARD_IA
2038
3427
  }));
2039
3428
  }
2040
3429
  async create(stack, capabilities) {
2041
- debug("Create the", style.info(stack.id), "stack");
2042
- await this.client.send(new import_client_cloudformation.CreateStackCommand({
2043
- StackName: stack.stackName,
3430
+ debug("Create the", style.info(stack.name), "stack");
3431
+ const client = this.getClient(stack.region);
3432
+ await client.send(new import_client_cloudformation.CreateStackCommand({
3433
+ StackName: this.stackName(stack.name),
2044
3434
  EnableTerminationProtection: false,
2045
3435
  OnFailure: import_client_cloudformation.OnFailure.DELETE,
2046
3436
  Capabilities: capabilities,
3437
+ Tags: this.tags(stack),
2047
3438
  ...this.templateProp(stack)
2048
3439
  }));
2049
3440
  await (0, import_client_cloudformation.waitUntilStackCreateComplete)({
2050
- client: this.client,
3441
+ client,
2051
3442
  maxWaitTime: this.maxWaitTime,
2052
3443
  maxDelay: this.maxDelay
2053
3444
  }, {
2054
- StackName: stack.stackName
3445
+ StackName: this.stackName(stack.name)
2055
3446
  });
2056
3447
  }
2057
3448
  async update(stack, capabilities) {
2058
- debug("Update the", style.info(stack.id), "stack");
2059
- await this.client.send(new import_client_cloudformation.UpdateStackCommand({
2060
- StackName: stack.stackName,
3449
+ debug("Update the", style.info(stack.name), "stack");
3450
+ const client = this.getClient(stack.region);
3451
+ await client.send(new import_client_cloudformation.UpdateStackCommand({
3452
+ StackName: this.stackName(stack.name),
2061
3453
  Capabilities: capabilities,
3454
+ Tags: this.tags(stack),
2062
3455
  ...this.templateProp(stack)
2063
3456
  }));
2064
3457
  await (0, import_client_cloudformation.waitUntilStackUpdateComplete)({
2065
- client: this.client,
3458
+ client,
2066
3459
  maxWaitTime: this.maxWaitTime,
2067
3460
  maxDelay: this.maxDelay
2068
3461
  }, {
2069
- StackName: stack.stackName
3462
+ StackName: this.stackName(stack.name)
2070
3463
  });
2071
3464
  }
2072
3465
  async validate(stack) {
2073
- debug("Validate the", style.info(stack.id), "stack");
2074
- const result = await this.client.send(new import_client_cloudformation.ValidateTemplateCommand({
3466
+ debug("Validate the", style.info(stack.name), "stack");
3467
+ const client = this.getClient(stack.region);
3468
+ const result = await client.send(new import_client_cloudformation.ValidateTemplateCommand({
2075
3469
  ...this.templateProp(stack)
2076
3470
  }));
2077
3471
  return result.Capabilities;
2078
3472
  }
2079
- async get(name) {
3473
+ async get(name, region) {
2080
3474
  debug("Get stack info for:", style.info(name));
3475
+ const client = this.getClient(region);
2081
3476
  let result;
2082
3477
  try {
2083
- result = await this.client.send(new import_client_cloudformation.DescribeStacksCommand({
2084
- StackName: name
3478
+ result = await client.send(new import_client_cloudformation.DescribeStacksCommand({
3479
+ StackName: this.stackName(name)
2085
3480
  }));
2086
3481
  } catch (error) {
2087
3482
  if (error instanceof Error && error.name === "ValidationError" && error.message.includes("does not exist")) {
3483
+ debug("Stack not found");
2088
3484
  return;
2089
3485
  }
2090
3486
  throw error;
@@ -2094,8 +3490,8 @@ var StackClient = class {
2094
3490
  debug("Stack not found");
2095
3491
  return;
2096
3492
  }
2097
- const resultTemplate = await this.client.send(new import_client_cloudformation.GetTemplateCommand({
2098
- StackName: name,
3493
+ const resultTemplate = await client.send(new import_client_cloudformation.GetTemplateCommand({
3494
+ StackName: this.stackName(name),
2099
3495
  TemplateStage: import_client_cloudformation.TemplateStage.Original
2100
3496
  }));
2101
3497
  const outputs = {};
@@ -2113,14 +3509,15 @@ var StackClient = class {
2113
3509
  };
2114
3510
  }
2115
3511
  async deploy(stack) {
2116
- const data = await this.get(stack.stackName);
2117
- debug("Deploy:", style.info(stack.stackName));
2118
- if (data?.template === JSON.stringify(stack.template)) {
3512
+ const template = stack.toString();
3513
+ const data = await this.get(stack.name, stack.region);
3514
+ debug("Deploy:", style.info(stack.name));
3515
+ if (data?.template === template) {
2119
3516
  debug("No stack changes");
2120
3517
  return false;
2121
3518
  }
2122
- if (this.shouldUploadTemplate(stack)) {
2123
- await this.upload(stack);
3519
+ if (this.shouldUploadTemplate(template)) {
3520
+ await this.upload(stack, template);
2124
3521
  }
2125
3522
  const capabilities = await this.validate(stack);
2126
3523
  if (!data) {
@@ -2132,22 +3529,23 @@ var StackClient = class {
2132
3529
  }
2133
3530
  return true;
2134
3531
  }
2135
- async delete(name) {
2136
- const data = await this.get(name);
3532
+ async delete(name, region) {
3533
+ const data = await this.get(name, region);
3534
+ const client = this.getClient(region);
2137
3535
  debug("Delete the", style.info(name), "stack");
2138
3536
  if (!data) {
2139
3537
  debug("Already deleted");
2140
3538
  return;
2141
3539
  }
2142
- await this.client.send(new import_client_cloudformation.DeleteStackCommand({
2143
- StackName: name
3540
+ await client.send(new import_client_cloudformation.DeleteStackCommand({
3541
+ StackName: this.stackName(name)
2144
3542
  }));
2145
3543
  await (0, import_client_cloudformation.waitUntilStackDeleteComplete)({
2146
- client: this.client,
3544
+ client,
2147
3545
  maxWaitTime: this.maxWaitTime,
2148
3546
  maxDelay: this.maxDelay
2149
3547
  }, {
2150
- StackName: name
3548
+ StackName: this.stackName(name)
2151
3549
  });
2152
3550
  }
2153
3551
  };
@@ -2236,10 +3634,9 @@ var confirmPrompt = (label, options = {}) => {
2236
3634
  var bootstrapDeployer = (config) => {
2237
3635
  return async (term) => {
2238
3636
  debug("Initializing bootstrap");
2239
- const app = makeApp(config);
2240
- const client = new StackClient(config);
2241
- const bootstrap2 = bootstrapStack(config, app);
2242
- const shouldDeploy = await shouldDeployBootstrap(client, bootstrap2.stackName);
3637
+ const { app, stack } = bootstrapStack(config.account, config.region);
3638
+ const client = new StackClient(app, config.account, config.region, config.credentials);
3639
+ const shouldDeploy = await shouldDeployBootstrap(client, stack);
2243
3640
  if (shouldDeploy) {
2244
3641
  term.out.write(dialog("warning", [`Your app hasn't been bootstrapped yet`]));
2245
3642
  const confirmed = await term.out.write(confirmPrompt("Would you like to bootstrap?"));
@@ -2247,8 +3644,7 @@ var bootstrapDeployer = (config) => {
2247
3644
  throw new Cancelled();
2248
3645
  }
2249
3646
  const done = term.out.write(loadingDialog("Bootstrapping..."));
2250
- const assembly = app.synth();
2251
- await client.deploy(assembly.stacks[0]);
3647
+ await client.deploy(stack);
2252
3648
  done("Done deploying the bootstrap stack");
2253
3649
  } else {
2254
3650
  term.out.write(dialog("success", [
@@ -2274,8 +3670,8 @@ var stackTree = (nodes, statuses) => {
2274
3670
  const render = (nodes2, deep = 0, parents = []) => {
2275
3671
  const size = nodes2.length - 1;
2276
3672
  nodes2.forEach((node, i) => {
2277
- const id = node.stack.artifactId;
2278
- const status2 = statuses[id];
3673
+ const name = node.stack.name;
3674
+ const status2 = statuses[name];
2279
3675
  const first = i === 0 && deep === 0;
2280
3676
  const last = i === size;
2281
3677
  const more = i < size;
@@ -2289,7 +3685,7 @@ var stackTree = (nodes, statuses) => {
2289
3685
  first && size === 0 ? " " : first ? "\u250C\u2500" : last ? "\u2514\u2500" : "\u251C\u2500"
2290
3686
  ),
2291
3687
  " ",
2292
- style.info(id),
3688
+ style.info(name),
2293
3689
  " "
2294
3690
  ], [
2295
3691
  " ",
@@ -2300,7 +3696,9 @@ var stackTree = (nodes, statuses) => {
2300
3696
  render(node.children, deep + 1, [...parents, more]);
2301
3697
  });
2302
3698
  };
3699
+ term.out.gap();
2303
3700
  render(nodes);
3701
+ term.out.gap();
2304
3702
  };
2305
3703
  };
2306
3704
 
@@ -2308,31 +3706,27 @@ var stackTree = (nodes, statuses) => {
2308
3706
  var status = (program2) => {
2309
3707
  program2.command("status").argument("[stacks...]", "Optionally filter stacks to lookup status").description("View the application status").action(async (filters) => {
2310
3708
  await layout(async (config, write) => {
2311
- const { app, assets, dependencyTree } = await toApp(config, filters);
3709
+ const { app, dependencyTree } = await toApp(config, filters);
2312
3710
  await cleanUp();
2313
- await write(assetBuilder(assets));
2314
- write(br());
2315
- const assembly = app.synth();
3711
+ await write(assetBuilder(app));
3712
+ await write(templateBuilder(app));
2316
3713
  const doneLoading = write(loadingDialog("Loading stack information..."));
2317
- const client = new StackClient(config);
3714
+ const client = new StackClient(app, config.account, config.region, config.credentials);
2318
3715
  const statuses = [];
2319
3716
  const stackStatuses = {};
2320
- assembly.stacks.forEach((stack) => {
2321
- stackStatuses[stack.id] = new Signal(style.info("Loading..."));
2322
- });
2323
- write(br());
3717
+ for (const stack of app) {
3718
+ stackStatuses[stack.name] = new Signal(style.info("Loading..."));
3719
+ }
2324
3720
  write(stackTree(dependencyTree, stackStatuses));
2325
- write(br());
2326
3721
  debug("Load metadata for all deployed stacks on AWS");
2327
- await Promise.all(assembly.stacks.map(async (stack, i) => {
2328
- const info = await client.get(stack.stackName);
2329
- const name = stack.id;
2330
- const signal = stackStatuses[name];
3722
+ await Promise.all(app.stacks.map(async (stack, i) => {
3723
+ const info = await client.get(stack.name, stack.region);
3724
+ const signal = stackStatuses[stack.name];
2331
3725
  await new Promise((resolve) => setTimeout(resolve, i * 1e3));
2332
3726
  if (!info) {
2333
3727
  signal.set(style.error("non-existent"));
2334
3728
  statuses.push("non-existent");
2335
- } else if (info.template !== JSON.stringify(stack.template)) {
3729
+ } else if (info.template !== stack.toString()) {
2336
3730
  signal.set(style.warning("out-of-date"));
2337
3731
  statuses.push("out-of-date");
2338
3732
  } else {
@@ -2351,12 +3745,77 @@ var status = (program2) => {
2351
3745
  });
2352
3746
  };
2353
3747
 
3748
+ // src/cli/ui/complex/publisher.ts
3749
+ var import_promises8 = require("fs/promises");
3750
+ var import_path11 = require("path");
3751
+ var import_client_s32 = require("@aws-sdk/client-s3");
3752
+ var assetPublisher = (config, app) => {
3753
+ const client = new import_client_s32.S3Client({
3754
+ credentials: config.credentials,
3755
+ region: config.region
3756
+ });
3757
+ return async (term) => {
3758
+ const done = term.out.write(loadingDialog("Publishing stack assets to AWS..."));
3759
+ await Promise.all(app.stacks.map(async (stack) => {
3760
+ await Promise.all([...stack.assets].map(async (asset) => {
3761
+ await asset.publish?.({
3762
+ async read(file) {
3763
+ const path = (0, import_path11.join)(assetDir, asset.type, app.name, stack.name, asset.id, file);
3764
+ const data = await (0, import_promises8.readFile)(path);
3765
+ return data;
3766
+ },
3767
+ async publish(name, data, hash) {
3768
+ const key = `${app.name}/${stack.name}/function/${name}`;
3769
+ const bucket = assetBucketName(config.account, config.region);
3770
+ let getResult;
3771
+ try {
3772
+ getResult = await client.send(new import_client_s32.GetObjectCommand({
3773
+ Bucket: bucket,
3774
+ Key: key
3775
+ }));
3776
+ } catch (error) {
3777
+ if (error instanceof Error && error.name === "NoSuchKey") {
3778
+ } else {
3779
+ throw error;
3780
+ }
3781
+ }
3782
+ if (getResult?.Metadata?.hash === hash) {
3783
+ return {
3784
+ bucket,
3785
+ key,
3786
+ version: getResult.VersionId
3787
+ };
3788
+ }
3789
+ const putResult = await client.send(new import_client_s32.PutObjectCommand({
3790
+ Bucket: bucket,
3791
+ Key: key,
3792
+ Body: data,
3793
+ ACL: import_client_s32.ObjectCannedACL.private,
3794
+ StorageClass: import_client_s32.StorageClass.STANDARD,
3795
+ Metadata: {
3796
+ hash
3797
+ }
3798
+ }));
3799
+ return {
3800
+ bucket,
3801
+ key,
3802
+ version: putResult.VersionId
3803
+ };
3804
+ }
3805
+ });
3806
+ }));
3807
+ }));
3808
+ done("Done publishing stack assets to AWS");
3809
+ };
3810
+ };
3811
+
2354
3812
  // src/cli/command/deploy.ts
2355
3813
  var deploy = (program2) => {
2356
3814
  program2.command("deploy").argument("[stacks...]", "Optionally filter stacks to deploy").description("Deploy your app to AWS").action(async (filters) => {
2357
3815
  await layout(async (config, write) => {
2358
3816
  await write(bootstrapDeployer(config));
2359
- const { app, stackNames, assets, dependencyTree } = await toApp(config, filters);
3817
+ const { app, dependencyTree } = await toApp(config, filters);
3818
+ const stackNames = app.stacks.map((stack) => stack.name);
2360
3819
  const formattedFilter = stackNames.map((i) => style.info(i)).join(style.placeholder(", "));
2361
3820
  debug("Stacks to deploy", formattedFilter);
2362
3821
  const deployAll = filters.length === 0;
@@ -2366,34 +3825,23 @@ var deploy = (program2) => {
2366
3825
  throw new Cancelled();
2367
3826
  }
2368
3827
  await cleanUp();
2369
- await write(assetBuilder(assets));
2370
- write(br());
2371
- write(br());
2372
- const donePublishing = write(loadingDialog("Publishing stack assets to AWS..."));
2373
- await Promise.all(assets.map(async (_, assets2) => {
2374
- await Promise.all(assets2.map(async (asset) => {
2375
- await asset.publish?.();
2376
- }));
2377
- }));
2378
- donePublishing("Done publishing stack assets to AWS");
2379
- const assembly = app.synth();
3828
+ await write(assetBuilder(app));
3829
+ await write(assetPublisher(config, app));
3830
+ await write(templateBuilder(app));
2380
3831
  const statuses = {};
2381
- assembly.stacks.map((stack) => {
2382
- statuses[stack.id] = new Signal(style.info("waiting"));
2383
- });
3832
+ for (const stack of app) {
3833
+ statuses[stack.name] = new Signal(style.info("waiting"));
3834
+ }
2384
3835
  const doneDeploying = write(loadingDialog("Deploying stacks to AWS..."));
2385
- write(br());
2386
3836
  write(stackTree(dependencyTree, statuses));
2387
- write(br());
2388
- const client = new StackClient(config);
3837
+ const client = new StackClient(app, config.account, config.region, config.credentials);
2389
3838
  const deploymentLine = createDeploymentLine(dependencyTree);
2390
3839
  for (const stacks of deploymentLine) {
2391
3840
  const results = await Promise.allSettled(stacks.map(async (stack) => {
2392
- const signal = statuses[stack.artifactId];
2393
- const stackArtifect = assembly.stacks.find((item) => item.id === stack.artifactId);
3841
+ const signal = statuses[stack.name];
2394
3842
  signal.set(style.warning("deploying"));
2395
3843
  try {
2396
- await client.deploy(stackArtifect);
3844
+ await client.deploy(stack);
2397
3845
  } catch (error) {
2398
3846
  debugError(error);
2399
3847
  signal.set(style.error("failed"));
@@ -2482,7 +3930,6 @@ var set = (program2) => {
2482
3930
  write(list({
2483
3931
  "Set secret parameter": style.info(name)
2484
3932
  }));
2485
- write(br());
2486
3933
  const value = await write(textPrompt("Enter secret value"));
2487
3934
  if (value === "") {
2488
3935
  write(dialog("error", [`Provided secret value can't be empty`]));
@@ -2503,7 +3950,6 @@ var get = (program2) => {
2503
3950
  const done = write(loadingDialog(`Getting remote secret parameter`));
2504
3951
  const value = await params.get(name);
2505
3952
  done(`Done getting remote secret parameter`);
2506
- write(br());
2507
3953
  write(list({
2508
3954
  Name: name,
2509
3955
  Value: value || style.error("(empty)")
@@ -2526,7 +3972,6 @@ var del = (program2) => {
2526
3972
  const value = await params.get(name);
2527
3973
  await params.delete(name);
2528
3974
  done(`Done deleting remote secret parameter`);
2529
- write(br());
2530
3975
  write(list({
2531
3976
  Name: name,
2532
3977
  Value: value || style.error("(empty)")
@@ -2544,7 +3989,6 @@ var list2 = (program2) => {
2544
3989
  const values = await params.list();
2545
3990
  done("Done loading secret values");
2546
3991
  if (Object.keys(values).length > 0) {
2547
- write(br());
2548
3992
  write(list(values));
2549
3993
  } else {
2550
3994
  write(dialog("warning", ["No secret parameters found"]));
@@ -2565,15 +4009,28 @@ var secrets = (program2) => {
2565
4009
  commands.forEach((cb) => cb(command));
2566
4010
  };
2567
4011
 
4012
+ // src/cli/command/test.ts
4013
+ var test = (program2) => {
4014
+ program2.command("test").action(async () => {
4015
+ await layout(async (config) => {
4016
+ const app = new App("test");
4017
+ const name = "test5";
4018
+ });
4019
+ });
4020
+ };
4021
+
2568
4022
  // src/cli/program.ts
2569
4023
  var program = new import_commander.Command();
2570
- program.name("awsless");
4024
+ program.name(logo().join("").replace(/\s+/, ""));
2571
4025
  program.option("--config-file <string>", "The config file location");
2572
4026
  program.option("--stage <string>", "The stage to use, defaults to prod stage", "prod");
2573
4027
  program.option("--profile <string>", "The AWS profile to use");
2574
4028
  program.option("--region <string>", "The AWS region to use");
2575
4029
  program.option("-m --mute", "Mute sound effects");
2576
4030
  program.option("-v --verbose", "Print verbose logs");
4031
+ program.exitOverride(() => {
4032
+ process.exit(0);
4033
+ });
2577
4034
  program.on("option:verbose", () => {
2578
4035
  process.env.VERBOSE = program.opts().verbose ? "1" : void 0;
2579
4036
  });
@@ -2582,13 +4039,12 @@ var commands2 = [
2582
4039
  status,
2583
4040
  build,
2584
4041
  deploy,
2585
- secrets
4042
+ secrets,
4043
+ test
2586
4044
  // diff,
2587
4045
  // remove,
2588
- // test,
2589
- // test2,
2590
4046
  ];
2591
- commands2.forEach((command) => command(program));
4047
+ commands2.forEach((fn) => fn(program));
2592
4048
 
2593
4049
  // src/bin.ts
2594
4050
  program.parse(process.argv);