From 0fd389731e4caa564a976063ead1f886093a6cb6 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Wed, 22 Jul 2026 10:00:20 +0100 Subject: [PATCH 1/2] fix(nuxt): reach app tsconfig with server auto-import types types/evlog-server.d.ts was only registered on the nitro tsconfig context, so `useLogger`/`log`/`createEvlogError` resolved to TS2304 in the app tsconfig project. $fetch's return-type inference imports server route modules directly, pulling their auto-imports into the app project's typecheck too. Register the template on the nuxt context as well. Fixes #435 --- .../fix-nuxt-typecheck-server-globals.md | 5 ++ packages/evlog/src/nuxt/module.ts | 7 +- .../test/nuxt/module-type-templates.test.ts | 71 +++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-nuxt-typecheck-server-globals.md create mode 100644 packages/evlog/test/nuxt/module-type-templates.test.ts diff --git a/.changeset/fix-nuxt-typecheck-server-globals.md b/.changeset/fix-nuxt-typecheck-server-globals.md new file mode 100644 index 00000000..edfe461a --- /dev/null +++ b/.changeset/fix-nuxt-typecheck-server-globals.md @@ -0,0 +1,5 @@ +--- +"evlog": patch +--- + +Fix `nuxt typecheck` failing with `TS2304: Cannot find name 'useLogger'` (and `log`/`createEvlogError`) on server routes. The Nuxt module now registers `types/evlog-server.d.ts` on both the `nitro` and `nuxt` tsconfig contexts — previously it was only added to the server project, but `$fetch`'s return-type inference pulls server routes (and their auto-imports) into the app project's typecheck too. diff --git a/packages/evlog/src/nuxt/module.ts b/packages/evlog/src/nuxt/module.ts index df31f145..2d76b39d 100644 --- a/packages/evlog/src/nuxt/module.ts +++ b/packages/evlog/src/nuxt/module.ts @@ -491,7 +491,12 @@ export {} } export {} `, - }, { nitro: true }) + // `nitro: true` puts the reference on the server tsconfig project. + // `nuxt: true` also puts it on the app tsconfig project — required + // because `$fetch` typings resolve server route return types by + // importing the route modules directly, which pulls server routes + // (and therefore these globals) into the app project's typecheck too. + }, { nitro: true, nuxt: true }) const stripLevels = options.strip ?? ['debug'] if (stripLevels.length > 0) { diff --git a/packages/evlog/test/nuxt/module-type-templates.test.ts b/packages/evlog/test/nuxt/module-type-templates.test.ts new file mode 100644 index 00000000..17fb418d --- /dev/null +++ b/packages/evlog/test/nuxt/module-type-templates.test.ts @@ -0,0 +1,71 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +// Regression test for https://github.com/HugoRCD/evlog/issues/435 +// +// `nuxt typecheck` (vue-tsc -b) type-checks the app tsconfig project *and* +// the server one. `$fetch`'s return-type inference does +// `typeof import('../../server/api/...')`, which pulls every server route +// (and therefore the auto-imported server globals it uses) into the app +// project's typecheck too. The `types/evlog-server.d.ts` type template must +// therefore be registered on both the `nitro` context (server tsconfig) +// and the `nuxt` context (app tsconfig) — registering it on `nitro` alone +// leaves the app project unable to resolve `useLogger`/`log`/ +// `createEvlogError`, causing a `TS2304: Cannot find name` failure. + +const addTypeTemplate = vi.fn() +const addImports = vi.fn() +const addServerImports = vi.fn() +const addServerHandler = vi.fn() +const addServerPlugin = vi.fn() +const addPlugin = vi.fn() +const addVitePlugin = vi.fn() +const createResolver = vi.fn(() => ({ resolve: (path: string) => path })) + +vi.mock('@nuxt/kit', () => ({ + addImports, + addPlugin, + addServerHandler, + addServerImports, + addServerPlugin, + addTypeTemplate, + addVitePlugin, + createResolver, + defineNuxtModule: (definition: { setup: (options: unknown, nuxt: unknown) => unknown }) => definition, +})) + +function makeFakeNuxt() { + return { + hook: vi.fn(), + options: { + dev: false, + runtimeConfig: { + public: {}, + }, + }, + } +} + +describe('nuxt module server type template', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('registers types/evlog-server.d.ts for both the nitro and the app (nuxt) tsconfig projects', async () => { + const moduleDefinition = (await import('../../src/nuxt/module')).default as unknown as { + setup: (options: Record, nuxt: ReturnType) => void + } + + moduleDefinition.setup({}, makeFakeNuxt()) + + const serverTemplateCall = addTypeTemplate.mock.calls.find( + ([template]) => template?.filename === 'types/evlog-server.d.ts', + ) + + expect(serverTemplateCall).toBeDefined() + const [, context] = serverTemplateCall! + // `nitro: true` alone only reaches `.nuxt/tsconfig.server.json` — the app + // project (`.nuxt/tsconfig.app.json`) never sees the declaration and + // `useLogger`/`log`/`createEvlogError` resolve to TS2304 there. + expect(context).toEqual({ nitro: true, nuxt: true }) + }) +}) From 1088880209f0eeaca24a8b226466d122ac8d3b82 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Wed, 22 Jul 2026 10:15:37 +0100 Subject: [PATCH 2/2] fix(nuxt): keep server-only log global out of the app tsconfig project Address CodeRabbit feedback on #437: declaring the server template's `log` global on the nuxt context too collided with the client `log` already declared in evlog-client.d.ts. Nuxt's generated tsconfigs set skipLibCheck: true, so TypeScript silently kept whichever declaration it saw first instead of erroring. Split the server type template: useLogger/createEvlogError stay on both nitro+nuxt contexts (identical types, no collision), while log moves to its own nitro-only template. --- .../fix-nuxt-typecheck-server-globals.md | 2 +- packages/evlog/src/nuxt/module.ts | 24 ++++++- .../test/nuxt/module-type-templates.test.ts | 69 +++++++++++++++---- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/.changeset/fix-nuxt-typecheck-server-globals.md b/.changeset/fix-nuxt-typecheck-server-globals.md index edfe461a..3508d53f 100644 --- a/.changeset/fix-nuxt-typecheck-server-globals.md +++ b/.changeset/fix-nuxt-typecheck-server-globals.md @@ -2,4 +2,4 @@ "evlog": patch --- -Fix `nuxt typecheck` failing with `TS2304: Cannot find name 'useLogger'` (and `log`/`createEvlogError`) on server routes. The Nuxt module now registers `types/evlog-server.d.ts` on both the `nitro` and `nuxt` tsconfig contexts — previously it was only added to the server project, but `$fetch`'s return-type inference pulls server routes (and their auto-imports) into the app project's typecheck too. +Fix `nuxt typecheck` failing with `TS2304: Cannot find name 'useLogger'` (and `createEvlogError`) on server routes. `$fetch`'s return-type inference pulls server routes — and their auto-imported globals — into the app tsconfig project's typecheck too, but the Nuxt module only declared these globals for the server project. `useLogger` and `createEvlogError` are now declared for both projects; the server-only `log` export stays scoped to the server project since it shares its global name with the (differently-typed) client `log`. diff --git a/packages/evlog/src/nuxt/module.ts b/packages/evlog/src/nuxt/module.ts index 2d76b39d..adc38904 100644 --- a/packages/evlog/src/nuxt/module.ts +++ b/packages/evlog/src/nuxt/module.ts @@ -486,7 +486,6 @@ export {} filename: 'types/evlog-server.d.ts', getContents: () => `declare global { const useLogger: typeof import('evlog').useLogger - const log: typeof import('evlog').log const createEvlogError: typeof import('evlog').createEvlogError } export {} @@ -496,8 +495,31 @@ export {} // because `$fetch` typings resolve server route return types by // importing the route modules directly, which pulls server routes // (and therefore these globals) into the app project's typecheck too. + // `createEvlogError` is safe to expose in both contexts: it resolves + // to the same `evlog` export as the one declared in evlog-client.d.ts. + // `log` is intentionally NOT declared here — the server `log` (a + // module-level logger for background/non-request code) and the + // client `log` (declared in evlog-client.d.ts) are different values + // with different types but share the same global name, so it can + // only be safely typed within the project that actually needs it. }, { nitro: true, nuxt: true }) + addTypeTemplate({ + filename: 'types/evlog-server-log.d.ts', + getContents: () => `declare global { + const log: typeof import('evlog').log +} +export {} +`, + // Nitro-only: the app tsconfig project already declares a (different) + // global `log` via evlog-client.d.ts. Referencing this template there + // too would make the two ambient declarations fight over the same + // name — since \`skipLibCheck\` is on by default in Nuxt's generated + // tsconfigs, TypeScript silently keeps whichever declaration it sees + // first instead of erroring, so the losing side's shape goes unused + // without warning. + }, { nitro: true }) + const stripLevels = options.strip ?? ['debug'] if (stripLevels.length > 0) { addVitePlugin(createStripPlugin(stripLevels)) diff --git a/packages/evlog/test/nuxt/module-type-templates.test.ts b/packages/evlog/test/nuxt/module-type-templates.test.ts index 17fb418d..6199dd9f 100644 --- a/packages/evlog/test/nuxt/module-type-templates.test.ts +++ b/packages/evlog/test/nuxt/module-type-templates.test.ts @@ -6,11 +6,21 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' // the server one. `$fetch`'s return-type inference does // `typeof import('../../server/api/...')`, which pulls every server route // (and therefore the auto-imported server globals it uses) into the app -// project's typecheck too. The `types/evlog-server.d.ts` type template must -// therefore be registered on both the `nitro` context (server tsconfig) -// and the `nuxt` context (app tsconfig) — registering it on `nitro` alone -// leaves the app project unable to resolve `useLogger`/`log`/ -// `createEvlogError`, causing a `TS2304: Cannot find name` failure. +// project's typecheck too. `useLogger` and `createEvlogError` must +// therefore be declared on both the `nitro` context (server tsconfig) and +// the `nuxt` context (app tsconfig) — declaring them on `nitro` alone +// leaves the app project unable to resolve them, causing a +// `TS2304: Cannot find name` failure. +// +// `log` is the one exception: it is declared globally by *both* the +// client type template (`typeof import('evlog/client').log`) and the +// server one (`typeof import('evlog').log`) — two different types sharing +// one global name. The app tsconfig project already declares the client +// `log` (evlog-client.d.ts, nuxt-only). If the server `log` were also +// exposed there, the two ambient declarations would collide. Because +// Nuxt's generated tsconfigs set `skipLibCheck: true`, TypeScript doesn't +// error on that collision — it silently keeps whichever declaration it +// resolves first, so the server `log` template must stay nitro-only. const addTypeTemplate = vi.fn() const addImports = vi.fn() @@ -45,27 +55,60 @@ function makeFakeNuxt() { } } -describe('nuxt module server type template', () => { +function findTemplateCall(filename: string) { + return addTypeTemplate.mock.calls.find(([template]) => template?.filename === filename) +} + +describe('nuxt module server type templates', () => { beforeEach(() => { vi.clearAllMocks() }) - it('registers types/evlog-server.d.ts for both the nitro and the app (nuxt) tsconfig projects', async () => { + it('registers useLogger/createEvlogError for both the nitro and the app (nuxt) tsconfig projects', async () => { const moduleDefinition = (await import('../../src/nuxt/module')).default as unknown as { setup: (options: Record, nuxt: ReturnType) => void } moduleDefinition.setup({}, makeFakeNuxt()) - const serverTemplateCall = addTypeTemplate.mock.calls.find( - ([template]) => template?.filename === 'types/evlog-server.d.ts', - ) - + const serverTemplateCall = findTemplateCall('types/evlog-server.d.ts') expect(serverTemplateCall).toBeDefined() - const [, context] = serverTemplateCall! + + const [template, context] = serverTemplateCall! // `nitro: true` alone only reaches `.nuxt/tsconfig.server.json` — the app // project (`.nuxt/tsconfig.app.json`) never sees the declaration and - // `useLogger`/`log`/`createEvlogError` resolve to TS2304 there. + // `useLogger`/`createEvlogError` resolve to TS2304 there. expect(context).toEqual({ nitro: true, nuxt: true }) + + const contents = template.getContents() + expect(contents).toContain('const useLogger: typeof import(\'evlog\').useLogger') + expect(contents).toContain('const createEvlogError: typeof import(\'evlog\').createEvlogError') + // `log` must NOT be declared here — see module comment above. + expect(contents).not.toContain('const log:') + }) + + it('keeps the server-scoped `log` global nitro-only, separate from the client `log`', async () => { + const moduleDefinition = (await import('../../src/nuxt/module')).default as unknown as { + setup: (options: Record, nuxt: ReturnType) => void + } + + moduleDefinition.setup({}, makeFakeNuxt()) + + const serverLogTemplateCall = findTemplateCall('types/evlog-server-log.d.ts') + expect(serverLogTemplateCall).toBeDefined() + + const [template, context] = serverLogTemplateCall! + // Must stay nitro-only: adding `nuxt: true` would make this collide + // with the client `log` declared in evlog-client.d.ts (already + // registered nuxt-only, by default) in the app tsconfig project. + expect(context).toEqual({ nitro: true }) + expect(template.getContents()).toContain('const log: typeof import(\'evlog\').log') + + const clientTemplateCall = findTemplateCall('types/evlog-client.d.ts') + expect(clientTemplateCall).toBeDefined() + const [clientTemplate, clientContext] = clientTemplateCall! + // No explicit context passed → nuxt-only by @nuxt/kit's default. + expect(clientContext).toBeUndefined() + expect(clientTemplate.getContents()).toContain('const log: typeof import(\'evlog/client\').log') }) })