diff --git a/packages/script/src/runtime/types.ts b/packages/script/src/runtime/types.ts index 9cd282bf..08c30e77 100644 --- a/packages/script/src/runtime/types.ts +++ b/packages/script/src/runtime/types.ts @@ -253,7 +253,7 @@ export type BuiltInRegistryScriptKey */ export type RegistryScriptKey = Exclude -type RegistryConfigInput = [T] extends [true] ? Record : T +type RegistryConfigInput = 0 extends 1 & T ? Record : [T] extends [true] ? Record : T export type NuxtConfigScriptRegistryEntry = true | false | 'mock' | (RegistryConfigInput & { trigger?: NuxtUseScriptOptionsSerializable['trigger'] | false, proxy?: boolean, bundle?: boolean, partytown?: boolean, privacy?: ProxyPrivacyInput }) diff --git a/test/types/types.test-d.ts b/test/types/types.test-d.ts index 39e7f579..155f9bac 100644 --- a/test/types/types.test-d.ts +++ b/test/types/types.test-d.ts @@ -1,13 +1,7 @@ import type { ModuleOptions } from '../../packages/script/src/module' import type { CrispApi } from '../../packages/script/src/runtime/registry/crisp' import type { DefaultEventName } from '../../packages/script/src/runtime/registry/google-analytics' -import type { - NuxtConfigScriptRegistry, - NuxtUseScriptOptions, - RegistryScriptInput, - ScriptRegistry, - UseScriptContext, -} from '../../packages/script/src/runtime/types' +import type { NuxtConfigScriptRegistry, NuxtConfigScriptRegistryEntry, NuxtUseScriptOptions, RegistryScriptInput, ScriptRegistry, UseScriptContext } from '../../packages/script/src/runtime/types' import { describe, expectTypeOf, it } from 'vitest' describe('module options registry', () => { @@ -78,6 +72,17 @@ describe('module options registry', () => { // Unknown keys fall through to the index signature (any), so custom scripts work expectTypeOf().toBeAny() }) + + // Issue #700: NuxtConfigScriptRegistryEntry must not collapse to Record + // This happens when Nuxt's $production/$development wraps the config in DeepPartial, + // collapsing the interface's index signature priority and resolving all keys to `any`. + it('NuxtConfigScriptRegistryEntry allows arbitrary properties', () => { + type Entry = Exclude, boolean | 'mock'> + // Must not be never (would mean Record killed the intersection) + expectTypeOf().not.toBeNever() + // Arbitrary properties must be assignable, not `never` + expectTypeOf<{ matomoUrl: string, siteId: number }>().toMatchTypeOf() + }) }) describe('registry api types', () => {