Repository: sveltejs/kit
Pull request #7159 (2022-10-05 22:23:37)

[fix] clear out build directory when running `adapter-netlify`
We used to do this and somehow lost it, which has been causing me frustration. I'm pretty sure we do it in the other adapters though I didn't confirm that

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: ac8f6eb220cf92ef856bbe039e34eb2efcd546a0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/sveltejs/kit/new/benmccann-patch-1?filename=.changeset/sharp-lies-grin.md&value=---%0A%22%40sveltejs%2Fadapter-netlify%22%3A%20patch%0A---%0A%0A%5Bfix%5D%20clear%20out%20build%20directory%20when%20running%20%60adapter-netlify%60%0A)



Patch:
diff --git a/.changeset/four-crabs-beg.md b/.changeset/four-crabs-beg.md
new file mode 100644
index 000000000..fd6a7d6d8
--- /dev/null
+++ b/.changeset/four-crabs-beg.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[perf] render head links before other content
diff --git a/.changeset/hot-shoes-bow.md b/.changeset/hot-shoes-bow.md
new file mode 100644
index 000000000..5a3a6794a
--- /dev/null
+++ b/.changeset/hot-shoes-bow.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Fix parsing content-type header for actions
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 54a1a5f4c..3f67b2dc2 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -363,6 +363,7 @@
     "forty-worms-deliver",
     "four-apes-attack",
     "four-cheetahs-yawn",
+    "four-crabs-beg",
     "four-glasses-explain",
     "four-meals-learn",
     "four-news-turn",
diff --git a/.changeset/sharp-lies-grin.md b/.changeset/sharp-lies-grin.md
new file mode 100644
index 000000000..a75c5f53b
--- /dev/null
+++ b/.changeset/sharp-lies-grin.md
@@ -0,0 +1,5 @@
+---
+"@sveltejs/adapter-netlify": patch
+---
+
+[fix] clear out build directory when running `adapter-netlify`
diff --git a/documentation/docs/02-web-standards.md b/documentation/docs/02-web-standards.md
index 1d68b36fc..937f1e65d 100644
--- a/documentation/docs/02-web-standards.md
+++ b/documentation/docs/02-web-standards.md
@@ -18,7 +18,7 @@ Besides `fetch` itself, the [Fetch API](https://developer.mozilla.org/en-US/docs
 
 #### Request
 
-An instance of [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) is accessible in [hooks](/docs/hooks) and [server routes](/docs/routing#server) as `event.request`. It contains useful methods like `request.json()` and `request.formData()` for e.g. getting data that was posted to an endpoint.
+An instance of [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) is accessible in [hooks](/docs/hooks) and [server routes](/docs/routing#server) as `event.request`. It contains useful methods like `request.json()` and `request.formData()` for getting data that was posted to an endpoint.
 
 #### Response
 
diff --git a/package.json b/package.json
index ba1db21ca..95178beaa 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
 	"homepage": "https://github.com/sveltejs/kit#readme",
 	"devDependencies": {
 		"@changesets/cli": "^2.22.0",
-		"@rollup/plugin-commonjs": "^22.0.1",
+		"@rollup/plugin-commonjs": "^23.0.0",
 		"@rollup/plugin-json": "^4.1.0",
 		"@rollup/plugin-node-resolve": "^14.1.0",
 		"@svitejs/changesets-changelog-github-compact": "^0.1.1",
@@ -36,7 +36,7 @@
 		"turbo": "^1.3.4",
 		"typescript": "^4.8.2"
 	},
-	"packageManager": "pnpm@7.13.1",
+	"packageManager": "pnpm@7.13.3",
 	"engines": {
 		"pnpm": "^7.0.0"
 	},
diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js
index 6d77b17a7..ac1db56c8 100644
--- a/packages/adapter-netlify/index.js
+++ b/packages/adapter-netlify/index.js
@@ -45,6 +45,7 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
 			const publish = get_publish_directory(netlify_config, builder) || 'build';
 
 			// empty out existing build directories
+			builder.rimraf(publish);
 			builder.rimraf('.netlify/edge-functions');
 			builder.rimraf('.netlify/functions-internal');
 			builder.rimraf('.netlify/server');
diff --git a/packages/adapter-netlify/package.json b/packages/adapter-netlify/package.json
index 759948d15..3f07cf186 100644
--- a/packages/adapter-netlify/package.json
+++ b/packages/adapter-netlify/package.json
@@ -38,7 +38,7 @@
 	},
 	"devDependencies": {
 		"@netlify/functions": "^1.0.0",
-		"@rollup/plugin-commonjs": "^22.0.1",
+		"@rollup/plugin-commonjs": "^23.0.0",
 		"@rollup/plugin-json": "^4.1.0",
 		"@rollup/plugin-node-resolve": "^14.1.0",
 		"@sveltejs/kit": "workspace:*",
diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json
index cf1c2dfb3..5c665ad2b 100644
--- a/packages/adapter-node/package.json
+++ b/packages/adapter-node/package.json
@@ -42,7 +42,7 @@
 		"uvu": "^0.5.3"
 	},
 	"dependencies": {
-		"@rollup/plugin-commonjs": "^22.0.1",
+		"@rollup/plugin-commonjs": "^23.0.0",
 		"@rollup/plugin-json": "^4.1.0",
 		"@rollup/plugin-node-resolve": "^14.1.0",
 		"rollup": "^2.78.1"
diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md
index 88d758f6e..4ee8fabb1 100644
--- a/packages/kit/CHANGELOG.md
+++ b/packages/kit/CHANGELOG.md
@@ -1,5 +1,11 @@
 # @sveltejs/kit
 
+## 1.0.0-next.511
+
+### Patch Changes
+
+- [perf] render head links before other content ([#7160](https://github.com/sveltejs/kit/pull/7160))
+
 ## 1.0.0-next.510
 
 ### Patch Changes
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 81300b885..b5c719581 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "@sveltejs/kit",
-	"version": "1.0.0-next.510",
+	"version": "1.0.0-next.511",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/sveltejs/kit",
diff --git a/packages/kit/src/core/sync/write_types/index.js b/packages/kit/src/core/sync/write_types/index.js
index de91ace4d..a55679811 100644
--- a/packages/kit/src/core/sync/write_types/index.js
+++ b/packages/kit/src/core/sync/write_types/index.js
@@ -617,7 +617,7 @@ export function tweak_types(content, is_server) {
 					}
 				}
 			}
-			modified ||= _modified;
+			modified = modified || _modified;
 			return _modified;
 		}
 
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index 6f08b62f1..8eb14c6da 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -3,6 +3,7 @@ import { render_page } from './page/index.js';
 import { render_response } from './page/render.js';
 import { respond_with_error } from './page/respond_with_error.js';
 import { coalesce_to_error } from '../../utils/error.js';
+import { is_form_content_type } from '../../utils/http.js';
 import { GENERIC_ERROR, handle_fatal_error } from './utils.js';
 import { decode_params, disable_search, normalize_path } from '../../utils/url.js';
 import { exec } from '../../utils/routing.js';
@@ -24,12 +25,10 @@ export async function respond(request, options, state) {
 	let url = new URL(request.url);
 
 	if (options.csrf.check_origin) {
-		const type = request.headers.get('content-type')?.split(';')[0];
-
 		const forbidden =
 			request.method === 'POST' &&
 			request.headers.get('origin') !== url.origin &&
-			(type === 'application/x-www-form-urlencoded' || type === 'multipart/form-data');
+			is_form_content_type(request);
 
 		if (forbidden) {
 			return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
diff --git a/packages/kit/src/runtime/server/page/actions.js b/packages/kit/src/runtime/server/page/actions.js
index 6f9507e87..02c038d5e 100644
--- a/packages/kit/src/runtime/server/page/actions.js
+++ b/packages/kit/src/runtime/server/page/actions.js
@@ -1,6 +1,6 @@
 import { error, json } from '../../../exports/index.js';
 import { normalize_error } from '../../../utils/error.js';
-import { negotiate } from '../../../utils/http.js';
+import { is_form_content_type, negotiate } from '../../../utils/http.js';
 import { HttpError, Redirect, ValidationError } from '../../control.js';
 import { handle_error_and_jsonify } from '../utils.js';
 
@@ -180,9 +180,10 @@ export async function call_action(event, actions) {
 		throw new Error(`No action with name '${name}' found`);
 	}
 
-	const type = event.request.headers.get('content-type')?.split('; ')[0];
-	if (type !== 'application/x-www-form-urlencoded' && type !== 'multipart/form-data') {
-		throw new Error(`Actions expect form-encoded data (received ${type})`);
+	if (!is_form_content_type(event.request)) {
+		throw new Error(
+			`Actions expect form-encoded data (received ${event.request.headers.get('content-type')}`
+		);
 	}
 
 	return action(event);
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index a5f74f943..b26c4da88 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -135,7 +135,8 @@ export async function render_response({
 		rendered = { head: '', html: '', css: { code: '', map: null } };
 	}
 
-	let { head, html: body } = rendered;
+	let head = '';
+	let body = rendered.html;
 
 	const csp = new Csp(options.csp, {
 		dev: options.dev,
@@ -303,6 +304,9 @@ export async function render_response({
 		}
 	}
 
+	// add the content after the script/css links so the link elements are parsed first
+	head += rendered.head;
+
 	// TODO flush chunks as early as we can
 	const html =
 		(await resolve_opts.transformPageChunk({
diff --git a/packages/kit/src/utils/http.js b/packages/kit/src/utils/http.js
index 434a0f1f8..31721d10c 100644
--- a/packages/kit/src/utils/http.js
+++ b/packages/kit/src/utils/http.js
@@ -53,3 +53,20 @@ export function negotiate(accept, types) {
 
 	return accepted;
 }
+
+/**
+ * Returns `true` if the request contains a `content-type` header with the given type
+ * @param {Request} request
+ * @param  {...string} types
+ */
+export function is_content_type(request, ...types) {
+	const type = request.headers.get('content-type')?.split(';', 1)[0].trim() ?? '';
+	return types.includes(type);
+}
+
+/**
+ * @param {Request} request
+ */
+export function is_form_content_type(request) {
+	return is_content_type(request, 'application/x-www-form-urlencoded', 'multipart/form-data');
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 31cb20af9..29568f41c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,7 +5,7 @@ importers:
   .:
     specifiers:
       '@changesets/cli': ^2.22.0
-      '@rollup/plugin-commonjs': ^22.0.1
+      '@rollup/plugin-commonjs': ^23.0.0
       '@rollup/plugin-json': ^4.1.0
       '@rollup/plugin-node-resolve': ^14.1.0
       '@svitejs/changesets-changelog-github-compact': ^0.1.1
@@ -18,7 +18,7 @@ importers:
       typescript: ^4.8.2
     devDependencies:
       '@changesets/cli': 2.23.0
-      '@rollup/plugin-commonjs': 22.0.1_rollup@2.78.1
+      '@rollup/plugin-commonjs': 23.0.0_rollup@2.78.1
       '@rollup/plugin-json': 4.1.0_rollup@2.78.1
       '@rollup/plugin-node-resolve': 14.1.0_rollup@2.78.1
       '@svitejs/changesets-changelog-github-compact': 0.1.1
@@ -83,7 +83,7 @@ importers:
     specifiers:
       '@iarna/toml': ^2.2.5
       '@netlify/functions': ^1.0.0
-      '@rollup/plugin-commonjs': ^22.0.1
+      '@rollup/plugin-commonjs': ^23.0.0
       '@rollup/plugin-json': ^4.1.0
       '@rollup/plugin-node-resolve': ^14.1.0
       '@sveltejs/kit': workspace:*
@@ -101,7 +101,7 @@ importers:
       set-cookie-parser: 2.5.0
     devDependencies:
       '@netlify/functions': 1.0.0
-      '@rollup/plugin-commonjs': 22.0.1_rollup@2.78.1
+      '@rollup/plugin-commonjs': 23.0.0_rollup@2.78.1
       '@rollup/plugin-json': 4.1.0_rollup@2.78.1
       '@rollup/plugin-node-resolve': 14.1.0_rollup@2.78.1
       '@sveltejs/kit': link:../kit
@@ -114,7 +114,7 @@ importers:
 
   packages/adapter-node:
     specifiers:
-      '@rollup/plugin-commonjs': ^22.0.1
+      '@rollup/plugin-commonjs': ^23.0.0
       '@rollup/plugin-json': ^4.1.0
       '@rollup/plugin-node-resolve': ^14.1.0
       '@sveltejs/kit': workspace:*
@@ -127,7 +127,7 @@ importers:
       typescript: ^4.8.2
       uvu: ^0.5.3
     dependencies:
-      '@rollup/plugin-commonjs': 22.0.1_rollup@2.78.1
+      '@rollup/plugin-commonjs': 23.0.0_rollup@2.78.1
       '@rollup/plugin-json': 4.1.0_rollup@2.78.1
       '@rollup/plugin-node-resolve': 14.1.0_rollup@2.78.1
       rollup: 2.78.1
@@ -1077,19 +1077,21 @@ packages:
   /@polka/url/1.0.0-next.21:
     resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
 
-  /@rollup/plugin-commonjs/22.0.1_rollup@2.78.1:
-    resolution: {integrity: sha512-dGfEZvdjDHObBiP5IvwTKMVeq/tBZGMBHZFMdIV1ClMM/YoWS34xrHFGfag9SN2ZtMgNZRFruqvxZQEa70O6nQ==}
-    engines: {node: '>= 12.0.0'}
+  /@rollup/plugin-commonjs/23.0.0_rollup@2.78.1:
+    resolution: {integrity: sha512-JbrTRyDNtLQj/rhl7RFUuYXwQ2fac+33oLDAu2k++WD95zweyo28UAomLVA0JMGx4vmCa7Nw4T6k/1F6lelExg==}
+    engines: {node: '>=14.0.0'}
     peerDependencies:
-      rollup: ^2.68.0
+      rollup: ^2.68.0||^3.0.0
+    peerDependenciesMeta:
+      rollup:
+        optional: true
     dependencies:
-      '@rollup/pluginutils': 3.1.0_rollup@2.78.1
+      '@rollup/pluginutils': 4.2.1
       commondir: 1.0.1
       estree-walker: 2.0.2
-      glob: 7.2.3
+      glob: 8.0.3
       is-reference: 1.2.1
-      magic-string: 0.25.9
-      resolve: 1.22.1
+      magic-string: 0.26.6
       rollup: 2.78.1
 
   /@rollup/plugin-json/4.1.0_rollup@2.78.1:
@@ -1474,6 +1476,11 @@ packages:
       balanced-match: 1.0.2
       concat-map: 0.0.1
 
+  /brace-expansion/2.0.1:
+    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+    dependencies:
+      balanced-match: 1.0.2
+
   /braces/3.0.2:
     resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
     engines: {node: '>=8'}
@@ -2411,6 +2418,16 @@ packages:
       once: 1.4.0
       path-is-absolute: 1.0.1
 
+  /glob/8.0.3:
+    resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      fs.realpath: 1.0.0
+      inflight: 1.0.6
+      inherits: 2.0.4
+      minimatch: 5.1.0
+      once: 1.4.0
+
   /globalyzer/0.1.0:
     resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
 
@@ -2896,6 +2913,7 @@ packages:
     resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
     dependencies:
       sourcemap-codec: 1.4.8
+    dev: true
 
   /magic-string/0.26.2:
     resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==}
@@ -2910,6 +2928,12 @@ packages:
     dependencies:
       sourcemap-codec: 1.4.8
 
+  /magic-string/0.26.6:
+    resolution: {integrity: sha512-6d+3bFybzyQFJYSoRsl9ZC0wheze8M1LrQC7tNMRqXR4izUTDOLMd9BtSuExK9iAukFh+s5K0WAhc/dlQ+HKYA==}
+    engines: {node: '>=12'}
+    dependencies:
+      sourcemap-codec: 1.4.8
+
   /make-dir/3.1.0:
     resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
     engines: {node: '>=8'}
@@ -2993,6 +3017,12 @@ packages:
     dependencies:
       brace-expansion: 1.1.11
 
+  /minimatch/5.1.0:
+    resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==}
+    engines: {node: '>=10'}
+    dependencies:
+      brace-expansion: 2.0.1
+
   /minimist-options/4.1.0:
     resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
     engines: {node: '>= 6'}


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/actions/success-data/+page.svelte b/packages/kit/test/apps/basics/src/routes/actions/success-data/+page.svelte
index e3699100e..e784d2b20 100644
--- a/packages/kit/test/apps/basics/src/routes/actions/success-data/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/actions/success-data/+page.svelte
@@ -2,10 +2,12 @@
 	/** @type {import('./$types').ActionData} */
 	export let form;
 
-	async function submit() {
+	async function submit({ submitter }) {
 		const res = await fetch(this.action, {
 			method: 'POST',
-			body: new FormData(this),
+			body: submitter.getAttribute('formenctype') === 'multipart/form-data'
+				? new FormData(this)
+				: new URLSearchParams({ username: this['username'].value }),
 			headers: {
 				accept: 'application/json'
 			}
@@ -19,5 +21,6 @@
 
 <form method="post" on:submit|preventDefault={submit}>
 	<input name="username" type="text" />
-	<button>Submit</button>
+	<button formenctype="multipart/form-data">Submit</button>
+	<button formenctype="application/x-www-form-urlencoded">Submit</button>
 </form>
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 93816939b..4522ee30b 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -1773,7 +1773,7 @@ test.describe('Actions', () => {
 		}
 	});
 
-	test('Success data is returned', async ({ page }) => {
+	test('Success data as form-data is returned', async ({ page }) => {
 		await page.goto('/actions/success-data');
 
 		expect(await page.textContent('pre')).toBe(JSON.stringify(null));
@@ -1781,12 +1781,26 @@ test.describe('Actions', () => {
 		await page.type('input[name="username"]', 'foo');
 		await Promise.all([
 			page.waitForRequest((request) => request.url().includes('/actions/success-data')),
-			page.click('button')
+			page.click('button[formenctype="multipart/form-data"]')
 		]);
 
 		await expect(page.locator('pre')).toHaveText(JSON.stringify({ result: 'foo' }));
 	});
 
+	test('Success data as form-urlencoded is returned', async ({ page }) => {
+		await page.goto('/actions/success-data');
+
+		expect(await page.textContent('pre')).toBe(JSON.stringify(null));
+
+		await page.type('input[name="username"]', 'bar');
+		await Promise.all([
+			page.waitForRequest((request) => request.url().includes('/actions/success-data')),
+			page.click('button[formenctype="application/x-www-form-urlencoded"]')
+		]);
+
+		await expect(page.locator('pre')).toHaveText(JSON.stringify({ result: 'bar' }));
+	});
+
 	test('applyAction updates form prop', async ({ page, javaScriptEnabled }) => {
 		await page.goto('/actions/update-form');
 		expect(await page.textContent('pre')).toBe(JSON.stringify(null));