@vercel/config 0.0.9 → 0.0.12
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 +139 -26
- package/dist/cli.js +32 -3
- package/dist/router.d.ts +171 -16
- package/dist/router.js +253 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,46 +1,159 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @vercel/config
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for defining Vercel configuration programmatically.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @vercel/config
|
|
9
|
+
```
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Create a `vercel.ts` file in your project root:
|
|
11
14
|
|
|
12
15
|
```typescript
|
|
13
|
-
import { createRouter } from
|
|
16
|
+
import { createRouter } from '@vercel/config';
|
|
14
17
|
|
|
15
18
|
const router = createRouter();
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
// Basic rewrite
|
|
21
|
+
router.rewrite('/api/(.*)', 'https://backend.com/$1');
|
|
22
|
+
|
|
23
|
+
// Rewrite with transforms
|
|
24
|
+
router.rewrite('/users/:userId', 'https://api.example.com/users/$1', ({userId, env}) => ({
|
|
25
|
+
requestHeaders: {
|
|
26
|
+
'x-user-id': userId,
|
|
27
|
+
'authorization': `Bearer ${env.API_TOKEN}`
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
// Redirects
|
|
32
|
+
router.redirect('/old-docs', '/docs', { permanent: true });
|
|
33
|
+
|
|
34
|
+
// Cache control
|
|
35
|
+
router.cacheControl('/static/(.*)', {
|
|
36
|
+
public: true,
|
|
37
|
+
maxAge: '1week',
|
|
38
|
+
immutable: true
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Global settings
|
|
42
|
+
router.cleanUrls = true;
|
|
43
|
+
router.trailingSlash = true;
|
|
44
|
+
|
|
45
|
+
// Bulk redirects
|
|
46
|
+
router.bulkRedirectsPath = './bulkRedirectsDemo.json';
|
|
47
|
+
|
|
48
|
+
// Cron jobs
|
|
49
|
+
router.cron('/api/cleanup', '0 0 * * *');
|
|
26
50
|
|
|
27
51
|
export default router.getConfig();
|
|
28
52
|
```
|
|
29
53
|
|
|
30
|
-
|
|
54
|
+
## Automatic Compilation
|
|
55
|
+
|
|
56
|
+
Your `vercel.ts` file is automatically compiled when you run:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
vercel build # For local builds
|
|
60
|
+
vercel dev # For local development
|
|
61
|
+
vercel deploy # For deployment
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The Vercel CLI automatically compiles `vercel.ts` to `.vercel/vercel.json` before building or deploying.
|
|
65
|
+
|
|
66
|
+
## API Reference
|
|
67
|
+
|
|
68
|
+
### `createRouter()`
|
|
69
|
+
|
|
70
|
+
Creates a new router instance.
|
|
71
|
+
|
|
72
|
+
### `router.rewrite(source, destination, options?)`
|
|
31
73
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
74
|
+
Add a rewrite rule. Options can be an object or a callback function that receives path parameters and environment variables.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// With callback for transforms
|
|
78
|
+
router.rewrite('/users/:userId', 'https://api.example.com/users/$1', ({userId, env}) => ({
|
|
79
|
+
requestHeaders: {
|
|
80
|
+
'x-user-id': userId,
|
|
81
|
+
'authorization': `Bearer ${env.API_TOKEN}`
|
|
82
|
+
},
|
|
83
|
+
responseHeaders: {
|
|
84
|
+
'x-powered-by': 'My API'
|
|
85
|
+
},
|
|
86
|
+
requestQuery: {
|
|
87
|
+
'version': '2.0'
|
|
36
88
|
}
|
|
37
|
-
}
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
// Simple rewrite without transforms
|
|
92
|
+
router.rewrite('/api/(.*)', 'https://backend.com/$1');
|
|
38
93
|
```
|
|
39
94
|
|
|
40
|
-
|
|
95
|
+
### `router.redirect(source, destination, options?)`
|
|
41
96
|
|
|
42
|
-
|
|
43
|
-
|
|
97
|
+
Add a redirect rule. Options include `permanent` and `statusCode`.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
router.redirect('/old-page', '/new-page', { permanent: true });
|
|
101
|
+
router.redirect('/temp', '/elsewhere', { statusCode: 302 });
|
|
44
102
|
```
|
|
45
103
|
|
|
46
|
-
|
|
104
|
+
### `router.header(source, headers, options?)`
|
|
105
|
+
|
|
106
|
+
Add custom headers for a path pattern.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
router.header('/api/(.*)', [
|
|
110
|
+
{ key: 'X-Custom-Header', value: 'value' }
|
|
111
|
+
]);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `router.cacheControl(source, options)`
|
|
115
|
+
|
|
116
|
+
Set cache control headers. Options include `public`, `private`, `maxAge`, `sMaxAge`, `immutable`, etc.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
router.cacheControl('/static/(.*)', {
|
|
120
|
+
public: true,
|
|
121
|
+
maxAge: '1week',
|
|
122
|
+
immutable: true
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `router.cleanUrls`
|
|
127
|
+
|
|
128
|
+
Set whether to enable clean URLs (removes file extensions).
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
router.cleanUrls = true;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### `router.trailingSlash`
|
|
135
|
+
|
|
136
|
+
Set whether to normalize paths to include trailing slashes.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
router.trailingSlash = true;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `router.bulkRedirectsPath`
|
|
143
|
+
|
|
144
|
+
Set the path to a bulk redirects JSON file.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
router.bulkRedirectsPath = './bulkRedirectsDemo.json';
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Important Notes
|
|
151
|
+
|
|
152
|
+
- **One config file only**: You cannot have both `vercel.ts` and `vercel.json`. The build will fail if both exist.
|
|
153
|
+
- **Automatic gitignore**: The generated `.vercel/vercel.json` file is automatically ignored by git (in the `.vercel/` directory).
|
|
154
|
+
- **No manual compilation needed**: The Vercel CLI handles compilation automatically - no need to run a separate command.
|
|
155
|
+
|
|
156
|
+
## Learn More
|
|
157
|
+
|
|
158
|
+
- [Vercel Configuration Documentation](https://vercel.com/docs/projects/project-configuration)
|
|
159
|
+
- [Routing Documentation](https://vercel.com/docs/edge-network/routing)
|
package/dist/cli.js
CHANGED
|
@@ -28,7 +28,22 @@ const fs_1 = require("fs");
|
|
|
28
28
|
const path_1 = require("path");
|
|
29
29
|
const fs_2 = require("fs");
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Named exports that should NOT be auto-converted to config
|
|
32
|
+
* (these are route-based features that compile into the routes array, or internal module properties)
|
|
33
|
+
*/
|
|
34
|
+
const ROUTE_BASED_EXPORTS = new Set([
|
|
35
|
+
'default',
|
|
36
|
+
'routes',
|
|
37
|
+
'redirects',
|
|
38
|
+
'rewrites',
|
|
39
|
+
'headers',
|
|
40
|
+
'crons',
|
|
41
|
+
'env',
|
|
42
|
+
'cacheControl',
|
|
43
|
+
'__esModule' // ES module metadata
|
|
44
|
+
]);
|
|
45
|
+
/**
|
|
46
|
+
* Read the user's vercel.ts file and collect both default export and export const declarations
|
|
32
47
|
*/
|
|
33
48
|
async function configureRouter() {
|
|
34
49
|
var _a;
|
|
@@ -36,8 +51,22 @@ async function configureRouter() {
|
|
|
36
51
|
const routerConfigPath = (0, path_1.resolve)(process.cwd(), "router.config.ts");
|
|
37
52
|
// Prefer vercel.ts, fallback to router.config.ts
|
|
38
53
|
const configPath = (0, fs_2.existsSync)(vercelTsPath) ? vercelTsPath : routerConfigPath;
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
// Import the entire module to get both default and named exports
|
|
55
|
+
const module = await (_a = configPath, Promise.resolve().then(() => __importStar(require(_a))));
|
|
56
|
+
// Start with the default export (router.getConfig())
|
|
57
|
+
const routerConfig = module.default || {};
|
|
58
|
+
// Auto-collect all export const declarations (except route-based ones)
|
|
59
|
+
const exportedConstants = {};
|
|
60
|
+
for (const [key, value] of Object.entries(module)) {
|
|
61
|
+
if (!ROUTE_BASED_EXPORTS.has(key)) {
|
|
62
|
+
exportedConstants[key] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Merge: export const declarations take precedence over default export
|
|
66
|
+
return {
|
|
67
|
+
...routerConfig,
|
|
68
|
+
...exportedConstants
|
|
69
|
+
};
|
|
41
70
|
}
|
|
42
71
|
/**
|
|
43
72
|
* Read existing vercel.json and extract fields to preserve
|
package/dist/router.d.ts
CHANGED
|
@@ -215,27 +215,28 @@ export interface Header {
|
|
|
215
215
|
export interface TransformOptions {
|
|
216
216
|
/**
|
|
217
217
|
* Headers to set/modify on the incoming request.
|
|
218
|
-
*
|
|
218
|
+
* Sets the key and value if missing.
|
|
219
219
|
*
|
|
220
220
|
* @example
|
|
221
221
|
* requestHeaders: {
|
|
222
|
-
* 'x-user-id':
|
|
223
|
-
* 'authorization': `Bearer ${
|
|
222
|
+
* 'x-user-id': userId,
|
|
223
|
+
* 'authorization': `Bearer ${env.API_TOKEN}`
|
|
224
224
|
* }
|
|
225
225
|
*/
|
|
226
226
|
requestHeaders?: Record<string, string | string[]>;
|
|
227
227
|
/**
|
|
228
228
|
* Headers to set/modify on the outgoing response.
|
|
229
|
-
*
|
|
229
|
+
* Sets the key and value if missing.
|
|
230
230
|
*
|
|
231
231
|
* @example
|
|
232
232
|
* responseHeaders: {
|
|
233
|
-
* 'x-post-id':
|
|
233
|
+
* 'x-post-id': postId
|
|
234
234
|
* }
|
|
235
235
|
*/
|
|
236
236
|
responseHeaders?: Record<string, string | string[]>;
|
|
237
237
|
/**
|
|
238
238
|
* Query parameters to set/modify on the request.
|
|
239
|
+
* Sets the key and value if missing.
|
|
239
240
|
*
|
|
240
241
|
* @example
|
|
241
242
|
* requestQuery: {
|
|
@@ -243,6 +244,60 @@ export interface TransformOptions {
|
|
|
243
244
|
* }
|
|
244
245
|
*/
|
|
245
246
|
requestQuery?: Record<string, string | string[]>;
|
|
247
|
+
/**
|
|
248
|
+
* Headers to append to the incoming request.
|
|
249
|
+
* Appends args to the value of the key, and will set if missing.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* appendRequestHeaders: {
|
|
253
|
+
* 'x-custom': 'value'
|
|
254
|
+
* }
|
|
255
|
+
*/
|
|
256
|
+
appendRequestHeaders?: Record<string, string | string[]>;
|
|
257
|
+
/**
|
|
258
|
+
* Headers to append to the outgoing response.
|
|
259
|
+
* Appends args to the value of the key, and will set if missing.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* appendResponseHeaders: {
|
|
263
|
+
* 'x-custom': 'value'
|
|
264
|
+
* }
|
|
265
|
+
*/
|
|
266
|
+
appendResponseHeaders?: Record<string, string | string[]>;
|
|
267
|
+
/**
|
|
268
|
+
* Query parameters to append to the request.
|
|
269
|
+
* Appends args to the value of the key, and will set if missing.
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* appendRequestQuery: {
|
|
273
|
+
* 'tag': 'value'
|
|
274
|
+
* }
|
|
275
|
+
*/
|
|
276
|
+
appendRequestQuery?: Record<string, string | string[]>;
|
|
277
|
+
/**
|
|
278
|
+
* Headers to delete from the incoming request.
|
|
279
|
+
* Deletes the key entirely if args is not provided; otherwise, it will delete the value of args from the matching key.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* deleteRequestHeaders: ['x-remove-this', 'x-remove-that']
|
|
283
|
+
*/
|
|
284
|
+
deleteRequestHeaders?: string[];
|
|
285
|
+
/**
|
|
286
|
+
* Headers to delete from the outgoing response.
|
|
287
|
+
* Deletes the key entirely if args is not provided; otherwise, it will delete the value of args from the matching key.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* deleteResponseHeaders: ['x-powered-by']
|
|
291
|
+
*/
|
|
292
|
+
deleteResponseHeaders?: string[];
|
|
293
|
+
/**
|
|
294
|
+
* Query parameters to delete from the request.
|
|
295
|
+
* Deletes the key entirely if args is not provided; otherwise, it will delete the value of args from the matching key.
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* deleteRequestQuery: ['debug', 'trace']
|
|
299
|
+
*/
|
|
300
|
+
deleteRequestQuery?: string[];
|
|
246
301
|
}
|
|
247
302
|
/**
|
|
248
303
|
* HeaderRule defines one or more headers to set for requests
|
|
@@ -352,6 +407,10 @@ export interface RouterConfig {
|
|
|
352
407
|
* When true, routes are normalized to include a trailing slash.
|
|
353
408
|
*/
|
|
354
409
|
trailingSlash?: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* Path to a JSON file containing bulk redirect rules.
|
|
412
|
+
*/
|
|
413
|
+
bulkRedirectsPath?: string;
|
|
355
414
|
/**
|
|
356
415
|
* Array of cron definitions for scheduled invocations.
|
|
357
416
|
*/
|
|
@@ -378,8 +437,45 @@ export declare class Router {
|
|
|
378
437
|
private cronRules;
|
|
379
438
|
private cleanUrlsConfig;
|
|
380
439
|
private trailingSlashConfig;
|
|
440
|
+
private bulkRedirectsPathConfig;
|
|
381
441
|
private buildCommandConfig;
|
|
382
442
|
private installCommandConfig;
|
|
443
|
+
/**
|
|
444
|
+
* Property setter for cleanUrls configuration.
|
|
445
|
+
*/
|
|
446
|
+
set cleanUrls(value: boolean | undefined);
|
|
447
|
+
/**
|
|
448
|
+
* Property getter for cleanUrls configuration.
|
|
449
|
+
*/
|
|
450
|
+
get cleanUrls(): boolean | undefined;
|
|
451
|
+
/**
|
|
452
|
+
* Property setter for trailingSlash configuration.
|
|
453
|
+
*/
|
|
454
|
+
set trailingSlash(value: boolean | undefined);
|
|
455
|
+
/**
|
|
456
|
+
* Property getter for trailingSlash configuration.
|
|
457
|
+
*/
|
|
458
|
+
get trailingSlash(): boolean | undefined;
|
|
459
|
+
/**
|
|
460
|
+
* Property setter for bulkRedirectsPath configuration.
|
|
461
|
+
*/
|
|
462
|
+
set bulkRedirectsPath(value: string | undefined);
|
|
463
|
+
/**
|
|
464
|
+
* Property getter for bulkRedirectsPath configuration.
|
|
465
|
+
*/
|
|
466
|
+
get bulkRedirectsPath(): string | undefined;
|
|
467
|
+
/**
|
|
468
|
+
* Helper to extract path parameter names from a source pattern.
|
|
469
|
+
* Path parameters are identified by :paramName syntax.
|
|
470
|
+
* @example
|
|
471
|
+
* extractPathParams('/users/:userId/posts/:postId') // Returns ['userId', 'postId']
|
|
472
|
+
*/
|
|
473
|
+
private extractPathParams;
|
|
474
|
+
/**
|
|
475
|
+
* Creates a Proxy object that tracks environment variable accesses.
|
|
476
|
+
* Returns both the proxy and a set of accessed environment variable names.
|
|
477
|
+
*/
|
|
478
|
+
private createEnvProxy;
|
|
383
479
|
/**
|
|
384
480
|
* Helper to extract environment variable names from a string or string array.
|
|
385
481
|
* Environment variables are identified by the pattern $VAR_NAME where VAR_NAME
|
|
@@ -388,6 +484,8 @@ export declare class Router {
|
|
|
388
484
|
private extractEnvVars;
|
|
389
485
|
/**
|
|
390
486
|
* Internal helper to convert TransformOptions to Transform array
|
|
487
|
+
* @param options Transform options to convert
|
|
488
|
+
* @param trackedEnvVars Optional set of environment variables that were accessed via the env proxy
|
|
391
489
|
*/
|
|
392
490
|
private transformOptionsToTransforms;
|
|
393
491
|
/**
|
|
@@ -395,26 +493,51 @@ export declare class Router {
|
|
|
395
493
|
* Automatically enables rewrite caching by adding the x-vercel-enable-rewrite-caching header.
|
|
396
494
|
*
|
|
397
495
|
* @example
|
|
398
|
-
* //
|
|
399
|
-
* import { createRouter, param, runtimeEnv } from '@vercel/router-sdk';
|
|
400
|
-
* const router = createRouter();
|
|
496
|
+
* // Simple rewrite
|
|
401
497
|
* router.rewrite('/api/(.*)', 'https://old-on-prem.com/$1')
|
|
402
498
|
*
|
|
403
|
-
* // With transforms
|
|
499
|
+
* // With transforms using callback
|
|
500
|
+
* router.rewrite('/users/:userId', 'https://api.example.com/users/$1', ({userId, env}) => ({
|
|
501
|
+
* requestHeaders: {
|
|
502
|
+
* 'x-user-id': userId,
|
|
503
|
+
* 'authorization': `Bearer ${env.API_TOKEN}`
|
|
504
|
+
* }
|
|
505
|
+
* }));
|
|
506
|
+
*
|
|
507
|
+
* // With transforms using object (legacy)
|
|
404
508
|
* router.rewrite('/users/:userId', 'https://api.example.com/users/$1', {
|
|
405
509
|
* requestHeaders: {
|
|
406
|
-
* 'x-user-id': param('userId')
|
|
407
|
-
* 'authorization': `Bearer ${runtimeEnv('API_TOKEN')}`
|
|
510
|
+
* 'x-user-id': param('userId')
|
|
408
511
|
* }
|
|
409
512
|
* });
|
|
410
513
|
*/
|
|
411
|
-
rewrite(source: string, destination: string,
|
|
514
|
+
rewrite(source: string, destination: string, optionsOrCallback?: {
|
|
412
515
|
has?: Condition[];
|
|
413
516
|
missing?: Condition[];
|
|
414
517
|
requestHeaders?: Record<string, string | string[]>;
|
|
415
518
|
responseHeaders?: Record<string, string | string[]>;
|
|
416
519
|
requestQuery?: Record<string, string | string[]>;
|
|
417
|
-
|
|
520
|
+
appendRequestHeaders?: Record<string, string | string[]>;
|
|
521
|
+
appendResponseHeaders?: Record<string, string | string[]>;
|
|
522
|
+
appendRequestQuery?: Record<string, string | string[]>;
|
|
523
|
+
deleteRequestHeaders?: string[];
|
|
524
|
+
deleteResponseHeaders?: string[];
|
|
525
|
+
deleteRequestQuery?: string[];
|
|
526
|
+
} | ((params: Record<string, string> & {
|
|
527
|
+
env: any;
|
|
528
|
+
}) => {
|
|
529
|
+
has?: Condition[];
|
|
530
|
+
missing?: Condition[];
|
|
531
|
+
requestHeaders?: Record<string, string | string[]>;
|
|
532
|
+
responseHeaders?: Record<string, string | string[]>;
|
|
533
|
+
requestQuery?: Record<string, string | string[]>;
|
|
534
|
+
appendRequestHeaders?: Record<string, string | string[]>;
|
|
535
|
+
appendResponseHeaders?: Record<string, string | string[]>;
|
|
536
|
+
appendRequestQuery?: Record<string, string | string[]>;
|
|
537
|
+
deleteRequestHeaders?: string[];
|
|
538
|
+
deleteResponseHeaders?: string[];
|
|
539
|
+
deleteRequestQuery?: string[];
|
|
540
|
+
})): this;
|
|
418
541
|
/**
|
|
419
542
|
* Loads rewrite rules asynchronously and appends them.
|
|
420
543
|
* Automatically enables rewrite caching for all loaded rules by adding the x-vercel-enable-rewrite-caching header.
|
|
@@ -427,9 +550,19 @@ export declare class Router {
|
|
|
427
550
|
/**
|
|
428
551
|
* Adds a single redirect rule (synchronous).
|
|
429
552
|
* @example
|
|
553
|
+
* // Simple redirect
|
|
430
554
|
* router.redirect('/old-path', '/new-path', { permanent: true })
|
|
431
555
|
*
|
|
432
|
-
* // With transforms
|
|
556
|
+
* // With transforms using callback
|
|
557
|
+
* router.redirect('/users/:userId', '/new-users/$1', ({userId, env}) => ({
|
|
558
|
+
* permanent: true,
|
|
559
|
+
* requestHeaders: {
|
|
560
|
+
* 'x-user-id': userId,
|
|
561
|
+
* 'x-api-key': env.API_KEY
|
|
562
|
+
* }
|
|
563
|
+
* }))
|
|
564
|
+
*
|
|
565
|
+
* // With transforms using object (legacy)
|
|
433
566
|
* router.redirect('/users/:userId', '/new-users/$1', {
|
|
434
567
|
* permanent: true,
|
|
435
568
|
* requestHeaders: {
|
|
@@ -437,7 +570,7 @@ export declare class Router {
|
|
|
437
570
|
* }
|
|
438
571
|
* })
|
|
439
572
|
*/
|
|
440
|
-
redirect(source: string, destination: string,
|
|
573
|
+
redirect(source: string, destination: string, optionsOrCallback?: {
|
|
441
574
|
permanent?: boolean;
|
|
442
575
|
statusCode?: number;
|
|
443
576
|
has?: Condition[];
|
|
@@ -445,7 +578,29 @@ export declare class Router {
|
|
|
445
578
|
requestHeaders?: Record<string, string | string[]>;
|
|
446
579
|
responseHeaders?: Record<string, string | string[]>;
|
|
447
580
|
requestQuery?: Record<string, string | string[]>;
|
|
448
|
-
|
|
581
|
+
appendRequestHeaders?: Record<string, string | string[]>;
|
|
582
|
+
appendResponseHeaders?: Record<string, string | string[]>;
|
|
583
|
+
appendRequestQuery?: Record<string, string | string[]>;
|
|
584
|
+
deleteRequestHeaders?: string[];
|
|
585
|
+
deleteResponseHeaders?: string[];
|
|
586
|
+
deleteRequestQuery?: string[];
|
|
587
|
+
} | ((params: Record<string, string> & {
|
|
588
|
+
env: any;
|
|
589
|
+
}) => {
|
|
590
|
+
permanent?: boolean;
|
|
591
|
+
statusCode?: number;
|
|
592
|
+
has?: Condition[];
|
|
593
|
+
missing?: Condition[];
|
|
594
|
+
requestHeaders?: Record<string, string | string[]>;
|
|
595
|
+
responseHeaders?: Record<string, string | string[]>;
|
|
596
|
+
requestQuery?: Record<string, string | string[]>;
|
|
597
|
+
appendRequestHeaders?: Record<string, string | string[]>;
|
|
598
|
+
appendResponseHeaders?: Record<string, string | string[]>;
|
|
599
|
+
appendRequestQuery?: Record<string, string | string[]>;
|
|
600
|
+
deleteRequestHeaders?: string[];
|
|
601
|
+
deleteResponseHeaders?: string[];
|
|
602
|
+
deleteRequestQuery?: string[];
|
|
603
|
+
})): this;
|
|
449
604
|
/**
|
|
450
605
|
* Loads redirect rules asynchronously and appends them.
|
|
451
606
|
*/
|
package/dist/router.js
CHANGED
|
@@ -40,9 +40,77 @@ class Router {
|
|
|
40
40
|
this.cronRules = [];
|
|
41
41
|
this.cleanUrlsConfig = undefined;
|
|
42
42
|
this.trailingSlashConfig = undefined;
|
|
43
|
+
this.bulkRedirectsPathConfig = undefined;
|
|
43
44
|
this.buildCommandConfig = undefined;
|
|
44
45
|
this.installCommandConfig = undefined;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Property setter for cleanUrls configuration.
|
|
49
|
+
*/
|
|
50
|
+
set cleanUrls(value) {
|
|
51
|
+
this.cleanUrlsConfig = value;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Property getter for cleanUrls configuration.
|
|
55
|
+
*/
|
|
56
|
+
get cleanUrls() {
|
|
57
|
+
return this.cleanUrlsConfig;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Property setter for trailingSlash configuration.
|
|
61
|
+
*/
|
|
62
|
+
set trailingSlash(value) {
|
|
63
|
+
this.trailingSlashConfig = value;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Property getter for trailingSlash configuration.
|
|
67
|
+
*/
|
|
68
|
+
get trailingSlash() {
|
|
69
|
+
return this.trailingSlashConfig;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Property setter for bulkRedirectsPath configuration.
|
|
73
|
+
*/
|
|
74
|
+
set bulkRedirectsPath(value) {
|
|
75
|
+
this.bulkRedirectsPathConfig = value;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Property getter for bulkRedirectsPath configuration.
|
|
79
|
+
*/
|
|
80
|
+
get bulkRedirectsPath() {
|
|
81
|
+
return this.bulkRedirectsPathConfig;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Helper to extract path parameter names from a source pattern.
|
|
85
|
+
* Path parameters are identified by :paramName syntax.
|
|
86
|
+
* @example
|
|
87
|
+
* extractPathParams('/users/:userId/posts/:postId') // Returns ['userId', 'postId']
|
|
88
|
+
*/
|
|
89
|
+
extractPathParams(source) {
|
|
90
|
+
const params = [];
|
|
91
|
+
const matches = source.matchAll(/:([a-zA-Z_][a-zA-Z0-9_]*)/g);
|
|
92
|
+
for (const match of matches) {
|
|
93
|
+
params.push(match[1]);
|
|
94
|
+
}
|
|
95
|
+
return params;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a Proxy object that tracks environment variable accesses.
|
|
99
|
+
* Returns both the proxy and a set of accessed environment variable names.
|
|
100
|
+
*/
|
|
101
|
+
createEnvProxy() {
|
|
102
|
+
const accessedVars = new Set();
|
|
103
|
+
const proxy = new Proxy({}, {
|
|
104
|
+
get(_target, prop) {
|
|
105
|
+
if (typeof prop === 'string') {
|
|
106
|
+
accessedVars.add(prop);
|
|
107
|
+
return `$${prop}`;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return { proxy, accessedVars };
|
|
113
|
+
}
|
|
46
114
|
/**
|
|
47
115
|
* Helper to extract environment variable names from a string or string array.
|
|
48
116
|
* Environment variables are identified by the pattern $VAR_NAME where VAR_NAME
|
|
@@ -63,13 +131,26 @@ class Router {
|
|
|
63
131
|
}
|
|
64
132
|
/**
|
|
65
133
|
* Internal helper to convert TransformOptions to Transform array
|
|
134
|
+
* @param options Transform options to convert
|
|
135
|
+
* @param trackedEnvVars Optional set of environment variables that were accessed via the env proxy
|
|
66
136
|
*/
|
|
67
|
-
transformOptionsToTransforms(options) {
|
|
137
|
+
transformOptionsToTransforms(options, trackedEnvVars) {
|
|
68
138
|
const transforms = [];
|
|
69
|
-
//
|
|
139
|
+
// Helper to get env vars for a value
|
|
140
|
+
const getEnvVars = (value) => {
|
|
141
|
+
if (trackedEnvVars) {
|
|
142
|
+
return Array.from(trackedEnvVars).filter(envVar => {
|
|
143
|
+
const valueStr = Array.isArray(value) ? value.join(' ') : value;
|
|
144
|
+
return valueStr.includes(`$${envVar}`);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return this.extractEnvVars(value);
|
|
148
|
+
};
|
|
149
|
+
// SET operations
|
|
150
|
+
// Convert requestHeaders (set)
|
|
70
151
|
if (options.requestHeaders) {
|
|
71
152
|
for (const [key, value] of Object.entries(options.requestHeaders)) {
|
|
72
|
-
const envVars =
|
|
153
|
+
const envVars = getEnvVars(value);
|
|
73
154
|
transforms.push({
|
|
74
155
|
type: 'request.headers',
|
|
75
156
|
op: 'set',
|
|
@@ -79,10 +160,10 @@ class Router {
|
|
|
79
160
|
});
|
|
80
161
|
}
|
|
81
162
|
}
|
|
82
|
-
// Convert responseHeaders
|
|
163
|
+
// Convert responseHeaders (set)
|
|
83
164
|
if (options.responseHeaders) {
|
|
84
165
|
for (const [key, value] of Object.entries(options.responseHeaders)) {
|
|
85
|
-
const envVars =
|
|
166
|
+
const envVars = getEnvVars(value);
|
|
86
167
|
transforms.push({
|
|
87
168
|
type: 'response.headers',
|
|
88
169
|
op: 'set',
|
|
@@ -92,10 +173,10 @@ class Router {
|
|
|
92
173
|
});
|
|
93
174
|
}
|
|
94
175
|
}
|
|
95
|
-
// Convert requestQuery
|
|
176
|
+
// Convert requestQuery (set)
|
|
96
177
|
if (options.requestQuery) {
|
|
97
178
|
for (const [key, value] of Object.entries(options.requestQuery)) {
|
|
98
|
-
const envVars =
|
|
179
|
+
const envVars = getEnvVars(value);
|
|
99
180
|
transforms.push({
|
|
100
181
|
type: 'request.query',
|
|
101
182
|
op: 'set',
|
|
@@ -105,6 +186,77 @@ class Router {
|
|
|
105
186
|
});
|
|
106
187
|
}
|
|
107
188
|
}
|
|
189
|
+
// APPEND operations
|
|
190
|
+
// Convert appendRequestHeaders
|
|
191
|
+
if (options.appendRequestHeaders) {
|
|
192
|
+
for (const [key, value] of Object.entries(options.appendRequestHeaders)) {
|
|
193
|
+
const envVars = getEnvVars(value);
|
|
194
|
+
transforms.push({
|
|
195
|
+
type: 'request.headers',
|
|
196
|
+
op: 'append',
|
|
197
|
+
target: { key },
|
|
198
|
+
args: value,
|
|
199
|
+
...(envVars.length > 0 && { env: envVars }),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Convert appendResponseHeaders
|
|
204
|
+
if (options.appendResponseHeaders) {
|
|
205
|
+
for (const [key, value] of Object.entries(options.appendResponseHeaders)) {
|
|
206
|
+
const envVars = getEnvVars(value);
|
|
207
|
+
transforms.push({
|
|
208
|
+
type: 'response.headers',
|
|
209
|
+
op: 'append',
|
|
210
|
+
target: { key },
|
|
211
|
+
args: value,
|
|
212
|
+
...(envVars.length > 0 && { env: envVars }),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Convert appendRequestQuery
|
|
217
|
+
if (options.appendRequestQuery) {
|
|
218
|
+
for (const [key, value] of Object.entries(options.appendRequestQuery)) {
|
|
219
|
+
const envVars = getEnvVars(value);
|
|
220
|
+
transforms.push({
|
|
221
|
+
type: 'request.query',
|
|
222
|
+
op: 'append',
|
|
223
|
+
target: { key },
|
|
224
|
+
args: value,
|
|
225
|
+
...(envVars.length > 0 && { env: envVars }),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// DELETE operations
|
|
230
|
+
// Convert deleteRequestHeaders
|
|
231
|
+
if (options.deleteRequestHeaders) {
|
|
232
|
+
for (const key of options.deleteRequestHeaders) {
|
|
233
|
+
transforms.push({
|
|
234
|
+
type: 'request.headers',
|
|
235
|
+
op: 'delete',
|
|
236
|
+
target: { key },
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Convert deleteResponseHeaders
|
|
241
|
+
if (options.deleteResponseHeaders) {
|
|
242
|
+
for (const key of options.deleteResponseHeaders) {
|
|
243
|
+
transforms.push({
|
|
244
|
+
type: 'response.headers',
|
|
245
|
+
op: 'delete',
|
|
246
|
+
target: { key },
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Convert deleteRequestQuery
|
|
251
|
+
if (options.deleteRequestQuery) {
|
|
252
|
+
for (const key of options.deleteRequestQuery) {
|
|
253
|
+
transforms.push({
|
|
254
|
+
type: 'request.query',
|
|
255
|
+
op: 'delete',
|
|
256
|
+
target: { key },
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
108
260
|
return transforms;
|
|
109
261
|
}
|
|
110
262
|
/**
|
|
@@ -112,32 +264,65 @@ class Router {
|
|
|
112
264
|
* Automatically enables rewrite caching by adding the x-vercel-enable-rewrite-caching header.
|
|
113
265
|
*
|
|
114
266
|
* @example
|
|
115
|
-
* //
|
|
116
|
-
* import { createRouter, param, runtimeEnv } from '@vercel/router-sdk';
|
|
117
|
-
* const router = createRouter();
|
|
267
|
+
* // Simple rewrite
|
|
118
268
|
* router.rewrite('/api/(.*)', 'https://old-on-prem.com/$1')
|
|
119
269
|
*
|
|
120
|
-
* // With transforms
|
|
270
|
+
* // With transforms using callback
|
|
271
|
+
* router.rewrite('/users/:userId', 'https://api.example.com/users/$1', ({userId, env}) => ({
|
|
272
|
+
* requestHeaders: {
|
|
273
|
+
* 'x-user-id': userId,
|
|
274
|
+
* 'authorization': `Bearer ${env.API_TOKEN}`
|
|
275
|
+
* }
|
|
276
|
+
* }));
|
|
277
|
+
*
|
|
278
|
+
* // With transforms using object (legacy)
|
|
121
279
|
* router.rewrite('/users/:userId', 'https://api.example.com/users/$1', {
|
|
122
280
|
* requestHeaders: {
|
|
123
|
-
* 'x-user-id': param('userId')
|
|
124
|
-
* 'authorization': `Bearer ${runtimeEnv('API_TOKEN')}`
|
|
281
|
+
* 'x-user-id': param('userId')
|
|
125
282
|
* }
|
|
126
283
|
* });
|
|
127
284
|
*/
|
|
128
|
-
rewrite(source, destination,
|
|
285
|
+
rewrite(source, destination, optionsOrCallback) {
|
|
129
286
|
this.validateSourcePattern(source);
|
|
130
287
|
(0, validation_1.validateCaptureGroupReferences)(source, destination);
|
|
288
|
+
let options;
|
|
289
|
+
let trackedEnvVars;
|
|
290
|
+
// Handle callback syntax
|
|
291
|
+
if (typeof optionsOrCallback === 'function') {
|
|
292
|
+
const pathParams = this.extractPathParams(source);
|
|
293
|
+
const { proxy: envProxy, accessedVars } = this.createEnvProxy();
|
|
294
|
+
trackedEnvVars = accessedVars;
|
|
295
|
+
// Create params object with path parameters as $paramName
|
|
296
|
+
const paramsObj = {};
|
|
297
|
+
for (const param of pathParams) {
|
|
298
|
+
paramsObj[param] = `$${param}`;
|
|
299
|
+
}
|
|
300
|
+
paramsObj.env = envProxy;
|
|
301
|
+
// Call the callback to get options
|
|
302
|
+
options = optionsOrCallback(paramsObj);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
options = optionsOrCallback;
|
|
306
|
+
}
|
|
131
307
|
// Extract transform options
|
|
132
|
-
const { requestHeaders, responseHeaders, requestQuery, has, missing } = options || {};
|
|
308
|
+
const { requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, has, missing } = options || {};
|
|
133
309
|
const transformOpts = {
|
|
134
310
|
requestHeaders,
|
|
135
311
|
responseHeaders,
|
|
136
312
|
requestQuery,
|
|
313
|
+
appendRequestHeaders,
|
|
314
|
+
appendResponseHeaders,
|
|
315
|
+
appendRequestQuery,
|
|
316
|
+
deleteRequestHeaders,
|
|
317
|
+
deleteResponseHeaders,
|
|
318
|
+
deleteRequestQuery,
|
|
137
319
|
};
|
|
138
320
|
// Convert to transforms if any transform options provided
|
|
139
|
-
const
|
|
140
|
-
|
|
321
|
+
const hasTransforms = requestHeaders || responseHeaders || requestQuery ||
|
|
322
|
+
appendRequestHeaders || appendResponseHeaders || appendRequestQuery ||
|
|
323
|
+
deleteRequestHeaders || deleteResponseHeaders || deleteRequestQuery;
|
|
324
|
+
const transforms = hasTransforms
|
|
325
|
+
? this.transformOptionsToTransforms(transformOpts, trackedEnvVars)
|
|
141
326
|
: undefined;
|
|
142
327
|
this.rewriteRules.push({
|
|
143
328
|
source,
|
|
@@ -182,9 +367,19 @@ class Router {
|
|
|
182
367
|
/**
|
|
183
368
|
* Adds a single redirect rule (synchronous).
|
|
184
369
|
* @example
|
|
370
|
+
* // Simple redirect
|
|
185
371
|
* router.redirect('/old-path', '/new-path', { permanent: true })
|
|
186
372
|
*
|
|
187
|
-
* // With transforms
|
|
373
|
+
* // With transforms using callback
|
|
374
|
+
* router.redirect('/users/:userId', '/new-users/$1', ({userId, env}) => ({
|
|
375
|
+
* permanent: true,
|
|
376
|
+
* requestHeaders: {
|
|
377
|
+
* 'x-user-id': userId,
|
|
378
|
+
* 'x-api-key': env.API_KEY
|
|
379
|
+
* }
|
|
380
|
+
* }))
|
|
381
|
+
*
|
|
382
|
+
* // With transforms using object (legacy)
|
|
188
383
|
* router.redirect('/users/:userId', '/new-users/$1', {
|
|
189
384
|
* permanent: true,
|
|
190
385
|
* requestHeaders: {
|
|
@@ -192,19 +387,47 @@ class Router {
|
|
|
192
387
|
* }
|
|
193
388
|
* })
|
|
194
389
|
*/
|
|
195
|
-
redirect(source, destination,
|
|
390
|
+
redirect(source, destination, optionsOrCallback) {
|
|
196
391
|
this.validateSourcePattern(source);
|
|
197
392
|
(0, validation_1.validateCaptureGroupReferences)(source, destination);
|
|
393
|
+
let options;
|
|
394
|
+
let trackedEnvVars;
|
|
395
|
+
// Handle callback syntax
|
|
396
|
+
if (typeof optionsOrCallback === 'function') {
|
|
397
|
+
const pathParams = this.extractPathParams(source);
|
|
398
|
+
const { proxy: envProxy, accessedVars } = this.createEnvProxy();
|
|
399
|
+
trackedEnvVars = accessedVars;
|
|
400
|
+
// Create params object with path parameters as $paramName
|
|
401
|
+
const paramsObj = {};
|
|
402
|
+
for (const param of pathParams) {
|
|
403
|
+
paramsObj[param] = `$${param}`;
|
|
404
|
+
}
|
|
405
|
+
paramsObj.env = envProxy;
|
|
406
|
+
// Call the callback to get options
|
|
407
|
+
options = optionsOrCallback(paramsObj);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
options = optionsOrCallback;
|
|
411
|
+
}
|
|
198
412
|
// Extract transform options
|
|
199
|
-
const { requestHeaders, responseHeaders, requestQuery, permanent, statusCode, has, missing, } = options || {};
|
|
413
|
+
const { requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, permanent, statusCode, has, missing, } = options || {};
|
|
200
414
|
// If transforms are provided, create a route instead of a redirect
|
|
201
|
-
|
|
415
|
+
const hasTransforms = requestHeaders || responseHeaders || requestQuery ||
|
|
416
|
+
appendRequestHeaders || appendResponseHeaders || appendRequestQuery ||
|
|
417
|
+
deleteRequestHeaders || deleteResponseHeaders || deleteRequestQuery;
|
|
418
|
+
if (hasTransforms) {
|
|
202
419
|
const transformOpts = {
|
|
203
420
|
requestHeaders,
|
|
204
421
|
responseHeaders,
|
|
205
422
|
requestQuery,
|
|
423
|
+
appendRequestHeaders,
|
|
424
|
+
appendResponseHeaders,
|
|
425
|
+
appendRequestQuery,
|
|
426
|
+
deleteRequestHeaders,
|
|
427
|
+
deleteResponseHeaders,
|
|
428
|
+
deleteRequestQuery,
|
|
206
429
|
};
|
|
207
|
-
const transforms = this.transformOptionsToTransforms(transformOpts);
|
|
430
|
+
const transforms = this.transformOptionsToTransforms(transformOpts, trackedEnvVars);
|
|
208
431
|
this.routeRules.push({
|
|
209
432
|
src: source,
|
|
210
433
|
dest: destination,
|
|
@@ -380,7 +603,10 @@ class Router {
|
|
|
380
603
|
const config = {
|
|
381
604
|
routes: allRoutes,
|
|
382
605
|
};
|
|
383
|
-
// Only include
|
|
606
|
+
// Only include optional fields if they're explicitly set
|
|
607
|
+
if (this.bulkRedirectsPathConfig !== undefined) {
|
|
608
|
+
config.bulkRedirectsPath = this.bulkRedirectsPathConfig;
|
|
609
|
+
}
|
|
384
610
|
if (this.buildCommandConfig !== undefined) {
|
|
385
611
|
config.buildCommand = this.buildCommandConfig;
|
|
386
612
|
}
|
|
@@ -398,7 +624,10 @@ class Router {
|
|
|
398
624
|
trailingSlash: this.trailingSlashConfig,
|
|
399
625
|
crons: this.cronRules,
|
|
400
626
|
};
|
|
401
|
-
// Only include
|
|
627
|
+
// Only include optional fields if they're explicitly set
|
|
628
|
+
if (this.bulkRedirectsPathConfig !== undefined) {
|
|
629
|
+
config.bulkRedirectsPath = this.bulkRedirectsPathConfig;
|
|
630
|
+
}
|
|
402
631
|
if (this.buildCommandConfig !== undefined) {
|
|
403
632
|
config.buildCommand = this.buildCommandConfig;
|
|
404
633
|
}
|