@awsless/awsless 0.0.40 → 0.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -617,7 +617,7 @@ var createDeploymentLine = (stacks) => {
617
617
  depends: config?.depends?.map((dep) => dep.name) || []
618
618
  }));
619
619
  const names = stacks.map(({ stack }) => stack.name);
620
- const line = [];
620
+ const line2 = [];
621
621
  const deps = [];
622
622
  let limit = 100;
623
623
  while (deps.length < list3.length) {
@@ -632,9 +632,9 @@ var createDeploymentLine = (stacks) => {
632
632
  throw new Error(`Circular stack dependencies arn't allowed: ${circularNames}`);
633
633
  }
634
634
  deps.push(...local.map((stack) => stack.name));
635
- line.push(local);
635
+ line2.push(local);
636
636
  }
637
- return line;
637
+ return line2;
638
638
  };
639
639
 
640
640
  // src/plugins/cron/index.ts
@@ -647,23 +647,30 @@ var definePlugin = (plugin) => plugin;
647
647
  var import_zod = require("zod");
648
648
  var import_aws_cron_expression_validator = require("aws-cron-expression-validator");
649
649
  var RateExpressionSchema = import_zod.z.custom((value) => {
650
- return import_zod.z.string().regex(/rate\([0-9]+ (seconds?|minutes?|hours?|days?)\)/).refine((rate) => {
651
- const [str] = rate.substring(5).split(" ");
650
+ return import_zod.z.string().regex(/^[0-9]+ (seconds?|minutes?|hours?|days?)$/).refine((rate) => {
651
+ const [str] = rate.split(" ");
652
652
  const number = parseInt(str);
653
653
  return number > 0;
654
654
  }).safeParse(value).success;
655
- }, "Invalid rate expression");
655
+ }, { message: "Invalid rate expression" }).transform((rate) => {
656
+ const [str] = rate.split(" ");
657
+ const number = parseInt(str);
658
+ const more = rate.endsWith("s");
659
+ if (more && number === 1) {
660
+ return `rate(${rate.substring(0, rate.length - 1)})`;
661
+ }
662
+ return `rate(${rate})`;
663
+ });
656
664
  var CronExpressionSchema = import_zod.z.custom((value) => {
657
- return import_zod.z.string().startsWith("cron(").endsWith(")").safeParse(value).success;
658
- }, "Invalid cron expression").superRefine((value, ctx) => {
659
- const cron = value.substring(5, value.length - 1);
665
+ return import_zod.z.string().safeParse(value).success;
666
+ }, { message: "Invalid cron expression" }).superRefine((value, ctx) => {
660
667
  try {
661
- (0, import_aws_cron_expression_validator.awsCronExpressionValidator)(cron);
668
+ (0, import_aws_cron_expression_validator.awsCronExpressionValidator)(value);
662
669
  } catch (error) {
663
670
  if (error instanceof Error) {
664
671
  ctx.addIssue({
665
672
  code: import_zod.z.ZodIssueCode.custom,
666
- message: error.message
673
+ message: `Invalid cron expression: ${error.message}`
667
674
  });
668
675
  } else {
669
676
  ctx.addIssue({
@@ -672,6 +679,8 @@ var CronExpressionSchema = import_zod.z.custom((value) => {
672
679
  });
673
680
  }
674
681
  }
682
+ }).transform((value) => {
683
+ return `cron(${value.trim()})`;
675
684
  });
676
685
  var ScheduleExpressionSchema = RateExpressionSchema.or(CronExpressionSchema);
677
686
 
@@ -735,7 +744,7 @@ function toDuration(duration) {
735
744
  return Duration.days(0);
736
745
  }
737
746
  var DurationSchema = import_zod2.z.custom((value) => {
738
- return import_zod2.z.string().regex(/[0-9]+ (seconds?|minutes?|hours?|days?)/).safeParse(value).success;
747
+ return import_zod2.z.string().regex(/^[0-9]+ (seconds?|minutes?|hours?|days?)$/).safeParse(value).success;
739
748
  }, "Invalid duration").transform(toDuration);
740
749
  var durationMin = (min) => {
741
750
  return (duration) => {
@@ -762,7 +771,7 @@ var LocalFileSchema = import_zod3.z.string().refine(async (path) => {
762
771
 
763
772
  // src/schema/resource-id.ts
764
773
  var import_zod4 = require("zod");
765
- var ResourceIdSchema = import_zod4.z.string().min(3).max(24).regex(/[a-z\-]+/, "Invalid resource ID");
774
+ var ResourceIdSchema = import_zod4.z.string().min(3).max(24).regex(/^[a-z\-]+$/, "Invalid resource ID");
766
775
 
767
776
  // src/schema/size.ts
768
777
  var import_zod5 = require("zod");
@@ -812,7 +821,7 @@ function toSize(size) {
812
821
  throw new TypeError(`Invalid size ${size}`);
813
822
  }
814
823
  var SizeSchema = import_zod5.z.custom((value) => {
815
- return import_zod5.z.string().regex(/[0-9]+ (KB|MB|GB)/).safeParse(value).success;
824
+ return import_zod5.z.string().regex(/^[0-9]+ (KB|MB|GB)$/).safeParse(value).success;
816
825
  }, "Invalid size").transform(toSize);
817
826
  var sizeMin = (min) => {
818
827
  return (size) => {
@@ -1317,6 +1326,7 @@ import { InvokeOptions } from '@awsless/lambda'
1317
1326
  type Invoke<Name extends string, Func extends (...args: any[]) => any> = {
1318
1327
  name: Name
1319
1328
  (payload: Parameters<Func>[0], options?: Omit<InvokeOptions, 'name' | 'payload'>): ReturnType<Func>
1329
+ async: (payload: Parameters<Func>[0], options?: Omit<InvokeOptions, 'name' | 'payload' | 'type'>) => ReturnType<Func>
1320
1330
  }`;
1321
1331
  var functionPlugin = definePlugin({
1322
1332
  name: "function",
@@ -1478,7 +1488,7 @@ var cronPlugin = definePlugin({
1478
1488
  * crons: {
1479
1489
  * CRON_NAME: {
1480
1490
  * consumer: 'function.ts',
1481
- * schedule: 'rate(5 minutes)',
1491
+ * schedule: '5 minutes',
1482
1492
  * }
1483
1493
  * }
1484
1494
  * }
@@ -1487,8 +1497,8 @@ var cronPlugin = definePlugin({
1487
1497
  /** The consuming lambda function properties. */
1488
1498
  consumer: FunctionSchema,
1489
1499
  /** The scheduling expression.
1490
- * @example 'cron(0 20 * * ? *)'
1491
- * @example 'rate(5 minutes)'
1500
+ * @example '0 20 * * ? *'
1501
+ * @example '5 minutes'
1492
1502
  */
1493
1503
  schedule: ScheduleExpressionSchema,
1494
1504
  // Valid JSON passed to the consumer.
@@ -1630,6 +1640,8 @@ var SqsEventSource = class extends Group {
1630
1640
  };
1631
1641
 
1632
1642
  // src/plugins/queue.ts
1643
+ var import_change_case6 = require("change-case");
1644
+ var import_path8 = require("path");
1633
1645
  var RetentionPeriodSchema = DurationSchema.refine(durationMin(Duration.minutes(1)), "Minimum retention period is 1 minute").refine(durationMax(Duration.days(14)), "Maximum retention period is 14 days");
1634
1646
  var VisibilityTimeoutSchema = DurationSchema.refine(durationMax(Duration.hours(12)), "Maximum visibility timeout is 12 hours");
1635
1647
  var DeliveryDelaySchema = DurationSchema.refine(durationMax(Duration.minutes(15)), "Maximum delivery delay is 15 minutes");
@@ -1639,11 +1651,14 @@ var BatchSizeSchema = import_zod8.z.number().int().min(1, "Minimum batch size is
1639
1651
  var MaxConcurrencySchema = import_zod8.z.number().int().min(2, "Minimum max concurrency is 2").max(1e3, "Maximum max concurrency is 1000");
1640
1652
  var MaxBatchingWindow = DurationSchema.refine(durationMax(Duration.minutes(5)), "Maximum max batching window is 5 minutes");
1641
1653
  var typeGenCode2 = `
1642
- import { SendMessageOptions } from '@awsless/sqs'
1654
+ import { SendMessageOptions, SendMessageBatchOptions, BatchItem } from '@awsless/sqs'
1643
1655
 
1644
- type Send<Name extends string> = {
1656
+ type Payload<Func extends (...args: any[]) => any> = Parameters<Func>[0]['Records'][number]['body']
1657
+
1658
+ type Send<Name extends string, Func extends (...args: any[]) => any> = {
1645
1659
  name: Name
1646
- (payload: unknown, options?: Omit<SendMessageOptions, 'queue' | 'payload'>): Promise<void>
1660
+ batch(items:BatchItem<Payload<Func>>[], options?:Omit<SendMessageBatchOptions, 'queue' | 'items'>): Promise<void>
1661
+ (payload: Payload<Func>, options?: Omit<SendMessageOptions, 'queue' | 'payload'>): Promise<void>
1647
1662
  }`;
1648
1663
  var queuePlugin = definePlugin({
1649
1664
  name: "queue",
@@ -1749,9 +1764,13 @@ var queuePlugin = definePlugin({
1749
1764
  types2.addCode(typeGenCode2);
1750
1765
  for (const stack of config.stacks) {
1751
1766
  const list3 = new TypeObject();
1752
- for (const name of Object.keys(stack.queues || {})) {
1767
+ for (const [name, fileOrProps] of Object.entries(stack.queues || {})) {
1768
+ const varName = (0, import_change_case6.camelCase)(`${stack.name}-${name}`);
1753
1769
  const queueName = formatName(`${config.name}-${stack.name}-${name}`);
1754
- list3.addType(name, `Send<'${queueName}'>`);
1770
+ const file = typeof fileOrProps === "string" ? fileOrProps : typeof fileOrProps.consumer === "string" ? fileOrProps.consumer : fileOrProps.consumer.file;
1771
+ const relFile = (0, import_path8.relative)(directories.types, file);
1772
+ types2.addImport(varName, relFile);
1773
+ list3.addType(name, `Send<'${queueName}', typeof ${varName}>`);
1755
1774
  }
1756
1775
  types2.addType(stack.name, list3.toString());
1757
1776
  }
@@ -1776,6 +1795,7 @@ var queuePlugin = definePlugin({
1776
1795
  stack.add(queue2, lambda, source);
1777
1796
  bind((lambda2) => {
1778
1797
  lambda2.addPermissions(queue2.permissions);
1798
+ lambda2.addEnvironment(`QUEUE_${(0, import_change_case6.constantCase)(stack.name)}_${(0, import_change_case6.constantCase)(id)}_URL`, queue2.url);
1779
1799
  });
1780
1800
  }
1781
1801
  }
@@ -1785,7 +1805,7 @@ var queuePlugin = definePlugin({
1785
1805
  var import_zod9 = require("zod");
1786
1806
 
1787
1807
  // src/formation/resource/dynamodb/table.ts
1788
- var import_change_case6 = require("change-case");
1808
+ var import_change_case7 = require("change-case");
1789
1809
  var Table = class extends Resource {
1790
1810
  constructor(logicalId, props) {
1791
1811
  super("AWS::DynamoDB::Table", logicalId);
@@ -1858,7 +1878,7 @@ var Table = class extends Resource {
1858
1878
  return {
1859
1879
  TableName: this.name,
1860
1880
  BillingMode: "PAY_PER_REQUEST",
1861
- TableClass: (0, import_change_case6.constantCase)(this.props.class || "standard"),
1881
+ TableClass: (0, import_change_case7.constantCase)(this.props.class || "standard"),
1862
1882
  PointInTimeRecoverySpecification: {
1863
1883
  PointInTimeRecoveryEnabled: this.props.pointInTimeRecovery || false
1864
1884
  },
@@ -1869,7 +1889,7 @@ var Table = class extends Resource {
1869
1889
  AttributeDefinitions: this.attributeDefinitions(),
1870
1890
  ...this.props.stream ? {
1871
1891
  StreamSpecification: {
1872
- StreamViewType: (0, import_change_case6.constantCase)(this.props.stream)
1892
+ StreamViewType: (0, import_change_case7.constantCase)(this.props.stream)
1873
1893
  }
1874
1894
  } : {},
1875
1895
  ...this.props.timeToLiveAttribute ? {
@@ -1886,7 +1906,7 @@ var Table = class extends Resource {
1886
1906
  ...props.sort ? [{ KeyType: "RANGE", AttributeName: props.sort }] : []
1887
1907
  ],
1888
1908
  Projection: {
1889
- ProjectionType: (0, import_change_case6.constantCase)(props.projection || "all")
1909
+ ProjectionType: (0, import_change_case7.constantCase)(props.projection || "all")
1890
1910
  }
1891
1911
  }))
1892
1912
  } : {}
@@ -2071,7 +2091,7 @@ var tablePlugin = definePlugin({
2071
2091
  var import_zod10 = require("zod");
2072
2092
 
2073
2093
  // src/formation/resource/s3/bucket.ts
2074
- var import_change_case7 = require("change-case");
2094
+ var import_change_case8 = require("change-case");
2075
2095
  var Bucket = class extends Resource {
2076
2096
  constructor(logicalId, props = {}) {
2077
2097
  super("AWS::S3::Bucket", logicalId);
@@ -2106,7 +2126,7 @@ var Bucket = class extends Resource {
2106
2126
  properties() {
2107
2127
  return {
2108
2128
  BucketName: this.name,
2109
- AccessControl: (0, import_change_case7.pascalCase)(this.props.accessControl ?? "private"),
2129
+ AccessControl: (0, import_change_case8.pascalCase)(this.props.accessControl ?? "private"),
2110
2130
  ...this.props.versioned ? {
2111
2131
  VersioningConfiguration: {
2112
2132
  Status: "Enabled"
@@ -2317,12 +2337,12 @@ var extendPlugin = definePlugin({
2317
2337
  var import_zod13 = require("zod");
2318
2338
 
2319
2339
  // src/formation/resource/iot/topic-rule.ts
2320
- var import_change_case8 = require("change-case");
2340
+ var import_change_case9 = require("change-case");
2321
2341
  var TopicRule = class extends Resource {
2322
2342
  constructor(logicalId, props) {
2323
2343
  super("AWS::IoT::TopicRule", logicalId);
2324
2344
  this.props = props;
2325
- this.name = (0, import_change_case8.snakeCase)(this.props.name || logicalId);
2345
+ this.name = (0, import_change_case9.snakeCase)(this.props.name || logicalId);
2326
2346
  }
2327
2347
  name;
2328
2348
  get arn() {
@@ -2422,10 +2442,10 @@ var toArray = (value) => {
2422
2442
  };
2423
2443
 
2424
2444
  // src/plugins/graphql.ts
2425
- var import_change_case12 = require("change-case");
2445
+ var import_change_case13 = require("change-case");
2426
2446
 
2427
2447
  // src/formation/resource/appsync/graphql-api.ts
2428
- var import_change_case9 = require("change-case");
2448
+ var import_change_case10 = require("change-case");
2429
2449
  var GraphQLApi = class extends Resource {
2430
2450
  constructor(logicalId, props) {
2431
2451
  super("AWS::AppSync::GraphQLApi", logicalId);
@@ -2457,7 +2477,7 @@ var GraphQLApi = class extends Resource {
2457
2477
  properties() {
2458
2478
  return {
2459
2479
  Name: this.name,
2460
- AuthenticationType: (0, import_change_case9.constantCase)(this.props.authenticationType || "api-key"),
2480
+ AuthenticationType: (0, import_change_case10.constantCase)(this.props.authenticationType || "api-key"),
2461
2481
  AdditionalAuthenticationProviders: this.lambdaAuthProviders.map((provider) => ({
2462
2482
  AuthenticationType: "AWS_LAMBDA",
2463
2483
  LambdaAuthorizerConfig: {
@@ -2584,12 +2604,12 @@ var FileCode2 = class extends Asset {
2584
2604
  };
2585
2605
 
2586
2606
  // src/formation/resource/appsync/data-source.ts
2587
- var import_change_case10 = require("change-case");
2607
+ var import_change_case11 = require("change-case");
2588
2608
  var DataSource = class extends Resource {
2589
2609
  constructor(logicalId, props) {
2590
2610
  super("AWS::AppSync::DataSource", logicalId);
2591
2611
  this.props = props;
2592
- this.name = (0, import_change_case10.snakeCase)(this.props.name || logicalId);
2612
+ this.name = (0, import_change_case11.snakeCase)(this.props.name || logicalId);
2593
2613
  }
2594
2614
  static fromLambda(logicalId, apiId, props) {
2595
2615
  return new DataSource(logicalId, {
@@ -2629,14 +2649,14 @@ var DataSource = class extends Resource {
2629
2649
  };
2630
2650
 
2631
2651
  // src/formation/resource/appsync/function-configuration.ts
2632
- var import_change_case11 = require("change-case");
2652
+ var import_change_case12 = require("change-case");
2633
2653
  var FunctionConfiguration = class extends Resource {
2634
2654
  constructor(logicalId, props) {
2635
2655
  super("AWS::AppSync::FunctionConfiguration", logicalId, [
2636
2656
  props.code
2637
2657
  ]);
2638
2658
  this.props = props;
2639
- this.name = (0, import_change_case11.snakeCase)(this.props.name || logicalId);
2659
+ this.name = (0, import_change_case12.snakeCase)(this.props.name || logicalId);
2640
2660
  }
2641
2661
  name;
2642
2662
  get id() {
@@ -2765,9 +2785,6 @@ export function response(ctx) {
2765
2785
  return ctx.result
2766
2786
  }
2767
2787
  `;
2768
- var ResolverFieldSchema = import_zod14.z.custom((value) => {
2769
- return import_zod14.z.string().regex(/([a-z0-9\_]+)(\s){1}([a-z0-9\_]+)/gi).safeParse(value).success;
2770
- }, `Invalid resolver field. Valid example: "Query list"`);
2771
2788
  var graphqlPlugin = definePlugin({
2772
2789
  name: "graphql",
2773
2790
  schema: import_zod14.z.object({
@@ -2788,7 +2805,13 @@ var graphqlPlugin = definePlugin({
2788
2805
  LocalFileSchema,
2789
2806
  import_zod14.z.array(LocalFileSchema).min(1)
2790
2807
  ]).optional(),
2791
- resolvers: import_zod14.z.record(ResolverFieldSchema, FunctionSchema).optional()
2808
+ resolvers: import_zod14.z.record(
2809
+ import_zod14.z.string(),
2810
+ import_zod14.z.record(
2811
+ import_zod14.z.string(),
2812
+ FunctionSchema
2813
+ )
2814
+ ).optional()
2792
2815
  })).optional()
2793
2816
  }).array()
2794
2817
  }),
@@ -2853,17 +2876,18 @@ var graphqlPlugin = definePlugin({
2853
2876
  const { stack, stackConfig, bootstrap: bootstrap2 } = ctx;
2854
2877
  for (const [id, props] of Object.entries(stackConfig.graphql || {})) {
2855
2878
  const apiId = bootstrap2.import(`graphql-${id}`);
2856
- for (const [typeAndField, functionProps] of Object.entries(props.resolvers || {})) {
2857
- const [typeName, fieldName] = typeAndField.split(/[\s]+/g);
2858
- const entryId = (0, import_change_case12.paramCase)(`${id}-${typeName}-${fieldName}`);
2859
- const lambda = toLambdaFunction(ctx, `graphql-${entryId}`, functionProps);
2860
- const source = new AppsyncEventSource(entryId, lambda, {
2861
- apiId,
2862
- typeName,
2863
- fieldName,
2864
- code: Code2.fromInline(entryId, defaultResolver)
2865
- });
2866
- stack.add(lambda, source);
2879
+ for (const [typeName, fields] of Object.entries(props.resolvers || {})) {
2880
+ for (const [fieldName, functionProps] of Object.entries(fields || {})) {
2881
+ const entryId = (0, import_change_case13.paramCase)(`${id}-${typeName}-${fieldName}`);
2882
+ const lambda = toLambdaFunction(ctx, `graphql-${entryId}`, functionProps);
2883
+ const source = new AppsyncEventSource(entryId, lambda, {
2884
+ apiId,
2885
+ typeName,
2886
+ fieldName,
2887
+ code: Code2.fromInline(entryId, defaultResolver)
2888
+ });
2889
+ stack.add(lambda, source);
2890
+ }
2867
2891
  }
2868
2892
  }
2869
2893
  }
@@ -3539,7 +3563,7 @@ var LoadBalancer = class extends Resource {
3539
3563
  };
3540
3564
 
3541
3565
  // src/formation/resource/elb/listener.ts
3542
- var import_change_case13 = require("change-case");
3566
+ var import_change_case14 = require("change-case");
3543
3567
  var Listener = class extends Resource {
3544
3568
  constructor(logicalId, props) {
3545
3569
  super("AWS::ElasticLoadBalancingV2::Listener", logicalId);
@@ -3555,7 +3579,7 @@ var Listener = class extends Resource {
3555
3579
  return {
3556
3580
  LoadBalancerArn: this.props.loadBalancerArn,
3557
3581
  Port: this.props.port,
3558
- Protocol: (0, import_change_case13.constantCase)(this.props.protocol),
3582
+ Protocol: (0, import_change_case14.constantCase)(this.props.protocol),
3559
3583
  Certificates: this.props.certificates.map((arn) => ({
3560
3584
  CertificateArn: arn
3561
3585
  })),
@@ -3994,6 +4018,7 @@ var SubnetGroup = class extends Resource {
3994
4018
  };
3995
4019
 
3996
4020
  // src/plugins/cache.ts
4021
+ var import_change_case15 = require("change-case");
3997
4022
  var TypeSchema = import_zod19.z.enum([
3998
4023
  "t4g.small",
3999
4024
  "t4g.medium",
@@ -4046,7 +4071,7 @@ var cachePlugin = definePlugin({
4046
4071
  for (const stack of config.stacks) {
4047
4072
  const list3 = new TypeObject();
4048
4073
  for (const name of Object.keys(stack.caches || {})) {
4049
- list3.addType(name, `{ host: string, port:number }`);
4074
+ list3.addType(name, `{ host: string, port: number }`);
4050
4075
  }
4051
4076
  gen.addType(stack.name, list3.toString());
4052
4077
  }
@@ -4079,7 +4104,7 @@ var cachePlugin = definePlugin({
4079
4104
  }).dependsOn(subnetGroup, securityGroup);
4080
4105
  stack.add(subnetGroup, securityGroup, cluster);
4081
4106
  bind((lambda) => {
4082
- lambda.addEnvironment(`CACHE_${stack.name}_${id}_HOST`, cluster.address).addEnvironment(`CACHE_${stack.name}_${id}_PORT`, props.port.toString());
4107
+ lambda.addEnvironment(`CACHE_${(0, import_change_case15.constantCase)(stack.name)}_${(0, import_change_case15.constantCase)(id)}_HOST`, cluster.address).addEnvironment(`CACHE_${(0, import_change_case15.constantCase)(stack.name)}_${(0, import_change_case15.constantCase)(id)}_PORT`, props.port.toString());
4083
4108
  });
4084
4109
  }
4085
4110
  }
@@ -4335,7 +4360,7 @@ var toApp = async (config, filters) => {
4335
4360
  };
4336
4361
 
4337
4362
  // src/config.ts
4338
- var import_path9 = require("path");
4363
+ var import_path11 = require("path");
4339
4364
 
4340
4365
  // src/util/account.ts
4341
4366
  var import_client_sts = require("@aws-sdk/client-sts");
@@ -4406,7 +4431,7 @@ var AppSchema = import_zod23.z.object({
4406
4431
  /** The deployment stage.
4407
4432
  * @default 'prod'
4408
4433
  */
4409
- stage: import_zod23.z.string().regex(/[a-z]+/).default("prod"),
4434
+ stage: import_zod23.z.string().regex(/^[a-z]+$/).default("prod"),
4410
4435
  /** Default properties. */
4411
4436
  defaults: import_zod23.z.object({}).default({}),
4412
4437
  /** The application stacks. */
@@ -4423,7 +4448,7 @@ var import_rollup3 = require("rollup");
4423
4448
  var import_rollup_plugin_swc32 = require("rollup-plugin-swc3");
4424
4449
  var import_rollup_plugin_replace = __toESM(require("rollup-plugin-replace"), 1);
4425
4450
  var import_event_iterator = require("event-iterator");
4426
- var import_path7 = require("path");
4451
+ var import_path9 = require("path");
4427
4452
  var import_promises6 = require("fs/promises");
4428
4453
  var importFile = async (path) => {
4429
4454
  const bundle = await (0, import_rollup3.rollup)({
@@ -4433,17 +4458,17 @@ var importFile = async (path) => {
4433
4458
  },
4434
4459
  plugins: [
4435
4460
  (0, import_rollup_plugin_replace.default)({
4436
- __dirname: (id) => `'${(0, import_path7.dirname)(id)}'`
4461
+ __dirname: (id) => `'${(0, import_path9.dirname)(id)}'`
4437
4462
  }),
4438
4463
  (0, import_rollup_plugin_swc32.swc)({
4439
4464
  minify: false,
4440
4465
  jsc: {
4441
- baseUrl: (0, import_path7.dirname)(path)
4466
+ baseUrl: (0, import_path9.dirname)(path)
4442
4467
  }
4443
4468
  })
4444
4469
  ]
4445
4470
  });
4446
- const outputFile = (0, import_path7.join)(directories.cache, "config.js");
4471
+ const outputFile = (0, import_path9.join)(directories.cache, "config.js");
4447
4472
  const result = await bundle.generate({
4448
4473
  format: "esm",
4449
4474
  exports: "default"
@@ -4467,12 +4492,12 @@ var watchFile = (path) => {
4467
4492
  },
4468
4493
  plugins: [
4469
4494
  (0, import_rollup_plugin_replace.default)({
4470
- __dirname: (id) => `'${(0, import_path7.dirname)(id)}'`
4495
+ __dirname: (id) => `'${(0, import_path9.dirname)(id)}'`
4471
4496
  }),
4472
4497
  (0, import_rollup_plugin_swc32.swc)({
4473
4498
  minify: false,
4474
4499
  jsc: {
4475
- baseUrl: (0, import_path7.dirname)(path)
4500
+ baseUrl: (0, import_path9.dirname)(path)
4476
4501
  }
4477
4502
  })
4478
4503
  ]
@@ -4494,7 +4519,7 @@ var watchFile = (path) => {
4494
4519
  event.result.close();
4495
4520
  const output = result.output[0];
4496
4521
  const code = output.code;
4497
- const outputFile = (0, import_path7.join)(directories.cache, "config.js");
4522
+ const outputFile = (0, import_path9.join)(directories.cache, "config.js");
4498
4523
  await (0, import_promises6.mkdir)(directories.cache, { recursive: true });
4499
4524
  await (0, import_promises6.writeFile)(outputFile, code);
4500
4525
  debug("Save config file:", style.info(outputFile));
@@ -4512,6 +4537,14 @@ var watchFile = (path) => {
4512
4537
  };
4513
4538
 
4514
4539
  // src/config.ts
4540
+ var import_zod24 = require("zod");
4541
+ var ConfigError = class extends Error {
4542
+ constructor(error, data) {
4543
+ super(error.message);
4544
+ this.error = error;
4545
+ this.data = data;
4546
+ }
4547
+ };
4515
4548
  var importConfig = async (options) => {
4516
4549
  debug("Find the root directory");
4517
4550
  const configFile = options.configFile || "awsless.config.ts";
@@ -4519,7 +4552,7 @@ var importConfig = async (options) => {
4519
4552
  setRoot(root2);
4520
4553
  debug("CWD:", style.info(root2));
4521
4554
  debug("Import config file");
4522
- const fileName = (0, import_path9.join)(root2, configFile);
4555
+ const fileName = (0, import_path11.join)(root2, configFile);
4523
4556
  const module2 = await importFile(fileName);
4524
4557
  const appConfig = typeof module2.default === "function" ? await module2.default(options) : module2.default;
4525
4558
  debug("Validate config file");
@@ -4533,7 +4566,15 @@ var importConfig = async (options) => {
4533
4566
  schema2 = schema2.and(plugin.schema);
4534
4567
  }
4535
4568
  }
4536
- const config = await schema2.parseAsync(appConfig);
4569
+ let config;
4570
+ try {
4571
+ config = await schema2.parseAsync(appConfig);
4572
+ } catch (error) {
4573
+ if (error instanceof import_zod24.z.ZodError) {
4574
+ throw new ConfigError(error, appConfig);
4575
+ }
4576
+ throw error;
4577
+ }
4537
4578
  debug("Load credentials", style.info(config.profile));
4538
4579
  const credentials = getCredentials(config.profile);
4539
4580
  debug("Load AWS account ID");
@@ -4552,7 +4593,7 @@ var watchConfig = async function* (options) {
4552
4593
  setRoot(root2);
4553
4594
  debug("CWD:", style.info(root2));
4554
4595
  debug("Import config file");
4555
- const fileName = (0, import_path9.join)(root2, configFile);
4596
+ const fileName = (0, import_path11.join)(root2, configFile);
4556
4597
  for await (const module2 of watchFile(fileName)) {
4557
4598
  const appConfig = typeof module2.default === "function" ? await module2.default(options) : module2.default;
4558
4599
  debug("Validate config file");
@@ -4566,7 +4607,15 @@ var watchConfig = async function* (options) {
4566
4607
  schema2 = schema2.and(plugin.schema);
4567
4608
  }
4568
4609
  }
4569
- const config = await schema2.parseAsync(appConfig);
4610
+ let config;
4611
+ try {
4612
+ config = await schema2.parseAsync(appConfig);
4613
+ } catch (error) {
4614
+ if (error instanceof import_zod24.z.ZodError) {
4615
+ throw new ConfigError(error, appConfig);
4616
+ }
4617
+ throw error;
4618
+ }
4570
4619
  debug("Load credentials", style.info(config.profile));
4571
4620
  const credentials = getCredentials(config.profile);
4572
4621
  debug("Load AWS account ID");
@@ -4691,11 +4740,11 @@ var dialog = (type, lines) => {
4691
4740
  const padding = 3;
4692
4741
  const icon = style[type](symbol[type].padEnd(padding));
4693
4742
  return (term) => {
4694
- term.out.write(lines.map((line, i) => {
4743
+ term.out.write(lines.map((line2, i) => {
4695
4744
  if (i === 0) {
4696
- return icon + (0, import_wrap_ansi.default)(line, term.out.width(), { hard: true });
4745
+ return icon + (0, import_wrap_ansi.default)(line2, term.out.width(), { hard: true });
4697
4746
  }
4698
- return (0, import_wrap_ansi.default)(" ".repeat(padding) + line, term.out.width(), { hard: true });
4747
+ return (0, import_wrap_ansi.default)(" ".repeat(padding) + line2, term.out.width(), { hard: true });
4699
4748
  }).join(br()) + br());
4700
4749
  };
4701
4750
  };
@@ -5018,6 +5067,86 @@ var logs = () => {
5018
5067
  };
5019
5068
  };
5020
5069
 
5070
+ // src/cli/ui/layout/zod-error.ts
5071
+ var line = (value, level = 0, highlight = false) => {
5072
+ return [
5073
+ highlight ? style.error(symbol.pointerSmall) + style.placeholder(" | ") : style.placeholder.dim(" | "),
5074
+ " ".repeat(level),
5075
+ value,
5076
+ br()
5077
+ ];
5078
+ };
5079
+ var format = (value) => {
5080
+ if (Array.isArray(value)) {
5081
+ return "[ ... ]";
5082
+ }
5083
+ if (value === null) {
5084
+ return "null";
5085
+ }
5086
+ switch (typeof value) {
5087
+ case "function":
5088
+ return "() => { ... }";
5089
+ case "bigint":
5090
+ return `${value}n`;
5091
+ case "symbol":
5092
+ return "Symbol()";
5093
+ case "object":
5094
+ return "{ ... }";
5095
+ case "undefined":
5096
+ case "string":
5097
+ case "number":
5098
+ case "boolean":
5099
+ return JSON.stringify(value);
5100
+ }
5101
+ return "";
5102
+ };
5103
+ var zodError = (error, data) => {
5104
+ return (term) => {
5105
+ for (const issue of error.issues) {
5106
+ term.out.gap();
5107
+ term.out.write(dialog("error", [
5108
+ style.error(issue.message)
5109
+ ]));
5110
+ term.out.gap();
5111
+ term.out.write(line("{"));
5112
+ let context = data;
5113
+ const inStack = issue.path[0] === "stacks" && typeof issue.path[1] === "number";
5114
+ const length2 = issue.path.length;
5115
+ const end = [];
5116
+ issue.path.forEach((path, i) => {
5117
+ const index = i + 1;
5118
+ context = context[path];
5119
+ if (typeof path === "string") {
5120
+ const key = path + `: `;
5121
+ if (index === length2) {
5122
+ const space = " ".repeat(key.length);
5123
+ const value = format(context);
5124
+ const error2 = "^".repeat(value.length);
5125
+ term.out.write(line(key + style.warning(value), index));
5126
+ term.out.write(line(space + style.error(error2), index, true));
5127
+ } else if (Array.isArray(context)) {
5128
+ term.out.write(line(key + "[", index));
5129
+ end.unshift(line("]", index));
5130
+ } else if (typeof context === "object") {
5131
+ if (inStack && index === 3) {
5132
+ const name = data.stacks[issue.path[1]].name;
5133
+ term.out.write(line("name: " + style.info(`"${name}"`) + ",", index));
5134
+ }
5135
+ term.out.write(line(key + "{", index));
5136
+ end.unshift(line("}", index));
5137
+ }
5138
+ } else if (typeof context === "object") {
5139
+ term.out.write(line("{", index));
5140
+ end.unshift(line("}", index));
5141
+ }
5142
+ });
5143
+ term.out.write(end);
5144
+ term.out.write(line("}"));
5145
+ term.out.gap();
5146
+ }
5147
+ };
5148
+ };
5149
+
5021
5150
  // src/cli/ui/layout/layout.ts
5022
5151
  var layout = async (cb) => {
5023
5152
  const term = createTerminal();
@@ -5033,7 +5162,9 @@ var layout = async (cb) => {
5033
5162
  await cb(config, term.out.write.bind(term.out), term);
5034
5163
  } catch (error) {
5035
5164
  term.out.gap();
5036
- if (error instanceof Error) {
5165
+ if (error instanceof ConfigError) {
5166
+ term.out.write(zodError(error.error, error.data));
5167
+ } else if (error instanceof Error) {
5037
5168
  term.out.write(dialog("error", [error.message]));
5038
5169
  } else if (typeof error === "string") {
5039
5170
  term.out.write(dialog("error", [error]));
@@ -5077,7 +5208,7 @@ var flexLine = (term, left, right, reserveSpace = 0) => {
5077
5208
  };
5078
5209
 
5079
5210
  // src/cli/ui/complex/builder.ts
5080
- var import_path12 = require("path");
5211
+ var import_path14 = require("path");
5081
5212
  var assetBuilder = (app) => {
5082
5213
  return async (term) => {
5083
5214
  const assets = [];
@@ -5111,7 +5242,7 @@ var assetBuilder = (app) => {
5111
5242
  }
5112
5243
  const [icon, stop] = createSpinner();
5113
5244
  const details = new Signal({});
5114
- const line = flexLine(term, [
5245
+ const line2 = flexLine(term, [
5115
5246
  icon,
5116
5247
  " ",
5117
5248
  style.label(stack.name),
@@ -5135,13 +5266,13 @@ var assetBuilder = (app) => {
5135
5266
  }),
5136
5267
  br()
5137
5268
  ]);
5138
- group.update((group2) => [...group2, line]);
5269
+ group.update((group2) => [...group2, line2]);
5139
5270
  const timer = createTimer();
5140
5271
  try {
5141
5272
  const data = await asset.build({
5142
5273
  async write(file, data2) {
5143
- const fullpath = (0, import_path12.join)(directories.asset, asset.type, app.name, stack.name, asset.id, file);
5144
- const basepath = (0, import_path12.dirname)(fullpath);
5274
+ const fullpath = (0, import_path14.join)(directories.asset, asset.type, app.name, stack.name, asset.id, file);
5275
+ const basepath = (0, import_path14.dirname)(fullpath);
5145
5276
  await (0, import_promises7.mkdir)(basepath, { recursive: true });
5146
5277
  await (0, import_promises7.writeFile)(fullpath, data2);
5147
5278
  }
@@ -5188,14 +5319,14 @@ var cleanUp = async () => {
5188
5319
 
5189
5320
  // src/cli/ui/complex/template.ts
5190
5321
  var import_promises9 = require("fs/promises");
5191
- var import_path15 = require("path");
5322
+ var import_path17 = require("path");
5192
5323
  var templateBuilder = (app) => {
5193
5324
  return async (term) => {
5194
5325
  const done = term.out.write(loadingDialog("Building stack templates..."));
5195
5326
  await Promise.all(app.stacks.map(async (stack) => {
5196
5327
  const template = stack.toString(true);
5197
- const path = (0, import_path15.join)(directories.template, app.name);
5198
- const file = (0, import_path15.join)(path, `${stack.name}.json`);
5328
+ const path = (0, import_path17.join)(directories.template, app.name);
5329
+ const file = (0, import_path17.join)(path, `${stack.name}.json`);
5199
5330
  await (0, import_promises9.mkdir)(path, { recursive: true });
5200
5331
  await (0, import_promises9.writeFile)(file, template);
5201
5332
  }));
@@ -5255,7 +5386,7 @@ var shouldDeployBootstrap = async (client, stack) => {
5255
5386
  // src/formation/client.ts
5256
5387
  var import_client_cloudformation = require("@aws-sdk/client-cloudformation");
5257
5388
  var import_client_s3 = require("@aws-sdk/client-s3");
5258
- var import_change_case14 = require("change-case");
5389
+ var import_change_case16 = require("change-case");
5259
5390
  var StackClient = class {
5260
5391
  constructor(app, account, region, credentials) {
5261
5392
  this.app = app;
@@ -5288,7 +5419,7 @@ var StackClient = class {
5288
5419
  };
5289
5420
  }
5290
5421
  stackName(stackName) {
5291
- return (0, import_change_case14.paramCase)(`${this.app.name}-${stackName}`);
5422
+ return (0, import_change_case16.paramCase)(`${this.app.name}-${stackName}`);
5292
5423
  }
5293
5424
  tags(stack) {
5294
5425
  const tags = [];
@@ -5606,13 +5737,13 @@ var bootstrap = (program2) => {
5606
5737
 
5607
5738
  // src/cli/ui/complex/deployer.ts
5608
5739
  var stacksDeployer = (deploymentLine) => {
5609
- const stackNames = deploymentLine.map((line) => line.map((stack) => stack.name)).flat();
5740
+ const stackNames = deploymentLine.map((line2) => line2.map((stack) => stack.name)).flat();
5610
5741
  const stackNameSize = Math.max(...stackNames.map((name) => name.length));
5611
5742
  return (term) => {
5612
5743
  const ui = {};
5613
5744
  term.out.gap();
5614
5745
  for (const i in deploymentLine) {
5615
- const line = flexLine(
5746
+ const line2 = flexLine(
5616
5747
  term,
5617
5748
  [" "],
5618
5749
  [
@@ -5621,7 +5752,7 @@ var stacksDeployer = (deploymentLine) => {
5621
5752
  style.placeholder(" \u2500\u2500")
5622
5753
  ]
5623
5754
  );
5624
- term.out.write(line);
5755
+ term.out.write(line2);
5625
5756
  term.out.write(br());
5626
5757
  for (const stack of deploymentLine[i]) {
5627
5758
  const icon = new Signal(" ");
@@ -5716,7 +5847,7 @@ var status = (program2) => {
5716
5847
 
5717
5848
  // src/cli/ui/complex/publisher.ts
5718
5849
  var import_promises10 = require("fs/promises");
5719
- var import_path17 = require("path");
5850
+ var import_path19 = require("path");
5720
5851
  var import_client_s32 = require("@aws-sdk/client-s3");
5721
5852
  var assetPublisher = (config, app) => {
5722
5853
  const client = new import_client_s32.S3Client({
@@ -5729,7 +5860,7 @@ var assetPublisher = (config, app) => {
5729
5860
  await Promise.all([...stack.assets].map(async (asset) => {
5730
5861
  await asset.publish?.({
5731
5862
  async read(file) {
5732
- const path = (0, import_path17.join)(directories.asset, asset.type, app.name, stack.name, asset.id, file);
5863
+ const path = (0, import_path19.join)(directories.asset, asset.type, app.name, stack.name, asset.id, file);
5733
5864
  const data = await (0, import_promises10.readFile)(path);
5734
5865
  return data;
5735
5866
  },
@@ -5801,8 +5932,8 @@ var deploy = (program2) => {
5801
5932
  const doneDeploying = write(loadingDialog("Deploying stacks to AWS..."));
5802
5933
  const client = new StackClient(app, config.account, config.region, config.credentials);
5803
5934
  const ui = write(stacksDeployer(deploymentLine));
5804
- for (const line of deploymentLine) {
5805
- const results = await Promise.allSettled(line.map(async (stack) => {
5935
+ for (const line2 of deploymentLine) {
5936
+ const results = await Promise.allSettled(line2.map(async (stack) => {
5806
5937
  const item = ui[stack.name];
5807
5938
  item.start("deploying");
5808
5939
  try {