diff --git a/.changeset/great-pans-shave.md b/.changeset/great-pans-shave.md
new file mode 100644
index 000000000..164ead88a
--- /dev/null
+++ b/.changeset/great-pans-shave.md
@@ -0,0 +1,5 @@
+---
+'sv': patch
+---
+
+fix: run prettier directly instead of through the package manager, and stop allowing builds for packages that no longer have install scripts (`@tailwindcss/oxide`, `sharp`)
diff --git a/.changeset/nervous-poems-clap.md b/.changeset/nervous-poems-clap.md
new file mode 100644
index 000000000..ae2c939dc
--- /dev/null
+++ b/.changeset/nervous-poems-clap.md
@@ -0,0 +1,5 @@
+---
+'sv': patch
+---
+
+fix(experimental): `@sveltejs/kit@next` projects now install, build and type-check - `#lib` imports instead of `$lib`, a `tsconfig` extending `$app/tsconfig`, and no options kit 3 removed
diff --git a/.changeset/olive-geese-repeat.md b/.changeset/olive-geese-repeat.md
new file mode 100644
index 000000000..3f9762e6c
--- /dev/null
+++ b/.changeset/olive-geese-repeat.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/sv-utils': patch
+---
+
+feat: add SvelteKit 3 helpers - `isKit3`, `resolveLibPrefix`, `libSubpathImports`
diff --git a/.changeset/remove-mcp-addon.md b/.changeset/remove-mcp-addon.md
new file mode 100644
index 000000000..73672764c
--- /dev/null
+++ b/.changeset/remove-mcp-addon.md
@@ -0,0 +1,5 @@
+---
+'sv': minor
+---
+
+feat(ai-tools): replace `mcp` add-on with `ai-tools` add-on - set up the Svelte plugin (Claude Code, opencode) or pick individual tools (MCP server, skills, sub-agents) per client
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2ef53d4f1..dc74f3899 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -114,8 +114,7 @@ Some snapshots are testing the output of `sv` directly from the generated binary
In one command:
```sh
-pnpm build && pnpm test:ui --project cli
-# Press `u` when prompted to update snapshots.
+pnpm build && pnpm test --project cli -u all
```
## Style Guide
diff --git a/documentation/docs/50-api/10-sv.md b/documentation/docs/50-api/10-sv.md
index 7b4b1483f..5e43fc7ce 100644
--- a/documentation/docs/50-api/10-sv.md
+++ b/documentation/docs/50-api/10-sv.md
@@ -148,6 +148,31 @@ const addon = defineAddon<{ theme: string }>()({
The type parameter maps value types (`boolean`, `string`, `number`) to question definitions. Without it, `defineAddon` stays strict and only allows statically defined options.
+### Typed dynamic options
+
+If your add-on adds options dynamically in `setup` (e.g. from a fetch), you can pass a type parameter to `defineAddon` to get strong typing for those options:
+
+```ts
+import { defineAddon, defineAddonOptions } from 'sv';
+// ---cut---
+const addon = defineAddon<{ theme: string }>()({
+ id: 'my-addon',
+ options: defineAddonOptions().build(),
+ setup: ({ addOption }) => {
+ addOption('theme', {
+ question: 'Which theme?',
+ type: 'string',
+ default: 'dark'
+ });
+ },
+ run: ({ options }) => {
+ options.theme; // string
+ }
+});
+```
+
+The type parameter maps value types (`boolean`, `string`, `number`) to question definitions. Without it, `defineAddon` stays strict and only allows statically defined options.
+
## `defineAddonOptions`
Builder for add-on options. Chained with `.add()` and finalized with `.build()`.
diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts
index 2ac309d0f..e7fe8c144 100644
--- a/packages/sv/src/addons/better-auth.ts
+++ b/packages/sv/src/addons/better-auth.ts
@@ -350,8 +350,8 @@ export default defineAddon({
? `
signInEmail: async (event) => {${d1AuthLine}
const formData = await event.request.formData();
- const email = formData.get('email')?.toString() ?? '';
- const password = formData.get('password')?.toString() ?? '';
+ const email = parseEntry(formData.get('email'));
+ const password = parseEntry(formData.get('password'));
try {
await auth.api.signInEmail({
@@ -372,9 +372,9 @@ export default defineAddon({
},
signUpEmail: async (event) => {${d1AuthLine}
const formData = await event.request.formData();
- const email = formData.get('email')?.toString() ?? '';
- const password = formData.get('password')?.toString() ?? '';
- const name = formData.get('name')?.toString() ?? '';
+ const email = parseEntry(formData.get('email'));
+ const password = parseEntry(formData.get('password'));
+ const name = parseEntry(formData.get('name'));
try {
await auth.api.signUpEmail({
@@ -400,8 +400,8 @@ export default defineAddon({
? `
signInSocial: async (event) => {${d1AuthLine}
const formData = await event.request.formData();
- const provider = formData.get('provider')?.toString() ?? 'github';
- const callbackURL = formData.get('callbackURL')?.toString() ?? '/demo/better-auth';
+ const provider = parseEntry(formData.get('provider')) || 'github';
+ const callbackURL = parseEntry(formData.get('callbackURL')) || '/demo/better-auth';
const result = await auth.api.signInSocial({
body: {
@@ -426,6 +426,9 @@ export default defineAddon({
${!d1 ? `import { auth } from '${lib}/server/auth.${language}';` : ''}
${needsAPIError ? "import { APIError } from 'better-auth/api';" : ''}
+ const parseEntry = (input${ts(' : FormDataEntryValue | null')}): string =>
+ input instanceof File ? input.name : (input ?? '');
+
export const load${ts(': PageServerLoad')} = (event) => {
if (event.locals.user) {
return redirect(302, '/demo/better-auth');
diff --git a/packages/sv/src/addons/eslint.ts b/packages/sv/src/addons/eslint.ts
index 05b667676..728bb1352 100644
--- a/packages/sv/src/addons/eslint.ts
+++ b/packages/sv/src/addons/eslint.ts
@@ -55,7 +55,7 @@ export default defineAddon({
eslintConfigs.push(jsConfig);
if (typescript) {
- const tsConfig = js.common.parseExpression('ts.configs.recommended');
+ const tsConfig = js.common.parseExpression('ts.configs.recommendedTypeChecked');
eslintConfigs.push(tsConfig);
}
@@ -84,6 +84,9 @@ export default defineAddon({
const globalsConfig = js.object.create({
languageOptions: {
+ parserOptions: typescript
+ ? { projectService: { allowDefaultProject: ['*.js'] } }
+ : undefined,
globals: globalsObjLiteral
},
rules: typescript ? rules : undefined
diff --git a/packages/sv/src/addons/vitest-addon.ts b/packages/sv/src/addons/vitest-addon.ts
index 9252d72a7..31620fb86 100644
--- a/packages/sv/src/addons/vitest-addon.ts
+++ b/packages/sv/src/addons/vitest-addon.ts
@@ -94,10 +94,10 @@ export default defineAddon({
if (content) return false;
return dedent`
-
{greet(host)}
diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+layout.svelte b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+layout.svelte
index d7b4b46d9..dbf8844ff 100644
--- a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+layout.svelte
+++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+layout.svelte
@@ -1,7 +1,8 @@
diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.server.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.server.ts
index babb9e0fe..72696a357 100644
--- a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.server.ts
+++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.server.ts
@@ -4,6 +4,9 @@ import type { PageServerLoad } from './$types';
import { auth } from '#lib/server/auth.ts';
import { APIError } from 'better-auth/api';
+const parseEntry = (input : FormDataEntryValue | null): string =>
+ input instanceof File ? input.name : (input ?? '');
+
export const load: PageServerLoad = (event) => {
if (event.locals.user) {
return redirect(302, '/demo/better-auth');
@@ -14,8 +17,8 @@ export const load: PageServerLoad = (event) => {
export const actions: Actions = {
signInEmail: async (event) => {
const formData = await event.request.formData();
- const email = formData.get('email')?.toString() ?? '';
- const password = formData.get('password')?.toString() ?? '';
+ const email = parseEntry(formData.get('email'));
+ const password = parseEntry(formData.get('password'));
try {
await auth.api.signInEmail({
@@ -36,9 +39,9 @@ export const actions: Actions = {
},
signUpEmail: async (event) => {
const formData = await event.request.formData();
- const email = formData.get('email')?.toString() ?? '';
- const password = formData.get('password')?.toString() ?? '';
- const name = formData.get('name')?.toString() ?? '';
+ const email = parseEntry(formData.get('email'));
+ const password = parseEntry(formData.get('password'));
+ const name = parseEntry(formData.get('name'));
try {
await auth.api.signUpEmail({
@@ -60,8 +63,8 @@ export const actions: Actions = {
},
signInSocial: async (event) => {
const formData = await event.request.formData();
- const provider = formData.get('provider')?.toString() ?? 'github';
- const callbackURL = formData.get('callbackURL')?.toString() ?? '/demo/better-auth';
+ const provider = parseEntry(formData.get('provider')) || 'github';
+ const callbackURL = parseEntry(formData.get('callbackURL')) || '/demo/better-auth';
const result = await auth.api.signInSocial({
body: {
diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/tsconfig.json b/packages/sv/src/cli/tests/snapshots/create-experimental/tsconfig.json
index 056b82ce9..557a81c4b 100644
--- a/packages/sv/src/cli/tests/snapshots/create-experimental/tsconfig.json
+++ b/packages/sv/src/cli/tests/snapshots/create-experimental/tsconfig.json
@@ -3,6 +3,7 @@
"compilerOptions": {
"strict": true,
"types": [
+ "node",
"./worker-configuration.d.ts"
]
},
diff --git a/packages/sv/src/cli/tests/snapshots/create-only/src/routes/+layout.svelte b/packages/sv/src/cli/tests/snapshots/create-only/src/routes/+layout.svelte
index d7b4b46d9..dbf8844ff 100644
--- a/packages/sv/src/cli/tests/snapshots/create-only/src/routes/+layout.svelte
+++ b/packages/sv/src/cli/tests/snapshots/create-only/src/routes/+layout.svelte
@@ -1,7 +1,8 @@
diff --git a/packages/sv/src/cli/tests/snapshots/create-only/tsconfig.json b/packages/sv/src/cli/tests/snapshots/create-only/tsconfig.json
index 62f2df920..65077d6d1 100644
--- a/packages/sv/src/cli/tests/snapshots/create-only/tsconfig.json
+++ b/packages/sv/src/cli/tests/snapshots/create-only/tsconfig.json
@@ -1,7 +1,8 @@
{
"extends": "$app/tsconfig",
"compilerOptions": {
- "strict": true
+ "strict": true,
+ "types": ["node"]
},
"include": ["src", "vite.config.ts"]
}
diff --git a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/eslint.config.js b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/eslint.config.js
index e46c5b535..6cd9d5f90 100644
--- a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/eslint.config.js
+++ b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/eslint.config.js
@@ -11,12 +11,15 @@ const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
export default defineConfig(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
- ts.configs.recommended,
+ ts.configs.recommendedTypeChecked,
svelte.configs.recommended,
prettier,
svelte.configs.prettier,
{
- languageOptions: { globals: { ...globals.browser, ...globals.node } },
+ languageOptions: {
+ parserOptions: { projectService: { allowDefaultProject: ['*.js'] } },
+ globals: { ...globals.browser, ...globals.node }
+ },
rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
diff --git a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/lib/vitest-examples/Welcome.svelte b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/lib/vitest-examples/Welcome.svelte
index c7b846031..e8f7c31c5 100644
--- a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/lib/vitest-examples/Welcome.svelte
+++ b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/lib/vitest-examples/Welcome.svelte
@@ -1,7 +1,7 @@
-
{greet(host)}
diff --git a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/routes/+layout.svelte b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/routes/+layout.svelte
index f867c9714..cc1adc843 100644
--- a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/routes/+layout.svelte
+++ b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/routes/+layout.svelte
@@ -4,9 +4,10 @@
import { page } from '$app/state';
import { locales, localizeHref } from '#lib/paraglide/runtime.js';
import './layout.css';
- import favicon from '#lib/assets/favicon.svg';
+ import favicon from '$lib/assets/favicon.svg';
+ import type { LayoutProps } from './$types';
- let { children } = $props();
+ let { children }: LayoutProps = $props();
diff --git a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/routes/demo/better-auth/login/+page.server.ts b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/routes/demo/better-auth/login/+page.server.ts
index babb9e0fe..72696a357 100644
--- a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/routes/demo/better-auth/login/+page.server.ts
+++ b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/routes/demo/better-auth/login/+page.server.ts
@@ -4,6 +4,9 @@ import type { PageServerLoad } from './$types';
import { auth } from '#lib/server/auth.ts';
import { APIError } from 'better-auth/api';
+const parseEntry = (input : FormDataEntryValue | null): string =>
+ input instanceof File ? input.name : (input ?? '');
+
export const load: PageServerLoad = (event) => {
if (event.locals.user) {
return redirect(302, '/demo/better-auth');
@@ -14,8 +17,8 @@ export const load: PageServerLoad = (event) => {
export const actions: Actions = {
signInEmail: async (event) => {
const formData = await event.request.formData();
- const email = formData.get('email')?.toString() ?? '';
- const password = formData.get('password')?.toString() ?? '';
+ const email = parseEntry(formData.get('email'));
+ const password = parseEntry(formData.get('password'));
try {
await auth.api.signInEmail({
@@ -36,9 +39,9 @@ export const actions: Actions = {
},
signUpEmail: async (event) => {
const formData = await event.request.formData();
- const email = formData.get('email')?.toString() ?? '';
- const password = formData.get('password')?.toString() ?? '';
- const name = formData.get('name')?.toString() ?? '';
+ const email = parseEntry(formData.get('email'));
+ const password = parseEntry(formData.get('password'));
+ const name = parseEntry(formData.get('name'));
try {
await auth.api.signUpEmail({
@@ -60,8 +63,8 @@ export const actions: Actions = {
},
signInSocial: async (event) => {
const formData = await event.request.formData();
- const provider = formData.get('provider')?.toString() ?? 'github';
- const callbackURL = formData.get('callbackURL')?.toString() ?? '/demo/better-auth';
+ const provider = parseEntry(formData.get('provider')) || 'github';
+ const callbackURL = parseEntry(formData.get('callbackURL')) || '/demo/better-auth';
const result = await auth.api.signInSocial({
body: {
diff --git a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/tsconfig.json b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/tsconfig.json
index 30355afe2..7242e723f 100644
--- a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/tsconfig.json
+++ b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/tsconfig.json
@@ -1,7 +1,8 @@
{
"extends": "$app/tsconfig",
"compilerOptions": {
- "strict": true
+ "strict": true,
+ "types": ["node"]
},
"include": [
"src",
diff --git a/packages/sv/src/create/shared/+library+typescript/tsconfig.json b/packages/sv/src/create/shared/+library+typescript/tsconfig.json
index 62f2df920..65077d6d1 100644
--- a/packages/sv/src/create/shared/+library+typescript/tsconfig.json
+++ b/packages/sv/src/create/shared/+library+typescript/tsconfig.json
@@ -1,7 +1,8 @@
{
"extends": "$app/tsconfig",
"compilerOptions": {
- "strict": true
+ "strict": true,
+ "types": ["node"]
},
"include": ["src", "vite.config.ts"]
}
diff --git a/packages/sv/src/create/shared/+typescript/tsconfig.json b/packages/sv/src/create/shared/+typescript/tsconfig.json
index 62f2df920..65077d6d1 100644
--- a/packages/sv/src/create/shared/+typescript/tsconfig.json
+++ b/packages/sv/src/create/shared/+typescript/tsconfig.json
@@ -1,7 +1,8 @@
{
"extends": "$app/tsconfig",
"compilerOptions": {
- "strict": true
+ "strict": true,
+ "types": ["node"]
},
"include": ["src", "vite.config.ts"]
}
diff --git a/packages/sv/src/create/templates/demo/src/routes/+layout.svelte b/packages/sv/src/create/templates/demo/src/routes/+layout.svelte
index 02ac943b0..a345e20db 100644
--- a/packages/sv/src/create/templates/demo/src/routes/+layout.svelte
+++ b/packages/sv/src/create/templates/demo/src/routes/+layout.svelte
@@ -1,9 +1,10 @@
diff --git a/packages/sv/src/create/templates/demo/src/routes/sverdle/+page.server.ts b/packages/sv/src/create/templates/demo/src/routes/sverdle/+page.server.ts
index 99e61a9b7..7d0a9f683 100644
--- a/packages/sv/src/create/templates/demo/src/routes/sverdle/+page.server.ts
+++ b/packages/sv/src/create/templates/demo/src/routes/sverdle/+page.server.ts
@@ -35,7 +35,7 @@ export const actions = {
const game = new Game(cookies.get('sverdle'));
const data = await request.formData();
- const key = data.get('key');
+ const key = (data.get('key') as string) ?? '';
const i = game.answers.length;
@@ -65,7 +65,7 @@ export const actions = {
cookies.set('sverdle', game.toString(), { path: '/' });
},
- restart: async ({ cookies }) => {
+ restart: ({ cookies }) => {
cookies.delete('sverdle', { path: '/' });
}
} satisfies Actions;
diff --git a/packages/sv/src/create/templates/demo/src/routes/sverdle/+page.svelte b/packages/sv/src/create/templates/demo/src/routes/sverdle/+page.svelte
index d38265b24..46f3054f0 100644
--- a/packages/sv/src/create/templates/demo/src/routes/sverdle/+page.svelte
+++ b/packages/sv/src/create/templates/demo/src/routes/sverdle/+page.svelte
@@ -107,7 +107,7 @@
// prevent default callback from resetting the form
return ({ result, update }) => {
shake = result.type === 'failure';
- update({ reset: false });
+ void update({ reset: false });
};
}}
>
diff --git a/packages/sv/src/create/templates/minimal/src/routes/+layout.svelte b/packages/sv/src/create/templates/minimal/src/routes/+layout.svelte
index d7b4b46d9..891a4ef3d 100644
--- a/packages/sv/src/create/templates/minimal/src/routes/+layout.svelte
+++ b/packages/sv/src/create/templates/minimal/src/routes/+layout.svelte
@@ -1,7 +1,9 @@