Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/templates.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import type { Nuxt } from '@nuxt/schema'
import type { ModuleOptions } from './module'
import { addTypeTemplate } from '@nuxt/kit'
import { addTypeTemplate, isNuxtMajorVersion } from '@nuxt/kit'

interface TemplateContext {
nuxt: Nuxt
config: ModuleOptions
}

export function registerTypeTemplates({ nuxt }: TemplateContext) {
// Nuxt 4 augments both 'nitropack' and 'nitropack/types' (matching @nuxt/nitro-server).
// Augmenting only one leaves the other's re-exported NitroRouteConfig/NitroRouteRules
// without our `robots` key, which breaks consumers like @nuxt/kit's extendRouteRules
// that pull the type through both module paths.
const isNuxt4 = Number(nuxt.options.future?.compatibilityVersion) === 4
const nitroModules = isNuxt4 ? ['nitropack', 'nitropack/types'] : ['nitropack']
// Nuxt 4 ships Nitro v3, which exposes `nitropack/types` as a public subpath.
// @nuxt/kit's extendRouteRules unions NitroRouteConfig across both 'nitropack'
// and 'nitropack/types', so augmenting only one leaves the other's re-exported
// interface without our `robots` key.
const nitroModules = isNuxtMajorVersion(4, nuxt)
? ['nitropack', 'nitropack/types']
: ['nitropack']

// Nuxt-only type augmentations (PageMeta)
addTypeTemplate({
Expand Down
14 changes: 14 additions & 0 deletions test/types/templates.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
import type { H3EventContext } from 'h3'
import type { NitroRouteConfig as NitroRouteConfigPack, NitroRouteRules as NitroRouteRulesPack } from 'nitropack'
import type { NitroRouteConfig, NitroRouteRules, NitroRuntimeHooks } from 'nitropack/types'
import type { NuxtConfig } from 'nuxt/schema'
import type { PageMeta } from '#app'
import { extendRouteRules } from '@nuxt/kit'
import { describe, expectTypeOf, it } from 'vitest'
Expand Down Expand Up @@ -65,6 +66,19 @@ describe('nitropack augmentations (cross-module-path)', () => {
})
})

// Regression test for https://github.com/nuxt-modules/robots/issues/299
// `nuxt.config.ts` routeRules are typed via `NuxtConfig['routeRules']`,
// which (in Nuxt 4) resolves NitroRouteConfig through 'nitropack/types'.
// If only 'nitropack' is augmented, declaring `robots` in routeRules fails
// with TS2353 "Object literal may only specify known properties".
describe('NuxtConfig routeRules (issue #299)', () => {
it('NuxtConfig.routeRules accepts a robots-tagged literal', () => {
expectTypeOf<{ '/admin': { robots: false } }>().toExtend<NonNullable<NuxtConfig['routeRules']>>()
expectTypeOf<{ '/private': { robots: 'noindex, nofollow' } }>().toExtend<NonNullable<NuxtConfig['routeRules']>>()
expectTypeOf<{ '/legacy': { robots: { indexable: false, rule: 'noindex' } } }>().toExtend<NonNullable<NuxtConfig['routeRules']>>()
})
})

describe('h3 augmentations', () => {
it('H3EventContext.robots is RobotsContext', () => {
expectTypeOf<H3EventContext['robots']>().toEqualTypeOf<RobotsContext>()
Expand Down
Loading