Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@vercel/routing-utils

Package Overview
Dependencies
Maintainers
4
Versions
107
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vercel/routing-utils - npm Package Compare versions

Comparing version
6.2.0
to
6.3.0
+12
-4
dist/index.js

@@ -74,4 +74,6 @@ "use strict";

}
route.dest = route.destination;
delete route.destination;
if (typeof route.destination === "string") {
route.dest = route.destination;
delete route.destination;
}
}

@@ -136,2 +138,7 @@ if (route.statusCode !== void 0) {

}
if (route.destination && typeof route.destination === "object" && route.continue) {
errors.push(
`Route at index ${i} cannot define \`continue: true\` with a service \`destination\`. The service handoff is terminal.`
);
}
const handleValue = handling[handling.length - 1];

@@ -203,5 +210,6 @@ if (handleValue === "hit") {

}
if (destination) {
const destinationString = typeof destination === "string" ? destination : typeof destination?.path === "string" ? destination.path : void 0;
if (destinationString !== void 0) {
try {
const { hostname, pathname, query } = (0, import_url.parse)(destination, true);
const { hostname, pathname, query } = (0, import_url.parse)(destinationString, true);
(0, import_superstatic.sourceToRegex)(hostname || "").segments.forEach(

@@ -208,0 +216,0 @@ (name) => destinationSegments.add(name)

@@ -43,2 +43,28 @@ "use strict";

};
const serviceNameSchema = {
description: "A service name identifier.",
type: "string",
minLength: 1,
maxLength: 64,
pattern: "^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$"
};
const serviceDestinationSchema = {
description: "A service-targeted destination that delegates routing into a named service from `services`.",
type: "object",
additionalProperties: false,
required: ["type", "service"],
properties: {
type: {
description: "Discriminator. Must be `service`.",
type: "string",
enum: ["service"]
},
service: serviceNameSchema,
path: {
description: "Routing-only path used to select a route inside the target service. It does not mutate the URL observed by user code.",
type: "string",
maxLength: 4096
}
}
};
const matchableValueSchema = {

@@ -392,4 +418,6 @@ description: "A value to match against. Can be a string (regex) or a condition operation object",

destination: {
type: "string",
maxLength: 4096
anyOf: [
{ type: "string", maxLength: 4096 },
serviceDestinationSchema
]
},

@@ -543,5 +571,4 @@ headers: {

destination: {
description: "An absolute pathname to an existing resource or an external URL.",
type: "string",
maxLength: 4096
description: "An absolute pathname to an existing resource, an external URL, or a service-targeted destination object.",
anyOf: [{ type: "string", maxLength: 4096 }, serviceDestinationSchema]
},

@@ -548,0 +575,0 @@ has: hasSchema,

@@ -148,10 +148,19 @@ "use strict";

try {
const dest = replaceSegments(
const interpolate = (value) => replaceSegments(
segments,
hasSegments,
r.destination,
value,
false,
internalParamNames
);
const route = { src, dest, check: true };
let route;
if (typeof r.destination === "string") {
route = { src, dest: interpolate(r.destination), check: true };
} else {
const destination = { ...r.destination };
if (typeof destination.path === "string") {
destination.path = interpolate(destination.path);
}
route = { src, destination };
}
if (typeof r.env !== "undefined") {

@@ -158,0 +167,0 @@ route.env = r.env;

@@ -52,2 +52,8 @@ import { HandleValue } from './index';

};
export type ServiceDestination = {
type: 'service';
service: string;
/** Routing-only path used to select a route inside the target service. */
path?: string;
};
export type RouteWithSrc = {

@@ -82,7 +88,12 @@ src: string;

* `rewrites`, `redirects`, and `headers` fields which use `source`, `destination`,
* and `statusCode`. During normalization, these are converted to their canonical
* forms (`src`, `dest`, `status`) and stripped from the route object.
* and `statusCode`. During normalization, the string forms are converted to
* their canonical forms (`src`, `dest`, `status`) and stripped from the route
* object.
*
* `destination` may also be a service-targeted object, in which case routing
* is delegated into the named service's internal route table and the object
* is preserved as-is (not folded into `dest`).
*/
source?: string;
destination?: string;
destination?: string | ServiceDestination;
statusCode?: number;

@@ -139,3 +150,3 @@ /**

source: string;
destination: string;
destination: string | ServiceDestination;
has?: HasField;

@@ -142,0 +153,0 @@ missing?: HasField;

{
"name": "@vercel/routing-utils",
"version": "6.2.0",
"version": "6.3.0",
"description": "Vercel routing utilities",

@@ -21,6 +21,5 @@ "main": "./dist/index.js",

"devDependencies": {
"@types/jest": "27.4.1",
"@types/node": "20.11.0",
"ajv": "^6.12.3",
"jest-junit": "16.0.0"
"vitest": "2.0.3"
},

@@ -32,6 +31,8 @@ "optionalDependencies": {

"build": "node ../../utils/build.mjs",
"test": "jest --reporters=default --reporters=jest-junit --env node --verbose --runInBand --bail",
"test": "vitest run --config ../../vitest.config.mts",
"test-unit": "pnpm test",
"type-check": "tsc --noEmit"
"type-check": "tsc --noEmit",
"vitest-run": "vitest -c ../../vitest.config.mts",
"vitest-unit": "glob --absolute 'test/**/*.test.js' 'test/**/*.test.ts' 'test/**/*.test.mjs' 'test/**/*.test.mts' 'tests/**/*.test.js' 'tests/**/*.test.ts'"
}
}
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var src_exports = {};
__export(src_exports, {
appendRoutesToPhase: () => import_append.appendRoutesToPhase,
getCleanUrls: () => import_superstatic2.getCleanUrls,
getOwnershipGuard: () => import_service_route_ownership.getOwnershipGuard,
getTransformedRoutes: () => getTransformedRoutes,
isHandler: () => isHandler,
isValidHandleValue: () => isValidHandleValue,
mergeRoutes: () => import_merge.mergeRoutes,
normalizeRoutePrefix: () => import_service_route_ownership.normalizeRoutePrefix,
normalizeRoutes: () => normalizeRoutes,
scopeRouteSourceToOwnership: () => import_service_route_ownership.scopeRouteSourceToOwnership,
sourceToRegex: () => import_superstatic2.sourceToRegex
});
module.exports = __toCommonJS(src_exports);
var import_url = require("url");
var import_superstatic = require("./superstatic");
var import_append = require("./append");
var import_merge = require("./merge");
var import_service_route_ownership = require("./service-route-ownership");
__reExport(src_exports, require("./schemas"), module.exports);
var import_superstatic2 = require("./superstatic");
__reExport(src_exports, require("./types"), module.exports);
const VALID_HANDLE_VALUES = [
"filesystem",
"hit",
"miss",
"rewrite",
"error",
"resource"
];
const validHandleValues = new Set(VALID_HANDLE_VALUES);
function isHandler(route) {
return typeof route.handle !== "undefined";
}
function isValidHandleValue(handle) {
return validHandleValues.has(handle);
}
function convertRouteAliases(route, index) {
if (route.source !== void 0) {
if (route.src !== void 0) {
throw new Error(
`Route at index ${index} cannot define both \`src\` and \`source\`. Please use only one.`
);
}
route.src = route.source;
delete route.source;
}
if (route.destination !== void 0) {
if (route.dest !== void 0) {
throw new Error(
`Route at index ${index} cannot define both \`dest\` and \`destination\`. Please use only one.`
);
}
route.dest = route.destination;
delete route.destination;
}
if (route.statusCode !== void 0) {
if (route.status !== void 0) {
throw new Error(
`Route at index ${index} cannot define both \`status\` and \`statusCode\`. Please use only one.`
);
}
route.status = route.statusCode;
delete route.statusCode;
}
}
function normalizeRoutes(inputRoutes) {
if (!inputRoutes || inputRoutes.length === 0) {
return { routes: inputRoutes, error: null };
}
const routes = [];
const handling = [];
const errors = [];
inputRoutes.forEach((r, i) => {
const route = { ...r };
routes.push(route);
if (!isHandler(route)) {
try {
convertRouteAliases(route, i);
} catch (err) {
errors.push(err.message);
}
}
const keys = Object.keys(route);
if (isHandler(route)) {
const { handle } = route;
if (keys.length !== 1) {
const unknownProp = keys.find((prop) => prop !== "handle");
errors.push(
`Route at index ${i} has unknown property \`${unknownProp}\`.`
);
} else if (!isValidHandleValue(handle)) {
errors.push(
`Route at index ${i} has unknown handle value \`handle: ${handle}\`.`
);
} else if (handling.includes(handle)) {
errors.push(
`Route at index ${i} is a duplicate. Please use one \`handle: ${handle}\` at most.`
);
} else {
handling.push(handle);
}
} else if (route.src) {
if (!route.src.startsWith("^")) {
route.src = `^${route.src}`;
}
if (!route.src.endsWith("$")) {
route.src = `${route.src}$`;
}
route.src = route.src.replace(/\\\//g, "/");
const regError = checkRegexSyntax("Route", i, route.src);
if (regError) {
errors.push(regError);
}
const handleValue = handling[handling.length - 1];
if (handleValue === "hit") {
if (route.dest) {
errors.push(
`Route at index ${i} cannot define \`dest\`/\`destination\` after \`handle: hit\`.`
);
}
if (route.status) {
errors.push(
`Route at index ${i} cannot define \`status\`/\`statusCode\` after \`handle: hit\`.`
);
}
if (!route.continue) {
errors.push(
`Route at index ${i} must define \`continue: true\` after \`handle: hit\`.`
);
}
} else if (handleValue === "miss") {
if (route.dest && !route.check) {
errors.push(
`Route at index ${i} must define \`check: true\` after \`handle: miss\`.`
);
} else if (!route.dest && !route.continue) {
errors.push(
`Route at index ${i} must define \`continue: true\` after \`handle: miss\`.`
);
}
}
} else {
errors.push(
`Route at index ${i} must define either \`src\` or \`source\` property.`
);
}
});
const error = errors.length > 0 ? createError(
"invalid_route",
errors,
"https://vercel.link/routes-json",
"Learn More"
) : null;
return { routes, error };
}
function checkRegexSyntax(type, index, src) {
try {
new RegExp(src);
} catch (_err) {
const prop = type === "Route" ? "src`/`source" : "source";
return `${type} at index ${index} has invalid \`${prop}\` regular expression "${src}".`;
}
return null;
}
function checkPatternSyntax(type, index, {
source,
destination,
has
}) {
let sourceSegments = /* @__PURE__ */ new Set();
const destinationSegments = /* @__PURE__ */ new Set();
try {
sourceSegments = new Set((0, import_superstatic.sourceToRegex)(source).segments);
} catch (_err) {
return {
message: `${type} at index ${index} has invalid \`source\` pattern "${source}".`,
link: "https://vercel.link/invalid-route-source-pattern"
};
}
if (destination) {
try {
const { hostname, pathname, query } = (0, import_url.parse)(destination, true);
(0, import_superstatic.sourceToRegex)(hostname || "").segments.forEach(
(name) => destinationSegments.add(name)
);
(0, import_superstatic.sourceToRegex)(pathname || "").segments.forEach(
(name) => destinationSegments.add(name)
);
for (const strOrArray of Object.values(query)) {
const value = Array.isArray(strOrArray) ? strOrArray[0] : strOrArray;
(0, import_superstatic.sourceToRegex)(value || "").segments.forEach(
(name) => destinationSegments.add(name)
);
}
} catch (_err) {
}
const hasSegments = (0, import_superstatic.collectHasSegments)(has);
for (const segment of destinationSegments) {
if (!sourceSegments.has(segment) && !hasSegments.includes(segment)) {
return {
message: `${type} at index ${index} has segment ":${segment}" in \`destination\` property but not in \`source\` or \`has\` property.`,
link: "https://vercel.link/invalid-route-destination-segment"
};
}
}
}
return null;
}
function checkRedirect(r, index) {
if (typeof r.permanent !== "undefined" && typeof r.statusCode !== "undefined") {
return `Redirect at index ${index} cannot define both \`permanent\` and \`statusCode\` properties.`;
}
return null;
}
function createError(code, allErrors, link, action) {
const errors = Array.isArray(allErrors) ? allErrors : [allErrors];
const message = errors[0];
const error = {
name: "RouteApiError",
code,
message,
link,
action,
errors
};
return error;
}
function notEmpty(value) {
return value !== null && value !== void 0;
}
function getTransformedRoutes(vercelConfig) {
const { cleanUrls, rewrites, redirects, headers, trailingSlash } = vercelConfig;
const { routes: userRoutes = null } = vercelConfig;
let routes = null;
if (typeof cleanUrls !== "undefined") {
const normalized = normalizeRoutes(
(0, import_superstatic.convertCleanUrls)(cleanUrls, trailingSlash)
);
if (normalized.error) {
normalized.error.code = "invalid_clean_urls";
return { routes, error: normalized.error };
}
routes = routes || [];
routes.push(...normalized.routes || []);
}
if (typeof trailingSlash !== "undefined") {
const normalized = normalizeRoutes((0, import_superstatic.convertTrailingSlash)(trailingSlash));
if (normalized.error) {
normalized.error.code = "invalid_trailing_slash";
return { routes, error: normalized.error };
}
routes = routes || [];
routes.push(...normalized.routes || []);
}
if (userRoutes) {
const normalized = normalizeRoutes(userRoutes);
if (normalized.error) {
return { routes, error: normalized.error };
}
routes = routes || [];
routes.push(...normalized.routes || []);
}
if (typeof redirects !== "undefined") {
const code = "invalid_redirect";
const regexErrorMessage = redirects.map((r, i) => checkRegexSyntax("Redirect", i, r.source)).find(notEmpty);
if (regexErrorMessage) {
return {
routes,
error: createError(
"invalid_redirect",
regexErrorMessage,
"https://vercel.link/invalid-route-source-pattern",
"Learn More"
)
};
}
const patternError = redirects.map((r, i) => checkPatternSyntax("Redirect", i, r)).find(notEmpty);
if (patternError) {
return {
routes,
error: createError(
code,
patternError.message,
patternError.link,
"Learn More"
)
};
}
const redirectErrorMessage = redirects.map(checkRedirect).find(notEmpty);
if (redirectErrorMessage) {
return {
routes,
error: createError(
code,
redirectErrorMessage,
"https://vercel.link/redirects-json",
"Learn More"
)
};
}
const normalized = normalizeRoutes((0, import_superstatic.convertRedirects)(redirects));
if (normalized.error) {
normalized.error.code = code;
return { routes, error: normalized.error };
}
routes = routes || [];
routes.push(...normalized.routes || []);
}
if (typeof headers !== "undefined") {
const code = "invalid_header";
const regexErrorMessage = headers.map((r, i) => checkRegexSyntax("Header", i, r.source)).find(notEmpty);
if (regexErrorMessage) {
return {
routes,
error: createError(
code,
regexErrorMessage,
"https://vercel.link/invalid-route-source-pattern",
"Learn More"
)
};
}
const patternError = headers.map((r, i) => checkPatternSyntax("Header", i, r)).find(notEmpty);
if (patternError) {
return {
routes,
error: createError(
code,
patternError.message,
patternError.link,
"Learn More"
)
};
}
const normalized = normalizeRoutes((0, import_superstatic.convertHeaders)(headers));
if (normalized.error) {
normalized.error.code = code;
return { routes, error: normalized.error };
}
routes = routes || [];
routes.push(...normalized.routes || []);
}
if (typeof rewrites !== "undefined") {
const code = "invalid_rewrite";
const regexErrorMessage = rewrites.map((r, i) => checkRegexSyntax("Rewrite", i, r.source)).find(notEmpty);
if (regexErrorMessage) {
return {
routes,
error: createError(
code,
regexErrorMessage,
"https://vercel.link/invalid-route-source-pattern",
"Learn More"
)
};
}
const patternError = rewrites.map((r, i) => checkPatternSyntax("Rewrite", i, r)).find(notEmpty);
if (patternError) {
return {
routes,
error: createError(
code,
patternError.message,
patternError.link,
"Learn More"
)
};
}
const normalized = normalizeRoutes((0, import_superstatic.convertRewrites)(rewrites));
if (normalized.error) {
normalized.error.code = code;
return { routes, error: normalized.error };
}
routes = routes || [];
routes.push({ handle: "filesystem" });
routes.push(...normalized.routes || []);
}
return { routes, error: null };
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
appendRoutesToPhase,
getCleanUrls,
getOwnershipGuard,
getTransformedRoutes,
isHandler,
isValidHandleValue,
mergeRoutes,
normalizeRoutePrefix,
normalizeRoutes,
scopeRouteSourceToOwnership,
sourceToRegex,
...require("./schemas"),
...require("./types")
});

Sorry, the diff of this file is too big to display