From fe0f79a9446fb93e23e2fb56e00585bfbbda3a39 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 27 May 2026 14:21:22 +1000 Subject: [PATCH 1/2] fix: detect Nuxt 4 via isNuxtMajorVersion for nitropack/types augmentation The previous gate relied on `future.compatibilityVersion === 4`, which is unset on most Nuxt 4 projects, so the `nitropack/types` augmentation never ran and `routeRules.robots` failed type-checking with TS2353. --- src/templates.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/templates.ts b/src/templates.ts index 15f9e41f..b0d4ec1b 100644 --- a/src/templates.ts +++ b/src/templates.ts @@ -1,6 +1,6 @@ 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 @@ -8,12 +8,13 @@ interface TemplateContext { } 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({ From ad833e447b04052cccba776983130760c46b89a5 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 27 May 2026 15:28:54 +1000 Subject: [PATCH 2/2] test: assert NuxtConfig.routeRules accepts robots literal Covers the user-facing TS2353 scenario from #299. Existing tests assert augmentation on `nitropack/types` directly; this asserts the full chain through `NuxtConfig['routeRules']` that breaks in user configs. --- test/types/templates.test-d.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/types/templates.test-d.ts b/test/types/templates.test-d.ts index acec9cad..1e58ee4a 100644 --- a/test/types/templates.test-d.ts +++ b/test/types/templates.test-d.ts @@ -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' @@ -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>() + expectTypeOf<{ '/private': { robots: 'noindex, nofollow' } }>().toExtend>() + expectTypeOf<{ '/legacy': { robots: { indexable: false, rule: 'noindex' } } }>().toExtend>() + }) +}) + describe('h3 augmentations', () => { it('H3EventContext.robots is RobotsContext', () => { expectTypeOf().toEqualTypeOf()