@vercel/config 0.0.14 → 0.0.17

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,92 @@ 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
16
  import { createRouter } from '@vercel/config';
17
+ import type { VercelConfig } from '@vercel/config';
17
18
 
18
19
  const router = createRouter();
19
20
 
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: '1 week',
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: '1 week',
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
- });
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
21
+ export const config: VercelConfig = {
22
+ buildCommand: 'npm run build',
23
+ framework: 'nextjs',
24
+
25
+ rewrites: [
26
+ // Simple rewrite
27
+ router.rewrite('/api/(.*)', 'https://backend.api.example.com/$1'),
28
+
29
+ // Rewrite with transforms
30
+ router.rewrite('/users/:userId', 'https://api.example.com/users/$1',
31
+ ({ userId, env }) => ({
32
+ requestHeaders: {
33
+ 'x-user-id': userId,
34
+ 'authorization': `Bearer ${env.API_TOKEN}`
35
+ }
36
+ })
37
+ )
38
+ ],
39
+
40
+ redirects: [
41
+ router.redirect('/old-docs', '/docs', { permanent: true })
42
+ ],
221
43
 
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
- });
44
+ headers: [
45
+ router.cacheControl('/static/(.*)', {
46
+ public: true,
47
+ maxAge: '1 week',
48
+ immutable: true
49
+ })
50
+ ],
229
51
 
230
- // Ends with (suffix)
231
- router.redirect('/dev/(.*)', '/development/$1', {
232
- has: [
233
- { type: 'header', key: 'x-environment', suf: '-dev' }
52
+ crons: [
53
+ { path: '/api/cleanup', schedule: '0 0 * * *' }
234
54
  ]
235
- });
55
+ };
236
56
  ```
237
57
 
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
- });
58
+ ## Features
254
59
 
255
- // Less than
256
- router.redirect('/legacy/(.*)', '/upgrade/$1', {
257
- has: [
258
- { type: 'query', key: 'api-version', lt: 2 }
259
- ]
260
- });
60
+ - **Type-safe configuration** - Full TypeScript support with IDE autocomplete
61
+ - **Readable syntax** - Helper methods like `router.redirect()`, `router.rewrite()`, `router.header()`
62
+ - **Transforms** - Modify request/response headers and query parameters on the fly
63
+ - **Conditions** - Advanced routing with `has` and `missing` conditions
64
+ - **CLI tools** - `compile` and `validate` commands for development
261
65
 
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
66
+ ## Build-Time Compilation
271
67
 
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
- });
68
+ Your `vercel.ts` is automatically compiled to `vercel.json` during:
69
+ ```bash
70
+ vercel build
71
+ vercel dev
72
+ vercel deploy
286
73
  ```
287
74
 
288
- ### Multiple Conditions
75
+ No manual build step needed - the Vercel CLI handles compilation automatically.
289
76
 
290
- All conditions in a `has` or `missing` array must match (AND logic):
77
+ ## CLI Commands
291
78
 
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
- ```
79
+ For development and validation:
302
80
 
303
- ### Combining with Transforms
81
+ ```bash
82
+ # Compile vercel.ts to JSON (output to stdout)
83
+ npx @vercel/config compile
304
84
 
305
- You can combine conditions with transforms for powerful routing logic:
85
+ # Validate config for errors and show summary
86
+ npx @vercel/config validate
306
87
 
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
- }));
88
+ # Generate vercel.json locally (for development)
89
+ npx @vercel/config generate
321
90
  ```
322
91
 
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
-
338
92
  ## Important Notes
339
93
 
340
- - **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.
94
+ - **One config file only**: You cannot have both `vercel.ts` and `vercel.json`.
95
+ - **Transforms compile to routes**: When you add transforms to rewrites/redirects (like `requestHeaders`), they're compiled to the lower-level `routes` primitive internally.
96
+ - **Automatic compilation**: The Vercel CLI compiles `vercel.ts` automatically.
343
97
 
344
98
  ## Learn More
345
99
 
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)
@@ -68,46 +69,51 @@ async function configureRouter() {
68
69
  };
69
70
  }
70
71
  /**
71
- * Read existing vercel.json and extract fields to preserve
72
+ * Compile vercel.ts to JSON and output to stdout
72
73
  */
73
- function readExistingVercelConfig() {
74
- const vercelJsonPath = (0, path_1.resolve)(process.cwd(), "vercel.json");
75
- if (!(0, fs_2.existsSync)(vercelJsonPath)) {
76
- 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);
77
83
  }
84
+ }
85
+ /**
86
+ * Validate the vercel.ts config
87
+ */
88
+ async function validateConfig() {
89
+ var _a, _b, _c, _d;
78
90
  try {
79
- const content = (0, fs_1.readFileSync)(vercelJsonPath, "utf-8");
80
- const existing = JSON.parse(content);
81
- // Extract fields we want to preserve
82
- const preserved = {};
83
- if (existing.buildCommand) {
84
- preserved.buildCommand = existing.buildCommand;
85
- }
86
- if (existing.installCommand) {
87
- preserved.installCommand = existing.installCommand;
88
- }
89
- 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)`);
90
101
  }
91
102
  catch (error) {
92
- console.warn("Could not read existing vercel.json:", error);
93
- return {};
103
+ console.error(" Config validation failed:");
104
+ console.error(` ${error}`);
105
+ process.exit(1);
94
106
  }
95
107
  }
96
108
  /**
97
- * Generates the vercel.json file
109
+ * Generate vercel.json file (for backwards compatibility / development)
98
110
  */
99
111
  async function generateVercelConfig() {
100
112
  try {
101
113
  const config = await configureRouter();
102
- const existingFields = readExistingVercelConfig();
103
- // Merge: generated config takes precedence, but preserve existing build/install commands if not set
104
- const mergedConfig = {
105
- ...existingFields,
106
- ...config,
107
- };
108
- const vercelConfig = JSON.stringify(mergedConfig, null, 2);
114
+ const json = JSON.stringify(config, null, 2);
109
115
  const outputPath = (0, path_1.resolve)(process.cwd(), "vercel.json");
110
- (0, fs_1.writeFileSync)(outputPath, vercelConfig);
116
+ (0, fs_1.writeFileSync)(outputPath, json);
111
117
  console.log("Successfully generated vercel.json");
112
118
  }
113
119
  catch (error) {
@@ -115,7 +121,30 @@ async function generateVercelConfig() {
115
121
  process.exit(1);
116
122
  }
117
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
+ }
118
147
  // Run if this file is executed directly
119
148
  if (require.main === module) {
120
- generateVercelConfig();
149
+ main();
121
150
  }