@sveltejs/adapter-netlify 5.2.4 → 6.0.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/ambient.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ declare global {
2
+ namespace App {
3
+ export interface Platform {
4
+ context: import('@netlify/functions').Context;
5
+ }
6
+ }
7
+ }
8
+
9
+ export {};
@@ -6,273 +6,6 @@ import process from 'node:process';
6
6
  import 'node:buffer';
7
7
  import 'node:crypto';
8
8
 
9
- var setCookie = {exports: {}};
10
-
11
- var hasRequiredSetCookie;
12
-
13
- function requireSetCookie () {
14
- if (hasRequiredSetCookie) return setCookie.exports;
15
- hasRequiredSetCookie = 1;
16
-
17
- var defaultParseOptions = {
18
- decodeValues: true,
19
- map: false,
20
- silent: false,
21
- };
22
-
23
- function isNonEmptyString(str) {
24
- return typeof str === "string" && !!str.trim();
25
- }
26
-
27
- function parseString(setCookieValue, options) {
28
- var parts = setCookieValue.split(";").filter(isNonEmptyString);
29
-
30
- var nameValuePairStr = parts.shift();
31
- var parsed = parseNameValuePair(nameValuePairStr);
32
- var name = parsed.name;
33
- var value = parsed.value;
34
-
35
- options = options
36
- ? Object.assign({}, defaultParseOptions, options)
37
- : defaultParseOptions;
38
-
39
- try {
40
- value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value
41
- } catch (e) {
42
- console.error(
43
- "set-cookie-parser encountered an error while decoding a cookie with value '" +
44
- value +
45
- "'. Set options.decodeValues to false to disable this feature.",
46
- e
47
- );
48
- }
49
-
50
- var cookie = {
51
- name: name,
52
- value: value,
53
- };
54
-
55
- parts.forEach(function (part) {
56
- var sides = part.split("=");
57
- var key = sides.shift().trimLeft().toLowerCase();
58
- var value = sides.join("=");
59
- if (key === "expires") {
60
- cookie.expires = new Date(value);
61
- } else if (key === "max-age") {
62
- cookie.maxAge = parseInt(value, 10);
63
- } else if (key === "secure") {
64
- cookie.secure = true;
65
- } else if (key === "httponly") {
66
- cookie.httpOnly = true;
67
- } else if (key === "samesite") {
68
- cookie.sameSite = value;
69
- } else {
70
- cookie[key] = value;
71
- }
72
- });
73
-
74
- return cookie;
75
- }
76
-
77
- function parseNameValuePair(nameValuePairStr) {
78
- // Parses name-value-pair according to rfc6265bis draft
79
-
80
- var name = "";
81
- var value = "";
82
- var nameValueArr = nameValuePairStr.split("=");
83
- if (nameValueArr.length > 1) {
84
- name = nameValueArr.shift();
85
- value = nameValueArr.join("="); // everything after the first =, joined by a "=" if there was more than one part
86
- } else {
87
- value = nameValuePairStr;
88
- }
89
-
90
- return { name: name, value: value };
91
- }
92
-
93
- function parse(input, options) {
94
- options = options
95
- ? Object.assign({}, defaultParseOptions, options)
96
- : defaultParseOptions;
97
-
98
- if (!input) {
99
- if (!options.map) {
100
- return [];
101
- } else {
102
- return {};
103
- }
104
- }
105
-
106
- if (input.headers) {
107
- if (typeof input.headers.getSetCookie === "function") {
108
- // for fetch responses - they combine headers of the same type in the headers array,
109
- // but getSetCookie returns an uncombined array
110
- input = input.headers.getSetCookie();
111
- } else if (input.headers["set-cookie"]) {
112
- // fast-path for node.js (which automatically normalizes header names to lower-case
113
- input = input.headers["set-cookie"];
114
- } else {
115
- // slow-path for other environments - see #25
116
- var sch =
117
- input.headers[
118
- Object.keys(input.headers).find(function (key) {
119
- return key.toLowerCase() === "set-cookie";
120
- })
121
- ];
122
- // warn if called on a request-like object with a cookie header rather than a set-cookie header - see #34, 36
123
- if (!sch && input.headers.cookie && !options.silent) {
124
- console.warn(
125
- "Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning."
126
- );
127
- }
128
- input = sch;
129
- }
130
- }
131
- if (!Array.isArray(input)) {
132
- input = [input];
133
- }
134
-
135
- options = options
136
- ? Object.assign({}, defaultParseOptions, options)
137
- : defaultParseOptions;
138
-
139
- if (!options.map) {
140
- return input.filter(isNonEmptyString).map(function (str) {
141
- return parseString(str, options);
142
- });
143
- } else {
144
- var cookies = {};
145
- return input.filter(isNonEmptyString).reduce(function (cookies, str) {
146
- var cookie = parseString(str, options);
147
- cookies[cookie.name] = cookie;
148
- return cookies;
149
- }, cookies);
150
- }
151
- }
152
-
153
- /*
154
- Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
155
- that are within a single set-cookie field-value, such as in the Expires portion.
156
-
157
- This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
158
- Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
159
- React Native's fetch does this for *every* header, including set-cookie.
160
-
161
- Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
162
- Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
163
- */
164
- function splitCookiesString(cookiesString) {
165
- if (Array.isArray(cookiesString)) {
166
- return cookiesString;
167
- }
168
- if (typeof cookiesString !== "string") {
169
- return [];
170
- }
171
-
172
- var cookiesStrings = [];
173
- var pos = 0;
174
- var start;
175
- var ch;
176
- var lastComma;
177
- var nextStart;
178
- var cookiesSeparatorFound;
179
-
180
- function skipWhitespace() {
181
- while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
182
- pos += 1;
183
- }
184
- return pos < cookiesString.length;
185
- }
186
-
187
- function notSpecialChar() {
188
- ch = cookiesString.charAt(pos);
189
-
190
- return ch !== "=" && ch !== ";" && ch !== ",";
191
- }
192
-
193
- while (pos < cookiesString.length) {
194
- start = pos;
195
- cookiesSeparatorFound = false;
196
-
197
- while (skipWhitespace()) {
198
- ch = cookiesString.charAt(pos);
199
- if (ch === ",") {
200
- // ',' is a cookie separator if we have later first '=', not ';' or ','
201
- lastComma = pos;
202
- pos += 1;
203
-
204
- skipWhitespace();
205
- nextStart = pos;
206
-
207
- while (pos < cookiesString.length && notSpecialChar()) {
208
- pos += 1;
209
- }
210
-
211
- // currently special character
212
- if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
213
- // we found cookies separator
214
- cookiesSeparatorFound = true;
215
- // pos is inside the next cookie, so back up and return it.
216
- pos = nextStart;
217
- cookiesStrings.push(cookiesString.substring(start, lastComma));
218
- start = pos;
219
- } else {
220
- // in param ',' or param separator ';',
221
- // we continue from that comma
222
- pos = lastComma + 1;
223
- }
224
- } else {
225
- pos += 1;
226
- }
227
- }
228
-
229
- if (!cookiesSeparatorFound || pos >= cookiesString.length) {
230
- cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
231
- }
232
- }
233
-
234
- return cookiesStrings;
235
- }
236
-
237
- setCookie.exports = parse;
238
- setCookie.exports.parse = parse;
239
- setCookie.exports.parseString = parseString;
240
- setCookie.exports.splitCookiesString = splitCookiesString;
241
- return setCookie.exports;
242
- }
243
-
244
- var setCookieExports = /*@__PURE__*/ requireSetCookie();
245
-
246
- /**
247
- * Splits headers into two categories: single value and multi value
248
- * @param {Headers} headers
249
- * @returns {{
250
- * headers: Record<string, string>,
251
- * multiValueHeaders: Record<string, string[]>
252
- * }}
253
- */
254
- function split_headers(headers) {
255
- /** @type {Record<string, string>} */
256
- const h = {};
257
-
258
- /** @type {Record<string, string[]>} */
259
- const m = {};
260
-
261
- headers.forEach((value, key) => {
262
- if (key === 'set-cookie') {
263
- if (!m[key]) m[key] = [];
264
- m[key].push(...setCookieExports.splitCookiesString(value));
265
- } else {
266
- h[key] = value;
267
- }
268
- });
269
-
270
- return {
271
- headers: h,
272
- multiValueHeaders: m
273
- };
274
- }
275
-
276
9
  /**
277
10
  * Converts a file on disk to a readable stream
278
11
  * @param {string} file
@@ -285,7 +18,7 @@ function createReadableStream(file) {
285
18
 
286
19
  /**
287
20
  * @param {import('@sveltejs/kit').SSRManifest} manifest
288
- * @returns {import('@netlify/functions').Handler}
21
+ * @returns {(request: Request, context: import('@netlify/functions').Context) => Promise<Response>}
289
22
  */
290
23
  function init(manifest) {
291
24
  const server = new Server(manifest);
@@ -296,79 +29,19 @@ function init(manifest) {
296
29
  read: (file) => createReadableStream(`.netlify/server/${file}`)
297
30
  });
298
31
 
299
- return async (event, context) => {
32
+ return async (request, context) => {
300
33
  if (init_promise !== null) {
301
34
  await init_promise;
302
35
  init_promise = null;
303
36
  }
304
37
 
305
- const response = await server.respond(to_request(event), {
38
+ return server.respond(request, {
306
39
  platform: { context },
307
40
  getClientAddress() {
308
- return /** @type {string} */ (event.headers['x-nf-client-connection-ip']);
41
+ return context.ip;
309
42
  }
310
43
  });
311
-
312
- const partial_response = {
313
- statusCode: response.status,
314
- ...split_headers(response.headers)
315
- };
316
-
317
- if (!is_text(response.headers.get('content-type'))) {
318
- // Function responses should be strings (or undefined), and responses with binary
319
- // content should be base64 encoded and set isBase64Encoded to true.
320
- // https://github.com/netlify/functions/blob/main/src/function/response.ts
321
- return {
322
- ...partial_response,
323
- isBase64Encoded: true,
324
- body: Buffer.from(await response.arrayBuffer()).toString('base64')
325
- };
326
- }
327
-
328
- return {
329
- ...partial_response,
330
- body: await response.text()
331
- };
332
- };
333
- }
334
-
335
- /**
336
- * @param {import('@netlify/functions').HandlerEvent} event
337
- * @returns {Request}
338
- */
339
- function to_request({ httpMethod, headers, rawUrl, body, isBase64Encoded }) {
340
- /** @type {RequestInit} */
341
- const init = {
342
- method: httpMethod,
343
- headers: new Headers(/** @type {Record<string, string>} */ (headers))
344
44
  };
345
-
346
- if (httpMethod !== 'GET' && httpMethod !== 'HEAD') {
347
- const encoding = isBase64Encoded ? 'base64' : 'utf-8';
348
- init.body = typeof body === 'string' ? Buffer.from(body, encoding) : body;
349
- }
350
-
351
- return new Request(rawUrl, init);
352
- }
353
-
354
- const text_types = new Set([
355
- 'application/xml',
356
- 'application/json',
357
- 'application/x-www-form-urlencoded',
358
- 'multipart/form-data'
359
- ]);
360
-
361
- /**
362
- * Decides how the body should be parsed based on its mime type
363
- *
364
- * @param {string | undefined | null} content_type The `content-type` header of a request/response.
365
- * @returns {boolean}
366
- */
367
- function is_text(content_type) {
368
- if (!content_type) return true; // defaults to json
369
- const type = content_type.split(';')[0].toLowerCase(); // get the mime type
370
-
371
- return type.startsWith('text/') || type.endsWith('+xml') || text_types.has(type);
372
45
  }
373
46
 
374
47
  export { init };
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import { Adapter } from '@sveltejs/kit';
2
+ import './ambient.d.ts';
2
3
 
3
4
  export default function plugin(opts?: { split?: boolean; edge?: boolean }): Adapter;
package/index.js CHANGED
@@ -1,14 +1,11 @@
1
1
  /** @import { BuildOptions } from 'esbuild' */
2
2
  import { appendFileSync, existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
3
- import { dirname, join, resolve, posix } from 'node:path';
3
+ import { join, resolve, posix } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { builtinModules } from 'node:module';
6
6
  import process from 'node:process';
7
7
  import esbuild from 'esbuild';
8
8
  import toml from '@iarna/toml';
9
- import { VERSION } from '@sveltejs/kit';
10
-
11
- const [kit_major, kit_minor] = VERSION.split('.');
12
9
 
13
10
  /**
14
11
  * @typedef {{
@@ -17,17 +14,6 @@ const [kit_major, kit_minor] = VERSION.split('.');
17
14
  * } & toml.JsonMap} NetlifyConfig
18
15
  */
19
16
 
20
- /**
21
- * @template T
22
- * @template {keyof T} K
23
- * @typedef {Partial<Omit<T, K>> & Required<Pick<T, K>>} PartialExcept
24
- */
25
-
26
- /**
27
- * We use a custom `Builder` type here to support the minimum version of SvelteKit.
28
- * @typedef {PartialExcept<import('@sveltejs/kit').Builder, 'log' | 'rimraf' | 'mkdirp' | 'config' | 'prerendered' | 'routes' | 'createEntries' | 'findServerAssets' | 'generateFallback' | 'generateEnvModule' | 'generateManifest' | 'getBuildDirectory' | 'getClientDirectory' | 'getServerDirectory' | 'getAppPath' | 'writeClient' | 'writePrerendered' | 'writePrerendered' | 'writeServer' | 'copy' | 'compress'>} Builder2_4_0
29
- */
30
-
31
17
  const name = '@sveltejs/adapter-netlify';
32
18
  const files = fileURLToPath(new URL('./files', import.meta.url).href);
33
19
 
@@ -41,7 +27,7 @@ const FUNCTION_PREFIX = 'sveltekit-';
41
27
  export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
42
28
  return {
43
29
  name,
44
- /** @param {Builder2_4_0} builder */
30
+ /** @param {import('@sveltejs/kit').Builder} builder */
45
31
  async adapt(builder) {
46
32
  if (!builder.routes) {
47
33
  throw new Error(
@@ -109,23 +95,14 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
109
95
  },
110
96
 
111
97
  supports: {
112
- read: ({ route }) => {
113
- // TODO bump peer dep in next adapter major to simplify this
114
- if (edge && kit_major === '2' && kit_minor < '25') {
115
- throw new Error(
116
- `${name}: Cannot use \`read\` from \`$app/server\` in route \`${route.id}\` when using edge functions and SvelteKit < 2.25.0`
117
- );
118
- }
119
-
120
- return true;
121
- },
98
+ read: () => true,
122
99
  instrumentation: () => true
123
100
  }
124
101
  };
125
102
  }
126
103
  /**
127
104
  * @param { object } params
128
- * @param {Builder2_4_0} params.builder
105
+ * @param {import('@sveltejs/kit').Builder} params.builder
129
106
  */
130
107
  async function generate_edge_functions({ builder }) {
131
108
  const tmp = builder.getBuildDirectory('netlify-tmp');
@@ -234,25 +211,20 @@ async function generate_edge_functions({ builder }) {
234
211
  }
235
212
  /**
236
213
  * @param { object } params
237
- * @param {Builder2_4_0} params.builder
214
+ * @param {import('@sveltejs/kit').Builder} params.builder
238
215
  * @param { string } params.publish
239
216
  * @param { boolean } params.split
240
217
  */
241
218
  function generate_lambda_functions({ builder, publish, split }) {
242
219
  builder.mkdirp('.netlify/functions-internal/.svelte-kit');
243
220
 
244
- /** @type {string[]} */
245
- const redirects = [];
246
221
  builder.writeServer('.netlify/server');
247
222
 
248
223
  const replace = {
249
224
  '0SERVER': './server/index.js' // digit prefix prevents CJS build from using this as a variable name, which would also get replaced
250
225
  };
251
226
 
252
- builder.copy(files, '.netlify', { replace });
253
-
254
- // Configuring the function to use ESM as the output format.
255
- const fn_config = JSON.stringify({ config: { nodeModuleFormat: 'esm' }, version: 1 });
227
+ builder.copy(files, '.netlify', { replace, filter: (name) => !name.endsWith('edge.js') });
256
228
 
257
229
  builder.log.minor('Generating serverless functions...');
258
230
 
@@ -302,33 +274,27 @@ function generate_lambda_functions({ builder, publish, split }) {
302
274
  routes
303
275
  });
304
276
 
305
- const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;
277
+ const fn = `import { init } from '../serverless.js';\n\nexport default init(${manifest});\n\nexport const config = {\n\tpath: "${pattern}",\n\texcludedPath: "/.netlify/*",\n\tpreferStatic: true\n};\n`;
306
278
 
307
279
  writeFileSync(`.netlify/functions-internal/${name}.mjs`, fn);
308
- writeFileSync(`.netlify/functions-internal/${name}.json`, fn_config);
309
280
  if (builder.hasServerInstrumentationFile?.()) {
310
281
  builder.instrument?.({
311
282
  entrypoint: `.netlify/functions-internal/${name}.mjs`,
312
283
  instrumentation: '.netlify/server/instrumentation.server.js',
313
284
  start: `.netlify/functions-start/${name}.start.mjs`,
314
285
  module: {
315
- exports: ['handler']
286
+ exports: ['default']
316
287
  }
317
288
  });
318
289
  }
319
-
320
- const redirect = `/.netlify/functions/${name} 200`;
321
- redirects.push(`${pattern} ${redirect}`);
322
- redirects.push(`${pattern === '/' ? '' : pattern}/__data.json ${redirect}`);
323
290
  }
324
291
  } else {
325
292
  const manifest = builder.generateManifest({
326
293
  relativePath: '../server'
327
294
  });
328
295
 
329
- const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;
296
+ const fn = `import { init } from '../serverless.js';\n\nexport default init(${manifest});\n\nexport const config = {\n\tpath: "/*",\n\texcludedPath: "/.netlify/*",\n\tpreferStatic: true\n};\n`;
330
297
 
331
- writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.json`, fn_config);
332
298
  writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.mjs`, fn);
333
299
  if (builder.hasServerInstrumentationFile?.()) {
334
300
  builder.instrument?.({
@@ -336,24 +302,18 @@ function generate_lambda_functions({ builder, publish, split }) {
336
302
  instrumentation: '.netlify/server/instrumentation.server.js',
337
303
  start: `.netlify/functions-start/${FUNCTION_PREFIX}render.start.mjs`,
338
304
  module: {
339
- exports: ['handler']
305
+ exports: ['default']
340
306
  }
341
307
  });
342
308
  }
343
-
344
- redirects.push(`* /.netlify/functions/${FUNCTION_PREFIX}render 200`);
345
309
  }
346
310
 
347
- // this should happen at the end, after builder.writeClient(...),
348
- // so that generated redirects are appended to custom redirects
349
- // rather than replaced by them
350
- builder.log.minor('Writing redirects...');
351
- const redirects_file = join(publish, '_redirects');
311
+ // Copy user's custom _redirects file if it exists
352
312
  if (existsSync('_redirects')) {
313
+ builder.log.minor('Copying user redirects...');
314
+ const redirects_file = join(publish, '_redirects');
353
315
  builder.copy('_redirects', redirects_file);
354
316
  }
355
- builder.mkdirp(dirname(redirects_file));
356
- appendFileSync(redirects_file, `\n\n${redirects.join('\n')}`);
357
317
  }
358
318
 
359
319
  function get_netlify_config() {
@@ -369,7 +329,7 @@ function get_netlify_config() {
369
329
 
370
330
  /**
371
331
  * @param {NetlifyConfig | null} netlify_config
372
- * @param {Builder2_4_0} builder
332
+ * @param {import('@sveltejs/kit').Builder} builder
373
333
  **/
374
334
  function get_publish_directory(netlify_config, builder) {
375
335
  if (netlify_config) {
@@ -378,11 +338,6 @@ function get_publish_directory(netlify_config, builder) {
378
338
  return;
379
339
  }
380
340
 
381
- if (netlify_config.redirects) {
382
- throw new Error(
383
- "Redirects are not supported in netlify.toml. Use _redirects instead. For more details consult the readme's troubleshooting section."
384
- );
385
- }
386
341
  if (resolve(netlify_config.build.publish) === process.cwd()) {
387
342
  throw new Error(
388
343
  'The publish directory cannot be set to the site root. Please change it to another value such as "build" in netlify.toml.'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/adapter-netlify",
3
- "version": "5.2.4",
3
+ "version": "6.0.0",
4
4
  "description": "A SvelteKit adapter that creates a Netlify app",
5
5
  "keywords": [
6
6
  "adapter",
@@ -29,29 +29,31 @@
29
29
  "files": [
30
30
  "files",
31
31
  "index.js",
32
- "index.d.ts"
32
+ "index.d.ts",
33
+ "ambient.d.ts"
33
34
  ],
34
35
  "dependencies": {
35
36
  "@iarna/toml": "^2.2.5",
36
- "esbuild": "^0.25.4",
37
- "set-cookie-parser": "^2.6.0"
37
+ "esbuild": "^0.25.4"
38
38
  },
39
39
  "devDependencies": {
40
- "@netlify/edge-functions": "^2.15.1",
41
- "@netlify/functions": "^4.0.0",
42
- "@rollup/plugin-commonjs": "^28.0.1",
40
+ "@netlify/dev": "^4.8.8",
41
+ "@netlify/edge-functions": "^3.0.0",
42
+ "@netlify/functions": "^5.0.0",
43
+ "@netlify/node-cookies": "^0.1.0",
44
+ "@netlify/types": "^2.1.0",
45
+ "@rollup/plugin-commonjs": "^29.0.0",
43
46
  "@rollup/plugin-json": "^6.1.0",
44
47
  "@rollup/plugin-node-resolve": "^16.0.0",
45
48
  "@sveltejs/vite-plugin-svelte": "^6.0.0-next.3",
46
49
  "@types/node": "^18.19.119",
47
- "@types/set-cookie-parser": "^2.4.7",
48
50
  "rollup": "^4.14.2",
49
51
  "typescript": "^5.3.3",
50
- "vitest": "^3.2.4",
51
- "@sveltejs/kit": "^2.43.7"
52
+ "vitest": "^4.0.0",
53
+ "@sveltejs/kit": "^2.51.0"
52
54
  },
53
55
  "peerDependencies": {
54
- "@sveltejs/kit": "^2.4.0"
56
+ "@sveltejs/kit": "^2.31.0"
55
57
  },
56
58
  "scripts": {
57
59
  "dev": "rollup -cw",
@@ -59,7 +61,7 @@
59
61
  "check": "tsc",
60
62
  "lint": "prettier --check .",
61
63
  "format": "pnpm lint --write",
62
- "test": "pnpm test:unit && pnpm test:integration",
64
+ "test": "pnpm test:integration",
63
65
  "test:unit": "vitest run",
64
66
  "test:integration": "pnpm build && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test"
65
67
  }