From c57dc9b104f755042b3bb59afa202c26d409ee17 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 19 Mar 2026 16:35:37 +0800 Subject: [PATCH 1/5] fix: auth integration hardening and test fixes - Use IF NOT EXISTS for SQLite ADD COLUMN in auth migrations (D1 supports it since SQLite 3.35+), matching PostgreSQL behavior - Pre-resolve better-auth client subpaths in Vite SSR dep optimizer via resolve.alias to prevent lazy discovery causing disruptive program reloads on first auth page load with cold cache - Update projects-command test to expect staging.void.app URLs after default auth env was switched to staging in f2a8d68 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/void/src/plugin-routing.ts | 25 +++++++++++++++++++ packages/void/src/runtime/auth-migrations.ts | 2 +- .../test/integration/projects-command.test.ts | 4 +-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/void/src/plugin-routing.ts b/packages/void/src/plugin-routing.ts index a9c582639..4f43bb5fd 100644 --- a/packages/void/src/plugin-routing.ts +++ b/packages/void/src/plugin-routing.ts @@ -246,6 +246,31 @@ export function routingPlugin(root: string, options: RoutingPluginOptions): Plug ); }, config(_, env) { + if (env.command === "serve" && options.isAuthEnabled()) { + // Pre-include better-auth client subpaths in SSR dep optimization + // so Vite doesn't discover them lazily on first auth page load, + // which triggers a disruptive program reload. We resolve absolute + // paths via import.meta.resolve because better-auth is a dep of + // void (not the user's project), so bare specifiers fail under + // pnpm's strict hoisting. + const betterAuthSubpaths = ["client", "react", "vue", "svelte", "solid"]; + const resolvedAliases = betterAuthSubpaths.flatMap((sub) => { + try { + return [{ find: `better-auth/${sub}`, replacement: resolveFile(`better-auth/${sub}`) }]; + } catch { + return []; + } + }); + return { + resolve: { alias: resolvedAliases }, + ssr: { + optimizeDeps: { + include: betterAuthSubpaths.map((sub) => `better-auth/${sub}`), + }, + }, + }; + } + const entries = validateSsrEntries(root); if (!entries || env.command !== "build") return; diff --git a/packages/void/src/runtime/auth-migrations.ts b/packages/void/src/runtime/auth-migrations.ts index a2e1065df..332a5ea00 100644 --- a/packages/void/src/runtime/auth-migrations.ts +++ b/packages/void/src/runtime/auth-migrations.ts @@ -128,7 +128,7 @@ function buildAddColumnSql( fieldName: string, field: VoidAuthMigrationField, ): string { - const ifNotExists = schema.dialect === "postgresql" ? " IF NOT EXISTS" : ""; + const ifNotExists = " IF NOT EXISTS"; return `ALTER TABLE ${quoteIdentifier(tableName)} ADD COLUMN${ifNotExists} ${buildColumnDefinition( schema.dialect, schema, diff --git a/packages/void/test/integration/projects-command.test.ts b/packages/void/test/integration/projects-command.test.ts index 5c2ff63db..89b29fd01 100644 --- a/packages/void/test/integration/projects-command.test.ts +++ b/packages/void/test/integration/projects-command.test.ts @@ -88,9 +88,9 @@ describe("runProjectsCommand", () => { headers: { Authorization: "Bearer test-token" }, }); expect(out).toContain("star-tracker"); - expect(out).toContain("https://star-tracker.void.app"); + expect(out).toContain("https://star-tracker.staging.void.app"); expect(out).toContain("todo-app"); - expect(out).toContain("https://todo-app.void.app"); + expect(out).toContain("https://todo-app.staging.void.app"); }); it("prints a friendly message when there are no projects", async () => { From 0af9eb532f81a8d4d9e062ce5cf756e4d4f37b61 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 19 Mar 2026 16:51:33 +0800 Subject: [PATCH 2/5] fix: warm up Vite SSR dep optimizer before kitchen-sink tests The first request to a cold Vite dev server triggers SSR dependency optimization (drizzle-orm, vue, hono, etc.), causing multiple program reloads that exceed the 10s Playwright timeout. Add a warmup fetch in beforeAll to let the reloads settle before tests run. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/void/test/integration/kitchen-sink.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/void/test/integration/kitchen-sink.test.ts b/packages/void/test/integration/kitchen-sink.test.ts index 1207ae599..073e2777b 100644 --- a/packages/void/test/integration/kitchen-sink.test.ts +++ b/packages/void/test/integration/kitchen-sink.test.ts @@ -64,7 +64,13 @@ describe("playground/kitchen-sink", () => { }); await waitForServer(BASE, 20_000); - }, 30_000); + + // Warm up: the first request triggers Vite SSR dep optimization + // (drizzle-orm, vue, hono, etc.) which causes multiple program + // reloads. Wait for the reloads to settle before running tests. + await fetch(BASE).catch(() => {}); + await new Promise((resolve) => setTimeout(resolve, 5_000)); + }, 40_000); afterAll(async () => { if (child) await killChild(child); From 5d02b804678492e97b15c9c874cb4ed323c54302 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 19 Mar 2026 16:58:23 +0800 Subject: [PATCH 3/5] fix: drop ssr.optimizeDeps.include for better-auth subpaths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The include list caused Vite to attempt pre-bundling the aliased better-auth modules, which involves reading package.json. Since the aliases point to raw .mjs files, Vite tried to JSON-parse JavaScript source, crashing the dev server on CI. The resolve.alias alone is sufficient — it resolves bare specifiers to absolute file paths, preventing lazy SSR dep discovery without needing pre-bundling. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/void/src/plugin-routing.ts | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/void/src/plugin-routing.ts b/packages/void/src/plugin-routing.ts index 4f43bb5fd..2cd8eeb6d 100644 --- a/packages/void/src/plugin-routing.ts +++ b/packages/void/src/plugin-routing.ts @@ -247,12 +247,11 @@ export function routingPlugin(root: string, options: RoutingPluginOptions): Plug }, config(_, env) { if (env.command === "serve" && options.isAuthEnabled()) { - // Pre-include better-auth client subpaths in SSR dep optimization - // so Vite doesn't discover them lazily on first auth page load, - // which triggers a disruptive program reload. We resolve absolute - // paths via import.meta.resolve because better-auth is a dep of - // void (not the user's project), so bare specifiers fail under - // pnpm's strict hoisting. + // Alias better-auth client subpaths to absolute file paths so + // Vite can resolve them during SSR without the bare specifiers + // needing to be hoisted into the user's node_modules (pnpm + // strict mode). Without this, Vite discovers them lazily on + // first auth page load, triggering a disruptive program reload. const betterAuthSubpaths = ["client", "react", "vue", "svelte", "solid"]; const resolvedAliases = betterAuthSubpaths.flatMap((sub) => { try { @@ -261,14 +260,7 @@ export function routingPlugin(root: string, options: RoutingPluginOptions): Plug return []; } }); - return { - resolve: { alias: resolvedAliases }, - ssr: { - optimizeDeps: { - include: betterAuthSubpaths.map((sub) => `better-auth/${sub}`), - }, - }, - }; + return { resolve: { alias: resolvedAliases } }; } const entries = validateSsrEntries(root); From 91ab4cdba4d07c5a703064b3cb345820b25baa4d Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 19 Mar 2026 17:47:25 +0800 Subject: [PATCH 4/5] revert: restore dialect-conditional IF NOT EXISTS for ADD COLUMN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SQLite does not support IF NOT EXISTS on ALTER TABLE ADD COLUMN — only PostgreSQL does. The SQLite path correctly relies on the check-then-act pattern (PRAGMA table_info → skip existing columns) in applySqliteAuthMigrations(). Revert to the original behavior. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/void/src/runtime/auth-migrations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/void/src/runtime/auth-migrations.ts b/packages/void/src/runtime/auth-migrations.ts index 332a5ea00..a2e1065df 100644 --- a/packages/void/src/runtime/auth-migrations.ts +++ b/packages/void/src/runtime/auth-migrations.ts @@ -128,7 +128,7 @@ function buildAddColumnSql( fieldName: string, field: VoidAuthMigrationField, ): string { - const ifNotExists = " IF NOT EXISTS"; + const ifNotExists = schema.dialect === "postgresql" ? " IF NOT EXISTS" : ""; return `ALTER TABLE ${quoteIdentifier(tableName)} ADD COLUMN${ifNotExists} ${buildColumnDefinition( schema.dialect, schema, From e7833c615c4e4a69c8d6a0c168b13dd65f475e37 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 20 Mar 2026 15:57:55 +0800 Subject: [PATCH 5/5] fix: merge defaults in object-form defineAuth config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When defineAuth() received a plain object (not a function), resolveUserConfig returned the user config directly, silently discarding all security defaults — secret, baseURL, basePath, database, and trustedOrigins. The object form now shallow-merges user overrides on top of defaults, matching the safe behavior of the no-config path while preserving the function form's full control. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/void/src/auth/drizzle.ts | 2 +- packages/void/src/runtime/better-auth-shared.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/void/src/auth/drizzle.ts b/packages/void/src/auth/drizzle.ts index 5ffd2fd4f..6cbbb60e5 100644 --- a/packages/void/src/auth/drizzle.ts +++ b/packages/void/src/auth/drizzle.ts @@ -91,7 +91,7 @@ function resolveSchemaUserConfig( }); } - return userConfig; + return { ...defaults, ...userConfig }; } function resolveSchemaOptions( diff --git a/packages/void/src/runtime/better-auth-shared.ts b/packages/void/src/runtime/better-auth-shared.ts index 9ab9070ee..4774a09dc 100644 --- a/packages/void/src/runtime/better-auth-shared.ts +++ b/packages/void/src/runtime/better-auth-shared.ts @@ -179,7 +179,7 @@ function resolveUserConfig( return userConfig({ defaults, request, env, dialect }); } - return userConfig; + return { ...defaults, ...userConfig }; } function assertRuntimeDialect(runtime: VoidAuthRuntimeOptions, dialect: VoidAuthDialect): void {