From f735bde8d792ea96682a9ecc39c3a90a2837f429 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Jul 2026 11:21:12 +0200 Subject: [PATCH 01/14] feat(sv-utils): add SvelteKit 3 helpers Version detection, the `$lib` -> `#lib` move and the generated `$app/tsconfig`, so add-ons stop hardcoding kit-version-specific shapes. --- .changeset/olive-geese-repeat.md | 5 ++++ packages/sv-utils/api-surface.md | 15 +++++++++++ packages/sv-utils/src/env.ts | 24 ++++++++++-------- packages/sv-utils/src/index.ts | 9 +++++++ packages/sv-utils/src/kit3.ts | 43 ++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 .changeset/olive-geese-repeat.md create mode 100644 packages/sv-utils/src/kit3.ts diff --git a/.changeset/olive-geese-repeat.md b/.changeset/olive-geese-repeat.md new file mode 100644 index 000000000..3eb452586 --- /dev/null +++ b/.changeset/olive-geese-repeat.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/sv-utils': patch +--- + +feat: add SvelteKit 3 helpers - `isKit3`, `resolveLibPrefix`, `libSubpathImports` and the generated `$app/tsconfig` constants, so add-ons emit the right shape without hardcoding it diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index c34e1e572..ef9235b68 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -854,6 +854,16 @@ type DefineEnv = { }; declare function defineEnv({ sv, cwd, dependencyVersion }: DefineEnvContext): DefineEnv; + +declare function isKit3(kitRange: string | undefined): boolean; + +declare function resolveLibPrefix(kitRange: string | undefined): '#lib' | '$lib'; + +declare function libSubpathImports(libDir: string): Record; + +declare const KIT3_TSCONFIG = '$app/tsconfig'; + +declare const KIT3_TSCONFIG_INHERITED: Record; type ColorInput = string | string[]; declare const color: { addon: (str: ColorInput) => string; @@ -886,6 +896,8 @@ export { COMMANDS, type Comments, type ConfigFileReader, + KIT3_TSCONFIG, + KIT3_TSCONFIG_INHERITED, type Package, type SvelteAst, type SvelteConfigKind, @@ -905,9 +917,11 @@ export { downloadJson, fileExists, index_d_exports$2 as html, + isKit3, isVersionUnsupportedBelow, index_d_exports$3 as js, json_d_exports as json, + libSubpathImports, loadFile, loadPackageJson, minVersion, @@ -915,6 +929,7 @@ export { pnpm_d_exports as pnpm, resolveCommand, resolveCommandArray, + resolveLibPrefix, sanitizeName, saveFile, splitVersion, diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 84a7d8c3e..1bca67158 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -1,4 +1,5 @@ import { fileExists } from './files.ts'; +import { isKit3 } from './kit3.ts'; import { coerceVersion } from './semver.ts'; import { svelteConfig, type ConfigFileReader, type SvFileApi } from './svelte-config.ts'; import type { AstTypes } from './tooling/index.ts'; @@ -15,10 +16,8 @@ export function resolveEnvMode({ explicitEnvFlag: boolean; }): EnvMode { if (!kitRange) return 'legacy'; - if (kitRange === 'next') return 'declared'; - const { major } = coerceVersion(kitRange); - if (major !== undefined && major >= 3) return 'declared'; - if (major === 2 && explicitEnvFlag) return 'declared'; + if (isKit3(kitRange)) return 'declared'; + if (coerceVersion(kitRange).major === 2 && explicitEnvFlag) return 'declared'; return 'legacy'; } @@ -110,23 +109,23 @@ function getOrCreateVariablesObject( * just call `define`/`reference` and never deal with the legacy-vs-declared distinction themselves. */ export function defineEnv({ sv, cwd, dependencyVersion }: DefineEnvContext): DefineEnv { - const mode = resolveEnvMode({ - kitRange: dependencyVersion('@sveltejs/kit'), - explicitEnvFlag: readExplicitEnvFlag(cwd) - }); + const kitRange = dependencyVersion('@sveltejs/kit'); + const mode = resolveEnvMode({ kitRange, explicitEnvFlag: readExplicitEnvFlag(cwd) }); const language = fileExists(cwd, 'tsconfig.json') ? 'ts' : 'js'; - return _bindEnv({ sv, mode, language }); + return _bindEnv({ sv, mode, language, kit3: isKit3(kitRange) }); } /** @internal The mode-resolved core, exported for filesystem-free tests. */ export function _bindEnv({ sv, mode, - language + language, + kit3 = false }: { sv: SvFileApi; mode: EnvMode; language: 'ts' | 'js'; + kit3?: boolean; }): DefineEnv { const declared = new Map(); @@ -138,7 +137,10 @@ export function _bindEnv({ const envPath = `src/env.${language}`; sv.file(envPath, (content) => transforms.script(({ ast, js }) => { - js.imports.addNamed(ast, { from: '@sveltejs/kit/hooks', imports: ['defineEnvVars'] }); + js.imports.addNamed(ast, { + from: kit3 ? '@sveltejs/kit/env' : '@sveltejs/kit/hooks', + imports: ['defineEnvVars'] + }); const variables = getOrCreateVariablesObject(ast, js); const entry = js.object.property(variables, { name: spec.name, diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index b91c7b553..8d755f564 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -87,6 +87,15 @@ export { // Env access (abstracts over legacy `$env/dynamic/*` vs declared `$app/env/*` + `src/env.ts`) export { defineEnv } from './env.ts'; +// Kit 3 specifics (version detection, `$lib` -> `#lib`, the generated tsconfig) +export { + KIT3_TSCONFIG, + KIT3_TSCONFIG_INHERITED, + isKit3, + libSubpathImports, + resolveLibPrefix +} from './kit3.ts'; + // Terminal styling export { color } from './color.ts'; diff --git a/packages/sv-utils/src/kit3.ts b/packages/sv-utils/src/kit3.ts new file mode 100644 index 000000000..9f358cf6f --- /dev/null +++ b/packages/sv-utils/src/kit3.ts @@ -0,0 +1,43 @@ +import { coerceVersion } from './semver.ts'; + +/** Whether a `@sveltejs/kit` range resolves to v3+, including the `next` dist-tag. */ +export function isKit3(kitRange: string | undefined): boolean { + if (!kitRange) return false; + if (kitRange === 'next') return true; + const { major } = coerceVersion(kitRange); + return major !== undefined && major >= 3; +} + +/** The prefix for `src/lib` imports. Kit 3 dropped the built-in `$lib` alias for `#lib` subpath imports. */ +export function resolveLibPrefix(kitRange: string | undefined): '#lib' | '$lib' { + return isKit3(kitRange) ? '#lib' : '$lib'; +} + +/** The `package.json#imports` entries backing `#lib`. `libDir` is workspace-relative, e.g. `src/lib`. */ +export function libSubpathImports(libDir: string): Record { + return { '#lib': `./${libDir}/index.js`, '#lib/*': `./${libDir}/*` }; +} + +/** The config kit 3 generates into `node_modules`, replacing `.svelte-kit/tsconfig.json`. */ +export const KIT3_TSCONFIG = '$app/tsconfig'; + +/** + * Options `$app/tsconfig` already sets. A local copy of the same value is noise, but a different + * value is a deliberate override and must stay - so only drop keys whose value matches. + */ +export const KIT3_TSCONFIG_INHERITED: Record = { + allowImportingTsExtensions: true, + allowJs: true, + checkJs: true, + esModuleInterop: true, + forceConsistentCasingInFileNames: true, + isolatedModules: true, + module: 'esnext', + moduleDetection: 'force', + moduleResolution: 'bundler', + noEmit: true, + resolveJsonModule: true, + skipLibCheck: true, + target: 'esnext', + verbatimModuleSyntax: true +}; From 62f311df261c3b45003ccf887657ac86966d186e Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Jul 2026 11:21:12 +0200 Subject: [PATCH 02/14] fix(core): make `runsAfter` actually order add-ons It sorted by number of constraints, so an add-on could still run before one it declared it runs after. Topologically sort instead, seeded with the previous order so unconstrained add-ons keep their relative position. --- packages/sv/src/core/engine.ts | 36 ++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/sv/src/core/engine.ts b/packages/sv/src/core/engine.ts index 38d0bf554..8540c56ba 100644 --- a/packages/sv/src/core/engine.ts +++ b/packages/sv/src/core/engine.ts @@ -319,14 +319,38 @@ async function runAddon({ addon, loaded, multiple, workspace, workspaceOptions } }; } -// orders addons by putting addons that don't require any other addon in the front. -// This is a drastic simplification, as this could still cause some inconvenient circumstances, -// but works for now in contrary to the previous implementation +/** + * Orders add-ons so every `runsAfter` is honoured, keeping the original order between add-ons that + * don't constrain each other. Cycles and unknown ids are ignored rather than fatal - an add-on that + * can't be placed simply keeps its position. + */ export function orderAddons( addons: Array>, setupResults: Record ): Array> { - return addons.sort((a, b) => { - return setupResults[a.id]?.runsAfter?.length - setupResults[b.id]?.runsAfter?.length; - }); + const byId = new Map(addons.map((addon) => [addon.id, addon])); + const ordered: Array> = []; + const placed = new Set(); + const visiting = new Set(); + + const place = (addon: Addon) => { + if (placed.has(addon.id) || visiting.has(addon.id)) return; + visiting.add(addon.id); + for (const id of setupResults[addon.id]?.runsAfter ?? []) { + const dependency = byId.get(id); + if (dependency) place(dependency); + } + visiting.delete(addon.id); + placed.add(addon.id); + ordered.push(addon); + }; + + // seeded with the "fewest constraints first" order this used to rely on, so add-ons that don't + // constrain each other keep the relative order they already had + const seeded = [...addons].sort( + (a, b) => + (setupResults[a.id]?.runsAfter?.length ?? 0) - (setupResults[b.id]?.runsAfter?.length ?? 0) + ); + for (const addon of seeded) place(addon); + return ordered; } From f5398b6d33ef23479c7d49ecabc5380552fe74e9 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Jul 2026 11:21:20 +0200 Subject: [PATCH 03/14] fix(experimental): create manages `#lib` and the rest of the SvelteKit 3 shape - skip `handleRenderingErrors`/`explicitEnvironmentVariables`, removed in kit 3 - extend `$app/tsconfig` and own `include`, keeping deliberate compiler option overrides - rewrite `$lib` to `#lib` and declare the subpath imports Vite resolves from - drizzle drops the removed `typescript.config` hook; `defineEnvVars` moves to `@sveltejs/kit/env` - better-auth and paraglide emit the right lib prefix and route type --- .changeset/nervous-poems-clap.md | 9 +++ packages/sv/src/addons/better-auth.ts | 12 ++-- packages/sv/src/addons/drizzle.ts | 36 +++++++--- packages/sv/src/addons/experimental.ts | 66 ++++++++++++++++++- packages/sv/src/addons/paraglide.ts | 36 ++++++---- .../sv/src/addons/tests/experimental/test.ts | 28 ++++++-- 6 files changed, 155 insertions(+), 32 deletions(-) create mode 100644 .changeset/nervous-poems-clap.md diff --git a/.changeset/nervous-poems-clap.md b/.changeset/nervous-poems-clap.md new file mode 100644 index 000000000..c5412684d --- /dev/null +++ b/.changeset/nervous-poems-clap.md @@ -0,0 +1,9 @@ +--- +'sv': patch +--- + +fix(experimental): create manages `#lib` and the rest of the SvelteKit 3 shape + +Picking `@sveltejs/kit@next` now produces a project that installs, builds and type-checks: `#lib` +subpath imports instead of `$lib`, `tsconfig` extending `$app/tsconfig`, and no options SvelteKit 3 +removed. diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index ef5d21dc8..ea1910e24 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -1,5 +1,6 @@ import { log } from '@clack/prompts'; import { + resolveLibPrefix, type AstTypes, Walker, color, @@ -44,6 +45,7 @@ export default defineAddon({ runsAfter('experimental'); }, run: ({ sv, cwd, language, options, directory, dependencyVersion, file }) => { + const lib = resolveLibPrefix(dependencyVersion('@sveltejs/kit')); const svelteVersion = dependencyVersion('svelte'); const svelte5 = !!svelteVersion && coerceVersion(svelteVersion).major === 5; const [ts, s5] = createPrinter(language === 'ts', svelte5); @@ -122,7 +124,7 @@ export default defineAddon({ sv.file( `${directory.lib}/server/auth.${language}`, transforms.script(({ ast, comments, js }) => { - js.imports.addNamed(ast, { from: '$lib/server/db', imports: [d1 ? 'getDb' : 'db'] }); + js.imports.addNamed(ast, { from: `${lib}/server/db`, imports: [d1 ? 'getDb' : 'db'] }); js.imports.addNamed(ast, { from: '$app/server', imports: ['getRequestEvent'] }); js.imports.addNamed(ast, { from: 'better-auth/svelte-kit', @@ -238,7 +240,7 @@ export default defineAddon({ sv.file( 'src/app.d.ts', transforms.script(({ ast, comments, js }) => { - if (d1) js.imports.addNamed(ast, { imports: ['createAuth'], from: '$lib/server/auth' }); + if (d1) js.imports.addNamed(ast, { imports: ['createAuth'], from: `${lib}/server/auth` }); js.imports.addNamed(ast, { imports: ['User', 'Session'], from: 'better-auth', @@ -286,7 +288,7 @@ export default defineAddon({ }); js.imports.addNamed(ast, { imports: [d1 ? 'createAuth' : 'auth'], - from: '$lib/server/auth' + from: `${lib}/server/auth` }); env.importEnv(ast, js, ['building']); @@ -413,7 +415,7 @@ export default defineAddon({ import { fail, redirect } from '@sveltejs/kit'; ${ts("import type { Actions } from './$types';")} ${ts("import type { PageServerLoad } from './$types';")} - ${!d1 ? "import { auth } from '$lib/server/auth';" : ''} + ${!d1 ? `import { auth } from '${lib}/server/auth';` : ''} ${needsAPIError ? "import { APIError } from 'better-auth/api';" : ''} export const load${ts(': PageServerLoad')} = (event) => { @@ -502,7 +504,7 @@ export default defineAddon({ import { redirect } from '@sveltejs/kit'; ${ts("import type { Actions } from './$types';")} ${ts("import type { PageServerLoad } from './$types';")} - ${!d1 ? "import { auth } from '$lib/server/auth';" : ''} + ${!d1 ? `import { auth } from '${lib}/server/auth';` : ''} export const load${ts(': PageServerLoad')} = (event) => { if (!event.locals.user) { diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index 1ff089565..51537617a 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -8,7 +8,8 @@ import { fileExists, createPrinter, svelteConfig, - defineEnv + defineEnv, + isKit3 } from '@sveltejs/sv-utils'; import crypto from 'node:crypto'; import fs from 'node:fs'; @@ -300,15 +301,32 @@ export default defineAddon({ }) ); - svelteConfig.edit({ sv, cwd }, ({ override, js }) => { - override({ - typescript: { - config: js.common.parseExpression( - `(config) => { config.include.push('../drizzle.config.${language}')}` - ) - } + // kit 3 dropped the `typescript.config` hook's `include` (and deprecates the hook itself), + // so the project's own ts/jsconfig has to cover the drizzle config + if (isKit3(dependencyVersion('@sveltejs/kit'))) { + const configFile = language === 'ts' ? 'tsconfig.json' : 'jsconfig.json'; + if (fileExists(cwd, configFile)) { + sv.file( + configFile, + transforms.json(({ data }) => { + const include: string[] = (data.include ??= ['src']); + if (!include.includes(`drizzle.config.${language}`)) { + include.push(`drizzle.config.${language}`); + } + }) + ); + } + } else { + svelteConfig.edit({ sv, cwd }, ({ override, js }) => { + override({ + typescript: { + config: js.common.parseExpression( + `(config) => { config.include.push('../drizzle.config.${language}')}` + ) + } + }); }); - }); + } sv.file( paths['database schema'], diff --git a/packages/sv/src/addons/experimental.ts b/packages/sv/src/addons/experimental.ts index dc07d1654..d6db228af 100644 --- a/packages/sv/src/addons/experimental.ts +++ b/packages/sv/src/addons/experimental.ts @@ -1,4 +1,15 @@ -import { isVersionUnsupportedBelow, loadPackageJson, svelteConfig } from '@sveltejs/sv-utils'; +import { + KIT3_TSCONFIG, + KIT3_TSCONFIG_INHERITED, + fileExists, + isVersionUnsupportedBelow, + libSubpathImports, + loadPackageJson, + svelteConfig, + transforms +} from '@sveltejs/sv-utils'; +import fs from 'node:fs'; +import path from 'node:path'; import { defineAddon, defineAddonOptions } from '../core/config.ts'; // Single source of truth, keyed by flag name. `path` defaults to `experimental.`; `off` opts out @@ -8,10 +19,13 @@ const FEATURES: Record = { async: { label: 'async', hint: 'await in components', path: 'compilerOptions.experimental.async' }, // prettier-ignore remoteFunctions: { label: 'remote functions' }, explicitEnvironmentVariables: { label: 'explicit environment variables', hint: 'kit ^2 only', inNext: false }, // prettier-ignore - handleRenderingErrors: { label: 'rendering error boundaries' }, + handleRenderingErrors: { label: 'rendering error boundaries', hint: 'kit ^2 only', inNext: false }, // prettier-ignore forkPreloads: { label: 'forked preloading', off: true } }; +// files whose `$lib` imports are rewritten to `#lib` +const SOURCE_EXTENSIONS = ['.svelte', '.svelte.ts', '.svelte.js', '.ts', '.js', '.svx', '.md']; + // kit 3 raises these peer floors; bump only when the project is below them (never downgrade). const KIT3_PEERS = { vite: '^8.0.0', @@ -47,7 +61,7 @@ export default defineAddon({ setup: ({ runsAfter }) => runsAfter('sveltekitAdapter'), - run: ({ sv, cwd, options, language, dependencyVersion }) => { + run: ({ sv, cwd, options, language, directory, dependencyVersion }) => { const kitNext = options.versions.includes('kit'); if (kitNext) { @@ -65,6 +79,38 @@ export default defineAddon({ } } + if (kitNext) { + // kit 3 serves the generated config from `$app/tsconfig` and no longer supplies `include` + for (const name of ['tsconfig.json', 'jsconfig.json']) { + if (!fileExists(cwd, name)) continue; + sv.file( + name, + transforms.json(({ data }) => { + data.extends = KIT3_TSCONFIG; + data.include ??= [directory.src]; + for (const [key, value] of Object.entries(data.compilerOptions ?? {})) { + // a differing value is a deliberate override and stays + if (KIT3_TSCONFIG_INHERITED[key] === value) delete data.compilerOptions[key]; + } + }) + ); + } + + // `$lib` is gone in favour of `#lib` subpath imports, which Vite resolves from `package.json` + sv.file( + 'package.json', + transforms.json(({ data }) => { + data.imports = { ...libSubpathImports(directory.lib), ...data.imports }; + }) + ); + // safe here: templates are already written and every add-on emitting `$lib` runs later + for (const relative of sourceFiles(cwd, directory.src)) { + sv.file(relative, (content) => + content.includes('$lib') ? content.replaceAll('$lib', '#lib') : false + ); + } + } + const config: Record = {}; for (const [name, f] of Object.entries(FEATURES)) { if (!options.features.includes(name)) continue; @@ -79,3 +125,17 @@ export default defineAddon({ svelteConfig.edit({ sv, cwd }, ({ override }) => override(config)); } }); + +/** Workspace-relative source files under `src`, for the `$lib` -> `#lib` rewrite. */ +function sourceFiles(cwd: string, src: string): string[] { + const root = path.resolve(cwd, src); + if (!fs.existsSync(root)) return []; + return fs + .readdirSync(root, { recursive: true }) + .map((entry) => path.join(src, entry as string)) + .filter( + (relative) => + SOURCE_EXTENSIONS.some((ext) => relative.endsWith(ext)) && + fs.statSync(path.resolve(cwd, relative)).isFile() + ); +} diff --git a/packages/sv/src/addons/paraglide.ts b/packages/sv/src/addons/paraglide.ts index 12048cf09..ccbfe1ddd 100644 --- a/packages/sv/src/addons/paraglide.ts +++ b/packages/sv/src/addons/paraglide.ts @@ -1,5 +1,13 @@ import { log } from '@clack/prompts'; -import { color, createPrinter, dedent, type SvelteAst, transforms } from '@sveltejs/sv-utils'; +import { + color, + createPrinter, + dedent, + isKit3, + resolveLibPrefix, + type SvelteAst, + transforms +} from '@sveltejs/sv-utils'; import { defineAddon, defineAddonOptions } from '../core/config.ts'; import { addToDemoPage } from './common.ts'; @@ -50,11 +58,17 @@ export default defineAddon({ shortDescription: 'i18n', homepage: 'https://inlang.com/m/gerre34r/library-inlang-paraglideJs', options, - setup: ({ isKit, unsupported }) => { + setup: ({ isKit, unsupported, runsAfter }) => { if (!isKit) unsupported('Requires SvelteKit'); + // it picks the kit-3 shape off the version `experimental` writes + runsAfter('experimental'); }, - run: ({ sv, options, file, language, directory }) => { + run: ({ sv, options, file, language, directory, dependencyVersion }) => { const [ts] = createPrinter(language === 'ts'); + const kitRange = dependencyVersion('@sveltejs/kit'); + const lib = resolveLibPrefix(kitRange); + // kit 3 renamed the `Pathname` route type to `Path` + const pathType = isKit3(kitRange) ? 'Path' : 'Pathname'; const paraglideOutDir = `${directory.lib}/paraglide`; sv.devDependency('@inlang/paraglide-js', '^2.18.2'); @@ -80,7 +94,7 @@ export default defineAddon({ `src/hooks.${language}`, transforms.script(({ ast, comments, js }) => { js.imports.addNamed(ast, { - from: '$lib/paraglide/runtime', + from: `${lib}/paraglide/runtime`, imports: ['deLocalizeUrl'] }); @@ -120,11 +134,11 @@ export default defineAddon({ `src/hooks.server.${language}`, transforms.script(({ ast, comments, js }) => { js.imports.addNamed(ast, { - from: '$lib/paraglide/server', + from: `${lib}/paraglide/server`, imports: ['paraglideMiddleware'] }); js.imports.addNamed(ast, { - from: '$lib/paraglide/runtime', + from: `${lib}/paraglide/runtime`, imports: ['getTextDirection'] }); @@ -196,13 +210,13 @@ export default defineAddon({ transforms.svelteScript({ language }, ({ ast, svelte, js }) => { js.imports.addNamed(ast.instance.content, { imports: ['locales', 'localizeHref'], - from: '$lib/paraglide/runtime' + from: `${lib}/paraglide/runtime` }); js.imports.addNamed(ast.instance.content, { imports: ['page'], from: '$app/state' }); js.imports.addNamed(ast.instance.content, { imports: ['resolve'], from: '$app/paths' }); if (language === 'ts') { js.imports.addNamed(ast.instance.content, { - imports: ['Pathname'], + imports: [pathType], from: '$app/types', isType: true }); @@ -212,7 +226,7 @@ export default defineAddon({ dedent`
{#each locales as locale (locale)} - {locale} + {locale} {/each}
`, { language } @@ -229,13 +243,13 @@ export default defineAddon({ transforms.svelteScript({ language }, ({ ast, svelte, js }) => { js.imports.addNamed(ast.instance.content, { imports: { m: 'm' }, - from: '$lib/paraglide/messages.js' + from: `${lib}/paraglide/messages.js` }); js.imports.addNamed(ast.instance.content, { imports: { setLocale: 'setLocale' }, - from: '$lib/paraglide/runtime' + from: `${lib}/paraglide/runtime` }); // add localized message diff --git a/packages/sv/src/addons/tests/experimental/test.ts b/packages/sv/src/addons/tests/experimental/test.ts index d42df2dbc..c849dc9d7 100644 --- a/packages/sv/src/addons/tests/experimental/test.ts +++ b/packages/sv/src/addons/tests/experimental/test.ts @@ -1,3 +1,4 @@ +import { parse } from '@sveltejs/sv-utils'; import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { expect } from 'vitest'; @@ -10,7 +11,7 @@ const { test, testCases } = setupTest( { kinds: [ { - // kit@next selected + every feature: explicitEnvironmentVariables must be dropped (gone in kit 3) + // kit@next selected + every feature: the flags removed in kit 3 must be dropped type: 'next-all', options: { [addonId]: { @@ -50,23 +51,42 @@ test.concurrent.for(testCases)('experimental $kind.type $variant', (testCase, { const source = readFileSync(config, 'utf8'); const pkg = readFileSync(join(cwd, 'package.json'), 'utf8'); + const tsconfigPath = ['tsconfig.json', 'jsconfig.json'] + .map((name) => join(cwd, name)) + .find((file) => existsSync(file)); + const tsconfig = tsconfigPath ? parse.json(readFileSync(tsconfigPath, 'utf8')).data : undefined; + if (testCase.kind.type === 'next-all') { expect(JSON.parse(pkg).devDependencies['@sveltejs/kit']).toBe('next'); + if (tsconfig) { + expect(tsconfig.extends).toBe('$app/tsconfig'); + expect(tsconfig.include).toStrictEqual(['src']); + expect(tsconfig.compilerOptions).not.toHaveProperty('checkJs'); + } // the adapter must follow kit onto its `next` line (it peers on kit's major) expect(JSON.parse(pkg).devDependencies['@sveltejs/adapter-auto']).toBe('next'); expect(source).toMatch('async: true'); expect(source).toMatch('remoteFunctions: true'); - expect(source).toMatch('handleRenderingErrors: true'); expect(source).toMatch('forkPreloads: true'); - // removed from experimental in kit 3, so it must be skipped when kit@next is chosen + // kit 3 no longer provides `$lib` on its own - sources move to `#lib` subpath imports + expect(JSON.parse(pkg).imports).toMatchObject({ '#lib': expect.any(String) }); + const libIndex = ['src/lib/index.ts', 'src/lib/index.js'] + .map((name) => join(cwd, name)) + .find((file) => existsSync(file))!; + expect(readFileSync(libIndex, 'utf8')).not.toMatch('$lib'); + expect(source).not.toMatch('alias'); + // removed from experimental in kit 3, so they must be skipped when kit@next is chosen expect(source).not.toMatch('explicitEnvironmentVariables'); + expect(source).not.toMatch('handleRenderingErrors'); } else if (testCase.kind.type === 'kit2-defaults') { expect(JSON.parse(pkg).devDependencies['@sveltejs/kit']).not.toBe('next'); + if (tsconfig) expect(tsconfig.extends).toBe('./.svelte-kit/tsconfig.json'); expect(source).toMatch('async: true'); expect(source).toMatch('remoteFunctions: true'); expect(source).toMatch('explicitEnvironmentVariables: true'); + // kit 2 provides `$lib` itself, so nothing is rewritten + expect(JSON.parse(pkg).imports).toBeUndefined(); // not selected -> absent expect(source).not.toMatch('forkPreloads'); - expect(source).not.toMatch('handleRenderingErrors'); } }); From 99c8e6b14b2fd6dfc4873420487942266f70d479 Mon Sep 17 00:00:00 2001 From: jycouet Date: Wed, 29 Jul 2026 11:21:20 +0200 Subject: [PATCH 04/14] test(cli): cover `kit@next` with a real install, build and check No snapshot: the point is that the project works, not what it looks like. --- packages/sv/src/cli/tests/cli.ts | 56 ++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index c2f04fccd..54220e9d3 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -46,6 +46,17 @@ describe('cli', () => { 'experimental=versions:+features:explicitEnvironmentVariables' ] }, + { + // guards the `kit@next` shape against upstream churn: no snapshot (the point is that it + // installs, builds and type-checks, not what it looks like) + projectName: 'create-experimental-next', + snapshot: false, + args: [ + '--add', + 'drizzle=database:sqlite+sqlite:libsql', + 'experimental=versions:kit+features:async,remoteFunctions' + ] + }, { projectName: '@my-org/sv', template: 'addon', @@ -57,7 +68,17 @@ describe('cli', () => { 'should create a new project with name $projectName', { timeout: 240_000 }, async (testCase) => { - const { projectName, args, template = 'minimal' } = testCase; + const { + projectName, + args, + template = 'minimal', + snapshot = true + } = testCase as { + projectName: string; + args: string[]; + template?: string; + snapshot?: boolean; + }; const testOutputPath = path.relative( monoRepoPath, @@ -102,7 +123,9 @@ describe('cli', () => { 'snapshots', projectName ); - const relativeFiles = fs.readdirSync(testOutputPath, { recursive: true }) as string[]; + const relativeFiles = snapshot + ? (fs.readdirSync(testOutputPath, { recursive: true }) as string[]) + : []; for (const relativeFile of relativeFiles) { if (!fs.statSync(path.resolve(testOutputPath, relativeFile)).isFile()) continue; if (['.svg', '.env'].some((ext) => relativeFile.endsWith(ext))) continue; @@ -169,6 +192,35 @@ describe('cli', () => { ).toBe(0); } + // `kit@next` moves fast - only a real install/build/check catches options it removed + if (projectName === 'create-experimental-next' && process.platform !== 'win32') { + const run = (cmd: string, cmdArgs: string[]) => + exec(cmd, cmdArgs, { nodeOptions: { stdio: 'pipe', cwd: testOutputPath } }); + + // without this pnpm walks up and installs the sv monorepo instead of the generated project + const install = await run('pnpm', [ + 'install', + '--no-frozen-lockfile', + '--ignore-workspace' + ]); + expect( + install.exitCode, + `pnpm install failed:\n stdout: ${install.stdout}\n stderr: ${install.stderr}` + ).toBe(0); + + const build = await run('pnpm', ['build']); + expect( + build.exitCode, + `build failed on kit@next:\n stdout: ${build.stdout}\n stderr: ${build.stderr}` + ).toBe(0); + + const check = await run('pnpm', ['check']); + expect( + check.exitCode, + `svelte-check failed on kit@next:\n stdout: ${check.stdout}\n stderr: ${check.stderr}` + ).toBe(0); + } + if (projectName === 'create-experimental') { const read = (p: string) => fs.readFileSync(path.resolve(testOutputPath, p), 'utf-8'); const envFile = read('src/env.ts'); From dfda94d918a41606e0dde4d11d98bcea7240045b Mon Sep 17 00:00:00 2001 From: "jyc.dev" Date: Thu, 30 Jul 2026 10:57:40 +0200 Subject: [PATCH 05/14] Update packages/sv/src/addons/drizzle.ts Co-authored-by: Scott Wu --- packages/sv/src/addons/drizzle.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index 51537617a..7e8f77346 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -317,6 +317,7 @@ export default defineAddon({ ); } } else { + // prior to kit 3 svelteConfig.edit({ sv, cwd }, ({ override, js }) => { override({ typescript: { From 585dd42413f1fef4090903f0546b5e39f5c312bb Mon Sep 17 00:00:00 2001 From: "jyc.dev" Date: Thu, 30 Jul 2026 10:58:44 +0200 Subject: [PATCH 06/14] Update packages/sv-utils/src/kit3.ts Co-authored-by: Scott Wu --- packages/sv-utils/src/kit3.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv-utils/src/kit3.ts b/packages/sv-utils/src/kit3.ts index 9f358cf6f..158751041 100644 --- a/packages/sv-utils/src/kit3.ts +++ b/packages/sv-utils/src/kit3.ts @@ -25,7 +25,7 @@ export const KIT3_TSCONFIG = '$app/tsconfig'; * Options `$app/tsconfig` already sets. A local copy of the same value is noise, but a different * value is a deliberate override and must stay - so only drop keys whose value matches. */ -export const KIT3_TSCONFIG_INHERITED: Record = { +export const KIT3_TSCONFIG_DEFAULT: Record = { allowImportingTsExtensions: true, allowJs: true, checkJs: true, From 5db6d874cc2a0909128883d922974cd928df5224 Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 30 Jul 2026 11:08:04 +0200 Subject: [PATCH 07/14] test(cli): cover better-auth on `kit@next`, finish the `KIT3_TSCONFIG_DEFAULT` rename better-auth has the most `#lib` imports, so it gets its own `kit@next` install/build/check case. It can't join the existing one: kit treats any `remote.js` as a remote module, including the one `jose` ships, so `remoteFunctions` breaks the build. Also rewords the changeset and updates the two remaining `KIT3_TSCONFIG_INHERITED` references. --- .changeset/nervous-poems-clap.md | 5 +++-- packages/sv-utils/api-surface.md | 4 ++-- packages/sv-utils/src/index.ts | 2 +- packages/sv/src/addons/experimental.ts | 4 ++-- packages/sv/src/cli/tests/cli.ts | 15 ++++++++++++++- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/.changeset/nervous-poems-clap.md b/.changeset/nervous-poems-clap.md index c5412684d..287f83605 100644 --- a/.changeset/nervous-poems-clap.md +++ b/.changeset/nervous-poems-clap.md @@ -5,5 +5,6 @@ fix(experimental): create manages `#lib` and the rest of the SvelteKit 3 shape Picking `@sveltejs/kit@next` now produces a project that installs, builds and type-checks: `#lib` -subpath imports instead of `$lib`, `tsconfig` extending `$app/tsconfig`, and no options SvelteKit 3 -removed. +subpath imports instead of `$lib`, a `tsconfig` extending `$app/tsconfig`, and no config options +that don't exist anymore in SvelteKit 3 (`experimental.handleRenderingErrors`, `typescript.config`). +Add-ons that used `typescript.config` to extend `include` now write to the project's `tsconfig`. diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index ef9235b68..919db761e 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -863,7 +863,7 @@ declare function libSubpathImports(libDir: string): Record; declare const KIT3_TSCONFIG = '$app/tsconfig'; -declare const KIT3_TSCONFIG_INHERITED: Record; +declare const KIT3_TSCONFIG_DEFAULT: Record; type ColorInput = string | string[]; declare const color: { addon: (str: ColorInput) => string; @@ -897,7 +897,7 @@ export { type Comments, type ConfigFileReader, KIT3_TSCONFIG, - KIT3_TSCONFIG_INHERITED, + KIT3_TSCONFIG_DEFAULT, type Package, type SvelteAst, type SvelteConfigKind, diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index 8d755f564..1c9e286bd 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -90,7 +90,7 @@ export { defineEnv } from './env.ts'; // Kit 3 specifics (version detection, `$lib` -> `#lib`, the generated tsconfig) export { KIT3_TSCONFIG, - KIT3_TSCONFIG_INHERITED, + KIT3_TSCONFIG_DEFAULT, isKit3, libSubpathImports, resolveLibPrefix diff --git a/packages/sv/src/addons/experimental.ts b/packages/sv/src/addons/experimental.ts index d6db228af..9cef4ad0e 100644 --- a/packages/sv/src/addons/experimental.ts +++ b/packages/sv/src/addons/experimental.ts @@ -1,6 +1,6 @@ import { KIT3_TSCONFIG, - KIT3_TSCONFIG_INHERITED, + KIT3_TSCONFIG_DEFAULT, fileExists, isVersionUnsupportedBelow, libSubpathImports, @@ -90,7 +90,7 @@ export default defineAddon({ data.include ??= [directory.src]; for (const [key, value] of Object.entries(data.compilerOptions ?? {})) { // a differing value is a deliberate override and stays - if (KIT3_TSCONFIG_INHERITED[key] === value) delete data.compilerOptions[key]; + if (KIT3_TSCONFIG_DEFAULT[key] === value) delete data.compilerOptions[key]; } }) ); diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index 54220e9d3..d9a8b425d 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -57,6 +57,19 @@ describe('cli', () => { 'experimental=versions:kit+features:async,remoteFunctions' ] }, + { + // better-auth exercises the most `#lib` imports, so it gets its own `kit@next` run. It can't + // join the case above: kit treats any `remote.js` as a remote module, including the one + // `jose` (a better-auth dependency) ships, which fails the build under `remoteFunctions` + projectName: 'create-experimental-next-auth', + snapshot: false, + args: [ + '--add', + 'drizzle=database:sqlite+sqlite:libsql', + 'better-auth=demo:password,github', + 'experimental=versions:kit+features:async' + ] + }, { projectName: '@my-org/sv', template: 'addon', @@ -193,7 +206,7 @@ describe('cli', () => { } // `kit@next` moves fast - only a real install/build/check catches options it removed - if (projectName === 'create-experimental-next' && process.platform !== 'win32') { + if (projectName.startsWith('create-experimental-next') && process.platform !== 'win32') { const run = (cmd: string, cmdArgs: string[]) => exec(cmd, cmdArgs, { nodeOptions: { stdio: 'pipe', cwd: testOutputPath } }); From 896505aa62f6319702b7d8c2320f554d178b9a05 Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 30 Jul 2026 11:17:08 +0200 Subject: [PATCH 08/14] test(cli): pin the `kit@next` prereleases the experimental cases run against --- packages/sv/src/cli/tests/cli.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index d9a8b425d..a6de9031d 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -9,6 +9,12 @@ const monoRepoPath = path.resolve(__dirname, '..', '..', '..', '..', '..'); const svBinPath = path.resolve(monoRepoPath, 'packages', 'sv', 'dist', 'bin.mjs'); const testOutputCliPath = path.resolve(monoRepoPath, 'packages', 'sv', '.test-output', 'cli'); +/** The `next` prereleases the `kit@next` cases run against. Bump by hand. */ +const NEXT_PINS: Record = { + '@sveltejs/kit': '3.0.0-next.13', + '@sveltejs/adapter-auto': '8.0.0-next.1' +}; + beforeAll(() => { if (fs.existsSync(testOutputCliPath)) { fs.rmSync(testOutputCliPath, { force: true, recursive: true }); @@ -210,6 +216,16 @@ describe('cli', () => { const run = (cmd: string, cmdArgs: string[]) => exec(cmd, cmdArgs, { nodeOptions: { stdio: 'pipe', cwd: testOutputPath } }); + // the add-on writes the floating `next` tag, which would make this test fail on upstream + // prereleases nobody here has seen yet - bump these deliberately instead + const pkgPath = path.resolve(testOutputPath, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + for (const [name, range] of Object.entries(NEXT_PINS)) { + if (pkg.devDependencies?.[name]) pkg.devDependencies[name] = range; + if (pkg.dependencies?.[name]) pkg.dependencies[name] = range; + } + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t') + '\n'); + // without this pnpm walks up and installs the sv monorepo instead of the generated project const install = await run('pnpm', [ 'install', From 61a9754baf20ef206adb47fc0145f151c83c4ae4 Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 30 Jul 2026 11:38:22 +0200 Subject: [PATCH 09/14] test(cli): drop the better-auth `kit@next` case --- packages/sv/src/cli/tests/cli.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index a6de9031d..7873a39c7 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -63,19 +63,6 @@ describe('cli', () => { 'experimental=versions:kit+features:async,remoteFunctions' ] }, - { - // better-auth exercises the most `#lib` imports, so it gets its own `kit@next` run. It can't - // join the case above: kit treats any `remote.js` as a remote module, including the one - // `jose` (a better-auth dependency) ships, which fails the build under `remoteFunctions` - projectName: 'create-experimental-next-auth', - snapshot: false, - args: [ - '--add', - 'drizzle=database:sqlite+sqlite:libsql', - 'better-auth=demo:password,github', - 'experimental=versions:kit+features:async' - ] - }, { projectName: '@my-org/sv', template: 'addon', @@ -212,7 +199,7 @@ describe('cli', () => { } // `kit@next` moves fast - only a real install/build/check catches options it removed - if (projectName.startsWith('create-experimental-next') && process.platform !== 'win32') { + if (projectName === 'create-experimental-next' && process.platform !== 'win32') { const run = (cmd: string, cmdArgs: string[]) => exec(cmd, cmdArgs, { nodeOptions: { stdio: 'pipe', cwd: testOutputPath } }); From 8c3d8c9dcd11e1797818310beee0139030b8189a Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 30 Jul 2026 11:40:34 +0200 Subject: [PATCH 10/14] chore: fix indentation --- packages/sv/src/addons/drizzle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index 7e8f77346..f064b0bac 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -317,7 +317,7 @@ export default defineAddon({ ); } } else { - // prior to kit 3 + // prior to kit 3 svelteConfig.edit({ sv, cwd }, ({ override, js }) => { override({ typescript: { From f643e41860700dc7eeb6b9b54c013587ad2a864d Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 30 Jul 2026 11:50:03 +0200 Subject: [PATCH 11/14] test(cli): let the generated project install a fresh prerelease --- packages/sv/src/cli/tests/cli.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index 7873a39c7..af588f055 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -213,11 +213,14 @@ describe('cli', () => { } fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t') + '\n'); - // without this pnpm walks up and installs the sv monorepo instead of the generated project const install = await run('pnpm', [ 'install', '--no-frozen-lockfile', - '--ignore-workspace' + // without this pnpm walks up and installs the sv monorepo instead of this project + '--ignore-workspace', + // ...which also loses the workspace's `minimumReleaseAgeExclude`, so a prerelease + // published in the last day would be refused + '--config.minimumReleaseAge=0' ]); expect( install.exitCode, From 92b694320c5d043bae0d89710c8d44488dded37f Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 30 Jul 2026 12:14:21 +0200 Subject: [PATCH 12/14] test(cli): track the `next` tag instead of a pinned prerelease --- packages/sv/src/cli/tests/cli.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index af588f055..9a6384c51 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -9,12 +9,6 @@ const monoRepoPath = path.resolve(__dirname, '..', '..', '..', '..', '..'); const svBinPath = path.resolve(monoRepoPath, 'packages', 'sv', 'dist', 'bin.mjs'); const testOutputCliPath = path.resolve(monoRepoPath, 'packages', 'sv', '.test-output', 'cli'); -/** The `next` prereleases the `kit@next` cases run against. Bump by hand. */ -const NEXT_PINS: Record = { - '@sveltejs/kit': '3.0.0-next.13', - '@sveltejs/adapter-auto': '8.0.0-next.1' -}; - beforeAll(() => { if (fs.existsSync(testOutputCliPath)) { fs.rmSync(testOutputCliPath, { force: true, recursive: true }); @@ -203,16 +197,6 @@ describe('cli', () => { const run = (cmd: string, cmdArgs: string[]) => exec(cmd, cmdArgs, { nodeOptions: { stdio: 'pipe', cwd: testOutputPath } }); - // the add-on writes the floating `next` tag, which would make this test fail on upstream - // prereleases nobody here has seen yet - bump these deliberately instead - const pkgPath = path.resolve(testOutputPath, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); - for (const [name, range] of Object.entries(NEXT_PINS)) { - if (pkg.devDependencies?.[name]) pkg.devDependencies[name] = range; - if (pkg.dependencies?.[name]) pkg.dependencies[name] = range; - } - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, '\t') + '\n'); - const install = await run('pnpm', [ 'install', '--no-frozen-lockfile', From c7fa72b018d4482aebfa2ec2ca858c4a0eb5cb6d Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 30 Jul 2026 12:28:28 +0200 Subject: [PATCH 13/14] chore: trim the changesets to one line each --- .changeset/nervous-poems-clap.md | 7 +------ .changeset/olive-geese-repeat.md | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.changeset/nervous-poems-clap.md b/.changeset/nervous-poems-clap.md index 287f83605..c273606b8 100644 --- a/.changeset/nervous-poems-clap.md +++ b/.changeset/nervous-poems-clap.md @@ -2,9 +2,4 @@ 'sv': patch --- -fix(experimental): create manages `#lib` and the rest of the SvelteKit 3 shape - -Picking `@sveltejs/kit@next` now produces a project that installs, builds and type-checks: `#lib` -subpath imports instead of `$lib`, a `tsconfig` extending `$app/tsconfig`, and no config options -that don't exist anymore in SvelteKit 3 (`experimental.handleRenderingErrors`, `typescript.config`). -Add-ons that used `typescript.config` to extend `include` now write to the project's `tsconfig`. +fix(experimental): `@sveltejs/kit@next` projects now install, build and type-check diff --git a/.changeset/olive-geese-repeat.md b/.changeset/olive-geese-repeat.md index 3eb452586..ed6a26d2d 100644 --- a/.changeset/olive-geese-repeat.md +++ b/.changeset/olive-geese-repeat.md @@ -2,4 +2,4 @@ '@sveltejs/sv-utils': patch --- -feat: add SvelteKit 3 helpers - `isKit3`, `resolveLibPrefix`, `libSubpathImports` and the generated `$app/tsconfig` constants, so add-ons emit the right shape without hardcoding it +feat: add SvelteKit 3 helpers From 99332a554cc75ce549e1e315df3a61586741f3c7 Mon Sep 17 00:00:00 2001 From: jycouet Date: Thu, 30 Jul 2026 12:29:58 +0200 Subject: [PATCH 14/14] chore: changeset wording --- .changeset/nervous-poems-clap.md | 2 +- .changeset/olive-geese-repeat.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/nervous-poems-clap.md b/.changeset/nervous-poems-clap.md index c273606b8..ae2c939dc 100644 --- a/.changeset/nervous-poems-clap.md +++ b/.changeset/nervous-poems-clap.md @@ -2,4 +2,4 @@ 'sv': patch --- -fix(experimental): `@sveltejs/kit@next` projects now install, build and type-check +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 index ed6a26d2d..3f9762e6c 100644 --- a/.changeset/olive-geese-repeat.md +++ b/.changeset/olive-geese-repeat.md @@ -2,4 +2,4 @@ '@sveltejs/sv-utils': patch --- -feat: add SvelteKit 3 helpers +feat: add SvelteKit 3 helpers - `isKit3`, `resolveLibPrefix`, `libSubpathImports`