@vercel/redwood 2.5.0 → 2.5.1

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.
Files changed (2) hide show
  1. package/dist/index.js +260 -19
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -814,10 +814,11 @@ var require_superstatic = __commonJS({
814
814
  var superstatic_exports = {};
815
815
  __export2(superstatic_exports, {
816
816
  collectHasSegments: () => collectHasSegments,
817
+ compilePathToRegexpTemplate: () => compilePathToRegexpTemplate2,
817
818
  convertCleanUrls: () => convertCleanUrls,
818
819
  convertHeaders: () => convertHeaders,
819
820
  convertRedirects: () => convertRedirects,
820
- convertRewrites: () => convertRewrites,
821
+ convertRewrites: () => convertRewrites2,
821
822
  convertTrailingSlash: () => convertTrailingSlash,
822
823
  getCleanUrls: () => getCleanUrls2,
823
824
  pathToRegexp: () => pathToRegexp,
@@ -933,21 +934,46 @@ var require_superstatic = __commonJS({
933
934
  }
934
935
  });
935
936
  }
936
- function convertRewrites(rewrites, internalParamNames) {
937
+ function convertRewrites2(rewrites, internalParamNames) {
937
938
  return rewrites.map((r) => {
938
939
  const { src, segments } = sourceToRegex2(r.source);
939
940
  const hasSegments = collectHasSegments(r.has);
940
941
  normalizeHasKeys(r.has);
941
942
  normalizeHasKeys(r.missing);
942
943
  try {
943
- const dest = replaceSegments(
944
+ const interpolate = (value) => replaceSegments(
944
945
  segments,
945
946
  hasSegments,
946
- r.destination,
947
+ value,
947
948
  false,
948
949
  internalParamNames
949
950
  );
950
- const route = { src, dest, check: true };
951
+ let route;
952
+ if (typeof r.destination === "string") {
953
+ route = { src, dest: interpolate(r.destination), check: true };
954
+ } else {
955
+ const destination = { ...r.destination, type: "service" };
956
+ if (typeof destination.path === "string") {
957
+ destination.path = interpolate(destination.path);
958
+ }
959
+ route = { src, destination };
960
+ }
961
+ if (r.transforms) {
962
+ route.transforms = r.transforms.map((transform) => {
963
+ if (transform.type !== "request.path") {
964
+ return { ...transform };
965
+ }
966
+ return {
967
+ ...transform,
968
+ args: compilePathToRegexpTemplateFromSegments(
969
+ transform.args,
970
+ segments,
971
+ hasSegments,
972
+ transform.env
973
+ )
974
+ };
975
+ });
976
+ }
951
977
  if (typeof r.env !== "undefined") {
952
978
  route.env = r.env;
953
979
  }
@@ -1088,6 +1114,81 @@ var require_superstatic = __commonJS({
1088
1114
  }
1089
1115
  var escapeSegment = (str, segmentName) => str.replace(new RegExp(`:${segmentName}`, "g"), `__ESC_COLON_${segmentName}`);
1090
1116
  var unescapeSegments = (str) => str.replace(/__ESC_COLON_/gi, ":");
1117
+ var pathTemplateSegmentNameRegex = /^([a-zA-Z_][a-zA-Z0-9_]*)/;
1118
+ function isEscaped(value, index) {
1119
+ let backslashCount = 0;
1120
+ for (let i = index - 1; i >= 0 && value[i] === "\\"; i--) {
1121
+ backslashCount++;
1122
+ }
1123
+ return backslashCount % 2 === 1;
1124
+ }
1125
+ function collectPathTemplateSegments(template) {
1126
+ const segments = [];
1127
+ for (let i = 0; i < template.length; i++) {
1128
+ if (template[i] !== ":" || isEscaped(template, i)) {
1129
+ continue;
1130
+ }
1131
+ const match = template.slice(i + 1).match(pathTemplateSegmentNameRegex);
1132
+ if (match) {
1133
+ segments.push(match[1]);
1134
+ i += match[1].length;
1135
+ }
1136
+ }
1137
+ return segments;
1138
+ }
1139
+ function collectNamedDollarReferences(template) {
1140
+ const references = [];
1141
+ for (let i = 0; i < template.length; i++) {
1142
+ if (template[i] !== "$" || isEscaped(template, i)) {
1143
+ continue;
1144
+ }
1145
+ const remainder = template.slice(i + 1);
1146
+ const bracedMatch = remainder.match(/^\{([a-zA-Z_][a-zA-Z0-9_]*)\}/);
1147
+ const unbracedMatch = remainder.match(pathTemplateSegmentNameRegex);
1148
+ const name = bracedMatch?.[1] || unbracedMatch?.[1];
1149
+ if (name) {
1150
+ references.push(name);
1151
+ }
1152
+ }
1153
+ return references;
1154
+ }
1155
+ function compilePathToRegexpTemplateFromSegments(template, segments, hasItemSegments, env = []) {
1156
+ const indexes = {};
1157
+ segments.forEach((name, index) => {
1158
+ indexes[name] = toSegmentDest(index);
1159
+ });
1160
+ hasItemSegments.forEach((name) => {
1161
+ indexes[name] = `$${name}`;
1162
+ });
1163
+ for (const name of collectPathTemplateSegments(template)) {
1164
+ if (!(name in indexes)) {
1165
+ throw new Error(
1166
+ `Path template references parameter ":${name}" that is not present in the source or has conditions.`
1167
+ );
1168
+ }
1169
+ }
1170
+ const routeParameters = /* @__PURE__ */ new Set([
1171
+ ...segments.filter((name) => name !== UN_NAMED_SEGMENT),
1172
+ ...hasItemSegments
1173
+ ]);
1174
+ for (const name of collectNamedDollarReferences(template)) {
1175
+ if (routeParameters.has(name) && !env.includes(name)) {
1176
+ throw new Error(
1177
+ `Path template references route parameter "${name}" as \`$${name}\`. Use \`:${name}\` path-to-regexp syntax in high-level rewrites, or list "${name}" in the transform env allowlist if it is an environment variable.`
1178
+ );
1179
+ }
1180
+ }
1181
+ return safelyCompile(template, indexes, true);
1182
+ }
1183
+ function compilePathToRegexpTemplate2(source, template, has, env) {
1184
+ const { segments } = sourceToRegex2(source);
1185
+ return compilePathToRegexpTemplateFromSegments(
1186
+ template,
1187
+ segments,
1188
+ collectHasSegments(has),
1189
+ env
1190
+ );
1191
+ }
1091
1192
  function replaceSegments(segments, hasItemSegments, destination, isRedirect, internalParamNames) {
1092
1193
  const namedSegments = segments.filter((name) => name !== UN_NAMED_SEGMENT);
1093
1194
  const canNeedReplacing = destination.includes(":") && namedSegments.length > 0 || hasItemSegments.length > 0 || !isRedirect;
@@ -1489,7 +1590,8 @@ var require_schemas = __commonJS({
1489
1590
  redirectsSchema: () => redirectsSchema,
1490
1591
  rewritesSchema: () => rewritesSchema,
1491
1592
  routesSchema: () => routesSchema,
1492
- trailingSlashSchema: () => trailingSlashSchema
1593
+ trailingSlashSchema: () => trailingSlashSchema,
1594
+ transformsSchema: () => transformsSchema
1493
1595
  });
1494
1596
  module2.exports = __toCommonJS2(schemas_exports);
1495
1597
  var mitigateSchema = {
@@ -1505,6 +1607,32 @@ var require_schemas = __commonJS({
1505
1607
  }
1506
1608
  }
1507
1609
  };
1610
+ var serviceNameSchema = {
1611
+ description: "A service name identifier.",
1612
+ type: "string",
1613
+ minLength: 1,
1614
+ maxLength: 64,
1615
+ pattern: "^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$"
1616
+ };
1617
+ var serviceDestinationSchema = {
1618
+ description: "A service-targeted destination that delegates routing into a named service from `services`. Identified by the presence of `service`.",
1619
+ type: "object",
1620
+ additionalProperties: false,
1621
+ required: ["service"],
1622
+ properties: {
1623
+ type: {
1624
+ description: "Optional explicit format marker. The destination shape is identified by the `service` property, so `type` is no longer required. When present it must be `service`.",
1625
+ type: "string",
1626
+ enum: ["service"]
1627
+ },
1628
+ service: serviceNameSchema,
1629
+ path: {
1630
+ description: "Routing-only path used to select a route inside the target service. It does not mutate the URL observed by user code.",
1631
+ type: "string",
1632
+ maxLength: 4096
1633
+ }
1634
+ }
1635
+ };
1508
1636
  var matchableValueSchema = {
1509
1637
  description: "A value to match against. Can be a string (regex) or a condition operation object",
1510
1638
  anyOf: [
@@ -1628,18 +1756,23 @@ var require_schemas = __commonJS({
1628
1756
  }
1629
1757
  };
1630
1758
  var transformsSchema = {
1631
- description: "A list of transform rules to adjust the query parameters of a request or HTTP headers of request or response",
1759
+ description: "A list of transform rules to adjust a request path, request query parameters, or request/response headers",
1632
1760
  type: "array",
1633
1761
  minItems: 1,
1634
1762
  items: {
1635
1763
  type: "object",
1636
1764
  additionalProperties: false,
1637
- required: ["type", "op", "target"],
1765
+ required: ["type", "op"],
1638
1766
  properties: {
1639
1767
  type: {
1640
1768
  description: "The scope of the transform to apply",
1641
1769
  type: "string",
1642
- enum: ["request.headers", "request.query", "response.headers"]
1770
+ enum: [
1771
+ "request.headers",
1772
+ "request.query",
1773
+ "response.headers",
1774
+ "request.path"
1775
+ ]
1643
1776
  },
1644
1777
  op: {
1645
1778
  description: "The operation to perform on the target",
@@ -1825,10 +1958,90 @@ var require_schemas = __commonJS({
1825
1958
  }
1826
1959
  }
1827
1960
  }
1961
+ },
1962
+ {
1963
+ if: {
1964
+ required: ["type"],
1965
+ properties: {
1966
+ type: {
1967
+ enum: ["request.headers", "request.query", "response.headers"]
1968
+ }
1969
+ }
1970
+ },
1971
+ // biome-ignore lint/suspicious/noThenProperty: JSON Schema if/then keyword
1972
+ then: {
1973
+ required: ["target"]
1974
+ }
1975
+ },
1976
+ {
1977
+ if: {
1978
+ required: ["type"],
1979
+ properties: {
1980
+ type: {
1981
+ enum: ["request.path"]
1982
+ }
1983
+ }
1984
+ },
1985
+ // biome-ignore lint/suspicious/noThenProperty: JSON Schema if/then keyword
1986
+ then: {
1987
+ required: ["args"],
1988
+ not: {
1989
+ required: ["target"]
1990
+ },
1991
+ properties: {
1992
+ op: {
1993
+ enum: ["set"]
1994
+ },
1995
+ args: {
1996
+ description: "The runtime-visible request path. Must be an origin-form path without query or fragment.",
1997
+ type: "string",
1998
+ maxLength: 2048,
1999
+ pattern: "^/(?!/)(?!.*[?#\\s\\x00-\\x1F\\x7F]).*$"
2000
+ }
2001
+ }
2002
+ }
1828
2003
  }
1829
2004
  ]
1830
2005
  }
1831
2006
  };
2007
+ var rewriteTransformsSchema = {
2008
+ description: "A list of request path transforms using path-to-regexp parameters.",
2009
+ type: "array",
2010
+ minItems: 1,
2011
+ items: {
2012
+ type: "object",
2013
+ additionalProperties: false,
2014
+ required: ["type", "op", "args"],
2015
+ properties: {
2016
+ type: {
2017
+ description: "The request path to expose to the target runtime",
2018
+ type: "string",
2019
+ enum: ["request.path"]
2020
+ },
2021
+ op: {
2022
+ description: "Replace the runtime-visible request path",
2023
+ type: "string",
2024
+ enum: ["set"]
2025
+ },
2026
+ args: {
2027
+ description: "An origin-form request path. Route parameters use path-to-regexp syntax such as `/:path*`.",
2028
+ type: "string",
2029
+ maxLength: 2048,
2030
+ pattern: "^/(?!/)(?!.*[?#\\s\\x00-\\x1F\\x7F]).*$"
2031
+ },
2032
+ env: {
2033
+ description: "An array of environment variable names that should be replaced at runtime in the args value",
2034
+ type: "array",
2035
+ minItems: 1,
2036
+ maxItems: 64,
2037
+ items: {
2038
+ type: "string",
2039
+ maxLength: 256
2040
+ }
2041
+ }
2042
+ }
2043
+ }
2044
+ };
1832
2045
  var routesSchema = {
1833
2046
  type: "array",
1834
2047
  description: "A list of routes objects used to rewrite paths to point towards other internal or external paths",
@@ -1853,8 +2066,10 @@ var require_schemas = __commonJS({
1853
2066
  maxLength: 4096
1854
2067
  },
1855
2068
  destination: {
1856
- type: "string",
1857
- maxLength: 4096
2069
+ anyOf: [
2070
+ { type: "string", maxLength: 4096 },
2071
+ serviceDestinationSchema
2072
+ ]
1858
2073
  },
1859
2074
  headers: {
1860
2075
  type: "object",
@@ -2003,10 +2218,10 @@ var require_schemas = __commonJS({
2003
2218
  maxLength: 4096
2004
2219
  },
2005
2220
  destination: {
2006
- description: "An absolute pathname to an existing resource or an external URL.",
2007
- type: "string",
2008
- maxLength: 4096
2221
+ description: "An absolute pathname to an existing resource, an external URL, or a service-targeted destination object.",
2222
+ anyOf: [{ type: "string", maxLength: 4096 }, serviceDestinationSchema]
2009
2223
  },
2224
+ transforms: rewriteTransformsSchema,
2010
2225
  has: hasSchema,
2011
2226
  missing: hasSchema,
2012
2227
  statusCode: {
@@ -2175,6 +2390,8 @@ var require_dist3 = __commonJS({
2175
2390
  var src_exports2 = {};
2176
2391
  __export2(src_exports2, {
2177
2392
  appendRoutesToPhase: () => import_append.appendRoutesToPhase,
2393
+ compilePathToRegexpTemplate: () => import_superstatic2.compilePathToRegexpTemplate,
2394
+ convertRewrites: () => import_superstatic2.convertRewrites,
2178
2395
  getCleanUrls: () => import_superstatic2.getCleanUrls,
2179
2396
  getOwnershipGuard: () => import_service_route_ownership.getOwnershipGuard,
2180
2397
  getTransformedRoutes: () => getTransformedRoutes2,
@@ -2226,8 +2443,12 @@ var require_dist3 = __commonJS({
2226
2443
  `Route at index ${index} cannot define both \`dest\` and \`destination\`. Please use only one.`
2227
2444
  );
2228
2445
  }
2229
- route.dest = route.destination;
2230
- delete route.destination;
2446
+ if (typeof route.destination === "string") {
2447
+ route.dest = route.destination;
2448
+ delete route.destination;
2449
+ } else if (typeof route.destination.service === "string") {
2450
+ route.destination = { ...route.destination, type: "service" };
2451
+ }
2231
2452
  }
2232
2453
  if (route.statusCode !== void 0) {
2233
2454
  if (route.status !== void 0) {
@@ -2287,6 +2508,11 @@ var require_dist3 = __commonJS({
2287
2508
  if (regError) {
2288
2509
  errors.push(regError);
2289
2510
  }
2511
+ if (route.destination && typeof route.destination === "object" && route.continue) {
2512
+ errors.push(
2513
+ `Route at index ${i} cannot define \`continue: true\` with a service \`destination\`. The service handoff is terminal.`
2514
+ );
2515
+ }
2290
2516
  const handleValue = handling[handling.length - 1];
2291
2517
  if (handleValue === "hit") {
2292
2518
  if (route.dest) {
@@ -2341,7 +2567,8 @@ var require_dist3 = __commonJS({
2341
2567
  function checkPatternSyntax(type, index, {
2342
2568
  source,
2343
2569
  destination,
2344
- has
2570
+ has,
2571
+ transforms
2345
2572
  }) {
2346
2573
  let sourceSegments = /* @__PURE__ */ new Set();
2347
2574
  const destinationSegments = /* @__PURE__ */ new Set();
@@ -2353,9 +2580,10 @@ var require_dist3 = __commonJS({
2353
2580
  link: "https://vercel.link/invalid-route-source-pattern"
2354
2581
  };
2355
2582
  }
2356
- if (destination) {
2583
+ const destinationString = typeof destination === "string" ? destination : typeof destination?.path === "string" ? destination.path : void 0;
2584
+ if (destinationString !== void 0) {
2357
2585
  try {
2358
- const { hostname, pathname, query } = (0, import_url.parse)(destination, true);
2586
+ const { hostname, pathname, query } = (0, import_url.parse)(destinationString, true);
2359
2587
  (0, import_superstatic.sourceToRegex)(hostname || "").segments.forEach(
2360
2588
  (name) => destinationSegments.add(name)
2361
2589
  );
@@ -2380,6 +2608,19 @@ var require_dist3 = __commonJS({
2380
2608
  }
2381
2609
  }
2382
2610
  }
2611
+ for (const transform of transforms || []) {
2612
+ if (transform.type !== "request.path") {
2613
+ continue;
2614
+ }
2615
+ try {
2616
+ (0, import_superstatic.compilePathToRegexpTemplate)(source, transform.args, has, transform.env);
2617
+ } catch (error) {
2618
+ return {
2619
+ message: `${type} at index ${index} has an invalid \`request.path\` transform: ${error instanceof Error ? error.message : String(error)}`,
2620
+ link: "https://vercel.link/invalid-route-destination-segment"
2621
+ };
2622
+ }
2623
+ }
2383
2624
  return null;
2384
2625
  }
2385
2626
  function checkRedirect(r, index) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/redwood",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs",
@@ -16,7 +16,7 @@
16
16
  "@vercel/nft": "1.10.0",
17
17
  "semver": "6.3.1",
18
18
  "ts-morph": "12.0.0",
19
- "@vercel/static-config": "3.4.0"
19
+ "@vercel/static-config": "3.4.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/aws-lambda": "8.10.19",
@@ -25,8 +25,8 @@
25
25
  "execa": "3.2.0",
26
26
  "fs-extra": "11.1.0",
27
27
  "vitest": "2.0.3",
28
- "@vercel/build-utils": "13.28.0",
29
- "@vercel/routing-utils": "6.2.0"
28
+ "@vercel/build-utils": "14.0.1",
29
+ "@vercel/routing-utils": "6.4.1"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "node ../../utils/build-builder.mjs",