From 822d01388c1be3642ca2adbfc75f999e9fcdd100 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Mon, 26 Oct 2026 11:37:40 +0800 Subject: [PATCH 1/2] fix(auth): match the dev better-auth client aliases exactly With auth on, `vp dev` aliases `better-auth/client`, `/react`, `/vue`, `/svelte` and `/solid` to files in Void's own better-auth. A string alias also matches every deeper path, so `better-auth/client/plugins` (where `adminClient()` and the other client plugins live) became `.../dist/client/index.mjs/plugins`, and apps using a client plugin failed in dev on both the client and SSR. Builds have no alias and were fine. Use an anchored RegExp for each alias, which is Vite's only exact match. The five exact ids resolve as before; deeper subpaths resolve normally. Fixes #565 Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/void/src/plugin-routing.ts | 10 ++++++- .../void/test/unit/plugin-routing.test.ts | 30 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/void/src/plugin-routing.ts b/packages/void/src/plugin-routing.ts index 40858dc3d..f21e06518 100644 --- a/packages/void/src/plugin-routing.ts +++ b/packages/void/src/plugin-routing.ts @@ -667,10 +667,18 @@ export function routingPlugin(root: string, options: RoutingPluginOptions): Plug // 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. + // A string `find` would also match `better-auth/client/plugins` + // and append `/plugins` to the file path; a RegExp is Vite's only + // exact match. const betterAuthSubpaths = ['client', 'react', 'vue', 'svelte', 'solid']; const resolvedAliases = betterAuthSubpaths.flatMap((sub) => { try { - return [{ find: `better-auth/${sub}`, replacement: resolveFile(`better-auth/${sub}`) }]; + return [ + { + find: new RegExp(`^better-auth/${sub}$`), + replacement: resolveFile(`better-auth/${sub}`), + }, + ]; } catch { return []; } diff --git a/packages/void/test/unit/plugin-routing.test.ts b/packages/void/test/unit/plugin-routing.test.ts index 94cd85282..f871f192c 100644 --- a/packages/void/test/unit/plugin-routing.test.ts +++ b/packages/void/test/unit/plugin-routing.test.ts @@ -9,6 +9,7 @@ import { } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { createServer, type ConfigEnv, @@ -724,6 +725,35 @@ describe('routingPlugin', () => { expect(hotUpdate.call({ environment: { name: 'void_worker' } })).toBeUndefined(); }); + it('resolves deeper better-auth subpaths past the dev client aliases', async () => { + const root = createTempRoot(); + writeFileSync(join(root, 'routes', 'health.ts'), 'export const GET = () => new Response();'); + const server = await createServer({ + root, + configFile: false, + logLevel: 'silent', + plugins: [createRoutingPlugin(root, { isAuthEnabled: () => true })], + optimizeDeps: { noDiscovery: true }, + server: { middlewareMode: true, ws: false, watch: null, preTransformRequests: false }, + }); + try { + const resolve = async (specifier: string, importer: string) => + (await server.environments.client.pluginContainer.resolveId(specifier, importer))?.id; + const fileOf = (specifier: string) => fileURLToPath(import.meta.resolve(specifier)); + // The app has no better-auth of its own, so these resolve only through the aliases. + const appFile = join(root, 'routes', 'health.ts'); + for (const specifier of ['better-auth/client', 'better-auth/react']) { + expect(await resolve(specifier, appFile)).toBe(fileOf(specifier)); + } + // A deeper subpath passes the aliases and resolves where better-auth is installed. + expect(await resolve('better-auth/client/plugins', fileURLToPath(import.meta.url))).toBe( + fileOf('better-auth/client/plugins'), + ); + } finally { + await server.close(); + } + }); + it('regenerates auth files when the auth config or a file it imports changes', async () => { // Vite's module graph keys files by real path, as the watcher reports them for a real root; // `.native` also expands Windows short names, as Vite does. From 67d306ea0a4e2642d20b671cf86f8a54e7834301 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Mon, 26 Oct 2026 11:52:54 +0800 Subject: [PATCH 2/2] chore(auth): drop the alias match comment Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/void/src/plugin-routing.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/void/src/plugin-routing.ts b/packages/void/src/plugin-routing.ts index f21e06518..7765ae32f 100644 --- a/packages/void/src/plugin-routing.ts +++ b/packages/void/src/plugin-routing.ts @@ -667,9 +667,6 @@ export function routingPlugin(root: string, options: RoutingPluginOptions): Plug // 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. - // A string `find` would also match `better-auth/client/plugins` - // and append `/plugins` to the file path; a RegExp is Vite's only - // exact match. const betterAuthSubpaths = ['client', 'react', 'vue', 'svelte', 'solid']; const resolvedAliases = betterAuthSubpaths.flatMap((sub) => { try {