@vercel/config 0.0.14 → 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.
package/dist/types.d.ts CHANGED
@@ -34,19 +34,67 @@ export interface GithubConfig {
34
34
  autoAlias?: boolean;
35
35
  autoJobCancelation?: boolean;
36
36
  }
37
+ /**
38
+ * HTTP header key/value pair
39
+ */
37
40
  export interface Header {
38
41
  key: string;
39
42
  value: string;
40
43
  }
44
+ /**
45
+ * Condition for matching in redirects, rewrites, and headers
46
+ */
47
+ export interface Condition {
48
+ type: 'header' | 'cookie' | 'host' | 'query' | 'path';
49
+ key?: string;
50
+ value?: string | number;
51
+ inc?: string[];
52
+ pre?: string;
53
+ eq?: string | number;
54
+ neq?: string;
55
+ gt?: number;
56
+ gte?: number;
57
+ lt?: number;
58
+ lte?: number;
59
+ }
60
+ /**
61
+ * Redirect matching vercel.json schema
62
+ * Returned by router.redirect()
63
+ */
64
+ export interface Redirect {
65
+ source: string;
66
+ destination: string;
67
+ permanent?: boolean;
68
+ statusCode?: number;
69
+ has?: Condition[];
70
+ missing?: Condition[];
71
+ }
72
+ /**
73
+ * Rewrite matching vercel.json schema
74
+ * Returned by router.rewrite()
75
+ */
76
+ export interface Rewrite {
77
+ source: string;
78
+ destination: string;
79
+ has?: Condition[];
80
+ missing?: Condition[];
81
+ }
82
+ /**
83
+ * Header rule matching vercel.json schema
84
+ * Returned by router.header() and router.cacheControl()
85
+ */
41
86
  export interface HeaderRule {
42
87
  source: string;
43
88
  headers: Header[];
89
+ has?: Condition[];
90
+ missing?: Condition[];
44
91
  }
45
92
  /**
46
- * Route represents a single routing rule that can be a redirect, rewrite, or header rule
47
- * Matches the output of router.redirect(), router.rewrite(), router.header(), etc.
93
+ * Union type for all router helper outputs
94
+ * Can be simple schema objects (Redirect, Rewrite, HeaderRule) or Routes with transforms
95
+ * Note: Route type is defined in router.ts (uses src/dest instead of source/destination)
48
96
  */
49
- export type RouteType = any;
97
+ export type RouteType = Redirect | Rewrite | HeaderRule | any;
50
98
  export interface WildcardDomain {
51
99
  domain: string;
52
100
  value: string;
@@ -103,6 +151,12 @@ export interface VercelConfig {
103
151
  * Can use router.rewrite() helper.
104
152
  */
105
153
  rewrites?: RouteType[];
154
+ /**
155
+ * Routes configuration using the lower-level routes primitive.
156
+ * Use this if you need transforms or want everything in one place.
157
+ * Cannot be mixed with headers, redirects, or rewrites.
158
+ */
159
+ routes?: RouteType[];
106
160
  /**
107
161
  * Wildcard domain configuration.
108
162
  */
@@ -41,3 +41,30 @@ export declare function countCaptureGroups(pattern: string): number;
41
41
  * that don't exist in the source pattern.
42
42
  */
43
43
  export declare function validateCaptureGroupReferences(source: string, destination: string): void;
44
+ /**
45
+ * Validates that a value is a static string literal (not computed, not a function call, etc.)
46
+ * Used for static fields that must be extracted before build execution.
47
+ */
48
+ export declare function validateStaticString(value: any, fieldName: string): void;
49
+ /**
50
+ * Validates that a value is a static boolean literal
51
+ */
52
+ export declare function validateStaticBoolean(value: any, fieldName: string): void;
53
+ /**
54
+ * Validates that a value is a static object with primitive values
55
+ * Used for git.deploymentEnabled and similar objects that need to be static
56
+ */
57
+ export declare function validateStaticObject(value: any, fieldName: string): void;
58
+ /**
59
+ * Validates that a value is a static array of strings
60
+ */
61
+ export declare function validateStaticStringArray(value: any, fieldName: string): void;
62
+ /**
63
+ * Validates static fields in VercelConfig that must be extracted before build execution.
64
+ * These fields include:
65
+ * - buildCommand, devCommand, installCommand, framework, nodeVersion, outputDirectory
66
+ * - github.enabled, github.autoAlias, github.autoJobCancelation
67
+ * - git.deploymentEnabled
68
+ * - relatedProjects
69
+ */
70
+ export declare function validateStaticFields(config: Record<string, any>): void;