@ripple-ts/adapter-vercel 0.2.214
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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/package.json +37 -0
- package/src/adapt.js +453 -0
- package/src/bin/adapt.js +70 -0
- package/src/index.js +10 -0
- package/tests/adapt.test.js +435 -0
- package/tests/types.test.js +84 -0
- package/types/index.d.ts +228 -0
package/src/bin/adapt.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for `ripple-adapt-vercel`.
|
|
5
|
+
*
|
|
6
|
+
* Runs the adapt() function to generate Vercel Build Output API v3
|
|
7
|
+
* from the Ripple build output.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ripple-adapt-vercel [--out-dir dist] [--runtime nodejs22.x] [--regions iad1,sfo1]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { adapt } from '../adapt.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse simple CLI flags.
|
|
17
|
+
*
|
|
18
|
+
* @param {string[]} argv
|
|
19
|
+
* @returns {Record<string, string>}
|
|
20
|
+
*/
|
|
21
|
+
function parse_args(argv) {
|
|
22
|
+
/** @type {Record<string, string>} */
|
|
23
|
+
const args = {};
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
if (arg.startsWith('--')) {
|
|
27
|
+
const key = arg.slice(2);
|
|
28
|
+
const value = argv[i + 1];
|
|
29
|
+
if (value && !value.startsWith('--')) {
|
|
30
|
+
args[key] = value;
|
|
31
|
+
i++;
|
|
32
|
+
} else {
|
|
33
|
+
args[key] = 'true';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return args;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const args = parse_args(process.argv.slice(2));
|
|
41
|
+
|
|
42
|
+
/** @type {import('@ripple-ts/adapter-vercel').AdaptOptions} */
|
|
43
|
+
const options = {};
|
|
44
|
+
|
|
45
|
+
if (args['out-dir']) {
|
|
46
|
+
options.outDir = args['out-dir'];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (args['runtime'] || args['regions'] || args['memory'] || args['max-duration']) {
|
|
50
|
+
options.serverless = {};
|
|
51
|
+
if (args['runtime']) {
|
|
52
|
+
options.serverless.runtime = args['runtime'];
|
|
53
|
+
}
|
|
54
|
+
if (args['regions']) {
|
|
55
|
+
options.serverless.regions = args['regions'].split(',');
|
|
56
|
+
}
|
|
57
|
+
if (args['memory']) {
|
|
58
|
+
options.serverless.memory = Number(args['memory']);
|
|
59
|
+
}
|
|
60
|
+
if (args['max-duration']) {
|
|
61
|
+
options.serverless.maxDuration = Number(args['max-duration']);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await adapt(options);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(error instanceof Error ? error.message : error);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ripple-ts/adapter-vercel — Vercel adapter for the Ripple metaframework.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports Node.js runtime primitives from @ripple-ts/adapter-node
|
|
5
|
+
* (Vercel Serverless Functions run on Node.js) and provides the `adapt()`
|
|
6
|
+
* function that generates Vercel's Build Output API v3 structure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { runtime, serve } from '@ripple-ts/adapter-node';
|
|
10
|
+
export { adapt } from './adapt.js';
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
// Mock @vercel/nft to avoid actual dependency tracing in unit tests
|
|
7
|
+
vi.mock('@vercel/nft', () => ({
|
|
8
|
+
nodeFileTrace: vi.fn(async () => ({
|
|
9
|
+
fileList: new Set(),
|
|
10
|
+
warnings: [],
|
|
11
|
+
})),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('adapt()', () => {
|
|
15
|
+
/** @type {string} */
|
|
16
|
+
let tmp_dir;
|
|
17
|
+
/** @type {string} */
|
|
18
|
+
let original_cwd;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
tmp_dir = mkdtempSync(join(tmpdir(), 'ripple-adapter-vercel-'));
|
|
22
|
+
original_cwd = process.cwd();
|
|
23
|
+
process.chdir(tmp_dir);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
process.chdir(original_cwd);
|
|
28
|
+
rmSync(tmp_dir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a minimal Ripple build output structure.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} root
|
|
35
|
+
* @param {{ outDir?: string }} [options]
|
|
36
|
+
*/
|
|
37
|
+
function create_build_output(root, options = {}) {
|
|
38
|
+
const { outDir = 'dist' } = options;
|
|
39
|
+
const build_dir = join(root, outDir);
|
|
40
|
+
|
|
41
|
+
// Client output
|
|
42
|
+
const client_dir = join(build_dir, 'client');
|
|
43
|
+
mkdirSync(join(client_dir, 'assets'), { recursive: true });
|
|
44
|
+
writeFileSync(join(client_dir, 'index.html'), '<!doctype html><html></html>');
|
|
45
|
+
writeFileSync(join(client_dir, 'assets', 'app.abc12345.js'), 'console.log("app")');
|
|
46
|
+
writeFileSync(join(client_dir, 'assets', 'style.def67890.css'), 'body{}');
|
|
47
|
+
|
|
48
|
+
// Server output
|
|
49
|
+
const server_dir = join(build_dir, 'server');
|
|
50
|
+
mkdirSync(server_dir, { recursive: true });
|
|
51
|
+
writeFileSync(
|
|
52
|
+
join(server_dir, 'entry.js'),
|
|
53
|
+
'export const handler = (req) => new Response("ok");',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
it('throws when client build output is missing', async () => {
|
|
58
|
+
const { adapt } = await import('../src/adapt.js');
|
|
59
|
+
await expect(adapt()).rejects.toThrow('Client build output not found');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('throws when server entry is missing', async () => {
|
|
63
|
+
const { adapt } = await import('../src/adapt.js');
|
|
64
|
+
const client_dir = join(tmp_dir, 'dist', 'client');
|
|
65
|
+
mkdirSync(client_dir, { recursive: true });
|
|
66
|
+
writeFileSync(join(client_dir, 'index.html'), '<html></html>');
|
|
67
|
+
|
|
68
|
+
await expect(adapt()).rejects.toThrow('Server entry not found');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('generates .vercel/output/ structure', async () => {
|
|
72
|
+
const { adapt } = await import('../src/adapt.js');
|
|
73
|
+
create_build_output(tmp_dir);
|
|
74
|
+
|
|
75
|
+
await adapt();
|
|
76
|
+
|
|
77
|
+
const output_dir = join(tmp_dir, '.vercel', 'output');
|
|
78
|
+
|
|
79
|
+
// config.json exists
|
|
80
|
+
expect(existsSync(join(output_dir, 'config.json'))).toBe(true);
|
|
81
|
+
|
|
82
|
+
// Static directory exists with assets
|
|
83
|
+
expect(existsSync(join(output_dir, 'static', 'assets', 'app.abc12345.js'))).toBe(true);
|
|
84
|
+
expect(existsSync(join(output_dir, 'static', 'assets', 'style.def67890.css'))).toBe(true);
|
|
85
|
+
|
|
86
|
+
// index.html is removed from static (SSR handles root)
|
|
87
|
+
expect(existsSync(join(output_dir, 'static', 'index.html'))).toBe(false);
|
|
88
|
+
|
|
89
|
+
// Function directory exists
|
|
90
|
+
expect(existsSync(join(output_dir, 'functions', 'index.func'))).toBe(true);
|
|
91
|
+
expect(existsSync(join(output_dir, 'functions', 'index.func', 'index.js'))).toBe(true);
|
|
92
|
+
expect(existsSync(join(output_dir, 'functions', 'index.func', '.vc-config.json'))).toBe(true);
|
|
93
|
+
expect(existsSync(join(output_dir, 'functions', 'index.func', 'package.json'))).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('generates valid config.json with Build Output API v3', async () => {
|
|
97
|
+
const { adapt } = await import('../src/adapt.js');
|
|
98
|
+
create_build_output(tmp_dir);
|
|
99
|
+
|
|
100
|
+
await adapt();
|
|
101
|
+
|
|
102
|
+
const config = JSON.parse(
|
|
103
|
+
readFileSync(join(tmp_dir, '.vercel', 'output', 'config.json'), 'utf-8'),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(config.version).toBe(3);
|
|
107
|
+
expect(config.cleanUrls).toBe(true);
|
|
108
|
+
expect(Array.isArray(config.routes)).toBe(true);
|
|
109
|
+
|
|
110
|
+
// Should have a filesystem handler
|
|
111
|
+
const filesystem_route = config.routes.find(
|
|
112
|
+
(/** @type {any} */ r) => r.handle === 'filesystem',
|
|
113
|
+
);
|
|
114
|
+
expect(filesystem_route).toBeTruthy();
|
|
115
|
+
|
|
116
|
+
// Should have a catch-all route to the serverless function
|
|
117
|
+
const catchall_route = config.routes.find((/** @type {any} */ r) => r.src === '/.*' && r.dest);
|
|
118
|
+
expect(catchall_route).toBeTruthy();
|
|
119
|
+
expect(catchall_route.dest).toBe('/index');
|
|
120
|
+
|
|
121
|
+
// Should have immutable cache header for assets
|
|
122
|
+
const asset_route = config.routes.find((/** @type {any} */ r) => r.src === '/assets/.+');
|
|
123
|
+
expect(asset_route).toBeTruthy();
|
|
124
|
+
expect(asset_route.headers['Cache-Control']).toContain('immutable');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('generates valid .vc-config.json', async () => {
|
|
128
|
+
const { adapt } = await import('../src/adapt.js');
|
|
129
|
+
create_build_output(tmp_dir);
|
|
130
|
+
|
|
131
|
+
await adapt();
|
|
132
|
+
|
|
133
|
+
const vc_config = JSON.parse(
|
|
134
|
+
readFileSync(
|
|
135
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', '.vc-config.json'),
|
|
136
|
+
'utf-8',
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(vc_config.handler).toBe('index.js');
|
|
141
|
+
expect(vc_config.launcherType).toBe('Nodejs');
|
|
142
|
+
expect(vc_config.experimentalResponseStreaming).toBe(true);
|
|
143
|
+
expect(vc_config.framework.slug).toBe('ripple');
|
|
144
|
+
expect(vc_config.framework.version).toMatch(/^\d+\.\d+\.\d+/);
|
|
145
|
+
expect(vc_config.runtime).toMatch(/^nodejs\d+\.x$/);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('generates ESM package.json in function directory', async () => {
|
|
149
|
+
const { adapt } = await import('../src/adapt.js');
|
|
150
|
+
create_build_output(tmp_dir);
|
|
151
|
+
|
|
152
|
+
await adapt();
|
|
153
|
+
|
|
154
|
+
const pkg = JSON.parse(
|
|
155
|
+
readFileSync(
|
|
156
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', 'package.json'),
|
|
157
|
+
'utf-8',
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(pkg.type).toBe('module');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('generates handler that bridges Node.js req/res to Ripple fetch handler', async () => {
|
|
165
|
+
const { adapt } = await import('../src/adapt.js');
|
|
166
|
+
create_build_output(tmp_dir);
|
|
167
|
+
|
|
168
|
+
await adapt();
|
|
169
|
+
|
|
170
|
+
const handler_source = readFileSync(
|
|
171
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', 'index.js'),
|
|
172
|
+
'utf-8',
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
expect(handler_source).toContain('import { handler }');
|
|
176
|
+
expect(handler_source).toContain(
|
|
177
|
+
"import { nodeRequestToWebRequest, webResponseToNodeResponse } from '@ripple-ts/adapter-node'",
|
|
178
|
+
);
|
|
179
|
+
expect(handler_source).toContain('export default async function (req, res)');
|
|
180
|
+
expect(handler_source).toContain('nodeRequestToWebRequest(req, controller.signal, true)');
|
|
181
|
+
expect(handler_source).toContain('webResponseToNodeResponse(response, res,');
|
|
182
|
+
|
|
183
|
+
// Handler should import the server entry at its project-relative path
|
|
184
|
+
// (dist/server/entry.js), not just "entry.js"
|
|
185
|
+
expect(handler_source).toContain('dist/server/entry.js');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('respects custom outDir', async () => {
|
|
189
|
+
const { adapt } = await import('../src/adapt.js');
|
|
190
|
+
create_build_output(tmp_dir, { outDir: 'build' });
|
|
191
|
+
|
|
192
|
+
await adapt({ outDir: 'build' });
|
|
193
|
+
|
|
194
|
+
expect(existsSync(join(tmp_dir, '.vercel', 'output', 'config.json'))).toBe(true);
|
|
195
|
+
expect(existsSync(join(tmp_dir, '.vercel', 'output', 'functions', 'index.func'))).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('applies serverless config options', async () => {
|
|
199
|
+
const { adapt } = await import('../src/adapt.js');
|
|
200
|
+
create_build_output(tmp_dir);
|
|
201
|
+
|
|
202
|
+
await adapt({
|
|
203
|
+
serverless: {
|
|
204
|
+
runtime: 'nodejs22.x',
|
|
205
|
+
regions: ['iad1', 'sfo1'],
|
|
206
|
+
memory: 1024,
|
|
207
|
+
maxDuration: 30,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const vc_config = JSON.parse(
|
|
212
|
+
readFileSync(
|
|
213
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', '.vc-config.json'),
|
|
214
|
+
'utf-8',
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect(vc_config.runtime).toBe('nodejs22.x');
|
|
219
|
+
expect(vc_config.regions).toEqual(['iad1', 'sfo1']);
|
|
220
|
+
expect(vc_config.memory).toBe(1024);
|
|
221
|
+
expect(vc_config.maxDuration).toBe(30);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('includes custom redirects in config', async () => {
|
|
225
|
+
const { adapt } = await import('../src/adapt.js');
|
|
226
|
+
create_build_output(tmp_dir);
|
|
227
|
+
|
|
228
|
+
await adapt({
|
|
229
|
+
redirects: [{ source: '/old', destination: '/new', permanent: true }],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const config = JSON.parse(
|
|
233
|
+
readFileSync(join(tmp_dir, '.vercel', 'output', 'config.json'), 'utf-8'),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const redirect = config.routes.find(
|
|
237
|
+
(/** @type {any} */ r) => r.src === '/old' && r.headers?.Location,
|
|
238
|
+
);
|
|
239
|
+
expect(redirect).toBeTruthy();
|
|
240
|
+
expect(redirect.headers.Location).toBe('/new');
|
|
241
|
+
expect(redirect.status).toBe(308);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('includes custom headers in config', async () => {
|
|
245
|
+
const { adapt } = await import('../src/adapt.js');
|
|
246
|
+
create_build_output(tmp_dir);
|
|
247
|
+
|
|
248
|
+
await adapt({
|
|
249
|
+
headers: [
|
|
250
|
+
{
|
|
251
|
+
source: '/(.*)',
|
|
252
|
+
headers: [{ key: 'X-Custom', value: 'test' }],
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const config = JSON.parse(
|
|
258
|
+
readFileSync(join(tmp_dir, '.vercel', 'output', 'config.json'), 'utf-8'),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const header_route = config.routes.find(
|
|
262
|
+
(/** @type {any} */ r) => r.src === '/(.*)' && r.headers?.['X-Custom'],
|
|
263
|
+
);
|
|
264
|
+
expect(header_route).toBeTruthy();
|
|
265
|
+
expect(header_route.headers['X-Custom']).toBe('test');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('includes images config when provided', async () => {
|
|
269
|
+
const { adapt } = await import('../src/adapt.js');
|
|
270
|
+
create_build_output(tmp_dir);
|
|
271
|
+
|
|
272
|
+
await adapt({
|
|
273
|
+
images: {
|
|
274
|
+
sizes: [640, 1080, 1920],
|
|
275
|
+
domains: ['example.com'],
|
|
276
|
+
formats: ['image/avif', 'image/webp'],
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const config = JSON.parse(
|
|
281
|
+
readFileSync(join(tmp_dir, '.vercel', 'output', 'config.json'), 'utf-8'),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect(config.images).toEqual({
|
|
285
|
+
sizes: [640, 1080, 1920],
|
|
286
|
+
domains: ['example.com'],
|
|
287
|
+
formats: ['image/avif', 'image/webp'],
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('cleans previous output before generating', async () => {
|
|
292
|
+
const { adapt } = await import('../src/adapt.js');
|
|
293
|
+
create_build_output(tmp_dir);
|
|
294
|
+
|
|
295
|
+
// Create a stale file in the output directory
|
|
296
|
+
const stale_dir = join(tmp_dir, '.vercel', 'output', 'stale');
|
|
297
|
+
mkdirSync(stale_dir, { recursive: true });
|
|
298
|
+
writeFileSync(join(stale_dir, 'old-file.txt'), 'stale');
|
|
299
|
+
|
|
300
|
+
await adapt();
|
|
301
|
+
|
|
302
|
+
// Stale file should be gone
|
|
303
|
+
expect(existsSync(join(stale_dir, 'old-file.txt'))).toBe(false);
|
|
304
|
+
// Fresh output should exist
|
|
305
|
+
expect(existsSync(join(tmp_dir, '.vercel', 'output', 'config.json'))).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('handles trailingSlash option', async () => {
|
|
309
|
+
const { adapt } = await import('../src/adapt.js');
|
|
310
|
+
create_build_output(tmp_dir);
|
|
311
|
+
|
|
312
|
+
await adapt({ trailingSlash: true });
|
|
313
|
+
|
|
314
|
+
const config = JSON.parse(
|
|
315
|
+
readFileSync(join(tmp_dir, '.vercel', 'output', 'config.json'), 'utf-8'),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
expect(config.trailingSlash).toBe(true);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('handles cleanUrls option', async () => {
|
|
322
|
+
const { adapt } = await import('../src/adapt.js');
|
|
323
|
+
create_build_output(tmp_dir);
|
|
324
|
+
|
|
325
|
+
await adapt({ cleanUrls: false });
|
|
326
|
+
|
|
327
|
+
const config = JSON.parse(
|
|
328
|
+
readFileSync(join(tmp_dir, '.vercel', 'output', 'config.json'), 'utf-8'),
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
expect(config.cleanUrls).toBe(false);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// ---------------------------------------------------------------
|
|
335
|
+
// ISR (Incremental Static Regeneration)
|
|
336
|
+
// ---------------------------------------------------------------
|
|
337
|
+
|
|
338
|
+
it('adds prerender config to .vc-config.json when isr is set', async () => {
|
|
339
|
+
const { adapt } = await import('../src/adapt.js');
|
|
340
|
+
create_build_output(tmp_dir);
|
|
341
|
+
|
|
342
|
+
await adapt({
|
|
343
|
+
isr: {
|
|
344
|
+
expiration: 60,
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const vc_config = JSON.parse(
|
|
349
|
+
readFileSync(
|
|
350
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', '.vc-config.json'),
|
|
351
|
+
'utf-8',
|
|
352
|
+
),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
expect(vc_config.prerender).toBeTruthy();
|
|
356
|
+
expect(vc_config.prerender.expiration).toBe(60);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('supports isr.expiration = false for never-expiring cache', async () => {
|
|
360
|
+
const { adapt } = await import('../src/adapt.js');
|
|
361
|
+
create_build_output(tmp_dir);
|
|
362
|
+
|
|
363
|
+
await adapt({
|
|
364
|
+
isr: {
|
|
365
|
+
expiration: false,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const vc_config = JSON.parse(
|
|
370
|
+
readFileSync(
|
|
371
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', '.vc-config.json'),
|
|
372
|
+
'utf-8',
|
|
373
|
+
),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
expect(vc_config.prerender).toBeTruthy();
|
|
377
|
+
expect(vc_config.prerender.expiration).toBe(false);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('includes bypassToken and allowQuery in prerender config', async () => {
|
|
381
|
+
const { adapt } = await import('../src/adapt.js');
|
|
382
|
+
create_build_output(tmp_dir);
|
|
383
|
+
|
|
384
|
+
await adapt({
|
|
385
|
+
isr: {
|
|
386
|
+
expiration: 300,
|
|
387
|
+
bypassToken: 'my-secret-token',
|
|
388
|
+
allowQuery: ['page', 'q'],
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const vc_config = JSON.parse(
|
|
393
|
+
readFileSync(
|
|
394
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', '.vc-config.json'),
|
|
395
|
+
'utf-8',
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
expect(vc_config.prerender.expiration).toBe(300);
|
|
400
|
+
expect(vc_config.prerender.bypassToken).toBe('my-secret-token');
|
|
401
|
+
expect(vc_config.prerender.allowQuery).toEqual(['page', 'q']);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('does not add prerender config when isr is false', async () => {
|
|
405
|
+
const { adapt } = await import('../src/adapt.js');
|
|
406
|
+
create_build_output(tmp_dir);
|
|
407
|
+
|
|
408
|
+
await adapt({ isr: false });
|
|
409
|
+
|
|
410
|
+
const vc_config = JSON.parse(
|
|
411
|
+
readFileSync(
|
|
412
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', '.vc-config.json'),
|
|
413
|
+
'utf-8',
|
|
414
|
+
),
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
expect(vc_config.prerender).toBeUndefined();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('does not add prerender config when isr is omitted', async () => {
|
|
421
|
+
const { adapt } = await import('../src/adapt.js');
|
|
422
|
+
create_build_output(tmp_dir);
|
|
423
|
+
|
|
424
|
+
await adapt();
|
|
425
|
+
|
|
426
|
+
const vc_config = JSON.parse(
|
|
427
|
+
readFileSync(
|
|
428
|
+
join(tmp_dir, '.vercel', 'output', 'functions', 'index.func', '.vc-config.json'),
|
|
429
|
+
'utf-8',
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
expect(vc_config.prerender).toBeUndefined();
|
|
434
|
+
});
|
|
435
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
describe('adapter-vercel types', () => {
|
|
9
|
+
it('exports runtime and serve from adapter-node', () => {
|
|
10
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
11
|
+
|
|
12
|
+
// Re-exports runtime
|
|
13
|
+
expect(types).toContain('export const runtime: RuntimePrimitives');
|
|
14
|
+
|
|
15
|
+
// Re-exports serve
|
|
16
|
+
expect(types).toContain('export const serve: ServeFunction');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('declares AdaptOptions interface', () => {
|
|
20
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
21
|
+
|
|
22
|
+
expect(types).toContain('export interface AdaptOptions');
|
|
23
|
+
expect(types).toContain('outDir?: string');
|
|
24
|
+
expect(types).toContain('serverless?: ServerlessConfig');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('declares ServerlessConfig interface', () => {
|
|
28
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
29
|
+
|
|
30
|
+
expect(types).toContain('export interface ServerlessConfig');
|
|
31
|
+
expect(types).toContain('runtime?: string');
|
|
32
|
+
expect(types).toContain('regions?: string[]');
|
|
33
|
+
expect(types).toContain('maxDuration?: number');
|
|
34
|
+
expect(types).toContain('memory?: number');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('declares VercelConfig interface for Build Output API v3', () => {
|
|
38
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
39
|
+
|
|
40
|
+
expect(types).toContain('export interface VercelConfig');
|
|
41
|
+
expect(types).toContain('version: 3');
|
|
42
|
+
expect(types).toContain('routes: VercelRoute[]');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('uses shared type aliases from @ripple-ts/adapter', () => {
|
|
46
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
47
|
+
|
|
48
|
+
expect(types).toContain("from '@ripple-ts/adapter'");
|
|
49
|
+
expect(types).toContain('RuntimePrimitives');
|
|
50
|
+
expect(types).toContain('ServeFunction');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('declares adapt() function', () => {
|
|
54
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
55
|
+
|
|
56
|
+
expect(types).toContain('export function adapt(options?: AdaptOptions): Promise<void>');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('declares ISRConfig interface', () => {
|
|
60
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
61
|
+
|
|
62
|
+
expect(types).toContain('export interface ISRConfig');
|
|
63
|
+
expect(types).toContain('expiration: number | false');
|
|
64
|
+
expect(types).toContain('bypassToken?: string');
|
|
65
|
+
expect(types).toContain('allowQuery?: string[]');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('declares ImagesConfig interface', () => {
|
|
69
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
70
|
+
|
|
71
|
+
expect(types).toContain('export interface ImagesConfig');
|
|
72
|
+
expect(types).toContain('sizes: number[]');
|
|
73
|
+
expect(types).toContain('domains: string[]');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('declares Vercel routing types', () => {
|
|
77
|
+
const types = readFileSync(join(__dirname, '..', 'types', 'index.d.ts'), 'utf-8');
|
|
78
|
+
|
|
79
|
+
expect(types).toContain('export interface VercelHeader');
|
|
80
|
+
expect(types).toContain('export interface VercelRedirect');
|
|
81
|
+
expect(types).toContain('export interface VercelRewrite');
|
|
82
|
+
expect(types).toContain('export interface VercelRoute');
|
|
83
|
+
});
|
|
84
|
+
});
|