@vercel/config 0.0.12 → 0.0.14
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/README.md +190 -2
- package/dist/cli.js +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/router.d.ts +76 -5
- package/dist/router.js +77 -12
- package/dist/types.d.ts +154 -0
- package/dist/types.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ router.redirect('/old-docs', '/docs', { permanent: true });
|
|
|
34
34
|
// Cache control
|
|
35
35
|
router.cacheControl('/static/(.*)', {
|
|
36
36
|
public: true,
|
|
37
|
-
maxAge: '
|
|
37
|
+
maxAge: '1 week',
|
|
38
38
|
immutable: true
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -118,7 +118,7 @@ Set cache control headers. Options include `public`, `private`, `maxAge`, `sMaxA
|
|
|
118
118
|
```typescript
|
|
119
119
|
router.cacheControl('/static/(.*)', {
|
|
120
120
|
public: true,
|
|
121
|
-
maxAge: '
|
|
121
|
+
maxAge: '1 week',
|
|
122
122
|
immutable: true
|
|
123
123
|
});
|
|
124
124
|
```
|
|
@@ -147,6 +147,194 @@ Set the path to a bulk redirects JSON file.
|
|
|
147
147
|
router.bulkRedirectsPath = './bulkRedirectsDemo.json';
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
## Conditional Routing
|
|
151
|
+
|
|
152
|
+
The SDK supports powerful conditional routing using `has` and `missing` conditions. These conditions can be added to rewrites, redirects, headers, and cache control rules.
|
|
153
|
+
|
|
154
|
+
### Condition Types
|
|
155
|
+
|
|
156
|
+
- `header`: Match HTTP headers
|
|
157
|
+
- `cookie`: Match cookies
|
|
158
|
+
- `host`: Match the request host
|
|
159
|
+
- `query`: Match query parameters
|
|
160
|
+
- `path`: Match the request path pattern
|
|
161
|
+
|
|
162
|
+
### Simple Presence Check
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// Only rewrite if x-api-key header exists
|
|
166
|
+
router.rewrite('/api/(.*)', 'https://backend.com/$1', {
|
|
167
|
+
has: [
|
|
168
|
+
{ type: 'header', key: 'x-api-key' }
|
|
169
|
+
]
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Redirect if auth-token cookie is missing
|
|
173
|
+
router.redirect('/dashboard', '/login', {
|
|
174
|
+
missing: [
|
|
175
|
+
{ type: 'cookie', key: 'auth-token' }
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Conditional Operators
|
|
181
|
+
|
|
182
|
+
The SDK supports advanced matching operators for more complex conditions:
|
|
183
|
+
|
|
184
|
+
#### Equality Operators
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Exact match using 'eq'
|
|
188
|
+
router.rewrite('/api/(.*)', 'https://backend.com/$1', {
|
|
189
|
+
has: [
|
|
190
|
+
{ type: 'header', key: 'x-api-version', eq: 'v2' }
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Not equal using 'neq'
|
|
195
|
+
router.redirect('/beta/(.*)', '/stable/$1', {
|
|
196
|
+
has: [
|
|
197
|
+
{ type: 'cookie', key: 'beta-access', neq: 'granted' }
|
|
198
|
+
]
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### Inclusion Operators
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// Must be one of (inclusion)
|
|
206
|
+
router.rewrite('/admin/(.*)', 'https://admin.backend.com/$1', {
|
|
207
|
+
has: [
|
|
208
|
+
{ type: 'header', key: 'x-user-role', inc: ['admin', 'moderator', 'superuser'] }
|
|
209
|
+
]
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Must NOT be one of (non-inclusion)
|
|
213
|
+
router.redirect('/public/(.*)', '/private/$1', {
|
|
214
|
+
has: [
|
|
215
|
+
{ type: 'header', key: 'x-user-role', ninc: ['guest', 'anonymous'] }
|
|
216
|
+
]
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### String Pattern Operators
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Starts with (prefix)
|
|
224
|
+
router.rewrite('/staging/(.*)', 'https://staging.backend.com/$1', {
|
|
225
|
+
has: [
|
|
226
|
+
{ type: 'cookie', key: 'session', pre: 'staging-' }
|
|
227
|
+
]
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Ends with (suffix)
|
|
231
|
+
router.redirect('/dev/(.*)', '/development/$1', {
|
|
232
|
+
has: [
|
|
233
|
+
{ type: 'header', key: 'x-environment', suf: '-dev' }
|
|
234
|
+
]
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### Numeric Comparison Operators
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// Greater than
|
|
242
|
+
router.rewrite('/api/v3/(.*)', 'https://api-v3.backend.com/$1', {
|
|
243
|
+
has: [
|
|
244
|
+
{ type: 'query', key: 'version', gt: 2 }
|
|
245
|
+
]
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Greater than or equal
|
|
249
|
+
router.rewrite('/premium/(.*)', '/premium-content/$1', {
|
|
250
|
+
has: [
|
|
251
|
+
{ type: 'header', key: 'x-subscription-tier', gte: 3 }
|
|
252
|
+
]
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Less than
|
|
256
|
+
router.redirect('/legacy/(.*)', '/upgrade/$1', {
|
|
257
|
+
has: [
|
|
258
|
+
{ type: 'query', key: 'api-version', lt: 2 }
|
|
259
|
+
]
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Less than or equal
|
|
263
|
+
router.rewrite('/free/(.*)', '/free-tier/$1', {
|
|
264
|
+
has: [
|
|
265
|
+
{ type: 'header', key: 'x-plan', lte: 1 }
|
|
266
|
+
]
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Host and Path Matching
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// Host matching (no key required)
|
|
274
|
+
router.redirect('/(.*)', 'https://www.example.com/$1', {
|
|
275
|
+
has: [
|
|
276
|
+
{ type: 'host', value: 'example.com' }
|
|
277
|
+
]
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Path pattern matching (no key required)
|
|
281
|
+
router.rewrite('/(.*)', '/internal/$1', {
|
|
282
|
+
has: [
|
|
283
|
+
{ type: 'path', value: '^/api/v[0-9]+/.*' }
|
|
284
|
+
]
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Multiple Conditions
|
|
289
|
+
|
|
290
|
+
All conditions in a `has` or `missing` array must match (AND logic):
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
router.rewrite('/secure/(.*)', 'https://secure.backend.com/$1', {
|
|
294
|
+
has: [
|
|
295
|
+
{ type: 'header', key: 'x-api-key' },
|
|
296
|
+
{ type: 'header', key: 'x-user-role', inc: ['admin', 'superuser'] },
|
|
297
|
+
{ type: 'cookie', key: 'session', pre: 'secure-' },
|
|
298
|
+
{ type: 'query', key: 'version', gte: 2 }
|
|
299
|
+
]
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Combining with Transforms
|
|
304
|
+
|
|
305
|
+
You can combine conditions with transforms for powerful routing logic:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
router.rewrite('/api/users/:userId', 'https://backend.com/users/$1', ({ userId, env }) => ({
|
|
309
|
+
has: [
|
|
310
|
+
{ type: 'header', key: 'authorization', pre: 'Bearer ' },
|
|
311
|
+
{ type: 'header', key: 'x-api-version', gte: 2 }
|
|
312
|
+
],
|
|
313
|
+
missing: [
|
|
314
|
+
{ type: 'header', key: 'x-deprecated-header' }
|
|
315
|
+
],
|
|
316
|
+
requestHeaders: {
|
|
317
|
+
'x-user-id': userId,
|
|
318
|
+
'x-internal-key': env.INTERNAL_API_KEY
|
|
319
|
+
}
|
|
320
|
+
}));
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Available Operators
|
|
324
|
+
|
|
325
|
+
| Operator | Type | Description | Example |
|
|
326
|
+
|----------|------|-------------|---------|
|
|
327
|
+
| `eq` | string \| number | Exact equality match | `{ eq: 'v2' }` |
|
|
328
|
+
| `neq` | string | Not equal | `{ neq: 'guest' }` |
|
|
329
|
+
| `inc` | string[] | Value is one of | `{ inc: ['admin', 'mod'] }` |
|
|
330
|
+
| `ninc` | string[] | Value is not one of | `{ ninc: ['guest', 'banned'] }` |
|
|
331
|
+
| `pre` | string | Starts with prefix | `{ pre: 'Bearer ' }` |
|
|
332
|
+
| `suf` | string | Ends with suffix | `{ suf: '-dev' }` |
|
|
333
|
+
| `gt` | number | Greater than | `{ gt: 2 }` |
|
|
334
|
+
| `gte` | number | Greater than or equal | `{ gte: 3 }` |
|
|
335
|
+
| `lt` | number | Less than | `{ lt: 5 }` |
|
|
336
|
+
| `lte` | number | Less than or equal | `{ lte: 10 }` |
|
|
337
|
+
|
|
150
338
|
## Important Notes
|
|
151
339
|
|
|
152
340
|
- **One config file only**: You cannot have both `vercel.ts` and `vercel.json`. The build will fail if both exist.
|
package/dist/cli.js
CHANGED
|
@@ -37,10 +37,9 @@ const ROUTE_BASED_EXPORTS = new Set([
|
|
|
37
37
|
'redirects',
|
|
38
38
|
'rewrites',
|
|
39
39
|
'headers',
|
|
40
|
-
'crons',
|
|
41
40
|
'env',
|
|
42
41
|
'cacheControl',
|
|
43
|
-
'__esModule'
|
|
42
|
+
'__esModule'
|
|
44
43
|
]);
|
|
45
44
|
/**
|
|
46
45
|
* Read the user's vercel.ts file and collect both default export and export const declarations
|
package/dist/index.d.ts
CHANGED
package/dist/router.d.ts
CHANGED
|
@@ -87,14 +87,75 @@ export interface CacheOptions {
|
|
|
87
87
|
* - 'cookie': Match if a specific cookie is present (or missing).
|
|
88
88
|
* - 'host': Match if the incoming host matches a given pattern.
|
|
89
89
|
* - 'query': Match if a query parameter is present (or missing).
|
|
90
|
+
* - 'path': Match if the path matches a given pattern.
|
|
90
91
|
*/
|
|
91
|
-
export type ConditionType = 'header' | 'cookie' | 'host' | 'query';
|
|
92
|
+
export type ConditionType = 'header' | 'cookie' | 'host' | 'query' | 'path';
|
|
92
93
|
/**
|
|
93
|
-
*
|
|
94
|
+
* Conditional matching operators for has/missing conditions.
|
|
95
|
+
* These can be used with the value field to perform advanced matching.
|
|
94
96
|
*/
|
|
95
|
-
export interface
|
|
97
|
+
export interface ConditionOperators {
|
|
98
|
+
/** Check equality on a value (exact match) */
|
|
99
|
+
eq?: string | number;
|
|
100
|
+
/** Check inequality on a value (not equal) */
|
|
101
|
+
neq?: string;
|
|
102
|
+
/** Check inclusion in an array of values (value is one of) */
|
|
103
|
+
inc?: string[];
|
|
104
|
+
/** Check non-inclusion in an array of values (value is not one of) */
|
|
105
|
+
ninc?: string[];
|
|
106
|
+
/** Check if value starts with a prefix */
|
|
107
|
+
pre?: string;
|
|
108
|
+
/** Check if value ends with a suffix */
|
|
109
|
+
suf?: string;
|
|
110
|
+
/** Check if value is greater than (numeric comparison) */
|
|
111
|
+
gt?: number;
|
|
112
|
+
/** Check if value is greater than or equal to */
|
|
113
|
+
gte?: number;
|
|
114
|
+
/** Check if value is less than (numeric comparison) */
|
|
115
|
+
lt?: number;
|
|
116
|
+
/** Check if value is less than or equal to */
|
|
117
|
+
lte?: number;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Used to define "has" or "missing" conditions with advanced matching operators.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* // Simple header presence check
|
|
124
|
+
* { type: 'header', key: 'x-api-key' }
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // Header with exact value match
|
|
128
|
+
* { type: 'header', key: 'x-api-version', value: 'v2' }
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* // Header with conditional operators
|
|
132
|
+
* { type: 'header', key: 'x-user-role', inc: ['admin', 'moderator'] }
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // Cookie with prefix matching
|
|
136
|
+
* { type: 'cookie', key: 'session', pre: 'prod-' }
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* // Host matching
|
|
140
|
+
* { type: 'host', value: 'api.example.com' }
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* // Query parameter with numeric comparison
|
|
144
|
+
* { type: 'query', key: 'version', gte: 2 }
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* // Path pattern matching
|
|
148
|
+
* { type: 'path', value: '^/api/v[0-9]+/.*' }
|
|
149
|
+
*/
|
|
150
|
+
export interface Condition extends ConditionOperators {
|
|
96
151
|
type: ConditionType;
|
|
97
|
-
key
|
|
152
|
+
/** The key to match. Not used for 'host' or 'path' types. */
|
|
153
|
+
key?: string;
|
|
154
|
+
/**
|
|
155
|
+
* Simple string/regex pattern to match against.
|
|
156
|
+
* For 'host' and 'path' types, this is the only matching option.
|
|
157
|
+
* For other types, you can use value OR the conditional operators (eq, neq, etc).
|
|
158
|
+
*/
|
|
98
159
|
value?: string;
|
|
99
160
|
}
|
|
100
161
|
/**
|
|
@@ -188,6 +249,8 @@ export interface Route {
|
|
|
188
249
|
src: string;
|
|
189
250
|
/** Optional destination for rewrite/redirect */
|
|
190
251
|
dest?: string;
|
|
252
|
+
/** Array of HTTP methods to match. If not provided, matches all methods */
|
|
253
|
+
methods?: string[];
|
|
191
254
|
/** Array of transforms to apply */
|
|
192
255
|
transforms?: Transform[];
|
|
193
256
|
/** Optional conditions that must be present */
|
|
@@ -196,7 +259,7 @@ export interface Route {
|
|
|
196
259
|
missing?: Condition[];
|
|
197
260
|
/** If true, this is a redirect (status defaults to 308 or specified) */
|
|
198
261
|
redirect?: boolean;
|
|
199
|
-
/** Status code for
|
|
262
|
+
/** Status code for the response */
|
|
200
263
|
status?: number;
|
|
201
264
|
/** Headers to set (alternative to using transforms) */
|
|
202
265
|
headers?: Record<string, string>;
|
|
@@ -359,6 +422,10 @@ export interface RewriteRule {
|
|
|
359
422
|
*/
|
|
360
423
|
source: string;
|
|
361
424
|
destination: string;
|
|
425
|
+
/** Array of HTTP methods to match. If not provided, matches all methods */
|
|
426
|
+
methods?: string[];
|
|
427
|
+
/** Status code for the response */
|
|
428
|
+
status?: number;
|
|
362
429
|
has?: Condition[];
|
|
363
430
|
missing?: Condition[];
|
|
364
431
|
/** Internal field: transforms generated from requestHeaders/responseHeaders/requestQuery */
|
|
@@ -512,6 +579,8 @@ export declare class Router {
|
|
|
512
579
|
* });
|
|
513
580
|
*/
|
|
514
581
|
rewrite(source: string, destination: string, optionsOrCallback?: {
|
|
582
|
+
methods?: string[];
|
|
583
|
+
status?: number;
|
|
515
584
|
has?: Condition[];
|
|
516
585
|
missing?: Condition[];
|
|
517
586
|
requestHeaders?: Record<string, string | string[]>;
|
|
@@ -526,6 +595,8 @@ export declare class Router {
|
|
|
526
595
|
} | ((params: Record<string, string> & {
|
|
527
596
|
env: any;
|
|
528
597
|
}) => {
|
|
598
|
+
methods?: string[];
|
|
599
|
+
status?: number;
|
|
529
600
|
has?: Condition[];
|
|
530
601
|
missing?: Condition[];
|
|
531
602
|
requestHeaders?: Record<string, string | string[]>;
|
package/dist/router.js
CHANGED
|
@@ -305,7 +305,7 @@ class Router {
|
|
|
305
305
|
options = optionsOrCallback;
|
|
306
306
|
}
|
|
307
307
|
// Extract transform options
|
|
308
|
-
const { requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, has, missing } = options || {};
|
|
308
|
+
const { methods, status, requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, has, missing } = options || {};
|
|
309
309
|
const transformOpts = {
|
|
310
310
|
requestHeaders,
|
|
311
311
|
responseHeaders,
|
|
@@ -327,6 +327,8 @@ class Router {
|
|
|
327
327
|
this.rewriteRules.push({
|
|
328
328
|
source,
|
|
329
329
|
destination,
|
|
330
|
+
...(methods && { methods }),
|
|
331
|
+
...(status && { status }),
|
|
330
332
|
has,
|
|
331
333
|
missing,
|
|
332
334
|
transforms,
|
|
@@ -410,7 +412,7 @@ class Router {
|
|
|
410
412
|
options = optionsOrCallback;
|
|
411
413
|
}
|
|
412
414
|
// Extract transform options
|
|
413
|
-
const { requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, permanent, statusCode, has, missing, } = options || {};
|
|
415
|
+
const { methods, requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, permanent, statusCode, has, missing, } = options || {};
|
|
414
416
|
// If transforms are provided, create a route instead of a redirect
|
|
415
417
|
const hasTransforms = requestHeaders || responseHeaders || requestQuery ||
|
|
416
418
|
appendRequestHeaders || appendResponseHeaders || appendRequestQuery ||
|
|
@@ -431,6 +433,7 @@ class Router {
|
|
|
431
433
|
this.routeRules.push({
|
|
432
434
|
src: source,
|
|
433
435
|
dest: destination,
|
|
436
|
+
...(methods && { methods }),
|
|
434
437
|
transforms,
|
|
435
438
|
redirect: true,
|
|
436
439
|
status: statusCode || (permanent ? 308 : 307),
|
|
@@ -580,16 +583,22 @@ class Router {
|
|
|
580
583
|
* so that Vercel can pick it up.
|
|
581
584
|
*/
|
|
582
585
|
getConfig() {
|
|
583
|
-
// Separate rewrites into those
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
586
|
+
// Separate rewrites into those that need to be routes vs. legacy rewrites
|
|
587
|
+
// Routes are needed for: transforms, methods, or custom status
|
|
588
|
+
const rewritesNeedingRoutes = this.rewriteRules.filter((r) => r.transforms || r.methods || r.status);
|
|
589
|
+
const legacyRewrites = this.rewriteRules.filter((r) => !r.transforms && !r.methods && !r.status);
|
|
590
|
+
// Convert rewrites to routes
|
|
591
|
+
const routesFromRewrites = rewritesNeedingRoutes.map((rewrite) => {
|
|
588
592
|
const route = {
|
|
589
593
|
src: rewrite.source,
|
|
590
594
|
dest: rewrite.destination,
|
|
591
|
-
transforms: rewrite.transforms,
|
|
592
595
|
};
|
|
596
|
+
if (rewrite.transforms)
|
|
597
|
+
route.transforms = rewrite.transforms;
|
|
598
|
+
if (rewrite.methods)
|
|
599
|
+
route.methods = rewrite.methods;
|
|
600
|
+
if (rewrite.status)
|
|
601
|
+
route.status = rewrite.status;
|
|
593
602
|
if (rewrite.has)
|
|
594
603
|
route.has = rewrite.has;
|
|
595
604
|
if (rewrite.missing)
|
|
@@ -598,11 +607,67 @@ class Router {
|
|
|
598
607
|
});
|
|
599
608
|
// Combine with existing routes
|
|
600
609
|
const allRoutes = [...routesFromRewrites, ...this.routeRules];
|
|
601
|
-
// If routes exist,
|
|
610
|
+
// If routes exist, convert everything to routes format
|
|
611
|
+
// Vercel doesn't allow mixing routes with redirects, rewrites, headers, cleanUrls, or trailingSlash
|
|
602
612
|
if (allRoutes.length > 0) {
|
|
613
|
+
// Convert standalone redirects to routes
|
|
614
|
+
const routesFromRedirects = this.redirectRules.map(redirectRule => {
|
|
615
|
+
const route = {
|
|
616
|
+
src: redirectRule.source,
|
|
617
|
+
dest: redirectRule.destination,
|
|
618
|
+
redirect: true,
|
|
619
|
+
status: redirectRule.statusCode || (redirectRule.permanent ? 308 : 307),
|
|
620
|
+
};
|
|
621
|
+
if (redirectRule.has)
|
|
622
|
+
route.has = redirectRule.has;
|
|
623
|
+
if (redirectRule.missing)
|
|
624
|
+
route.missing = redirectRule.missing;
|
|
625
|
+
return route;
|
|
626
|
+
});
|
|
627
|
+
// Convert legacy rewrites (without transforms) to routes
|
|
628
|
+
const routesFromLegacyRewrites = legacyRewrites.map(rewrite => {
|
|
629
|
+
const route = {
|
|
630
|
+
src: rewrite.source,
|
|
631
|
+
dest: rewrite.destination,
|
|
632
|
+
};
|
|
633
|
+
if (rewrite.has)
|
|
634
|
+
route.has = rewrite.has;
|
|
635
|
+
if (rewrite.missing)
|
|
636
|
+
route.missing = rewrite.missing;
|
|
637
|
+
return route;
|
|
638
|
+
});
|
|
639
|
+
// Convert standalone headers to routes (except rewrite caching headers)
|
|
640
|
+
const routesFromHeaders = this.headerRules
|
|
641
|
+
.filter(rule => {
|
|
642
|
+
// Exclude rewrite caching headers (they're automatically added for rewrites)
|
|
643
|
+
const isCachingHeader = rule.headers.length === 1 &&
|
|
644
|
+
rule.headers[0].key === 'x-vercel-enable-rewrite-caching';
|
|
645
|
+
return !isCachingHeader;
|
|
646
|
+
})
|
|
647
|
+
.map(headerRule => {
|
|
648
|
+
const transforms = headerRule.headers.map(header => ({
|
|
649
|
+
type: 'response.headers',
|
|
650
|
+
op: 'set',
|
|
651
|
+
target: { key: header.key },
|
|
652
|
+
args: header.value,
|
|
653
|
+
}));
|
|
654
|
+
const route = {
|
|
655
|
+
src: headerRule.source,
|
|
656
|
+
transforms,
|
|
657
|
+
};
|
|
658
|
+
if (headerRule.has)
|
|
659
|
+
route.has = headerRule.has;
|
|
660
|
+
if (headerRule.missing)
|
|
661
|
+
route.missing = headerRule.missing;
|
|
662
|
+
return route;
|
|
663
|
+
});
|
|
664
|
+
// Combine all routes: redirects, legacy rewrites, rewrites with transforms, explicit routes, and headers as routes
|
|
665
|
+
const combinedRoutes = [...routesFromRedirects, ...routesFromLegacyRewrites, ...routesFromRewrites, ...this.routeRules, ...routesFromHeaders];
|
|
603
666
|
const config = {
|
|
604
|
-
routes:
|
|
667
|
+
routes: combinedRoutes,
|
|
605
668
|
};
|
|
669
|
+
// NOTE: crons are now handled via export const crons in vercel.ts
|
|
670
|
+
// They are no longer included in router.getConfig()
|
|
606
671
|
// Only include optional fields if they're explicitly set
|
|
607
672
|
if (this.bulkRedirectsPathConfig !== undefined) {
|
|
608
673
|
config.bulkRedirectsPath = this.bulkRedirectsPathConfig;
|
|
@@ -619,10 +684,10 @@ class Router {
|
|
|
619
684
|
const config = {
|
|
620
685
|
redirects: this.redirectRules,
|
|
621
686
|
headers: this.headerRules,
|
|
622
|
-
rewrites:
|
|
687
|
+
rewrites: legacyRewrites,
|
|
623
688
|
cleanUrls: this.cleanUrlsConfig,
|
|
624
689
|
trailingSlash: this.trailingSlashConfig,
|
|
625
|
-
|
|
690
|
+
// NOTE: crons are now handled via export const crons in vercel.ts
|
|
626
691
|
};
|
|
627
692
|
// Only include optional fields if they're explicitly set
|
|
628
693
|
if (this.bulkRedirectsPathConfig !== undefined) {
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel configuration type that mirrors the vercel.json schema
|
|
3
|
+
* https://openapi.vercel.sh/vercel.json
|
|
4
|
+
*/
|
|
5
|
+
export type Framework = 'blitzjs' | 'nextjs' | 'gatsby' | 'remix' | 'react-router' | 'astro' | 'hexo' | 'eleventy' | 'docusaurus-2' | 'docusaurus' | 'preact' | 'solidstart-1' | 'solidstart' | 'dojo' | 'ember' | 'vue' | 'scully' | 'ionic-angular' | 'angular' | 'polymer' | 'svelte' | 'sveltekit' | 'sveltekit-1' | 'ionic-react' | 'create-react-app' | 'gridsome' | 'umijs' | 'sapper' | 'saber' | 'stencil' | 'nuxtjs' | 'redwoodjs' | 'hugo' | 'jekyll' | 'brunch' | 'middleman' | 'zola' | 'hydrogen' | 'vite' | 'tanstack-start' | 'vitepress' | 'vuepress' | 'parcel' | 'fastapi' | 'flask' | 'fasthtml' | 'sanity-v3' | 'sanity' | 'storybook' | 'nitro' | 'hono' | 'express' | 'h3' | 'nestjs' | 'elysia' | 'fastify' | 'xmcp' | null;
|
|
6
|
+
export interface CronJob {
|
|
7
|
+
schedule: string;
|
|
8
|
+
path: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FunctionConfig {
|
|
11
|
+
excludeFiles?: string;
|
|
12
|
+
includeFiles?: string;
|
|
13
|
+
maxDuration?: number;
|
|
14
|
+
memory?: number;
|
|
15
|
+
runtime?: string;
|
|
16
|
+
supportsCancellation?: boolean;
|
|
17
|
+
experimentalTriggers?: Array<{
|
|
18
|
+
type: 'queue/v1beta';
|
|
19
|
+
topic: string;
|
|
20
|
+
consumer: string;
|
|
21
|
+
maxDeliveries?: number;
|
|
22
|
+
retryAfterSeconds?: number;
|
|
23
|
+
initialDelaySeconds?: number;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export interface GitDeploymentConfig {
|
|
27
|
+
[branch: string]: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface GitConfig {
|
|
30
|
+
deploymentEnabled?: boolean | GitDeploymentConfig;
|
|
31
|
+
}
|
|
32
|
+
export interface GithubConfig {
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
autoAlias?: boolean;
|
|
35
|
+
autoJobCancelation?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface Header {
|
|
38
|
+
key: string;
|
|
39
|
+
value: string;
|
|
40
|
+
}
|
|
41
|
+
export interface HeaderRule {
|
|
42
|
+
source: string;
|
|
43
|
+
headers: Header[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Route represents a single routing rule that can be a redirect, rewrite, or header rule
|
|
47
|
+
* Matches the output of router.redirect(), router.rewrite(), router.header(), etc.
|
|
48
|
+
*/
|
|
49
|
+
export type RouteType = any;
|
|
50
|
+
export interface WildcardDomain {
|
|
51
|
+
domain: string;
|
|
52
|
+
value: string;
|
|
53
|
+
}
|
|
54
|
+
export interface VercelConfig {
|
|
55
|
+
/**
|
|
56
|
+
* Aliases that will get assigned when the deployment is `READY` and the target is `production`.
|
|
57
|
+
*/
|
|
58
|
+
alias?: string | string[];
|
|
59
|
+
/**
|
|
60
|
+
* When set to `true`, all HTML files and Serverless Functions will have their extension removed.
|
|
61
|
+
*/
|
|
62
|
+
cleanUrls?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* An array of cron jobs that should be created for production Deployments.
|
|
65
|
+
*/
|
|
66
|
+
crons?: CronJob[];
|
|
67
|
+
/**
|
|
68
|
+
* An object containing the deployment's environment variables.
|
|
69
|
+
*/
|
|
70
|
+
env?: Record<string, string>;
|
|
71
|
+
/**
|
|
72
|
+
* An array of the passive regions the deployment's Serverless Functions should be deployed to.
|
|
73
|
+
*/
|
|
74
|
+
passiveRegions?: string[];
|
|
75
|
+
/**
|
|
76
|
+
* Same as passiveRegions. An array of passive regions for failover.
|
|
77
|
+
*/
|
|
78
|
+
functionFailoverRegions?: string[];
|
|
79
|
+
/**
|
|
80
|
+
* An object describing custom options for Serverless Functions.
|
|
81
|
+
*/
|
|
82
|
+
functions?: Record<string, FunctionConfig>;
|
|
83
|
+
/**
|
|
84
|
+
* Git-related configuration.
|
|
85
|
+
*/
|
|
86
|
+
git?: GitConfig;
|
|
87
|
+
/**
|
|
88
|
+
* GitHub-related configuration.
|
|
89
|
+
*/
|
|
90
|
+
github?: GithubConfig;
|
|
91
|
+
/**
|
|
92
|
+
* HTTP headers configuration.
|
|
93
|
+
* Can use router.header() and router.cacheControl() helpers.
|
|
94
|
+
*/
|
|
95
|
+
headers?: RouteType[];
|
|
96
|
+
/**
|
|
97
|
+
* HTTP redirects configuration.
|
|
98
|
+
* Can use router.redirect() helper.
|
|
99
|
+
*/
|
|
100
|
+
redirects?: RouteType[];
|
|
101
|
+
/**
|
|
102
|
+
* HTTP rewrites configuration.
|
|
103
|
+
* Can use router.rewrite() helper.
|
|
104
|
+
*/
|
|
105
|
+
rewrites?: RouteType[];
|
|
106
|
+
/**
|
|
107
|
+
* Wildcard domain configuration.
|
|
108
|
+
*/
|
|
109
|
+
wildcard?: WildcardDomain[];
|
|
110
|
+
/**
|
|
111
|
+
* The build command for this project. When `null`, automatically detected.
|
|
112
|
+
*/
|
|
113
|
+
buildCommand?: string | null;
|
|
114
|
+
/**
|
|
115
|
+
* The ignore command for this project.
|
|
116
|
+
*/
|
|
117
|
+
ignoreCommand?: string | null;
|
|
118
|
+
/**
|
|
119
|
+
* The dev command for this project. When `null`, automatically detected.
|
|
120
|
+
*/
|
|
121
|
+
devCommand?: string | null;
|
|
122
|
+
/**
|
|
123
|
+
* The framework being used. When `null`, no framework is selected.
|
|
124
|
+
*/
|
|
125
|
+
framework?: Framework;
|
|
126
|
+
/**
|
|
127
|
+
* The install command for this project. When `null`, automatically detected.
|
|
128
|
+
*/
|
|
129
|
+
installCommand?: string | null;
|
|
130
|
+
/**
|
|
131
|
+
* The output directory of the project. When `null`, automatically detected.
|
|
132
|
+
*/
|
|
133
|
+
outputDirectory?: string | null;
|
|
134
|
+
/**
|
|
135
|
+
* When `false`, visiting a path that ends with a forward slash will redirect to the path without the trailing slash.
|
|
136
|
+
*/
|
|
137
|
+
trailingSlash?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* An array of projectIds to associate with the current project.
|
|
140
|
+
*/
|
|
141
|
+
relatedProjects?: string[];
|
|
142
|
+
/**
|
|
143
|
+
* Enables Fluid compute for the project.
|
|
144
|
+
*/
|
|
145
|
+
fluid?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Enables Bun for the project and specifies the version to use.
|
|
148
|
+
*/
|
|
149
|
+
bunVersion?: string;
|
|
150
|
+
/**
|
|
151
|
+
* Node.js version for this project.
|
|
152
|
+
*/
|
|
153
|
+
nodeVersion?: string;
|
|
154
|
+
}
|
package/dist/types.js
ADDED