@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 CHANGED
@@ -1,46 +1,159 @@
1
- # Vercel Router SDK
1
+ # @vercel/config
2
2
 
3
- This is a prototype of what a Router SDK could look like.
3
+ TypeScript SDK for defining Vercel configuration programmatically.
4
4
 
5
- ## Usage
5
+ ## Installation
6
6
 
7
- 1. Install the package:
8
- `npm install @vercel/router`
7
+ ```bash
8
+ npm install @vercel/config
9
+ ```
9
10
 
10
- 2. Create a router configuration file (e.g., `router.config.ts`):
11
+ ## Usage
12
+
13
+ Create a `vercel.ts` file in your project root:
11
14
 
12
15
  ```typescript
13
- import { createRouter } from "@vercel/router";
16
+ import { createRouter } from '@vercel/config';
14
17
 
15
18
  const router = createRouter();
16
19
 
17
- router
18
- .rewrite("/api/(.*)", "/api/$1")
19
- .redirect("/old-docs", "/docs", { permanent: true })
20
- .cacheControl("/static/(.*)", {
21
- public: true,
22
- maxAge: "1week",
23
- immutable: true
24
- })
25
- .setCleanUrls(true);
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
- 3. Add the generate-config script to your package.json:
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
- ```json
33
- {
34
- "scripts": {
35
- "generate-config": "@vercel/router"
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
- 4. Run the script to generate your `vercel.json`:
95
+ ### `router.redirect(source, destination, options?)`
41
96
 
42
- ```bash
43
- npm run generate-config
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
- This will generate a `vercel.json` file in your project root with all your routing configurations.
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
- * Read the user's vercel.ts file (or fallback to router.config.ts for backwards compatibility)
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
- const routerConfig = (await (_a = configPath, Promise.resolve().then(() => __importStar(require(_a))))).default;
40
- return routerConfig;
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
- * Use param() for path parameters and runtimeEnv() for environment variables.
218
+ * Sets the key and value if missing.
219
219
  *
220
220
  * @example
221
221
  * requestHeaders: {
222
- * 'x-user-id': param('userId'),
223
- * 'authorization': `Bearer ${runtimeEnv('API_TOKEN')}`
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
- * Use param() for path parameters and runtimeEnv() for environment variables.
229
+ * Sets the key and value if missing.
230
230
  *
231
231
  * @example
232
232
  * responseHeaders: {
233
- * 'x-post-id': param('postId')
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
- * // This will automatically enable caching for the rewrite
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, options?: {
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
- }): this;
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, options?: {
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
- }): this;
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
- // Convert requestHeaders
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 = this.extractEnvVars(value);
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 = this.extractEnvVars(value);
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 = this.extractEnvVars(value);
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
- * // This will automatically enable caching for the rewrite
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, options) {
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 transforms = requestHeaders || responseHeaders || requestQuery
140
- ? this.transformOptionsToTransforms(transformOpts)
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, options) {
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
- if (requestHeaders || responseHeaders || requestQuery) {
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 buildCommand/installCommand if they're explicitly set
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 buildCommand/installCommand if they're explicitly set
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/config",
3
- "version": "0.0.9",
3
+ "version": "0.0.12",
4
4
  "description": "A TypeScript SDK for programmatically generating Vercel configuration files",
5
5
  "bugs": {
6
6
  "url": "https://github.com/vercel/router-sdk/issues"