From 6fdd719c9950ad5782f1e5ae47387f30bf24536e Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:23:26 +0200 Subject: [PATCH 01/25] feat(sv-utils): add resolveEnvMode env-mode detection --- packages/sv-utils/src/env.ts | 18 ++++++++++++++++++ packages/sv-utils/src/tests/env.ts | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 packages/sv-utils/src/env.ts create mode 100644 packages/sv-utils/src/tests/env.ts diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts new file mode 100644 index 000000000..358633189 --- /dev/null +++ b/packages/sv-utils/src/env.ts @@ -0,0 +1,18 @@ +import { coerceVersion } from './semver.ts'; + +export type EnvMode = 'declared' | 'legacy'; + +export function resolveEnvMode({ + kitRange, + explicitEnvFlag +}: { + kitRange: string | undefined; + 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'; + return 'legacy'; +} diff --git a/packages/sv-utils/src/tests/env.ts b/packages/sv-utils/src/tests/env.ts new file mode 100644 index 000000000..89f759e05 --- /dev/null +++ b/packages/sv-utils/src/tests/env.ts @@ -0,0 +1,20 @@ +import { describe, expect, test } from 'vitest'; +import { resolveEnvMode } from '../env.ts'; + +describe('resolveEnvMode', () => { + test('no kit -> legacy', () => { + expect(resolveEnvMode({ kitRange: undefined, explicitEnvFlag: false })).toBe('legacy'); + }); + test('kit 2 default -> legacy', () => { + expect(resolveEnvMode({ kitRange: '^2.0.0', explicitEnvFlag: false })).toBe('legacy'); + }); + test('kit 2 + explicit flag -> declared', () => { + expect(resolveEnvMode({ kitRange: '^2.0.0', explicitEnvFlag: true })).toBe('declared'); + }); + test('kit 3 range -> declared', () => { + expect(resolveEnvMode({ kitRange: '^3.0.0-next.1', explicitEnvFlag: false })).toBe('declared'); + }); + test('next dist-tag -> declared', () => { + expect(resolveEnvMode({ kitRange: 'next', explicitEnvFlag: false })).toBe('declared'); + }); +}); From 569c14d14fb2cd28de06355857a2a8f37c6f4a5f Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:25:42 +0200 Subject: [PATCH 02/25] feat(sv-utils): defineEnv.reference (import + accessor string) --- packages/sv-utils/src/env.ts | 53 ++++++++++++++++++++++++++++++ packages/sv-utils/src/tests/env.ts | 49 +++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 358633189..33d190852 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -16,3 +16,56 @@ export function resolveEnvMode({ if (major === 2 && explicitEnvFlag) return 'declared'; return 'legacy'; } + +import type { AstTypes } from './tooling/index.ts'; +import * as jsNs from './tooling/js/index.ts'; + +export type EnvScope = 'private' | 'public'; + +export type EnvVarSpec = { + name: string; + description?: string; + public?: boolean; + static?: boolean; +}; + +export type DefineEnvContext = { + sv: { file: (path: string, edit: (content: string) => string | false) => void }; + cwd: string; + language: 'ts' | 'js'; + dependencyVersion: (pkg: string) => string | undefined; +}; + +export type ReferenceOpts = { name: string; scope?: EnvScope; static?: boolean }; + +export type DefineEnv = { + mode: EnvMode; + declare: (spec: EnvVarSpec) => void; + reference: (ast: AstTypes.Program, js: typeof jsNs, opts: ReferenceOpts) => string; +}; + +export function defineEnv(ctx: DefineEnvContext): DefineEnv { + const mode = resolveEnvMode({ kitRange: ctx.dependencyVersion('@sveltejs/kit'), explicitEnvFlag: false }); + const declared = new Map(); + + return { + mode, + declare() {}, + reference(ast, js, opts) { + const spec = declared.get(opts.name); + const scope: EnvScope = opts.scope ?? (spec?.public ? 'public' : 'private'); + const isStatic = opts.static ?? spec?.static ?? false; + + if (mode === 'declared') { + js.imports.addNamed(ast, { from: `$app/env/${scope}`, imports: [opts.name] }); + return opts.name; + } + if (isStatic) { + js.imports.addNamed(ast, { from: `$env/static/${scope}`, imports: [opts.name] }); + return opts.name; + } + js.imports.addNamed(ast, { from: `$env/dynamic/${scope}`, imports: ['env'] }); + return `env.${opts.name}`; + } + }; +} diff --git a/packages/sv-utils/src/tests/env.ts b/packages/sv-utils/src/tests/env.ts index 89f759e05..609a0a420 100644 --- a/packages/sv-utils/src/tests/env.ts +++ b/packages/sv-utils/src/tests/env.ts @@ -18,3 +18,52 @@ describe('resolveEnvMode', () => { expect(resolveEnvMode({ kitRange: 'next', explicitEnvFlag: false })).toBe('declared'); }); }); + +import { defineEnv } from '../env.ts'; +import * as js from '../tooling/js/index.ts'; +import { parseScript } from '../tooling/parsers.ts'; + +/** A fake `sv` capturing file writes in-memory. */ +function fakeSv(files: Record = {}) { + return { + files, + sv: { + file(path: string, edit: (content: string) => string | false) { + const out = edit(files[path] ?? ''); + if (out !== false) files[path] = out; + } + } + }; +} + +function envFor(kitRange: string) { + const { sv, files } = fakeSv(); + const env = defineEnv({ sv, cwd: '/proj', language: 'ts', dependencyVersion: () => kitRange }); + return { env, files }; +} + +describe('defineEnv.reference', () => { + test('declared: named import from $app/env/private + bare accessor', () => { + const { env } = envFor('next'); + const { ast, generateCode } = parseScript(''); + const access = env.reference(ast, js, { name: 'DATABASE_URL' }); + expect(access).toBe('DATABASE_URL'); + expect(generateCode()).toContain("import { DATABASE_URL } from '$app/env/private';"); + }); + + test('legacy dynamic: env import + env.DATABASE_URL accessor', () => { + const { env } = envFor('^2.0.0'); + const { ast, generateCode } = parseScript(''); + const access = env.reference(ast, js, { name: 'DATABASE_URL' }); + expect(access).toBe('env.DATABASE_URL'); + expect(generateCode()).toContain("import { env } from '$env/dynamic/private';"); + }); + + test('legacy static: named import from $env/static/public + bare accessor', () => { + const { env } = envFor('^2.0.0'); + const { ast, generateCode } = parseScript(''); + const access = env.reference(ast, js, { name: 'PUBLIC_X', scope: 'public', static: true }); + expect(access).toBe('PUBLIC_X'); + expect(generateCode()).toContain("import { PUBLIC_X } from '$env/static/public';"); + }); +}); From 5e02f9c10df8f3e1fc9d888811ba1a6d7b246411 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:28:51 +0200 Subject: [PATCH 03/25] feat(sv-utils): defineEnv.declare get-or-create src/env.ts --- packages/sv-utils/src/env.ts | 83 ++++++++++++++++++++++++++++-- packages/sv-utils/src/tests/env.ts | 53 +++++++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 33d190852..ec36c6034 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -1,4 +1,7 @@ import { coerceVersion } from './semver.ts'; +import { svelteConfig, type ConfigFileReader } from './svelte-config.ts'; +import type { AstTypes } from './tooling/index.ts'; +import { transforms } from './tooling/transforms.ts'; export type EnvMode = 'declared' | 'legacy'; @@ -17,7 +20,6 @@ export function resolveEnvMode({ return 'legacy'; } -import type { AstTypes } from './tooling/index.ts'; import * as jsNs from './tooling/js/index.ts'; export type EnvScope = 'private' | 'public'; @@ -44,13 +46,88 @@ export type DefineEnv = { reference: (ast: AstTypes.Program, js: typeof jsNs, opts: ReferenceOpts) => string; }; +function findProp( + obj: AstTypes.ObjectExpression, + name: string +): AstTypes.Property | undefined { + return obj.properties.find( + (p): p is AstTypes.Property => + p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === name + ); +} + +export function readExplicitEnvFlag(source: string | ConfigFileReader): boolean { + let objs; + try { + objs = svelteConfig.read(source); + } catch { + return false; + } + if (!objs) return false; + const experimental = findProp(objs.kit, 'experimental'); + if (!experimental || experimental.value.type !== 'ObjectExpression') return false; + const flag = findProp(experimental.value, 'explicitEnvironmentVariables'); + return !!flag && flag.value.type === 'Literal' && flag.value.value === true; +} + +function getOrCreateVariablesObject( + ast: AstTypes.Program, + js: typeof jsNs +): AstTypes.ObjectExpression { + for (const node of ast.body) { + if (node.type !== 'ExportNamedDeclaration') continue; + const decl = node.declaration; + if (decl?.type !== 'VariableDeclaration') continue; + const d = decl.declarations[0]; + if ( + d?.type === 'VariableDeclarator' && + d.id.type === 'Identifier' && + d.id.name === 'variables' && + d.init?.type === 'CallExpression' && + d.init.callee.type === 'Identifier' && + d.init.callee.name === 'defineEnvVars' && + d.init.arguments[0]?.type === 'ObjectExpression' + ) { + return d.init.arguments[0] as AstTypes.ObjectExpression; + } + } + const stmt = js.common.parseStatement( + 'export const variables = defineEnvVars({});' + ) as AstTypes.ExportNamedDeclaration; + ast.body.push(stmt); + const decl = stmt.declaration as AstTypes.VariableDeclaration; + const call = decl.declarations[0].init as AstTypes.CallExpression; + return call.arguments[0] as AstTypes.ObjectExpression; +} + export function defineEnv(ctx: DefineEnvContext): DefineEnv { - const mode = resolveEnvMode({ kitRange: ctx.dependencyVersion('@sveltejs/kit'), explicitEnvFlag: false }); + const kitRange = ctx.dependencyVersion('@sveltejs/kit'); + const explicitEnvFlag = readExplicitEnvFlag(ctx.cwd); + const mode = resolveEnvMode({ kitRange, explicitEnvFlag }); const declared = new Map(); return { mode, - declare() {}, + declare(spec) { + declared.set(spec.name, spec); + if (mode !== 'declared') return; + const envPath = `src/env.${ctx.language}`; + ctx.sv.file(envPath, (content) => + transforms.script(({ ast, js }) => { + js.imports.addNamed(ast, { from: '@sveltejs/kit/hooks', imports: ['defineEnvVars'] }); + const variables = getOrCreateVariablesObject(ast, js); + const entry = js.object.property(variables, { + name: spec.name, + fallback: js.object.create({}) + }) as AstTypes.ObjectExpression; + const props: Record = {}; + if (spec.description) props.description = js.common.createLiteral(spec.description); + if (spec.public) props.public = js.common.createLiteral(true); + if (spec.static) props.static = js.common.createLiteral(true); + if (Object.keys(props).length) js.object.overrideProperties(entry, props); + })(content) + ); + }, reference(ast, js, opts) { const spec = declared.get(opts.name); const scope: EnvScope = opts.scope ?? (spec?.public ? 'public' : 'private'); diff --git a/packages/sv-utils/src/tests/env.ts b/packages/sv-utils/src/tests/env.ts index 609a0a420..9fc898eb8 100644 --- a/packages/sv-utils/src/tests/env.ts +++ b/packages/sv-utils/src/tests/env.ts @@ -67,3 +67,56 @@ describe('defineEnv.reference', () => { expect(generateCode()).toContain("import { PUBLIC_X } from '$env/static/public';"); }); }); + +import { readExplicitEnvFlag } from '../env.ts'; + + +const reader = (files: Record) => (path: string) => files[path] ?? null; + +describe('readExplicitEnvFlag', () => { + test('true when set in svelte.config', () => { + const files = { + 'svelte.config.js': + "export default { kit: { experimental: { explicitEnvironmentVariables: true } } };\n" + }; + expect(readExplicitEnvFlag(reader(files))).toBe(true); + }); + test('false when absent', () => { + const files = { 'svelte.config.js': 'export default { kit: {} };\n' }; + expect(readExplicitEnvFlag(reader(files))).toBe(false); + }); + test('false when no config', () => { + expect(readExplicitEnvFlag(reader({}))).toBe(false); + }); +}); + +describe('defineEnv.declare', () => { + test('declared: creates src/env.ts with defineEnvVars + description', () => { + const { sv, files } = fakeSv(); + const env = defineEnv({ sv, cwd: '/proj', language: 'ts', dependencyVersion: () => 'next' }); + env.declare({ name: 'DATABASE_URL', description: 'db url' }); + const out = files['src/env.ts']; + expect(out).toContain("import { defineEnvVars } from '@sveltejs/kit/hooks';"); + expect(out).toContain('export const variables = defineEnvVars({'); + expect(out).toContain('DATABASE_URL'); + expect(out).toContain("description: 'db url'"); + }); + + test('declared: second declare merges into the same variables object', () => { + const { sv, files } = fakeSv(); + const env = defineEnv({ sv, cwd: '/proj', language: 'ts', dependencyVersion: () => 'next' }); + env.declare({ name: 'DATABASE_URL' }); + env.declare({ name: 'DATABASE_AUTH_TOKEN' }); + const out = files['src/env.ts']; + expect(out).toContain('DATABASE_URL'); + expect(out).toContain('DATABASE_AUTH_TOKEN'); + expect(out.match(/defineEnvVars/g)?.length).toBe(2); // import + one call, no duplicate object + }); + + test('legacy: declare is a no-op (no env file written)', () => { + const { sv, files } = fakeSv(); + const env = defineEnv({ sv, cwd: '/proj', language: 'ts', dependencyVersion: () => '^2.0.0' }); + env.declare({ name: 'DATABASE_URL' }); + expect(files['src/env.ts']).toBeUndefined(); + }); +}); From e7d50627e37dd738d21b80ba2b622d5878a032b6 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:29:33 +0200 Subject: [PATCH 04/25] style(sv-utils): hoist js import to top of env.ts --- packages/sv-utils/src/env.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index ec36c6034..1f4b6a43c 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -1,6 +1,7 @@ import { coerceVersion } from './semver.ts'; import { svelteConfig, type ConfigFileReader } from './svelte-config.ts'; import type { AstTypes } from './tooling/index.ts'; +import * as jsNs from './tooling/js/index.ts'; import { transforms } from './tooling/transforms.ts'; export type EnvMode = 'declared' | 'legacy'; @@ -20,8 +21,6 @@ export function resolveEnvMode({ return 'legacy'; } -import * as jsNs from './tooling/js/index.ts'; - export type EnvScope = 'private' | 'public'; export type EnvVarSpec = { From fb13bd864c6e64adee3651f425866e87f7fa5f69 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:30:20 +0200 Subject: [PATCH 05/25] feat(sv-utils): export defineEnv from index --- packages/sv-utils/src/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index b43817a8a..1f9437ec0 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -84,6 +84,19 @@ export { type SvelteConfigObjects } from './svelte-config.ts'; +// Env access (abstracts over legacy `$env/dynamic/*` vs declared `$app/env/*` + `src/env.ts`) +export { + defineEnv, + resolveEnvMode, + readExplicitEnvFlag, + type DefineEnv, + type DefineEnvContext, + type EnvMode, + type EnvScope, + type EnvVarSpec, + type ReferenceOpts +} from './env.ts'; + // Terminal styling export { color } from './color.ts'; From b385cbf1845c3c2b6aba160cd156e08345efa7c8 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:30:46 +0200 Subject: [PATCH 06/25] fix(sv-utils): correct parseStatement cast in env.ts --- packages/sv-utils/src/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 1f4b6a43c..9671967b1 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -92,7 +92,7 @@ function getOrCreateVariablesObject( } const stmt = js.common.parseStatement( 'export const variables = defineEnvVars({});' - ) as AstTypes.ExportNamedDeclaration; + ) as unknown as AstTypes.ExportNamedDeclaration; ast.body.push(stmt); const decl = stmt.declaration as AstTypes.VariableDeclaration; const call = decl.declarations[0].init as AstTypes.CallExpression; From 7eab383346e0126b08a1ca640e22ffda102acd97 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:33:42 +0200 Subject: [PATCH 07/25] refactor(drizzle): emit env access via defineEnv --- packages/sv/src/addons/drizzle.ts | 35 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index fbaf5030d..fe3f0e812 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -7,7 +7,8 @@ import { resolveCommandArray, fileExists, createPrinter, - svelteConfig + svelteConfig, + defineEnv } from '@sveltejs/sv-utils'; import crypto from 'node:crypto'; import fs from 'node:fs'; @@ -88,6 +89,7 @@ export default defineAddon({ setup: ({ isKit, unsupported, runsAfter }) => { runsAfter('prettier'); runsAfter('sveltekitAdapter'); + runsAfter('experimental'); if (!isKit) return unsupported('Requires SvelteKit'); }, @@ -362,6 +364,14 @@ export default defineAddon({ }) ); + const env = defineEnv({ sv, cwd, language, dependencyVersion }); + if (options.database !== 'd1') { + env.declare({ name: 'DATABASE_URL', description: 'Connection string for the database' }); + if (options.sqlite === 'turso') { + env.declare({ name: 'DATABASE_AUTH_TOKEN', description: 'Auth token for Turso' }); + } + } + sv.file( paths.database, transforms.script(({ ast, js }) => { @@ -378,14 +388,12 @@ export default defineAddon({ return; } - js.imports.addNamed(ast, { from: '$env/dynamic/private', imports: ['env'] }); + const dbUrl = env.reference(ast, js, { name: 'DATABASE_URL' }); js.imports.addNamespace(ast, { from: './schema', as: 'schema' }); - // env var checks - const dbURLCheck = js.common.parseStatement( - "if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set');" + ast.body.push( + js.common.parseStatement(`if (!${dbUrl}) throw new Error('DATABASE_URL is not set');`) ); - ast.body.push(dbURLCheck); let clientExpression; // SQLite @@ -396,7 +404,7 @@ export default defineAddon({ imports: ['drizzle'] }); - clientExpression = js.common.parseExpression('new Database(env.DATABASE_URL)'); + clientExpression = js.common.parseExpression(`new Database(${dbUrl})`); } if (options.sqlite === 'libsql' || options.sqlite === 'turso') { js.imports.addNamed(ast, { @@ -409,16 +417,17 @@ export default defineAddon({ }); if (options.sqlite === 'turso') { + const dbToken = env.reference(ast, js, { name: 'DATABASE_AUTH_TOKEN' }); ast.body.push( js.common.parseStatement( - "if (!env.DATABASE_AUTH_TOKEN) throw new Error('DATABASE_AUTH_TOKEN is not set');" + `if (!${dbToken}) throw new Error('DATABASE_AUTH_TOKEN is not set');` ) ); clientExpression = js.common.parseExpression( - 'createClient({ url: env.DATABASE_URL, authToken: env.DATABASE_AUTH_TOKEN })' + `createClient({ url: ${dbUrl}, authToken: ${dbToken} })` ); } else { - clientExpression = js.common.parseExpression('createClient({ url: env.DATABASE_URL })'); + clientExpression = js.common.parseExpression(`createClient({ url: ${dbUrl} })`); } } // MySQL @@ -429,7 +438,7 @@ export default defineAddon({ imports: ['drizzle'] }); - clientExpression = js.common.parseExpression('mysql.createPool(env.DATABASE_URL)'); + clientExpression = js.common.parseExpression(`mysql.createPool(${dbUrl})`); } // PostgreSQL if (options.postgresql === 'neon') { @@ -442,7 +451,7 @@ export default defineAddon({ imports: ['drizzle'] }); - clientExpression = js.common.parseExpression('neon(env.DATABASE_URL)'); + clientExpression = js.common.parseExpression(`neon(${dbUrl})`); } if (options.postgresql === 'postgres.js') { js.imports.addDefault(ast, { from: 'postgres', as: 'postgres' }); @@ -451,7 +460,7 @@ export default defineAddon({ imports: ['drizzle'] }); - clientExpression = js.common.parseExpression('postgres(env.DATABASE_URL)'); + clientExpression = js.common.parseExpression(`postgres(${dbUrl})`); } if (!clientExpression) throw new Error('unreachable state...'); From 5bc88b2035a283de583cbd48f27179faec5af823 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:36:15 +0200 Subject: [PATCH 08/25] refactor(better-auth): emit env access via defineEnv --- packages/sv/src/addons/better-auth.ts | 34 ++++++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 82cea8e25..487fa81a5 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -8,7 +8,8 @@ import { resolveCommandArray, createPrinter, type TransformFn, - coerceVersion + coerceVersion, + defineEnv } from '@sveltejs/sv-utils'; import crypto from 'node:crypto'; import { defineAddon, defineAddonOptions } from '../core/config.ts'; @@ -40,8 +41,9 @@ export default defineAddon({ runsAfter('sveltekitAdapter'); runsAfter('tailwindcss'); + runsAfter('experimental'); }, - run: ({ sv, language, options, directory, dependencyVersion, file }) => { + run: ({ sv, cwd, language, options, directory, dependencyVersion, file }) => { const svelteVersion = dependencyVersion('svelte'); const svelte5 = !!svelteVersion && coerceVersion(svelteVersion).major === 5; const [ts, s5] = createPrinter(language === 'ts', svelte5); @@ -94,12 +96,19 @@ export default defineAddon({ sv.file('.env', generateEnv(demoGithub, false)); sv.file('.env.example', generateEnv(demoGithub, true)); + const env = defineEnv({ sv, cwd, language, dependencyVersion }); + env.declare({ name: 'ORIGIN', description: 'App origin / base URL' }); + env.declare({ name: 'BETTER_AUTH_SECRET', description: 'Secret used by better-auth' }); + if (demoGithub) { + env.declare({ name: 'GITHUB_CLIENT_ID' }); + env.declare({ name: 'GITHUB_CLIENT_SECRET' }); + } + 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: '$app/server', imports: ['getRequestEvent'] }); - js.imports.addNamed(ast, { from: '$env/dynamic/private', imports: ['env'] }); js.imports.addNamed(ast, { from: 'better-auth/svelte-kit', imports: ['sveltekitCookies'] @@ -110,6 +119,13 @@ export default defineAddon({ }); js.imports.addNamed(ast, { from: 'better-auth/minimal', imports: ['betterAuth'] }); + const origin = env.reference(ast, js, { name: 'ORIGIN' }); + const secret = env.reference(ast, js, { name: 'BETTER_AUTH_SECRET' }); + const githubId = demoGithub ? env.reference(ast, js, { name: 'GITHUB_CLIENT_ID' }) : ''; + const githubSecret = demoGithub + ? env.reference(ast, js, { name: 'GITHUB_CLIENT_SECRET' }) + : ''; + const dialectMap: Record = { mysql: 'mysql', postgresql: 'pg', @@ -122,8 +138,8 @@ export default defineAddon({ ? ` socialProviders: { github: { - clientId: env.GITHUB_CLIENT_ID, - clientSecret: env.GITHUB_CLIENT_SECRET, + clientId: ${githubId}, + clientSecret: ${githubSecret}, }, },` : ''; @@ -132,8 +148,8 @@ export default defineAddon({ if (d1) { authConfig = dedent` const authConfig = { - baseURL: env.ORIGIN, - secret: env.BETTER_AUTH_SECRET, + baseURL: ${origin}, + secret: ${secret}, emailAndPassword: { enabled: true },${githubProvider} @@ -157,8 +173,8 @@ export default defineAddon({ } else { authConfig = dedent` export const auth = betterAuth({ - baseURL: env.ORIGIN, - secret: env.BETTER_AUTH_SECRET, + baseURL: ${origin}, + secret: ${secret}, database: drizzleAdapter(db, { provider: '${provider}' }), emailAndPassword: { enabled: true From 436a2c3524770b5b34627172d3ff2d4a34dc5647 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:46:44 +0200 Subject: [PATCH 09/25] test(cli): declared env on kit 2 + explicitEnvironmentVariables --- packages/sv/src/cli/tests/cli.ts | 20 +++++ .../create-experimental/.env.example | 13 ++++ .../snapshots/create-experimental/.gitignore | 25 ++++++ .../snapshots/create-experimental/.npmrc | 1 + .../.vscode/extensions.json | 3 + .../snapshots/create-experimental/README.md | 42 ++++++++++ .../create-experimental/drizzle.config.ts | 11 +++ .../create-experimental/package.json | 35 +++++++++ .../create-experimental/pnpm-workspace.yaml | 3 + .../create-experimental/src/app.d.ts | 22 ++++++ .../create-experimental/src/app.html | 12 +++ .../snapshots/create-experimental/src/env.ts | 9 +++ .../create-experimental/src/hooks.server.ts | 17 ++++ .../create-experimental/src/lib/index.ts | 1 + .../src/lib/server/auth.ts | 28 +++++++ .../src/lib/server/db/auth.schema.ts | 1 + .../src/lib/server/db/index.ts | 10 +++ .../src/lib/server/db/schema.ts | 9 +++ .../src/routes/+layout.svelte | 11 +++ .../src/routes/+page.svelte | 2 + .../src/routes/demo/+page.svelte | 5 ++ .../routes/demo/better-auth/+page.server.ts | 20 +++++ .../src/routes/demo/better-auth/+page.svelte | 12 +++ .../demo/better-auth/login/+page.server.ts | 78 +++++++++++++++++++ .../demo/better-auth/login/+page.svelte | 33 ++++++++ .../create-experimental/static/robots.txt | 3 + .../create-experimental/tsconfig.json | 18 +++++ .../create-experimental/vite.config.ts | 22 ++++++ .../create-experimental/wrangler.jsonc | 15 ++++ 29 files changed, 481 insertions(+) create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/.env.example create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/.gitignore create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/.npmrc create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/.vscode/extensions.json create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/README.md create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/drizzle.config.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/package.json create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/pnpm-workspace.yaml create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/app.d.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/app.html create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/env.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/hooks.server.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/index.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/auth.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/auth.schema.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/index.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/schema.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+layout.svelte create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+page.svelte create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/+page.svelte create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/+page.server.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/+page.svelte create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.server.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.svelte create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/static/robots.txt create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/tsconfig.json create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/vite.config.ts create mode 100644 packages/sv/src/cli/tests/snapshots/create-experimental/wrangler.jsonc diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index 8908fd040..e30166e4f 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -36,6 +36,16 @@ describe('cli', () => { // 'storybook' // No storybook addon during tests! ] }, + { + projectName: 'create-experimental', + args: [ + '--add', + 'sveltekit-adapter=adapter:cloudflare+cfTarget:workers', + 'drizzle=database:sqlite+sqlite:libsql', + 'better-auth=demo:password,github', + 'experimental=versions:+features:explicitEnvironmentVariables' + ] + }, { projectName: '@my-org/sv', template: 'addon', @@ -151,6 +161,16 @@ describe('cli', () => { ).toBe(0); } + if (projectName === 'create-experimental') { + const read = (p: string) => fs.readFileSync(path.resolve(testOutputPath, p), 'utf-8'); + const envFile = read('src/env.ts'); + expect(envFile).toContain('defineEnvVars'); + expect(envFile).toContain('DATABASE_URL'); + expect(read('src/lib/server/db/index.ts')).toContain("from '$app/env/private'"); + expect(read('src/lib/server/auth.ts')).toContain("from '$app/env/private'"); + expect(read('src/lib/server/db/index.ts')).not.toContain('$env/dynamic/private'); + } + if (template === 'addon') { // replace sv and sv-utils versions in package.json for tests const packageJsonPath = path.resolve(testOutputPath, 'package.json'); diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/.env.example b/packages/sv/src/cli/tests/snapshots/create-experimental/.env.example new file mode 100644 index 000000000..34a24e9fe --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/.env.example @@ -0,0 +1,13 @@ +# Drizzle +DATABASE_URL=file:local.db + +ORIGIN="" + +# Better Auth +# For production use 32 characters and generated with high entropy +# https://www.better-auth.com/docs/installation +BETTER_AUTH_SECRET="" +# GitHub OAuth +# https://www.better-auth.com/docs/authentication/github +GITHUB_CLIENT_ID="" +GITHUB_CLIENT_SECRET="" diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/.gitignore b/packages/sv/src/cli/tests/snapshots/create-experimental/.gitignore new file mode 100644 index 000000000..23be53486 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/.gitignore @@ -0,0 +1,25 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +# SQLite +*.db diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/.npmrc b/packages/sv/src/cli/tests/snapshots/create-experimental/.npmrc new file mode 100644 index 000000000..b6f27f135 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/.vscode/extensions.json b/packages/sv/src/cli/tests/snapshots/create-experimental/.vscode/extensions.json new file mode 100644 index 000000000..28d1e67ad --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/README.md b/packages/sv/src/cli/tests/snapshots/create-experimental/README.md new file mode 100644 index 000000000..3f51a1b4d --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/README.md @@ -0,0 +1,42 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project +npx sv create my-app +``` + +To recreate this project with the same configuration: + +```sh +# recreate this project +npx sv@0.0.0 create --template minimal --types ts --add sveltekit-adapter="adapter:cloudflare+cfTarget:workers" drizzle="database:sqlite+sqlite:libsql" better-auth="demo:password,github" experimental="versions:none+features:explicitEnvironmentVariables" --no-install packages/sv/.test-output/cli/create-experimental +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/drizzle.config.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/drizzle.config.ts new file mode 100644 index 000000000..317f310ed --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/drizzle.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'drizzle-kit'; + +if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); + +export default defineConfig({ + schema: './src/lib/server/db/schema.ts', + dialect: 'sqlite', + dbCredentials: { url: process.env.DATABASE_URL }, + verbose: true, + strict: true +}); diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/package.json b/packages/sv/src/cli/tests/snapshots/create-experimental/package.json new file mode 100644 index 000000000..c0190161e --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/package.json @@ -0,0 +1,35 @@ +{ + "name": "create-experimental", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "wrangler types --check && vite build", + "preview": "wrangler dev .svelte-kit/cloudflare/_worker.js --port 4173", + "prepare": "svelte-kit sync || echo ''", + "check": "wrangler types --check && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "gen": "wrangler types", + "db:push": "drizzle-kit push", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio", + "auth:schema": "better-auth generate --config src/lib/server/auth.ts --output src/lib/server/db/auth.schema.ts --yes" + }, + "devDependencies": { + "@better-auth/cli": "~1.4.21", + "@libsql/client": "^0.17.2", + "@sveltejs/adapter-cloudflare": "^7.2.8", + "@sveltejs/kit": "^2.62.0", + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "better-auth": "~1.4.21", + "drizzle-kit": "^0.31.10", + "drizzle-orm": "^0.45.2", + "svelte": "^5.56.1", + "svelte-check": "^4.4.6", + "typescript": "^6.0.2", + "vite": "^8.0.7", + "wrangler": "^4.81.0" + } +} diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/pnpm-workspace.yaml b/packages/sv/src/cli/tests/snapshots/create-experimental/pnpm-workspace.yaml new file mode 100644 index 000000000..8dbdc836d --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +onlyBuiltDependencies: + - workerd + - sharp diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/app.d.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/app.d.ts new file mode 100644 index 000000000..16910ac28 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/app.d.ts @@ -0,0 +1,22 @@ +import type { User, Session } from 'better-auth'; + +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + interface Platform { + env: Env; + ctx: ExecutionContext; + caches: CacheStorage; + cf?: IncomingRequestCfProperties + } + + interface Locals { user?: User; session?: Session } + + // interface Error {} + // interface PageData {} + // interface PageState {} + } +} + +export {}; diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/app.html b/packages/sv/src/cli/tests/snapshots/create-experimental/src/app.html new file mode 100644 index 000000000..6a2bb5822 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/env.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/env.ts new file mode 100644 index 000000000..d8de65dcf --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/env.ts @@ -0,0 +1,9 @@ +import { defineEnvVars } from '@sveltejs/kit/hooks'; + +export const variables = defineEnvVars({ + DATABASE_URL: { description: 'Connection string for the database' }, + ORIGIN: { description: 'App origin / base URL' }, + BETTER_AUTH_SECRET: { description: 'Secret used by better-auth' }, + GITHUB_CLIENT_ID: {}, + GITHUB_CLIENT_SECRET: {} +}); diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/hooks.server.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/hooks.server.ts new file mode 100644 index 000000000..22e93f9c4 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/hooks.server.ts @@ -0,0 +1,17 @@ +import type { Handle } from '@sveltejs/kit'; +import { building } from '$app/environment'; +import { auth } from '$lib/server/auth'; +import { svelteKitHandler } from 'better-auth/svelte-kit'; + +const handleBetterAuth: Handle = async ({ event, resolve }) => { + const session = await auth.api.getSession({ headers: event.request.headers }); + + if (session) { + event.locals.session = session.session; + event.locals.user = session.user; + } + + return svelteKitHandler({ event, resolve, auth, building }); +}; + +export const handle: Handle = handleBetterAuth; diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/index.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/index.ts new file mode 100644 index 000000000..856f2b6c3 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/auth.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/auth.ts new file mode 100644 index 000000000..c9857fab5 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/auth.ts @@ -0,0 +1,28 @@ +import { + ORIGIN, + BETTER_AUTH_SECRET, + GITHUB_CLIENT_ID, + GITHUB_CLIENT_SECRET +} from '$app/env/private'; + +import { betterAuth } from 'better-auth/minimal'; +import { drizzleAdapter } from 'better-auth/adapters/drizzle'; +import { sveltekitCookies } from 'better-auth/svelte-kit'; +import { getRequestEvent } from '$app/server'; +import { db } from '$lib/server/db'; + +export const auth = betterAuth({ + baseURL: ORIGIN, + secret: BETTER_AUTH_SECRET, + database: drizzleAdapter(db, { provider: 'sqlite' }), + emailAndPassword: { enabled: true }, + socialProviders: { + github: { + clientId: GITHUB_CLIENT_ID, + clientSecret: GITHUB_CLIENT_SECRET + } + }, + plugins: [ + sveltekitCookies(getRequestEvent) // make sure this is the last plugin in the array + ] +}); diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/auth.schema.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/auth.schema.ts new file mode 100644 index 000000000..952b9b328 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/auth.schema.ts @@ -0,0 +1 @@ +// If you see this file, you have not run the auth:schema script yet, but you should! diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/index.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/index.ts new file mode 100644 index 000000000..2f0cd7aa7 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/index.ts @@ -0,0 +1,10 @@ +import { drizzle } from 'drizzle-orm/libsql'; +import { createClient } from '@libsql/client'; +import * as schema from './schema'; +import { DATABASE_URL } from '$app/env/private'; + +if (!DATABASE_URL) throw new Error('DATABASE_URL is not set'); + +const client = createClient({ url: DATABASE_URL }); + +export const db = drizzle(client, { schema }); diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/schema.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/schema.ts new file mode 100644 index 000000000..35de29be6 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/lib/server/db/schema.ts @@ -0,0 +1,9 @@ +import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const task = sqliteTable('task', { + id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + title: text('title').notNull(), + priority: integer('priority').notNull().default(1) +}); + +export * from './auth.schema'; 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 new file mode 100644 index 000000000..9cebde545 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+layout.svelte @@ -0,0 +1,11 @@ + + + + + + +{@render children()} diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+page.svelte b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+page.svelte new file mode 100644 index 000000000..cc88df0ea --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/+page.svelte @@ -0,0 +1,2 @@ +

Welcome to SvelteKit

+

Visit svelte.dev/docs/kit to read the documentation

diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/+page.svelte b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/+page.svelte new file mode 100644 index 000000000..948d26f74 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/+page.svelte @@ -0,0 +1,5 @@ + + +better-auth diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/+page.server.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/+page.server.ts new file mode 100644 index 000000000..92a1f2ec3 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/+page.server.ts @@ -0,0 +1,20 @@ +import { redirect } from '@sveltejs/kit'; +import type { Actions } from './$types'; +import type { PageServerLoad } from './$types'; +import { auth } from '$lib/server/auth'; + +export const load: PageServerLoad = (event) => { + if (!event.locals.user) { + return redirect(302, '/demo/better-auth/login'); + } + return { user: event.locals.user }; +}; + +export const actions: Actions = { + signOut: async (event) => { + await auth.api.signOut({ + headers: event.request.headers + }); + return redirect(302, '/demo/better-auth/login'); + } +}; diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/+page.svelte b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/+page.svelte new file mode 100644 index 000000000..b05d0e463 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/+page.svelte @@ -0,0 +1,12 @@ + + +

Hi, {data.user.name}!

+

Your user ID is {data.user.id}.

+
+ +
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 new file mode 100644 index 000000000..d97509ba9 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.server.ts @@ -0,0 +1,78 @@ +import { fail, redirect } from '@sveltejs/kit'; +import type { Actions } from './$types'; +import type { PageServerLoad } from './$types'; +import { auth } from '$lib/server/auth'; +import { APIError } from 'better-auth/api'; + +export const load: PageServerLoad = (event) => { + if (event.locals.user) { + return redirect(302, '/demo/better-auth'); + } + return {}; +}; + +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() ?? ''; + + try { + await auth.api.signInEmail({ + body: { + email, + password, + callbackURL: '/auth/verification-success' + } + }); + } catch (error) { + if (error instanceof APIError) { + return fail(400, { message: error.message || 'Signin failed' }); + } + return fail(500, { message: 'Unexpected error' }); + } + + return redirect(302, '/demo/better-auth'); + }, + 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() ?? ''; + + try { + await auth.api.signUpEmail({ + body: { + email, + password, + name, + callbackURL: '/auth/verification-success' + } + }); + } catch (error) { + if (error instanceof APIError) { + return fail(400, { message: error.message || 'Registration failed' }); + } + return fail(500, { message: 'Unexpected error' }); + } + + return redirect(302, '/demo/better-auth'); + }, + 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 result = await auth.api.signInSocial({ + body: { + provider: provider as "github", + callbackURL + } + }); + + if (result.url) { + return redirect(302, result.url); + } + return fail(400, { message: 'Social sign-in failed' }); + }, +}; diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.svelte b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.svelte new file mode 100644 index 000000000..b20008d6a --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/routes/demo/better-auth/login/+page.svelte @@ -0,0 +1,33 @@ + + +

Login

+
+ + + + + +
+

{form?.message ?? ''}

+ +
+ +
+ + + +
diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/static/robots.txt b/packages/sv/src/cli/tests/snapshots/create-experimental/static/robots.txt new file mode 100644 index 000000000..b6dd6670c --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/tsconfig.json b/packages/sv/src/cli/tests/snapshots/create-experimental/tsconfig.json new file mode 100644 index 000000000..333f76845 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler", + "types": [ + "./worker-configuration.d.ts" + ] + } +} diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/vite.config.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/vite.config.ts new file mode 100644 index 000000000..a6b70ef19 --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/vite.config.ts @@ -0,0 +1,22 @@ +import adapter from '@sveltejs/adapter-cloudflare'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + sveltekit({ + compilerOptions: { + // Force runes mode for the project, except for libraries. Can be removed in svelte 6. + runes: ({ filename }) => filename.split(/[/\\]/).includes('node_modules') ? undefined : true + }, + adapter: adapter(), + experimental: { explicitEnvironmentVariables: true }, + typescript: { + config: (config) => ({ + ...config, + include: [...config.include, '../drizzle.config.ts'] + }) + } + }) + ] +}); diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/wrangler.jsonc b/packages/sv/src/cli/tests/snapshots/create-experimental/wrangler.jsonc new file mode 100644 index 000000000..a648cd20d --- /dev/null +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/wrangler.jsonc @@ -0,0 +1,15 @@ +{ + "$schema": "./node_modules/wrangler/config-schema.json", + "name": "create-experimental", + "compatibility_date": "2026-06-08", + "compatibility_flags": [ + "nodejs_als" + ], + "main": ".svelte-kit/cloudflare/_worker.js", + "assets": { + "binding": "ASSETS", + "directory": ".svelte-kit/cloudflare" + }, + "workers_dev": true, + "preview_urls": true +} From d68ced2cbbe10ebb31c148e6bf675d8c33e71c79 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:53:51 +0200 Subject: [PATCH 10/25] refactor(sv-utils): export only defineEnv from package barrel --- packages/sv-utils/src/index.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index 1f9437ec0..b91c7b553 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -85,17 +85,7 @@ export { } from './svelte-config.ts'; // Env access (abstracts over legacy `$env/dynamic/*` vs declared `$app/env/*` + `src/env.ts`) -export { - defineEnv, - resolveEnvMode, - readExplicitEnvFlag, - type DefineEnv, - type DefineEnvContext, - type EnvMode, - type EnvScope, - type EnvVarSpec, - type ReferenceOpts -} from './env.ts'; +export { defineEnv } from './env.ts'; // Terminal styling export { color } from './color.ts'; From 8edd92e89066075e3c241177244a9bd690d4eaa1 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 21:59:56 +0200 Subject: [PATCH 11/25] refactor(sv-utils): trim env public API to defineEnv + types --- packages/sv-utils/api-surface.md | 34 ++++++++++++++++++++++++++++++++ packages/sv-utils/src/index.ts | 10 +++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index 0107fc460..e0ec76f92 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -827,6 +827,33 @@ declare const svelteConfig: { find: (source: ConfigSource) => SvelteConfigLocation | null; read: (source: ConfigSource) => SvelteConfigObjects | null; }; +type EnvMode = 'declared' | 'legacy'; +type EnvScope = 'private' | 'public'; +type EnvVarSpec = { + name: string; + description?: string; + public?: boolean; + static?: boolean; +}; +type DefineEnvContext = { + sv: { + file: (path: string, edit: (content: string) => string | false) => void; + }; + cwd: string; + language: 'ts' | 'js'; + dependencyVersion: (pkg: string) => string | undefined; +}; +type ReferenceOpts = { + name: string; + scope?: EnvScope; + static?: boolean; +}; +type DefineEnv = { + mode: EnvMode; + declare: (spec: EnvVarSpec) => void; + reference: (ast: estree.Program, js: typeof index_d_exports$3, opts: ReferenceOpts) => string; +}; +declare function defineEnv(ctx: DefineEnvContext): DefineEnv; type ColorInput = string | string[]; declare const color: { addon: (str: ColorInput) => string; @@ -859,7 +886,13 @@ export { COMMANDS, type Comments, type ConfigFileReader, + type DefineEnv, + type DefineEnvContext, + type EnvMode, + type EnvScope, + type EnvVarSpec, type Package, + type ReferenceOpts, type SvelteAst, type SvelteConfigKind, type SvelteConfigLocation, @@ -873,6 +906,7 @@ export { createPrinter, index_d_exports$1 as css, dedent, + defineEnv, detect, downloadJson, fileExists, diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index b91c7b553..b6c24d496 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -85,7 +85,15 @@ export { } from './svelte-config.ts'; // Env access (abstracts over legacy `$env/dynamic/*` vs declared `$app/env/*` + `src/env.ts`) -export { defineEnv } from './env.ts'; +export { + defineEnv, + type DefineEnv, + type DefineEnvContext, + type EnvMode, + type EnvScope, + type EnvVarSpec, + type ReferenceOpts +} from './env.ts'; // Terminal styling export { color } from './color.ts'; From 33d9a12fd2b44fa4d6d008a7a0ca105472c25d81 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 22:05:51 +0200 Subject: [PATCH 12/25] refactor(sv-utils): export only defineEnv (types stay internal) --- packages/sv-utils/api-surface.md | 6 ------ packages/sv-utils/src/index.ts | 10 +--------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index e0ec76f92..c171e920d 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -886,13 +886,7 @@ export { COMMANDS, type Comments, type ConfigFileReader, - type DefineEnv, - type DefineEnvContext, - type EnvMode, - type EnvScope, - type EnvVarSpec, type Package, - type ReferenceOpts, type SvelteAst, type SvelteConfigKind, type SvelteConfigLocation, diff --git a/packages/sv-utils/src/index.ts b/packages/sv-utils/src/index.ts index b6c24d496..b91c7b553 100644 --- a/packages/sv-utils/src/index.ts +++ b/packages/sv-utils/src/index.ts @@ -85,15 +85,7 @@ export { } from './svelte-config.ts'; // Env access (abstracts over legacy `$env/dynamic/*` vs declared `$app/env/*` + `src/env.ts`) -export { - defineEnv, - type DefineEnv, - type DefineEnvContext, - type EnvMode, - type EnvScope, - type EnvVarSpec, - type ReferenceOpts -} from './env.ts'; +export { defineEnv } from './env.ts'; // Terminal styling export { color } from './color.ts'; From b80333e9749cc33d67dff97e92e9f02e23b850ba Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 22:46:10 +0200 Subject: [PATCH 13/25] refactor(sv-utils): defineEnv takes { sv, cwd }; mode/language read from cwd Drop dependencyVersion/language from the context (read kit version and ts/js from cwd) to match svelteConfig's { sv, cwd } shape. Rename declare -> define. Mode stays internal; add-ons never thread it. --- packages/sv-utils/api-surface.md | 11 +++---- packages/sv-utils/src/env.ts | 43 +++++++++++++++++++------- packages/sv-utils/src/svelte-config.ts | 4 +-- packages/sv-utils/src/tests/env.ts | 35 ++++++++++----------- packages/sv/src/addons/better-auth.ts | 10 +++--- packages/sv/src/addons/drizzle.ts | 6 ++-- 6 files changed, 61 insertions(+), 48 deletions(-) diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index c171e920d..025f5b9c4 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -836,12 +836,8 @@ type EnvVarSpec = { static?: boolean; }; type DefineEnvContext = { - sv: { - file: (path: string, edit: (content: string) => string | false) => void; - }; + sv: SvFileApi; cwd: string; - language: 'ts' | 'js'; - dependencyVersion: (pkg: string) => string | undefined; }; type ReferenceOpts = { name: string; @@ -850,10 +846,11 @@ type ReferenceOpts = { }; type DefineEnv = { mode: EnvMode; - declare: (spec: EnvVarSpec) => void; + define: (spec: EnvVarSpec) => void; reference: (ast: estree.Program, js: typeof index_d_exports$3, opts: ReferenceOpts) => string; }; -declare function defineEnv(ctx: DefineEnvContext): DefineEnv; + +declare function defineEnv({ sv, cwd }: DefineEnvContext): DefineEnv; type ColorInput = string | string[]; declare const color: { addon: (str: ColorInput) => string; diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 9671967b1..327855abb 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -1,5 +1,6 @@ +import { fileExists, loadPackageJson } from './files.ts'; import { coerceVersion } from './semver.ts'; -import { svelteConfig, type ConfigFileReader } from './svelte-config.ts'; +import { svelteConfig, type ConfigFileReader, type SvFileApi } from './svelte-config.ts'; import type { AstTypes } from './tooling/index.ts'; import * as jsNs from './tooling/js/index.ts'; import { transforms } from './tooling/transforms.ts'; @@ -31,17 +32,16 @@ export type EnvVarSpec = { }; export type DefineEnvContext = { - sv: { file: (path: string, edit: (content: string) => string | false) => void }; + sv: SvFileApi; cwd: string; - language: 'ts' | 'js'; - dependencyVersion: (pkg: string) => string | undefined; }; export type ReferenceOpts = { name: string; scope?: EnvScope; static?: boolean }; export type DefineEnv = { + /** Resolved once from the project; add-ons don't need to consult it. */ mode: EnvMode; - declare: (spec: EnvVarSpec) => void; + define: (spec: EnvVarSpec) => void; reference: (ast: AstTypes.Program, js: typeof jsNs, opts: ReferenceOpts) => string; }; @@ -99,19 +99,38 @@ function getOrCreateVariablesObject( return call.arguments[0] as AstTypes.ObjectExpression; } -export function defineEnv(ctx: DefineEnvContext): DefineEnv { - const kitRange = ctx.dependencyVersion('@sveltejs/kit'); - const explicitEnvFlag = readExplicitEnvFlag(ctx.cwd); - const mode = resolveEnvMode({ kitRange, explicitEnvFlag }); +/** + * Detects the env mode from the project at `cwd` (kit major version / `next` tag, or the kit-2 + * `explicitEnvironmentVariables` flag) and binds the `sv`/`cwd` context. Add-ons just call + * `define`/`reference` and never deal with the legacy-vs-declared distinction themselves. + */ +export function defineEnv({ sv, cwd }: DefineEnvContext): DefineEnv { + const { data } = loadPackageJson(cwd); + const kitRange = data.devDependencies?.['@sveltejs/kit'] ?? data.dependencies?.['@sveltejs/kit']; + const mode = resolveEnvMode({ kitRange, explicitEnvFlag: readExplicitEnvFlag(cwd) }); + const language = fileExists(cwd, 'tsconfig.json') ? 'ts' : 'js'; + return _bindEnv({ sv, mode, language }); +} + +/** @internal The mode-resolved core, exported for filesystem-free tests. */ +export function _bindEnv({ + sv, + mode, + language +}: { + sv: SvFileApi; + mode: EnvMode; + language: 'ts' | 'js'; +}): DefineEnv { const declared = new Map(); return { mode, - declare(spec) { + define(spec) { declared.set(spec.name, spec); if (mode !== 'declared') return; - const envPath = `src/env.${ctx.language}`; - ctx.sv.file(envPath, (content) => + const envPath = `src/env.${language}`; + sv.file(envPath, (content) => transforms.script(({ ast, js }) => { js.imports.addNamed(ast, { from: '@sveltejs/kit/hooks', imports: ['defineEnvVars'] }); const variables = getOrCreateVariablesObject(ast, js); diff --git a/packages/sv-utils/src/svelte-config.ts b/packages/sv-utils/src/svelte-config.ts index 309623652..f62641824 100644 --- a/packages/sv-utils/src/svelte-config.ts +++ b/packages/sv-utils/src/svelte-config.ts @@ -133,8 +133,8 @@ export type SvelteConfEdit = (file: { override: (props: ObjectMap, opts?: { dropLeadingComments?: string[] }) => void; }) => void | false; -/** Minimal shape of the `sv` api needed to write the config file. */ -type SvFileApi = { +/** Minimal shape of the `sv` api needed to write a file. */ +export type SvFileApi = { file: (path: string, edit: (content: string) => string | false) => void; }; diff --git a/packages/sv-utils/src/tests/env.ts b/packages/sv-utils/src/tests/env.ts index 9fc898eb8..55dca347c 100644 --- a/packages/sv-utils/src/tests/env.ts +++ b/packages/sv-utils/src/tests/env.ts @@ -19,7 +19,7 @@ describe('resolveEnvMode', () => { }); }); -import { defineEnv } from '../env.ts'; +import { _bindEnv, type EnvMode } from '../env.ts'; import * as js from '../tooling/js/index.ts'; import { parseScript } from '../tooling/parsers.ts'; @@ -36,15 +36,15 @@ function fakeSv(files: Record = {}) { }; } -function envFor(kitRange: string) { +function envFor(mode: EnvMode) { const { sv, files } = fakeSv(); - const env = defineEnv({ sv, cwd: '/proj', language: 'ts', dependencyVersion: () => kitRange }); + const env = _bindEnv({ sv, mode, language: 'ts' }); return { env, files }; } describe('defineEnv.reference', () => { test('declared: named import from $app/env/private + bare accessor', () => { - const { env } = envFor('next'); + const { env } = envFor('declared'); const { ast, generateCode } = parseScript(''); const access = env.reference(ast, js, { name: 'DATABASE_URL' }); expect(access).toBe('DATABASE_URL'); @@ -52,7 +52,7 @@ describe('defineEnv.reference', () => { }); test('legacy dynamic: env import + env.DATABASE_URL accessor', () => { - const { env } = envFor('^2.0.0'); + const { env } = envFor('legacy'); const { ast, generateCode } = parseScript(''); const access = env.reference(ast, js, { name: 'DATABASE_URL' }); expect(access).toBe('env.DATABASE_URL'); @@ -60,7 +60,7 @@ describe('defineEnv.reference', () => { }); test('legacy static: named import from $env/static/public + bare accessor', () => { - const { env } = envFor('^2.0.0'); + const { env } = envFor('legacy'); const { ast, generateCode } = parseScript(''); const access = env.reference(ast, js, { name: 'PUBLIC_X', scope: 'public', static: true }); expect(access).toBe('PUBLIC_X'); @@ -90,11 +90,10 @@ describe('readExplicitEnvFlag', () => { }); }); -describe('defineEnv.declare', () => { +describe('defineEnv.define', () => { test('declared: creates src/env.ts with defineEnvVars + description', () => { - const { sv, files } = fakeSv(); - const env = defineEnv({ sv, cwd: '/proj', language: 'ts', dependencyVersion: () => 'next' }); - env.declare({ name: 'DATABASE_URL', description: 'db url' }); + const { env, files } = envFor('declared'); + env.define({ name: 'DATABASE_URL', description: 'db url' }); const out = files['src/env.ts']; expect(out).toContain("import { defineEnvVars } from '@sveltejs/kit/hooks';"); expect(out).toContain('export const variables = defineEnvVars({'); @@ -102,21 +101,19 @@ describe('defineEnv.declare', () => { expect(out).toContain("description: 'db url'"); }); - test('declared: second declare merges into the same variables object', () => { - const { sv, files } = fakeSv(); - const env = defineEnv({ sv, cwd: '/proj', language: 'ts', dependencyVersion: () => 'next' }); - env.declare({ name: 'DATABASE_URL' }); - env.declare({ name: 'DATABASE_AUTH_TOKEN' }); + test('declared: second define merges into the same variables object', () => { + const { env, files } = envFor('declared'); + env.define({ name: 'DATABASE_URL' }); + env.define({ name: 'DATABASE_AUTH_TOKEN' }); const out = files['src/env.ts']; expect(out).toContain('DATABASE_URL'); expect(out).toContain('DATABASE_AUTH_TOKEN'); expect(out.match(/defineEnvVars/g)?.length).toBe(2); // import + one call, no duplicate object }); - test('legacy: declare is a no-op (no env file written)', () => { - const { sv, files } = fakeSv(); - const env = defineEnv({ sv, cwd: '/proj', language: 'ts', dependencyVersion: () => '^2.0.0' }); - env.declare({ name: 'DATABASE_URL' }); + test('legacy: define is a no-op (no env file written)', () => { + const { env, files } = envFor('legacy'); + env.define({ name: 'DATABASE_URL' }); expect(files['src/env.ts']).toBeUndefined(); }); }); diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 487fa81a5..24d777bfe 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -96,12 +96,12 @@ export default defineAddon({ sv.file('.env', generateEnv(demoGithub, false)); sv.file('.env.example', generateEnv(demoGithub, true)); - const env = defineEnv({ sv, cwd, language, dependencyVersion }); - env.declare({ name: 'ORIGIN', description: 'App origin / base URL' }); - env.declare({ name: 'BETTER_AUTH_SECRET', description: 'Secret used by better-auth' }); + const env = defineEnv({ sv, cwd }); + env.define({ name: 'ORIGIN', description: 'App origin / base URL' }); + env.define({ name: 'BETTER_AUTH_SECRET', description: 'Secret used by better-auth' }); if (demoGithub) { - env.declare({ name: 'GITHUB_CLIENT_ID' }); - env.declare({ name: 'GITHUB_CLIENT_SECRET' }); + env.define({ name: 'GITHUB_CLIENT_ID' }); + env.define({ name: 'GITHUB_CLIENT_SECRET' }); } sv.file( diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index fe3f0e812..fa876378f 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -364,11 +364,11 @@ export default defineAddon({ }) ); - const env = defineEnv({ sv, cwd, language, dependencyVersion }); + const env = defineEnv({ sv, cwd }); if (options.database !== 'd1') { - env.declare({ name: 'DATABASE_URL', description: 'Connection string for the database' }); + env.define({ name: 'DATABASE_URL', description: 'Connection string for the database' }); if (options.sqlite === 'turso') { - env.declare({ name: 'DATABASE_AUTH_TOKEN', description: 'Auth token for Turso' }); + env.define({ name: 'DATABASE_AUTH_TOKEN', description: 'Auth token for Turso' }); } } From d81be473b5de9671fbaeb04a6e630dff8ada2bef Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 22:57:43 +0200 Subject: [PATCH 14/25] fix(sv-utils): walk up to parent package.json for kit version detection Mirrors the workspace's resolution so a monorepo project that declares kit only in a parent package.json is still detected (closest wins). --- packages/sv-utils/src/env.ts | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 327855abb..53fa341e3 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -1,3 +1,4 @@ +import path from 'node:path'; import { fileExists, loadPackageJson } from './files.ts'; import { coerceVersion } from './semver.ts'; import { svelteConfig, type ConfigFileReader, type SvFileApi } from './svelte-config.ts'; @@ -99,15 +100,36 @@ function getOrCreateVariablesObject( return call.arguments[0] as AstTypes.ObjectExpression; } +/** + * Resolves `@sveltejs/kit`'s range, walking up to parent `package.json` files (closest wins) the + * same way the workspace does - so a monorepo project that only declares kit in a parent is still + * detected. Returns `undefined` when kit is nowhere up the tree (i.e. not a kit project). + */ +function findKitRange(cwd: string): string | undefined { + let dir = path.resolve(cwd); + for (;;) { + if (fileExists(dir, 'package.json')) { + const { data } = loadPackageJson(dir); + const range = + data.devDependencies?.['@sveltejs/kit'] ?? data.dependencies?.['@sveltejs/kit']; + if (range) return range; + } + const parent = path.dirname(dir); + if (parent === dir) return undefined; + dir = parent; + } +} + /** * Detects the env mode from the project at `cwd` (kit major version / `next` tag, or the kit-2 * `explicitEnvironmentVariables` flag) and binds the `sv`/`cwd` context. Add-ons just call * `define`/`reference` and never deal with the legacy-vs-declared distinction themselves. */ export function defineEnv({ sv, cwd }: DefineEnvContext): DefineEnv { - const { data } = loadPackageJson(cwd); - const kitRange = data.devDependencies?.['@sveltejs/kit'] ?? data.dependencies?.['@sveltejs/kit']; - const mode = resolveEnvMode({ kitRange, explicitEnvFlag: readExplicitEnvFlag(cwd) }); + const mode = resolveEnvMode({ + kitRange: findKitRange(cwd), + explicitEnvFlag: readExplicitEnvFlag(cwd) + }); const language = fileExists(cwd, 'tsconfig.json') ? 'ts' : 'js'; return _bindEnv({ sv, mode, language }); } From c508c96a7bacb5c14f6e87500449d81948d5679e Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:10:26 +0200 Subject: [PATCH 15/25] refactor(sv-utils): pass kitVersion to defineEnv instead of re-deriving it Add-ons pass dependencyVersion('@sveltejs/kit') (the engine's authoritative, walk-up-aware resolution). Removes findKitRange's package.json walk-up from sv-utils - dependency resolution belongs to the engine, not sv-utils. The experimental flag is still read from the config here, where config-reading already lives. --- packages/sv-utils/api-surface.md | 3 ++- packages/sv-utils/src/env.ts | 38 ++++++--------------------- packages/sv/src/addons/better-auth.ts | 2 +- packages/sv/src/addons/drizzle.ts | 2 +- 4 files changed, 12 insertions(+), 33 deletions(-) diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index 025f5b9c4..52a3bf849 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -838,6 +838,7 @@ type EnvVarSpec = { type DefineEnvContext = { sv: SvFileApi; cwd: string; + kitVersion: string | undefined; }; type ReferenceOpts = { name: string; @@ -850,7 +851,7 @@ type DefineEnv = { reference: (ast: estree.Program, js: typeof index_d_exports$3, opts: ReferenceOpts) => string; }; -declare function defineEnv({ sv, cwd }: DefineEnvContext): DefineEnv; +declare function defineEnv({ sv, cwd, kitVersion }: DefineEnvContext): DefineEnv; type ColorInput = string | string[]; declare const color: { addon: (str: ColorInput) => string; diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 53fa341e3..990029f78 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -1,5 +1,4 @@ -import path from 'node:path'; -import { fileExists, loadPackageJson } from './files.ts'; +import { fileExists } from './files.ts'; import { coerceVersion } from './semver.ts'; import { svelteConfig, type ConfigFileReader, type SvFileApi } from './svelte-config.ts'; import type { AstTypes } from './tooling/index.ts'; @@ -35,6 +34,8 @@ export type EnvVarSpec = { export type DefineEnvContext = { sv: SvFileApi; cwd: string; + /** The project's `@sveltejs/kit` range, e.g. from `dependencyVersion('@sveltejs/kit')`. */ + kitVersion: string | undefined; }; export type ReferenceOpts = { name: string; scope?: EnvScope; static?: boolean }; @@ -101,35 +102,12 @@ function getOrCreateVariablesObject( } /** - * Resolves `@sveltejs/kit`'s range, walking up to parent `package.json` files (closest wins) the - * same way the workspace does - so a monorepo project that only declares kit in a parent is still - * detected. Returns `undefined` when kit is nowhere up the tree (i.e. not a kit project). + * Detects the env mode from the project (the passed `kitVersion`, or the kit-2 + * `explicitEnvironmentVariables` flag read from the config at `cwd`) and binds the context. Add-ons + * just call `define`/`reference` and never deal with the legacy-vs-declared distinction themselves. */ -function findKitRange(cwd: string): string | undefined { - let dir = path.resolve(cwd); - for (;;) { - if (fileExists(dir, 'package.json')) { - const { data } = loadPackageJson(dir); - const range = - data.devDependencies?.['@sveltejs/kit'] ?? data.dependencies?.['@sveltejs/kit']; - if (range) return range; - } - const parent = path.dirname(dir); - if (parent === dir) return undefined; - dir = parent; - } -} - -/** - * Detects the env mode from the project at `cwd` (kit major version / `next` tag, or the kit-2 - * `explicitEnvironmentVariables` flag) and binds the `sv`/`cwd` context. Add-ons just call - * `define`/`reference` and never deal with the legacy-vs-declared distinction themselves. - */ -export function defineEnv({ sv, cwd }: DefineEnvContext): DefineEnv { - const mode = resolveEnvMode({ - kitRange: findKitRange(cwd), - explicitEnvFlag: readExplicitEnvFlag(cwd) - }); +export function defineEnv({ sv, cwd, kitVersion }: DefineEnvContext): DefineEnv { + const mode = resolveEnvMode({ kitRange: kitVersion, explicitEnvFlag: readExplicitEnvFlag(cwd) }); const language = fileExists(cwd, 'tsconfig.json') ? 'ts' : 'js'; return _bindEnv({ sv, mode, language }); } diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 24d777bfe..8c881f541 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -96,7 +96,7 @@ export default defineAddon({ sv.file('.env', generateEnv(demoGithub, false)); sv.file('.env.example', generateEnv(demoGithub, true)); - const env = defineEnv({ sv, cwd }); + const env = defineEnv({ sv, cwd, kitVersion: dependencyVersion('@sveltejs/kit') }); env.define({ name: 'ORIGIN', description: 'App origin / base URL' }); env.define({ name: 'BETTER_AUTH_SECRET', description: 'Secret used by better-auth' }); if (demoGithub) { diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index fa876378f..4550c2c06 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -364,7 +364,7 @@ export default defineAddon({ }) ); - const env = defineEnv({ sv, cwd }); + const env = defineEnv({ sv, cwd, kitVersion: dependencyVersion('@sveltejs/kit') }); if (options.database !== 'd1') { env.define({ name: 'DATABASE_URL', description: 'Connection string for the database' }); if (options.sqlite === 'turso') { From efb61a271bd0ea173b4daf9028b8b1f13dea3063 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:22:47 +0200 Subject: [PATCH 16/25] refactor(sv-utils): defineEnv takes dependencyVersion fn directly Call site is just { sv, cwd, dependencyVersion } - the workspace's authoritative walk-up lookup, called internally for @sveltejs/kit. Cleaner than threading a pre-resolved kitVersion value. --- packages/sv-utils/api-surface.md | 4 ++-- packages/sv-utils/src/env.ts | 11 +++++++---- packages/sv/src/addons/better-auth.ts | 2 +- packages/sv/src/addons/drizzle.ts | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/sv-utils/api-surface.md b/packages/sv-utils/api-surface.md index 52a3bf849..8eaaec176 100644 --- a/packages/sv-utils/api-surface.md +++ b/packages/sv-utils/api-surface.md @@ -838,7 +838,7 @@ type EnvVarSpec = { type DefineEnvContext = { sv: SvFileApi; cwd: string; - kitVersion: string | undefined; + dependencyVersion: (pkg: string) => string | undefined; }; type ReferenceOpts = { name: string; @@ -851,7 +851,7 @@ type DefineEnv = { reference: (ast: estree.Program, js: typeof index_d_exports$3, opts: ReferenceOpts) => string; }; -declare function defineEnv({ sv, cwd, kitVersion }: DefineEnvContext): DefineEnv; +declare function defineEnv({ sv, cwd, dependencyVersion }: DefineEnvContext): DefineEnv; type ColorInput = string | string[]; declare const color: { addon: (str: ColorInput) => string; diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 990029f78..196ebca93 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -34,8 +34,8 @@ export type EnvVarSpec = { export type DefineEnvContext = { sv: SvFileApi; cwd: string; - /** The project's `@sveltejs/kit` range, e.g. from `dependencyVersion('@sveltejs/kit')`. */ - kitVersion: string | undefined; + /** Resolves a dependency's range (the workspace's authoritative, walk-up-aware lookup). */ + dependencyVersion: (pkg: string) => string | undefined; }; export type ReferenceOpts = { name: string; scope?: EnvScope; static?: boolean }; @@ -106,8 +106,11 @@ function getOrCreateVariablesObject( * `explicitEnvironmentVariables` flag read from the config at `cwd`) and binds the context. Add-ons * just call `define`/`reference` and never deal with the legacy-vs-declared distinction themselves. */ -export function defineEnv({ sv, cwd, kitVersion }: DefineEnvContext): DefineEnv { - const mode = resolveEnvMode({ kitRange: kitVersion, explicitEnvFlag: readExplicitEnvFlag(cwd) }); +export function defineEnv({ sv, cwd, dependencyVersion }: DefineEnvContext): DefineEnv { + const mode = resolveEnvMode({ + kitRange: dependencyVersion('@sveltejs/kit'), + explicitEnvFlag: readExplicitEnvFlag(cwd) + }); const language = fileExists(cwd, 'tsconfig.json') ? 'ts' : 'js'; return _bindEnv({ sv, mode, language }); } diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 8c881f541..20a90e82c 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -96,7 +96,7 @@ export default defineAddon({ sv.file('.env', generateEnv(demoGithub, false)); sv.file('.env.example', generateEnv(demoGithub, true)); - const env = defineEnv({ sv, cwd, kitVersion: dependencyVersion('@sveltejs/kit') }); + const env = defineEnv({ sv, cwd, dependencyVersion }); env.define({ name: 'ORIGIN', description: 'App origin / base URL' }); env.define({ name: 'BETTER_AUTH_SECRET', description: 'Secret used by better-auth' }); if (demoGithub) { diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index 4550c2c06..e79bcd0e6 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -364,7 +364,7 @@ export default defineAddon({ }) ); - const env = defineEnv({ sv, cwd, kitVersion: dependencyVersion('@sveltejs/kit') }); + const env = defineEnv({ sv, cwd, dependencyVersion }); if (options.database !== 'd1') { env.define({ name: 'DATABASE_URL', description: 'Connection string for the database' }); if (options.sqlite === 'turso') { From 29af04ba24218011f167730636939984e37d5b8f Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:26:58 +0200 Subject: [PATCH 17/25] chore: changeset for env modes --- .changeset/env-modes.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/env-modes.md diff --git a/.changeset/env-modes.md b/.changeset/env-modes.md new file mode 100644 index 000000000..8889ac6dd --- /dev/null +++ b/.changeset/env-modes.md @@ -0,0 +1,6 @@ +--- +'@sveltejs/sv-utils': minor +'sv': patch +--- + +`drizzle` and `better-auth` now emit the declared env API (`src/env.ts` + `$app/env/private`) on kit 3 / `next` or kit 2 with `explicitEnvironmentVariables`, and fall back to legacy `$env/dynamic/private` otherwise. Adds a `defineEnv` helper to `sv-utils`. From f81c34f671fc652c0af681b7eacc56f81042e52e Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:28:28 +0200 Subject: [PATCH 18/25] chore: minimal changeset for env modes --- .changeset/env-modes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/env-modes.md b/.changeset/env-modes.md index 8889ac6dd..7dcf40f27 100644 --- a/.changeset/env-modes.md +++ b/.changeset/env-modes.md @@ -3,4 +3,4 @@ 'sv': patch --- -`drizzle` and `better-auth` now emit the declared env API (`src/env.ts` + `$app/env/private`) on kit 3 / `next` or kit 2 with `explicitEnvironmentVariables`, and fall back to legacy `$env/dynamic/private` otherwise. Adds a `defineEnv` helper to `sv-utils`. +support kit's explicit environment variables in `drizzle` and `better-auth` From 58124b3a9282c2acb76ac23ba90c967287a72114 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:28:58 +0200 Subject: [PATCH 19/25] chore: split env changesets per package --- .changeset/env-sv-utils.md | 5 +++++ .changeset/{env-modes.md => env-sv.md} | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .changeset/env-sv-utils.md rename .changeset/{env-modes.md => env-sv.md} (77%) diff --git a/.changeset/env-sv-utils.md b/.changeset/env-sv-utils.md new file mode 100644 index 000000000..2dc8e3a3d --- /dev/null +++ b/.changeset/env-sv-utils.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/sv-utils': minor +--- + +add `defineEnv` helper for version-aware environment variable access (`$app/env` or legacy `$env`) diff --git a/.changeset/env-modes.md b/.changeset/env-sv.md similarity index 77% rename from .changeset/env-modes.md rename to .changeset/env-sv.md index 7dcf40f27..c42ac63dc 100644 --- a/.changeset/env-modes.md +++ b/.changeset/env-sv.md @@ -1,5 +1,4 @@ --- -'@sveltejs/sv-utils': minor 'sv': patch --- From c4a6889f94be1ddc47ad0b99c71e0f30938bb132 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:30:19 +0200 Subject: [PATCH 20/25] cs --- .changeset/env-sv-utils.md | 2 +- .changeset/env-sv.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/env-sv-utils.md b/.changeset/env-sv-utils.md index 2dc8e3a3d..bffc93f82 100644 --- a/.changeset/env-sv-utils.md +++ b/.changeset/env-sv-utils.md @@ -2,4 +2,4 @@ '@sveltejs/sv-utils': minor --- -add `defineEnv` helper for version-aware environment variable access (`$app/env` or legacy `$env`) +Add `defineEnv` helper for version-aware environment variable access (`$app/env` or legacy `$env`) diff --git a/.changeset/env-sv.md b/.changeset/env-sv.md index c42ac63dc..5a839413b 100644 --- a/.changeset/env-sv.md +++ b/.changeset/env-sv.md @@ -2,4 +2,4 @@ 'sv': patch --- -support kit's explicit environment variables in `drizzle` and `better-auth` +chore: support kit's explicit environment variables in `drizzle` and `better-auth` From b1a92cfa9c2a559be8ba6cc669106f11656fe7c4 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:31:17 +0200 Subject: [PATCH 21/25] fmt --- packages/sv-utils/src/env.ts | 5 +---- packages/sv-utils/src/tests/env.ts | 13 +++++-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 196ebca93..6eb81ecaf 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -47,10 +47,7 @@ export type DefineEnv = { reference: (ast: AstTypes.Program, js: typeof jsNs, opts: ReferenceOpts) => string; }; -function findProp( - obj: AstTypes.ObjectExpression, - name: string -): AstTypes.Property | undefined { +function findProp(obj: AstTypes.ObjectExpression, name: string): AstTypes.Property | undefined { return obj.properties.find( (p): p is AstTypes.Property => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === name diff --git a/packages/sv-utils/src/tests/env.ts b/packages/sv-utils/src/tests/env.ts index 55dca347c..76dc4b2f5 100644 --- a/packages/sv-utils/src/tests/env.ts +++ b/packages/sv-utils/src/tests/env.ts @@ -1,5 +1,9 @@ import { describe, expect, test } from 'vitest'; import { resolveEnvMode } from '../env.ts'; +import { _bindEnv, type EnvMode } from '../env.ts'; +import { readExplicitEnvFlag } from '../env.ts'; +import * as js from '../tooling/js/index.ts'; +import { parseScript } from '../tooling/parsers.ts'; describe('resolveEnvMode', () => { test('no kit -> legacy', () => { @@ -19,10 +23,6 @@ describe('resolveEnvMode', () => { }); }); -import { _bindEnv, type EnvMode } from '../env.ts'; -import * as js from '../tooling/js/index.ts'; -import { parseScript } from '../tooling/parsers.ts'; - /** A fake `sv` capturing file writes in-memory. */ function fakeSv(files: Record = {}) { return { @@ -68,16 +68,13 @@ describe('defineEnv.reference', () => { }); }); -import { readExplicitEnvFlag } from '../env.ts'; - - const reader = (files: Record) => (path: string) => files[path] ?? null; describe('readExplicitEnvFlag', () => { test('true when set in svelte.config', () => { const files = { 'svelte.config.js': - "export default { kit: { experimental: { explicitEnvironmentVariables: true } } };\n" + 'export default { kit: { experimental: { explicitEnvironmentVariables: true } } };\n' }; expect(readExplicitEnvFlag(reader(files))).toBe(true); }); From 5365eabddd5a9ee169bd9d0b17702c71a660c4c8 Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:41:50 +0200 Subject: [PATCH 22/25] ffmmtt --- packages/sv-utils/src/tests/env.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/sv-utils/src/tests/env.ts b/packages/sv-utils/src/tests/env.ts index 76dc4b2f5..14f290c62 100644 --- a/packages/sv-utils/src/tests/env.ts +++ b/packages/sv-utils/src/tests/env.ts @@ -1,7 +1,5 @@ import { describe, expect, test } from 'vitest'; -import { resolveEnvMode } from '../env.ts'; -import { _bindEnv, type EnvMode } from '../env.ts'; -import { readExplicitEnvFlag } from '../env.ts'; +import { _bindEnv, readExplicitEnvFlag, resolveEnvMode, type EnvMode } from '../env.ts'; import * as js from '../tooling/js/index.ts'; import { parseScript } from '../tooling/parsers.ts'; From 1d02141cd90513ffde12e729ec205fa76eccc84a Mon Sep 17 00:00:00 2001 From: jycouet Date: Mon, 8 Jun 2026 23:48:33 +0200 Subject: [PATCH 23/25] update location --- .../snapshots/create-with-all-addons/src/lib/server/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/lib/server/auth.ts b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/lib/server/auth.ts index b05416873..fc0fdf8fe 100644 --- a/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/lib/server/auth.ts +++ b/packages/sv/src/cli/tests/snapshots/create-with-all-addons/src/lib/server/auth.ts @@ -1,7 +1,7 @@ +import { env } from '$env/dynamic/private'; import { betterAuth } from 'better-auth/minimal'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; import { sveltekitCookies } from 'better-auth/svelte-kit'; -import { env } from '$env/dynamic/private'; import { getRequestEvent } from '$app/server'; import { db } from '$lib/server/db'; From 5ae5a33705914b6b46e4355f73a5b5d133954894 Mon Sep 17 00:00:00 2001 From: jycouet Date: Tue, 9 Jun 2026 07:45:13 +0200 Subject: [PATCH 24/25] add desc to env & fix date drift in snap --- packages/sv-utils/src/env.ts | 4 ++-- packages/sv/src/addons/better-auth.ts | 20 +++++++++++++++---- packages/sv/src/addons/drizzle.ts | 7 +++++-- packages/sv/src/cli/tests/cli.ts | 8 ++++++++ .../create-experimental/package.json | 14 ++++++------- .../snapshots/create-experimental/src/env.ts | 18 ++++++++++++----- .../create-experimental/wrangler.jsonc | 2 +- 7 files changed, 52 insertions(+), 21 deletions(-) diff --git a/packages/sv-utils/src/env.ts b/packages/sv-utils/src/env.ts index 6eb81ecaf..3c18a1d59 100644 --- a/packages/sv-utils/src/env.ts +++ b/packages/sv-utils/src/env.ts @@ -99,8 +99,8 @@ function getOrCreateVariablesObject( } /** - * Detects the env mode from the project (the passed `kitVersion`, or the kit-2 - * `explicitEnvironmentVariables` flag read from the config at `cwd`) and binds the context. Add-ons + * Detects the env mode from the project (the `@sveltejs/kit` range via `dependencyVersion`, or the + * kit-2 `explicitEnvironmentVariables` flag read from the config at `cwd`) and binds the context. Add-ons * just call `define`/`reference` and never deal with the legacy-vs-declared distinction themselves. */ export function defineEnv({ sv, cwd, dependencyVersion }: DefineEnvContext): DefineEnv { diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index 20a90e82c..a65032300 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -97,11 +97,23 @@ export default defineAddon({ sv.file('.env.example', generateEnv(demoGithub, true)); const env = defineEnv({ sv, cwd, dependencyVersion }); - env.define({ name: 'ORIGIN', description: 'App origin / base URL' }); - env.define({ name: 'BETTER_AUTH_SECRET', description: 'Secret used by better-auth' }); + env.define({ name: 'ORIGIN', description: 'The app origin (base URL), e.g. `http://localhost:5173`.' }); + env.define({ + name: 'BETTER_AUTH_SECRET', + description: + 'Secret used to sign tokens. For production use 32 characters generated with high entropy. See [Better Auth installation](https://www.better-auth.com/docs/installation).' + }); if (demoGithub) { - env.define({ name: 'GITHUB_CLIENT_ID' }); - env.define({ name: 'GITHUB_CLIENT_SECRET' }); + env.define({ + name: 'GITHUB_CLIENT_ID', + description: + 'GitHub OAuth client ID. See [Better Auth GitHub provider](https://www.better-auth.com/docs/authentication/github).' + }); + env.define({ + name: 'GITHUB_CLIENT_SECRET', + description: + 'GitHub OAuth client secret. See [Better Auth GitHub provider](https://www.better-auth.com/docs/authentication/github).' + }); } sv.file( diff --git a/packages/sv/src/addons/drizzle.ts b/packages/sv/src/addons/drizzle.ts index effec11ff..ee0d7a605 100644 --- a/packages/sv/src/addons/drizzle.ts +++ b/packages/sv/src/addons/drizzle.ts @@ -366,9 +366,12 @@ export default defineAddon({ const env = defineEnv({ sv, cwd, dependencyVersion }); if (options.database !== 'd1') { - env.define({ name: 'DATABASE_URL', description: 'Connection string for the database' }); + env.define({ name: 'DATABASE_URL', description: 'The database connection string.' }); if (options.sqlite === 'turso') { - env.define({ name: 'DATABASE_AUTH_TOKEN', description: 'Auth token for Turso' }); + env.define({ + name: 'DATABASE_AUTH_TOKEN', + description: 'Auth token for the [Turso](https://turso.tech) database.' + }); } } diff --git a/packages/sv/src/cli/tests/cli.ts b/packages/sv/src/cli/tests/cli.ts index e30166e4f..dca2412a4 100644 --- a/packages/sv/src/cli/tests/cli.ts +++ b/packages/sv/src/cli/tests/cli.ts @@ -132,6 +132,14 @@ describe('cli', () => { generated = generated.replace(/sv@\d+\.\d+\.\d+/g, 'sv@0.0.0'); } + // Normalize the cloudflare adapter's `compatibility_date` (set to today) to avoid daily drift + if (relativeFile === 'wrangler.jsonc') { + generated = generated.replace( + /"compatibility_date": "\d{4}-\d{2}-\d{2}"/, + '"compatibility_date": "2020-01-01"' + ); + } + await expect(generated).toMatchFileSnapshot( path.resolve(snapPath, relativeFile), `file "${relativeFile}" does not match snapshot` diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/package.json b/packages/sv/src/cli/tests/snapshots/create-experimental/package.json index c0190161e..0236f1082 100644 --- a/packages/sv/src/cli/tests/snapshots/create-experimental/package.json +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/package.json @@ -19,17 +19,17 @@ }, "devDependencies": { "@better-auth/cli": "~1.4.21", - "@libsql/client": "^0.17.2", + "@libsql/client": "^0.17.3", "@sveltejs/adapter-cloudflare": "^7.2.8", - "@sveltejs/kit": "^2.62.0", - "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@sveltejs/kit": "^2.63.0", + "@sveltejs/vite-plugin-svelte": "^7.1.2", "better-auth": "~1.4.21", "drizzle-kit": "^0.31.10", "drizzle-orm": "^0.45.2", "svelte": "^5.56.1", - "svelte-check": "^4.4.6", - "typescript": "^6.0.2", - "vite": "^8.0.7", - "wrangler": "^4.81.0" + "svelte-check": "^4.6.0", + "typescript": "^6.0.3", + "vite": "^8.0.16", + "wrangler": "^4.97.0" } } diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/src/env.ts b/packages/sv/src/cli/tests/snapshots/create-experimental/src/env.ts index d8de65dcf..ff20235e9 100644 --- a/packages/sv/src/cli/tests/snapshots/create-experimental/src/env.ts +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/src/env.ts @@ -1,9 +1,17 @@ import { defineEnvVars } from '@sveltejs/kit/hooks'; export const variables = defineEnvVars({ - DATABASE_URL: { description: 'Connection string for the database' }, - ORIGIN: { description: 'App origin / base URL' }, - BETTER_AUTH_SECRET: { description: 'Secret used by better-auth' }, - GITHUB_CLIENT_ID: {}, - GITHUB_CLIENT_SECRET: {} + DATABASE_URL: { description: 'The database connection string.' }, + ORIGIN: { + description: 'The app origin (base URL), e.g. `http://localhost:5173`.' + }, + BETTER_AUTH_SECRET: { + description: 'Secret used to sign tokens. For production use 32 characters generated with high entropy. See [Better Auth installation](https://www.better-auth.com/docs/installation).' + }, + GITHUB_CLIENT_ID: { + description: 'GitHub OAuth client ID. See [Better Auth GitHub provider](https://www.better-auth.com/docs/authentication/github).' + }, + GITHUB_CLIENT_SECRET: { + description: 'GitHub OAuth client secret. See [Better Auth GitHub provider](https://www.better-auth.com/docs/authentication/github).' + } }); diff --git a/packages/sv/src/cli/tests/snapshots/create-experimental/wrangler.jsonc b/packages/sv/src/cli/tests/snapshots/create-experimental/wrangler.jsonc index a648cd20d..0dd99a5f3 100644 --- a/packages/sv/src/cli/tests/snapshots/create-experimental/wrangler.jsonc +++ b/packages/sv/src/cli/tests/snapshots/create-experimental/wrangler.jsonc @@ -1,7 +1,7 @@ { "$schema": "./node_modules/wrangler/config-schema.json", "name": "create-experimental", - "compatibility_date": "2026-06-08", + "compatibility_date": "2020-01-01", "compatibility_flags": [ "nodejs_als" ], From b67ab070ea5aedb689d34263977fb184354b76ad Mon Sep 17 00:00:00 2001 From: jycouet Date: Tue, 9 Jun 2026 07:48:08 +0200 Subject: [PATCH 25/25] format --- packages/sv/src/addons/better-auth.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/sv/src/addons/better-auth.ts b/packages/sv/src/addons/better-auth.ts index a65032300..6549fd5a3 100644 --- a/packages/sv/src/addons/better-auth.ts +++ b/packages/sv/src/addons/better-auth.ts @@ -97,7 +97,10 @@ export default defineAddon({ sv.file('.env.example', generateEnv(demoGithub, true)); const env = defineEnv({ sv, cwd, dependencyVersion }); - env.define({ name: 'ORIGIN', description: 'The app origin (base URL), e.g. `http://localhost:5173`.' }); + env.define({ + name: 'ORIGIN', + description: 'The app origin (base URL), e.g. `http://localhost:5173`.' + }); env.define({ name: 'BETTER_AUTH_SECRET', description: