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 @@