@vercel/config 0.0.10 → 0.0.13

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
@@ -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
@@ -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
@@ -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
- * Used to define "has" or "missing" conditions.
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 Condition {
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: string;
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 redirects */
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>;
@@ -215,27 +278,28 @@ export interface Header {
215
278
  export interface TransformOptions {
216
279
  /**
217
280
  * Headers to set/modify on the incoming request.
218
- * Use param() for path parameters and runtimeEnv() for environment variables.
281
+ * Sets the key and value if missing.
219
282
  *
220
283
  * @example
221
284
  * requestHeaders: {
222
- * 'x-user-id': param('userId'),
223
- * 'authorization': `Bearer ${runtimeEnv('API_TOKEN')}`
285
+ * 'x-user-id': userId,
286
+ * 'authorization': `Bearer ${env.API_TOKEN}`
224
287
  * }
225
288
  */
226
289
  requestHeaders?: Record<string, string | string[]>;
227
290
  /**
228
291
  * Headers to set/modify on the outgoing response.
229
- * Use param() for path parameters and runtimeEnv() for environment variables.
292
+ * Sets the key and value if missing.
230
293
  *
231
294
  * @example
232
295
  * responseHeaders: {
233
- * 'x-post-id': param('postId')
296
+ * 'x-post-id': postId
234
297
  * }
235
298
  */
236
299
  responseHeaders?: Record<string, string | string[]>;
237
300
  /**
238
301
  * Query parameters to set/modify on the request.
302
+ * Sets the key and value if missing.
239
303
  *
240
304
  * @example
241
305
  * requestQuery: {
@@ -243,6 +307,60 @@ export interface TransformOptions {
243
307
  * }
244
308
  */
245
309
  requestQuery?: Record<string, string | string[]>;
310
+ /**
311
+ * Headers to append to the incoming request.
312
+ * Appends args to the value of the key, and will set if missing.
313
+ *
314
+ * @example
315
+ * appendRequestHeaders: {
316
+ * 'x-custom': 'value'
317
+ * }
318
+ */
319
+ appendRequestHeaders?: Record<string, string | string[]>;
320
+ /**
321
+ * Headers to append to the outgoing response.
322
+ * Appends args to the value of the key, and will set if missing.
323
+ *
324
+ * @example
325
+ * appendResponseHeaders: {
326
+ * 'x-custom': 'value'
327
+ * }
328
+ */
329
+ appendResponseHeaders?: Record<string, string | string[]>;
330
+ /**
331
+ * Query parameters to append to the request.
332
+ * Appends args to the value of the key, and will set if missing.
333
+ *
334
+ * @example
335
+ * appendRequestQuery: {
336
+ * 'tag': 'value'
337
+ * }
338
+ */
339
+ appendRequestQuery?: Record<string, string | string[]>;
340
+ /**
341
+ * Headers to delete from the incoming request.
342
+ * Deletes the key entirely if args is not provided; otherwise, it will delete the value of args from the matching key.
343
+ *
344
+ * @example
345
+ * deleteRequestHeaders: ['x-remove-this', 'x-remove-that']
346
+ */
347
+ deleteRequestHeaders?: string[];
348
+ /**
349
+ * Headers to delete from the outgoing response.
350
+ * Deletes the key entirely if args is not provided; otherwise, it will delete the value of args from the matching key.
351
+ *
352
+ * @example
353
+ * deleteResponseHeaders: ['x-powered-by']
354
+ */
355
+ deleteResponseHeaders?: string[];
356
+ /**
357
+ * Query parameters to delete from the request.
358
+ * Deletes the key entirely if args is not provided; otherwise, it will delete the value of args from the matching key.
359
+ *
360
+ * @example
361
+ * deleteRequestQuery: ['debug', 'trace']
362
+ */
363
+ deleteRequestQuery?: string[];
246
364
  }
247
365
  /**
248
366
  * HeaderRule defines one or more headers to set for requests
@@ -304,6 +422,10 @@ export interface RewriteRule {
304
422
  */
305
423
  source: string;
306
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;
307
429
  has?: Condition[];
308
430
  missing?: Condition[];
309
431
  /** Internal field: transforms generated from requestHeaders/responseHeaders/requestQuery */
@@ -429,6 +551,8 @@ export declare class Router {
429
551
  private extractEnvVars;
430
552
  /**
431
553
  * Internal helper to convert TransformOptions to Transform array
554
+ * @param options Transform options to convert
555
+ * @param trackedEnvVars Optional set of environment variables that were accessed via the env proxy
432
556
  */
433
557
  private transformOptionsToTransforms;
434
558
  /**
@@ -455,19 +579,35 @@ export declare class Router {
455
579
  * });
456
580
  */
457
581
  rewrite(source: string, destination: string, optionsOrCallback?: {
582
+ methods?: string[];
583
+ status?: number;
458
584
  has?: Condition[];
459
585
  missing?: Condition[];
460
586
  requestHeaders?: Record<string, string | string[]>;
461
587
  responseHeaders?: Record<string, string | string[]>;
462
588
  requestQuery?: Record<string, string | string[]>;
589
+ appendRequestHeaders?: Record<string, string | string[]>;
590
+ appendResponseHeaders?: Record<string, string | string[]>;
591
+ appendRequestQuery?: Record<string, string | string[]>;
592
+ deleteRequestHeaders?: string[];
593
+ deleteResponseHeaders?: string[];
594
+ deleteRequestQuery?: string[];
463
595
  } | ((params: Record<string, string> & {
464
596
  env: any;
465
597
  }) => {
598
+ methods?: string[];
599
+ status?: number;
466
600
  has?: Condition[];
467
601
  missing?: Condition[];
468
602
  requestHeaders?: Record<string, string | string[]>;
469
603
  responseHeaders?: Record<string, string | string[]>;
470
604
  requestQuery?: Record<string, string | string[]>;
605
+ appendRequestHeaders?: Record<string, string | string[]>;
606
+ appendResponseHeaders?: Record<string, string | string[]>;
607
+ appendRequestQuery?: Record<string, string | string[]>;
608
+ deleteRequestHeaders?: string[];
609
+ deleteResponseHeaders?: string[];
610
+ deleteRequestQuery?: string[];
471
611
  })): this;
472
612
  /**
473
613
  * Loads rewrite rules asynchronously and appends them.
@@ -509,6 +649,12 @@ export declare class Router {
509
649
  requestHeaders?: Record<string, string | string[]>;
510
650
  responseHeaders?: Record<string, string | string[]>;
511
651
  requestQuery?: Record<string, string | string[]>;
652
+ appendRequestHeaders?: Record<string, string | string[]>;
653
+ appendResponseHeaders?: Record<string, string | string[]>;
654
+ appendRequestQuery?: Record<string, string | string[]>;
655
+ deleteRequestHeaders?: string[];
656
+ deleteResponseHeaders?: string[];
657
+ deleteRequestQuery?: string[];
512
658
  } | ((params: Record<string, string> & {
513
659
  env: any;
514
660
  }) => {
@@ -519,6 +665,12 @@ export declare class Router {
519
665
  requestHeaders?: Record<string, string | string[]>;
520
666
  responseHeaders?: Record<string, string | string[]>;
521
667
  requestQuery?: Record<string, string | string[]>;
668
+ appendRequestHeaders?: Record<string, string | string[]>;
669
+ appendResponseHeaders?: Record<string, string | string[]>;
670
+ appendRequestQuery?: Record<string, string | string[]>;
671
+ deleteRequestHeaders?: string[];
672
+ deleteResponseHeaders?: string[];
673
+ deleteRequestQuery?: string[];
522
674
  })): this;
523
675
  /**
524
676
  * Loads redirect rules asynchronously and appends them.
package/dist/router.js CHANGED
@@ -131,13 +131,26 @@ class Router {
131
131
  }
132
132
  /**
133
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
134
136
  */
135
- transformOptionsToTransforms(options) {
137
+ transformOptionsToTransforms(options, trackedEnvVars) {
136
138
  const transforms = [];
137
- // 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)
138
151
  if (options.requestHeaders) {
139
152
  for (const [key, value] of Object.entries(options.requestHeaders)) {
140
- const envVars = this.extractEnvVars(value);
153
+ const envVars = getEnvVars(value);
141
154
  transforms.push({
142
155
  type: 'request.headers',
143
156
  op: 'set',
@@ -147,10 +160,10 @@ class Router {
147
160
  });
148
161
  }
149
162
  }
150
- // Convert responseHeaders
163
+ // Convert responseHeaders (set)
151
164
  if (options.responseHeaders) {
152
165
  for (const [key, value] of Object.entries(options.responseHeaders)) {
153
- const envVars = this.extractEnvVars(value);
166
+ const envVars = getEnvVars(value);
154
167
  transforms.push({
155
168
  type: 'response.headers',
156
169
  op: 'set',
@@ -160,10 +173,10 @@ class Router {
160
173
  });
161
174
  }
162
175
  }
163
- // Convert requestQuery
176
+ // Convert requestQuery (set)
164
177
  if (options.requestQuery) {
165
178
  for (const [key, value] of Object.entries(options.requestQuery)) {
166
- const envVars = this.extractEnvVars(value);
179
+ const envVars = getEnvVars(value);
167
180
  transforms.push({
168
181
  type: 'request.query',
169
182
  op: 'set',
@@ -173,6 +186,77 @@ class Router {
173
186
  });
174
187
  }
175
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
+ }
176
260
  return transforms;
177
261
  }
178
262
  /**
@@ -202,10 +286,12 @@ class Router {
202
286
  this.validateSourcePattern(source);
203
287
  (0, validation_1.validateCaptureGroupReferences)(source, destination);
204
288
  let options;
289
+ let trackedEnvVars;
205
290
  // Handle callback syntax
206
291
  if (typeof optionsOrCallback === 'function') {
207
292
  const pathParams = this.extractPathParams(source);
208
- const { proxy: envProxy } = this.createEnvProxy();
293
+ const { proxy: envProxy, accessedVars } = this.createEnvProxy();
294
+ trackedEnvVars = accessedVars;
209
295
  // Create params object with path parameters as $paramName
210
296
  const paramsObj = {};
211
297
  for (const param of pathParams) {
@@ -219,19 +305,30 @@ class Router {
219
305
  options = optionsOrCallback;
220
306
  }
221
307
  // Extract transform options
222
- const { requestHeaders, responseHeaders, requestQuery, has, missing } = options || {};
308
+ const { methods, status, requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, has, missing } = options || {};
223
309
  const transformOpts = {
224
310
  requestHeaders,
225
311
  responseHeaders,
226
312
  requestQuery,
313
+ appendRequestHeaders,
314
+ appendResponseHeaders,
315
+ appendRequestQuery,
316
+ deleteRequestHeaders,
317
+ deleteResponseHeaders,
318
+ deleteRequestQuery,
227
319
  };
228
320
  // Convert to transforms if any transform options provided
229
- const transforms = requestHeaders || responseHeaders || requestQuery
230
- ? 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)
231
326
  : undefined;
232
327
  this.rewriteRules.push({
233
328
  source,
234
329
  destination,
330
+ ...(methods && { methods }),
331
+ ...(status && { status }),
235
332
  has,
236
333
  missing,
237
334
  transforms,
@@ -296,10 +393,12 @@ class Router {
296
393
  this.validateSourcePattern(source);
297
394
  (0, validation_1.validateCaptureGroupReferences)(source, destination);
298
395
  let options;
396
+ let trackedEnvVars;
299
397
  // Handle callback syntax
300
398
  if (typeof optionsOrCallback === 'function') {
301
399
  const pathParams = this.extractPathParams(source);
302
- const { proxy: envProxy } = this.createEnvProxy();
400
+ const { proxy: envProxy, accessedVars } = this.createEnvProxy();
401
+ trackedEnvVars = accessedVars;
303
402
  // Create params object with path parameters as $paramName
304
403
  const paramsObj = {};
305
404
  for (const param of pathParams) {
@@ -313,18 +412,28 @@ class Router {
313
412
  options = optionsOrCallback;
314
413
  }
315
414
  // Extract transform options
316
- const { requestHeaders, responseHeaders, requestQuery, permanent, statusCode, has, missing, } = options || {};
415
+ const { methods, requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, permanent, statusCode, has, missing, } = options || {};
317
416
  // If transforms are provided, create a route instead of a redirect
318
- if (requestHeaders || responseHeaders || requestQuery) {
417
+ const hasTransforms = requestHeaders || responseHeaders || requestQuery ||
418
+ appendRequestHeaders || appendResponseHeaders || appendRequestQuery ||
419
+ deleteRequestHeaders || deleteResponseHeaders || deleteRequestQuery;
420
+ if (hasTransforms) {
319
421
  const transformOpts = {
320
422
  requestHeaders,
321
423
  responseHeaders,
322
424
  requestQuery,
425
+ appendRequestHeaders,
426
+ appendResponseHeaders,
427
+ appendRequestQuery,
428
+ deleteRequestHeaders,
429
+ deleteResponseHeaders,
430
+ deleteRequestQuery,
323
431
  };
324
- const transforms = this.transformOptionsToTransforms(transformOpts);
432
+ const transforms = this.transformOptionsToTransforms(transformOpts, trackedEnvVars);
325
433
  this.routeRules.push({
326
434
  src: source,
327
435
  dest: destination,
436
+ ...(methods && { methods }),
328
437
  transforms,
329
438
  redirect: true,
330
439
  status: statusCode || (permanent ? 308 : 307),
@@ -474,16 +583,22 @@ class Router {
474
583
  * so that Vercel can pick it up.
475
584
  */
476
585
  getConfig() {
477
- // Separate rewrites into those with and without transforms
478
- const rewritesWithoutTransforms = this.rewriteRules.filter((r) => !r.transforms);
479
- const rewritesWithTransforms = this.rewriteRules.filter((r) => r.transforms);
480
- // Convert rewrites with transforms to routes
481
- const routesFromRewrites = rewritesWithTransforms.map((rewrite) => {
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) => {
482
592
  const route = {
483
593
  src: rewrite.source,
484
594
  dest: rewrite.destination,
485
- transforms: rewrite.transforms,
486
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;
487
602
  if (rewrite.has)
488
603
  route.has = rewrite.has;
489
604
  if (rewrite.missing)
@@ -513,7 +628,7 @@ class Router {
513
628
  const config = {
514
629
  redirects: this.redirectRules,
515
630
  headers: this.headerRules,
516
- rewrites: rewritesWithoutTransforms,
631
+ rewrites: legacyRewrites,
517
632
  cleanUrls: this.cleanUrlsConfig,
518
633
  trailingSlash: this.trailingSlashConfig,
519
634
  crons: this.cronRules,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/config",
3
- "version": "0.0.10",
3
+ "version": "0.0.13",
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"