@sveltejs/adapter-vercel 5.1.0 → 5.2.0

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/files/edge.js CHANGED
@@ -12,6 +12,7 @@ const initialized = server.init({
12
12
  */
13
13
  export default async (request, context) => {
14
14
  await initialized;
15
+
15
16
  return server.respond(request, {
16
17
  getClientAddress() {
17
18
  return /** @type {string} */ (request.headers.get('x-forwarded-for'));
package/index.js CHANGED
@@ -3,7 +3,8 @@ import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { nodeFileTrace } from '@vercel/nft';
5
5
  import esbuild from 'esbuild';
6
- import { get_pathname } from './utils.js';
6
+ import { get_pathname, pattern_to_src } from './utils.js';
7
+ import { VERSION } from '@sveltejs/kit';
7
8
 
8
9
  const name = '@sveltejs/adapter-vercel';
9
10
  const DEFAULT_FUNCTION_NAME = 'fn';
@@ -57,7 +58,12 @@ const plugin = function (defaults = {}) {
57
58
  functions: `${dir}/functions`
58
59
  };
59
60
 
60
- const static_config = static_vercel_config(builder, defaults);
61
+ builder.log.minor('Copying assets...');
62
+
63
+ builder.writeClient(dirs.static);
64
+ builder.writePrerendered(dirs.static);
65
+
66
+ const static_config = static_vercel_config(builder, defaults, dirs.static);
61
67
 
62
68
  builder.log.minor('Generating serverless function...');
63
69
 
@@ -174,7 +180,11 @@ const plugin = function (defaults = {}) {
174
180
  {
175
181
  runtime: config.runtime,
176
182
  regions: config.regions,
177
- entrypoint: 'index.js'
183
+ entrypoint: 'index.js',
184
+ framework: {
185
+ slug: 'sveltekit',
186
+ version: VERSION
187
+ }
178
188
  },
179
189
  null,
180
190
  '\t'
@@ -305,18 +315,7 @@ const plugin = function (defaults = {}) {
305
315
  if (is_prerendered(route)) continue;
306
316
 
307
317
  const pattern = route.pattern.toString();
308
-
309
- let src = pattern
310
- // remove leading / and trailing $/
311
- .slice(1, -2)
312
- // replace escaped \/ with /
313
- .replace(/\\\//g, '/');
314
-
315
- // replace the root route "^/" with "^/?"
316
- if (src === '^/') {
317
- src = '^/?';
318
- }
319
-
318
+ const src = pattern_to_src(pattern);
320
319
  const name = functions.get(pattern) ?? 'fn-0';
321
320
 
322
321
  const isr = isr_config.get(route);
@@ -374,11 +373,6 @@ const plugin = function (defaults = {}) {
374
373
  // including ISR aliases if there is only one function
375
374
  static_config.routes.push({ src: '/.*', dest: `/${DEFAULT_FUNCTION_NAME}` });
376
375
 
377
- builder.log.minor('Copying assets...');
378
-
379
- builder.writeClient(dirs.static);
380
- builder.writePrerendered(dirs.static);
381
-
382
376
  builder.log.minor('Writing routes...');
383
377
 
384
378
  write(`${dir}/config.json`, JSON.stringify(static_config, null, '\t'));
@@ -431,8 +425,9 @@ function write(file, data) {
431
425
  /**
432
426
  * @param {import('@sveltejs/kit').Builder} builder
433
427
  * @param {import('.').Config} config
428
+ * @param {string} dir
434
429
  */
435
- function static_vercel_config(builder, config) {
430
+ function static_vercel_config(builder, config, dir) {
436
431
  /** @type {any[]} */
437
432
  const prerendered_redirects = [];
438
433
 
@@ -473,20 +468,60 @@ function static_vercel_config(builder, config) {
473
468
  overrides[page.file] = { path: overrides_path };
474
469
  }
475
470
 
476
- return {
477
- version: 3,
478
- routes: [
479
- ...prerendered_redirects,
480
- {
481
- src: `/${builder.getAppPath()}/immutable/.+`,
482
- headers: {
483
- 'cache-control': 'public, immutable, max-age=31536000'
471
+ const routes = [
472
+ ...prerendered_redirects,
473
+ {
474
+ src: `/${builder.getAppPath()}/immutable/.+`,
475
+ headers: {
476
+ 'cache-control': 'public, immutable, max-age=31536000'
477
+ }
478
+ }
479
+ ];
480
+
481
+ // https://vercel.com/docs/deployments/skew-protection
482
+ if (process.env.VERCEL_SKEW_PROTECTION_ENABLED) {
483
+ routes.push({
484
+ src: '/.*',
485
+ has: [
486
+ {
487
+ type: 'header',
488
+ key: 'Sec-Fetch-Dest',
489
+ value: 'document'
484
490
  }
491
+ ],
492
+ headers: {
493
+ 'Set-Cookie': `__vdpl=${process.env.VERCEL_DEPLOYMENT_ID}; Path=${builder.config.kit.paths.base}/; SameSite=Strict; Secure; HttpOnly`
485
494
  },
486
- {
487
- handle: 'filesystem'
488
- }
489
- ],
495
+ continue: true
496
+ });
497
+
498
+ // this is a dreadful hack that is necessary until the Vercel Build Output API
499
+ // allows you to set multiple cookies for a single route. essentially, since we
500
+ // know that the entry file will be requested immediately, we can set the second
501
+ // cookie in _that_ response rather than the document response
502
+ const base = `${dir}/${builder.config.kit.appDir}/immutable/entry`;
503
+ const entry = fs.readdirSync(base).find((file) => file.startsWith('start.'));
504
+
505
+ if (!entry) {
506
+ throw new Error('Could not find entry point');
507
+ }
508
+
509
+ routes.splice(-2, 0, {
510
+ src: `/${builder.getAppPath()}/immutable/entry/${entry}`,
511
+ headers: {
512
+ 'Set-Cookie': `__vdpl=; Path=/${builder.getAppPath()}/version.json; SameSite=Strict; Secure; HttpOnly`
513
+ },
514
+ continue: true
515
+ });
516
+ }
517
+
518
+ routes.push({
519
+ handle: 'filesystem'
520
+ });
521
+
522
+ return {
523
+ version: 3,
524
+ routes,
490
525
  overrides,
491
526
  images
492
527
  };
@@ -599,7 +634,11 @@ async function create_function_bundle(builder, entry, dir, config) {
599
634
  maxDuration: config.maxDuration,
600
635
  handler: path.relative(base + ancestor, entry),
601
636
  launcherType: 'Nodejs',
602
- experimentalResponseStreaming: !config.isr
637
+ experimentalResponseStreaming: !config.isr,
638
+ framework: {
639
+ slug: 'sveltekit',
640
+ version: VERSION
641
+ }
603
642
  },
604
643
  null,
605
644
  '\t'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/adapter-vercel",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "A SvelteKit adapter that creates a Vercel app",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  "@types/node": "^18.19.3",
34
34
  "typescript": "^5.3.3",
35
35
  "vitest": "^1.2.0",
36
- "@sveltejs/kit": "^2.4.1"
36
+ "@sveltejs/kit": "^2.5.4"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@sveltejs/kit": "^2.4.0"
package/utils.js CHANGED
@@ -2,22 +2,68 @@
2
2
  export function get_pathname(route) {
3
3
  let i = 1;
4
4
 
5
- return route.segments
5
+ const pathname = route.segments
6
6
  .map((segment) => {
7
7
  if (!segment.dynamic) {
8
- return segment.content;
8
+ return '/' + segment.content;
9
9
  }
10
10
 
11
11
  const parts = segment.content.split(/\[(.+?)\](?!\])/);
12
- return parts
13
- .map((content, j) => {
14
- if (j % 2) {
15
- return `$${i++}`;
16
- } else {
17
- return content;
18
- }
19
- })
20
- .join('');
12
+ let result = '';
13
+
14
+ if (
15
+ parts.length === 3 &&
16
+ !parts[0] &&
17
+ !parts[2] &&
18
+ (parts[1].startsWith('...') || parts[1][0] === '[')
19
+ ) {
20
+ // Special case: segment is a single optional or rest parameter.
21
+ // In that case we don't prepend a slash (also see comment in pattern_to_src).
22
+ result = `$${i++}`;
23
+ } else {
24
+ result =
25
+ '/' +
26
+ parts
27
+ .map((content, j) => {
28
+ if (j % 2) {
29
+ return `$${i++}`;
30
+ } else {
31
+ return content;
32
+ }
33
+ })
34
+ .join('');
35
+ }
36
+
37
+ return result;
21
38
  })
22
- .join('/');
39
+ .join('');
40
+
41
+ return pathname[0] === '/' ? pathname.slice(1) : pathname;
42
+ }
43
+
44
+ /**
45
+ * Adjusts the stringified route regex for Vercel's routing system
46
+ * @param {string} pattern stringified route regex
47
+ */
48
+ export function pattern_to_src(pattern) {
49
+ let src = pattern
50
+ // remove leading / and trailing $/
51
+ .slice(1, -2)
52
+ // replace escaped \/ with /
53
+ .replace(/\\\//g, '/');
54
+
55
+ // replace the root route "^/" with "^/?"
56
+ if (src === '^/') {
57
+ src = '^/?';
58
+ }
59
+
60
+ // Move non-capturing groups that swallow slashes into their following capturing groups.
61
+ // This is necessary because during ISR we're using the regex to construct the __pathname
62
+ // query parameter: In case of a route like [required]/[...rest] we need to turn them
63
+ // into $1$2 and not $1/$2, because if [...rest] is empty, we don't want to have a trailing
64
+ // slash in the __pathname query parameter which wasn't there in the original URL, as that
65
+ // could result in a false trailing slash redirect in the SvelteKit runtime, leading to infinite redirects.
66
+ src = src.replace(/\(\?:\/\((.+?)\)\)/g, '(/$1)');
67
+
68
+ return src;
23
69
  }