Repository: sveltejs/kit
Pull request #8966 (2023-02-09 08:03:12)

fix: string concatenation
Realized a bug in #8953 two seconds after merging it. No changeset assuming it still gets in the same patch release as #8953.

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [ ] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [ ] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [ ] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. Changesets that add features should be `minor` and those that fix bugs should be `patch`. Please prefix changeset messages with `feat:`, `fix:`, or `chore:`.



Patch:
diff --git a/.changeset/dirty-days-fix.md b/.changeset/dirty-days-fix.md
new file mode 100644
index 000000000..75c205093
--- /dev/null
+++ b/.changeset/dirty-days-fix.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: correctly include exported http methods in allow header
diff --git a/.changeset/famous-hornets-invite.md b/.changeset/famous-hornets-invite.md
new file mode 100644
index 000000000..7dec00bed
--- /dev/null
+++ b/.changeset/famous-hornets-invite.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+chore: polyfill File from node:buffer
diff --git a/.changeset/good-carrots-flow.md b/.changeset/good-carrots-flow.md
new file mode 100644
index 000000000..27e047633
--- /dev/null
+++ b/.changeset/good-carrots-flow.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-node': patch
+---
+
+fix: use `strictRequires: true` when bundling output
diff --git a/.changeset/large-toes-fetch.md b/.changeset/large-toes-fetch.md
new file mode 100644
index 000000000..f9695953c
--- /dev/null
+++ b/.changeset/large-toes-fetch.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: print useful error when subscribing to SvelteKit's stores at the wrong time during SSR
diff --git a/.changeset/rich-chefs-refuse.md b/.changeset/rich-chefs-refuse.md
new file mode 100644
index 000000000..7a27232ab
--- /dev/null
+++ b/.changeset/rich-chefs-refuse.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-netlify': patch
+---
+
+fix: correctly compare routes when generating split functions
diff --git a/.changeset/six-poems-exist.md b/.changeset/six-poems-exist.md
new file mode 100644
index 000000000..612f7e646
--- /dev/null
+++ b/.changeset/six-poems-exist.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: ignore external links when automatically preloading
diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js
index 2d2f91a98..74d21f5df 100644
--- a/packages/adapter-netlify/index.js
+++ b/packages/adapter-netlify/index.js
@@ -205,7 +205,7 @@ async function generate_lambda_functions({ builder, publish, split }) {
 
 			const pattern = `/${parts.join('/')}`;
 			const name =
-				FUNCTION_PREFIX + parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index';
+				FUNCTION_PREFIX + (parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index');
 
 			// skip routes with identical patterns, they were already folded into another function
 			if (seen.has(pattern)) continue;
@@ -213,10 +213,11 @@ async function generate_lambda_functions({ builder, publish, split }) {
 
 			// figure out which lower priority routes should be considered fallbacks
 			for (let j = i + 1; j < builder.routes.length; j += 1) {
-				if (routes[j].prerender === true) continue;
+				const other = builder.routes[j];
+				if (other.prerender === true) continue;
 
-				if (matches(route.segments, routes[j].segments)) {
-					routes.push(builder.routes[j]);
+				if (matches(route.segments, other.segments)) {
+					routes.push(other);
 				}
 			}
 
diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js
index ac16008bc..85371fd97 100644
--- a/packages/adapter-node/index.js
+++ b/packages/adapter-node/index.js
@@ -57,7 +57,7 @@ export default function (opts = {}) {
 					// dependencies could have deep exports, so we need a regex
 					...Object.keys(pkg.dependencies || {}).map((d) => new RegExp(`^${d}(\\/.*)?$`))
 				],
-				plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()]
+				plugins: [nodeResolve({ preferBuiltins: true }), commonjs({ strictRequires: true }), json()]
 			});
 
 			await bundle.write({
diff --git a/packages/kit/src/exports/node/polyfills.js b/packages/kit/src/exports/node/polyfills.js
index 28f9de5f9..950c64525 100644
--- a/packages/kit/src/exports/node/polyfills.js
+++ b/packages/kit/src/exports/node/polyfills.js
@@ -1,6 +1,10 @@
 import { ReadableStream, TransformStream, WritableStream } from 'node:stream/web';
+import buffer from 'node:buffer';
 import { webcrypto as crypto } from 'node:crypto';
-import { fetch, Response, Request, Headers, FormData } from 'undici';
+import { fetch, Response, Request, Headers, FormData, File as UndiciFile } from 'undici';
+
+// @ts-expect-error
+const File = buffer.File ?? UndiciFile;
 
 /** @type {Record<string, any>} */
 const globals = {
@@ -12,7 +16,8 @@ const globals = {
 	ReadableStream,
 	TransformStream,
 	WritableStream,
-	FormData
+	FormData,
+	File
 };
 
 // exported for dev/preview and node environments
diff --git a/packages/kit/src/runtime/app/stores.js b/packages/kit/src/runtime/app/stores.js
index 5c79a7ad3..67727a119 100644
--- a/packages/kit/src/runtime/app/stores.js
+++ b/packages/kit/src/runtime/app/stores.js
@@ -23,7 +23,7 @@ export const getStores = () => {
 export const page = {
 	/** @param {(value: any) => void} fn */
 	subscribe(fn) {
-		const store = getStores().page;
+		const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page;
 		return store.subscribe(fn);
 	}
 };
@@ -31,7 +31,7 @@ export const page = {
 /** @type {typeof import('$app/stores').navigating} */
 export const navigating = {
 	subscribe(fn) {
-		const store = getStores().navigating;
+		const store = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating;
 		return store.subscribe(fn);
 	}
 };
@@ -39,7 +39,7 @@ export const navigating = {
 /** @type {typeof import('$app/stores').updated} */
 export const updated = {
 	subscribe(fn) {
-		const store = getStores().updated;
+		const store = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated;
 
 		if (browser) {
 			updated.check = store.check;
@@ -55,3 +55,18 @@ export const updated = {
 		);
 	}
 };
+
+/**
+ * @template {keyof ReturnType<typeof getStores>} Name
+ * @param {Name} name
+ * @returns {ReturnType<typeof getStores>[Name]}
+ */
+function get_store(name) {
+	try {
+		return getStores()[name];
+	} catch (e) {
+		throw new Error(
+			`Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.`
+		);
+	}
+}
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index f5c53e7dd..bcf81c2b7 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -221,14 +221,8 @@ export function create_client({ target }) {
 		});
 	}
 
-	/** @param {URL} url */
-	async function preload_data(url) {
-		const intent = get_navigation_intent(url, false);
-
-		if (!intent) {
-			throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
-		}
-
+	/** @param {import('./types').NavigationIntent} intent */
+	async function preload_data(intent) {
 		load_cache = {
 			id: intent.id,
 			promise: load_route(intent).then((result) => {
@@ -1261,7 +1255,8 @@ export function create_client({ target }) {
 
 			if (!options.reload) {
 				if (priority <= options.preload_data) {
-					preload_data(/** @type {URL} */ (url));
+					const intent = get_navigation_intent(/** @type {URL} */ (url), false);
+					if (intent) preload_data(intent);
 				} else if (priority <= options.preload_code) {
 					preload_code(get_url_path(/** @type {URL} */ (url)));
 				}
@@ -1347,7 +1342,13 @@ export function create_client({ target }) {
 
 		preload_data: async (href) => {
 			const url = new URL(href, get_base_uri(document));
-			await preload_data(url);
+			const intent = get_navigation_intent(url, false);
+
+			if (!intent) {
+				throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
+			}
+
+			await preload_data(intent);
 		},
 
 		preload_code,
diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js
index ed8c4803d..68dad34d3 100644
--- a/packages/kit/src/runtime/server/utils.js
+++ b/packages/kit/src/runtime/server/utils.js
@@ -41,7 +41,7 @@ export function method_not_allowed(mod, method) {
 export function allowed_methods(mod) {
 	const allowed = [];
 
-	for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
+	for (const method of ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
 		if (method in mod) allowed.push(method);
 	}
 
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index 606475b4c..5573b72ce 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -292,16 +292,22 @@ declare module '$app/stores' {
 
 	/**
 	 * A readable store whose value contains page data.
+	 *
+	 * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
 	 */
 	export const page: Readable<Page>;
 	/**
 	 * A readable store.
 	 * When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
 	 * When navigating finishes, its value reverts to `null`.
+	 *
+	 * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
 	 */
 	export const navigating: Readable<Navigation | null>;
 	/**
-	 *  A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
+	 * A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
+	 *
+	 * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
 	 */
 	export const updated: Readable<boolean> & { check(): Promise<boolean> };
 


Test patch:
diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js
index cad3f9f88..f2e09a28e 100644
--- a/packages/kit/test/apps/basics/test/server.test.js
+++ b/packages/kit/test/apps/basics/test/server.test.js
@@ -108,6 +108,13 @@ test.describe('Endpoints', () => {
 		});
 	});
 
+	test('invalid request method returns allow header', async ({ request }) => {
+		const response = await request.post('/endpoint-output/body');
+
+		expect(response.status()).toBe(405);
+		expect(response.headers()['allow'].includes('GET'));
+	});
+
 	// TODO all the remaining tests in this section are really only testing
 	// setResponse, since we're not otherwise changing anything on the response.
 	// might be worth making these unit tests instead