Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/great-pans-shave.md
Original file line number Diff line number Diff line change
@@ -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`)
5 changes: 5 additions & 0 deletions .changeset/nervous-poems-clap.md
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions .changeset/olive-geese-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/sv-utils': patch
---

feat: add SvelteKit 3 helpers - `isKit3`, `resolveLibPrefix`, `libSubpathImports`
5 changes: 5 additions & 0 deletions .changeset/remove-mcp-addon.md
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions documentation/docs/50-api/10-sv.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`.
Expand Down
17 changes: 10 additions & 7 deletions packages/sv/src/addons/better-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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({
Expand All @@ -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: {
Expand All @@ -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');
Expand Down
5 changes: 4 additions & 1 deletion packages/sv/src/addons/eslint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/sv/src/addons/vitest-addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ export default defineAddon({
if (content) return false;

return dedent`
<script>
<script${ts(' lang="ts"')}>
import { greet } from './greet';

let { host = 'SvelteKit', guest = 'Vitest' } = $props();
let { host = 'SvelteKit', guest = 'Vitest' }${ts(': { host: string; guest: string }')} = $props();
</script>

<h1>{greet(host)}</h1>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts">
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();
</script>

<svelte:head>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"compilerOptions": {
"strict": true,
"types": [
"node",
"./worker-configuration.d.ts"
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts">
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();
</script>

<svelte:head>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "$app/tsconfig",
"compilerOptions": {
"strict": true
"strict": true,
"types": ["node"]
},
"include": ["src", "vite.config.ts"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script>
<script lang="ts">
import { greet } from './greet';

let { host = 'SvelteKit', guest = 'Vitest' } = $props();
let { host = 'SvelteKit', guest = 'Vitest' }: { host: string; guest: string } = $props();
</script>

<h1>{greet(host)}</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
</script>

<svelte:head>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "$app/tsconfig",
"compilerOptions": {
"strict": true
"strict": true,
"types": ["node"]
},
"include": [
"src",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "$app/tsconfig",
"compilerOptions": {
"strict": true
"strict": true,
"types": ["node"]
},
"include": ["src", "vite.config.ts"]
}
3 changes: 2 additions & 1 deletion packages/sv/src/create/shared/+typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "$app/tsconfig",
"compilerOptions": {
"strict": true
"strict": true,
"types": ["node"]
},
"include": ["src", "vite.config.ts"]
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script lang="ts">
import Header from './Header.svelte';
import './layout.css';
import type { LayoutProps } from './$types';

/** @type {{children: import('svelte').Snippet}} */
let { children } = $props();
let { children }: LayoutProps = $props();
</script>

<div class="app">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -65,7 +65,7 @@ export const actions = {
cookies.set('sverdle', game.toString(), { path: '/' });
},

restart: async ({ cookies }) => {
restart: ({ cookies }) => {
cookies.delete('sverdle', { path: '/' });
}
} satisfies Actions;
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
};
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script lang="ts">
import favicon from '#lib/assets/favicon.svg';
import favicon from '$lib/assets/favicon.svg';
import type { LayoutProps } from './$types';

let { children } = $props();
/** @type {{children: import('svelte').Snippet}} */
let { children }: LayoutProps = $props();
</script>

<svelte:head>
Expand Down
Loading