Repository: sveltejs/kit (js)

Issue:
load parent function doesn't infer LayoutData from named layout
### Describe the bug

LayoutLoad interface doesn't include named layout's LayoutData in `parent()` function

### Reproduction

With this files structure
![image](https://user-images.githubusercontent.com/53383860/185288017-0c93e3fb-00fe-456d-b1be-61be36fa01dc.png)

and this named layout `layout-customer.ts`
![image](https://user-images.githubusercontent.com/53383860/185288465-00f64e5f-b8be-4ede-830b-1c47677d32c2.png)

I expect `layout.ts` to get LayoutData from layout-customer but it doesn't
![image](https://user-images.githubusercontent.com/53383860/185288257-95844267-30ee-408e-871e-95bee4f5f8d9.png)




### Logs

_No response_

### System Info

```shell
System:
    OS: Windows 10 10.0.22000
    CPU: (16) x64 AMD Ryzen 9 4900H with Radeon Graphics
    Memory: 7.55 GB / 31.42 GB
  Binaries:
    Node: 18.7.0 - C:\Program Files\nodejs\node.EXE
    npm: 8.15.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.22000.120.0), Chromium (104.0.1293.54)
    Internet Explorer: 11.0.22000.120
  npmPackages:
    @sveltejs/adapter-node: 1.0.0-next.86 => 1.0.0-next.86 
    @sveltejs/kit: 1.0.0-next.418 => 1.0.0-next.418
    svelte: ^3.49.0 => 3.49.0
```


### Severity

annoyance

### Additional Information

To fix the value `Record<never, never>` in the related `./$types`
![image](https://user-images.githubusercontent.com/53383860/185289162-c5a8dddb-2574-496b-bbc4-87e8a19b98a2.png)

should become `LayoutData.customer`
![image](https://user-images.githubusercontent.com/53383860/185289099-9114e8c2-6336-4e37-9ec4-ee801367cda0.png)


Pull request:
[fix] correct parent data type for layouts
Was wrong if a layout referenced a named layout in the same folder
Fixes #6013

### 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.
- [x] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [x] 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. All changesets should be `patch` until SvelteKit 1.0


Patch:
diff --git a/.changeset/fresh-pigs-tease.md b/.changeset/fresh-pigs-tease.md
new file mode 100644
index 000000000..9af327627
--- /dev/null
+++ b/.changeset/fresh-pigs-tease.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[breaking] remove ability for `+page.server.js` to respond to `GET` requests with JSON
diff --git a/.changeset/honest-parrots-compare.md b/.changeset/honest-parrots-compare.md
new file mode 100644
index 000000000..4c8442e71
--- /dev/null
+++ b/.changeset/honest-parrots-compare.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Handle `throw error/redirect` in `+server.js`
diff --git a/.changeset/lemon-kids-double.md b/.changeset/lemon-kids-double.md
new file mode 100644
index 000000000..e98b9d4d2
--- /dev/null
+++ b/.changeset/lemon-kids-double.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+handle `set-cookie` in `setHeaders`
diff --git a/.changeset/mighty-students-develop.md b/.changeset/mighty-students-develop.md
new file mode 100644
index 000000000..cbffb0dea
--- /dev/null
+++ b/.changeset/mighty-students-develop.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Handle windows paths and regexp chars in kit.alias
diff --git a/.changeset/ninety-fans-cover.md b/.changeset/ninety-fans-cover.md
new file mode 100644
index 000000000..aba7c9ea3
--- /dev/null
+++ b/.changeset/ninety-fans-cover.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Make errors type in Action type less restrictive
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 58258eebe..ea7fe6e55 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -256,6 +256,7 @@
     "few-buses-argue",
     "few-carrots-sell",
     "few-cobras-switch",
+    "few-hornets-joke",
     "few-points-taste",
     "few-rockets-sort",
     "few-rockets-think",
@@ -335,6 +336,7 @@
     "gold-carrots-sniff",
     "gold-colts-train",
     "gold-moles-hunt",
+    "gold-rocks-hang",
     "gold-spies-fail",
     "gold-trains-dream",
     "good-beans-invite",
diff --git a/.changeset/selfish-tips-divide.md b/.changeset/selfish-tips-divide.md
new file mode 100644
index 000000000..ac7e76767
--- /dev/null
+++ b/.changeset/selfish-tips-divide.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': patch
+---
+
+Run svelte-kit sync before svelte-check in check scripts
diff --git a/.changeset/wise-berries-pay.md b/.changeset/wise-berries-pay.md
new file mode 100644
index 000000000..457972dc1
--- /dev/null
+++ b/.changeset/wise-berries-pay.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Fix parent data type for layouts referencing named layouts in the same folder
diff --git a/documentation/docs/05-load.md b/documentation/docs/05-load.md
index 8631e2139..2f956a7cf 100644
--- a/documentation/docs/05-load.md
+++ b/documentation/docs/05-load.md
@@ -188,6 +188,28 @@ export async function load({ parent }) {
 
 In `+page.js` or `+layout.js` it will return data from `load` functions in parent `+layout.js` files. Implicitly, a missing `+layout.js` is treated as a `({ data }) => data` function, meaning that it will also return data from parent `+layout.server.js` files.
 
+Be careful not to introduce accidental waterfalls when using `await parent()`. If for example you only want to merge parent data into the returned output, call it _after_ fetching your other data.
+
+```diff
+/// file: src/routes/foo/+page.server.js
+// @filename: $types.d.ts
+export type PageLoad = import('@sveltejs/kit').Load<{}, null, { a: number, b: number }>;
+
+// @filename: index.js
+// ---cut---
+/** @type {import('./$types').PageLoad} */
+export async function load({ parent, fetch }) {
+-	const parentData = await parent();
+	const data = await fetch('./some-api');
++	const parentData = await parent();
+
+	return {
+		...data
+		meta: { ...parentData.meta, ...data.meta }
+	};
+}
+```
+
 #### setHeaders
 
 If you need to set headers for the response, you can do so using the `setHeaders` method. This is useful if you want the page to be cached, for example:
diff --git a/documentation/docs/17-seo.md b/documentation/docs/17-seo.md
index afd79d00f..64208d7c4 100644
--- a/documentation/docs/17-seo.md
+++ b/documentation/docs/17-seo.md
@@ -26,7 +26,7 @@ SvelteKit redirects pathnames with trailing slashes to ones without (or vice ver
 
 Every page should have well-written and unique `<title>` and `<meta name="description">` elements inside a [`<svelte:head>`](https://svelte.dev/docs#template-syntax-svelte-head). Guidance on how to write descriptive titles and descriptions, along with other suggestions on making content understandable by search engines, can be found on Google's [Lighthouse SEO audits](https://web.dev/lighthouse-seo/) documentation.
 
-> A common pattern is to return SEO-related [`stuff`](/docs/load#output-stuff) from page `load` functions, then use it (as [`$page.stuff`](/docs/modules#$app-stores)) in a `<svelte:head>` in your root [layout](/docs/routing#layout).
+> A common pattern is to return SEO-related `data` from page [`load`](/docs/load) functions, then use it (as [`$page.data`](/docs/modules#$app-stores)) in a `<svelte:head>` in your root [layout](/docs/routing#layout).
 
 #### Structured data
 
@@ -59,7 +59,8 @@ export default config;
 ```js
 /// file: src/routes/sitemap.xml/+server.js
 export async function GET() {
-	return new Response(`
+	return new Response(
+		`
 		<?xml version="1.0" encoding="UTF-8" ?>
 		<urlset
 			xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
diff --git a/packages/create-svelte/CHANGELOG.md b/packages/create-svelte/CHANGELOG.md
index dbdb840a5..f03c4fa56 100644
--- a/packages/create-svelte/CHANGELOG.md
+++ b/packages/create-svelte/CHANGELOG.md
@@ -1,5 +1,11 @@
 # create-svelte
 
+## 2.0.0-next.156
+
+### Patch Changes
+
+- update `app.d.ts` for library skeleton template ([#6020](https://github.com/sveltejs/kit/pull/6020))
+
 ## 2.0.0-next.155
 
 ### Patch Changes
diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json
index ca8f89dce..56e941bcc 100644
--- a/packages/create-svelte/package.json
+++ b/packages/create-svelte/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "create-svelte",
-	"version": "2.0.0-next.155",
+	"version": "2.0.0-next.156",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/create-svelte/shared/+typescript/package.json b/packages/create-svelte/shared/+typescript/package.json
index 465f674b8..7b5612f97 100644
--- a/packages/create-svelte/shared/+typescript/package.json
+++ b/packages/create-svelte/shared/+typescript/package.json
@@ -1,7 +1,7 @@
 {
 	"scripts": {
-		"check": "svelte-check --tsconfig ./tsconfig.json",
-		"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch"
+		"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
 	},
 	"devDependencies": {
 		"typescript": "^4.7.4",
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 7b859cf14..fdef39d3d 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/kit
 
+## 1.0.0-next.421
+
+### Patch Changes
+
+- Respect `export const prerender = false` in `+page.server.js` ([#6012](https://github.com/sveltejs/kit/pull/6012))
+
 ## 1.0.0-next.420
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 664069a66..3645e8c6a 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.420",
+	"version": "1.0.0-next.421",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/src/core/sync/write_types.js b/packages/kit/src/core/sync/write_types.js
index 34ec6f33e..c5cfe1ac5 100644
--- a/packages/kit/src/core/sync/write_types.js
+++ b/packages/kit/src/core/sync/write_types.js
@@ -139,12 +139,23 @@ function get_groups(manifest_data, routes_dir) {
 		return group;
 	}
 
-	// first, sort nodes by path length (necessary for finding the nearest layout more efficiently)...
-	const nodes = [...manifest_data.nodes].sort(
-		(n1, n2) =>
+	// first, sort nodes (necessary for finding the nearest layout more efficiently)...
+	const nodes = [...manifest_data.nodes].sort((n1, n2) => {
+		// Sort by path length first...
+		const path_length_diff =
 			/** @type {string} */ (n1.component ?? n1.shared ?? n1.server).split('/').length -
-			/** @type {string} */ (n2.component ?? n2.shared ?? n2.server).split('/').length
-	);
+			/** @type {string} */ (n2.component ?? n2.shared ?? n2.server).split('/').length;
+
+		return (
+			path_length_diff ||
+			// ...on ties, sort named layouts first
+			(path.basename(n1.component || '').includes('-')
+				? -1
+				: path.basename(n2.component || '').includes('-')
+				? 1
+				: 0)
+		);
+	});
 
 	// ...then, populate `directories` with +page/+layout files...
 	for (let i = 0; i < nodes.length; i += 1) {
@@ -713,12 +724,12 @@ export function find_nearest_layout(routes_dir, nodes, start_idx) {
 	}
 
 	let common_path = path.dirname(start_file);
-	if (match[1] === 'layout' && !name) {
+	if (match[1] === 'layout' && !match[2] && !name) {
 		// We are a default layout, so we skip the current level
 		common_path = path.dirname(common_path);
 	}
 
-	for (let i = start_idx; i >= 0; i -= 1) {
+	for (let i = start_idx - 1; i >= 0; i -= 1) {
 		const node = nodes[i];
 		const file = /** @type {string} */ (node.component || node.shared || node.server);
 
diff --git a/packages/kit/src/core/sync/write_types.spec.js b/packages/kit/src/core/sync/write_types.spec.js
index 2191e7421..4f0a85d14 100644
--- a/packages/kit/src/core/sync/write_types.spec.js
+++ b/packages/kit/src/core/sync/write_types.spec.js
@@ -160,6 +160,14 @@ test('Finds nearest layout (named)', () => {
 	});
 });
 
+test('Finds nearest named layout from layout', () => {
+	assert.equal(find_nearest_layout('src/routes', nodes, 1), {
+		key: '',
+		folder_depth_diff: 0,
+		name: ''
+	});
+});
+
 test('Finds nearest layout (recursively named)', () => {
 	assert.equal(find_nearest_layout('src/routes', nodes, 3), {
 		key: '',
diff --git a/packages/kit/src/runtime/server/endpoint.js b/packages/kit/src/runtime/server/endpoint.js
index 338791b4d..9d112214a 100644
--- a/packages/kit/src/runtime/server/endpoint.js
+++ b/packages/kit/src/runtime/server/endpoint.js
@@ -1,3 +1,4 @@
+import { HttpError, Redirect } from '../../index/private.js';
 import { check_method_names, method_not_allowed } from './utils.js';
 
 /**
@@ -29,16 +30,29 @@ export async function render_endpoint(event, route) {
 		return method_not_allowed(mod, method);
 	}
 
-	const response = await handler(
-		/** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
-	);
-
-	if (!(response instanceof Response)) {
-		return new Response(
-			`Invalid response from route ${event.url.pathname}: handler should return a Response object`,
-			{ status: 500 }
+	try {
+		const response = await handler(
+			/** @type {import('types').RequestEvent<Record<string, any>>} */ (event)
 		);
-	}
 
-	return response;
+		if (!(response instanceof Response)) {
+			return new Response(
+				`Invalid response from route ${event.url.pathname}: handler should return a Response object`,
+				{ status: 500 }
+			);
+		}
+
+		return response;
+	} catch (error) {
+		if (error instanceof HttpError) {
+			return new Response(error.message, { status: error.status });
+		} else if (error instanceof Redirect) {
+			return new Response(undefined, {
+				status: error.status,
+				headers: { Location: error.location }
+			});
+		} else {
+			throw error;
+		}
+	}
 }
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index 625fc0539..61d4033a8 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -118,6 +118,9 @@ export async function respond(request, options, state) {
 	/** @type {import('types').ResponseHeaders} */
 	const headers = {};
 
+	/** @type {string[]} */
+	const cookies = [];
+
 	/** @type {import('types').RequestEvent} */
 	const event = {
 		get clientAddress() {
@@ -141,16 +144,26 @@ export async function respond(request, options, state) {
 		setHeaders: (new_headers) => {
 			for (const key in new_headers) {
 				const lower = key.toLowerCase();
+				const value = new_headers[key];
 
-				if (lower in headers) {
-					throw new Error(`"${key}" header is already set`);
-				}
+				if (lower === 'set-cookie') {
+					const new_cookies = /** @type {string[]} */ (Array.isArray(value) ? value : [value]);
 
-				// TODO apply these headers to the response
-				headers[lower] = new_headers[key];
+					for (const cookie of new_cookies) {
+						if (cookies.includes(cookie)) {
+							throw new Error(`"${key}" header already has cookie with same value`);
+						}
 
-				if (state.prerendering && lower === 'cache-control') {
-					state.prerendering.cache = /** @type {string} */ (new_headers[key]);
+						cookies.push(cookie);
+					}
+				} else if (lower in headers) {
+					throw new Error(`"${key}" header is already set`);
+				} else {
+					headers[lower] = value;
+
+					if (state.prerendering && lower === 'cache-control') {
+						state.prerendering.cache = /** @type {string} */ (value);
+					}
 				}
 			}
 		},
@@ -312,19 +325,19 @@ export async function respond(request, options, state) {
 								: await render_page(event, route, options, state, resolve_opts);
 					}
 
-					for (const key in headers) {
-						const value = headers[key];
-						if (key === 'set-cookie') {
-							for (const cookie of Array.isArray(value) ? value : [value]) {
-								response.headers.append(key, /** @type {string} */ (cookie));
-							}
-						} else if (!is_data_request) {
-							// we only want to set cookies on __data.json requests, we don't
-							// want to cache stuff erroneously etc
+					if (!is_data_request) {
+						// we only want to set cookies on __data.json requests, we don't
+						// want to cache stuff erroneously etc
+						for (const key in headers) {
+							const value = headers[key];
 							response.headers.set(key, /** @type {string} */ (value));
 						}
 					}
 
+					for (const cookie of cookies) {
+						response.headers.append('set-cookie', cookie);
+					}
+
 					// respond with 304 if etag matches
 					if (response.status === 200 && response.headers.has('etag')) {
 						let if_none_match_value = request.headers.get('if-none-match');
diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js
index edaa42016..91f0a6470 100644
--- a/packages/kit/src/runtime/server/page/index.js
+++ b/packages/kit/src/runtime/server/page/index.js
@@ -36,7 +36,11 @@ export async function render_page(event, route, options, state, resolve_opts) {
 		'application/json'
 	]);
 
-	if (accept === 'application/json') {
+	if (
+		accept === 'application/json' &&
+		event.request.method !== 'GET' &&
+		event.request.method !== 'HEAD'
+	) {
 		const node = await options.manifest._.nodes[route.leaf]();
 		if (node.server) {
 			return handle_json_request(event, options, node.server);
@@ -346,8 +350,8 @@ function get_page_config(leaf, options) {
  * @param {import('types').SSRNode['server']} mod
  */
 export async function handle_json_request(event, options, mod) {
-	const method = /** @type {import('types').HttpMethod} */ (event.request.method);
-	const handler = mod[method === 'HEAD' || method === 'GET' ? 'load' : method];
+	const method = /** @type {'POST' | 'PUT' | 'PATCH' | 'DELETE'} */ (event.request.method);
+	const handler = mod[method];
 
 	if (!handler) {
 		return method_not_allowed(mod, method);
@@ -357,14 +361,6 @@ export async function handle_json_request(event, options, mod) {
 		// @ts-ignore
 		const result = await handler.call(null, event);
 
-		if (method === 'HEAD') {
-			return new Response();
-		}
-
-		if (method === 'GET') {
-			return json(result);
-		}
-
 		if (result?.errors) {
 			// @ts-ignore
 			return json({ errors: result.errors }, { status: result.status || 400 });
diff --git a/packages/kit/src/vite/utils.js b/packages/kit/src/vite/utils.js
index 6c62d2636..4e9ae7543 100644
--- a/packages/kit/src/vite/utils.js
+++ b/packages/kit/src/vite/utils.js
@@ -2,6 +2,7 @@ import fs from 'fs';
 import path from 'path';
 import { loadConfigFromFile, loadEnv, normalizePath } from 'vite';
 import { runtime_directory } from '../core/utils.js';
+import { posixify } from '../utils/filesystem.js';
 
 /**
  * @param {import('vite').ResolvedConfig} config
@@ -113,18 +114,22 @@ export function get_aliases(config) {
 	];
 
 	for (let [key, value] of Object.entries(config.alias)) {
+		value = posixify(value);
 		if (value.endsWith('/*')) {
 			value = value.slice(0, -2);
 		}
 		if (key.endsWith('/*')) {
 			// Doing just `{ find: key.slice(0, -2) ,..}` would mean `import .. from "key"` would also be matched, which we don't want
 			alias.push({
-				find: new RegExp(`^${key.slice(0, -2)}\\/(.+)$`),
+				find: new RegExp(`^${escape_for_regexp(key.slice(0, -2))}\\/(.+)$`),
 				replacement: `${path.resolve(value)}/$1`
 			});
 		} else if (key + '/*' in config.alias) {
 			// key and key/* both exist -> the replacement for key needs to happen _only_ on import .. from "key"
-			alias.push({ find: new RegExp(`^${key}$`), replacement: path.resolve(value) });
+			alias.push({
+				find: new RegExp(`^${escape_for_regexp(key)}$`),
+				replacement: path.resolve(value)
+			});
 		} else {
 			alias.push({ find: key, replacement: path.resolve(value) });
 		}
@@ -133,6 +138,13 @@ export function get_aliases(config) {
 	return alias;
 }
 
+/**
+ * @param {string} str
+ */
+function escape_for_regexp(str) {
+	return str.replace(/[.*+?^${}()|[\]\\]/g, (match) => '\\' + match);
+}
+
 /**
  * Given an entry point like [cwd]/src/hooks, returns a filename like [cwd]/src/hooks.js or [cwd]/src/hooks/index.js
  * @param {string} entry
diff --git a/packages/kit/src/vite/utils.spec.js b/packages/kit/src/vite/utils.spec.js
index a566434f5..6fa338f64 100644
--- a/packages/kit/src/vite/utils.spec.js
+++ b/packages/kit/src/vite/utils.spec.js
@@ -207,7 +207,15 @@ test('merge resolve.alias', () => {
 
 test('transform kit.alias to resolve.alias', () => {
 	const config = validate_config({
-		kit: { alias: { simpleKey: 'simple/value', key: 'value', 'key/*': 'value/*' } }
+		kit: {
+			alias: {
+				simpleKey: 'simple/value',
+				key: 'value',
+				'key/*': 'value/*',
+				$regexChar: 'windows\\path',
+				'$regexChar/*': 'windows\\path\\*'
+			}
+		}
 	});
 
 	const transformed = get_aliases(config.kit).map((entry) => {
@@ -227,7 +235,9 @@ test('transform kit.alias to resolve.alias', () => {
 		{ find: '$lib', replacement: 'src/lib' },
 		{ find: 'simpleKey', replacement: 'simple/value' },
 		{ find: /^key$/.toString(), replacement: 'value' },
-		{ find: /^key\/(.+)$/.toString(), replacement: 'value/$1' }
+		{ find: /^key\/(.+)$/.toString(), replacement: 'value/$1' },
+		{ find: /^\$regexChar$/.toString(), replacement: 'windows/path' },
+		{ find: /^\$regexChar\/(.+)$/.toString(), replacement: 'windows/path/$1' }
 	]);
 });
 
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index 44b15f463..294330e19 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -312,7 +312,7 @@ export interface Action<
 	Params extends Partial<Record<string, string>> = Partial<Record<string, string>>
 > {
 	(event: RequestEvent<Params>): MaybePromise<
-		| { status?: number; errors: Record<string, string>; location?: never }
+		| { status?: number; errors: Record<string, any>; location?: never }
 		| { status?: never; errors?: never; location: string }
 		| void
 	>;


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-error/+server.js b/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-error/+server.js
new file mode 100644
index 000000000..14c9aec1c
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-error/+server.js
@@ -0,0 +1,5 @@
+import { error } from '@sveltejs/kit';
+
+export function GET() {
+	throw error(401, 'You shall not pass');
+}
diff --git a/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-redirect/+server.js b/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-redirect/+server.js
new file mode 100644
index 000000000..26300742e
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/errors/endpoint-throw-redirect/+server.js
@@ -0,0 +1,5 @@
+import { redirect } from '@sveltejs/kit';
+
+export function GET() {
+	throw redirect(302, '/');
+}
diff --git a/packages/kit/test/apps/basics/src/routes/headers/set-cookie/+layout.server.js b/packages/kit/test/apps/basics/src/routes/headers/set-cookie/+layout.server.js
new file mode 100644
index 000000000..21fd24121
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/headers/set-cookie/+layout.server.js
@@ -0,0 +1,5 @@
+export function load({ setHeaders }) {
+	setHeaders({
+		'set-cookie': 'cookie1=value1'
+	});
+}
diff --git a/packages/kit/test/apps/basics/src/routes/headers/set-cookie/+layout.svelte b/packages/kit/test/apps/basics/src/routes/headers/set-cookie/+layout.svelte
new file mode 100644
index 000000000..0385342ce
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/headers/set-cookie/+layout.svelte
@@ -0,0 +1,1 @@
+<slot />
\ No newline at end of file
diff --git a/packages/kit/test/apps/basics/src/routes/headers/set-cookie/sub/+page.server.js b/packages/kit/test/apps/basics/src/routes/headers/set-cookie/sub/+page.server.js
new file mode 100644
index 000000000..1d7acff07
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/headers/set-cookie/sub/+page.server.js
@@ -0,0 +1,5 @@
+export function load({ setHeaders }) {
+	setHeaders({
+		'set-cookie': 'cookie2=value2'
+	});
+}
diff --git a/packages/kit/test/apps/basics/src/routes/headers/set-cookie/sub/+page.svelte b/packages/kit/test/apps/basics/src/routes/headers/set-cookie/sub/+page.svelte
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js
index 45615bcb5..1cafac3ba 100644
--- a/packages/kit/test/apps/basics/test/server.test.js
+++ b/packages/kit/test/apps/basics/test/server.test.js
@@ -124,40 +124,6 @@ test.describe('Errors', () => {
 		expect(await response.text()).toMatch('thisvariableisnotdefined is not defined');
 	});
 
-	test('page endpoint thrown error respects `accept: application/json`', async ({ request }) => {
-		const response = await request.get('/errors/page-endpoint/get-implicit', {
-			headers: {
-				accept: 'application/json'
-			}
-		});
-
-		const { message, name, stack, fancy } = await response.json();
-
-		expect(response.status()).toBe(500);
-		expect(name).toBe('FancyError');
-		expect(message).toBe('oops');
-		expect(fancy).toBe(true);
-
-		if (process.env.DEV) {
-			expect(stack.split('\n').length).toBeGreaterThan(1);
-		} else {
-			expect(stack.split('\n').length).toBe(1);
-		}
-	});
-
-	test('page endpoint returned error respects `accept: application/json`', async ({ request }) => {
-		const response = await request.get('/errors/page-endpoint/get-explicit', {
-			headers: {
-				accept: 'application/json'
-			}
-		});
-
-		const { message } = await response.json();
-
-		expect(response.status()).toBe(400);
-		expect(message).toBe('oops');
-	});
-
 	test('returns 400 when accessing a malformed URI', async ({ page }) => {
 		const response = await page.goto('/%c0%ae%c0%ae/etc/passwd');
 		if (process.env.DEV) {
@@ -181,6 +147,26 @@ test.describe('Errors', () => {
 			'This is your custom error page saying: "Cannot read properties of undefined (reading \'toUpperCase\')"'
 		);
 	});
+
+	test('throw error(..) in endpoint', async ({ page, read_errors }) => {
+		const res = await page.goto('/errors/endpoint-throw-error');
+
+		const error = read_errors('/errors/endpoint-throw-error');
+		expect(error).toBe(undefined);
+
+		expect(await res?.text()).toBe('You shall not pass');
+		expect(res?.status()).toBe(401);
+	});
+
+	test('throw redirect(..) in endpoint', async ({ page, read_errors }) => {
+		const res = await page.goto('/errors/endpoint-throw-redirect');
+		expect(res?.status()).toBe(200); // redirects are opaque to the browser
+
+		const error = read_errors('/errors/endpoint-throw-redirect');
+		expect(error).toBe(undefined);
+
+		expect(await page.textContent('h1')).toBe('the answer is 42');
+	});
 });
 
 test.describe('Load', () => {
@@ -211,53 +197,6 @@ test.describe('Routing', () => {
 });
 
 test.describe('Shadowed pages', () => {
-	test('responds to HEAD requests from endpoint', async ({ request }) => {
-		const url = '/shadowed/simple';
-
-		const opts = {
-			headers: {
-				accept: 'application/json'
-			}
-		};
-
-		const responses = {
-			head: await request.head(url, opts),
-			get: await request.get(url, opts)
-		};
-
-		const headers = {
-			head: responses.head.headers(),
-			get: responses.get.headers()
-		};
-
-		expect(responses.head.status()).toBe(200);
-		expect(responses.get.status()).toBe(200);
-		expect(await responses.head.text()).toBe('');
-		expect(await responses.get.json()).toEqual({ answer: 42 });
-
-		['date', 'transfer-encoding'].forEach((name) => {
-			delete headers.head[name];
-			delete headers.get[name];
-		});
-
-		expect(headers.get).toEqual({
-			...headers.head,
-			'content-type': 'application/json'
-		});
-	});
-
-	test('Responds from endpoint if Accept includes application/json but not text/html', async ({
-		request
-	}) => {
-		const response = await request.get('/shadowed/simple', {
-			headers: {
-				accept: 'application/json'
-			}
-		});
-
-		expect(await response.json()).toEqual({ answer: 42 });
-	});
-
 	test('Action can return undefined', async ({ request }) => {
 		const response = await request.post('/shadowed/simple/post', {
 			headers: {
@@ -314,6 +253,14 @@ test.describe('Static files', () => {
 	});
 });
 
+test.describe('setHeaders', () => {
+	test('allows multiple set-cookie headers with different values', async ({ page }) => {
+		const response = await page.goto('/headers/set-cookie/sub');
+		const cookies = (await response?.allHeaders())['set-cookie'];
+		expect(cookies.includes('cookie1=value1') && cookies.includes('cookie2=value2')).toBe(true);
+	});
+});
+
 test.describe('Miscellaneous', () => {
 	test('does not serve version.json with an immutable cache header', async ({ request }) => {
 		// this isn't actually a great test, because caching behaviour is down to adapters.