@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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @vercel/config
2
2
 
3
- TypeScript SDK for defining Vercel configuration programmatically.
3
+ TypeScript SDK for programmatically defining Vercel configuration. Write type-safe routing rules and build configuration in TypeScript instead of JSON.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,338 +8,115 @@ TypeScript SDK for defining Vercel configuration programmatically.
8
8
  npm install @vercel/config
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Quick Start
12
12
 
13
13
  Create a `vercel.ts` file in your project root:
14
14
 
15
15
  ```typescript
16
- import { createRouter } from '@vercel/config';
16
+ import { createRouter, VercelConfig } from '@vercel/config/v1';
17
17
 
18
18
  const router = createRouter();
19
19
 
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 * * *');
50
-
51
- export default router.getConfig();
52
- ```
53
-
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?)`
73
-
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'
88
- }
89
- }));
90
-
91
- // Simple rewrite without transforms
92
- router.rewrite('/api/(.*)', 'https://backend.com/$1');
93
- ```
94
-
95
- ### `router.redirect(source, destination, options?)`
96
-
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 });
102
- ```
103
-
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
- ## 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
- });
20
+ export const config: VercelConfig = {
21
+ buildCommand: 'npm run build',
22
+ framework: 'nextjs',
23
+
24
+ routes: [
25
+ // Headers
26
+ router.cacheControl('/static/(.*)', {
27
+ public: true,
28
+ maxAge: '1 week',
29
+ immutable: true
30
+ }),
31
+
32
+ // Rewrites with transforms
33
+ router.rewrite('/users/:userId', 'https://api.example.com/users/$1',
34
+ ({ userId, env }) => ({
35
+ requestHeaders: {
36
+ 'x-user-id': userId,
37
+ 'authorization': `Bearer ${env.API_TOKEN}`
38
+ }
39
+ })
40
+ ),
41
+
42
+ // Simple redirects
43
+ router.redirect('/old-docs', '/docs', { permanent: true })
44
+ ],
193
45
 
194
- // Not equal using 'neq'
195
- router.redirect('/beta/(.*)', '/stable/$1', {
196
- has: [
197
- { type: 'cookie', key: 'beta-access', neq: 'granted' }
46
+ crons: [
47
+ { path: '/api/cleanup', schedule: '0 0 * * *' }
198
48
  ]
199
- });
49
+ };
200
50
  ```
201
51
 
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
- ```
52
+ ## Features
219
53
 
220
- #### String Pattern Operators
54
+ - **Type-safe configuration** - Full TypeScript support with IDE autocomplete
55
+ - **Readable syntax** - Helper methods like `router.redirect()`, `router.rewrite()`, `router.header()`
56
+ - **Static validation** - Catch configuration errors at development time
57
+ - **Transforms** - Modify request/response headers and query parameters on the fly
58
+ - **Conditions** - Advanced routing with `has` and `missing` conditions
59
+ - **CLI tools** - `compile` and `validate` commands for development
221
60
 
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
- });
61
+ ## Build-Time Compilation
229
62
 
230
- // Ends with (suffix)
231
- router.redirect('/dev/(.*)', '/development/$1', {
232
- has: [
233
- { type: 'header', key: 'x-environment', suf: '-dev' }
234
- ]
235
- });
63
+ Your `vercel.ts` is automatically compiled to `vercel.json` during:
64
+ ```bash
65
+ vercel build
66
+ vercel dev
67
+ vercel deploy
236
68
  ```
237
69
 
238
- #### Numeric Comparison Operators
70
+ No manual build step needed - the Vercel CLI handles compilation automatically.
239
71
 
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
- });
72
+ ## CLI Commands
247
73
 
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
- ```
74
+ For development and validation:
269
75
 
270
- ### Host and Path Matching
76
+ ```bash
77
+ # Compile vercel.ts to JSON (output to stdout)
78
+ npx @vercel/config compile
271
79
 
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
- });
80
+ # Validate config for errors and show summary
81
+ npx @vercel/config validate
279
82
 
280
- // Path pattern matching (no key required)
281
- router.rewrite('/(.*)', '/internal/$1', {
282
- has: [
283
- { type: 'path', value: '^/api/v[0-9]+/.*' }
284
- ]
285
- });
83
+ # Generate vercel.json locally (for development)
84
+ npx @vercel/config generate
286
85
  ```
287
86
 
288
- ### Multiple Conditions
87
+ ## Validation
289
88
 
290
- All conditions in a `has` or `missing` array must match (AND logic):
89
+ Static fields are validated to ensure they only contain literal values (strings, booleans, objects with primitives):
291
90
 
292
91
  ```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
92
+ // Valid - static string
93
+ export const buildCommand = 'npm run build';
304
94
 
305
- You can combine conditions with transforms for powerful routing logic:
95
+ // Invalid - computed value
96
+ export const buildCommand = process.env.BUILD_CMD;
306
97
 
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
98
+ // ✅ Valid - static object
99
+ export const git = {
100
+ deploymentEnabled: {
101
+ 'main': true,
102
+ 'dev': false
319
103
  }
320
- }));
104
+ };
321
105
  ```
322
106
 
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 }` |
107
+ Static fields that are validated:
108
+ - `buildCommand`, `devCommand`, `installCommand`
109
+ - `framework`, `nodeVersion`, `outputDirectory`
110
+ - `github.enabled`, `github.autoAlias`, `github.autoJobCancelation`
111
+ - `git.deploymentEnabled`
112
+ - `relatedProjects`
337
113
 
338
114
  ## Important Notes
339
115
 
340
116
  - **One config file only**: You cannot have both `vercel.ts` and `vercel.json`. The build will fail if both exist.
341
- - **Automatic gitignore**: The generated `.vercel/vercel.json` file is automatically ignored by git (in the `.vercel/` directory).
342
- - **No manual compilation needed**: The Vercel CLI handles compilation automatically - no need to run a separate command.
117
+
118
+
119
+ - **Versioned API**: Use `@vercel/config/v1` for imports to enable future versioning.
343
120
 
344
121
  ## Learn More
345
122
 
package/dist/cli.js CHANGED
@@ -27,6 +27,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
27
27
  const fs_1 = require("fs");
28
28
  const path_1 = require("path");
29
29
  const fs_2 = require("fs");
30
+ const validation_1 = require("./utils/validation");
30
31
  /**
31
32
  * Named exports that should NOT be auto-converted to config
32
33
  * (these are route-based features that compile into the routes array, or internal module properties)
@@ -37,10 +38,9 @@ const ROUTE_BASED_EXPORTS = new Set([
37
38
  'redirects',
38
39
  'rewrites',
39
40
  'headers',
40
- 'crons',
41
41
  'env',
42
42
  'cacheControl',
43
- '__esModule' // ES module metadata
43
+ '__esModule'
44
44
  ]);
45
45
  /**
46
46
  * Read the user's vercel.ts file and collect both default export and export const declarations
@@ -69,46 +69,51 @@ async function configureRouter() {
69
69
  };
70
70
  }
71
71
  /**
72
- * Read existing vercel.json and extract fields to preserve
72
+ * Compile vercel.ts to JSON and output to stdout
73
73
  */
74
- function readExistingVercelConfig() {
75
- const vercelJsonPath = (0, path_1.resolve)(process.cwd(), "vercel.json");
76
- if (!(0, fs_2.existsSync)(vercelJsonPath)) {
77
- return {};
74
+ async function compileConfig() {
75
+ try {
76
+ const config = await configureRouter();
77
+ const json = JSON.stringify(config, null, 2);
78
+ console.log(json);
79
+ }
80
+ catch (error) {
81
+ console.error("Failed to compile config:", error);
82
+ process.exit(1);
78
83
  }
84
+ }
85
+ /**
86
+ * Validate the vercel.ts config
87
+ */
88
+ async function validateConfig() {
89
+ var _a, _b, _c, _d;
79
90
  try {
80
- const content = (0, fs_1.readFileSync)(vercelJsonPath, "utf-8");
81
- const existing = JSON.parse(content);
82
- // Extract fields we want to preserve
83
- const preserved = {};
84
- if (existing.buildCommand) {
85
- preserved.buildCommand = existing.buildCommand;
86
- }
87
- if (existing.installCommand) {
88
- preserved.installCommand = existing.installCommand;
89
- }
90
- return preserved;
91
+ const config = await configureRouter();
92
+ // Validate static fields
93
+ (0, validation_1.validateStaticFields)(config);
94
+ console.log("✓ Config is valid");
95
+ console.log(` - buildCommand: ${config.buildCommand || '(not set)'}`);
96
+ console.log(` - framework: ${config.framework || '(not set)'}`);
97
+ console.log(` - routes: ${((_a = config.routes) === null || _a === void 0 ? void 0 : _a.length) || 0} route(s)`);
98
+ console.log(` - redirects: ${((_b = config.redirects) === null || _b === void 0 ? void 0 : _b.length) || 0} redirect(s)`);
99
+ console.log(` - rewrites: ${((_c = config.rewrites) === null || _c === void 0 ? void 0 : _c.length) || 0} rewrite(s)`);
100
+ console.log(` - headers: ${((_d = config.headers) === null || _d === void 0 ? void 0 : _d.length) || 0} header(s)`);
91
101
  }
92
102
  catch (error) {
93
- console.warn("Could not read existing vercel.json:", error);
94
- return {};
103
+ console.error(" Config validation failed:");
104
+ console.error(` ${error}`);
105
+ process.exit(1);
95
106
  }
96
107
  }
97
108
  /**
98
- * Generates the vercel.json file
109
+ * Generate vercel.json file (for backwards compatibility / development)
99
110
  */
100
111
  async function generateVercelConfig() {
101
112
  try {
102
113
  const config = await configureRouter();
103
- const existingFields = readExistingVercelConfig();
104
- // Merge: generated config takes precedence, but preserve existing build/install commands if not set
105
- const mergedConfig = {
106
- ...existingFields,
107
- ...config,
108
- };
109
- const vercelConfig = JSON.stringify(mergedConfig, null, 2);
114
+ const json = JSON.stringify(config, null, 2);
110
115
  const outputPath = (0, path_1.resolve)(process.cwd(), "vercel.json");
111
- (0, fs_1.writeFileSync)(outputPath, vercelConfig);
116
+ (0, fs_1.writeFileSync)(outputPath, json);
112
117
  console.log("Successfully generated vercel.json");
113
118
  }
114
119
  catch (error) {
@@ -116,7 +121,30 @@ async function generateVercelConfig() {
116
121
  process.exit(1);
117
122
  }
118
123
  }
124
+ /**
125
+ * CLI entry point
126
+ */
127
+ async function main() {
128
+ const command = process.argv[2];
129
+ switch (command) {
130
+ case 'compile':
131
+ await compileConfig();
132
+ break;
133
+ case 'validate':
134
+ await validateConfig();
135
+ break;
136
+ case 'generate':
137
+ case undefined:
138
+ // Default to generate for backwards compatibility
139
+ await generateVercelConfig();
140
+ break;
141
+ default:
142
+ console.error(`Unknown command: ${command}`);
143
+ console.error('Available commands: compile, validate, generate');
144
+ process.exit(1);
145
+ }
146
+ }
119
147
  // Run if this file is executed directly
120
148
  if (require.main === module) {
121
- generateVercelConfig();
149
+ main();
122
150
  }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export { createRouter, Router } from "./router";
2
2
  export * from "./router";
3
+ export type { VercelConfig, Redirect, Rewrite, HeaderRule, Condition, RouteType } from "./types";
4
+ export { validateStaticString, validateStaticBoolean, validateStaticObject, validateStaticStringArray, validateStaticFields } from "./utils/validation";
package/dist/index.js CHANGED
@@ -14,8 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.Router = exports.createRouter = void 0;
17
+ exports.validateStaticFields = exports.validateStaticStringArray = exports.validateStaticObject = exports.validateStaticBoolean = exports.validateStaticString = exports.Router = exports.createRouter = void 0;
18
18
  var router_1 = require("./router");
19
19
  Object.defineProperty(exports, "createRouter", { enumerable: true, get: function () { return router_1.createRouter; } });
20
20
  Object.defineProperty(exports, "Router", { enumerable: true, get: function () { return router_1.Router; } });
21
21
  __exportStar(require("./router"), exports);
22
+ var validation_1 = require("./utils/validation");
23
+ Object.defineProperty(exports, "validateStaticString", { enumerable: true, get: function () { return validation_1.validateStaticString; } });
24
+ Object.defineProperty(exports, "validateStaticBoolean", { enumerable: true, get: function () { return validation_1.validateStaticBoolean; } });
25
+ Object.defineProperty(exports, "validateStaticObject", { enumerable: true, get: function () { return validation_1.validateStaticObject; } });
26
+ Object.defineProperty(exports, "validateStaticStringArray", { enumerable: true, get: function () { return validation_1.validateStaticStringArray; } });
27
+ Object.defineProperty(exports, "validateStaticFields", { enumerable: true, get: function () { return validation_1.validateStaticFields; } });