@vercel/config 0.0.13 → 0.0.16

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/dist/router.js CHANGED
@@ -111,187 +111,35 @@ class Router {
111
111
  });
112
112
  return { proxy, accessedVars };
113
113
  }
114
+ // Deprecated: extractEnvVars method no longer needed after refactor
114
115
  /**
115
- * Helper to extract environment variable names from a string or string array.
116
- * Environment variables are identified by the pattern $VAR_NAME where VAR_NAME
117
- * is typically uppercase with underscores (e.g., $API_KEY, $BEARER_TOKEN).
118
- */
119
- extractEnvVars(args) {
120
- const envVars = new Set();
121
- const values = Array.isArray(args) ? args : [args];
122
- for (const value of values) {
123
- const matches = value.match(/\$([A-Z][A-Z0-9_]*)/g);
124
- if (matches) {
125
- for (const match of matches) {
126
- envVars.add(match.substring(1));
127
- }
128
- }
129
- }
130
- return Array.from(envVars);
131
- }
132
- /**
116
+ * @deprecated No longer used after refactor to return schema objects directly
133
117
  * Internal helper to convert TransformOptions to Transform array
134
118
  * @param options Transform options to convert
135
119
  * @param trackedEnvVars Optional set of environment variables that were accessed via the env proxy
136
120
  */
137
- transformOptionsToTransforms(options, trackedEnvVars) {
138
- const transforms = [];
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)
151
- if (options.requestHeaders) {
152
- for (const [key, value] of Object.entries(options.requestHeaders)) {
153
- const envVars = getEnvVars(value);
154
- transforms.push({
155
- type: 'request.headers',
156
- op: 'set',
157
- target: { key },
158
- args: value,
159
- ...(envVars.length > 0 && { env: envVars }),
160
- });
161
- }
162
- }
163
- // Convert responseHeaders (set)
164
- if (options.responseHeaders) {
165
- for (const [key, value] of Object.entries(options.responseHeaders)) {
166
- const envVars = getEnvVars(value);
167
- transforms.push({
168
- type: 'response.headers',
169
- op: 'set',
170
- target: { key },
171
- args: value,
172
- ...(envVars.length > 0 && { env: envVars }),
173
- });
174
- }
175
- }
176
- // Convert requestQuery (set)
177
- if (options.requestQuery) {
178
- for (const [key, value] of Object.entries(options.requestQuery)) {
179
- const envVars = getEnvVars(value);
180
- transforms.push({
181
- type: 'request.query',
182
- op: 'set',
183
- target: { key },
184
- args: value,
185
- ...(envVars.length > 0 && { env: envVars }),
186
- });
187
- }
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
- }
260
- return transforms;
261
- }
121
+ // Deprecated: transformOptionsToTransforms method removed after refactor
262
122
  /**
263
- * Adds a single rewrite rule (synchronous).
264
- * Automatically enables rewrite caching by adding the x-vercel-enable-rewrite-caching header.
123
+ * Creates a rewrite rule. Returns either a Rewrite object (simple case) or Route with transforms.
265
124
  *
266
125
  * @example
267
126
  * // Simple rewrite
268
127
  * router.rewrite('/api/(.*)', 'https://old-on-prem.com/$1')
269
128
  *
270
- * // With transforms using callback
129
+ * // With transforms
271
130
  * 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)
279
- * router.rewrite('/users/:userId', 'https://api.example.com/users/$1', {
280
- * requestHeaders: {
281
- * 'x-user-id': param('userId')
282
- * }
283
- * });
131
+ * requestHeaders: { 'x-user-id': userId }
132
+ * }))
133
+ * @internal Can return Route with transforms internally
284
134
  */
285
135
  rewrite(source, destination, optionsOrCallback) {
286
136
  this.validateSourcePattern(source);
287
137
  (0, validation_1.validateCaptureGroupReferences)(source, destination);
288
138
  let options;
289
- let trackedEnvVars;
290
139
  // Handle callback syntax
291
140
  if (typeof optionsOrCallback === 'function') {
292
141
  const pathParams = this.extractPathParams(source);
293
- const { proxy: envProxy, accessedVars } = this.createEnvProxy();
294
- trackedEnvVars = accessedVars;
142
+ const { proxy: envProxy } = this.createEnvProxy();
295
143
  // Create params object with path parameters as $paramName
296
144
  const paramsObj = {};
297
145
  for (const param of pathParams) {
@@ -304,101 +152,86 @@ class Router {
304
152
  else {
305
153
  options = optionsOrCallback;
306
154
  }
307
- // Extract transform options
308
- const { methods, status, requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, has, missing } = options || {};
309
- const transformOpts = {
310
- requestHeaders,
311
- responseHeaders,
312
- requestQuery,
313
- appendRequestHeaders,
314
- appendResponseHeaders,
315
- appendRequestQuery,
316
- deleteRequestHeaders,
317
- deleteResponseHeaders,
318
- deleteRequestQuery,
319
- };
320
- // Convert to transforms if any transform options provided
321
- const hasTransforms = requestHeaders || responseHeaders || requestQuery ||
322
- appendRequestHeaders || appendResponseHeaders || appendRequestQuery ||
323
- deleteRequestHeaders || deleteResponseHeaders || deleteRequestQuery;
324
- const transforms = hasTransforms
325
- ? this.transformOptionsToTransforms(transformOpts, trackedEnvVars)
326
- : undefined;
327
- this.rewriteRules.push({
155
+ const { has, missing, requestHeaders, responseHeaders, requestQuery } = options || {};
156
+ // Check if any transforms were provided
157
+ const hasTransforms = requestHeaders || responseHeaders || requestQuery;
158
+ if (hasTransforms) {
159
+ // Build a Route object with transforms
160
+ const transforms = [];
161
+ if (requestHeaders) {
162
+ for (const [key, value] of Object.entries(requestHeaders)) {
163
+ transforms.push({
164
+ type: 'request.headers',
165
+ op: 'set',
166
+ target: { key },
167
+ args: value,
168
+ });
169
+ }
170
+ }
171
+ if (responseHeaders) {
172
+ for (const [key, value] of Object.entries(responseHeaders)) {
173
+ transforms.push({
174
+ type: 'response.headers',
175
+ op: 'set',
176
+ target: { key },
177
+ args: value,
178
+ });
179
+ }
180
+ }
181
+ if (requestQuery) {
182
+ for (const [key, value] of Object.entries(requestQuery)) {
183
+ transforms.push({
184
+ type: 'request.query',
185
+ op: 'set',
186
+ target: { key },
187
+ args: value,
188
+ });
189
+ }
190
+ }
191
+ const route = {
192
+ src: source,
193
+ dest: destination,
194
+ transforms,
195
+ };
196
+ if (has)
197
+ route.has = has;
198
+ if (missing)
199
+ route.missing = missing;
200
+ return route;
201
+ }
202
+ // Simple rewrite without transforms
203
+ const rewrite = {
328
204
  source,
329
205
  destination,
330
- ...(methods && { methods }),
331
- ...(status && { status }),
332
- has,
333
- missing,
334
- transforms,
335
- });
336
- // Only enable rewrite caching for rewrites without transforms
337
- // (transforms convert to routes, which don't need the caching header)
338
- if (!transforms) {
339
- this.headerRules.push({
340
- source,
341
- headers: [{ key: 'x-vercel-enable-rewrite-caching', value: '1' }],
342
- has,
343
- missing,
344
- });
345
- }
346
- return this;
206
+ };
207
+ if (has)
208
+ rewrite.has = has;
209
+ if (missing)
210
+ rewrite.missing = missing;
211
+ return rewrite;
347
212
  }
348
213
  /**
349
- * Loads rewrite rules asynchronously and appends them.
350
- * Automatically enables rewrite caching for all loaded rules by adding the x-vercel-enable-rewrite-caching header.
214
+ * Creates a redirect rule. Returns either a Redirect object (simple case) or Route with transforms.
351
215
  *
352
216
  * @example
353
- * // This will automatically enable caching for all rewrites
354
- * await router.rewrites(() => fetchRewriteRulesFromDB());
355
- */
356
- async rewrites(provider) {
357
- const rules = await provider();
358
- this.rewriteRules.push(...rules);
359
- // Automatically enable rewrite caching for all rules
360
- const headerRules = rules.map((rule) => ({
361
- source: rule.source,
362
- headers: [{ key: 'x-vercel-enable-rewrite-caching', value: '1' }],
363
- has: rule.has,
364
- missing: rule.missing,
365
- }));
366
- this.headerRules.push(...headerRules);
367
- return this;
368
- }
369
- /**
370
- * Adds a single redirect rule (synchronous).
371
- * @example
372
217
  * // Simple redirect
373
218
  * router.redirect('/old-path', '/new-path', { permanent: true })
374
219
  *
375
- * // With transforms using callback
220
+ * // With transforms
376
221
  * router.redirect('/users/:userId', '/new-users/$1', ({userId, env}) => ({
377
222
  * permanent: true,
378
- * requestHeaders: {
379
- * 'x-user-id': userId,
380
- * 'x-api-key': env.API_KEY
381
- * }
223
+ * requestHeaders: { 'x-user-id': userId }
382
224
  * }))
383
- *
384
- * // With transforms using object (legacy)
385
- * router.redirect('/users/:userId', '/new-users/$1', {
386
- * permanent: true,
387
- * requestHeaders: {
388
- * 'x-user-id': param('userId')
389
- * }
390
- * })
225
+ * @internal Can return Route with transforms internally
391
226
  */
392
227
  redirect(source, destination, optionsOrCallback) {
393
228
  this.validateSourcePattern(source);
394
229
  (0, validation_1.validateCaptureGroupReferences)(source, destination);
395
230
  let options;
396
- let trackedEnvVars;
397
231
  // Handle callback syntax
398
232
  if (typeof optionsOrCallback === 'function') {
399
233
  const pathParams = this.extractPathParams(source);
400
- const { proxy: envProxy, accessedVars } = this.createEnvProxy();
401
- trackedEnvVars = accessedVars;
234
+ const { proxy: envProxy } = this.createEnvProxy();
402
235
  // Create params object with path parameters as $paramName
403
236
  const paramsObj = {};
404
237
  for (const param of pathParams) {
@@ -411,92 +244,75 @@ class Router {
411
244
  else {
412
245
  options = optionsOrCallback;
413
246
  }
414
- // Extract transform options
415
- const { methods, requestHeaders, responseHeaders, requestQuery, appendRequestHeaders, appendResponseHeaders, appendRequestQuery, deleteRequestHeaders, deleteResponseHeaders, deleteRequestQuery, permanent, statusCode, has, missing, } = options || {};
416
- // If transforms are provided, create a route instead of a redirect
417
- const hasTransforms = requestHeaders || responseHeaders || requestQuery ||
418
- appendRequestHeaders || appendResponseHeaders || appendRequestQuery ||
419
- deleteRequestHeaders || deleteResponseHeaders || deleteRequestQuery;
420
- if (hasTransforms) {
421
- const transformOpts = {
422
- requestHeaders,
423
- responseHeaders,
424
- requestQuery,
425
- appendRequestHeaders,
426
- appendResponseHeaders,
427
- appendRequestQuery,
428
- deleteRequestHeaders,
429
- deleteResponseHeaders,
430
- deleteRequestQuery,
431
- };
432
- const transforms = this.transformOptionsToTransforms(transformOpts, trackedEnvVars);
433
- this.routeRules.push({
247
+ const { permanent, statusCode, has, missing, requestHeaders } = options || {};
248
+ // Check if transforms were provided
249
+ if (requestHeaders) {
250
+ // Build a Route object with transforms
251
+ const transforms = [];
252
+ for (const [key, value] of Object.entries(requestHeaders)) {
253
+ transforms.push({
254
+ type: 'request.headers',
255
+ op: 'set',
256
+ target: { key },
257
+ args: value,
258
+ });
259
+ }
260
+ const route = {
434
261
  src: source,
435
262
  dest: destination,
436
- ...(methods && { methods }),
437
- transforms,
438
263
  redirect: true,
439
264
  status: statusCode || (permanent ? 308 : 307),
440
- has,
441
- missing,
442
- });
443
- }
444
- else {
445
- this.redirectRules.push({
446
- source,
447
- destination,
448
- permanent,
449
- statusCode,
450
- has,
451
- missing,
452
- });
265
+ transforms,
266
+ };
267
+ if (has)
268
+ route.has = has;
269
+ if (missing)
270
+ route.missing = missing;
271
+ return route;
453
272
  }
454
- return this;
455
- }
456
- /**
457
- * Loads redirect rules asynchronously and appends them.
458
- */
459
- async redirects(provider) {
460
- const rules = await provider();
461
- this.redirectRules.push(...rules);
462
- return this;
273
+ // Simple redirect without transforms
274
+ const redirect = {
275
+ source,
276
+ destination,
277
+ };
278
+ if (permanent !== undefined)
279
+ redirect.permanent = permanent;
280
+ if (statusCode !== undefined)
281
+ redirect.statusCode = statusCode;
282
+ if (has)
283
+ redirect.has = has;
284
+ if (missing)
285
+ redirect.missing = missing;
286
+ return redirect;
463
287
  }
464
288
  /**
465
- * Adds a single header rule (synchronous).
289
+ * Creates a header rule matching the vercel.json schema.
466
290
  * @example
467
291
  * router.header('/api/(.*)', [{ key: 'X-Custom', value: 'HelloWorld' }])
468
292
  */
469
293
  header(source, headers, options) {
470
294
  this.validateSourcePattern(source);
471
- this.headerRules.push({ source, headers, ...options });
472
- return this;
295
+ return { source, headers, ...options };
473
296
  }
474
297
  /**
475
- * Loads header rules asynchronously and appends them.
476
- */
477
- async headers(provider) {
478
- const rules = await provider();
479
- this.headerRules.push(...rules);
480
- return this;
481
- }
482
- /**
483
- * Adds a typed "Cache-Control" header, leveraging `pretty-cache-header`.
484
- * This method is purely for convenience, so you can do:
298
+ * Creates a Cache-Control header rule, leveraging `pretty-cache-header`.
299
+ * Returns a HeaderRule matching the vercel.json schema.
485
300
  *
486
- * router.cacheControl('/my-page', {
487
- * public: true,
488
- * maxAge: '1week',
489
- * staleWhileRevalidate: '1year'
490
- * });
301
+ * @example
302
+ * router.cacheControl('/my-page', {
303
+ * public: true,
304
+ * maxAge: '1week',
305
+ * staleWhileRevalidate: '1year'
306
+ * })
491
307
  */
492
308
  cacheControl(source, cacheOptions, options) {
309
+ this.validateSourcePattern(source);
493
310
  const value = (0, pretty_cache_header_1.cacheHeader)(cacheOptions);
494
- this.headerRules.push({
311
+ return {
495
312
  source,
496
313
  headers: [{ key: 'Cache-Control', value }],
497
314
  ...options,
498
- });
499
- return this;
315
+ };
500
316
  }
501
317
  /**
502
318
  * Adds a route with transforms support.
@@ -607,11 +423,67 @@ class Router {
607
423
  });
608
424
  // Combine with existing routes
609
425
  const allRoutes = [...routesFromRewrites, ...this.routeRules];
610
- // If routes exist, only return routes (not the legacy fields)
426
+ // If routes exist, convert everything to routes format
427
+ // Vercel doesn't allow mixing routes with redirects, rewrites, headers, cleanUrls, or trailingSlash
611
428
  if (allRoutes.length > 0) {
429
+ // Convert standalone redirects to routes
430
+ const routesFromRedirects = this.redirectRules.map(redirectRule => {
431
+ const route = {
432
+ src: redirectRule.source,
433
+ dest: redirectRule.destination,
434
+ redirect: true,
435
+ status: redirectRule.statusCode || (redirectRule.permanent ? 308 : 307),
436
+ };
437
+ if (redirectRule.has)
438
+ route.has = redirectRule.has;
439
+ if (redirectRule.missing)
440
+ route.missing = redirectRule.missing;
441
+ return route;
442
+ });
443
+ // Convert legacy rewrites (without transforms) to routes
444
+ const routesFromLegacyRewrites = legacyRewrites.map(rewrite => {
445
+ const route = {
446
+ src: rewrite.source,
447
+ dest: rewrite.destination,
448
+ };
449
+ if (rewrite.has)
450
+ route.has = rewrite.has;
451
+ if (rewrite.missing)
452
+ route.missing = rewrite.missing;
453
+ return route;
454
+ });
455
+ // Convert standalone headers to routes (except rewrite caching headers)
456
+ const routesFromHeaders = this.headerRules
457
+ .filter(rule => {
458
+ // Exclude rewrite caching headers (they're automatically added for rewrites)
459
+ const isCachingHeader = rule.headers.length === 1 &&
460
+ rule.headers[0].key === 'x-vercel-enable-rewrite-caching';
461
+ return !isCachingHeader;
462
+ })
463
+ .map(headerRule => {
464
+ const transforms = headerRule.headers.map(header => ({
465
+ type: 'response.headers',
466
+ op: 'set',
467
+ target: { key: header.key },
468
+ args: header.value,
469
+ }));
470
+ const route = {
471
+ src: headerRule.source,
472
+ transforms,
473
+ };
474
+ if (headerRule.has)
475
+ route.has = headerRule.has;
476
+ if (headerRule.missing)
477
+ route.missing = headerRule.missing;
478
+ return route;
479
+ });
480
+ // Combine all routes: redirects, legacy rewrites, rewrites with transforms, explicit routes, and headers as routes
481
+ const combinedRoutes = [...routesFromRedirects, ...routesFromLegacyRewrites, ...routesFromRewrites, ...this.routeRules, ...routesFromHeaders];
612
482
  const config = {
613
- routes: allRoutes,
483
+ routes: combinedRoutes,
614
484
  };
485
+ // NOTE: crons are now handled via export const crons in vercel.ts
486
+ // They are no longer included in router.getConfig()
615
487
  // Only include optional fields if they're explicitly set
616
488
  if (this.bulkRedirectsPathConfig !== undefined) {
617
489
  config.bulkRedirectsPath = this.bulkRedirectsPathConfig;
@@ -631,7 +503,7 @@ class Router {
631
503
  rewrites: legacyRewrites,
632
504
  cleanUrls: this.cleanUrlsConfig,
633
505
  trailingSlash: this.trailingSlashConfig,
634
- crons: this.cronRules,
506
+ // NOTE: crons are now handled via export const crons in vercel.ts
635
507
  };
636
508
  // Only include optional fields if they're explicitly set
637
509
  if (this.bulkRedirectsPathConfig !== undefined) {