@awsless/cli 0.0.46-next.0 → 0.0.46-next.10

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.
@@ -237,7 +237,7 @@ var CodeSchema = z10.union([
237
237
  var FnSchema = z10.object({
238
238
  code: CodeSchema,
239
239
  handler: HandlerSchema.optional()
240
- });
240
+ }).strict();
241
241
  var FunctionSchema = z10.union([
242
242
  LocalFileSchema.transform((code) => ({
243
243
  code
@@ -483,6 +483,70 @@ var InstanceDefaultSchema = z15.object({
483
483
  // src/feature/router/schema.ts
484
484
  import { days as days3, minutes as minutes2, parse as parse3 } from "@awsless/duration";
485
485
  import { z as z16 } from "zod";
486
+
487
+ // src/error.ts
488
+ var ExpectedError = class extends Error {
489
+ };
490
+
491
+ // src/feature/router/pattern.ts
492
+ var PARAM_TOKEN = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}|\*/g;
493
+ var escapeRegex = (value) => {
494
+ return value.replace(/[|\\{}()[\]^$+*?.\-]/g, "\\$&");
495
+ };
496
+ var compileRoutePattern = (pattern) => {
497
+ if (!pattern.startsWith("/")) {
498
+ throw new ExpectedError(`Route pattern "${pattern}" must start with a slash (/)`);
499
+ }
500
+ if (pattern === "/*") {
501
+ return { key: pattern };
502
+ }
503
+ const params = [];
504
+ let regex = "";
505
+ let stars = 0;
506
+ let last = 0;
507
+ let token;
508
+ PARAM_TOKEN.lastIndex = 0;
509
+ while (token = PARAM_TOKEN.exec(pattern)) {
510
+ regex += escapeRegex(pattern.slice(last, token.index));
511
+ const param = token[1];
512
+ if (param) {
513
+ if (params.includes(param)) {
514
+ throw new ExpectedError(`Duplicate param "${param}" in route pattern "${pattern}"`);
515
+ }
516
+ params.push(param);
517
+ regex += "([^/]+)";
518
+ } else {
519
+ stars++;
520
+ regex += ".*";
521
+ }
522
+ last = PARAM_TOKEN.lastIndex;
523
+ }
524
+ if (params.length === 0 && stars === 0) {
525
+ return { key: pattern };
526
+ }
527
+ regex += escapeRegex(pattern.slice(last));
528
+ const root = pattern.split("/")[1] ?? "";
529
+ if (root === "" || root.includes("*") || root.includes("{")) {
530
+ throw new ExpectedError(
531
+ `The first path segment of route pattern "${pattern}" must be static when the pattern contains params or wildcards.`
532
+ );
533
+ }
534
+ if (root.includes(".")) {
535
+ throw new ExpectedError(
536
+ `The first path segment of route pattern "${pattern}" can't contain a dot when the pattern contains params or wildcards.`
537
+ );
538
+ }
539
+ if (params.length === 0 && pattern === `/${root}/*`) {
540
+ return { key: pattern };
541
+ }
542
+ return {
543
+ key: `/${root}/*`,
544
+ match: `^${regex}$`,
545
+ params: params.length > 0 ? params : void 0
546
+ };
547
+ };
548
+
549
+ // src/feature/router/schema.ts
486
550
  var ErrorResponsePathSchema = z16.string().describe(
487
551
  [
488
552
  "The path to the custom error page that you want to return to the viewer when your origin returns the HTTP status code specified.",
@@ -511,7 +575,29 @@ var ErrorResponseSchema = z16.union([
511
575
  minTTL: MinTTLSchema.optional()
512
576
  })
513
577
  ]).optional();
514
- var RouteSchema = z16.string().regex(/^\//, "Route must start with a slash (/)");
578
+ var RouteSchema = z16.string().regex(/^\//, "Route must start with a slash (/)").regex(/^\/([^/*.]+)?$/, 'Router paths mount a single segment without dots, like "/api".');
579
+ var RoutesSchema = z16.record(
580
+ ResourceIdSchema.describe("The router id to add your routes to."),
581
+ z16.record(z16.string().regex(/^\//, "Route must start with a slash (/)"), FunctionSchema).superRefine((routes, ctx) => {
582
+ for (const pattern of Object.keys(routes)) {
583
+ try {
584
+ compileRoutePattern(pattern);
585
+ } catch (error) {
586
+ ctx.addIssue({
587
+ code: z16.ZodIssueCode.custom,
588
+ path: [pattern],
589
+ message: error instanceof Error ? error.message : `Invalid route pattern: ${pattern}`
590
+ });
591
+ }
592
+ }
593
+ }).describe(
594
+ [
595
+ "Define the routes and the lambda function that should handle them.",
596
+ 'Routes can be an exact path like "/sitemap.xml", a wildcard like "/sitemap/*", or contain params like "/sitemap/{locale}/{page}.xml".',
597
+ 'Param values are passed to the function as "x-param-[NAME]" request headers.'
598
+ ].join("\n")
599
+ )
600
+ ).optional().describe("Add routes to your global Router that link a path pattern to a lambda function.");
515
601
  var VisibilitySchema = z16.boolean().default(false).describe("Whether to enable CloudWatch metrics for the WAF rule.");
516
602
  var WafSettingsSchema = z16.object({
517
603
  rateLimiter: z16.object({
@@ -560,6 +646,7 @@ var RouterDefaultSchema = z16.record(
560
646
  z16.object({
561
647
  domain: ResourceIdSchema.describe("The domain id to link your Router.").optional(),
562
648
  subDomain: z16.string().optional(),
649
+ redirectWww: z16.boolean().default(false).describe("Redirect all www subdomain requests to your root domain."),
563
650
  waf: WafSettingsSchema.optional(),
564
651
  geoRestrictions: z16.array(z16.string().length(2).toUpperCase()).default([]).describe("Specifies a blacklist of countries that should be blocked."),
565
652
  errors: z16.object({
@@ -648,6 +735,21 @@ var RouterDefaultSchema = z16.record(
648
735
  }).optional().describe(
649
736
  "Specifies the cookies, headers, and query values that CloudFront includes in the cache key."
650
737
  )
738
+ }).superRefine((props, ctx) => {
739
+ if (props.redirectWww && !props.domain) {
740
+ ctx.addIssue({
741
+ code: z16.ZodIssueCode.custom,
742
+ path: ["redirectWww"],
743
+ message: "The redirectWww option requires a domain to be set."
744
+ });
745
+ }
746
+ if (props.redirectWww && props.subDomain) {
747
+ ctx.addIssue({
748
+ code: z16.ZodIssueCode.custom,
749
+ path: ["redirectWww"],
750
+ message: `The redirectWww option can't be combined with a subDomain, because the domain certificate only covers single level subdomains.`
751
+ });
752
+ }
651
753
  })
652
754
  ).optional().describe(`Define the global Router. Backed by AWS CloudFront.`);
653
755
 
@@ -993,8 +1095,8 @@ var AppSchema = z25.object({
993
1095
  layers: LayerSchema,
994
1096
  router: RouterDefaultSchema
995
1097
  // dataRetention: z.boolean().describe('Configure how your resources are handled on delete.').default(false),
996
- }).default({}).describe("Default properties")
997
- });
1098
+ }).strict().default({}).describe("Default properties")
1099
+ }).strict();
998
1100
 
999
1101
  // src/config/stack.ts
1000
1102
  import { z as z41 } from "zod";
@@ -1050,7 +1152,7 @@ var CommandsSchema = z27.record(ResourceIdSchema, CommandSchema).optional().desc
1050
1152
 
1051
1153
  // src/feature/config/schema.ts
1052
1154
  import { z as z28 } from "zod";
1053
- var ConfigNameSchema = z28.string().regex(/[a-z0-9\-]/g, "Invalid config name");
1155
+ var ConfigNameSchema = z28.string().regex(/^[a-z0-9-]+$/, "Invalid config name");
1054
1156
  var ConfigsSchema = z28.array(ConfigNameSchema).optional().describe("Define the config values for your stack.");
1055
1157
 
1056
1158
  // src/feature/cron/schema/index.ts
@@ -1603,14 +1705,13 @@ var TestsSchema = z40.union([
1603
1705
  ]).describe("Define the location of your tests for your stack.").optional();
1604
1706
 
1605
1707
  // src/config/stack.ts
1606
- var DependsSchema = ResourceIdSchema.array().optional().describe("Define the stacks that this stack is depended on.");
1607
1708
  var NameSchema = ResourceIdSchema.refine((name) => !["base", "hostedzones"].includes(name), {
1608
1709
  message: `Stack name can't be a reserved name.`
1609
1710
  }).describe("Stack name.");
1610
1711
  var StackSchema = z41.object({
1611
1712
  $schema: z41.string().optional(),
1612
1713
  name: NameSchema,
1613
- depends: DependsSchema,
1714
+ routes: RoutesSchema,
1614
1715
  commands: CommandsSchema,
1615
1716
  // auth: AuthSchema,
1616
1717
  // http: HttpSchema,
@@ -1636,7 +1737,7 @@ var StackSchema = z41.object({
1636
1737
  images: ImagesSchema,
1637
1738
  icons: IconsSchema,
1638
1739
  metrics: MetricsSchema
1639
- });
1740
+ }).strict();
1640
1741
 
1641
1742
  // src/config/stage-patch-json-schema.ts
1642
1743
  var clone = (value) => {
@@ -3,128 +3,6 @@ import { patch, unpatch } from "@awsless/json";
3
3
  import { ExpectedError, invoke, isErrorResponse } from "@awsless/lambda";
4
4
  import { formatRoutePayload, getCurrentRoute, withRoute } from "awsless";
5
5
 
6
- // src/feature/bundle/server/preview.ts
7
- import { GetObjectCommand, NoSuchKey } from "@aws-sdk/client-s3";
8
- import { s3Client } from "@awsless/s3";
9
- var getPossibleRouteKeys = (path) => {
10
- if (path === "" || path === "/") {
11
- return ["/", "/*"];
12
- }
13
- const parts = path.split("/");
14
- const root = path.startsWith("/") ? parts[1] : parts[0];
15
- const file = parts[parts.length - 1].includes(".");
16
- if (root.includes(".")) {
17
- return [path, "/*.", "/*"];
18
- }
19
- if (file) {
20
- return [path, "/" + root + "/*.", "/" + root + "/*", "/*.", "/*"];
21
- }
22
- return [path, "/" + root + "/*", "/*"];
23
- };
24
- var findRoute = (props, path, method) => {
25
- for (const key of getPossibleRouteKeys(path)) {
26
- const route = props.routes[`${props.router}:${key}`];
27
- if (!route) {
28
- continue;
29
- }
30
- if (route.type === "s3" && method !== "GET" && method !== "HEAD") {
31
- continue;
32
- }
33
- return route;
34
- }
35
- return;
36
- };
37
- var rewritePath = (route, path) => {
38
- if (!route.rewrite) {
39
- return path;
40
- }
41
- if (route.rewrite.regex) {
42
- return path.replace(new RegExp(route.rewrite.regex), route.rewrite.to);
43
- }
44
- return route.rewrite.to;
45
- };
46
- var serveObject = async (route, path, method) => {
47
- const bucket = route.domainName.split(".s3")[0];
48
- const key = rewritePath(route, path).replace(/^\//, "");
49
- let result;
50
- try {
51
- result = await s3Client().send(new GetObjectCommand({
52
- Bucket: bucket,
53
- Key: key
54
- }));
55
- } catch (error) {
56
- if (error instanceof NoSuchKey) {
57
- return {
58
- statusCode: 404
59
- };
60
- }
61
- throw error;
62
- }
63
- const headers = {};
64
- if (result.ContentType) {
65
- headers["content-type"] = result.ContentType;
66
- }
67
- if (result.CacheControl) {
68
- headers["cache-control"] = result.CacheControl;
69
- }
70
- if (result.ETag) {
71
- headers["etag"] = result.ETag;
72
- }
73
- if (method === "HEAD") {
74
- return { statusCode: 200, headers };
75
- }
76
- return {
77
- statusCode: 200,
78
- headers,
79
- body: await result.Body.transformToString("base64"),
80
- isBase64Encoded: true
81
- };
82
- };
83
- var createPreviewHandler = (props) => {
84
- return async (event) => {
85
- const method = event.requestContext.http.method;
86
- const headers = event.headers ?? {};
87
- let path = event.rawPath;
88
- try {
89
- path = decodeURIComponent(path);
90
- } catch {}
91
- if (method === "OPTIONS") {
92
- return {
93
- statusCode: 204,
94
- headers: {
95
- "access-control-allow-origin": "*",
96
- "access-control-allow-methods": "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS",
97
- "access-control-allow-headers": "*",
98
- "access-control-max-age": "86400"
99
- }
100
- };
101
- }
102
- const route = findRoute(props, path, method);
103
- if (!route) {
104
- return {
105
- statusCode: 404
106
- };
107
- }
108
- if (route.type === "s3") {
109
- return serveObject(route, path, method);
110
- }
111
- for (const [name, value] of Object.entries(route.requestHeaders ?? {})) {
112
- headers[name] = value;
113
- }
114
- if (headers.authorization) {
115
- headers["x-awsless-authorization"] = headers.authorization;
116
- } else {
117
- delete headers["x-awsless-authorization"];
118
- }
119
- if (route.forwardHost && headers.host) {
120
- headers["x-forwarded-host"] = headers.host;
121
- }
122
- event.headers = headers;
123
- event.rawPath = rewritePath(route, path);
124
- return props.dispatch(event);
125
- };
126
- };
127
-
128
6
  // src/feature/bundle/server/resource/util.ts
129
7
  var asyncRoute = (key, payload) => {
130
8
  process.env.THROW_EXPECTED_ERRORS = "1";
@@ -434,7 +312,6 @@ var topicHandler = (event, routes) => {
434
312
  // src/feature/bundle/server/handle.ts
435
313
  var createBundle = (handlers) => {
436
314
  const routes = Object.keys(handlers);
437
- let previewConfig;
438
315
  const matchers = [
439
316
  functionHandler,
440
317
  cronHandler,
@@ -473,7 +350,8 @@ var createBundle = (handlers) => {
473
350
  process.env.AWSLESS_ROUTE = match2.key;
474
351
  return withRoute(match2.key, invokeRoute, async () => {
475
352
  const handle = await load();
476
- return handle(match2.payload ?? {}, context);
353
+ const routedContext = { ...context, route: match2.key };
354
+ return handle(match2.payload ?? {}, routedContext);
477
355
  });
478
356
  };
479
357
  const invokeRoute = async (key, payload) => {
@@ -502,20 +380,6 @@ var createBundle = (handlers) => {
502
380
  }
503
381
  return response;
504
382
  };
505
- const raw = event;
506
- if (process.env.AWSLESS_PREVIEW && raw?.requestContext?.http && !event?.headers?.["x-awsless-route"]) {
507
- previewConfig ??= JSON.parse(process.env.AWSLESS_PREVIEW);
508
- return createPreviewHandler({
509
- ...previewConfig,
510
- dispatch: async (event2) => {
511
- const match2 = matchRoute(event2);
512
- if (Array.isArray(match2)) {
513
- throw new Error("Unknown bundle route");
514
- }
515
- return handleRoute(match2);
516
- }
517
- })(event);
518
- }
519
383
  const match = matchRoute(event);
520
384
  if (Array.isArray(match)) {
521
385
  const name = `${process.env.AWS_LAMBDA_FUNCTION_NAME}:${process.env.AWS_LAMBDA_FUNCTION_VERSION}`;
@@ -1,8 +1,81 @@
1
1
  // src/feature/on-failure/server/handle.ts
2
- import { parse, patch } from "@awsless/json";
2
+ import { parse as parse2, patch } from "@awsless/json";
3
3
  import { invoke } from "@awsless/lambda";
4
4
  import { deleteObject, getObject } from "@awsless/s3";
5
5
  import { formatRoutePayload, getRouteEnv } from "awsless";
6
+
7
+ // src/feature/on-failure/server/util.ts
8
+ import { parse } from "@awsless/json";
9
+ var isDynamoDBFailureEvent = (event) => {
10
+ return "DDBStreamBatchInfo" in event;
11
+ };
12
+ var logicalResourceName = (physical) => {
13
+ const segments = physical.replace(/\.fifo$/, "").split("--");
14
+ if (segments.length === 4) {
15
+ return `${segments[1]}:${segments[2]}:${segments[3]}`;
16
+ }
17
+ if (segments.length === 3) {
18
+ return `${segments[1]}:${segments[2]}`;
19
+ }
20
+ return physical;
21
+ };
22
+ var getFailureSource = (payload) => {
23
+ const record = getDeliveryRecord(payload);
24
+ if (!record) {
25
+ return;
26
+ }
27
+ if (isTopicRecord(record)) {
28
+ return {
29
+ resource: record.Sns.TopicArn ? logicalResourceName(lastArnSegment(record.Sns.TopicArn)) : "topic",
30
+ event: parseEvent(record.Sns.Message)
31
+ };
32
+ }
33
+ if (isStreamRecord(record)) {
34
+ const table = record.eventSourceARN.split("/")[1];
35
+ return {
36
+ resource: table ? logicalResourceName(table) : "table-stream"
37
+ };
38
+ }
39
+ if (isQueueRecord(record)) {
40
+ return {
41
+ resource: logicalResourceName(lastArnSegment(record.eventSourceARN)),
42
+ event: parseEvent(record.body)
43
+ };
44
+ }
45
+ return;
46
+ };
47
+ var getDeliveryRecord = (payload) => {
48
+ if (!payload || typeof payload !== "object") {
49
+ return;
50
+ }
51
+ const records = payload.Records;
52
+ const record = Array.isArray(records) ? records[0] : undefined;
53
+ return record && typeof record === "object" ? record : undefined;
54
+ };
55
+ var isTopicRecord = (record) => {
56
+ return "Sns" in record && typeof record.Sns === "object" && record.Sns !== null;
57
+ };
58
+ var isStreamRecord = (record) => {
59
+ return "eventSource" in record && record.eventSource === "aws:dynamodb" && "eventSourceARN" in record;
60
+ };
61
+ var isQueueRecord = (record) => {
62
+ return "eventSource" in record && record.eventSource === "aws:sqs" && "eventSourceARN" in record;
63
+ };
64
+ var lastArnSegment = (arn) => {
65
+ return arn.split(":").at(-1);
66
+ };
67
+ var parseEvent = (value) => {
68
+ if (typeof value !== "string") {
69
+ return value;
70
+ }
71
+ try {
72
+ return parse(value);
73
+ } catch {
74
+ return value;
75
+ }
76
+ };
77
+
78
+ // src/feature/on-failure/server/handle.ts
6
79
  var handle_default = async (event, context) => {
7
80
  if (!Array.isArray(event.Records)) {
8
81
  throw new TypeError(`Unknown Event Type: ${JSON.stringify(event)}`);
@@ -28,13 +101,16 @@ var sqsRecord = async (record, context) => {
28
101
  await Promise.all(s3Records.map((record2) => s3Record(record2, context)));
29
102
  return;
30
103
  }
104
+ const queueName = record.messageAttributes.queueName?.stringValue;
105
+ const body = parsePayload(record.body);
31
106
  const payload = {
32
107
  type: "queue",
33
108
  id: record.messageId,
34
109
  date: new Date(Number(record.attributes.SentTimestamp)),
35
- payload: parsePayload(record.body),
110
+ payload: body,
111
+ source: queueName ? { resource: logicalResourceName(queueName), event: body } : undefined,
36
112
  queue: {
37
- name: record.messageAttributes.queueName?.stringValue,
113
+ name: queueName,
38
114
  url: record.messageAttributes.queueUrl?.stringValue
39
115
  }
40
116
  };
@@ -70,9 +146,6 @@ var s3Record = async (record, context) => {
70
146
  key
71
147
  });
72
148
  };
73
- var isDynamoDBFailureEvent = (event) => {
74
- return "DDBStreamBatchInfo" in event;
75
- };
76
149
  var formatUnknownFailureEvent = (event) => {
77
150
  if (isDynamoDBFailureEvent(event)) {
78
151
  return formatDynamoDBStreamFailureEvent(event);
@@ -90,6 +163,7 @@ var formatAsyncLambdaFailureEvent = (event) => {
90
163
  name: typeof route === "string" ? route : event.requestContext.functionArn.split(":")[6]
91
164
  },
92
165
  payload: typeof route === "string" ? payload.event ?? {} : payload,
166
+ source: typeof route === "string" ? { resource: route, event: payload.event ?? {} } : getFailureSource(payload),
93
167
  error: {
94
168
  type: event.responsePayload.errorType,
95
169
  message: event.responsePayload.errorMessage,
@@ -98,6 +172,9 @@ var formatAsyncLambdaFailureEvent = (event) => {
98
172
  };
99
173
  };
100
174
  var formatDynamoDBStreamFailureEvent = (event) => {
175
+ const payload = parsePayload(event.payload);
176
+ const streamArn = event.DDBStreamBatchInfo?.streamArn;
177
+ const table = streamArn?.split("/")[1];
101
178
  return {
102
179
  type: "dynamodb-stream",
103
180
  date: new Date(event.timestamp),
@@ -105,12 +182,13 @@ var formatDynamoDBStreamFailureEvent = (event) => {
105
182
  function: {
106
183
  name: event.requestContext.functionArn.split(":")[6]
107
184
  },
108
- payload: parsePayload(event.payload)
185
+ payload,
186
+ source: table ? { resource: logicalResourceName(table) } : getFailureSource(payload)
109
187
  };
110
188
  };
111
189
  var parsePayload = (payload) => {
112
190
  try {
113
- return parse(payload);
191
+ return parse2(payload);
114
192
  } catch {
115
193
  return payload;
116
194
  }