From 5f49fd949562fbb206a7cf64fcdd36fd4092ad44 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 27 Oct 2026 14:54:08 +0800 Subject: [PATCH 1/3] fix(auth): load auth.ts through Vite in builds and void prepare Dev already loads auth.ts through a Vite module runner, but `vite build`, `void prepare` and the CLI commands that run it still used a plain Node `import()`. An auth.ts that imports a project file without an extension (`import './db'`) or through a Vite alias (`@schema/...`) failed there with "Cannot find module", even though dev worked. Builds and CLI commands now evaluate auth.ts once with Vite's `runnerImport`, so imports resolve as in dev. A build passes its own resolved aliases and `tsconfigPaths`; `void prepare` reads them from the app's Vite config. The loader also accepted `export const auth` or `export const config`, but the Worker only imports the default export, so the generated schema and the runtime could disagree. It now requires a default export and says to use `export default defineAuth(...)`. Co-Authored-By: Claude Opus 5.5 --- packages/void/src/auth/config.ts | 56 ++++++++++++-- packages/void/src/auth/node.ts | 12 ++- packages/void/src/codegen/prepare.ts | 4 +- packages/void/src/plugin-routing.ts | 10 ++- .../void/test/unit/auth-config-loader.test.ts | 76 ++++++++++++++++--- .../void/test/unit/plugin-routing.test.ts | 38 ++++++++++ 6 files changed, 173 insertions(+), 23 deletions(-) diff --git a/packages/void/src/auth/config.ts b/packages/void/src/auth/config.ts index 181da39d7..c8ed0b2eb 100644 --- a/packages/void/src/auth/config.ts +++ b/packages/void/src/auth/config.ts @@ -1,6 +1,12 @@ import { existsSync, readFileSync } from 'node:fs'; -import { pathToFileURL } from 'node:url'; -import { isRunnableDevEnvironment, parseSync, type ViteDevServer } from 'vite'; +import { + type AliasOptions, + isRunnableDevEnvironment, + loadConfigFromFile, + parseSync, + runnerImport, + type ViteDevServer, +} from 'vite'; import { readProjectPaths, type ProjectPaths } from '../project-paths.ts'; import type { VoidAuthConfig } from '../runtime/auth.ts'; import type { AstNode, AstProgram } from '../shared/ast.ts'; @@ -62,17 +68,53 @@ export function devAuthConfigImporter(server: ViteDevServer): ModuleImporter | u } /** - * Evaluate the auth config. A native `import()` keeps its first evaluation for - * the life of the process, which suits one-off commands; the dev server passes - * its module runner. + * Builds and CLI commands evaluate the auth config once in a throwaway module + * runner, so its imports resolve as in dev: without file extensions and through + * the app's aliases. A native `import()` resolves neither. */ +export function viteAuthConfigImporter( + root: string, + mode: string | undefined, + resolve: { alias?: AliasOptions; tsconfigPaths?: boolean } | undefined, +): ModuleImporter { + return async (path) => { + const { module } = await runnerImport>(path, { + root, + mode, + logLevel: 'silent', + resolve: { alias: resolve?.alias, tsconfigPaths: resolve?.tsconfigPaths }, + }); + return module; + }; +} + +/** The importer for commands outside Vite, with the aliases of the app's Vite config. */ +export function projectAuthConfigImporter(root: string): ModuleImporter { + return async (path) => { + const loaded = await loadConfigFromFile( + { command: 'serve', mode: 'development' }, + undefined, + root, + 'silent', + ); + return viteAuthConfigImporter(root, 'development', loaded?.config.resolve)(path); + }; +} + +/** Evaluate the auth config through the importer of the current command. */ export async function loadVoidAuthConfig( configPath: string | null, - importModule: ModuleImporter = (path) => import(pathToFileURL(path).href), + importModule: ModuleImporter, ): Promise { if (!configPath) { return undefined; } const mod = await importModule(configPath); - return (mod.default ?? mod.auth ?? mod.config) as VoidAuthConfig | undefined; + // The Worker imports only the default export. + if (!Object.hasOwn(mod, 'default')) { + throw new Error( + `${configPath} has no default export. Export the auth config with \`export default defineAuth(...)\`.`, + ); + } + return mod.default as VoidAuthConfig | undefined; } diff --git a/packages/void/src/auth/node.ts b/packages/void/src/auth/node.ts index c739401ec..4ce1e442e 100644 --- a/packages/void/src/auth/node.ts +++ b/packages/void/src/auth/node.ts @@ -11,7 +11,12 @@ import { } from '../runtime/auth-migrations.ts'; import type { VoidAuthDialect, VoidAuthEnv } from '../runtime/better-auth-shared.ts'; import { loadLocalEnv } from '../shared/env.ts'; -import { findVoidAuthConfig, loadVoidAuthConfig, type ModuleImporter } from './config.ts'; +import { + findVoidAuthConfig, + loadVoidAuthConfig, + projectAuthConfigImporter, + type ModuleImporter, +} from './config.ts'; import { resolveVoidAuthMigrationSchema } from './drizzle.ts'; export async function runLocalVoidAuthMigrations( @@ -28,7 +33,10 @@ export async function runLocalVoidAuthMigrations( /** The dev server's runner, so a restarted server sees the current config. */ importModule?: ModuleImporter, ): Promise { - const authConfig = await loadVoidAuthConfig(configPath, importModule); + const authConfig = await loadVoidAuthConfig( + configPath, + importModule ?? projectAuthConfigImporter(root), + ); const runtime = { dialect, providers: readConfig(root).auth?.providers ?? null, diff --git a/packages/void/src/codegen/prepare.ts b/packages/void/src/codegen/prepare.ts index 0c3b80cdf..e6e780c37 100644 --- a/packages/void/src/codegen/prepare.ts +++ b/packages/void/src/codegen/prepare.ts @@ -2,7 +2,7 @@ import { existsSync, rmSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { join } from 'pathslash'; import { analyzeApplication } from '../application-routing.ts'; -import { loadVoidAuthConfig } from '../auth/config.ts'; +import { loadVoidAuthConfig, projectAuthConfigImporter } from '../auth/config.ts'; import { generateVoidAuthDatabaseModule, generateVoidAuthDrizzleSchema } from '../auth/drizzle.ts'; import { getDatabaseCasing, readConfig } from '../config.ts'; import { discoverSchema } from '../drizzle/discover.ts'; @@ -88,7 +88,7 @@ export async function prepareProject(root: string): Promise; const schemaPath = join(outDir, 'better-auth-schema.ts'); diff --git a/packages/void/src/plugin-routing.ts b/packages/void/src/plugin-routing.ts index e39a5b581..9860cd858 100644 --- a/packages/void/src/plugin-routing.ts +++ b/packages/void/src/plugin-routing.ts @@ -23,7 +23,11 @@ import { isGeneratedAssetHeaders, resolveAssetWorkerBypass, } from './asset-worker-bypass.ts'; -import { AUTH_CONFIG_ENVIRONMENT, loadVoidAuthConfig } from './auth/config.ts'; +import { + AUTH_CONFIG_ENVIRONMENT, + loadVoidAuthConfig, + viteAuthConfigImporter, +} from './auth/config.ts'; import { resolveDistDir, resolveWorkerDirName } from './cli/build-output-dir.ts'; import { lintRoutingPhases, mergeRoutingRules } from './cli/headers.ts'; import { stampClientAssetCachePolicy } from './client-asset-manifest.ts'; @@ -342,7 +346,9 @@ export function routingPlugin(root: string, options: RoutingPluginOptions): Plug try { return await loadVoidAuthConfig( configPath, - environment && ((path) => environment.runner.import(path)), + environment + ? (path) => environment.runner.import(path) + : viteAuthConfigImporter(root, resolvedConfig?.mode, resolvedConfig?.resolve), ); } catch (error) { authConfigStale = true; diff --git a/packages/void/test/unit/auth-config-loader.test.ts b/packages/void/test/unit/auth-config-loader.test.ts index 5b89442b6..0925167c8 100644 --- a/packages/void/test/unit/auth-config-loader.test.ts +++ b/packages/void/test/unit/auth-config-loader.test.ts @@ -1,12 +1,15 @@ -import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { createRunnableDevEnvironment, createServer } from 'vite'; import { afterEach, describe, expect, it, vi } from 'vite-plus/test'; import { AUTH_CONFIG_ENVIRONMENT, devAuthConfigImporter, loadVoidAuthConfig, + projectAuthConfigImporter, } from '../../src/auth/config.ts'; const tempDirs: Array = []; @@ -30,18 +33,71 @@ describe('loadVoidAuthConfig', () => { expect(importModule).not.toHaveBeenCalled(); }); - it.each([ - ['default', "export default { marker: 'default' };", 'default'], - ['auth', "export const auth = { marker: 'auth' };", 'auth'], - ['config', "export const config = { marker: 'config' };", 'config'], - ])('reads the %s export', async (_name, source, marker) => { - const path = join(createTempDir(), 'auth.ts'); - writeFileSync(path, source); - expect(await loadVoidAuthConfig(path)).toEqual({ marker }); + it('reads the default export', async () => { + const dir = createTempDir(); + const path = join(dir, 'auth.ts'); + writeFileSync(path, "export default { marker: 'default' };"); + expect(await loadVoidAuthConfig(path, projectAuthConfigImporter(dir))).toEqual({ + marker: 'default', + }); + }); + + // The Worker imports only the default export. + it.each(['auth', 'config'])('rejects a config with only an `%s` export', async (name) => { + const dir = createTempDir(); + const path = join(dir, 'auth.ts'); + writeFileSync(path, `export const ${name} = { marker: 'named' };`); + await expect(loadVoidAuthConfig(path, projectAuthConfigImporter(dir))).rejects.toThrow( + `${path} has no default export. Export the auth config with \`export default defineAuth(...)\`.`, + ); }); + // Vitest resolves the native `import()` itself; plain Node is what `void prepare` runs in. + it('resolves extensionless and Vite alias imports in void prepare', () => { + const root = createTempDir(); + mkdirSync(join(root, 'lib')); + mkdirSync(join(root, 'fields')); + writeFileSync( + join(root, 'vite.config.ts'), + `export default { resolve: { alias: { '@fields': ${JSON.stringify(join(root, 'fields'))} } } };\n`, + ); + writeFileSync( + join(root, 'auth.ts'), + [ + "import type { VoidAuthConfig } from 'void/auth';", + "import { nickname } from './lib/nickname';", + "import { pronouns } from '@fields/pronouns';", + 'export default { user: { additionalFields: { nickname, pronouns } } } satisfies VoidAuthConfig;', + ].join('\n'), + ); + writeFileSync( + join(root, 'lib', 'nickname.ts'), + "export const nickname = { type: 'string', required: false } as const;", + ); + writeFileSync( + join(root, 'fields', 'pronouns.ts'), + "export const pronouns = { type: 'string', required: false } as const;", + ); + + execFileSync( + process.execPath, + [ + '--input-type=module', + '-e', + 'const { prepareProject } = await import(process.argv[1]); await prepareProject(process.argv[2]);', + fileURLToPath(new URL('../../src/codegen/prepare.ts', import.meta.url)), + root, + ], + { stdio: 'pipe' }, + ); + + const schema = readFileSync(join(root, '.void', 'better-auth-schema.ts'), 'utf8'); + expect(schema).toContain('nickname'); + expect(schema).toContain('pronouns'); + }, 30_000); + it('evaluates the config through the module importer it is given', async () => { - const importModule = vi.fn(async () => ({ auth: { marker: 'runner' } })); + const importModule = vi.fn(async () => ({ default: { marker: 'runner' } })); expect(await loadVoidAuthConfig('/app/auth.ts', importModule)).toEqual({ marker: 'runner' }); expect(importModule).toHaveBeenCalledWith('/app/auth.ts'); }); diff --git a/packages/void/test/unit/plugin-routing.test.ts b/packages/void/test/unit/plugin-routing.test.ts index 51fd7cd82..32b8607ef 100644 --- a/packages/void/test/unit/plugin-routing.test.ts +++ b/packages/void/test/unit/plugin-routing.test.ts @@ -174,6 +174,44 @@ describe('routingPlugin', () => { ); }); + it('resolves extensionless and Vite alias imports in auth.ts during a build', async () => { + const root = createTempRoot(); + mkdirSync(join(root, 'lib')); + mkdirSync(join(root, 'fields')); + writeFileSync( + join(root, 'auth.ts'), + [ + "import type { VoidAuthConfig } from 'void/auth';", + "import { nickname } from './lib/nickname';", + "import { pronouns } from '@fields/pronouns';", + 'export default { user: { additionalFields: { nickname, pronouns } } } satisfies VoidAuthConfig;', + ].join('\n'), + ); + writeFileSync( + join(root, 'lib', 'nickname.ts'), + "export const nickname = { type: 'string', required: false } as const;", + ); + writeFileSync( + join(root, 'fields', 'pronouns.ts'), + "export const pronouns = { type: 'string', required: false } as const;", + ); + + const plugin = createRoutingPlugin(root, { isAuthEnabled: () => true }); + (getHookHandler(plugin.configResolved) as (config: ResolvedConfig) => void)({ + build: { assetsDir: 'assets' }, + command: 'build', + mode: 'production', + plugins: [], + root, + resolve: { alias: [{ find: '@fields', replacement: join(root, 'fields') }] }, + } as unknown as ResolvedConfig); + await runBuildStart(plugin); + + const schema = readFileSync(join(root, '.void', 'better-auth-schema.ts'), 'utf8'); + expect(schema).toContain('nickname'); + expect(schema).toContain('pronouns'); + }); + it('bakes explicit page opt-outs into the native ISR policy', async () => { const root = createTempRoot(); mkdirSync(join(root, 'pages'), { recursive: true }); From 7e8d32aca88a52adbb6f096cedb10a6848f76e88 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 27 Oct 2026 15:16:16 +0800 Subject: [PATCH 2/3] fix(auth): resolve plugin aliases in auth.ts during void prepare `void prepare`, `void gen` and `void db generate` read only the raw vite.config to find aliases for auth.ts. Aliases that plugins add in their `config()` hook were missing, such as Void's own `@schema`, so an auth.ts with `import { ... } from '@schema'` failed with "Cannot find module '@schema'" there, while dev and build worked. These commands now resolve the app's Vite config with its plugins and use its aliases. When the dev server has no auth config environment, the migration fallback uses the server's resolved aliases too. Co-Authored-By: Claude Opus 5.5 --- packages/void/src/auth/config.ts | 29 +++++++++------- .../void/test/unit/auth-config-loader.test.ts | 34 +++++++++++++++---- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/packages/void/src/auth/config.ts b/packages/void/src/auth/config.ts index c8ed0b2eb..2facce4f7 100644 --- a/packages/void/src/auth/config.ts +++ b/packages/void/src/auth/config.ts @@ -2,8 +2,8 @@ import { existsSync, readFileSync } from 'node:fs'; import { type AliasOptions, isRunnableDevEnvironment, - loadConfigFromFile, parseSync, + resolveConfig, runnerImport, type ViteDevServer, } from 'vite'; @@ -59,12 +59,18 @@ export const AUTH_CONFIG_ENVIRONMENT = 'void_auth_config'; export type ModuleImporter = (path: string) => Promise>; -/** The dev server's importer for the auth config, when it has the environment. */ -export function devAuthConfigImporter(server: ViteDevServer): ModuleImporter | undefined { +/** + * The dev server's importer for the auth config: its auth config environment, or + * a one-off runner with the server's aliases when it has no such environment. + */ +export function devAuthConfigImporter(server: ViteDevServer): ModuleImporter { const environment = server.environments[AUTH_CONFIG_ENVIRONMENT]; return environment && isRunnableDevEnvironment(environment) ? (path) => environment.runner.import(path) - : undefined; + : (path) => { + const { root, mode, resolve } = server.config; + return viteAuthConfigImporter(root, mode, resolve)(path); + }; } /** @@ -88,16 +94,15 @@ export function viteAuthConfigImporter( }; } -/** The importer for commands outside Vite, with the aliases of the app's Vite config. */ +/** + * The importer for commands outside Vite. It resolves the app's Vite config with + * its plugins, because plugins add aliases in their `config()` hook, such as + * Void's `@schema`. + */ export function projectAuthConfigImporter(root: string): ModuleImporter { return async (path) => { - const loaded = await loadConfigFromFile( - { command: 'serve', mode: 'development' }, - undefined, - root, - 'silent', - ); - return viteAuthConfigImporter(root, 'development', loaded?.config.resolve)(path); + const config = await resolveConfig({ root, mode: 'development', logLevel: 'silent' }, 'serve'); + return viteAuthConfigImporter(root, config.mode, config.resolve)(path); }; } diff --git a/packages/void/test/unit/auth-config-loader.test.ts b/packages/void/test/unit/auth-config-loader.test.ts index 0925167c8..d0f0b3b05 100644 --- a/packages/void/test/unit/auth-config-loader.test.ts +++ b/packages/void/test/unit/auth-config-loader.test.ts @@ -53,13 +53,23 @@ describe('loadVoidAuthConfig', () => { }); // Vitest resolves the native `import()` itself; plain Node is what `void prepare` runs in. - it('resolves extensionless and Vite alias imports in void prepare', () => { + // `@schema` comes from a plugin's `config()` hook, as Void's drizzle plugin adds it. + it('resolves extensionless, config alias and plugin alias imports in void prepare', () => { const root = createTempDir(); mkdirSync(join(root, 'lib')); mkdirSync(join(root, 'fields')); + mkdirSync(join(root, 'db')); writeFileSync( join(root, 'vite.config.ts'), - `export default { resolve: { alias: { '@fields': ${JSON.stringify(join(root, 'fields'))} } } };\n`, + [ + 'export default {', + ` resolve: { alias: { '@fields': ${JSON.stringify(join(root, 'fields'))} } },`, + ' plugins: [{', + " name: 'schema-alias',", + ` config: () => ({ resolve: { alias: { '@schema': ${JSON.stringify(join(root, 'db', 'schema.ts'))} } } }),`, + ' }],', + '};', + ].join('\n'), ); writeFileSync( join(root, 'auth.ts'), @@ -67,7 +77,8 @@ describe('loadVoidAuthConfig', () => { "import type { VoidAuthConfig } from 'void/auth';", "import { nickname } from './lib/nickname';", "import { pronouns } from '@fields/pronouns';", - 'export default { user: { additionalFields: { nickname, pronouns } } } satisfies VoidAuthConfig;', + "import { website } from '@schema';", + 'export default { user: { additionalFields: { nickname, pronouns, website } } } satisfies VoidAuthConfig;', ].join('\n'), ); writeFileSync( @@ -78,6 +89,10 @@ describe('loadVoidAuthConfig', () => { join(root, 'fields', 'pronouns.ts'), "export const pronouns = { type: 'string', required: false } as const;", ); + writeFileSync( + join(root, 'db', 'schema.ts'), + "export const website = { type: 'string', required: false } as const;", + ); execFileSync( process.execPath, @@ -94,6 +109,7 @@ describe('loadVoidAuthConfig', () => { const schema = readFileSync(join(root, '.void', 'better-auth-schema.ts'), 'utf8'); expect(schema).toContain('nickname'); expect(schema).toContain('pronouns'); + expect(schema).toContain('website'); }, 30_000); it('evaluates the config through the module importer it is given', async () => { @@ -102,7 +118,7 @@ describe('loadVoidAuthConfig', () => { expect(importModule).toHaveBeenCalledWith('/app/auth.ts'); }); - it("imports through the dev server's auth config environment when it has one", async () => { + it("imports through the dev server's auth config environment, or with its aliases", async () => { const dir = createTempDir(); const path = join(dir, 'auth.ts'); writeFileSync(path, "export default { marker: 'first' };"); @@ -120,19 +136,25 @@ describe('loadVoidAuthConfig', () => { }, }); try { - const importModule = devAuthConfigImporter(server)!; + const importModule = devAuthConfigImporter(server); expect(await loadVoidAuthConfig(path, importModule)).toEqual({ marker: 'first' }); } finally { await server.close(); } + mkdirSync(join(dir, 'fields')); + writeFileSync(join(dir, 'fields', 'marker.ts'), "export const marker = 'alias';"); + writeFileSync(path, "import { marker } from '@fields/marker';\nexport default { marker };"); const plain = await createServer({ root: dir, configFile: false, logLevel: 'silent', + resolve: { alias: { '@fields': join(dir, 'fields') } }, server: { middlewareMode: true, ws: false, watch: null }, }); try { - expect(devAuthConfigImporter(plain)).toBeUndefined(); + expect(await loadVoidAuthConfig(path, devAuthConfigImporter(plain))).toEqual({ + marker: 'alias', + }); } finally { await plain.close(); } From 106d132575c3bc68070cec9b371374f97d48ccaf Mon Sep 17 00:00:00 2001 From: LongYinan Date: Tue, 27 Oct 2026 16:05:10 +0800 Subject: [PATCH 3/3] fix(auth): keep void prepare from loading the app's Vite config `void prepare`, `void gen` and `void db generate` loaded the app's vite.config to find aliases for auth.ts. Evaluating it constructs the app's plugins, and Void's plugin writes generated files and Cloudflare config when constructed, so preparing types could change the project. `void prepare` must not boot the app's Vite. These commands now evaluate auth.ts without the app's Vite config. Imports still resolve without extensions, through tsconfig `paths`, and through Void's own `@schema` alias. An alias set only in vite.config resolves in dev and builds. Co-Authored-By: Claude Opus 5.5 --- packages/void/src/auth/config.ts | 19 +++++++++------ .../void/test/unit/auth-config-loader.test.ts | 23 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/void/src/auth/config.ts b/packages/void/src/auth/config.ts index 2facce4f7..9dd85ea06 100644 --- a/packages/void/src/auth/config.ts +++ b/packages/void/src/auth/config.ts @@ -3,10 +3,10 @@ import { type AliasOptions, isRunnableDevEnvironment, parseSync, - resolveConfig, runnerImport, type ViteDevServer, } from 'vite'; +import { discoverSchema } from '../drizzle/discover.ts'; import { readProjectPaths, type ProjectPaths } from '../project-paths.ts'; import type { VoidAuthConfig } from '../runtime/auth.ts'; import type { AstNode, AstProgram } from '../shared/ast.ts'; @@ -95,14 +95,19 @@ export function viteAuthConfigImporter( } /** - * The importer for commands outside Vite. It resolves the app's Vite config with - * its plugins, because plugins add aliases in their `config()` hook, such as - * Void's `@schema`. + * The importer for commands outside Vite, such as `void prepare`. It does not load + * the app's Vite config: evaluating it constructs the app's plugins, and Void's + * write generated files and Cloudflare config when constructed. Imports resolve + * without extensions, through tsconfig `paths`, and through Void's `@schema` + * alias; an alias set only in vite.config resolves in dev and builds. */ export function projectAuthConfigImporter(root: string): ModuleImporter { - return async (path) => { - const config = await resolveConfig({ root, mode: 'development', logLevel: 'silent' }, 'serve'); - return viteAuthConfigImporter(root, config.mode, config.resolve)(path); + return (path) => { + const schema = discoverSchema(root); + return viteAuthConfigImporter(root, 'development', { + alias: schema ? { '@schema': schema.path } : undefined, + tsconfigPaths: true, + })(path); }; } diff --git a/packages/void/test/unit/auth-config-loader.test.ts b/packages/void/test/unit/auth-config-loader.test.ts index d0f0b3b05..175422781 100644 --- a/packages/void/test/unit/auth-config-loader.test.ts +++ b/packages/void/test/unit/auth-config-loader.test.ts @@ -1,5 +1,5 @@ import { execFileSync } from 'node:child_process'; -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -53,24 +53,26 @@ describe('loadVoidAuthConfig', () => { }); // Vitest resolves the native `import()` itself; plain Node is what `void prepare` runs in. - // `@schema` comes from a plugin's `config()` hook, as Void's drizzle plugin adds it. - it('resolves extensionless, config alias and plugin alias imports in void prepare', () => { + // `void prepare` boots no Vite: it never evaluates vite.config, whose plugins write files + // when constructed. `@fields` comes from tsconfig `paths`, `@schema` from Void's own alias. + it('resolves extensionless, tsconfig paths and @schema imports in void prepare without vite.config', () => { const root = createTempDir(); mkdirSync(join(root, 'lib')); mkdirSync(join(root, 'fields')); mkdirSync(join(root, 'db')); + const marker = join(root, 'vite-config-loaded'); writeFileSync( join(root, 'vite.config.ts'), [ - 'export default {', - ` resolve: { alias: { '@fields': ${JSON.stringify(join(root, 'fields'))} } },`, - ' plugins: [{', - " name: 'schema-alias',", - ` config: () => ({ resolve: { alias: { '@schema': ${JSON.stringify(join(root, 'db', 'schema.ts'))} } } }),`, - ' }],', - '};', + "import { writeFileSync } from 'node:fs';", + `writeFileSync(${JSON.stringify(marker)}, '');`, + 'export default {};', ].join('\n'), ); + writeFileSync( + join(root, 'tsconfig.json'), + JSON.stringify({ compilerOptions: { paths: { '@fields/*': ['./fields/*'] } } }), + ); writeFileSync( join(root, 'auth.ts'), [ @@ -110,6 +112,7 @@ describe('loadVoidAuthConfig', () => { expect(schema).toContain('nickname'); expect(schema).toContain('pronouns'); expect(schema).toContain('website'); + expect(existsSync(marker)).toBe(false); }, 30_000); it('evaluates the config through the module importer it is given', async () => {