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

@vercel/config

Package Overview
Dependencies
Maintainers
336
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vercel/config - npm Package Compare versions

Comparing version
0.0.14
to
0.0.16
+9
dist/v1/index.d.ts
/**
* @vercel/config/v1 - Main API entry point
*
* Usage:
* import { createRouter, VercelConfig } from '@vercel/config/v1';
*/
export { createRouter, Router } from "../router";
export * from "../router";
export type { VercelConfig, Redirect, Rewrite, HeaderRule, Condition, RouteType } from "../types";
"use strict";
/**
* @vercel/config/v1 - Main API entry point
*
* Usage:
* import { createRouter, VercelConfig } from '@vercel/config/v1';
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Router = exports.createRouter = void 0;
var router_1 = require("../router");
Object.defineProperty(exports, "createRouter", { enumerable: true, get: function () { return router_1.createRouter; } });
Object.defineProperty(exports, "Router", { enumerable: true, get: function () { return router_1.Router; } });
__exportStar(require("../router"), exports);
+57
-28

@@ -30,2 +30,3 @@ #!/usr/bin/env node

const fs_2 = require("fs");
const validation_1 = require("./utils/validation");
/**

@@ -72,29 +73,40 @@ * Named exports that should NOT be auto-converted to config

/**
* Read existing vercel.json and extract fields to preserve
* Compile vercel.ts to JSON and output to stdout
*/
function readExistingVercelConfig() {
const vercelJsonPath = (0, path_1.resolve)(process.cwd(), "vercel.json");
if (!(0, fs_2.existsSync)(vercelJsonPath)) {
return {};
async function compileConfig() {
try {
const config = await configureRouter();
const json = JSON.stringify(config, null, 2);
console.log(json);
}
catch (error) {
console.error("Failed to compile config:", error);
process.exit(1);
}
}
/**
* Validate the vercel.ts config
*/
async function validateConfig() {
var _a, _b, _c, _d;
try {
const content = (0, fs_1.readFileSync)(vercelJsonPath, "utf-8");
const existing = JSON.parse(content);
// Extract fields we want to preserve
const preserved = {};
if (existing.buildCommand) {
preserved.buildCommand = existing.buildCommand;
}
if (existing.installCommand) {
preserved.installCommand = existing.installCommand;
}
return preserved;
const config = await configureRouter();
// Validate static fields
(0, validation_1.validateStaticFields)(config);
console.log("✓ Config is valid");
console.log(` - buildCommand: ${config.buildCommand || '(not set)'}`);
console.log(` - framework: ${config.framework || '(not set)'}`);
console.log(` - routes: ${((_a = config.routes) === null || _a === void 0 ? void 0 : _a.length) || 0} route(s)`);
console.log(` - redirects: ${((_b = config.redirects) === null || _b === void 0 ? void 0 : _b.length) || 0} redirect(s)`);
console.log(` - rewrites: ${((_c = config.rewrites) === null || _c === void 0 ? void 0 : _c.length) || 0} rewrite(s)`);
console.log(` - headers: ${((_d = config.headers) === null || _d === void 0 ? void 0 : _d.length) || 0} header(s)`);
}
catch (error) {
console.warn("Could not read existing vercel.json:", error);
return {};
console.error("✗ Config validation failed:");
console.error(` ${error}`);
process.exit(1);
}
}
/**
* Generates the vercel.json file
* Generate vercel.json file (for backwards compatibility / development)
*/

@@ -104,11 +116,5 @@ async function generateVercelConfig() {

const config = await configureRouter();
const existingFields = readExistingVercelConfig();
// Merge: generated config takes precedence, but preserve existing build/install commands if not set
const mergedConfig = {
...existingFields,
...config,
};
const vercelConfig = JSON.stringify(mergedConfig, null, 2);
const json = JSON.stringify(config, null, 2);
const outputPath = (0, path_1.resolve)(process.cwd(), "vercel.json");
(0, fs_1.writeFileSync)(outputPath, vercelConfig);
(0, fs_1.writeFileSync)(outputPath, json);
console.log("Successfully generated vercel.json");

@@ -121,5 +127,28 @@ }

}
/**
* CLI entry point
*/
async function main() {
const command = process.argv[2];
switch (command) {
case 'compile':
await compileConfig();
break;
case 'validate':
await validateConfig();
break;
case 'generate':
case undefined:
// Default to generate for backwards compatibility
await generateVercelConfig();
break;
default:
console.error(`Unknown command: ${command}`);
console.error('Available commands: compile, validate, generate');
process.exit(1);
}
}
// Run if this file is executed directly
if (require.main === module) {
generateVercelConfig();
main();
}
export { createRouter, Router } from "./router";
export * from "./router";
export type { VercelConfig } from "./types";
export type { VercelConfig, Redirect, Rewrite, HeaderRule, Condition, RouteType } from "./types";
export { validateStaticString, validateStaticBoolean, validateStaticObject, validateStaticStringArray, validateStaticFields } from "./utils/validation";

@@ -17,3 +17,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.Router = exports.createRouter = void 0;
exports.validateStaticFields = exports.validateStaticStringArray = exports.validateStaticObject = exports.validateStaticBoolean = exports.validateStaticString = exports.Router = exports.createRouter = void 0;
var router_1 = require("./router");

@@ -23,1 +23,7 @@ Object.defineProperty(exports, "createRouter", { enumerable: true, get: function () { return router_1.createRouter; } });

__exportStar(require("./router"), exports);
var validation_1 = require("./utils/validation");
Object.defineProperty(exports, "validateStaticString", { enumerable: true, get: function () { return validation_1.validateStaticString; } });
Object.defineProperty(exports, "validateStaticBoolean", { enumerable: true, get: function () { return validation_1.validateStaticBoolean; } });
Object.defineProperty(exports, "validateStaticObject", { enumerable: true, get: function () { return validation_1.validateStaticObject; } });
Object.defineProperty(exports, "validateStaticStringArray", { enumerable: true, get: function () { return validation_1.validateStaticStringArray; } });
Object.defineProperty(exports, "validateStaticFields", { enumerable: true, get: function () { return validation_1.validateStaticFields; } });

@@ -0,1 +1,2 @@

import type { Redirect, Rewrite } from './types';
/**

@@ -547,8 +548,3 @@ * Helper function to reference a path parameter in transforms.

/**
* Helper to extract environment variable names from a string or string array.
* Environment variables are identified by the pattern $VAR_NAME where VAR_NAME
* is typically uppercase with underscores (e.g., $API_KEY, $BEARER_TOKEN).
*/
private extractEnvVars;
/**
* @deprecated No longer used after refactor to return schema objects directly
* Internal helper to convert TransformOptions to Transform array

@@ -558,6 +554,4 @@ * @param options Transform options to convert

*/
private transformOptionsToTransforms;
/**
* Adds a single rewrite rule (synchronous).
* Automatically enables rewrite caching by adding the x-vercel-enable-rewrite-caching header.
* Creates a rewrite rule. Returns either a Rewrite object (simple case) or Route with transforms.
*

@@ -568,20 +562,9 @@ * @example

*
* // With transforms using callback
* // With transforms
* router.rewrite('/users/:userId', 'https://api.example.com/users/$1', ({userId, env}) => ({
* requestHeaders: {
* 'x-user-id': userId,
* 'authorization': `Bearer ${env.API_TOKEN}`
* }
* }));
*
* // With transforms using object (legacy)
* router.rewrite('/users/:userId', 'https://api.example.com/users/$1', {
* requestHeaders: {
* 'x-user-id': param('userId')
* }
* });
* requestHeaders: { 'x-user-id': userId }
* }))
* @internal Can return Route with transforms internally
*/
rewrite(source: string, destination: string, optionsOrCallback?: {
methods?: string[];
status?: number;
has?: Condition[];

@@ -592,13 +575,5 @@ missing?: Condition[];

requestQuery?: Record<string, string | string[]>;
appendRequestHeaders?: Record<string, string | string[]>;
appendResponseHeaders?: Record<string, string | string[]>;
appendRequestQuery?: Record<string, string | string[]>;
deleteRequestHeaders?: string[];
deleteResponseHeaders?: string[];
deleteRequestQuery?: string[];
} | ((params: Record<string, string> & {
env: any;
}) => {
methods?: string[];
status?: number;
has?: Condition[];

@@ -609,40 +584,16 @@ missing?: Condition[];

requestQuery?: Record<string, string | string[]>;
appendRequestHeaders?: Record<string, string | string[]>;
appendResponseHeaders?: Record<string, string | string[]>;
appendRequestQuery?: Record<string, string | string[]>;
deleteRequestHeaders?: string[];
deleteResponseHeaders?: string[];
deleteRequestQuery?: string[];
})): this;
})): Rewrite | Route;
/**
* Loads rewrite rules asynchronously and appends them.
* Automatically enables rewrite caching for all loaded rules by adding the x-vercel-enable-rewrite-caching header.
* Creates a redirect rule. Returns either a Redirect object (simple case) or Route with transforms.
*
* @example
* // This will automatically enable caching for all rewrites
* await router.rewrites(() => fetchRewriteRulesFromDB());
*/
rewrites(provider: RewriteProvider): Promise<this>;
/**
* Adds a single redirect rule (synchronous).
* @example
* // Simple redirect
* router.redirect('/old-path', '/new-path', { permanent: true })
*
* // With transforms using callback
* // With transforms
* router.redirect('/users/:userId', '/new-users/$1', ({userId, env}) => ({
* permanent: true,
* requestHeaders: {
* 'x-user-id': userId,
* 'x-api-key': env.API_KEY
* }
* requestHeaders: { 'x-user-id': userId }
* }))
*
* // With transforms using object (legacy)
* router.redirect('/users/:userId', '/new-users/$1', {
* permanent: true,
* requestHeaders: {
* 'x-user-id': param('userId')
* }
* })
* @internal Can return Route with transforms internally
*/

@@ -655,10 +606,2 @@ redirect(source: string, destination: string, optionsOrCallback?: {

requestHeaders?: Record<string, string | string[]>;
responseHeaders?: Record<string, string | string[]>;
requestQuery?: Record<string, string | string[]>;
appendRequestHeaders?: Record<string, string | string[]>;
appendResponseHeaders?: Record<string, string | string[]>;
appendRequestQuery?: Record<string, string | string[]>;
deleteRequestHeaders?: string[];
deleteResponseHeaders?: string[];
deleteRequestQuery?: string[];
} | ((params: Record<string, string> & {

@@ -672,17 +615,5 @@ env: any;

requestHeaders?: Record<string, string | string[]>;
responseHeaders?: Record<string, string | string[]>;
requestQuery?: Record<string, string | string[]>;
appendRequestHeaders?: Record<string, string | string[]>;
appendResponseHeaders?: Record<string, string | string[]>;
appendRequestQuery?: Record<string, string | string[]>;
deleteRequestHeaders?: string[];
deleteResponseHeaders?: string[];
deleteRequestQuery?: string[];
})): this;
})): Redirect | Route;
/**
* Loads redirect rules asynchronously and appends them.
*/
redirects(provider: RedirectProvider): Promise<this>;
/**
* Adds a single header rule (synchronous).
* Creates a header rule matching the vercel.json schema.
* @example

@@ -694,16 +625,18 @@ * router.header('/api/(.*)', [{ key: 'X-Custom', value: 'HelloWorld' }])

missing?: Condition[];
}): this;
}): {
source: string;
headers: Header[];
has?: Condition[];
missing?: Condition[];
};
/**
* Loads header rules asynchronously and appends them.
*/
headers(provider: HeaderProvider): Promise<this>;
/**
* Adds a typed "Cache-Control" header, leveraging `pretty-cache-header`.
* This method is purely for convenience, so you can do:
* Creates a Cache-Control header rule, leveraging `pretty-cache-header`.
* Returns a HeaderRule matching the vercel.json schema.
*
* router.cacheControl('/my-page', {
* public: true,
* maxAge: '1week',
* staleWhileRevalidate: '1year'
* });
* @example
* router.cacheControl('/my-page', {
* public: true,
* maxAge: '1week',
* staleWhileRevalidate: '1year'
* })
*/

@@ -713,3 +646,8 @@ cacheControl(source: string, cacheOptions: CacheOptions, options?: {

missing?: Condition[];
}): this;
}): {
source: string;
headers: Header[];
has?: Condition[];
missing?: Condition[];
};
/**

@@ -716,0 +654,0 @@ * Adds a route with transforms support.

@@ -114,21 +114,5 @@ "use strict";

}
// Deprecated: extractEnvVars method no longer needed after refactor
/**
* Helper to extract environment variable names from a string or string array.
* Environment variables are identified by the pattern $VAR_NAME where VAR_NAME
* is typically uppercase with underscores (e.g., $API_KEY, $BEARER_TOKEN).
*/
extractEnvVars(args) {
const envVars = new Set();
const values = Array.isArray(args) ? args : [args];
for (const value of values) {
const matches = value.match(/\$([A-Z][A-Z0-9_]*)/g);
if (matches) {
for (const match of matches) {
envVars.add(match.substring(1));
}
}
}
return Array.from(envVars);
}
/**
* @deprecated No longer used after refactor to return schema objects directly
* Internal helper to convert TransformOptions to Transform array

@@ -138,130 +122,5 @@ * @param options Transform options to convert

*/
transformOptionsToTransforms(options, trackedEnvVars) {
const transforms = [];
// Helper to get env vars for a value
const getEnvVars = (value) => {
if (trackedEnvVars) {
return Array.from(trackedEnvVars).filter(envVar => {
const valueStr = Array.isArray(value) ? value.join(' ') : value;
return valueStr.includes(`$${envVar}`);
});
}
return this.extractEnvVars(value);
};
// SET operations
// Convert requestHeaders (set)
if (options.requestHeaders) {
for (const [key, value] of Object.entries(options.requestHeaders)) {
const envVars = getEnvVars(value);
transforms.push({
type: 'request.headers',
op: 'set',
target: { key },
args: value,
...(envVars.length > 0 && { env: envVars }),
});
}
}
// Convert responseHeaders (set)
if (options.responseHeaders) {
for (const [key, value] of Object.entries(options.responseHeaders)) {
const envVars = getEnvVars(value);
transforms.push({
type: 'response.headers',
op: 'set',
target: { key },
args: value,
...(envVars.length > 0 && { env: envVars }),
});
}
}
// Convert requestQuery (set)
if (options.requestQuery) {
for (const [key, value] of Object.entries(options.requestQuery)) {
const envVars = getEnvVars(value);
transforms.push({
type: 'request.query',
op: 'set',
target: { key },
args: value,
...(envVars.length > 0 && { env: envVars }),
});
}
}
// APPEND operations
// Convert appendRequestHeaders
if (options.appendRequestHeaders) {
for (const [key, value] of Object.entries(options.appendRequestHeaders)) {
const envVars = getEnvVars(value);
transforms.push({
type: 'request.headers',
op: 'append',
target: { key },
args: value,
...(envVars.length > 0 && { env: envVars }),
});
}
}
// Convert appendResponseHeaders
if (options.appendResponseHeaders) {
for (const [key, value] of Object.entries(options.appendResponseHeaders)) {
const envVars = getEnvVars(value);
transforms.push({
type: 'response.headers',
op: 'append',
target: { key },
args: value,
...(envVars.length > 0 && { env: envVars }),
});
}
}
// Convert appendRequestQuery
if (options.appendRequestQuery) {
for (const [key, value] of Object.entries(options.appendRequestQuery)) {
const envVars = getEnvVars(value);
transforms.push({
type: 'request.query',
op: 'append',
target: { key },
args: value,
...(envVars.length > 0 && { env: envVars }),
});
}
}
// DELETE operations
// Convert deleteRequestHeaders
if (options.deleteRequestHeaders) {
for (const key of options.deleteRequestHeaders) {
transforms.push({
type: 'request.headers',
op: 'delete',
target: { key },
});
}
}
// Convert deleteResponseHeaders
if (options.deleteResponseHeaders) {
for (const key of options.deleteResponseHeaders) {
transforms.push({
type: 'response.headers',
op: 'delete',
target: { key },
});
}
}
// Convert deleteRequestQuery
if (options.deleteRequestQuery) {
for (const key of options.deleteRequestQuery) {
transforms.push({
type: 'request.query',
op: 'delete',
target: { key },
});
}
}
return transforms;
}
// Deprecated: transformOptionsToTransforms method removed after refactor
/**
* Adds a single rewrite rule (synchronous).
* Automatically enables rewrite caching by adding the x-vercel-enable-rewrite-caching header.
* Creates a rewrite rule. Returns either a Rewrite object (simple case) or Route with transforms.
*

@@ -272,16 +131,7 @@ * @example

*
* // With transforms using callback
* // With transforms
* router.rewrite('/users/:userId', 'https://api.example.com/users/$1', ({userId, env}) => ({
* requestHeaders: {
* 'x-user-id': userId,
* 'authorization': `Bearer ${env.API_TOKEN}`
* }
* }));
*
* // With transforms using object (legacy)
* router.rewrite('/users/:userId', 'https://api.example.com/users/$1', {
* requestHeaders: {
* 'x-user-id': param('userId')
* }
* });
* requestHeaders: { 'x-user-id': userId }
* }))
* @internal Can return Route with transforms internally
*/

@@ -292,8 +142,6 @@ rewrite(source, destination, optionsOrCallback) {

let options;
let trackedEnvVars;
// Handle callback syntax
if (typeof optionsOrCallback === 'function') {
const pathParams = this.extractPathParams(source);
const { proxy: envProxy, accessedVars } = this.createEnvProxy();
trackedEnvVars = accessedVars;
const { proxy: envProxy } = this.createEnvProxy();
// Create params object with path parameters as $paramName

@@ -311,86 +159,73 @@ const paramsObj = {};

}
// Extract transform options
const { methods, status, requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, has, missing } = options || {};
const transformOpts = {
requestHeaders,
responseHeaders,
requestQuery,
appendRequestHeaders,
appendResponseHeaders,
appendRequestQuery,
deleteRequestHeaders,
deleteResponseHeaders,
deleteRequestQuery,
};
// Convert to transforms if any transform options provided
const hasTransforms = requestHeaders || responseHeaders || requestQuery ||
appendRequestHeaders || appendResponseHeaders || appendRequestQuery ||
deleteRequestHeaders || deleteResponseHeaders || deleteRequestQuery;
const transforms = hasTransforms
? this.transformOptionsToTransforms(transformOpts, trackedEnvVars)
: undefined;
this.rewriteRules.push({
const { has, missing, requestHeaders, responseHeaders, requestQuery } = options || {};
// Check if any transforms were provided
const hasTransforms = requestHeaders || responseHeaders || requestQuery;
if (hasTransforms) {
// Build a Route object with transforms
const transforms = [];
if (requestHeaders) {
for (const [key, value] of Object.entries(requestHeaders)) {
transforms.push({
type: 'request.headers',
op: 'set',
target: { key },
args: value,
});
}
}
if (responseHeaders) {
for (const [key, value] of Object.entries(responseHeaders)) {
transforms.push({
type: 'response.headers',
op: 'set',
target: { key },
args: value,
});
}
}
if (requestQuery) {
for (const [key, value] of Object.entries(requestQuery)) {
transforms.push({
type: 'request.query',
op: 'set',
target: { key },
args: value,
});
}
}
const route = {
src: source,
dest: destination,
transforms,
};
if (has)
route.has = has;
if (missing)
route.missing = missing;
return route;
}
// Simple rewrite without transforms
const rewrite = {
source,
destination,
...(methods && { methods }),
...(status && { status }),
has,
missing,
transforms,
});
// Only enable rewrite caching for rewrites without transforms
// (transforms convert to routes, which don't need the caching header)
if (!transforms) {
this.headerRules.push({
source,
headers: [{ key: 'x-vercel-enable-rewrite-caching', value: '1' }],
has,
missing,
});
}
return this;
};
if (has)
rewrite.has = has;
if (missing)
rewrite.missing = missing;
return rewrite;
}
/**
* Loads rewrite rules asynchronously and appends them.
* Automatically enables rewrite caching for all loaded rules by adding the x-vercel-enable-rewrite-caching header.
* Creates a redirect rule. Returns either a Redirect object (simple case) or Route with transforms.
*
* @example
* // This will automatically enable caching for all rewrites
* await router.rewrites(() => fetchRewriteRulesFromDB());
*/
async rewrites(provider) {
const rules = await provider();
this.rewriteRules.push(...rules);
// Automatically enable rewrite caching for all rules
const headerRules = rules.map((rule) => ({
source: rule.source,
headers: [{ key: 'x-vercel-enable-rewrite-caching', value: '1' }],
has: rule.has,
missing: rule.missing,
}));
this.headerRules.push(...headerRules);
return this;
}
/**
* Adds a single redirect rule (synchronous).
* @example
* // Simple redirect
* router.redirect('/old-path', '/new-path', { permanent: true })
*
* // With transforms using callback
* // With transforms
* router.redirect('/users/:userId', '/new-users/$1', ({userId, env}) => ({
* permanent: true,
* requestHeaders: {
* 'x-user-id': userId,
* 'x-api-key': env.API_KEY
* }
* requestHeaders: { 'x-user-id': userId }
* }))
*
* // With transforms using object (legacy)
* router.redirect('/users/:userId', '/new-users/$1', {
* permanent: true,
* requestHeaders: {
* 'x-user-id': param('userId')
* }
* })
* @internal Can return Route with transforms internally
*/

@@ -401,8 +236,6 @@ redirect(source, destination, optionsOrCallback) {

let options;
let trackedEnvVars;
// Handle callback syntax
if (typeof optionsOrCallback === 'function') {
const pathParams = this.extractPathParams(source);
const { proxy: envProxy, accessedVars } = this.createEnvProxy();
trackedEnvVars = accessedVars;
const { proxy: envProxy } = this.createEnvProxy();
// Create params object with path parameters as $paramName

@@ -420,54 +253,45 @@ const paramsObj = {};

}
// Extract transform options
const { methods, requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, permanent, statusCode, has, missing, } = options || {};
// If transforms are provided, create a route instead of a redirect
const hasTransforms = requestHeaders || responseHeaders || requestQuery ||
appendRequestHeaders || appendResponseHeaders || appendRequestQuery ||
deleteRequestHeaders || deleteResponseHeaders || deleteRequestQuery;
if (hasTransforms) {
const transformOpts = {
requestHeaders,
responseHeaders,
requestQuery,
appendRequestHeaders,
appendResponseHeaders,
appendRequestQuery,
deleteRequestHeaders,
deleteResponseHeaders,
deleteRequestQuery,
};
const transforms = this.transformOptionsToTransforms(transformOpts, trackedEnvVars);
this.routeRules.push({
const { permanent, statusCode, has, missing, requestHeaders } = options || {};
// Check if transforms were provided
if (requestHeaders) {
// Build a Route object with transforms
const transforms = [];
for (const [key, value] of Object.entries(requestHeaders)) {
transforms.push({
type: 'request.headers',
op: 'set',
target: { key },
args: value,
});
}
const route = {
src: source,
dest: destination,
...(methods && { methods }),
transforms,
redirect: true,
status: statusCode || (permanent ? 308 : 307),
has,
missing,
});
transforms,
};
if (has)
route.has = has;
if (missing)
route.missing = missing;
return route;
}
else {
this.redirectRules.push({
source,
destination,
permanent,
statusCode,
has,
missing,
});
}
return this;
// Simple redirect without transforms
const redirect = {
source,
destination,
};
if (permanent !== undefined)
redirect.permanent = permanent;
if (statusCode !== undefined)
redirect.statusCode = statusCode;
if (has)
redirect.has = has;
if (missing)
redirect.missing = missing;
return redirect;
}
/**
* Loads redirect rules asynchronously and appends them.
*/
async redirects(provider) {
const rules = await provider();
this.redirectRules.push(...rules);
return this;
}
/**
* Adds a single header rule (synchronous).
* Creates a header rule matching the vercel.json schema.
* @example

@@ -478,31 +302,23 @@ * router.header('/api/(.*)', [{ key: 'X-Custom', value: 'HelloWorld' }])

this.validateSourcePattern(source);
this.headerRules.push({ source, headers, ...options });
return this;
return { source, headers, ...options };
}
/**
* Loads header rules asynchronously and appends them.
*/
async headers(provider) {
const rules = await provider();
this.headerRules.push(...rules);
return this;
}
/**
* Adds a typed "Cache-Control" header, leveraging `pretty-cache-header`.
* This method is purely for convenience, so you can do:
* Creates a Cache-Control header rule, leveraging `pretty-cache-header`.
* Returns a HeaderRule matching the vercel.json schema.
*
* router.cacheControl('/my-page', {
* public: true,
* maxAge: '1week',
* staleWhileRevalidate: '1year'
* });
* @example
* router.cacheControl('/my-page', {
* public: true,
* maxAge: '1week',
* staleWhileRevalidate: '1year'
* })
*/
cacheControl(source, cacheOptions, options) {
this.validateSourcePattern(source);
const value = (0, pretty_cache_header_1.cacheHeader)(cacheOptions);
this.headerRules.push({
return {
source,
headers: [{ key: 'Cache-Control', value }],
...options,
});
return this;
};
}

@@ -509,0 +325,0 @@ /**

@@ -37,2 +37,5 @@ /**

}
/**
* HTTP header key/value pair
*/
export interface Header {

@@ -42,11 +45,56 @@ key: string;

}
/**
* Condition for matching in redirects, rewrites, and headers
*/
export interface Condition {
type: 'header' | 'cookie' | 'host' | 'query' | 'path';
key?: string;
value?: string | number;
inc?: string[];
pre?: string;
eq?: string | number;
neq?: string;
gt?: number;
gte?: number;
lt?: number;
lte?: number;
}
/**
* Redirect matching vercel.json schema
* Returned by router.redirect()
*/
export interface Redirect {
source: string;
destination: string;
permanent?: boolean;
statusCode?: number;
has?: Condition[];
missing?: Condition[];
}
/**
* Rewrite matching vercel.json schema
* Returned by router.rewrite()
*/
export interface Rewrite {
source: string;
destination: string;
has?: Condition[];
missing?: Condition[];
}
/**
* Header rule matching vercel.json schema
* Returned by router.header() and router.cacheControl()
*/
export interface HeaderRule {
source: string;
headers: Header[];
has?: Condition[];
missing?: Condition[];
}
/**
* Route represents a single routing rule that can be a redirect, rewrite, or header rule
* Matches the output of router.redirect(), router.rewrite(), router.header(), etc.
* Union type for all router helper outputs
* Can be simple schema objects (Redirect, Rewrite, HeaderRule) or Routes with transforms
* Note: Route type is defined in router.ts (uses src/dest instead of source/destination)
*/
export type RouteType = any;
export type RouteType = Redirect | Rewrite | HeaderRule | any;
export interface WildcardDomain {

@@ -109,2 +157,8 @@ domain: string;

/**
* Routes configuration using the lower-level routes primitive.
* Use this if you need transforms or want everything in one place.
* Cannot be mixed with headers, redirects, or rewrites.
*/
routes?: RouteType[];
/**
* Wildcard domain configuration.

@@ -111,0 +165,0 @@ */

@@ -44,1 +44,28 @@ /**

export declare function validateCaptureGroupReferences(source: string, destination: string): void;
/**
* Validates that a value is a static string literal (not computed, not a function call, etc.)
* Used for static fields that must be extracted before build execution.
*/
export declare function validateStaticString(value: any, fieldName: string): void;
/**
* Validates that a value is a static boolean literal
*/
export declare function validateStaticBoolean(value: any, fieldName: string): void;
/**
* Validates that a value is a static object with primitive values
* Used for git.deploymentEnabled and similar objects that need to be static
*/
export declare function validateStaticObject(value: any, fieldName: string): void;
/**
* Validates that a value is a static array of strings
*/
export declare function validateStaticStringArray(value: any, fieldName: string): void;
/**
* Validates static fields in VercelConfig that must be extracted before build execution.
* These fields include:
* - buildCommand, devCommand, installCommand, framework, nodeVersion, outputDirectory
* - github.enabled, github.autoAlias, github.autoJobCancelation
* - git.deploymentEnabled
* - relatedProjects
*/
export declare function validateStaticFields(config: Record<string, any>): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateCaptureGroupReferences = exports.countCaptureGroups = exports.createCronExpression = exports.parseCronExpression = exports.validateRegexPattern = void 0;
exports.validateStaticFields = exports.validateStaticStringArray = exports.validateStaticObject = exports.validateStaticBoolean = exports.validateStaticString = exports.validateCaptureGroupReferences = exports.countCaptureGroups = exports.createCronExpression = exports.parseCronExpression = exports.validateRegexPattern = void 0;
const zod_1 = require("zod");

@@ -125,1 +125,96 @@ /**

exports.validateCaptureGroupReferences = validateCaptureGroupReferences;
/**
* Validates that a value is a static string literal (not computed, not a function call, etc.)
* Used for static fields that must be extracted before build execution.
*/
function validateStaticString(value, fieldName) {
if (typeof value !== 'string') {
throw new Error(`Field "${fieldName}" must be a static string literal. ` +
`Got ${typeof value}. Function calls, variables, and expressions are not allowed.`);
}
}
exports.validateStaticString = validateStaticString;
/**
* Validates that a value is a static boolean literal
*/
function validateStaticBoolean(value, fieldName) {
if (typeof value !== 'boolean') {
throw new Error(`Field "${fieldName}" must be a static boolean literal. ` +
`Got ${typeof value}. Only true or false are allowed.`);
}
}
exports.validateStaticBoolean = validateStaticBoolean;
/**
* Validates that a value is a static object with primitive values
* Used for git.deploymentEnabled and similar objects that need to be static
*/
function validateStaticObject(value, fieldName) {
if (typeof value !== 'object' || value === null) {
throw new Error(`Field "${fieldName}" must be a static object with primitive values. ` +
`Got ${typeof value}.`);
}
for (const [key, val] of Object.entries(value)) {
if (typeof val !== 'boolean' && typeof val !== 'string' && typeof val !== 'number') {
throw new Error(`Field "${fieldName}.${key}" must contain only static primitive values (string, number, boolean). ` +
`Got ${typeof val}.`);
}
}
}
exports.validateStaticObject = validateStaticObject;
/**
* Validates that a value is a static array of strings
*/
function validateStaticStringArray(value, fieldName) {
if (!Array.isArray(value)) {
throw new Error(`Field "${fieldName}" must be a static array of strings. ` +
`Got ${typeof value}.`);
}
for (let i = 0; i < value.length; i++) {
if (typeof value[i] !== 'string') {
throw new Error(`Field "${fieldName}[${i}]" must be a static string. ` +
`Got ${typeof value[i]}.`);
}
}
}
exports.validateStaticStringArray = validateStaticStringArray;
/**
* Validates static fields in VercelConfig that must be extracted before build execution.
* These fields include:
* - buildCommand, devCommand, installCommand, framework, nodeVersion, outputDirectory
* - github.enabled, github.autoAlias, github.autoJobCancelation
* - git.deploymentEnabled
* - relatedProjects
*/
function validateStaticFields(config) {
// Validate string fields
const stringFields = ['buildCommand', 'devCommand', 'installCommand', 'framework', 'nodeVersion', 'outputDirectory'];
for (const field of stringFields) {
if (config[field] !== undefined && config[field] !== null) {
validateStaticString(config[field], field);
}
}
// Validate relatedProjects (array of strings)
if (config.relatedProjects !== undefined) {
validateStaticStringArray(config.relatedProjects, 'relatedProjects');
}
// Validate git.deploymentEnabled (boolean or object with branch booleans)
if (config.git !== undefined && config.git.deploymentEnabled !== undefined) {
const deploymentEnabled = config.git.deploymentEnabled;
if (typeof deploymentEnabled === 'boolean') {
validateStaticBoolean(deploymentEnabled, 'git.deploymentEnabled');
}
else {
validateStaticObject(deploymentEnabled, 'git.deploymentEnabled');
}
}
// Validate github fields (booleans)
const githubBooleanFields = ['enabled', 'autoAlias', 'autoJobCancelation'];
if (config.github !== undefined) {
for (const field of githubBooleanFields) {
if (config.github[field] !== undefined) {
validateStaticBoolean(config.github[field], `github.${field}`);
}
}
}
}
exports.validateStaticFields = validateStaticFields;
{
"name": "@vercel/config",
"version": "0.0.14",
"version": "0.0.16",
"description": "A TypeScript SDK for programmatically generating Vercel configuration files",

@@ -16,2 +16,16 @@ "bugs": {

"types": "dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./v1": "./dist/v1/index.js"
},
"typesVersions": {
"*": {
".": [
"dist/index.d.ts"
],
"v1": [
"dist/v1/index.d.ts"
]
}
},
"bin": {

@@ -18,0 +32,0 @@ "@vercel/config": "./dist/cli.js"

+75
-298
# @vercel/config
TypeScript SDK for defining Vercel configuration programmatically.
TypeScript SDK for programmatically defining Vercel configuration. Write type-safe routing rules and build configuration in TypeScript instead of JSON.

@@ -11,3 +11,3 @@ ## Installation

## Usage
## Quick Start

@@ -17,330 +17,107 @@ Create a `vercel.ts` file in your project root:

```typescript
import { createRouter } from '@vercel/config';
import { createRouter, VercelConfig } from '@vercel/config/v1';
const router = createRouter();
// Basic rewrite
router.rewrite('/api/(.*)', 'https://backend.com/$1');
export const config: VercelConfig = {
buildCommand: 'npm run build',
framework: 'nextjs',
routes: [
// Headers
router.cacheControl('/static/(.*)', {
public: true,
maxAge: '1 week',
immutable: true
}),
// Rewrites with transforms
router.rewrite('/users/:userId', 'https://api.example.com/users/$1',
({ userId, env }) => ({
requestHeaders: {
'x-user-id': userId,
'authorization': `Bearer ${env.API_TOKEN}`
}
})
),
// Simple redirects
router.redirect('/old-docs', '/docs', { permanent: true })
],
// Rewrite with transforms
router.rewrite('/users/:userId', 'https://api.example.com/users/$1', ({userId, env}) => ({
requestHeaders: {
'x-user-id': userId,
'authorization': `Bearer ${env.API_TOKEN}`
}
}));
// Redirects
router.redirect('/old-docs', '/docs', { permanent: true });
// Cache control
router.cacheControl('/static/(.*)', {
public: true,
maxAge: '1 week',
immutable: true
});
// Global settings
router.cleanUrls = true;
router.trailingSlash = true;
// Bulk redirects
router.bulkRedirectsPath = './bulkRedirectsDemo.json';
// Cron jobs
router.cron('/api/cleanup', '0 0 * * *');
export default router.getConfig();
```
## Automatic Compilation
Your `vercel.ts` file is automatically compiled when you run:
```bash
vercel build # For local builds
vercel dev # For local development
vercel deploy # For deployment
```
The Vercel CLI automatically compiles `vercel.ts` to `.vercel/vercel.json` before building or deploying.
## API Reference
### `createRouter()`
Creates a new router instance.
### `router.rewrite(source, destination, options?)`
Add a rewrite rule. Options can be an object or a callback function that receives path parameters and environment variables.
```typescript
// With callback for transforms
router.rewrite('/users/:userId', 'https://api.example.com/users/$1', ({userId, env}) => ({
requestHeaders: {
'x-user-id': userId,
'authorization': `Bearer ${env.API_TOKEN}`
},
responseHeaders: {
'x-powered-by': 'My API'
},
requestQuery: {
'version': '2.0'
}
}));
// Simple rewrite without transforms
router.rewrite('/api/(.*)', 'https://backend.com/$1');
```
### `router.redirect(source, destination, options?)`
Add a redirect rule. Options include `permanent` and `statusCode`.
```typescript
router.redirect('/old-page', '/new-page', { permanent: true });
router.redirect('/temp', '/elsewhere', { statusCode: 302 });
```
### `router.header(source, headers, options?)`
Add custom headers for a path pattern.
```typescript
router.header('/api/(.*)', [
{ key: 'X-Custom-Header', value: 'value' }
]);
```
### `router.cacheControl(source, options)`
Set cache control headers. Options include `public`, `private`, `maxAge`, `sMaxAge`, `immutable`, etc.
```typescript
router.cacheControl('/static/(.*)', {
public: true,
maxAge: '1 week',
immutable: true
});
```
### `router.cleanUrls`
Set whether to enable clean URLs (removes file extensions).
```typescript
router.cleanUrls = true;
```
### `router.trailingSlash`
Set whether to normalize paths to include trailing slashes.
```typescript
router.trailingSlash = true;
```
### `router.bulkRedirectsPath`
Set the path to a bulk redirects JSON file.
```typescript
router.bulkRedirectsPath = './bulkRedirectsDemo.json';
```
## Conditional Routing
The SDK supports powerful conditional routing using `has` and `missing` conditions. These conditions can be added to rewrites, redirects, headers, and cache control rules.
### Condition Types
- `header`: Match HTTP headers
- `cookie`: Match cookies
- `host`: Match the request host
- `query`: Match query parameters
- `path`: Match the request path pattern
### Simple Presence Check
```typescript
// Only rewrite if x-api-key header exists
router.rewrite('/api/(.*)', 'https://backend.com/$1', {
has: [
{ type: 'header', key: 'x-api-key' }
crons: [
{ path: '/api/cleanup', schedule: '0 0 * * *' }
]
});
// Redirect if auth-token cookie is missing
router.redirect('/dashboard', '/login', {
missing: [
{ type: 'cookie', key: 'auth-token' }
]
});
};
```
### Conditional Operators
## Features
The SDK supports advanced matching operators for more complex conditions:
- **Type-safe configuration** - Full TypeScript support with IDE autocomplete
- **Readable syntax** - Helper methods like `router.redirect()`, `router.rewrite()`, `router.header()`
- **Static validation** - Catch configuration errors at development time
- **Transforms** - Modify request/response headers and query parameters on the fly
- **Conditions** - Advanced routing with `has` and `missing` conditions
- **CLI tools** - `compile` and `validate` commands for development
#### Equality Operators
## Build-Time Compilation
```typescript
// Exact match using 'eq'
router.rewrite('/api/(.*)', 'https://backend.com/$1', {
has: [
{ type: 'header', key: 'x-api-version', eq: 'v2' }
]
});
// Not equal using 'neq'
router.redirect('/beta/(.*)', '/stable/$1', {
has: [
{ type: 'cookie', key: 'beta-access', neq: 'granted' }
]
});
Your `vercel.ts` is automatically compiled to `vercel.json` during:
```bash
vercel build
vercel dev
vercel deploy
```
#### Inclusion Operators
No manual build step needed - the Vercel CLI handles compilation automatically.
```typescript
// Must be one of (inclusion)
router.rewrite('/admin/(.*)', 'https://admin.backend.com/$1', {
has: [
{ type: 'header', key: 'x-user-role', inc: ['admin', 'moderator', 'superuser'] }
]
});
## CLI Commands
// Must NOT be one of (non-inclusion)
router.redirect('/public/(.*)', '/private/$1', {
has: [
{ type: 'header', key: 'x-user-role', ninc: ['guest', 'anonymous'] }
]
});
```
For development and validation:
#### String Pattern Operators
```bash
# Compile vercel.ts to JSON (output to stdout)
npx @vercel/config compile
```typescript
// Starts with (prefix)
router.rewrite('/staging/(.*)', 'https://staging.backend.com/$1', {
has: [
{ type: 'cookie', key: 'session', pre: 'staging-' }
]
});
# Validate config for errors and show summary
npx @vercel/config validate
// Ends with (suffix)
router.redirect('/dev/(.*)', '/development/$1', {
has: [
{ type: 'header', key: 'x-environment', suf: '-dev' }
]
});
# Generate vercel.json locally (for development)
npx @vercel/config generate
```
#### Numeric Comparison Operators
## Validation
```typescript
// Greater than
router.rewrite('/api/v3/(.*)', 'https://api-v3.backend.com/$1', {
has: [
{ type: 'query', key: 'version', gt: 2 }
]
});
Static fields are validated to ensure they only contain literal values (strings, booleans, objects with primitives):
// Greater than or equal
router.rewrite('/premium/(.*)', '/premium-content/$1', {
has: [
{ type: 'header', key: 'x-subscription-tier', gte: 3 }
]
});
// Less than
router.redirect('/legacy/(.*)', '/upgrade/$1', {
has: [
{ type: 'query', key: 'api-version', lt: 2 }
]
});
// Less than or equal
router.rewrite('/free/(.*)', '/free-tier/$1', {
has: [
{ type: 'header', key: 'x-plan', lte: 1 }
]
});
```
### Host and Path Matching
```typescript
// Host matching (no key required)
router.redirect('/(.*)', 'https://www.example.com/$1', {
has: [
{ type: 'host', value: 'example.com' }
]
});
// ✅ Valid - static string
export const buildCommand = 'npm run build';
// Path pattern matching (no key required)
router.rewrite('/(.*)', '/internal/$1', {
has: [
{ type: 'path', value: '^/api/v[0-9]+/.*' }
]
});
```
// ❌ Invalid - computed value
export const buildCommand = process.env.BUILD_CMD;
### Multiple Conditions
All conditions in a `has` or `missing` array must match (AND logic):
```typescript
router.rewrite('/secure/(.*)', 'https://secure.backend.com/$1', {
has: [
{ type: 'header', key: 'x-api-key' },
{ type: 'header', key: 'x-user-role', inc: ['admin', 'superuser'] },
{ type: 'cookie', key: 'session', pre: 'secure-' },
{ type: 'query', key: 'version', gte: 2 }
]
});
```
### Combining with Transforms
You can combine conditions with transforms for powerful routing logic:
```typescript
router.rewrite('/api/users/:userId', 'https://backend.com/users/$1', ({ userId, env }) => ({
has: [
{ type: 'header', key: 'authorization', pre: 'Bearer ' },
{ type: 'header', key: 'x-api-version', gte: 2 }
],
missing: [
{ type: 'header', key: 'x-deprecated-header' }
],
requestHeaders: {
'x-user-id': userId,
'x-internal-key': env.INTERNAL_API_KEY
// ✅ Valid - static object
export const git = {
deploymentEnabled: {
'main': true,
'dev': false
}
}));
};
```
### Available Operators
Static fields that are validated:
- `buildCommand`, `devCommand`, `installCommand`
- `framework`, `nodeVersion`, `outputDirectory`
- `github.enabled`, `github.autoAlias`, `github.autoJobCancelation`
- `git.deploymentEnabled`
- `relatedProjects`
| Operator | Type | Description | Example |
|----------|------|-------------|---------|
| `eq` | string \| number | Exact equality match | `{ eq: 'v2' }` |
| `neq` | string | Not equal | `{ neq: 'guest' }` |
| `inc` | string[] | Value is one of | `{ inc: ['admin', 'mod'] }` |
| `ninc` | string[] | Value is not one of | `{ ninc: ['guest', 'banned'] }` |
| `pre` | string | Starts with prefix | `{ pre: 'Bearer ' }` |
| `suf` | string | Ends with suffix | `{ suf: '-dev' }` |
| `gt` | number | Greater than | `{ gt: 2 }` |
| `gte` | number | Greater than or equal | `{ gte: 3 }` |
| `lt` | number | Less than | `{ lt: 5 }` |
| `lte` | number | Less than or equal | `{ lte: 10 }` |
## Important Notes
- **One config file only**: You cannot have both `vercel.ts` and `vercel.json`. The build will fail if both exist.
- **Automatic gitignore**: The generated `.vercel/vercel.json` file is automatically ignored by git (in the `.vercel/` directory).
- **No manual compilation needed**: The Vercel CLI handles compilation automatically - no need to run a separate command.
- **Versioned API**: Use `@vercel/config/v1` for imports to enable future versioning.
## Learn More

@@ -347,0 +124,0 @@