@awsless/cli 0.0.41 → 0.0.43

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
@@ -1205,7 +1205,9 @@ var LayersSchema = z15.string().array().describe(
1205
1205
  var FileCodeSchema = z15.object({
1206
1206
  file: LocalFileSchema.describe("The file path of the function code."),
1207
1207
  minify: MinifySchema.optional().default(true),
1208
- moduleSideEffects: z15.boolean().default(false).describe(`Will flag all modules as having potential side effects.`),
1208
+ moduleSideEffects: RelativePathSchema.array().default([]).describe(
1209
+ `A list of glob patterns for modules that should be flagged as having potential side effects. For example "./.svelte-kit/**" will flag every file inside the .svelte-kit folder.`
1210
+ ),
1209
1211
  external: z15.string().array().optional().describe(`A list of external packages that won't be included in the bundle.`),
1210
1212
  importAsString: z15.string().array().optional().describe(`A list of glob patterns, which specifies the files that should be imported as string.`)
1211
1213
  });
@@ -1505,6 +1507,66 @@ var InstanceDefaultSchema = z20.object({
1505
1507
  // src/feature/router/schema.ts
1506
1508
  import { days as days3, minutes as minutes2, parse as parse3 } from "@awsless/duration";
1507
1509
  import { z as z21 } from "zod";
1510
+
1511
+ // src/feature/router/pattern.ts
1512
+ var PARAM_TOKEN = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}|\*/g;
1513
+ var escapeRegex = (value) => {
1514
+ return value.replace(/[|\\{}()[\]^$+*?.\-]/g, "\\$&");
1515
+ };
1516
+ var compileRoutePattern = (pattern) => {
1517
+ if (!pattern.startsWith("/")) {
1518
+ throw new ExpectedError(`Route pattern "${pattern}" must start with a slash (/)`);
1519
+ }
1520
+ if (pattern === "/*") {
1521
+ return { key: pattern };
1522
+ }
1523
+ const params = [];
1524
+ let regex = "";
1525
+ let stars = 0;
1526
+ let last = 0;
1527
+ let token;
1528
+ PARAM_TOKEN.lastIndex = 0;
1529
+ while (token = PARAM_TOKEN.exec(pattern)) {
1530
+ regex += escapeRegex(pattern.slice(last, token.index));
1531
+ const param = token[1];
1532
+ if (param) {
1533
+ if (params.includes(param)) {
1534
+ throw new ExpectedError(`Duplicate param "${param}" in route pattern "${pattern}"`);
1535
+ }
1536
+ params.push(param);
1537
+ regex += "([^/]+)";
1538
+ } else {
1539
+ stars++;
1540
+ regex += ".*";
1541
+ }
1542
+ last = PARAM_TOKEN.lastIndex;
1543
+ }
1544
+ if (params.length === 0 && stars === 0) {
1545
+ return { key: pattern };
1546
+ }
1547
+ regex += escapeRegex(pattern.slice(last));
1548
+ const root2 = pattern.split("/")[1] ?? "";
1549
+ if (root2 === "" || root2.includes("*") || root2.includes("{")) {
1550
+ throw new ExpectedError(
1551
+ `The first path segment of route pattern "${pattern}" must be static when the pattern contains params or wildcards.`
1552
+ );
1553
+ }
1554
+ if (root2.includes(".")) {
1555
+ throw new ExpectedError(
1556
+ `The first path segment of route pattern "${pattern}" can't contain a dot when the pattern contains params or wildcards.`
1557
+ );
1558
+ }
1559
+ if (params.length === 0 && pattern === `/${root2}/*`) {
1560
+ return { key: pattern };
1561
+ }
1562
+ return {
1563
+ key: `/${root2}/*`,
1564
+ match: `^${regex}$`,
1565
+ params: params.length > 0 ? params : void 0
1566
+ };
1567
+ };
1568
+
1569
+ // src/feature/router/schema.ts
1508
1570
  var ErrorResponsePathSchema = z21.string().describe(
1509
1571
  [
1510
1572
  "The path to the custom error page that you want to return to the viewer when your origin returns the HTTP status code specified.",
@@ -1534,6 +1596,28 @@ var ErrorResponseSchema = z21.union([
1534
1596
  })
1535
1597
  ]).optional();
1536
1598
  var RouteSchema = z21.string().regex(/^\//, "Route must start with a slash (/)");
1599
+ var RoutesSchema = z21.record(
1600
+ ResourceIdSchema.describe("The router id to add your routes to."),
1601
+ z21.record(RouteSchema, FunctionSchema).superRefine((routes, ctx) => {
1602
+ for (const pattern of Object.keys(routes)) {
1603
+ try {
1604
+ compileRoutePattern(pattern);
1605
+ } catch (error) {
1606
+ ctx.addIssue({
1607
+ code: z21.ZodIssueCode.custom,
1608
+ path: [pattern],
1609
+ message: error instanceof Error ? error.message : `Invalid route pattern: ${pattern}`
1610
+ });
1611
+ }
1612
+ }
1613
+ }).describe(
1614
+ [
1615
+ "Define the routes and the lambda function that should handle them.",
1616
+ 'Routes can be an exact path like "/sitemap.xml", a wildcard like "/sitemap/*", or contain params like "/sitemap/{locale}/{page}.xml".',
1617
+ 'Param values are passed to the function as "x-param-[NAME]" request headers.'
1618
+ ].join("\n")
1619
+ )
1620
+ ).optional().describe("Add routes to your global Router that link a path pattern to a lambda function.");
1537
1621
  var VisibilitySchema = z21.boolean().default(false).describe("Whether to enable CloudWatch metrics for the WAF rule.");
1538
1622
  var WafSettingsSchema = z21.object({
1539
1623
  rateLimiter: z21.object({
@@ -2647,6 +2731,7 @@ var StackSchema = z45.object({
2647
2731
  // auth: AuthSchema,
2648
2732
  // http: HttpSchema,
2649
2733
  rest: RestSchema,
2734
+ routes: RoutesSchema,
2650
2735
  rpc: RpcSchema,
2651
2736
  configs: ConfigsSchema,
2652
2737
  crons: CronsSchema,
@@ -3727,8 +3812,18 @@ var zipBundle = async ({ directory }) => {
3727
3812
 
3728
3813
  // src/feature/function/build/typescript/rolldown.ts
3729
3814
  import { createHash as createHash2 } from "crypto";
3815
+ import { Minimatch } from "minimatch";
3730
3816
  import { rolldown } from "rolldown";
3731
3817
  import { importAsString } from "rollup-plugin-string-import";
3818
+ var createModuleMatcher = (patterns = []) => {
3819
+ const globs = patterns.map((pattern) => {
3820
+ return new Minimatch(pattern.replaceAll("\\", "/"), { dot: true });
3821
+ });
3822
+ return (id) => {
3823
+ const path = id.replaceAll("\\", "/");
3824
+ return globs.some((glob6) => glob6.match(path));
3825
+ };
3826
+ };
3732
3827
  var bundleTypeScriptWithRolldown = async ({
3733
3828
  format: format3 = "esm",
3734
3829
  minify = true,
@@ -3740,6 +3835,7 @@ var bundleTypeScriptWithRolldown = async ({
3740
3835
  codeSplitting = true,
3741
3836
  importAsString: importAsStringList
3742
3837
  }) => {
3838
+ const hasModuleSideEffects = createModuleMatcher(moduleSideEffects);
3743
3839
  const bundle = await rolldown({
3744
3840
  input: file,
3745
3841
  platform: "node",
@@ -3750,7 +3846,7 @@ var bundleTypeScriptWithRolldown = async ({
3750
3846
  debugError(error.message);
3751
3847
  },
3752
3848
  treeshake: {
3753
- moduleSideEffects: moduleSideEffects ? true : (id) => file === id
3849
+ moduleSideEffects: (id) => file === id || hasModuleSideEffects(id)
3754
3850
  },
3755
3851
  plugins: [
3756
3852
  // nodeResolve({ preferBuiltins: true }),
@@ -9063,7 +9159,7 @@ var metricFeature = defineFeature({
9063
9159
  import { days as days12, seconds as seconds12, toSeconds as toSeconds12, years } from "@awsless/duration";
9064
9160
  import { Future, Group as Group31 } from "@terraforge/core";
9065
9161
  import { aws as aws32 } from "@terraforge/aws";
9066
- import { camelCase as camelCase9, constantCase as constantCase16 } from "change-case";
9162
+ import { camelCase as camelCase9, constantCase as constantCase16, kebabCase as kebabCase9 } from "change-case";
9067
9163
 
9068
9164
  // src/feature/router/router-code.ts
9069
9165
  var getViewerRequestFunctionCode = (props) => {
@@ -9153,18 +9249,49 @@ function isValidRoute(route, method) {
9153
9249
  return true;
9154
9250
  }
9155
9251
 
9252
+ function matchRoute(value, path, method) {
9253
+ const list = Array.isArray(value) ? value : [value];
9254
+
9255
+ for(const i in list) {
9256
+ const route = list[i];
9257
+
9258
+ if(!isValidRoute(route, method)) {
9259
+ continue;
9260
+ }
9261
+
9262
+ if(route.match) {
9263
+ const found = path.match(new RegExp(route.match));
9264
+
9265
+ if(!found) {
9266
+ continue;
9267
+ }
9268
+
9269
+ const params = {};
9270
+
9271
+ if(route.params) {
9272
+ for(const p in route.params) {
9273
+ params[route.params[p]] = found[Number(p) + 1];
9274
+ }
9275
+ }
9276
+
9277
+ return { route, params };
9278
+ }
9279
+
9280
+ return { route };
9281
+ }
9282
+ }
9283
+
9156
9284
  async function findRoute(path, method) {
9157
9285
  const store = cf.kvs();
9158
9286
  const keys = getPossibleRouteKeys(path);
9159
9287
 
9160
9288
  for(const i in keys) {
9161
- const key = keys[i];
9162
-
9163
9289
  try {
9164
- const route = await store.get(key, { format: 'json' });
9290
+ const value = await store.get(keys[i], { format: 'json' });
9291
+ const result = matchRoute(value, path, method);
9165
9292
 
9166
- if(isValidRoute(route, method)) {
9167
- return route;
9293
+ if(result) {
9294
+ return result;
9168
9295
  }
9169
9296
  } catch (e) {}
9170
9297
  }
@@ -9271,11 +9398,19 @@ async function handler(event) {
9271
9398
 
9272
9399
  ${injection.join("\n")}
9273
9400
 
9274
- const route = await findRoute(path, request.method);
9401
+ const result = await findRoute(path, request.method);
9402
+
9403
+ if(result) {
9404
+ const route = result.route;
9275
9405
 
9276
- if(route) {
9277
9406
  setRouteOrigin(route);
9278
9407
 
9408
+ if(result.params) {
9409
+ for(const name in result.params) {
9410
+ headers['x-param-' + name.toLowerCase()] = { value: encodeURIComponent(result.params[name]) };
9411
+ }
9412
+ }
9413
+
9279
9414
  if(route.forwardHost && headers.host && headers.host.value) {
9280
9415
  headers['x-forwarded-host'] = { value: headers.host.value };
9281
9416
  }
@@ -9704,6 +9839,67 @@ var routerFeature = defineFeature({
9704
9839
  ctx.bind(`ROUTER_${constantCase16(id)}_ENDPOINT`, domainName);
9705
9840
  }
9706
9841
  }
9842
+ },
9843
+ onStack(ctx) {
9844
+ for (const [id, routes] of Object.entries(ctx.stackConfig.routes ?? {})) {
9845
+ if (!ctx.appConfig.defaults.router?.[id]) {
9846
+ throw new FileError(ctx.stackConfig.file, `Router "${id}" is not defined on the app level.`);
9847
+ }
9848
+ const group = new Group31(ctx.stack, "routes", id);
9849
+ const addRoutes = ctx.shared.entry("router", "addRoutes", id);
9850
+ const addInvalidation = ctx.shared.entry("router", "addInvalidation", id);
9851
+ const grouped = {};
9852
+ const invalidationPaths = /* @__PURE__ */ new Set();
9853
+ const versions = [];
9854
+ for (const [pattern, props] of Object.entries(routes)) {
9855
+ const compiled = compileRoutePattern(pattern);
9856
+ const slug = pattern.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 20);
9857
+ const entryId = kebabCase9(`${slug || "root"}-${shortId(pattern)}`);
9858
+ const routeGroup = new Group31(group, "route", entryId);
9859
+ const result = createLambdaFunction(routeGroup, ctx, "route", entryId, props);
9860
+ versions.push(result.code.sourceHash);
9861
+ ctx.onBind((name, value) => {
9862
+ result.setEnvironment(name, value);
9863
+ });
9864
+ new aws32.lambda.Permission(routeGroup, "permission", {
9865
+ principal: "cloudfront.amazonaws.com",
9866
+ action: "lambda:InvokeFunctionUrl",
9867
+ functionName: result.lambda.functionName,
9868
+ functionUrlAuthType: "AWS_IAM",
9869
+ sourceArn: `arn:aws:cloudfront::${ctx.accountId}:*`
9870
+ });
9871
+ new aws32.lambda.Permission(routeGroup, "invoke-permission", {
9872
+ principal: "cloudfront.amazonaws.com",
9873
+ action: "lambda:InvokeFunction",
9874
+ functionName: result.lambda.functionName,
9875
+ sourceArn: `arn:aws:cloudfront::${ctx.accountId}:*`
9876
+ });
9877
+ const url = new aws32.lambda.FunctionUrl(routeGroup, "url", {
9878
+ functionName: result.lambda.functionName,
9879
+ authorizationType: "AWS_IAM"
9880
+ });
9881
+ grouped[compiled.key] ??= [];
9882
+ grouped[compiled.key].push({
9883
+ type: "lambda",
9884
+ domainName: url.functionUrl.pipe((url2) => url2.split("/")[2]),
9885
+ forwardHost: true,
9886
+ urlEncodedQueryString: true,
9887
+ match: compiled.match,
9888
+ params: compiled.params
9889
+ });
9890
+ invalidationPaths.add(compiled.key);
9891
+ }
9892
+ const merged = {};
9893
+ for (const [key, list3] of Object.entries(grouped)) {
9894
+ if (list3.length === 1) {
9895
+ merged[key] = list3[0];
9896
+ } else {
9897
+ merged[key] = [...list3.filter((route) => route.match), ...list3.filter((route) => !route.match)];
9898
+ }
9899
+ }
9900
+ addRoutes(group, "routes", merged);
9901
+ addInvalidation(group, "invalidate", [...invalidationPaths], versions);
9902
+ }
9707
9903
  }
9708
9904
  });
9709
9905
 
@@ -259,7 +259,9 @@ var LayersSchema = z11.string().array().describe(
259
259
  var FileCodeSchema = z11.object({
260
260
  file: LocalFileSchema.describe("The file path of the function code."),
261
261
  minify: MinifySchema.optional().default(true),
262
- moduleSideEffects: z11.boolean().default(false).describe(`Will flag all modules as having potential side effects.`),
262
+ moduleSideEffects: RelativePathSchema.array().default([]).describe(
263
+ `A list of glob patterns for modules that should be flagged as having potential side effects. For example "./.svelte-kit/**" will flag every file inside the .svelte-kit folder.`
264
+ ),
263
265
  external: z11.string().array().optional().describe(`A list of external packages that won't be included in the bundle.`),
264
266
  importAsString: z11.string().array().optional().describe(`A list of glob patterns, which specifies the files that should be imported as string.`)
265
267
  });
@@ -559,6 +561,70 @@ var InstanceDefaultSchema = z16.object({
559
561
  // src/feature/router/schema.ts
560
562
  import { days as days3, minutes as minutes2, parse as parse3 } from "@awsless/duration";
561
563
  import { z as z17 } from "zod";
564
+
565
+ // src/error.ts
566
+ var ExpectedError = class extends Error {
567
+ };
568
+
569
+ // src/feature/router/pattern.ts
570
+ var PARAM_TOKEN = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}|\*/g;
571
+ var escapeRegex = (value) => {
572
+ return value.replace(/[|\\{}()[\]^$+*?.\-]/g, "\\$&");
573
+ };
574
+ var compileRoutePattern = (pattern) => {
575
+ if (!pattern.startsWith("/")) {
576
+ throw new ExpectedError(`Route pattern "${pattern}" must start with a slash (/)`);
577
+ }
578
+ if (pattern === "/*") {
579
+ return { key: pattern };
580
+ }
581
+ const params = [];
582
+ let regex = "";
583
+ let stars = 0;
584
+ let last = 0;
585
+ let token;
586
+ PARAM_TOKEN.lastIndex = 0;
587
+ while (token = PARAM_TOKEN.exec(pattern)) {
588
+ regex += escapeRegex(pattern.slice(last, token.index));
589
+ const param = token[1];
590
+ if (param) {
591
+ if (params.includes(param)) {
592
+ throw new ExpectedError(`Duplicate param "${param}" in route pattern "${pattern}"`);
593
+ }
594
+ params.push(param);
595
+ regex += "([^/]+)";
596
+ } else {
597
+ stars++;
598
+ regex += ".*";
599
+ }
600
+ last = PARAM_TOKEN.lastIndex;
601
+ }
602
+ if (params.length === 0 && stars === 0) {
603
+ return { key: pattern };
604
+ }
605
+ regex += escapeRegex(pattern.slice(last));
606
+ const root = pattern.split("/")[1] ?? "";
607
+ if (root === "" || root.includes("*") || root.includes("{")) {
608
+ throw new ExpectedError(
609
+ `The first path segment of route pattern "${pattern}" must be static when the pattern contains params or wildcards.`
610
+ );
611
+ }
612
+ if (root.includes(".")) {
613
+ throw new ExpectedError(
614
+ `The first path segment of route pattern "${pattern}" can't contain a dot when the pattern contains params or wildcards.`
615
+ );
616
+ }
617
+ if (params.length === 0 && pattern === `/${root}/*`) {
618
+ return { key: pattern };
619
+ }
620
+ return {
621
+ key: `/${root}/*`,
622
+ match: `^${regex}$`,
623
+ params: params.length > 0 ? params : void 0
624
+ };
625
+ };
626
+
627
+ // src/feature/router/schema.ts
562
628
  var ErrorResponsePathSchema = z17.string().describe(
563
629
  [
564
630
  "The path to the custom error page that you want to return to the viewer when your origin returns the HTTP status code specified.",
@@ -588,6 +654,28 @@ var ErrorResponseSchema = z17.union([
588
654
  })
589
655
  ]).optional();
590
656
  var RouteSchema = z17.string().regex(/^\//, "Route must start with a slash (/)");
657
+ var RoutesSchema = z17.record(
658
+ ResourceIdSchema.describe("The router id to add your routes to."),
659
+ z17.record(RouteSchema, FunctionSchema).superRefine((routes, ctx) => {
660
+ for (const pattern of Object.keys(routes)) {
661
+ try {
662
+ compileRoutePattern(pattern);
663
+ } catch (error) {
664
+ ctx.addIssue({
665
+ code: z17.ZodIssueCode.custom,
666
+ path: [pattern],
667
+ message: error instanceof Error ? error.message : `Invalid route pattern: ${pattern}`
668
+ });
669
+ }
670
+ }
671
+ }).describe(
672
+ [
673
+ "Define the routes and the lambda function that should handle them.",
674
+ 'Routes can be an exact path like "/sitemap.xml", a wildcard like "/sitemap/*", or contain params like "/sitemap/{locale}/{page}.xml".',
675
+ 'Param values are passed to the function as "x-param-[NAME]" request headers.'
676
+ ].join("\n")
677
+ )
678
+ ).optional().describe("Add routes to your global Router that link a path pattern to a lambda function.");
591
679
  var VisibilitySchema = z17.boolean().default(false).describe("Whether to enable CloudWatch metrics for the WAF rule.");
592
680
  var WafSettingsSchema = z17.object({
593
681
  rateLimiter: z17.object({
@@ -1700,6 +1788,7 @@ var StackSchema = z41.object({
1700
1788
  // auth: AuthSchema,
1701
1789
  // http: HttpSchema,
1702
1790
  rest: RestSchema,
1791
+ routes: RoutesSchema,
1703
1792
  rpc: RpcSchema,
1704
1793
  configs: ConfigsSchema,
1705
1794
  crons: CronsSchema,
@@ -1994,6 +2083,7 @@ var generateJsonSchema = (props) => {
1994
2083
  var appendDefaults = (object) => {
1995
2084
  if (Array.isArray(object)) {
1996
2085
  object.forEach(appendDefaults);
2086
+ return;
1997
2087
  }
1998
2088
  if (typeof object === "object" && object !== null) {
1999
2089
  if ("default" in object && "type" in object) {
Binary file
Binary file
Binary file
Binary file
package/dist/prebuild.js CHANGED
@@ -12,6 +12,7 @@ import { join } from "path";
12
12
 
13
13
  // src/feature/function/build/typescript/rolldown.ts
14
14
  import { createHash } from "crypto";
15
+ import { Minimatch } from "minimatch";
15
16
  import { rolldown } from "rolldown";
16
17
  import { importAsString } from "rollup-plugin-string-import";
17
18
 
@@ -45,6 +46,15 @@ var debugError = (error) => {
45
46
  };
46
47
 
47
48
  // src/feature/function/build/typescript/rolldown.ts
49
+ var createModuleMatcher = (patterns = []) => {
50
+ const globs = patterns.map((pattern) => {
51
+ return new Minimatch(pattern.replaceAll("\\", "/"), { dot: true });
52
+ });
53
+ return (id) => {
54
+ const path = id.replaceAll("\\", "/");
55
+ return globs.some((glob) => glob.match(path));
56
+ };
57
+ };
48
58
  var bundleTypeScriptWithRolldown = async ({
49
59
  format = "esm",
50
60
  minify = true,
@@ -56,6 +66,7 @@ var bundleTypeScriptWithRolldown = async ({
56
66
  codeSplitting = true,
57
67
  importAsString: importAsStringList
58
68
  }) => {
69
+ const hasModuleSideEffects = createModuleMatcher(moduleSideEffects);
59
70
  const bundle = await rolldown({
60
71
  input: file,
61
72
  platform: "node",
@@ -66,7 +77,7 @@ var bundleTypeScriptWithRolldown = async ({
66
77
  debugError(error.message);
67
78
  },
68
79
  treeshake: {
69
- moduleSideEffects: moduleSideEffects ? true : (id) => file === id
80
+ moduleSideEffects: (id) => file === id || hasModuleSideEffects(id)
70
81
  },
71
82
  plugins: [
72
83
  // nodeResolve({ preferBuiltins: true }),