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